blob: 9ee640d1be8a84f22cbbef44629c59938cdc0a63 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// channeld.c
2//
3// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
4//
5
6#pragma strong_types
7#pragma no_shadow // keine Shadowing...
8#pragma no_clone
9#pragma no_inherit
10#pragma save_types
11
12#include <sys_debug.h>
13#include <lpctypes.h>
14#include <wizlevels.h>
Arathorn78c08372019-12-11 20:14:23 +010015#include <regexp.h>
MG Mud User88f12472016-06-24 23:31:02 +020016
17#include <properties.h>
18#include <config.h>
19#include <language.h>
20
21#define NEED_PROTOTYPES
22#include "channel.h"
23
Arathorn78c08372019-12-11 20:14:23 +010024#define CHANNEL_SAVE "/p/daemon/save/channeld"
25#define MEMORY "/secure/memory"
26#define MAX_HIST_SIZE 200
27#define MAX_CHANNELS 90
Zesstra6ddbacb2020-08-13 21:43:28 +020028// Default-Zeit (in Sekunden), die eine Ebene und ihr History inaktiv sein darf,
29// bevor sie expired wird. Das reduziert sich jedoch, falls es zuviele
30// inaktive Ebenen gibt. Entspricht 30 Tagen.
31#define INACTIVE_EXPIRE 2592000
32// Aber eine Ebene darf min. solange inaktiv sein, bevor sie geloescht wird
33#define MIN_INACTIVE_LIFETIME 3600
34// max. Anzahl der inaktiven Ebenen. Wenn die Haelfte davon ueberschritten
35// wird, wird mit zunehmend kleinerem CHANNEL_EXPIRE gearbeitet.
36#define MAX_INACTIVE_CHANNELS 500
Arathorn78c08372019-12-11 20:14:23 +010037#define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
MG Mud User88f12472016-06-24 23:31:02 +020038
Arathorn78c08372019-12-11 20:14:23 +010039
Zesstrab7720dc2020-08-11 22:14:18 +020040// Datenstrukturen fuer die Ebenen.
41// Basisdaten, welche auch inaktive Ebenen in channelC haben
42struct channel_base_s {
43 string name; // readable channelname, case-sensitive
44 string|closure desc; // stat. oder dyn. Beschreibung
Zesstra66f801d2020-09-24 21:00:13 +020045 string creator; // Ersteller der Ebene (Objektname), Besitzer
Zesstrad9ec04b2020-08-11 23:47:03 +020046 int flags; // Flags, die bestimmtes Verhalten steuern.
Zesstrab7720dc2020-08-11 22:14:18 +020047};
48
49// Basisdaten + die von aktiven Ebenen
50struct channel_s (channel_base_s) {
Zesstra9359fab2020-08-13 12:03:01 +020051 object supervisor; // aktueller Supervisor der Ebene
52 closure access_cl; // Closure fuer Zugriffsrechtepruefung
53 object *members; // Zuhoerer der Ebene
Zesstrab7720dc2020-08-11 22:14:18 +020054};
55
56/* Ebenenliste und die zugehoerigen Daten in struct (<channel>).
57 channels = ([string channelname : (<channel_s>) ])
Zesstra2aeb6a82020-08-13 23:50:36 +020058// HINWEIS: Bitte beachten, dass channels immer nur so manipuliert werden
59// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
60// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
61// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
62// Referenz in Memory und Channeld dieselbe ist.
Zesstra78310012020-08-09 12:21:48 +020063 */
Zesstra2aeb6a82020-08-13 23:50:36 +020064private nosave mapping channels;
Arathorn19459eb2019-11-30 00:45:51 +010065
Arathorn78c08372019-12-11 20:14:23 +010066/* Ebenenhistory
67 mapping channelH = ([ string channelname : ({ ({string channelname,
68 string sender,
69 string msg,
70 int msg_type}) }) ]) */
71// channelH wird in create() geeignet initialisiert
72// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
73// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
74// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
75// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
76// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020077private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010078
Arathorn78c08372019-12-11 20:14:23 +010079/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +020080 mapping channelC = ([ string channelname : (<channel_base_s>);
81 int time() ])
82 Der Zeitstempel ist die letzte Aenderung, d.h. in der Regel des Ablegens in
83 channelC.
84 */
85private mapping channelC = ([:2]);
Arathorn19459eb2019-11-30 00:45:51 +010086
Arathorn78c08372019-12-11 20:14:23 +010087/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
88 mapping channelB = ([ string playername : string* banned_command ]) */
89private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020090
Arathorn78c08372019-12-11 20:14:23 +010091/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
92 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
93 kann.
94
Arathorn19459eb2019-11-30 00:45:51 +010095 mapping Tcmd = ([ "lag": int timestamp,
96 "uptime": int timestamp,
97 "statistik": int timestamp]) */
98private mapping Tcmd = ([]);
99
Zesstra2aeb6a82020-08-13 23:50:36 +0200100/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
101 zerstoerte Ebenen.
102 mapping stats = ([ "time" : int object_time(),
103 "boot" : string getuid(previous_object()),
104 "new" : int total_channels_created,
105 "disposed" : int total_channels_removed ]) */
106// stats wird in create() geeignet initialisiert
107private nosave mapping stats;
108
Arathorn78c08372019-12-11 20:14:23 +0100109/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
110 Speicherevent das Savefile geschrieben werden soll.
111 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +0200112private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +0200113
Zesstra46c564e2020-09-11 21:52:29 +0200114// Private Prototypen
115public int join(string chname, object joining);
116
MG Mud User88f12472016-06-24 23:31:02 +0200117
Arathorn78c08372019-12-11 20:14:23 +0100118/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
119// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
120private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200121{
Arathorn78c08372019-12-11 20:14:23 +0100122 object* userlist = ({});
Zesstrab7720dc2020-08-11 22:14:18 +0200123 foreach(string ch_name, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +0100124 {
Zesstrab7720dc2020-08-11 22:14:18 +0200125 userlist += ch.members;
Arathorn78c08372019-12-11 20:14:23 +0100126 }
127 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
128 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200129}
130
Arathorn78c08372019-12-11 20:14:23 +0100131// Ist das Objekt <sender> Abonnent der Ebene <ch>?
Zesstrab7720dc2020-08-11 22:14:18 +0200132private int IsChannelMember(struct channel_s ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200133{
Zesstrab7720dc2020-08-11 22:14:18 +0200134 return (member(ch.members, sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200135}
136
Arathorn78c08372019-12-11 20:14:23 +0100137// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
138private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200139{
Arathorn78c08372019-12-11 20:14:23 +0100140 if (objectp(ob))
141 ob = getuid(ob);
142 return(pointerp(channelB[ob]) &&
143 member(channelB[ob], command) != -1);
144}
MG Mud User88f12472016-06-24 23:31:02 +0200145
Arathorn78c08372019-12-11 20:14:23 +0100146private void banned(string plname, string* cmds, string res)
147{
148 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
149}
150
151#define TIMEOUT (time() - 60)
152
153// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
154// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
155// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
156// zurueckliegt, als "nicht gesperrt".
157private int IsNotBlocked(string* cmd)
158{
159 string* res = filter(cmd, function int (string str) {
160 return (Tcmd[str] < TIMEOUT);
161 });
162 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
163 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
164 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
165 if (sizeof(res) == sizeof(cmd)) {
166 foreach(string str : cmd) {
167 Tcmd[str] = time();
168 }
169 return 1;
170 }
171 return 0;
172}
173
174// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
175// zugelassen wird. Anforderungen:
176// 1) <cmd> muss Teilstring von <check> sein
177// 2) <cmd> muss am Anfang von <check> stehen
178// 3) <cmd> darf nicht laenger sein als <check>
179// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
180// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
181// cmd = "statistiker" oder cmd = "tist"
182// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
183private int IsValidChannelCommand(string cmd, string check) {
184 // Syntaxcheck (prueft Bedingungen 1 bis 3).
185 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
186 string* cmd_to_check;
187 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
188 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
189 // geben.
190 if ( cmd == "lust" )
191 cmd_to_check = ({"lag", "statistik", "uptime"});
192 else
193 cmd_to_check = ({cmd});
194 // Prueft die Zeitsperre (Bedingung 4).
195 return (IsNotBlocked(cmd_to_check));
196 }
197 return 0;
198}
199
200#define CH_NAME 0
201#define CH_SENDER 1
202#define CH_MSG 2
203#define CH_MSG_TYPE 3
204// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
205// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
206// und dieses per Callout an send() uebergeben.
Zesstrab7720dc2020-08-11 22:14:18 +0200207// Argument: ({channel.name, object pl, string msg, int type})
Arathorn78c08372019-12-11 20:14:23 +0100208// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
209// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
210// was aber bei einer private oder protected Funktion nicht moeglich waere.
211public void ChannelMessage(<string|object|int>* msg)
212{
213 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
214 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
215 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
216 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100217 return;
MG Mud User88f12472016-06-24 23:31:02 +0200218
Arathorn78c08372019-12-11 20:14:23 +0100219 float* lag;
220 int max, rekord;
221 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200222 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200223
Arathorn78c08372019-12-11 20:14:23 +0100224 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200225 {
Arathorn78c08372019-12-11 20:14:23 +0100226 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
227 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100228 }
Arathorn78c08372019-12-11 20:14:23 +0100229 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100230 {
MG Mud User88f12472016-06-24 23:31:02 +0200231 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
232 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100233 "%.1f%%/20s, %.1f%%/2s",
234 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100235 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
236 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100237 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200238 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100239 }
Arathorn78c08372019-12-11 20:14:23 +0100240 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200241 {
Arathorn78c08372019-12-11 20:14:23 +0100242 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100243 {
Arathorn78c08372019-12-11 20:14:23 +0100244 string unused;
245 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
246 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
247 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
248 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
249 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100250 }
251 else
252 {
Arathorn78c08372019-12-11 20:14:23 +0100253 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200254 }
Arathorn19459eb2019-11-30 00:45:51 +0100255 }
Arathorn78c08372019-12-11 20:14:23 +0100256 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200257 {
MG Mud User88f12472016-06-24 23:31:02 +0200258 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100259 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
260 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
261 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
262 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100263 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
264 }
Arathorn78c08372019-12-11 20:14:23 +0100265 // Ebenenaktion beginnt mit "bann"?
266 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200267 {
268 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100269
270 if (mesg == "bann")
271 {
272 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200273 {
Arathorn78c08372019-12-11 20:14:23 +0100274 ret = "Fuer folgende Spieler besteht ein Bann: ";
275 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
276 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
277 string* banlist = ({});
278 foreach(string plname, string* banned_cmds : channelB) {
279 banlist += ({ sprintf("%s [%s]",
280 capitalize(plname), implode(banned_cmds, ", "))});
281 }
282 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200283 }
284 else
285 {
Arathorn19459eb2019-11-30 00:45:51 +0100286 ret = "Zur Zeit ist kein Bann aktiv.";
287 }
288 }
289 else
290 {
Arathorn78c08372019-12-11 20:14:23 +0100291 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
292 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100293 {
294 pl = lower_case(pl);
295 cmd = lower_case(cmd);
296
297 if (member(CMDS, cmd) != -1)
298 {
Arathorn78c08372019-12-11 20:14:23 +0100299 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
300 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
301 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100302 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100303 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100304
Arathorn78c08372019-12-11 20:14:23 +0100305 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100306 channelB[pl] -= ({ cmd });
307 else
308 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100309
Arathorn78c08372019-12-11 20:14:23 +0100310 ret = "Fuer '" + capitalize(pl) + "' besteht " +
311 (sizeof(channelB[pl])
Zesstraf5f10122020-08-13 21:47:33 +0200312 ? "folgender Bann: " + CountUp(channelB[pl]) + "."
Arathorn78c08372019-12-11 20:14:23 +0100313 : "kein Bann mehr.");
314
315 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100316 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100317 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100318
Zesstra18f2ad62020-08-13 21:45:57 +0200319 save_me_soon = 1;
Arathorn19459eb2019-11-30 00:45:51 +0100320 }
321 else
322 {
323 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100324 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100325 }
326 }
327 else
328 {
Arathorn78c08372019-12-11 20:14:23 +0100329 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100330 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200331 }
332 }
333 }
Arathorn78c08372019-12-11 20:14:23 +0100334 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200335 {
MG Mud User88f12472016-06-24 23:31:02 +0200336 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100337 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
338 {
339 string unused;
340 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
341 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
342 }
MG Mud User88f12472016-06-24 23:31:02 +0200343
Arathorn78c08372019-12-11 20:14:23 +0100344 int t = time() - last_reboot_time();
345
346 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
347 // die Funktionalitaet oefter benoetigt wird als nur hier.
348 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100349 if (t >= 86400)
350 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200351
Arathorn78c08372019-12-11 20:14:23 +0100352 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100353 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100354 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100355
Arathorn78c08372019-12-11 20:14:23 +0100356 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100357 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100358 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100359
360 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100361
MG Mud User88f12472016-06-24 23:31:02 +0200362 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100363 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100364 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100365 }
366 else
367 {
368 return;
369 }
MG Mud User88f12472016-06-24 23:31:02 +0200370
Arathorn78c08372019-12-11 20:14:23 +0100371 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
372 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
373 // nicht erfuellt sind.
374 if (stringp(ret) && sizeof(ret))
375 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200376}
377
378// setup() -- set up a channel and register it
379// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100380// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrad9ec04b2020-08-11 23:47:03 +0200381// adminflags, channelflags, description,supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100382private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200383{
Arathorn78c08372019-12-11 20:14:23 +0100384 string desc = "- Keine Beschreibung -";
Zesstra10341b82020-09-12 13:02:54 +0200385 object supervisor;
Zesstrad9ec04b2020-08-11 23:47:03 +0200386 int chflags;
Arathorn19459eb2019-11-30 00:45:51 +0100387
Arathorn78c08372019-12-11 20:14:23 +0100388 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
389 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200390
Zesstra2aeb6a82020-08-13 23:50:36 +0200391 // Wenn der channeld nur neugeladen wurde, aber das Mud nicht neugestartet,
Zesstra10341b82020-09-12 13:02:54 +0200392 // sind alle Ebenen noch da, weil sie im MEMORY liegen. D.h. ist die Ebene
393 // noch bekannt, muss nichts gemacht werden.
394 if (member(channels, lower_case(chinfo[0])))
395 return;
396
397 // Nur die Angabe des SV (Index 6) im initfile ist optional, alle Elemente
398 // davor muessen da sein.
399 if (sizeof(chinfo) < 6)
400 return;
401 // Bei genug Elementen schauen, ob der SV ladbar ist.
402 if (sizeof(chinfo) >= 7)
MG Mud User88f12472016-06-24 23:31:02 +0200403 {
Zesstra10341b82020-09-12 13:02:54 +0200404 if (stringp(chinfo[6]) && sizeof(chinfo[6]))
405 catch(supervisor = load_object(chinfo[6]); publish);
MG Mud User88f12472016-06-24 23:31:02 +0200406 }
Zesstra10341b82020-09-12 13:02:54 +0200407 // Aber falls kein SV angegeben wird oder das Objekt nicht ladbar war, wird
408 // ein Default-SV genutzt.
409 if (!supervisor)
410 supervisor = load_object(DEFAULTSV);
411
412 desc = chinfo[5];
413 chflags = to_int(chinfo[4]);
414
415 if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
Zesstra46c564e2020-09-11 21:52:29 +0200416 {
Zesstra10341b82020-09-12 13:02:54 +0200417 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
418 dtime(time()), chinfo[0], supervisor));
Zesstra46c564e2020-09-11 21:52:29 +0200419 }
MG Mud User88f12472016-06-24 23:31:02 +0200420 return;
421}
422
Arathorn78c08372019-12-11 20:14:23 +0100423private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200424{
Arathorn78c08372019-12-11 20:14:23 +0100425 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200426#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100427 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200428#else
Arathorn78c08372019-12-11 20:14:23 +0100429 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200430#endif
Arathorn19459eb2019-11-30 00:45:51 +0100431
Arathorn78c08372019-12-11 20:14:23 +0100432 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200433 return;
Arathorn19459eb2019-11-30 00:45:51 +0100434
Arathorn78c08372019-12-11 20:14:23 +0100435 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
436 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
437 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
438 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
439 foreach(string ch : old_explode(ch_list, "\n"))
440 {
441 if (ch[0]=='#')
442 continue;
443 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
444 }
MG Mud User88f12472016-06-24 23:31:02 +0200445}
446
Arathorn78c08372019-12-11 20:14:23 +0100447// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200448protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200449{
Zesstra2aeb6a82020-08-13 23:50:36 +0200450 int do_complete_init;
451
MG Mud User88f12472016-06-24 23:31:02 +0200452 seteuid(getuid());
453 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100454
Zesstrab7720dc2020-08-11 22:14:18 +0200455 // Altes channelC aus Savefiles konvertieren...
456 if (widthof(channelC) == 1)
457 {
458 mapping new = m_allocate(sizeof(channelC), 2);
459 foreach(string chname, mixed arr: channelC)
460 {
461 struct channel_base_s ch = (<channel_base_s> name: arr[0],
462 desc: arr[1]);
463 // die anderen beiden Werte bleiben 0
464 m_add(new, chname, ch, arr[2]);
465 }
466 channelC = new;
467 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200468
Zesstra2aeb6a82020-08-13 23:50:36 +0200469 /* Die aktiven Ebenen und die Channel-History wird nicht nur lokal sondern
470 * auch noch im Memory gespeichert, dadurch bleibt sie auch ueber ein Reload
471 * erhalten.
472 Der folgende Code versucht, den Zeiger auf die alten Mappings aus dem
473 Memory zu holen. Falls das nicht moeglich ist, wird ein neuer erzeugt und
474 gegebenenfalls fuer spaeter im Memory abgelegt. */
MG Mud User88f12472016-06-24 23:31:02 +0200475 // Hab ich die noetigen Rechte im Memory?
Zesstra2aeb6a82020-08-13 23:50:36 +0200476 if (MEMORY->HaveRights())
Arathorn19459eb2019-11-30 00:45:51 +0100477 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200478 // channelH und channels laden
479 channels = ({mapping}) MEMORY->Load("Channels");
480 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
481 if (!mappingp(channels))
482 {
483 // Mapping erzeugen
484 channels = ([]);
485 // und Zeiger auf das Mapping in den Memory schreiben
486 MEMORY->Save("Channels", channels);
487 do_complete_init = 1;
488 }
489 // Und das gleiche fuer die History
490 channelH = ({mapping}) MEMORY->Load("History");
491 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
Arathorn78c08372019-12-11 20:14:23 +0100492 if (!mappingp(channelH))
493 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200494 // Mapping erzeugen
MG Mud User88f12472016-06-24 23:31:02 +0200495 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200496 // und Zeiger auf das Mapping in den Memory schreiben
497 MEMORY->Save("History", channelH);
498 // In diesem Fall muessen die Ebenenhistories auch erzeugt werden, falls
499 // es aktive Ebenen gibt.
500 foreach(string chname: channels)
501 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200502 }
Arathorn19459eb2019-11-30 00:45:51 +0100503 }
504 else
505 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200506 // Keine Rechte im Memory, dann liegt das nur lokal und ist bei
507 // remove/destruct weg.
MG Mud User88f12472016-06-24 23:31:02 +0200508 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200509 channels = ([]);
510 do_complete_init = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200511 }
512
513 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100514 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
515
Zesstra2aeb6a82020-08-13 23:50:36 +0200516 // Das muss auch laufen, wenn wir die alten Ebenen aus dem MEMORY bekommen
Zesstra10341b82020-09-12 13:02:54 +0200517 // haben, weil es ja neue Ebenen geben koennte, die dann erstellt werden
518 // muessen (verschwundete werden aber nicht aufgeraeumt!)
MG Mud User88f12472016-06-24 23:31:02 +0200519 initialize();
Zesstra10341b82020-09-12 13:02:54 +0200520 // <MasteR>-Ebene betreten, damit der channeld auf seine Kommandos auf
521 // dieser Ebene reagieren kann.
522 this_object()->join(CMNAME, this_object());
Arathorn78c08372019-12-11 20:14:23 +0100523
Zesstra2aeb6a82020-08-13 23:50:36 +0200524 // Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
525 // erledigen.
526 if (do_complete_init)
527 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200528 // Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
Zesstra10341b82020-09-12 13:02:54 +0200529 // leider Pech gehabt, falls das nicht das erste Laden nach Reboot war.
Zesstra2aeb6a82020-08-13 23:50:36 +0200530 users()->RegisterChannels();
531 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
532 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
533 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
534 // explizites call_other() auf this_object() gemacht, damit der
535 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
536 // einem externen.
537 this_object()->send(CMNAME, this_object(),
538 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
539 sizeof(channels),
540 CountUsers()));
541 }
542 else
543 {
544 this_object()->send(CMNAME, this_object(),
545 sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
546 sizeof(channels),
547 CountUsers()));
548 }
MG Mud User88f12472016-06-24 23:31:02 +0200549}
550
Arathorn78c08372019-12-11 20:14:23 +0100551varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200552{
Zesstra6ddbacb2020-08-13 21:43:28 +0200553 // Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
554 // zwischen 21.6h und 26.4h von jetzt.
555 set_next_reset(77760 + random(17280));
Zesstra26aaf1a2020-08-07 19:10:39 +0200556
Zesstra6ddbacb2020-08-13 21:43:28 +0200557 // inaktive Ebenen bereinigen
558 int timeout = INACTIVE_EXPIRE;
559 // Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
560 // inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
561 // bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
562 // Ebenen min. inaktiv bestehen.
563 while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
564 && timeout > MIN_INACTIVE_LIFETIME)
565 {
566 channelC = filter(channelC,
567 function int (string ch_name, mixed values)
Zesstra8f5102c2020-08-08 12:51:52 +0200568 {
Zesstra6ddbacb2020-08-13 21:43:28 +0200569 struct channel_base_s data = values[0];
570 int ts = values[1];
571 if (ts + timeout > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200572 return 1;
573 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
574 m_delete(channelH, ch_name);
575 return 0;
576 });
Zesstra6ddbacb2020-08-13 21:43:28 +0200577 // timeout halbieren und neu versuchen wenn noetig.
578 timeout /= 2;
579 }
580 // achja, speichern sollten wir uns ggf. auch noch.
MG Mud User88f12472016-06-24 23:31:02 +0200581 if (save_me_soon)
582 {
Arathorn19459eb2019-11-30 00:45:51 +0100583 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200584 save_object(CHANNEL_SAVE);
585 }
586}
587
Zesstra5856ada2020-08-13 21:52:47 +0200588varargs int remove(int silent)
589{
590 if (save_me_soon)
591 {
592 save_me_soon = 0;
593 save_object(CHANNEL_SAVE);
594 }
595 if (!silent)
596 {
597 this_object()->send(CMNAME, this_object(),
598 sprintf("remove() durch %O gerufen. Speichern und Ende.\n",
599 previous_object()));
600 }
601 destruct(this_object());
602 return 1;
603}
604
MG Mud User88f12472016-06-24 23:31:02 +0200605// name() - define the name of this object.
Zesstra10341b82020-09-12 13:02:54 +0200606public varargs string name(int casus,int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100607{
608 return CMNAME;
609}
610
Zesstra10341b82020-09-12 13:02:54 +0200611public varargs string Name(int casus, int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100612{
Zesstra10341b82020-09-12 13:02:54 +0200613 return capitalize(CMNAME);
Arathorn19459eb2019-11-30 00:45:51 +0100614}
MG Mud User88f12472016-06-24 23:31:02 +0200615
Zesstra28986e12020-08-09 12:44:26 +0200616// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200617// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200618private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200619{
620 if (IsChannelMember(ch, m))
621 return E_ALREADY_JOINED;
622
Zesstrab7720dc2020-08-11 22:14:18 +0200623 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200624 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200625}
626
Zesstra52d5f8a2020-08-12 00:39:15 +0200627private void remove_all_members(struct channel_s ch)
628{
629 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
630 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
631 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
632 string chname = lower_case(ch.name);
633 foreach(object listener : ch.members)
634 {
635 string* pl_chans = listener->QueryProp(P_CHANNELS);
636 if (pointerp(pl_chans))
637 {
638 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
639 }
640 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
641 if (pointerp(pl_chans))
642 {
643 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
644 }
645 }
646}
647
Zesstra6ddbacb2020-08-13 21:43:28 +0200648private void delete_channel(string chname, int force);
649
Zesstraf87cb772020-08-10 11:14:45 +0200650// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
651// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200652// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
653// anwesend sind.
654private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200655{
Zesstra6ddbacb2020-08-13 21:43:28 +0200656 // Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
657 if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
658 {
659 log_file("CHANNEL",
660 sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
661 "deaktiviert.\n", dtime(time()), chname));
662 this_object()->send(CMNAME, this_object(),
663 sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
664 "deaktiviert.\n", chname));
665 delete_channel(chname, force);
666 return;
667 }
Zesstrab7720dc2020-08-11 22:14:18 +0200668 chname = lower_case(chname);
669 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200670 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200671 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200672 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200673 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200674 if (sizeof(ch.members))
675 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200676 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
677 if (!force)
678 {
679 raise_error(
680 sprintf("Attempt to deactivate channel %s with listeners.\n",
681 ch.name));
682 }
683 else
684 {
685 remove_all_members(ch);
686 }
Zesstrab7720dc2020-08-11 22:14:18 +0200687 }
Zesstraf87cb772020-08-10 11:14:45 +0200688 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
689 // einloggt, der die Ebene abonniert hat.
Zesstrab7720dc2020-08-11 22:14:18 +0200690 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
691 time());
Zesstraf87cb772020-08-10 11:14:45 +0200692
Zesstrab7720dc2020-08-11 22:14:18 +0200693 // aktive Ebene loeschen bzw. deaktivieren.
694 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200695 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
696 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
697 // channelC bereinigt wird.
698
699 stats["dispose"]++;
700 save_me_soon = 1;
701}
702
703// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200704// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
705// anwesend sind.
706private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200707{
Zesstrab7720dc2020-08-11 22:14:18 +0200708 chname = lower_case(chname);
709 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200710 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200711 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200712 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200713 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200714 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200715 {
716 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
717 if (!force)
718 {
719 raise_error(
720 sprintf("Attempt to delete channel %s with listeners.\n",
721 ch.name));
722 }
723 else
724 {
725 remove_all_members(ch);
726 }
727 }
Zesstraf87cb772020-08-10 11:14:45 +0200728 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200729 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200730 }
731 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstra6ddbacb2020-08-13 21:43:28 +0200732 m_delete(channelC, chname);
733 m_delete(channelH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200734 save_me_soon = 1;
735}
736
Zesstra5b7f2fc2020-08-10 02:09:13 +0200737// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
738// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200739private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200740{
741 if (!new_sv)
742 {
Zesstrab7720dc2020-08-11 22:14:18 +0200743 ch.members -= ({0});
744 if (sizeof(ch.members))
745 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200746 else
747 return 0; // kein neuer SV moeglich.
748 }
Zesstra9359fab2020-08-13 12:03:01 +0200749 // evtl. darf der supervisor aber nicht zu was anderes als dem creator
750 // wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
751 // Ebene ist.
752 if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
753 && new_sv != find_object(ch.creator))
754 || !IsChannelMember(ch, new_sv)
755 )
756 return 0;
757
Zesstrabf4f86d2020-08-12 00:56:17 +0200758 object old_sv = ch.supervisor;
759
Zesstrab7720dc2020-08-11 22:14:18 +0200760 ch.supervisor = new_sv;
Zesstra10341b82020-09-12 13:02:54 +0200761 ch.access_cl = symbol_function("ch_check_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200762
Zesstra5b7f2fc2020-08-10 02:09:13 +0200763 if (old_sv && new_sv
764 && !old_sv->QueryProp(P_INVIS)
765 && !new_sv->QueryProp(P_INVIS))
766 {
767 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
768 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
769 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
770 // explizites call_other() auf this_object() gemacht, damit der
771 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
772 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200773 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200774 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
775 MSG_EMOTE);
776 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200777 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200778 {
Zesstrab7720dc2020-08-11 22:14:18 +0200779 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200780 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
781 }
782 else if (new_sv && !new_sv->QueryProp(P_INVIS))
783 {
Zesstrab7720dc2020-08-11 22:14:18 +0200784 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200785 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
786 }
787 return 1;
788}
789
Zesstra56692c72020-08-09 13:03:10 +0200790// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
791// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200792// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
793// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200794// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200795private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200796{
Zesstra9359fab2020-08-13 12:03:01 +0200797 // Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
798 if (!ch.supervisor)
MG Mud User88f12472016-06-24 23:31:02 +0200799 {
Zesstra9359fab2020-08-13 12:03:01 +0200800 // Wenn der Wechsel des SV verboten ist, wird versucht, den
801 // urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
802 // machen.
803 if (ch.flags & CHF_FIXED_SUPERVISOR)
MG Mud User88f12472016-06-24 23:31:02 +0200804 {
Zesstra9359fab2020-08-13 12:03:01 +0200805 object sv;
806 string err=catch(sv=load_object(ch.creator);publish);
Arathorn78c08372019-12-11 20:14:23 +0100807 if (!err)
808 {
Zesstra9359fab2020-08-13 12:03:01 +0200809 // Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
810 // und kann dann wieder SV werden.
811 add_member(ch, sv);
812 if (!change_sv_object(ch, sv))
813 {
814 // ich wuesste nicht, was in change_sv_object in diesem Fall
815 // schiefgehen kann, daher einfach ein raise_error.
816 raise_error(sprintf("Supervisor von Channel %s konnte nicht "
817 "reaktiviert werden: %O\n",ch.name,sv));
818 }
Arathorn78c08372019-12-11 20:14:23 +0100819 }
Zesstra9359fab2020-08-13 12:03:01 +0200820 // wenns nicht geklappt hat, wird die Ebene deaktiviert.
Arathorn78c08372019-12-11 20:14:23 +0100821 else
822 {
Zesstra9359fab2020-08-13 12:03:01 +0200823 // Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
824 // urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
825 // solange der das nicht tut, ist weder die History zugaenglich, noch
826 // kann jemand sonst was damit machen. Wenn die inaktive Ebene
827 // irgendwann inkl. History expired wird, kann jemand anderes dann
828 // den Namen wieder verwenden und ein komplett neue Ebene erstellen.
829 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200830 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200831 sprintf("[%s] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200832 dtime(time()), ch.name, ch.supervisor, err));
Arathorn78c08372019-12-11 20:14:23 +0100833 return 0;
834 }
MG Mud User88f12472016-06-24 23:31:02 +0200835 }
Zesstra9359fab2020-08-13 12:03:01 +0200836 // Der normalfall ist aber, dass wir einfach einen supervisor aus dem
837 // Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
838 // change_sv_object().
839 else
Zesstra56692c72020-08-09 13:03:10 +0200840 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200841 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200842 {
Zesstra9359fab2020-08-13 12:03:01 +0200843 // wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
844 // Zuhoerer.
845 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200846 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200847 sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200848 dtime(time()), ch.name));
Zesstra5770ba62020-08-10 10:19:23 +0200849 return 0;
850 }
Zesstra56692c72020-08-09 13:03:10 +0200851 }
MG Mud User88f12472016-06-24 23:31:02 +0200852 }
Zesstra78310012020-08-09 12:21:48 +0200853 return 1;
854}
855
856// access() - check access by looking for the right argument types and
857// calling access closures respectively
858// SEE: new, join, leave, send, list, users
Zesstra78310012020-08-09 12:21:48 +0200859// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
860// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
861// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
862// als Erfolg und alles ueber >2 als privilegiert.)
Zesstra752ae7d2020-08-16 22:46:04 +0200863varargs private int access(struct channel_s ch, object user, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200864 string txt)
865{
Zesstra10341b82020-09-12 13:02:54 +0200866 if (!ch || !user)
Zesstra78310012020-08-09 12:21:48 +0200867 return 0;
868
Zesstra46c564e2020-09-11 21:52:29 +0200869 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
Zesstrafbfe6362020-08-09 13:30:21 +0200870 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra46c564e2020-09-11 21:52:29 +0200871 // Ausserdem duerfen sie auch alles andere machen unter Umgehung aller
872 // Supervisoren. (z.B. kann dieses Objekt sogar Meldungen im Namen anderer
873 // Objekte faken)
874 // Die Pruefung erfolgt absichtlich vor assert_supervisor(), damit der
Zesstra10341b82020-09-12 13:02:54 +0200875 // CHANNELD auch in temporaeren SV-losen Zustaenden was machen kann.
Zesstra78310012020-08-09 12:21:48 +0200876 if ( !previous_object(1) || !extern_call() ||
877 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200878 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200879 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200880
Zesstra46c564e2020-09-11 21:52:29 +0200881 // Objekte duerfen keine Meldungen im Namen anderer Objekte faken, d.h. der
Zesstra10341b82020-09-12 13:02:54 +0200882 // vermeintliche <user> muss auch der Aufrufer sein. Ausser darf auch sonst
883 // kein Objekt was fuer ein anderes Objekt duerfen, sonst kann jemand z.B.
884 // eine History abfragen indem einfach ein anderes Objekt uebergeben wird.
885 if (previous_object(1) != user)
Arathorn739a4fa2020-08-06 21:52:58 +0200886 return 0;
887
Zesstra752ae7d2020-08-16 22:46:04 +0200888 if (IsBanned(user, cmd))
Arathorn739a4fa2020-08-06 21:52:58 +0200889 return 0;
890
Zesstra56692c72020-08-09 13:03:10 +0200891 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
892 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200893 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200894 return 0;
895 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200896 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200897 return 1;
898
Zesstra6fe46cd2020-08-09 13:12:15 +0200899 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
Zesstra10341b82020-09-12 13:02:54 +0200900 // fuer EM+ aber nur, wenn es das Default-SV-Objekt ist, damit
Zesstra6fe46cd2020-08-09 13:12:15 +0200901 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
Zesstra10341b82020-09-12 13:02:54 +0200902 // Default-SV koennen aber auch EM+ Zugriff verweigern.
903 if (IS_ARCH(previous_object(1))
904 && ch.supervisor != find_object(DEFAULTSV))
Zesstra6fe46cd2020-08-09 13:12:15 +0200905 return 1;
906
Zesstra752ae7d2020-08-16 22:46:04 +0200907 return funcall(ch.access_cl, lower_case(ch.name), user, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200908}
909
Arathorn78c08372019-12-11 20:14:23 +0100910// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200911// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100912// die dynamisch aktualisierte Infos ausgibt.
Zesstra10341b82020-09-12 13:02:54 +0200913// Das Objekt <owner> sollte eine Funktion ch_check_access() definieren, die
Arathorn78c08372019-12-11 20:14:23 +0100914// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
915// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200916#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200917public varargs int new(string ch_name, object owner, string|closure desc,
918 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200919{
Arathorn78c08372019-12-11 20:14:23 +0100920 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
921 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
922 // fuer eine neue Ebene eintragen.)
923 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200924 return E_ACCESS_DENIED;
925
Arathorn78c08372019-12-11 20:14:23 +0100926 // Kein gescheiter Channelname angegeben.
927 if (!stringp(ch_name) || !sizeof(ch_name))
928 return E_ACCESS_DENIED;
929
930 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
931 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
932 return E_ACCESS_DENIED;
933
934 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
935 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
936 // seinen Objektnamen matcht.
937 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
938 return E_ACCESS_DENIED;
939
Zesstraf20e8ba2020-08-12 01:56:30 +0200940 // Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
941 // existiert.
942 struct channel_base_s cbase = channelC[lower_case(ch_name)];
Zesstrab7720dc2020-08-11 22:14:18 +0200943 struct channel_s ch;
Zesstraf20e8ba2020-08-12 01:56:30 +0200944 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100945 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200946 // Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
947 // neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
948 // CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
949 // damit niemand inaktive Ebenen und deren History auf diesem Wege
950 // uebernehmen kann, d.h. den Supervisor ersetzen kann.
951 if ((cbase.flags & CHF_FIXED_SUPERVISOR)
952 && object_name(owner) != cbase.creator)
Arathorn19459eb2019-11-30 00:45:51 +0100953 return E_ACCESS_DENIED;
Zesstraf20e8ba2020-08-12 01:56:30 +0200954 // Alte Daten der Ebene uebernehmen
955 ch = to_struct(cbase, (<channel_s>));
956 // Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
957 if (desc)
958 ch.desc = desc;
959 // creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
960 // die Schreibweise (Gross-/Kleinschreibung) des Namens aus
961 // Konsistenzgruenden bei.
MG Mud User88f12472016-06-24 23:31:02 +0200962 }
Arathorn19459eb2019-11-30 00:45:51 +0100963 else
964 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200965 // Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
966 // Fehler...
967 if (!desc)
968 return E_ACCESS_DENIED;
969 // prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
970 // erzeugt.
Zesstrad9ec04b2020-08-11 23:47:03 +0200971 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
972 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +0100973 }
MG Mud User88f12472016-06-24 23:31:02 +0200974
Zesstrab7720dc2020-08-11 22:14:18 +0200975 ch_name = lower_case(ch_name);
976
977 ch.members = ({ owner });
Zesstra9359fab2020-08-13 12:03:01 +0200978 ch.supervisor = owner;
Zesstra10341b82020-09-12 13:02:54 +0200979 // ch_check_access() dient der Zugriffskontrolle und entscheidet, ob die
Zesstrad9ec04b2020-08-11 23:47:03 +0200980 // Nachricht gesendet werden darf oder nicht.
Zesstra10341b82020-09-12 13:02:54 +0200981 ch.access_cl = symbol_function("ch_check_access", owner);
Zesstrab7720dc2020-08-11 22:14:18 +0200982
983 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +0200984
Arathorn78c08372019-12-11 20:14:23 +0100985 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
986 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +0200987 if (!pointerp(channelH[ch_name]))
988 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200989
Zesstraf20e8ba2020-08-12 01:56:30 +0200990 // Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
991 // jetzt auch weg.
992 if (cbase)
993 m_delete(channelC, ch_name);
994
Arathorn78c08372019-12-11 20:14:23 +0100995 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
996 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +0200997 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200998 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +0100999
Arathorn78c08372019-12-11 20:14:23 +01001000 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
1001 if (!owner->QueryProp(P_INVIS))
1002 {
1003 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1004 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1005 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1006 // explizites call_other() auf this_object() gemacht, damit der
1007 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1008 // einem externen.
1009 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +02001010 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +01001011 }
Arathorn19459eb2019-11-30 00:45:51 +01001012
MG Mud User88f12472016-06-24 23:31:02 +02001013 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +01001014 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001015}
1016
Arathorn78c08372019-12-11 20:14:23 +01001017// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
1018// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
1019// zweimal beitreten.)
Zesstra165157f2020-08-16 22:47:36 +02001020public int join(string chname, object joining)
MG Mud User88f12472016-06-24 23:31:02 +02001021{
Zesstrab7720dc2020-08-11 22:14:18 +02001022 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001023 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1024 zu erzeugen, weil access() mit extern_call() und previous_object()
1025 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1026 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001027 if (!funcall(#'access, ch, joining, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +01001028 return E_ACCESS_DENIED;
Zesstra10341b82020-09-12 13:02:54 +02001029 //TODO: Sollte der creator das Recht auf join haben, auch wenn der aktuelle
1030 //SV es verweigert? (s.u.)
Zesstra165157f2020-08-16 22:47:36 +02001031 int res = add_member(ch, joining);
Zesstrafb350dc2020-08-12 00:49:31 +02001032 if (res != 1)
1033 return res;
1034
Zesstra46c564e2020-09-11 21:52:29 +02001035 // Wenn <joining> der urspruengliche Ersteller der Ebene und kein
1036 // Spieler ist, wird es automatisch wieder zum Supervisor.
Zesstra165157f2020-08-16 22:47:36 +02001037 if (!query_once_interactive(joining)
1038 && object_name(joining) == ch.creator)
1039 change_sv_object(ch, joining);
Zesstra2b7ed1a2020-08-12 00:58:33 +02001040
Zesstrafb350dc2020-08-12 00:49:31 +02001041 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001042}
1043
Arathorn78c08372019-12-11 20:14:23 +01001044// Objekt <pl> verlaesst Ebene <ch>.
1045// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
1046// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
Zesstra10341b82020-09-12 13:02:54 +02001047// Dies ist in ch_check_access() so geregelt, allerdings koennte dem Objekt
Arathorn78c08372019-12-11 20:14:23 +01001048// <pl> das Verlassen auf Grund eines Banns verboten sein.
Zesstra10341b82020-09-12 13:02:54 +02001049// Wenn kein Zuhoerer mehr auf der Ebene ist, loest sie sich auf.
Zesstra165157f2020-08-16 22:47:36 +02001050public int leave(string chname, object leaving)
MG Mud User88f12472016-06-24 23:31:02 +02001051{
Zesstrab7720dc2020-08-11 22:14:18 +02001052 struct channel_s ch = channels[lower_case(chname)];
Zesstra0b4a5652020-09-23 00:40:37 +02001053 // Nicht-existierenden Ebenen soll das Spielerobjekt austragen, also tun wir
1054 // so, als sei das erfolgreich gewesen.
1055 if (!ch)
1056 return 0;
Zesstra877cb0a2020-08-10 02:10:21 +02001057
Zesstrab7720dc2020-08-11 22:14:18 +02001058 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +02001059
Zesstra165157f2020-08-16 22:47:36 +02001060 if (!IsChannelMember(ch, leaving))
Zesstra877cb0a2020-08-10 02:10:21 +02001061 return E_NOT_MEMBER;
1062
Arathorn739a4fa2020-08-06 21:52:58 +02001063 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1064 zu erzeugen, weil access() mit extern_call() und previous_object()
1065 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1066 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001067 if (!funcall(#'access, ch, leaving, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +01001068 return E_ACCESS_DENIED;
1069
Zesstrab7720dc2020-08-11 22:14:18 +02001070 // Dann mal den Zuhoerer raus.
Zesstra165157f2020-08-16 22:47:36 +02001071 ch.members -= ({leaving});
Zesstrae6d33852020-08-09 14:37:53 +02001072
Zesstra5b7f2fc2020-08-10 02:09:13 +02001073 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
1074 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +02001075 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +02001076 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001077 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1078 // diese verlassen hat. change_sv_object() waehlt per Default den
1079 // aeltesten Zuhoerer.
Zesstra165157f2020-08-16 22:47:36 +02001080 if (leaving == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001081 {
Zesstrabf4f86d2020-08-12 00:56:17 +02001082 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +01001083 }
MG Mud User88f12472016-06-24 23:31:02 +02001084 }
Zesstra137ea1c2020-08-10 02:15:20 +02001085 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1086 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1087 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1088 else
MG Mud User88f12472016-06-24 23:31:02 +02001089 {
Arathorn78c08372019-12-11 20:14:23 +01001090 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001091 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1092 // wird diese zerstoert, mit Meldung.
Zesstra165157f2020-08-16 22:47:36 +02001093 if (!leaving->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001094 {
1095 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1096 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1097 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1098 // explizites call_other() auf this_object() gemacht, damit der
1099 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1100 // einem externen.
Zesstra165157f2020-08-16 22:47:36 +02001101 this_object()->send(CMNAME, leaving,
Arathorn78c08372019-12-11 20:14:23 +01001102 "verlaesst als "+
Zesstra165157f2020-08-16 22:47:36 +02001103 (leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001104 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001105 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1106 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001107 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +02001108 }
Arathorn19459eb2019-11-30 00:45:51 +01001109 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001110}
1111
Arathorn78c08372019-12-11 20:14:23 +01001112// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1113// sofern <pl> dort senden darf.
Zesstra165157f2020-08-16 22:47:36 +02001114public varargs int send(string chname, object sender, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001115{
Zesstrab7720dc2020-08-11 22:14:18 +02001116 chname = lower_case(chname);
1117 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001118 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1119 zu erzeugen, weil access() mit extern_call() und previous_object()
1120 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1121 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001122 int a = funcall(#'access, ch, sender, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001123 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001124 return E_ACCESS_DENIED;
1125
Zesstra26aaf1a2020-08-07 19:10:39 +02001126 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
Zesstra10341b82020-09-12 13:02:54 +02001127 // Rueckgabewert von ch_check_access() entspricht, wenn die Aktion zugelassen
1128 // wird. access() liefert allerdings 2 fuer "privilegierte" Objekte (z.B.
Zesstra26aaf1a2020-08-07 19:10:39 +02001129 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1130 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Zesstra165157f2020-08-16 22:47:36 +02001131 if (a < 2 && !IsChannelMember(ch, sender))
Arathorn19459eb2019-11-30 00:45:51 +01001132 return E_NOT_MEMBER;
1133
1134 if (!msg || !stringp(msg) || !sizeof(msg))
1135 return E_EMPTY_MESSAGE;
1136
Arathorn78c08372019-12-11 20:14:23 +01001137 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1138 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1139 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1140 // Nachricht ebenfalls erhaelt.
1141 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1142 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1143 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1144 // erzeugt und der Channeld als Besitzer angegeben.
1145 // Die Aufrufkette ist dann wie folgt:
1146 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001147 (ch.members)->ChannelMessage(
Zesstra165157f2020-08-16 22:47:36 +02001148 ({ ch.name, sender, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001149
Zesstrab7720dc2020-08-11 22:14:18 +02001150 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1151 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001152
Zesstrab7720dc2020-08-11 22:14:18 +02001153 channelH[chname] +=
1154 ({ ({ ch.name,
Zesstra556c54d2020-08-16 22:50:10 +02001155 (sender->QueryProp(P_INVIS)
Zesstra165157f2020-08-16 22:47:36 +02001156 ? "/(" + capitalize(getuid(sender)) + ")$"
Arathorn19459eb2019-11-30 00:45:51 +01001157 : "")
Zesstra556c54d2020-08-16 22:50:10 +02001158 + (sender->Name(WER, 2) || "<Unbekannt>"),
Arathorn19459eb2019-11-30 00:45:51 +01001159 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1160 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001161 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001162}
1163
Arathorn78c08372019-12-11 20:14:23 +01001164// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1165// oder einen Integer-Fehlercode
1166public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001167{
Arathorn78c08372019-12-11 20:14:23 +01001168 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001169 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001170 {
Arathorn739a4fa2020-08-06 21:52:58 +02001171 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1172 zu erzeugen, weil access() mit extern_call() und previous_object()
1173 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1174 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001175 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001176 {
Zesstrab7720dc2020-08-11 22:14:18 +02001177 ch.members = filter(ch.members, #'objectp);
1178 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1179 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001180 }
1181 }
Arathorn19459eb2019-11-30 00:45:51 +01001182
1183 if (!sizeof(chs))
1184 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001185 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001186}
1187
Arathorn78c08372019-12-11 20:14:23 +01001188// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1189// Rueckgabewerte:
1190// - den gefundenen Namen als String
1191// - String-Array, wenn es mehrere Treffer gibt
1192// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001193public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001194{
Zesstrab7720dc2020-08-11 22:14:18 +02001195 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001196
Arathorn78c08372019-12-11 20:14:23 +01001197 // Suchstring <ch> muss Formatanforderung erfuellen;
1198 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1199 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1200 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1201 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001202 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001203 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001204
Arathorn78c08372019-12-11 20:14:23 +01001205 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1206 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1207 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001208 string* chs = filter(m_indices(channels), function int (string ch_n) {
Arathorn739a4fa2020-08-06 21:52:58 +02001209 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1210 im Caller Stack zu erzeugen, weil access() mit
1211 extern_call() und previous_object() arbeitet und
1212 sichergestellt sein muss, dass das in jedem Fall das
1213 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001214 return ( stringp(regmatch(ch_n, "^"+chname)) &&
1215 funcall(#'access, channels[ch_n], pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001216 });
Arathorn19459eb2019-11-30 00:45:51 +01001217
Arathorn78c08372019-12-11 20:14:23 +01001218 int num_channels = sizeof(chs);
1219 if (num_channels > 1)
1220 return chs;
1221 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001222 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001223 else
1224 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001225}
1226
Arathorn78c08372019-12-11 20:14:23 +01001227// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001228public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001229{
Zesstrab7720dc2020-08-11 22:14:18 +02001230 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001231 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1232 zu erzeugen, weil access() mit extern_call() und previous_object()
1233 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1234 richtige ist. */
1235 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001236 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001237 else
Zesstrab7720dc2020-08-11 22:14:18 +02001238 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001239}
1240
Arathorn78c08372019-12-11 20:14:23 +01001241// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001242public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001243{
Zesstra66f801d2020-09-24 21:00:13 +02001244 if (!member(channels, chname))
1245 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001246 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001247 if (previous_object() != this_object())
1248 {
Zesstrab7720dc2020-08-11 22:14:18 +02001249 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001250 pl != this_player() || this_player() != this_interactive() ||
1251 this_interactive() != previous_object() ||
1252 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001253 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001254 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001255
1256 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001257
Arathorn19459eb2019-11-30 00:45:51 +01001258 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001259}
1260
Arathorn78c08372019-12-11 20:14:23 +01001261// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001262public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001263{
Zesstra66f801d2020-09-24 21:00:13 +02001264 if (!member(channelH, chname))
1265 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001266 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001267 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001268 if (previous_object() != this_object())
1269 {
Zesstrab7720dc2020-08-11 22:14:18 +02001270 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001271 this_player() != this_interactive() ||
1272 this_interactive() != previous_object() ||
1273 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001274 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001275 }
Zesstrab7720dc2020-08-11 22:14:18 +02001276 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001277 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1278 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001279 if (pointerp(channelH[chname]))
1280 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001281
1282 return 0;
1283}
Zesstra66f801d2020-09-24 21:00:13 +02001284
1285// Aendert den Ersteller/Besitzer der Ebene.
1286// Achtung: das ist nicht das gleiche wie der aktuelle Supervisor!
1287public int transfer_ownership(string chname, object new_owner)
1288{
1289 struct channel_s ch = channels[lower_case(chname)];
1290 if (!ch)
1291 return E_ACCESS_DENIED;
1292 // Nur der aktuelle Besitzer oder EM+ darf die Ebene verschenken
1293 if (ch.creator != object_name(previous_object())
1294 && !IS_ARCH(previous_object()))
1295 return E_ACCESS_DENIED;
1296
1297 ch.creator = object_name(new_owner);
1298 return 1;
1299}
1300
1301// Aendert den Flags der Ebene.
1302public int change_channel_flags(string chname, int newflags)
1303{
1304 struct channel_s ch = channels[lower_case(chname)];
1305 if (!ch)
1306 return E_ACCESS_DENIED;
1307 // Nur der aktuelle Besitzer, Supervisor oder EM+ darf die Flags aendern.
1308 if (ch.supervisor != previous_object()
1309 && ch.creator != object_name(previous_object())
1310 && !IS_ARCH(previous_object()))
1311 return E_ACCESS_DENIED;
1312
1313 ch.flags = newflags;
1314 return 1;
1315}
1316