blob: 630093d463790087a3e785056a73a72b092309c7 [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
Zesstra46c564e2020-09-11 21:52:29 +0200117// Private Prototypen
118public int join(string chname, object joining);
119
MG Mud User88f12472016-06-24 23:31:02 +0200120
Arathorn78c08372019-12-11 20:14:23 +0100121/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
122// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
123private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200124{
Arathorn78c08372019-12-11 20:14:23 +0100125 object* userlist = ({});
Zesstrab7720dc2020-08-11 22:14:18 +0200126 foreach(string ch_name, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +0100127 {
Zesstrab7720dc2020-08-11 22:14:18 +0200128 userlist += ch.members;
Arathorn78c08372019-12-11 20:14:23 +0100129 }
130 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
131 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200132}
133
Arathorn78c08372019-12-11 20:14:23 +0100134// Ist das Objekt <sender> Abonnent der Ebene <ch>?
Zesstrab7720dc2020-08-11 22:14:18 +0200135private int IsChannelMember(struct channel_s ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200136{
Zesstrab7720dc2020-08-11 22:14:18 +0200137 return (member(ch.members, sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200138}
139
Arathorn78c08372019-12-11 20:14:23 +0100140// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
141private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200142{
Arathorn78c08372019-12-11 20:14:23 +0100143 if (objectp(ob))
144 ob = getuid(ob);
145 return(pointerp(channelB[ob]) &&
146 member(channelB[ob], command) != -1);
147}
MG Mud User88f12472016-06-24 23:31:02 +0200148
Arathorn78c08372019-12-11 20:14:23 +0100149private void banned(string plname, string* cmds, string res)
150{
151 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
152}
153
154#define TIMEOUT (time() - 60)
155
156// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
157// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
158// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
159// zurueckliegt, als "nicht gesperrt".
160private int IsNotBlocked(string* cmd)
161{
162 string* res = filter(cmd, function int (string str) {
163 return (Tcmd[str] < TIMEOUT);
164 });
165 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
166 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
167 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
168 if (sizeof(res) == sizeof(cmd)) {
169 foreach(string str : cmd) {
170 Tcmd[str] = time();
171 }
172 return 1;
173 }
174 return 0;
175}
176
177// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
178// zugelassen wird. Anforderungen:
179// 1) <cmd> muss Teilstring von <check> sein
180// 2) <cmd> muss am Anfang von <check> stehen
181// 3) <cmd> darf nicht laenger sein als <check>
182// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
183// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
184// cmd = "statistiker" oder cmd = "tist"
185// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
186private int IsValidChannelCommand(string cmd, string check) {
187 // Syntaxcheck (prueft Bedingungen 1 bis 3).
188 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
189 string* cmd_to_check;
190 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
191 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
192 // geben.
193 if ( cmd == "lust" )
194 cmd_to_check = ({"lag", "statistik", "uptime"});
195 else
196 cmd_to_check = ({cmd});
197 // Prueft die Zeitsperre (Bedingung 4).
198 return (IsNotBlocked(cmd_to_check));
199 }
200 return 0;
201}
202
203#define CH_NAME 0
204#define CH_SENDER 1
205#define CH_MSG 2
206#define CH_MSG_TYPE 3
207// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
208// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
209// und dieses per Callout an send() uebergeben.
Zesstrab7720dc2020-08-11 22:14:18 +0200210// Argument: ({channel.name, object pl, string msg, int type})
Arathorn78c08372019-12-11 20:14:23 +0100211// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
212// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
213// was aber bei einer private oder protected Funktion nicht moeglich waere.
214public void ChannelMessage(<string|object|int>* msg)
215{
216 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
217 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
218 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
219 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100220 return;
MG Mud User88f12472016-06-24 23:31:02 +0200221
Arathorn78c08372019-12-11 20:14:23 +0100222 float* lag;
223 int max, rekord;
224 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200225 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200226
Arathorn78c08372019-12-11 20:14:23 +0100227 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200228 {
Arathorn78c08372019-12-11 20:14:23 +0100229 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
230 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100231 }
Arathorn78c08372019-12-11 20:14:23 +0100232 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100233 {
MG Mud User88f12472016-06-24 23:31:02 +0200234 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
235 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100236 "%.1f%%/20s, %.1f%%/2s",
237 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100238 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
239 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100240 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200241 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100242 }
Arathorn78c08372019-12-11 20:14:23 +0100243 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200244 {
Arathorn78c08372019-12-11 20:14:23 +0100245 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100246 {
Arathorn78c08372019-12-11 20:14:23 +0100247 string unused;
248 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
249 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
250 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
251 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
252 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100253 }
254 else
255 {
Arathorn78c08372019-12-11 20:14:23 +0100256 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200257 }
Arathorn19459eb2019-11-30 00:45:51 +0100258 }
Arathorn78c08372019-12-11 20:14:23 +0100259 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200260 {
MG Mud User88f12472016-06-24 23:31:02 +0200261 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100262 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
263 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
264 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
265 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100266 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
267 }
Arathorn78c08372019-12-11 20:14:23 +0100268 // Ebenenaktion beginnt mit "bann"?
269 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200270 {
271 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100272
273 if (mesg == "bann")
274 {
275 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200276 {
Arathorn78c08372019-12-11 20:14:23 +0100277 ret = "Fuer folgende Spieler besteht ein Bann: ";
278 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
279 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
280 string* banlist = ({});
281 foreach(string plname, string* banned_cmds : channelB) {
282 banlist += ({ sprintf("%s [%s]",
283 capitalize(plname), implode(banned_cmds, ", "))});
284 }
285 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200286 }
287 else
288 {
Arathorn19459eb2019-11-30 00:45:51 +0100289 ret = "Zur Zeit ist kein Bann aktiv.";
290 }
291 }
292 else
293 {
Arathorn78c08372019-12-11 20:14:23 +0100294 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
295 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100296 {
297 pl = lower_case(pl);
298 cmd = lower_case(cmd);
299
300 if (member(CMDS, cmd) != -1)
301 {
Arathorn78c08372019-12-11 20:14:23 +0100302 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
303 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
304 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100305 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100306 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100307
Arathorn78c08372019-12-11 20:14:23 +0100308 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100309 channelB[pl] -= ({ cmd });
310 else
311 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100312
Arathorn78c08372019-12-11 20:14:23 +0100313 ret = "Fuer '" + capitalize(pl) + "' besteht " +
314 (sizeof(channelB[pl])
Zesstraf5f10122020-08-13 21:47:33 +0200315 ? "folgender Bann: " + CountUp(channelB[pl]) + "."
Arathorn78c08372019-12-11 20:14:23 +0100316 : "kein Bann mehr.");
317
318 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100319 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100320 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100321
Zesstra18f2ad62020-08-13 21:45:57 +0200322 save_me_soon = 1;
Arathorn19459eb2019-11-30 00:45:51 +0100323 }
324 else
325 {
326 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100327 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100328 }
329 }
330 else
331 {
Arathorn78c08372019-12-11 20:14:23 +0100332 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100333 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200334 }
335 }
336 }
Arathorn78c08372019-12-11 20:14:23 +0100337 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200338 {
MG Mud User88f12472016-06-24 23:31:02 +0200339 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100340 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
341 {
342 string unused;
343 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
344 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
345 }
MG Mud User88f12472016-06-24 23:31:02 +0200346
Arathorn78c08372019-12-11 20:14:23 +0100347 int t = time() - last_reboot_time();
348
349 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
350 // die Funktionalitaet oefter benoetigt wird als nur hier.
351 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100352 if (t >= 86400)
353 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200354
Arathorn78c08372019-12-11 20:14:23 +0100355 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100356 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100357 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100358
Arathorn78c08372019-12-11 20:14:23 +0100359 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100360 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100361 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100362
363 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100364
MG Mud User88f12472016-06-24 23:31:02 +0200365 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100366 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100367 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100368 }
369 else
370 {
371 return;
372 }
MG Mud User88f12472016-06-24 23:31:02 +0200373
Arathorn78c08372019-12-11 20:14:23 +0100374 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
375 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
376 // nicht erfuellt sind.
377 if (stringp(ret) && sizeof(ret))
378 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200379}
380
381// setup() -- set up a channel and register it
382// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100383// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrad9ec04b2020-08-11 23:47:03 +0200384// adminflags, channelflags, description,supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100385private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200386{
Arathorn78c08372019-12-11 20:14:23 +0100387 string desc = "- Keine Beschreibung -";
Zesstrae19391f2020-08-09 13:40:12 +0200388 object supervisor = this_object();
Zesstra0caa8e42020-08-11 22:51:59 +0200389 int sv_recv, sv_send, sv_flags; // an den Supervisor weiterreichen
Zesstrad9ec04b2020-08-11 23:47:03 +0200390 int chflags;
Arathorn19459eb2019-11-30 00:45:51 +0100391
Arathorn78c08372019-12-11 20:14:23 +0100392 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
393 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200394
Arathorn78c08372019-12-11 20:14:23 +0100395 switch (sizeof(chinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200396 {
Arathorn78c08372019-12-11 20:14:23 +0100397 // Alle Fallthroughs in dem switch() sind Absicht.
Zesstra0caa8e42020-08-11 22:51:59 +0200398 default:
Zesstrad9ec04b2020-08-11 23:47:03 +0200399 if (stringp(chinfo[6]) && sizeof(chinfo[6]))
400 catch(supervisor = load_object(chinfo[6]); publish);
Zesstrae19391f2020-08-09 13:40:12 +0200401 if (!objectp(supervisor))
402 supervisor = this_object();
Zesstrad9ec04b2020-08-11 23:47:03 +0200403 case 6:
404 if (stringp(chinfo[5]))
405 desc = chinfo[5];
Arathorn19459eb2019-11-30 00:45:51 +0100406 case 5:
Zesstrad9ec04b2020-08-11 23:47:03 +0200407 chflags = to_int(chinfo[4]);
Arathorn19459eb2019-11-30 00:45:51 +0100408 case 4:
Zesstra0caa8e42020-08-11 22:51:59 +0200409 sv_flags = to_int(chinfo[3]);
Arathorn19459eb2019-11-30 00:45:51 +0100410 case 3:
Zesstra0caa8e42020-08-11 22:51:59 +0200411 sv_send = to_int(chinfo[2]);
Arathorn19459eb2019-11-30 00:45:51 +0100412 case 2:
Zesstra0caa8e42020-08-11 22:51:59 +0200413 sv_recv = to_int(chinfo[1]);
Arathorn19459eb2019-11-30 00:45:51 +0100414 break;
415
416 case 0:
Zesstra0caa8e42020-08-11 22:51:59 +0200417 case 1:
Arathorn19459eb2019-11-30 00:45:51 +0100418 return;
MG Mud User88f12472016-06-24 23:31:02 +0200419 }
Zesstra0caa8e42020-08-11 22:51:59 +0200420 // Zugriffsrechte im channel_supervisor konfigurieren. (Kann auch dieses
421 // Objekt selber sein...)
422 supervisor->ch_supervisor_setup(lower_case(chinfo[0]), sv_recv,
423 sv_send, sv_flags);
Zesstra2aeb6a82020-08-13 23:50:36 +0200424 // Wenn der channeld nur neugeladen wurde, aber das Mud nicht neugestartet,
425 // sind alle Ebenen noch da, weil sie im MEMORY liegen. Dann muessen wir das
426 // new() natuerlich ueberspringen.
427 if (!member(channels, lower_case(chinfo[0])))
MG Mud User88f12472016-06-24 23:31:02 +0200428 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200429 if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
430 {
431 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
432 dtime(time()), chinfo[0], supervisor));
433 }
MG Mud User88f12472016-06-24 23:31:02 +0200434 }
Zesstra46c564e2020-09-11 21:52:29 +0200435 else
436 {
437 // aber auch falls die Ebene (noch) existiert: wenn der channeld
438 // Supervisor sein soll, muss er die Ebene in jedem Fall
439 // (neu) betreten. Dabei wird er dann wieder Supervisor.
440 // Das Recht hat er immer und access() erlaubt das sogar vor dem Pruefen,
441 // ob es einen Supervisor gibt (den es jetzt gerade nicht gibt fuer
442 // Ebenen, in denen der CHANNELD supervisor war).
443 if (supervisor == this_object())
444 join(chinfo[0], this_object());
445 }
MG Mud User88f12472016-06-24 23:31:02 +0200446 return;
447}
448
Arathorn78c08372019-12-11 20:14:23 +0100449private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200450{
Arathorn78c08372019-12-11 20:14:23 +0100451 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200452#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100453 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200454#else
Arathorn78c08372019-12-11 20:14:23 +0100455 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200456#endif
Arathorn19459eb2019-11-30 00:45:51 +0100457
Arathorn78c08372019-12-11 20:14:23 +0100458 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200459 return;
Arathorn19459eb2019-11-30 00:45:51 +0100460
Arathorn78c08372019-12-11 20:14:23 +0100461 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
462 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
463 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
464 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
465 foreach(string ch : old_explode(ch_list, "\n"))
466 {
467 if (ch[0]=='#')
468 continue;
469 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
470 }
MG Mud User88f12472016-06-24 23:31:02 +0200471}
472
Arathorn78c08372019-12-11 20:14:23 +0100473// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200474protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200475{
Zesstra2aeb6a82020-08-13 23:50:36 +0200476 int do_complete_init;
477
MG Mud User88f12472016-06-24 23:31:02 +0200478 seteuid(getuid());
479 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100480
Zesstrab7720dc2020-08-11 22:14:18 +0200481 // Altes channelC aus Savefiles konvertieren...
482 if (widthof(channelC) == 1)
483 {
484 mapping new = m_allocate(sizeof(channelC), 2);
485 foreach(string chname, mixed arr: channelC)
486 {
487 struct channel_base_s ch = (<channel_base_s> name: arr[0],
488 desc: arr[1]);
489 // die anderen beiden Werte bleiben 0
490 m_add(new, chname, ch, arr[2]);
491 }
492 channelC = new;
493 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200494
Zesstra2aeb6a82020-08-13 23:50:36 +0200495 /* Die aktiven Ebenen und die Channel-History wird nicht nur lokal sondern
496 * auch noch im Memory gespeichert, dadurch bleibt sie auch ueber ein Reload
497 * erhalten.
498 Der folgende Code versucht, den Zeiger auf die alten Mappings aus dem
499 Memory zu holen. Falls das nicht moeglich ist, wird ein neuer erzeugt und
500 gegebenenfalls fuer spaeter im Memory abgelegt. */
MG Mud User88f12472016-06-24 23:31:02 +0200501 // Hab ich die noetigen Rechte im Memory?
Zesstra2aeb6a82020-08-13 23:50:36 +0200502 if (MEMORY->HaveRights())
Arathorn19459eb2019-11-30 00:45:51 +0100503 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200504 // channelH und channels laden
505 channels = ({mapping}) MEMORY->Load("Channels");
506 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
507 if (!mappingp(channels))
508 {
509 // Mapping erzeugen
510 channels = ([]);
511 // und Zeiger auf das Mapping in den Memory schreiben
512 MEMORY->Save("Channels", channels);
513 do_complete_init = 1;
514 }
515 // Und das gleiche fuer die History
516 channelH = ({mapping}) MEMORY->Load("History");
517 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
Arathorn78c08372019-12-11 20:14:23 +0100518 if (!mappingp(channelH))
519 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200520 // Mapping erzeugen
MG Mud User88f12472016-06-24 23:31:02 +0200521 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200522 // und Zeiger auf das Mapping in den Memory schreiben
523 MEMORY->Save("History", channelH);
524 // In diesem Fall muessen die Ebenenhistories auch erzeugt werden, falls
525 // es aktive Ebenen gibt.
526 foreach(string chname: channels)
527 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200528 }
Arathorn19459eb2019-11-30 00:45:51 +0100529 }
530 else
531 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200532 // Keine Rechte im Memory, dann liegt das nur lokal und ist bei
533 // remove/destruct weg.
MG Mud User88f12472016-06-24 23:31:02 +0200534 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200535 channels = ([]);
536 do_complete_init = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200537 }
538
539 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100540 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
541
Zesstra2aeb6a82020-08-13 23:50:36 +0200542 // Das muss auch laufen, wenn wir die alten Ebenen aus dem MEMORY bekommen
543 // haben, weil es dafuer sorgt, dass das Mapping <admin> aus
544 // channel_supervisor wieder mit den Informationen aus .init befuellt wird,
545 // weil das nicht in MEMORY liegt (weil das vermutlich ein Grund ist, den
546 // channeld neuzuladen: zum neuen Einlesen der Ebenenrechte).
Zesstra46c564e2020-09-11 21:52:29 +0200547 // Ausserdem ist der channeld selber ja ein neues Objekt und trotz MEMORY
548 // nirgendwo mehr als Listener/Supervisor eingetragen, wo er eingetragen
549 // sein sollte...
Zesstra2aeb6a82020-08-13 23:50:36 +0200550 // initialize() und setup() koennen aber mit Ebenen umgehen, die es schon
551 // gibt
MG Mud User88f12472016-06-24 23:31:02 +0200552 initialize();
Arathorn78c08372019-12-11 20:14:23 +0100553
Zesstra2aeb6a82020-08-13 23:50:36 +0200554 // Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
555 // erledigen.
556 if (do_complete_init)
557 {
558 // <MasteR>-Ebene erstellen. Channeld wird Ebenenbesitzer und somit auch
559 // Zuhoerer, damit er auf Kommandos auf dieser Ebene reagieren kann.
560 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
561 // Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
562 // leider Pech gehabt.
563 users()->RegisterChannels();
564 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
565 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
566 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
567 // explizites call_other() auf this_object() gemacht, damit der
568 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
569 // einem externen.
570 this_object()->send(CMNAME, this_object(),
571 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
572 sizeof(channels),
573 CountUsers()));
574 }
575 else
576 {
577 this_object()->send(CMNAME, this_object(),
578 sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
579 sizeof(channels),
580 CountUsers()));
581 }
MG Mud User88f12472016-06-24 23:31:02 +0200582}
583
Arathorn78c08372019-12-11 20:14:23 +0100584varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200585{
Zesstra6ddbacb2020-08-13 21:43:28 +0200586 // Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
587 // zwischen 21.6h und 26.4h von jetzt.
588 set_next_reset(77760 + random(17280));
Zesstra26aaf1a2020-08-07 19:10:39 +0200589
Zesstra6ddbacb2020-08-13 21:43:28 +0200590 // inaktive Ebenen bereinigen
591 int timeout = INACTIVE_EXPIRE;
592 // Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
593 // inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
594 // bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
595 // Ebenen min. inaktiv bestehen.
596 while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
597 && timeout > MIN_INACTIVE_LIFETIME)
598 {
599 channelC = filter(channelC,
600 function int (string ch_name, mixed values)
Zesstra8f5102c2020-08-08 12:51:52 +0200601 {
Zesstra6ddbacb2020-08-13 21:43:28 +0200602 struct channel_base_s data = values[0];
603 int ts = values[1];
604 if (ts + timeout > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200605 return 1;
606 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
607 m_delete(channelH, ch_name);
608 return 0;
609 });
Zesstra6ddbacb2020-08-13 21:43:28 +0200610 // timeout halbieren und neu versuchen wenn noetig.
611 timeout /= 2;
612 }
613 // achja, speichern sollten wir uns ggf. auch noch.
MG Mud User88f12472016-06-24 23:31:02 +0200614 if (save_me_soon)
615 {
Arathorn19459eb2019-11-30 00:45:51 +0100616 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200617 save_object(CHANNEL_SAVE);
618 }
619}
620
Zesstra5856ada2020-08-13 21:52:47 +0200621varargs int remove(int silent)
622{
623 if (save_me_soon)
624 {
625 save_me_soon = 0;
626 save_object(CHANNEL_SAVE);
627 }
628 if (!silent)
629 {
630 this_object()->send(CMNAME, this_object(),
631 sprintf("remove() durch %O gerufen. Speichern und Ende.\n",
632 previous_object()));
633 }
634 destruct(this_object());
635 return 1;
636}
637
MG Mud User88f12472016-06-24 23:31:02 +0200638// name() - define the name of this object.
Arathorn19459eb2019-11-30 00:45:51 +0100639string name()
640{
641 return CMNAME;
642}
643
644string Name()
645{
646 return CMNAME;
647}
MG Mud User88f12472016-06-24 23:31:02 +0200648
Zesstra28986e12020-08-09 12:44:26 +0200649// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200650// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200651private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200652{
653 if (IsChannelMember(ch, m))
654 return E_ALREADY_JOINED;
655
Zesstrab7720dc2020-08-11 22:14:18 +0200656 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200657 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200658}
659
Zesstra52d5f8a2020-08-12 00:39:15 +0200660private void remove_all_members(struct channel_s ch)
661{
662 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
663 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
664 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
665 string chname = lower_case(ch.name);
666 foreach(object listener : ch.members)
667 {
668 string* pl_chans = listener->QueryProp(P_CHANNELS);
669 if (pointerp(pl_chans))
670 {
671 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
672 }
673 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
674 if (pointerp(pl_chans))
675 {
676 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
677 }
678 }
679}
680
Zesstra6ddbacb2020-08-13 21:43:28 +0200681private void delete_channel(string chname, int force);
682
Zesstraf87cb772020-08-10 11:14:45 +0200683// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
684// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200685// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
686// anwesend sind.
687private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200688{
Zesstra6ddbacb2020-08-13 21:43:28 +0200689 // Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
690 if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
691 {
692 log_file("CHANNEL",
693 sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
694 "deaktiviert.\n", dtime(time()), chname));
695 this_object()->send(CMNAME, this_object(),
696 sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
697 "deaktiviert.\n", chname));
698 delete_channel(chname, force);
699 return;
700 }
Zesstrab7720dc2020-08-11 22:14:18 +0200701 chname = lower_case(chname);
702 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200703 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200704 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200705 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200706 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200707 if (sizeof(ch.members))
708 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200709 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
710 if (!force)
711 {
712 raise_error(
713 sprintf("Attempt to deactivate channel %s with listeners.\n",
714 ch.name));
715 }
716 else
717 {
718 remove_all_members(ch);
719 }
Zesstrab7720dc2020-08-11 22:14:18 +0200720 }
Zesstraf87cb772020-08-10 11:14:45 +0200721 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
722 // einloggt, der die Ebene abonniert hat.
Zesstrab7720dc2020-08-11 22:14:18 +0200723 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
724 time());
Zesstraf87cb772020-08-10 11:14:45 +0200725
Zesstrab7720dc2020-08-11 22:14:18 +0200726 // aktive Ebene loeschen bzw. deaktivieren.
727 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200728 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
729 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
730 // channelC bereinigt wird.
731
732 stats["dispose"]++;
733 save_me_soon = 1;
734}
735
736// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200737// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
738// anwesend sind.
739private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200740{
Zesstrab7720dc2020-08-11 22:14:18 +0200741 chname = lower_case(chname);
742 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200743 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200744 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200745 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200746 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200747 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200748 {
749 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
750 if (!force)
751 {
752 raise_error(
753 sprintf("Attempt to delete channel %s with listeners.\n",
754 ch.name));
755 }
756 else
757 {
758 remove_all_members(ch);
759 }
760 }
Zesstraf87cb772020-08-10 11:14:45 +0200761 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200762 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200763 }
764 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstra6ddbacb2020-08-13 21:43:28 +0200765 m_delete(channelC, chname);
766 m_delete(channelH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200767 save_me_soon = 1;
768}
769
Zesstra5b7f2fc2020-08-10 02:09:13 +0200770// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
771// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200772private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200773{
774 if (!new_sv)
775 {
Zesstrab7720dc2020-08-11 22:14:18 +0200776 ch.members -= ({0});
777 if (sizeof(ch.members))
778 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200779 else
780 return 0; // kein neuer SV moeglich.
781 }
Zesstra9359fab2020-08-13 12:03:01 +0200782 // evtl. darf der supervisor aber nicht zu was anderes als dem creator
783 // wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
784 // Ebene ist.
785 if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
786 && new_sv != find_object(ch.creator))
787 || !IsChannelMember(ch, new_sv)
788 )
789 return 0;
790
Zesstrabf4f86d2020-08-12 00:56:17 +0200791 object old_sv = ch.supervisor;
792
Zesstrab7720dc2020-08-11 22:14:18 +0200793 ch.supervisor = new_sv;
Zesstrab7720dc2020-08-11 22:14:18 +0200794 ch.access_cl = symbol_function("check_ch_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200795
Zesstra5b7f2fc2020-08-10 02:09:13 +0200796 if (old_sv && new_sv
797 && !old_sv->QueryProp(P_INVIS)
798 && !new_sv->QueryProp(P_INVIS))
799 {
800 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
801 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
802 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
803 // explizites call_other() auf this_object() gemacht, damit der
804 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
805 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200806 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200807 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
808 MSG_EMOTE);
809 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200810 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200811 {
Zesstrab7720dc2020-08-11 22:14:18 +0200812 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200813 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
814 }
815 else if (new_sv && !new_sv->QueryProp(P_INVIS))
816 {
Zesstrab7720dc2020-08-11 22:14:18 +0200817 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200818 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
819 }
820 return 1;
821}
822
Zesstra56692c72020-08-09 13:03:10 +0200823// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
824// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200825// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
826// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200827// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200828private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200829{
Zesstra9359fab2020-08-13 12:03:01 +0200830 // Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
831 if (!ch.supervisor)
MG Mud User88f12472016-06-24 23:31:02 +0200832 {
Zesstra9359fab2020-08-13 12:03:01 +0200833 // Wenn der Wechsel des SV verboten ist, wird versucht, den
834 // urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
835 // machen.
836 if (ch.flags & CHF_FIXED_SUPERVISOR)
MG Mud User88f12472016-06-24 23:31:02 +0200837 {
Zesstra9359fab2020-08-13 12:03:01 +0200838 object sv;
839 string err=catch(sv=load_object(ch.creator);publish);
Arathorn78c08372019-12-11 20:14:23 +0100840 if (!err)
841 {
Zesstra9359fab2020-08-13 12:03:01 +0200842 // Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
843 // und kann dann wieder SV werden.
844 add_member(ch, sv);
845 if (!change_sv_object(ch, sv))
846 {
847 // ich wuesste nicht, was in change_sv_object in diesem Fall
848 // schiefgehen kann, daher einfach ein raise_error.
849 raise_error(sprintf("Supervisor von Channel %s konnte nicht "
850 "reaktiviert werden: %O\n",ch.name,sv));
851 }
Arathorn78c08372019-12-11 20:14:23 +0100852 }
Zesstra9359fab2020-08-13 12:03:01 +0200853 // wenns nicht geklappt hat, wird die Ebene deaktiviert.
Arathorn78c08372019-12-11 20:14:23 +0100854 else
855 {
Zesstra9359fab2020-08-13 12:03:01 +0200856 // Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
857 // urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
858 // solange der das nicht tut, ist weder die History zugaenglich, noch
859 // kann jemand sonst was damit machen. Wenn die inaktive Ebene
860 // irgendwann inkl. History expired wird, kann jemand anderes dann
861 // den Namen wieder verwenden und ein komplett neue Ebene erstellen.
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] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200865 dtime(time()), ch.name, ch.supervisor, err));
Arathorn78c08372019-12-11 20:14:23 +0100866 return 0;
867 }
MG Mud User88f12472016-06-24 23:31:02 +0200868 }
Zesstra9359fab2020-08-13 12:03:01 +0200869 // Der normalfall ist aber, dass wir einfach einen supervisor aus dem
870 // Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
871 // change_sv_object().
872 else
Zesstra56692c72020-08-09 13:03:10 +0200873 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200874 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200875 {
Zesstra9359fab2020-08-13 12:03:01 +0200876 // wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
877 // Zuhoerer.
878 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200879 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200880 sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200881 dtime(time()), ch.name));
Zesstra5770ba62020-08-10 10:19:23 +0200882 return 0;
883 }
Zesstra56692c72020-08-09 13:03:10 +0200884 }
MG Mud User88f12472016-06-24 23:31:02 +0200885 }
Zesstra78310012020-08-09 12:21:48 +0200886 return 1;
887}
888
889// access() - check access by looking for the right argument types and
890// calling access closures respectively
891// SEE: new, join, leave, send, list, users
Zesstra78310012020-08-09 12:21:48 +0200892// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
893// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
894// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
895// als Erfolg und alles ueber >2 als privilegiert.)
Zesstra752ae7d2020-08-16 22:46:04 +0200896varargs private int access(struct channel_s ch, object user, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200897 string txt)
898{
Zesstrab7720dc2020-08-11 22:14:18 +0200899 if (!ch)
Zesstra78310012020-08-09 12:21:48 +0200900 return 0;
901
Zesstra46c564e2020-09-11 21:52:29 +0200902 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
Zesstrafbfe6362020-08-09 13:30:21 +0200903 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra46c564e2020-09-11 21:52:29 +0200904 // Ausserdem duerfen sie auch alles andere machen unter Umgehung aller
905 // Supervisoren. (z.B. kann dieses Objekt sogar Meldungen im Namen anderer
906 // Objekte faken)
907 // Die Pruefung erfolgt absichtlich vor assert_supervisor(), damit der
908 // CHANNELD z.b. Ebenen re-joinen kann und dort wieder supervisor werden
909 // kann.
Zesstra78310012020-08-09 12:21:48 +0200910 if ( !previous_object(1) || !extern_call() ||
911 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200912 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200913 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200914
Zesstra46c564e2020-09-11 21:52:29 +0200915 // Objekte duerfen keine Meldungen im Namen anderer Objekte faken, d.h. der
916 // vermeintliche <user> muss auch der Aufrufer sein.
917 if (!objectp(user) || previous_object(1) != user)
Arathorn739a4fa2020-08-06 21:52:58 +0200918 return 0;
919
Zesstra752ae7d2020-08-16 22:46:04 +0200920 if (IsBanned(user, cmd))
Arathorn739a4fa2020-08-06 21:52:58 +0200921 return 0;
922
Zesstra56692c72020-08-09 13:03:10 +0200923 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
924 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200925 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200926 return 0;
927 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200928 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200929 return 1;
930
Zesstra6fe46cd2020-08-09 13:12:15 +0200931 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
932 // fuer EM+ aber nur, wenn der CHANNELD selber das SV-Objekt ist, damit
933 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
934 // CHANNELD als SV koennen aber natuerlich auch EM+ Zugriff verweigern.
Zesstra9359fab2020-08-13 12:03:01 +0200935 if (IS_ARCH(previous_object(1)) && ch.supervisor != this_object())
Zesstra6fe46cd2020-08-09 13:12:15 +0200936 return 1;
937
Zesstra752ae7d2020-08-16 22:46:04 +0200938 return funcall(ch.access_cl, lower_case(ch.name), user, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200939}
940
Arathorn78c08372019-12-11 20:14:23 +0100941// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200942// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100943// die dynamisch aktualisierte Infos ausgibt.
944// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
945// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
946// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200947#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200948public varargs int new(string ch_name, object owner, string|closure desc,
949 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200950{
Arathorn78c08372019-12-11 20:14:23 +0100951 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
952 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
953 // fuer eine neue Ebene eintragen.)
954 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200955 return E_ACCESS_DENIED;
956
Arathorn78c08372019-12-11 20:14:23 +0100957 // Kein gescheiter Channelname angegeben.
958 if (!stringp(ch_name) || !sizeof(ch_name))
959 return E_ACCESS_DENIED;
960
961 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
962 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
963 return E_ACCESS_DENIED;
964
965 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
966 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
967 // seinen Objektnamen matcht.
968 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
969 return E_ACCESS_DENIED;
970
Zesstraf20e8ba2020-08-12 01:56:30 +0200971 // Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
972 // existiert.
973 struct channel_base_s cbase = channelC[lower_case(ch_name)];
Zesstrab7720dc2020-08-11 22:14:18 +0200974 struct channel_s ch;
Zesstraf20e8ba2020-08-12 01:56:30 +0200975 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100976 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200977 // Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
978 // neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
979 // CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
980 // damit niemand inaktive Ebenen und deren History auf diesem Wege
981 // uebernehmen kann, d.h. den Supervisor ersetzen kann.
982 if ((cbase.flags & CHF_FIXED_SUPERVISOR)
983 && object_name(owner) != cbase.creator)
Arathorn19459eb2019-11-30 00:45:51 +0100984 return E_ACCESS_DENIED;
Zesstraf20e8ba2020-08-12 01:56:30 +0200985 // Alte Daten der Ebene uebernehmen
986 ch = to_struct(cbase, (<channel_s>));
987 // Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
988 if (desc)
989 ch.desc = desc;
990 // creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
991 // die Schreibweise (Gross-/Kleinschreibung) des Namens aus
992 // Konsistenzgruenden bei.
MG Mud User88f12472016-06-24 23:31:02 +0200993 }
Arathorn19459eb2019-11-30 00:45:51 +0100994 else
995 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200996 // Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
997 // Fehler...
998 if (!desc)
999 return E_ACCESS_DENIED;
1000 // prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
1001 // erzeugt.
Zesstrad9ec04b2020-08-11 23:47:03 +02001002 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
1003 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +01001004 }
MG Mud User88f12472016-06-24 23:31:02 +02001005
Zesstrab7720dc2020-08-11 22:14:18 +02001006 ch_name = lower_case(ch_name);
1007
1008 ch.members = ({ owner });
Zesstra9359fab2020-08-13 12:03:01 +02001009 ch.supervisor = owner;
Zesstrad9ec04b2020-08-11 23:47:03 +02001010 // check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
1011 // Nachricht gesendet werden darf oder nicht.
Zesstra798312a2020-08-12 23:34:19 +02001012 ch.access_cl = symbol_function("check_ch_access", owner);
Zesstrab7720dc2020-08-11 22:14:18 +02001013
1014 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +02001015
Arathorn78c08372019-12-11 20:14:23 +01001016 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
1017 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +02001018 if (!pointerp(channelH[ch_name]))
1019 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001020
Zesstraf20e8ba2020-08-12 01:56:30 +02001021 // Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
1022 // jetzt auch weg.
1023 if (cbase)
1024 m_delete(channelC, ch_name);
1025
Arathorn78c08372019-12-11 20:14:23 +01001026 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
1027 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +02001028 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +02001029 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +01001030
Arathorn78c08372019-12-11 20:14:23 +01001031 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
1032 if (!owner->QueryProp(P_INVIS))
1033 {
1034 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1035 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1036 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1037 // explizites call_other() auf this_object() gemacht, damit der
1038 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1039 // einem externen.
1040 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +02001041 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +01001042 }
Arathorn19459eb2019-11-30 00:45:51 +01001043
MG Mud User88f12472016-06-24 23:31:02 +02001044 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +01001045 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001046}
1047
Arathorn78c08372019-12-11 20:14:23 +01001048// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
1049// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
1050// zweimal beitreten.)
Zesstra165157f2020-08-16 22:47:36 +02001051public int join(string chname, object joining)
MG Mud User88f12472016-06-24 23:31:02 +02001052{
Zesstrab7720dc2020-08-11 22:14:18 +02001053 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001054 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1055 zu erzeugen, weil access() mit extern_call() und previous_object()
1056 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1057 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001058 if (!funcall(#'access, ch, joining, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +01001059 return E_ACCESS_DENIED;
1060
Zesstra165157f2020-08-16 22:47:36 +02001061 int res = add_member(ch, joining);
Zesstrafb350dc2020-08-12 00:49:31 +02001062 if (res != 1)
1063 return res;
1064
Zesstra46c564e2020-09-11 21:52:29 +02001065 // Wenn <joining> der urspruengliche Ersteller der Ebene und kein
1066 // Spieler ist, wird es automatisch wieder zum Supervisor.
Zesstra165157f2020-08-16 22:47:36 +02001067 if (!query_once_interactive(joining)
1068 && object_name(joining) == ch.creator)
1069 change_sv_object(ch, joining);
Zesstra2b7ed1a2020-08-12 00:58:33 +02001070
Zesstrafb350dc2020-08-12 00:49:31 +02001071 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001072}
1073
Arathorn78c08372019-12-11 20:14:23 +01001074// Objekt <pl> verlaesst Ebene <ch>.
1075// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
1076// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
1077// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
1078// <pl> das Verlassen auf Grund eines Banns verboten sein.
1079// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
1080// noch ein Ebenenbesitzer eingetragen ist.
Zesstra165157f2020-08-16 22:47:36 +02001081public int leave(string chname, object leaving)
MG Mud User88f12472016-06-24 23:31:02 +02001082{
Zesstrab7720dc2020-08-11 22:14:18 +02001083 struct channel_s ch = channels[lower_case(chname)];
Zesstra0b4a5652020-09-23 00:40:37 +02001084 // Nicht-existierenden Ebenen soll das Spielerobjekt austragen, also tun wir
1085 // so, als sei das erfolgreich gewesen.
1086 if (!ch)
1087 return 0;
Zesstra877cb0a2020-08-10 02:10:21 +02001088
Zesstrab7720dc2020-08-11 22:14:18 +02001089 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +02001090
Zesstra165157f2020-08-16 22:47:36 +02001091 if (!IsChannelMember(ch, leaving))
Zesstra877cb0a2020-08-10 02:10:21 +02001092 return E_NOT_MEMBER;
1093
Arathorn739a4fa2020-08-06 21:52:58 +02001094 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1095 zu erzeugen, weil access() mit extern_call() und previous_object()
1096 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1097 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001098 if (!funcall(#'access, ch, leaving, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +01001099 return E_ACCESS_DENIED;
1100
Zesstrab7720dc2020-08-11 22:14:18 +02001101 // Dann mal den Zuhoerer raus.
Zesstra165157f2020-08-16 22:47:36 +02001102 ch.members -= ({leaving});
Zesstrae6d33852020-08-09 14:37:53 +02001103
Zesstra5b7f2fc2020-08-10 02:09:13 +02001104 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
1105 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +02001106 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +02001107 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001108 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1109 // diese verlassen hat. change_sv_object() waehlt per Default den
1110 // aeltesten Zuhoerer.
Zesstra165157f2020-08-16 22:47:36 +02001111 if (leaving == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001112 {
Zesstrabf4f86d2020-08-12 00:56:17 +02001113 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +01001114 }
MG Mud User88f12472016-06-24 23:31:02 +02001115 }
Zesstra137ea1c2020-08-10 02:15:20 +02001116 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1117 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1118 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1119 else
MG Mud User88f12472016-06-24 23:31:02 +02001120 {
Arathorn78c08372019-12-11 20:14:23 +01001121 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001122 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1123 // wird diese zerstoert, mit Meldung.
Zesstra165157f2020-08-16 22:47:36 +02001124 if (!leaving->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001125 {
1126 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1127 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1128 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1129 // explizites call_other() auf this_object() gemacht, damit der
1130 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1131 // einem externen.
Zesstra165157f2020-08-16 22:47:36 +02001132 this_object()->send(CMNAME, leaving,
Arathorn78c08372019-12-11 20:14:23 +01001133 "verlaesst als "+
Zesstra165157f2020-08-16 22:47:36 +02001134 (leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001135 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001136 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1137 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001138 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +02001139 }
Arathorn19459eb2019-11-30 00:45:51 +01001140 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001141}
1142
Arathorn78c08372019-12-11 20:14:23 +01001143// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1144// sofern <pl> dort senden darf.
Zesstra165157f2020-08-16 22:47:36 +02001145public varargs int send(string chname, object sender, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001146{
Zesstrab7720dc2020-08-11 22:14:18 +02001147 chname = lower_case(chname);
1148 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001149 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1150 zu erzeugen, weil access() mit extern_call() und previous_object()
1151 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1152 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001153 int a = funcall(#'access, ch, sender, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001154 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001155 return E_ACCESS_DENIED;
1156
Zesstra26aaf1a2020-08-07 19:10:39 +02001157 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
1158 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
1159 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
1160 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1161 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Zesstra165157f2020-08-16 22:47:36 +02001162 if (a < 2 && !IsChannelMember(ch, sender))
Arathorn19459eb2019-11-30 00:45:51 +01001163 return E_NOT_MEMBER;
1164
1165 if (!msg || !stringp(msg) || !sizeof(msg))
1166 return E_EMPTY_MESSAGE;
1167
Arathorn78c08372019-12-11 20:14:23 +01001168 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1169 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1170 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1171 // Nachricht ebenfalls erhaelt.
1172 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1173 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1174 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1175 // erzeugt und der Channeld als Besitzer angegeben.
1176 // Die Aufrufkette ist dann wie folgt:
1177 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001178 (ch.members)->ChannelMessage(
Zesstra165157f2020-08-16 22:47:36 +02001179 ({ ch.name, sender, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001180
Zesstrab7720dc2020-08-11 22:14:18 +02001181 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1182 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001183
Zesstrab7720dc2020-08-11 22:14:18 +02001184 channelH[chname] +=
1185 ({ ({ ch.name,
Zesstra556c54d2020-08-16 22:50:10 +02001186 (sender->QueryProp(P_INVIS)
Zesstra165157f2020-08-16 22:47:36 +02001187 ? "/(" + capitalize(getuid(sender)) + ")$"
Arathorn19459eb2019-11-30 00:45:51 +01001188 : "")
Zesstra556c54d2020-08-16 22:50:10 +02001189 + (sender->Name(WER, 2) || "<Unbekannt>"),
Arathorn19459eb2019-11-30 00:45:51 +01001190 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1191 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001192 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001193}
1194
Arathorn78c08372019-12-11 20:14:23 +01001195// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1196// oder einen Integer-Fehlercode
1197public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001198{
Arathorn78c08372019-12-11 20:14:23 +01001199 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001200 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001201 {
Arathorn739a4fa2020-08-06 21:52:58 +02001202 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1203 zu erzeugen, weil access() mit extern_call() und previous_object()
1204 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1205 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001206 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001207 {
Zesstrab7720dc2020-08-11 22:14:18 +02001208 ch.members = filter(ch.members, #'objectp);
1209 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1210 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001211 }
1212 }
Arathorn19459eb2019-11-30 00:45:51 +01001213
1214 if (!sizeof(chs))
1215 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001216 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001217}
1218
Arathorn78c08372019-12-11 20:14:23 +01001219// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1220// Rueckgabewerte:
1221// - den gefundenen Namen als String
1222// - String-Array, wenn es mehrere Treffer gibt
1223// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001224public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001225{
Zesstrab7720dc2020-08-11 22:14:18 +02001226 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001227
Arathorn78c08372019-12-11 20:14:23 +01001228 // Suchstring <ch> muss Formatanforderung erfuellen;
1229 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1230 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1231 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1232 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001233 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001234 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001235
Arathorn78c08372019-12-11 20:14:23 +01001236 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1237 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1238 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001239 string* chs = filter(m_indices(channels), function int (string ch_n) {
Arathorn739a4fa2020-08-06 21:52:58 +02001240 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1241 im Caller Stack zu erzeugen, weil access() mit
1242 extern_call() und previous_object() arbeitet und
1243 sichergestellt sein muss, dass das in jedem Fall das
1244 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001245 return ( stringp(regmatch(ch_n, "^"+chname)) &&
1246 funcall(#'access, channels[ch_n], pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001247 });
Arathorn19459eb2019-11-30 00:45:51 +01001248
Arathorn78c08372019-12-11 20:14:23 +01001249 int num_channels = sizeof(chs);
1250 if (num_channels > 1)
1251 return chs;
1252 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001253 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001254 else
1255 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001256}
1257
Arathorn78c08372019-12-11 20:14:23 +01001258// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001259public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001260{
Zesstrab7720dc2020-08-11 22:14:18 +02001261 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001262 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1263 zu erzeugen, weil access() mit extern_call() und previous_object()
1264 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1265 richtige ist. */
1266 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001267 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001268 else
Zesstrab7720dc2020-08-11 22:14:18 +02001269 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001270}
1271
Arathorn78c08372019-12-11 20:14:23 +01001272// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001273public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001274{
Zesstra26aaf1a2020-08-07 19:10:39 +02001275 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001276 if (previous_object() != this_object())
1277 {
Zesstrab7720dc2020-08-11 22:14:18 +02001278 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001279 pl != this_player() || this_player() != this_interactive() ||
1280 this_interactive() != previous_object() ||
1281 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001282 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001283 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001284
1285 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001286
Arathorn19459eb2019-11-30 00:45:51 +01001287 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001288}
1289
Arathorn78c08372019-12-11 20:14:23 +01001290// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001291public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001292{
Zesstra26aaf1a2020-08-07 19:10:39 +02001293 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001294 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001295 if (previous_object() != this_object())
1296 {
Zesstrab7720dc2020-08-11 22:14:18 +02001297 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001298 this_player() != this_interactive() ||
1299 this_interactive() != previous_object() ||
1300 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001301 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001302 }
Zesstrab7720dc2020-08-11 22:14:18 +02001303 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001304 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1305 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001306 if (pointerp(channelH[chname]))
1307 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001308
1309 return 0;
1310}