blob: 09cb6a19ef1987bc0e2605c2d47b51b071f1b139 [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
Zesstra0caa8e42020-08-11 22:51:59 +020039// Standard-Ebenen-Supervisor erben, Variablen nosave, die sollen hier nicht
40// gespeichert werden.
41nosave variables inherit "/std/channel_supervisor";
Arathorn78c08372019-12-11 20:14:23 +010042
Zesstrab7720dc2020-08-11 22:14:18 +020043// Datenstrukturen fuer die Ebenen.
44// Basisdaten, welche auch inaktive Ebenen in channelC haben
45struct channel_base_s {
46 string name; // readable channelname, case-sensitive
47 string|closure desc; // stat. oder dyn. Beschreibung
Zesstrad9ec04b2020-08-11 23:47:03 +020048 string creator; // Ersteller der Ebene (Objektname), Original-SV
49 int flags; // Flags, die bestimmtes Verhalten steuern.
Zesstrab7720dc2020-08-11 22:14:18 +020050};
51
52// Basisdaten + die von aktiven Ebenen
53struct channel_s (channel_base_s) {
Zesstra9359fab2020-08-13 12:03:01 +020054 object supervisor; // aktueller Supervisor der Ebene
55 closure access_cl; // Closure fuer Zugriffsrechtepruefung
56 object *members; // Zuhoerer der Ebene
Zesstrab7720dc2020-08-11 22:14:18 +020057};
58
59/* Ebenenliste und die zugehoerigen Daten in struct (<channel>).
60 channels = ([string channelname : (<channel_s>) ])
Zesstra2aeb6a82020-08-13 23:50:36 +020061// HINWEIS: Bitte beachten, dass channels immer nur so manipuliert werden
62// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
63// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
64// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
65// Referenz in Memory und Channeld dieselbe ist.
Zesstra78310012020-08-09 12:21:48 +020066 */
Zesstra2aeb6a82020-08-13 23:50:36 +020067private nosave mapping channels;
Arathorn19459eb2019-11-30 00:45:51 +010068
Arathorn78c08372019-12-11 20:14:23 +010069/* Ebenenhistory
70 mapping channelH = ([ string channelname : ({ ({string channelname,
71 string sender,
72 string msg,
73 int msg_type}) }) ]) */
74// channelH wird in create() geeignet initialisiert
75// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
76// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
77// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
78// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
79// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020080private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010081
Arathorn78c08372019-12-11 20:14:23 +010082/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +020083 mapping channelC = ([ string channelname : (<channel_base_s>);
84 int time() ])
85 Der Zeitstempel ist die letzte Aenderung, d.h. in der Regel des Ablegens in
86 channelC.
87 */
88private mapping channelC = ([:2]);
Arathorn19459eb2019-11-30 00:45:51 +010089
Arathorn78c08372019-12-11 20:14:23 +010090/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
91 mapping channelB = ([ string playername : string* banned_command ]) */
92private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020093
Arathorn78c08372019-12-11 20:14:23 +010094/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
95 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
96 kann.
97
Arathorn19459eb2019-11-30 00:45:51 +010098 mapping Tcmd = ([ "lag": int timestamp,
99 "uptime": int timestamp,
100 "statistik": int timestamp]) */
101private mapping Tcmd = ([]);
102
Zesstra2aeb6a82020-08-13 23:50:36 +0200103/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
104 zerstoerte Ebenen.
105 mapping stats = ([ "time" : int object_time(),
106 "boot" : string getuid(previous_object()),
107 "new" : int total_channels_created,
108 "disposed" : int total_channels_removed ]) */
109// stats wird in create() geeignet initialisiert
110private nosave mapping stats;
111
Arathorn78c08372019-12-11 20:14:23 +0100112/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
113 Speicherevent das Savefile geschrieben werden soll.
114 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +0200115private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +0200116
117// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
118
Arathorn78c08372019-12-11 20:14:23 +0100119/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
120// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
121private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200122{
Arathorn78c08372019-12-11 20:14:23 +0100123 object* userlist = ({});
Zesstrab7720dc2020-08-11 22:14:18 +0200124 foreach(string ch_name, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +0100125 {
Zesstrab7720dc2020-08-11 22:14:18 +0200126 userlist += ch.members;
Arathorn78c08372019-12-11 20:14:23 +0100127 }
128 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
129 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200130}
131
Arathorn78c08372019-12-11 20:14:23 +0100132// Ist das Objekt <sender> Abonnent der Ebene <ch>?
Zesstrab7720dc2020-08-11 22:14:18 +0200133private int IsChannelMember(struct channel_s ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200134{
Zesstrab7720dc2020-08-11 22:14:18 +0200135 return (member(ch.members, sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200136}
137
Arathorn78c08372019-12-11 20:14:23 +0100138// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
139private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200140{
Arathorn78c08372019-12-11 20:14:23 +0100141 if (objectp(ob))
142 ob = getuid(ob);
143 return(pointerp(channelB[ob]) &&
144 member(channelB[ob], command) != -1);
145}
MG Mud User88f12472016-06-24 23:31:02 +0200146
Arathorn78c08372019-12-11 20:14:23 +0100147private void banned(string plname, string* cmds, string res)
148{
149 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
150}
151
152#define TIMEOUT (time() - 60)
153
154// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
155// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
156// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
157// zurueckliegt, als "nicht gesperrt".
158private int IsNotBlocked(string* cmd)
159{
160 string* res = filter(cmd, function int (string str) {
161 return (Tcmd[str] < TIMEOUT);
162 });
163 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
164 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
165 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
166 if (sizeof(res) == sizeof(cmd)) {
167 foreach(string str : cmd) {
168 Tcmd[str] = time();
169 }
170 return 1;
171 }
172 return 0;
173}
174
175// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
176// zugelassen wird. Anforderungen:
177// 1) <cmd> muss Teilstring von <check> sein
178// 2) <cmd> muss am Anfang von <check> stehen
179// 3) <cmd> darf nicht laenger sein als <check>
180// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
181// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
182// cmd = "statistiker" oder cmd = "tist"
183// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
184private int IsValidChannelCommand(string cmd, string check) {
185 // Syntaxcheck (prueft Bedingungen 1 bis 3).
186 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
187 string* cmd_to_check;
188 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
189 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
190 // geben.
191 if ( cmd == "lust" )
192 cmd_to_check = ({"lag", "statistik", "uptime"});
193 else
194 cmd_to_check = ({cmd});
195 // Prueft die Zeitsperre (Bedingung 4).
196 return (IsNotBlocked(cmd_to_check));
197 }
198 return 0;
199}
200
201#define CH_NAME 0
202#define CH_SENDER 1
203#define CH_MSG 2
204#define CH_MSG_TYPE 3
205// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
206// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
207// und dieses per Callout an send() uebergeben.
Zesstrab7720dc2020-08-11 22:14:18 +0200208// Argument: ({channel.name, object pl, string msg, int type})
Arathorn78c08372019-12-11 20:14:23 +0100209// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
210// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
211// was aber bei einer private oder protected Funktion nicht moeglich waere.
212public void ChannelMessage(<string|object|int>* msg)
213{
214 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
215 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
216 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
217 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100218 return;
MG Mud User88f12472016-06-24 23:31:02 +0200219
Arathorn78c08372019-12-11 20:14:23 +0100220 float* lag;
221 int max, rekord;
222 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200223 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200224
Arathorn78c08372019-12-11 20:14:23 +0100225 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200226 {
Arathorn78c08372019-12-11 20:14:23 +0100227 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
228 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100229 }
Arathorn78c08372019-12-11 20:14:23 +0100230 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100231 {
MG Mud User88f12472016-06-24 23:31:02 +0200232 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
233 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100234 "%.1f%%/20s, %.1f%%/2s",
235 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100236 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
237 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100238 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200239 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100240 }
Arathorn78c08372019-12-11 20:14:23 +0100241 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200242 {
Arathorn78c08372019-12-11 20:14:23 +0100243 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100244 {
Arathorn78c08372019-12-11 20:14:23 +0100245 string unused;
246 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
247 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
248 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
249 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
250 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100251 }
252 else
253 {
Arathorn78c08372019-12-11 20:14:23 +0100254 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200255 }
Arathorn19459eb2019-11-30 00:45:51 +0100256 }
Arathorn78c08372019-12-11 20:14:23 +0100257 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200258 {
MG Mud User88f12472016-06-24 23:31:02 +0200259 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100260 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
261 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
262 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
263 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100264 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
265 }
Arathorn78c08372019-12-11 20:14:23 +0100266 // Ebenenaktion beginnt mit "bann"?
267 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200268 {
269 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100270
271 if (mesg == "bann")
272 {
273 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200274 {
Arathorn78c08372019-12-11 20:14:23 +0100275 ret = "Fuer folgende Spieler besteht ein Bann: ";
276 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
277 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
278 string* banlist = ({});
279 foreach(string plname, string* banned_cmds : channelB) {
280 banlist += ({ sprintf("%s [%s]",
281 capitalize(plname), implode(banned_cmds, ", "))});
282 }
283 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200284 }
285 else
286 {
Arathorn19459eb2019-11-30 00:45:51 +0100287 ret = "Zur Zeit ist kein Bann aktiv.";
288 }
289 }
290 else
291 {
Arathorn78c08372019-12-11 20:14:23 +0100292 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
293 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100294 {
295 pl = lower_case(pl);
296 cmd = lower_case(cmd);
297
298 if (member(CMDS, cmd) != -1)
299 {
Arathorn78c08372019-12-11 20:14:23 +0100300 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
301 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
302 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100303 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100304 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100305
Arathorn78c08372019-12-11 20:14:23 +0100306 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100307 channelB[pl] -= ({ cmd });
308 else
309 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100310
Arathorn78c08372019-12-11 20:14:23 +0100311 ret = "Fuer '" + capitalize(pl) + "' besteht " +
312 (sizeof(channelB[pl])
Zesstraf5f10122020-08-13 21:47:33 +0200313 ? "folgender Bann: " + CountUp(channelB[pl]) + "."
Arathorn78c08372019-12-11 20:14:23 +0100314 : "kein Bann mehr.");
315
316 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100317 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100318 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100319
Zesstra18f2ad62020-08-13 21:45:57 +0200320 save_me_soon = 1;
Arathorn19459eb2019-11-30 00:45:51 +0100321 }
322 else
323 {
324 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100325 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100326 }
327 }
328 else
329 {
Arathorn78c08372019-12-11 20:14:23 +0100330 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100331 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200332 }
333 }
334 }
Arathorn78c08372019-12-11 20:14:23 +0100335 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200336 {
MG Mud User88f12472016-06-24 23:31:02 +0200337 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100338 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
339 {
340 string unused;
341 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
342 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
343 }
MG Mud User88f12472016-06-24 23:31:02 +0200344
Arathorn78c08372019-12-11 20:14:23 +0100345 int t = time() - last_reboot_time();
346
347 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
348 // die Funktionalitaet oefter benoetigt wird als nur hier.
349 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100350 if (t >= 86400)
351 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200352
Arathorn78c08372019-12-11 20:14:23 +0100353 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100354 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100355 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100356
Arathorn78c08372019-12-11 20:14:23 +0100357 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100358 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100359 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100360
361 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100362
MG Mud User88f12472016-06-24 23:31:02 +0200363 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100364 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100365 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100366 }
367 else
368 {
369 return;
370 }
MG Mud User88f12472016-06-24 23:31:02 +0200371
Arathorn78c08372019-12-11 20:14:23 +0100372 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
373 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
374 // nicht erfuellt sind.
375 if (stringp(ret) && sizeof(ret))
376 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200377}
378
379// setup() -- set up a channel and register it
380// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100381// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrad9ec04b2020-08-11 23:47:03 +0200382// adminflags, channelflags, description,supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100383private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200384{
Arathorn78c08372019-12-11 20:14:23 +0100385 string desc = "- Keine Beschreibung -";
Zesstrae19391f2020-08-09 13:40:12 +0200386 object supervisor = this_object();
Zesstra0caa8e42020-08-11 22:51:59 +0200387 int sv_recv, sv_send, sv_flags; // an den Supervisor weiterreichen
Zesstrad9ec04b2020-08-11 23:47:03 +0200388 int chflags;
Arathorn19459eb2019-11-30 00:45:51 +0100389
Arathorn78c08372019-12-11 20:14:23 +0100390 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
391 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200392
Arathorn78c08372019-12-11 20:14:23 +0100393 switch (sizeof(chinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200394 {
Arathorn78c08372019-12-11 20:14:23 +0100395 // Alle Fallthroughs in dem switch() sind Absicht.
Zesstra0caa8e42020-08-11 22:51:59 +0200396 default:
Zesstrad9ec04b2020-08-11 23:47:03 +0200397 if (stringp(chinfo[6]) && sizeof(chinfo[6]))
398 catch(supervisor = load_object(chinfo[6]); publish);
Zesstrae19391f2020-08-09 13:40:12 +0200399 if (!objectp(supervisor))
400 supervisor = this_object();
Zesstrad9ec04b2020-08-11 23:47:03 +0200401 case 6:
402 if (stringp(chinfo[5]))
403 desc = chinfo[5];
Arathorn19459eb2019-11-30 00:45:51 +0100404 case 5:
Zesstrad9ec04b2020-08-11 23:47:03 +0200405 chflags = to_int(chinfo[4]);
Arathorn19459eb2019-11-30 00:45:51 +0100406 case 4:
Zesstra0caa8e42020-08-11 22:51:59 +0200407 sv_flags = to_int(chinfo[3]);
Arathorn19459eb2019-11-30 00:45:51 +0100408 case 3:
Zesstra0caa8e42020-08-11 22:51:59 +0200409 sv_send = to_int(chinfo[2]);
Arathorn19459eb2019-11-30 00:45:51 +0100410 case 2:
Zesstra0caa8e42020-08-11 22:51:59 +0200411 sv_recv = to_int(chinfo[1]);
Arathorn19459eb2019-11-30 00:45:51 +0100412 break;
413
414 case 0:
Zesstra0caa8e42020-08-11 22:51:59 +0200415 case 1:
Arathorn19459eb2019-11-30 00:45:51 +0100416 return;
MG Mud User88f12472016-06-24 23:31:02 +0200417 }
Zesstra0caa8e42020-08-11 22:51:59 +0200418 // Zugriffsrechte im channel_supervisor konfigurieren. (Kann auch dieses
419 // Objekt selber sein...)
420 supervisor->ch_supervisor_setup(lower_case(chinfo[0]), sv_recv,
421 sv_send, sv_flags);
Zesstra2aeb6a82020-08-13 23:50:36 +0200422 // Wenn der channeld nur neugeladen wurde, aber das Mud nicht neugestartet,
423 // sind alle Ebenen noch da, weil sie im MEMORY liegen. Dann muessen wir das
424 // new() natuerlich ueberspringen.
425 if (!member(channels, lower_case(chinfo[0])))
MG Mud User88f12472016-06-24 23:31:02 +0200426 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200427 if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
428 {
429 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
430 dtime(time()), chinfo[0], supervisor));
431 }
MG Mud User88f12472016-06-24 23:31:02 +0200432 }
433 return;
434}
435
Arathorn78c08372019-12-11 20:14:23 +0100436private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200437{
Arathorn78c08372019-12-11 20:14:23 +0100438 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200439#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100440 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200441#else
Arathorn78c08372019-12-11 20:14:23 +0100442 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200443#endif
Arathorn19459eb2019-11-30 00:45:51 +0100444
Arathorn78c08372019-12-11 20:14:23 +0100445 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200446 return;
Arathorn19459eb2019-11-30 00:45:51 +0100447
Arathorn78c08372019-12-11 20:14:23 +0100448 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
449 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
450 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
451 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
452 foreach(string ch : old_explode(ch_list, "\n"))
453 {
454 if (ch[0]=='#')
455 continue;
456 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
457 }
MG Mud User88f12472016-06-24 23:31:02 +0200458}
459
Arathorn78c08372019-12-11 20:14:23 +0100460// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200461protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200462{
Zesstra2aeb6a82020-08-13 23:50:36 +0200463 int do_complete_init;
464
MG Mud User88f12472016-06-24 23:31:02 +0200465 seteuid(getuid());
466 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100467
Zesstrab7720dc2020-08-11 22:14:18 +0200468 // Altes channelC aus Savefiles konvertieren...
469 if (widthof(channelC) == 1)
470 {
471 mapping new = m_allocate(sizeof(channelC), 2);
472 foreach(string chname, mixed arr: channelC)
473 {
474 struct channel_base_s ch = (<channel_base_s> name: arr[0],
475 desc: arr[1]);
476 // die anderen beiden Werte bleiben 0
477 m_add(new, chname, ch, arr[2]);
478 }
479 channelC = new;
480 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200481
Zesstra2aeb6a82020-08-13 23:50:36 +0200482 /* Die aktiven Ebenen und die Channel-History wird nicht nur lokal sondern
483 * auch noch im Memory gespeichert, dadurch bleibt sie auch ueber ein Reload
484 * erhalten.
485 Der folgende Code versucht, den Zeiger auf die alten Mappings aus dem
486 Memory zu holen. Falls das nicht moeglich ist, wird ein neuer erzeugt und
487 gegebenenfalls fuer spaeter im Memory abgelegt. */
MG Mud User88f12472016-06-24 23:31:02 +0200488 // Hab ich die noetigen Rechte im Memory?
Zesstra2aeb6a82020-08-13 23:50:36 +0200489 if (MEMORY->HaveRights())
Arathorn19459eb2019-11-30 00:45:51 +0100490 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200491 // channelH und channels laden
492 channels = ({mapping}) MEMORY->Load("Channels");
493 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
494 if (!mappingp(channels))
495 {
496 // Mapping erzeugen
497 channels = ([]);
498 // und Zeiger auf das Mapping in den Memory schreiben
499 MEMORY->Save("Channels", channels);
500 do_complete_init = 1;
501 }
502 // Und das gleiche fuer die History
503 channelH = ({mapping}) MEMORY->Load("History");
504 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
Arathorn78c08372019-12-11 20:14:23 +0100505 if (!mappingp(channelH))
506 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200507 // Mapping erzeugen
MG Mud User88f12472016-06-24 23:31:02 +0200508 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200509 // und Zeiger auf das Mapping in den Memory schreiben
510 MEMORY->Save("History", channelH);
511 // In diesem Fall muessen die Ebenenhistories auch erzeugt werden, falls
512 // es aktive Ebenen gibt.
513 foreach(string chname: channels)
514 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200515 }
Arathorn19459eb2019-11-30 00:45:51 +0100516 }
517 else
518 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200519 // Keine Rechte im Memory, dann liegt das nur lokal und ist bei
520 // remove/destruct weg.
MG Mud User88f12472016-06-24 23:31:02 +0200521 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200522 channels = ([]);
523 do_complete_init = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200524 }
525
526 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100527 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
528
Zesstra2aeb6a82020-08-13 23:50:36 +0200529 // Das muss auch laufen, wenn wir die alten Ebenen aus dem MEMORY bekommen
530 // haben, weil es dafuer sorgt, dass das Mapping <admin> aus
531 // channel_supervisor wieder mit den Informationen aus .init befuellt wird,
532 // weil das nicht in MEMORY liegt (weil das vermutlich ein Grund ist, den
533 // channeld neuzuladen: zum neuen Einlesen der Ebenenrechte).
534 // initialize() und setup() koennen aber mit Ebenen umgehen, die es schon
535 // gibt
MG Mud User88f12472016-06-24 23:31:02 +0200536 initialize();
Arathorn78c08372019-12-11 20:14:23 +0100537
Zesstra2aeb6a82020-08-13 23:50:36 +0200538 // Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
539 // erledigen.
540 if (do_complete_init)
541 {
542 // <MasteR>-Ebene erstellen. Channeld wird Ebenenbesitzer und somit auch
543 // Zuhoerer, damit er auf Kommandos auf dieser Ebene reagieren kann.
544 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
545 // Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
546 // leider Pech gehabt.
547 users()->RegisterChannels();
548 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
549 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
550 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
551 // explizites call_other() auf this_object() gemacht, damit der
552 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
553 // einem externen.
554 this_object()->send(CMNAME, this_object(),
555 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
556 sizeof(channels),
557 CountUsers()));
558 }
559 else
560 {
561 this_object()->send(CMNAME, this_object(),
562 sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
563 sizeof(channels),
564 CountUsers()));
565 }
MG Mud User88f12472016-06-24 23:31:02 +0200566}
567
Arathorn78c08372019-12-11 20:14:23 +0100568varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200569{
Zesstra6ddbacb2020-08-13 21:43:28 +0200570 // Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
571 // zwischen 21.6h und 26.4h von jetzt.
572 set_next_reset(77760 + random(17280));
Zesstra26aaf1a2020-08-07 19:10:39 +0200573
Zesstra6ddbacb2020-08-13 21:43:28 +0200574 // inaktive Ebenen bereinigen
575 int timeout = INACTIVE_EXPIRE;
576 // Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
577 // inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
578 // bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
579 // Ebenen min. inaktiv bestehen.
580 while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
581 && timeout > MIN_INACTIVE_LIFETIME)
582 {
583 channelC = filter(channelC,
584 function int (string ch_name, mixed values)
Zesstra8f5102c2020-08-08 12:51:52 +0200585 {
Zesstra6ddbacb2020-08-13 21:43:28 +0200586 struct channel_base_s data = values[0];
587 int ts = values[1];
588 if (ts + timeout > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200589 return 1;
590 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
591 m_delete(channelH, ch_name);
592 return 0;
593 });
Zesstra6ddbacb2020-08-13 21:43:28 +0200594 // timeout halbieren und neu versuchen wenn noetig.
595 timeout /= 2;
596 }
597 // achja, speichern sollten wir uns ggf. auch noch.
MG Mud User88f12472016-06-24 23:31:02 +0200598 if (save_me_soon)
599 {
Arathorn19459eb2019-11-30 00:45:51 +0100600 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200601 save_object(CHANNEL_SAVE);
602 }
603}
604
Zesstra5856ada2020-08-13 21:52:47 +0200605varargs int remove(int silent)
606{
607 if (save_me_soon)
608 {
609 save_me_soon = 0;
610 save_object(CHANNEL_SAVE);
611 }
612 if (!silent)
613 {
614 this_object()->send(CMNAME, this_object(),
615 sprintf("remove() durch %O gerufen. Speichern und Ende.\n",
616 previous_object()));
617 }
618 destruct(this_object());
619 return 1;
620}
621
MG Mud User88f12472016-06-24 23:31:02 +0200622// name() - define the name of this object.
Arathorn19459eb2019-11-30 00:45:51 +0100623string name()
624{
625 return CMNAME;
626}
627
628string Name()
629{
630 return CMNAME;
631}
MG Mud User88f12472016-06-24 23:31:02 +0200632
Zesstra28986e12020-08-09 12:44:26 +0200633// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200634// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200635private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200636{
637 if (IsChannelMember(ch, m))
638 return E_ALREADY_JOINED;
639
Zesstrab7720dc2020-08-11 22:14:18 +0200640 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200641 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200642}
643
Zesstra52d5f8a2020-08-12 00:39:15 +0200644private void remove_all_members(struct channel_s ch)
645{
646 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
647 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
648 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
649 string chname = lower_case(ch.name);
650 foreach(object listener : ch.members)
651 {
652 string* pl_chans = listener->QueryProp(P_CHANNELS);
653 if (pointerp(pl_chans))
654 {
655 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
656 }
657 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
658 if (pointerp(pl_chans))
659 {
660 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
661 }
662 }
663}
664
Zesstra6ddbacb2020-08-13 21:43:28 +0200665private void delete_channel(string chname, int force);
666
Zesstraf87cb772020-08-10 11:14:45 +0200667// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
668// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200669// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
670// anwesend sind.
671private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200672{
Zesstra6ddbacb2020-08-13 21:43:28 +0200673 // Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
674 if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
675 {
676 log_file("CHANNEL",
677 sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
678 "deaktiviert.\n", dtime(time()), chname));
679 this_object()->send(CMNAME, this_object(),
680 sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
681 "deaktiviert.\n", chname));
682 delete_channel(chname, force);
683 return;
684 }
Zesstrab7720dc2020-08-11 22:14:18 +0200685 chname = lower_case(chname);
686 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200687 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200688 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200689 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200690 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200691 if (sizeof(ch.members))
692 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200693 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
694 if (!force)
695 {
696 raise_error(
697 sprintf("Attempt to deactivate channel %s with listeners.\n",
698 ch.name));
699 }
700 else
701 {
702 remove_all_members(ch);
703 }
Zesstrab7720dc2020-08-11 22:14:18 +0200704 }
Zesstraf87cb772020-08-10 11:14:45 +0200705 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
706 // einloggt, der die Ebene abonniert hat.
Zesstrab7720dc2020-08-11 22:14:18 +0200707 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
708 time());
Zesstraf87cb772020-08-10 11:14:45 +0200709
Zesstrab7720dc2020-08-11 22:14:18 +0200710 // aktive Ebene loeschen bzw. deaktivieren.
711 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200712 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
713 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
714 // channelC bereinigt wird.
715
716 stats["dispose"]++;
717 save_me_soon = 1;
718}
719
720// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200721// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
722// anwesend sind.
723private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200724{
Zesstrab7720dc2020-08-11 22:14:18 +0200725 chname = lower_case(chname);
726 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200727 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200728 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200729 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200730 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200731 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200732 {
733 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
734 if (!force)
735 {
736 raise_error(
737 sprintf("Attempt to delete channel %s with listeners.\n",
738 ch.name));
739 }
740 else
741 {
742 remove_all_members(ch);
743 }
744 }
Zesstraf87cb772020-08-10 11:14:45 +0200745 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200746 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200747 }
748 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstra6ddbacb2020-08-13 21:43:28 +0200749 m_delete(channelC, chname);
750 m_delete(channelH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200751 save_me_soon = 1;
752}
753
Zesstra5b7f2fc2020-08-10 02:09:13 +0200754// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
755// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200756private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200757{
758 if (!new_sv)
759 {
Zesstrab7720dc2020-08-11 22:14:18 +0200760 ch.members -= ({0});
761 if (sizeof(ch.members))
762 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200763 else
764 return 0; // kein neuer SV moeglich.
765 }
Zesstra9359fab2020-08-13 12:03:01 +0200766 // evtl. darf der supervisor aber nicht zu was anderes als dem creator
767 // wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
768 // Ebene ist.
769 if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
770 && new_sv != find_object(ch.creator))
771 || !IsChannelMember(ch, new_sv)
772 )
773 return 0;
774
Zesstrabf4f86d2020-08-12 00:56:17 +0200775 object old_sv = ch.supervisor;
776
Zesstrab7720dc2020-08-11 22:14:18 +0200777 ch.supervisor = new_sv;
Zesstrab7720dc2020-08-11 22:14:18 +0200778 ch.access_cl = symbol_function("check_ch_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200779
Zesstra5b7f2fc2020-08-10 02:09:13 +0200780 if (old_sv && new_sv
781 && !old_sv->QueryProp(P_INVIS)
782 && !new_sv->QueryProp(P_INVIS))
783 {
784 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
785 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
786 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
787 // explizites call_other() auf this_object() gemacht, damit der
788 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
789 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200790 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200791 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
792 MSG_EMOTE);
793 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200794 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200795 {
Zesstrab7720dc2020-08-11 22:14:18 +0200796 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200797 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
798 }
799 else if (new_sv && !new_sv->QueryProp(P_INVIS))
800 {
Zesstrab7720dc2020-08-11 22:14:18 +0200801 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200802 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
803 }
804 return 1;
805}
806
Zesstra56692c72020-08-09 13:03:10 +0200807// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
808// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200809// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
810// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200811// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200812private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200813{
Zesstra9359fab2020-08-13 12:03:01 +0200814 // Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
815 if (!ch.supervisor)
MG Mud User88f12472016-06-24 23:31:02 +0200816 {
Zesstra9359fab2020-08-13 12:03:01 +0200817 // Wenn der Wechsel des SV verboten ist, wird versucht, den
818 // urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
819 // machen.
820 if (ch.flags & CHF_FIXED_SUPERVISOR)
MG Mud User88f12472016-06-24 23:31:02 +0200821 {
Zesstra9359fab2020-08-13 12:03:01 +0200822 object sv;
823 string err=catch(sv=load_object(ch.creator);publish);
Arathorn78c08372019-12-11 20:14:23 +0100824 if (!err)
825 {
Zesstra9359fab2020-08-13 12:03:01 +0200826 // Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
827 // und kann dann wieder SV werden.
828 add_member(ch, sv);
829 if (!change_sv_object(ch, sv))
830 {
831 // ich wuesste nicht, was in change_sv_object in diesem Fall
832 // schiefgehen kann, daher einfach ein raise_error.
833 raise_error(sprintf("Supervisor von Channel %s konnte nicht "
834 "reaktiviert werden: %O\n",ch.name,sv));
835 }
Arathorn78c08372019-12-11 20:14:23 +0100836 }
Zesstra9359fab2020-08-13 12:03:01 +0200837 // wenns nicht geklappt hat, wird die Ebene deaktiviert.
Arathorn78c08372019-12-11 20:14:23 +0100838 else
839 {
Zesstra9359fab2020-08-13 12:03:01 +0200840 // Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
841 // urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
842 // solange der das nicht tut, ist weder die History zugaenglich, noch
843 // kann jemand sonst was damit machen. Wenn die inaktive Ebene
844 // irgendwann inkl. History expired wird, kann jemand anderes dann
845 // den Namen wieder verwenden und ein komplett neue Ebene erstellen.
846 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200847 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200848 sprintf("[%s] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200849 dtime(time()), ch.name, ch.supervisor, err));
Arathorn78c08372019-12-11 20:14:23 +0100850 return 0;
851 }
MG Mud User88f12472016-06-24 23:31:02 +0200852 }
Zesstra9359fab2020-08-13 12:03:01 +0200853 // Der normalfall ist aber, dass wir einfach einen supervisor aus dem
854 // Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
855 // change_sv_object().
856 else
Zesstra56692c72020-08-09 13:03:10 +0200857 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200858 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200859 {
Zesstra9359fab2020-08-13 12:03:01 +0200860 // wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
861 // Zuhoerer.
862 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200863 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200864 sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200865 dtime(time()), ch.name));
Zesstra5770ba62020-08-10 10:19:23 +0200866 return 0;
867 }
Zesstra56692c72020-08-09 13:03:10 +0200868 }
MG Mud User88f12472016-06-24 23:31:02 +0200869 }
Zesstra78310012020-08-09 12:21:48 +0200870 return 1;
871}
872
873// access() - check access by looking for the right argument types and
874// calling access closures respectively
875// SEE: new, join, leave, send, list, users
Zesstra78310012020-08-09 12:21:48 +0200876// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
877// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
878// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
879// als Erfolg und alles ueber >2 als privilegiert.)
Zesstra752ae7d2020-08-16 22:46:04 +0200880varargs private int access(struct channel_s ch, object user, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200881 string txt)
882{
Zesstrab7720dc2020-08-11 22:14:18 +0200883 if (!ch)
Zesstra78310012020-08-09 12:21:48 +0200884 return 0;
885
Zesstrafbfe6362020-08-09 13:30:21 +0200886 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
887 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra78310012020-08-09 12:21:48 +0200888 if ( !previous_object(1) || !extern_call() ||
889 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200890 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200891 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200892
Zesstra56692c72020-08-09 13:03:10 +0200893 // Nur dieses Objekt darf Meldungen im Namen anderer Objekte faken,
894 // ansonsten muss <pl> der Aufrufer sein.
Zesstra752ae7d2020-08-16 22:46:04 +0200895 if (!objectp(user) ||
896 ((previous_object(1) != user) &&
897 (previous_object(1) != this_object())))
Arathorn739a4fa2020-08-06 21:52:58 +0200898 return 0;
899
Zesstra752ae7d2020-08-16 22:46:04 +0200900 if (IsBanned(user, cmd))
Arathorn739a4fa2020-08-06 21:52:58 +0200901 return 0;
902
Zesstra56692c72020-08-09 13:03:10 +0200903 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
904 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200905 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200906 return 0;
907 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200908 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200909 return 1;
910
Zesstra6fe46cd2020-08-09 13:12:15 +0200911 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
912 // fuer EM+ aber nur, wenn der CHANNELD selber das SV-Objekt ist, damit
913 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
914 // CHANNELD als SV koennen aber natuerlich auch EM+ Zugriff verweigern.
Zesstra9359fab2020-08-13 12:03:01 +0200915 if (IS_ARCH(previous_object(1)) && ch.supervisor != this_object())
Zesstra6fe46cd2020-08-09 13:12:15 +0200916 return 1;
917
Zesstra752ae7d2020-08-16 22:46:04 +0200918 return funcall(ch.access_cl, lower_case(ch.name), user, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200919}
920
Arathorn78c08372019-12-11 20:14:23 +0100921// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200922// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100923// die dynamisch aktualisierte Infos ausgibt.
924// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
925// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
926// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200927#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200928public varargs int new(string ch_name, object owner, string|closure desc,
929 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200930{
Arathorn78c08372019-12-11 20:14:23 +0100931 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
932 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
933 // fuer eine neue Ebene eintragen.)
934 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200935 return E_ACCESS_DENIED;
936
Arathorn78c08372019-12-11 20:14:23 +0100937 // Kein gescheiter Channelname angegeben.
938 if (!stringp(ch_name) || !sizeof(ch_name))
939 return E_ACCESS_DENIED;
940
941 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
942 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
943 return E_ACCESS_DENIED;
944
945 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
946 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
947 // seinen Objektnamen matcht.
948 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
949 return E_ACCESS_DENIED;
950
Zesstraf20e8ba2020-08-12 01:56:30 +0200951 // Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
952 // existiert.
953 struct channel_base_s cbase = channelC[lower_case(ch_name)];
Zesstrab7720dc2020-08-11 22:14:18 +0200954 struct channel_s ch;
Zesstraf20e8ba2020-08-12 01:56:30 +0200955 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100956 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200957 // Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
958 // neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
959 // CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
960 // damit niemand inaktive Ebenen und deren History auf diesem Wege
961 // uebernehmen kann, d.h. den Supervisor ersetzen kann.
962 if ((cbase.flags & CHF_FIXED_SUPERVISOR)
963 && object_name(owner) != cbase.creator)
Arathorn19459eb2019-11-30 00:45:51 +0100964 return E_ACCESS_DENIED;
Zesstraf20e8ba2020-08-12 01:56:30 +0200965 // Alte Daten der Ebene uebernehmen
966 ch = to_struct(cbase, (<channel_s>));
967 // Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
968 if (desc)
969 ch.desc = desc;
970 // creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
971 // die Schreibweise (Gross-/Kleinschreibung) des Namens aus
972 // Konsistenzgruenden bei.
MG Mud User88f12472016-06-24 23:31:02 +0200973 }
Arathorn19459eb2019-11-30 00:45:51 +0100974 else
975 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200976 // Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
977 // Fehler...
978 if (!desc)
979 return E_ACCESS_DENIED;
980 // prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
981 // erzeugt.
Zesstrad9ec04b2020-08-11 23:47:03 +0200982 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
983 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +0100984 }
MG Mud User88f12472016-06-24 23:31:02 +0200985
Zesstrab7720dc2020-08-11 22:14:18 +0200986 ch_name = lower_case(ch_name);
987
988 ch.members = ({ owner });
Zesstra9359fab2020-08-13 12:03:01 +0200989 ch.supervisor = owner;
Zesstrad9ec04b2020-08-11 23:47:03 +0200990 // check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
991 // Nachricht gesendet werden darf oder nicht.
Zesstra798312a2020-08-12 23:34:19 +0200992 ch.access_cl = symbol_function("check_ch_access", owner);
Zesstrab7720dc2020-08-11 22:14:18 +0200993
994 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +0200995
Arathorn78c08372019-12-11 20:14:23 +0100996 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
997 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +0200998 if (!pointerp(channelH[ch_name]))
999 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001000
Zesstraf20e8ba2020-08-12 01:56:30 +02001001 // Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
1002 // jetzt auch weg.
1003 if (cbase)
1004 m_delete(channelC, ch_name);
1005
Arathorn78c08372019-12-11 20:14:23 +01001006 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
1007 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +02001008 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +02001009 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +01001010
Arathorn78c08372019-12-11 20:14:23 +01001011 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
1012 if (!owner->QueryProp(P_INVIS))
1013 {
1014 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1015 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1016 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1017 // explizites call_other() auf this_object() gemacht, damit der
1018 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1019 // einem externen.
1020 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +02001021 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +01001022 }
Arathorn19459eb2019-11-30 00:45:51 +01001023
MG Mud User88f12472016-06-24 23:31:02 +02001024 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +01001025 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001026}
1027
Arathorn78c08372019-12-11 20:14:23 +01001028// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
1029// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
1030// zweimal beitreten.)
Zesstra165157f2020-08-16 22:47:36 +02001031public int join(string chname, object joining)
MG Mud User88f12472016-06-24 23:31:02 +02001032{
Zesstrab7720dc2020-08-11 22:14:18 +02001033 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001034 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1035 zu erzeugen, weil access() mit extern_call() und previous_object()
1036 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1037 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001038 if (!funcall(#'access, ch, joining, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +01001039 return E_ACCESS_DENIED;
1040
Zesstra165157f2020-08-16 22:47:36 +02001041 int res = add_member(ch, joining);
Zesstrafb350dc2020-08-12 00:49:31 +02001042 if (res != 1)
1043 return res;
1044
Zesstra2b7ed1a2020-08-12 00:58:33 +02001045 // Wenn der <pl> der urspruengliche Ersteller der Ebene und kein Spieler
1046 // ist, wird er automatisch wieder zum Supervisor.
Zesstra165157f2020-08-16 22:47:36 +02001047 if (!query_once_interactive(joining)
1048 && object_name(joining) == ch.creator)
1049 change_sv_object(ch, joining);
Zesstra2b7ed1a2020-08-12 00:58:33 +02001050
Zesstrafb350dc2020-08-12 00:49:31 +02001051 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001052}
1053
Arathorn78c08372019-12-11 20:14:23 +01001054// Objekt <pl> verlaesst Ebene <ch>.
1055// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
1056// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
1057// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
1058// <pl> das Verlassen auf Grund eines Banns verboten sein.
1059// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
1060// noch ein Ebenenbesitzer eingetragen ist.
Zesstra165157f2020-08-16 22:47:36 +02001061public int leave(string chname, object leaving)
MG Mud User88f12472016-06-24 23:31:02 +02001062{
Zesstrab7720dc2020-08-11 22:14:18 +02001063 struct channel_s ch = channels[lower_case(chname)];
Zesstra877cb0a2020-08-10 02:10:21 +02001064
Zesstrab7720dc2020-08-11 22:14:18 +02001065 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +02001066
Zesstra165157f2020-08-16 22:47:36 +02001067 if (!IsChannelMember(ch, leaving))
Zesstra877cb0a2020-08-10 02:10:21 +02001068 return E_NOT_MEMBER;
1069
Arathorn739a4fa2020-08-06 21:52:58 +02001070 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1071 zu erzeugen, weil access() mit extern_call() und previous_object()
1072 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1073 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001074 if (!funcall(#'access, ch, leaving, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +01001075 return E_ACCESS_DENIED;
1076
Zesstrab7720dc2020-08-11 22:14:18 +02001077 // Dann mal den Zuhoerer raus.
Zesstra165157f2020-08-16 22:47:36 +02001078 ch.members -= ({leaving});
Zesstrae6d33852020-08-09 14:37:53 +02001079
Zesstra5b7f2fc2020-08-10 02:09:13 +02001080 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
1081 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +02001082 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +02001083 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001084 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1085 // diese verlassen hat. change_sv_object() waehlt per Default den
1086 // aeltesten Zuhoerer.
Zesstra165157f2020-08-16 22:47:36 +02001087 if (leaving == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001088 {
Zesstrabf4f86d2020-08-12 00:56:17 +02001089 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +01001090 }
MG Mud User88f12472016-06-24 23:31:02 +02001091 }
Zesstra137ea1c2020-08-10 02:15:20 +02001092 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1093 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1094 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1095 else
MG Mud User88f12472016-06-24 23:31:02 +02001096 {
Arathorn78c08372019-12-11 20:14:23 +01001097 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001098 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1099 // wird diese zerstoert, mit Meldung.
Zesstra165157f2020-08-16 22:47:36 +02001100 if (!leaving->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001101 {
1102 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1103 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1104 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1105 // explizites call_other() auf this_object() gemacht, damit der
1106 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1107 // einem externen.
Zesstra165157f2020-08-16 22:47:36 +02001108 this_object()->send(CMNAME, leaving,
Arathorn78c08372019-12-11 20:14:23 +01001109 "verlaesst als "+
Zesstra165157f2020-08-16 22:47:36 +02001110 (leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001111 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001112 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1113 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001114 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +02001115 }
Arathorn19459eb2019-11-30 00:45:51 +01001116 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001117}
1118
Arathorn78c08372019-12-11 20:14:23 +01001119// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1120// sofern <pl> dort senden darf.
Zesstra165157f2020-08-16 22:47:36 +02001121public varargs int send(string chname, object sender, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001122{
Zesstrab7720dc2020-08-11 22:14:18 +02001123 chname = lower_case(chname);
1124 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001125 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1126 zu erzeugen, weil access() mit extern_call() und previous_object()
1127 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1128 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001129 int a = funcall(#'access, ch, sender, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001130 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001131 return E_ACCESS_DENIED;
1132
Zesstra26aaf1a2020-08-07 19:10:39 +02001133 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
1134 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
1135 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
1136 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1137 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Zesstra165157f2020-08-16 22:47:36 +02001138 if (a < 2 && !IsChannelMember(ch, sender))
Arathorn19459eb2019-11-30 00:45:51 +01001139 return E_NOT_MEMBER;
1140
1141 if (!msg || !stringp(msg) || !sizeof(msg))
1142 return E_EMPTY_MESSAGE;
1143
Arathorn78c08372019-12-11 20:14:23 +01001144 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1145 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1146 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1147 // Nachricht ebenfalls erhaelt.
1148 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1149 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1150 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1151 // erzeugt und der Channeld als Besitzer angegeben.
1152 // Die Aufrufkette ist dann wie folgt:
1153 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001154 (ch.members)->ChannelMessage(
Zesstra165157f2020-08-16 22:47:36 +02001155 ({ ch.name, sender, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001156
Zesstrab7720dc2020-08-11 22:14:18 +02001157 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1158 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001159
Zesstrab7720dc2020-08-11 22:14:18 +02001160 channelH[chname] +=
1161 ({ ({ ch.name,
Zesstra556c54d2020-08-16 22:50:10 +02001162 (sender->QueryProp(P_INVIS)
Zesstra165157f2020-08-16 22:47:36 +02001163 ? "/(" + capitalize(getuid(sender)) + ")$"
Arathorn19459eb2019-11-30 00:45:51 +01001164 : "")
Zesstra556c54d2020-08-16 22:50:10 +02001165 + (sender->Name(WER, 2) || "<Unbekannt>"),
Arathorn19459eb2019-11-30 00:45:51 +01001166 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1167 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001168 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001169}
1170
Arathorn78c08372019-12-11 20:14:23 +01001171// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1172// oder einen Integer-Fehlercode
1173public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001174{
Arathorn78c08372019-12-11 20:14:23 +01001175 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001176 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001177 {
Arathorn739a4fa2020-08-06 21:52:58 +02001178 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1179 zu erzeugen, weil access() mit extern_call() und previous_object()
1180 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1181 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001182 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001183 {
Zesstrab7720dc2020-08-11 22:14:18 +02001184 ch.members = filter(ch.members, #'objectp);
1185 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1186 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001187 }
1188 }
Arathorn19459eb2019-11-30 00:45:51 +01001189
1190 if (!sizeof(chs))
1191 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001192 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001193}
1194
Arathorn78c08372019-12-11 20:14:23 +01001195// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1196// Rueckgabewerte:
1197// - den gefundenen Namen als String
1198// - String-Array, wenn es mehrere Treffer gibt
1199// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001200public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001201{
Zesstrab7720dc2020-08-11 22:14:18 +02001202 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001203
Arathorn78c08372019-12-11 20:14:23 +01001204 // Suchstring <ch> muss Formatanforderung erfuellen;
1205 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1206 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1207 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1208 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001209 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001210 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001211
Arathorn78c08372019-12-11 20:14:23 +01001212 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1213 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1214 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001215 string* chs = filter(m_indices(channels), function int (string ch_n) {
Arathorn739a4fa2020-08-06 21:52:58 +02001216 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1217 im Caller Stack zu erzeugen, weil access() mit
1218 extern_call() und previous_object() arbeitet und
1219 sichergestellt sein muss, dass das in jedem Fall das
1220 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001221 return ( stringp(regmatch(ch_n, "^"+chname)) &&
1222 funcall(#'access, channels[ch_n], pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001223 });
Arathorn19459eb2019-11-30 00:45:51 +01001224
Arathorn78c08372019-12-11 20:14:23 +01001225 int num_channels = sizeof(chs);
1226 if (num_channels > 1)
1227 return chs;
1228 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001229 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001230 else
1231 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001232}
1233
Arathorn78c08372019-12-11 20:14:23 +01001234// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001235public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001236{
Zesstrab7720dc2020-08-11 22:14:18 +02001237 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001238 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1239 zu erzeugen, weil access() mit extern_call() und previous_object()
1240 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1241 richtige ist. */
1242 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001243 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001244 else
Zesstrab7720dc2020-08-11 22:14:18 +02001245 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001246}
1247
Arathorn78c08372019-12-11 20:14:23 +01001248// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001249public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001250{
Zesstra26aaf1a2020-08-07 19:10:39 +02001251 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001252 if (previous_object() != this_object())
1253 {
Zesstrab7720dc2020-08-11 22:14:18 +02001254 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001255 pl != this_player() || this_player() != this_interactive() ||
1256 this_interactive() != previous_object() ||
1257 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001258 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001259 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001260
1261 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001262
Arathorn19459eb2019-11-30 00:45:51 +01001263 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001264}
1265
Arathorn78c08372019-12-11 20:14:23 +01001266// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001267public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001268{
Zesstra26aaf1a2020-08-07 19:10:39 +02001269 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001270 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001271 if (previous_object() != this_object())
1272 {
Zesstrab7720dc2020-08-11 22:14:18 +02001273 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001274 this_player() != this_interactive() ||
1275 this_interactive() != previous_object() ||
1276 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001277 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001278 }
Zesstrab7720dc2020-08-11 22:14:18 +02001279 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001280 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1281 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001282 if (pointerp(channelH[chname]))
1283 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001284
1285 return 0;
1286}