blob: 8bd361f280dcc1e5af57f230c009ec394eabaa96 [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
28#define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
MG Mud User88f12472016-06-24 23:31:02 +020029
Arathorn78c08372019-12-11 20:14:23 +010030
31/* Ebenenliste und die zugehoerigen Daten, z.B. Mitglieder oder Beschreibung
Arathorn19459eb2019-11-30 00:45:51 +010032 channels = ([string channelname : ({ ({object* members}),
33 closure access_rights,
Arathorn78c08372019-12-11 20:14:23 +010034 string channel_desc,
Arathorn19459eb2019-11-30 00:45:51 +010035 string|object master_object,
Zesstra78310012020-08-09 12:21:48 +020036 string readable_channelname }) ])
37 The master_object is also called the supervisor.
38 */
Arathorn78c08372019-12-11 20:14:23 +010039private nosave mapping channels = ([]);
Arathorn19459eb2019-11-30 00:45:51 +010040//private nosave mapping lowerch; // unused
41
Arathorn78c08372019-12-11 20:14:23 +010042/* Ebenenhistory
43 mapping channelH = ([ string channelname : ({ ({string channelname,
44 string sender,
45 string msg,
46 int msg_type}) }) ]) */
47// channelH wird in create() geeignet initialisiert
48// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
49// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
50// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
51// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
52// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020053private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010054
Arathorn78c08372019-12-11 20:14:23 +010055/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
56 zerstoerte Ebenen.
57 mapping stats = ([ "time" : int object_time(),
58 "boot" : string getuid(previous_object()),
Arathorn19459eb2019-11-30 00:45:51 +010059 "new" : int total_channels_created,
60 "disposed" : int total_channels_removed ]) */
Arathorn78c08372019-12-11 20:14:23 +010061// stats wird in create() geeignet initialisiert
MG Mud User88f12472016-06-24 23:31:02 +020062private nosave mapping stats;
63
Arathorn78c08372019-12-11 20:14:23 +010064/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Arathorn19459eb2019-11-30 00:45:51 +010065 mapping channelC = ([ string channelname : ({ string I_NAME,
66 string I_INFO,
67 int time() }) ]) */
Arathorn78c08372019-12-11 20:14:23 +010068private mapping channelC = ([]);
Arathorn19459eb2019-11-30 00:45:51 +010069
Arathorn78c08372019-12-11 20:14:23 +010070/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
71 mapping channelB = ([ string playername : string* banned_command ]) */
72private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020073
Arathorn78c08372019-12-11 20:14:23 +010074/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
75 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
76 kann.
77
Arathorn19459eb2019-11-30 00:45:51 +010078 mapping Tcmd = ([ "lag": int timestamp,
79 "uptime": int timestamp,
80 "statistik": int timestamp]) */
81private mapping Tcmd = ([]);
82
Arathorn78c08372019-12-11 20:14:23 +010083/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
84 Speicherevent das Savefile geschrieben werden soll.
85 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +020086private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +020087
Arathorn19459eb2019-11-30 00:45:51 +010088
MG Mud User88f12472016-06-24 23:31:02 +020089// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
90
Arathorn78c08372019-12-11 20:14:23 +010091// Indizes fuer Zugriffe auf das Mapping <admin>.
MG Mud User88f12472016-06-24 23:31:02 +020092#define RECV 0
93#define SEND 1
94#define FLAG 2
95
Arathorn78c08372019-12-11 20:14:23 +010096// Ebenenflags, gespeichert in admin[ch, FLAG]
97// F_WIZARD kennzeichnet reine Magierebenen
MG Mud User88f12472016-06-24 23:31:02 +020098#define F_WIZARD 1
Arathorn78c08372019-12-11 20:14:23 +010099// Ebenen, auf denen keine Gaeste erlaubt sind, sind mit F_NOGUEST markiert.
MG Mud User88f12472016-06-24 23:31:02 +0200100#define F_NOGUEST 2
101
Arathorn78c08372019-12-11 20:14:23 +0100102/* Speichert Sende- und Empfangslevel sowie Flags zu den einzelnen Channeln.
103 Wird beim Laden des Masters via create() -> initalize() -> setup() mit den
104 Daten aus dem Init-File ./channeld.init befuellt.
105 mapping admin = ([ string channel_name : int RECV_LVL,
106 int SEND_LVL,
107 int FLAG ]) */
MG Mud User88f12472016-06-24 23:31:02 +0200108private nosave mapping admin = m_allocate(0, 3);
109
Arathorn78c08372019-12-11 20:14:23 +0100110// check_ch_access() prueft die Zugriffsberechtigungen auf Ebenen.
111//
112// Gibt 1 zurueck, wenn Aktion erlaubt, 0 sonst.
113// Wird von access() gerufen; access() gibt das Ergebnis von
114// check_ch_access() zurueck.
115//
116// Verlassen (C_LEAVE) ist immer erlaubt. Die anderen Aktionen sind in zwei
117// Gruppen eingeteilt:
118// 1) RECV. Die Aktionen dieser Gruppe sind Suchen (C_FIND), Auflisten
119// (C_LIST) und Betreten (C_JOIN).
120// 2) SEND. Die Aktion dieser Gruppe ist zur Zeit nur Senden (C_SEND).
121//
122// Aktionen werden zugelassen, wenn Spieler/MagierLevel groesser ist als die
123// fuer die jeweilige Aktionsgruppe RECV oder SEND festgelegte Stufe.
124// Handelt es sich um eine Magierebene (F_WIZARD), muss die Magierstufe
125// des Spielers groesser sein als die Mindeststufe der Ebene. Ansonsten
126// wird gegen den Spielerlevel geprueft.
127//
128// Wenn RECV_LVL oder SEND_LVL auf -1 gesetzt ist, sind die Aktionen der
129// jeweiligen Gruppen komplett geblockt.
MG Mud User88f12472016-06-24 23:31:02 +0200130
Arathorn739a4fa2020-08-06 21:52:58 +0200131public int check_ch_access(string ch, object pl, string cmd)
Arathorn78c08372019-12-11 20:14:23 +0100132{
133 // <pl> ist Gast, es sind aber keine Gaeste zugelassen? Koennen wir
134 // direkt ablehnen.
Arathorn19459eb2019-11-30 00:45:51 +0100135 if ((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest())
136 return 0;
137
Arathorn78c08372019-12-11 20:14:23 +0100138 // Ebenso auf Magier- oder Seherebenen, wenn ein Spieler anfragt, der
139 // noch kein Seher ist.
Arathorn19459eb2019-11-30 00:45:51 +0100140 if ((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL)
141 return 0;
142
Arathorn78c08372019-12-11 20:14:23 +0100143 // Ebene ist Magierebene? Dann werden alle Stufenlimits gegen Magierlevel
144 // geprueft, ansonsten gegen Spielerlevel.
145 int level = (admin[ch, FLAG] & F_WIZARD
146 ? query_wiz_level(pl)
147 : pl->QueryProp(P_LEVEL));
MG Mud User88f12472016-06-24 23:31:02 +0200148
Arathorn19459eb2019-11-30 00:45:51 +0100149 switch (cmd)
MG Mud User88f12472016-06-24 23:31:02 +0200150 {
Arathorn19459eb2019-11-30 00:45:51 +0100151 case C_FIND:
152 case C_LIST:
153 case C_JOIN:
154 if (admin[ch, RECV] == -1)
155 return 0;
156 if (admin[ch, RECV] <= level)
157 return 1;
158 break;
159
160 case C_SEND:
161 if (admin[ch, SEND] == -1)
162 return 0;
163 if (admin[ch, SEND] <= level)
164 return 1;
165 break;
166
Arathorn78c08372019-12-11 20:14:23 +0100167 // Verlassen ist immer erlaubt
Arathorn19459eb2019-11-30 00:45:51 +0100168 case C_LEAVE:
169 return 1;
170
171 default:
172 break;
MG Mud User88f12472016-06-24 23:31:02 +0200173 }
Arathorn19459eb2019-11-30 00:45:51 +0100174 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200175}
176
Arathorn78c08372019-12-11 20:14:23 +0100177/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
178// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
179private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200180{
Arathorn78c08372019-12-11 20:14:23 +0100181 object* userlist = ({});
182 foreach(string ch_name, mixed* ch_data : channels)
183 {
184 userlist += ch_data[I_MEMBER];
185 }
186 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
187 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200188}
189
Arathorn78c08372019-12-11 20:14:23 +0100190// Ist das Objekt <sender> Abonnent der Ebene <ch>?
191private int IsChannelMember(string ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200192{
Arathorn78c08372019-12-11 20:14:23 +0100193 return (member(channels[ch][I_MEMBER], sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200194}
195
Arathorn78c08372019-12-11 20:14:23 +0100196// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
197private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200198{
Arathorn78c08372019-12-11 20:14:23 +0100199 if (objectp(ob))
200 ob = getuid(ob);
201 return(pointerp(channelB[ob]) &&
202 member(channelB[ob], command) != -1);
203}
MG Mud User88f12472016-06-24 23:31:02 +0200204
Arathorn78c08372019-12-11 20:14:23 +0100205private void banned(string plname, string* cmds, string res)
206{
207 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
208}
209
210#define TIMEOUT (time() - 60)
211
212// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
213// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
214// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
215// zurueckliegt, als "nicht gesperrt".
216private int IsNotBlocked(string* cmd)
217{
218 string* res = filter(cmd, function int (string str) {
219 return (Tcmd[str] < TIMEOUT);
220 });
221 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
222 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
223 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
224 if (sizeof(res) == sizeof(cmd)) {
225 foreach(string str : cmd) {
226 Tcmd[str] = time();
227 }
228 return 1;
229 }
230 return 0;
231}
232
233// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
234// zugelassen wird. Anforderungen:
235// 1) <cmd> muss Teilstring von <check> sein
236// 2) <cmd> muss am Anfang von <check> stehen
237// 3) <cmd> darf nicht laenger sein als <check>
238// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
239// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
240// cmd = "statistiker" oder cmd = "tist"
241// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
242private int IsValidChannelCommand(string cmd, string check) {
243 // Syntaxcheck (prueft Bedingungen 1 bis 3).
244 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
245 string* cmd_to_check;
246 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
247 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
248 // geben.
249 if ( cmd == "lust" )
250 cmd_to_check = ({"lag", "statistik", "uptime"});
251 else
252 cmd_to_check = ({cmd});
253 // Prueft die Zeitsperre (Bedingung 4).
254 return (IsNotBlocked(cmd_to_check));
255 }
256 return 0;
257}
258
259#define CH_NAME 0
260#define CH_SENDER 1
261#define CH_MSG 2
262#define CH_MSG_TYPE 3
263// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
264// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
265// und dieses per Callout an send() uebergeben.
266// Argument: ({string channels[ch][I_NAME], object pl, string msg, int type})
267// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
268// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
269// was aber bei einer private oder protected Funktion nicht moeglich waere.
270public void ChannelMessage(<string|object|int>* msg)
271{
272 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
273 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
274 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
275 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100276 return;
MG Mud User88f12472016-06-24 23:31:02 +0200277
Arathorn78c08372019-12-11 20:14:23 +0100278 float* lag;
279 int max, rekord;
280 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200281 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200282
Arathorn78c08372019-12-11 20:14:23 +0100283 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200284 {
Arathorn78c08372019-12-11 20:14:23 +0100285 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
286 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100287 }
Arathorn78c08372019-12-11 20:14:23 +0100288 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100289 {
MG Mud User88f12472016-06-24 23:31:02 +0200290 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
291 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100292 "%.1f%%/20s, %.1f%%/2s",
293 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100294 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
295 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100296 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200297 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100298 }
Arathorn78c08372019-12-11 20:14:23 +0100299 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200300 {
Arathorn78c08372019-12-11 20:14:23 +0100301 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100302 {
Arathorn78c08372019-12-11 20:14:23 +0100303 string unused;
304 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
305 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
306 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
307 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
308 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100309 }
310 else
311 {
Arathorn78c08372019-12-11 20:14:23 +0100312 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200313 }
Arathorn19459eb2019-11-30 00:45:51 +0100314 }
Arathorn78c08372019-12-11 20:14:23 +0100315 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200316 {
MG Mud User88f12472016-06-24 23:31:02 +0200317 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100318 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
319 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
320 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
321 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100322 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
323 }
Arathorn78c08372019-12-11 20:14:23 +0100324 // Ebenenaktion beginnt mit "bann"?
325 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200326 {
327 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100328
329 if (mesg == "bann")
330 {
331 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200332 {
Arathorn78c08372019-12-11 20:14:23 +0100333 ret = "Fuer folgende Spieler besteht ein Bann: ";
334 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
335 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
336 string* banlist = ({});
337 foreach(string plname, string* banned_cmds : channelB) {
338 banlist += ({ sprintf("%s [%s]",
339 capitalize(plname), implode(banned_cmds, ", "))});
340 }
341 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200342 }
343 else
344 {
Arathorn19459eb2019-11-30 00:45:51 +0100345 ret = "Zur Zeit ist kein Bann aktiv.";
346 }
347 }
348 else
349 {
Arathorn78c08372019-12-11 20:14:23 +0100350 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
351 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100352 {
353 pl = lower_case(pl);
354 cmd = lower_case(cmd);
355
356 if (member(CMDS, cmd) != -1)
357 {
Arathorn78c08372019-12-11 20:14:23 +0100358 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
359 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
360 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100361 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100362 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100363
Arathorn78c08372019-12-11 20:14:23 +0100364 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100365 channelB[pl] -= ({ cmd });
366 else
367 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100368
Arathorn78c08372019-12-11 20:14:23 +0100369 ret = "Fuer '" + capitalize(pl) + "' besteht " +
370 (sizeof(channelB[pl])
371 // TODO: implode() -> CountUp()?
372 ? "folgender Bann: " + implode(channelB[pl], ", ") + "."
373 : "kein Bann mehr.");
374
375 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100376 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100377 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100378
Zesstraa2db5522020-08-11 22:14:55 +0200379 //TODO: save_me_soon=1 sollte auch reichen...
Arathorn19459eb2019-11-30 00:45:51 +0100380 save_object(CHANNEL_SAVE);
381 }
382 else
383 {
384 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100385 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100386 }
387 }
388 else
389 {
Arathorn78c08372019-12-11 20:14:23 +0100390 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100391 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200392 }
393 }
394 }
Arathorn78c08372019-12-11 20:14:23 +0100395 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200396 {
MG Mud User88f12472016-06-24 23:31:02 +0200397 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100398 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
399 {
400 string unused;
401 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
402 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
403 }
MG Mud User88f12472016-06-24 23:31:02 +0200404
Arathorn78c08372019-12-11 20:14:23 +0100405 int t = time() - last_reboot_time();
406
407 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
408 // die Funktionalitaet oefter benoetigt wird als nur hier.
409 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100410 if (t >= 86400)
411 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200412
Arathorn78c08372019-12-11 20:14:23 +0100413 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100414 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100415 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100416
Arathorn78c08372019-12-11 20:14:23 +0100417 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100418 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100419 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100420
421 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100422
MG Mud User88f12472016-06-24 23:31:02 +0200423 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100424 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100425 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100426 }
427 else
428 {
429 return;
430 }
MG Mud User88f12472016-06-24 23:31:02 +0200431
Arathorn78c08372019-12-11 20:14:23 +0100432 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
433 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
434 // nicht erfuellt sind.
435 if (stringp(ret) && sizeof(ret))
436 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200437}
438
439// setup() -- set up a channel and register it
440// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100441// string* chinfo = ({ channel_name, receive_level, send_level,
442// flags, description, masterobj })
443private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200444{
Arathorn78c08372019-12-11 20:14:23 +0100445 string desc = "- Keine Beschreibung -";
446 object chmaster = this_object();
Arathorn19459eb2019-11-30 00:45:51 +0100447
Arathorn78c08372019-12-11 20:14:23 +0100448 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
449 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200450
Arathorn78c08372019-12-11 20:14:23 +0100451 switch (sizeof(chinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200452 {
Arathorn78c08372019-12-11 20:14:23 +0100453 // Alle Fallthroughs in dem switch() sind Absicht.
Arathorn19459eb2019-11-30 00:45:51 +0100454 case 6:
Arathorn78c08372019-12-11 20:14:23 +0100455 if (stringp(chinfo[5]) && sizeof(chinfo[5]))
456 catch(chmaster = load_object(chinfo[5]); publish);
457 if (!objectp(chmaster))
458 chmaster = this_object();
Arathorn19459eb2019-11-30 00:45:51 +0100459
460 case 5:
Arathorn78c08372019-12-11 20:14:23 +0100461 if (stringp(chinfo[4]) || closurep(chinfo[4]))
462 desc = chinfo[4];
Zesstra26aaf1a2020-08-07 19:10:39 +0200463 // Die admin-Daten sind nicht fuer die Ebene wichtig, nur fuer die
464 // check_ch_access().
Arathorn19459eb2019-11-30 00:45:51 +0100465 case 4:
Arathorn739a4fa2020-08-06 21:52:58 +0200466 admin[lower_case(chinfo[0]), FLAG] = to_int(chinfo[3]);
Arathorn19459eb2019-11-30 00:45:51 +0100467
468 case 3:
Arathorn739a4fa2020-08-06 21:52:58 +0200469 admin[lower_case(chinfo[0]), SEND] = to_int(chinfo[2]);
Arathorn19459eb2019-11-30 00:45:51 +0100470
471 case 2:
Arathorn739a4fa2020-08-06 21:52:58 +0200472 admin[lower_case(chinfo[0]), RECV] = to_int(chinfo[1]);
Arathorn19459eb2019-11-30 00:45:51 +0100473 break;
474
475 case 0:
476 default:
477 return;
MG Mud User88f12472016-06-24 23:31:02 +0200478 }
Arathorn19459eb2019-11-30 00:45:51 +0100479
Arathorn78c08372019-12-11 20:14:23 +0100480 if (new(chinfo[0], chmaster, desc) == E_ACCESS_DENIED)
MG Mud User88f12472016-06-24 23:31:02 +0200481 {
Arathorn78c08372019-12-11 20:14:23 +0100482 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
483 dtime(time()), chinfo[0], chmaster));
MG Mud User88f12472016-06-24 23:31:02 +0200484 }
485 return;
486}
487
Arathorn78c08372019-12-11 20:14:23 +0100488private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200489{
Arathorn78c08372019-12-11 20:14:23 +0100490 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200491#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100492 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200493#else
Arathorn78c08372019-12-11 20:14:23 +0100494 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200495#endif
Arathorn19459eb2019-11-30 00:45:51 +0100496
Arathorn78c08372019-12-11 20:14:23 +0100497 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200498 return;
Arathorn19459eb2019-11-30 00:45:51 +0100499
Arathorn78c08372019-12-11 20:14:23 +0100500 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
501 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
502 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
503 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
504 foreach(string ch : old_explode(ch_list, "\n"))
505 {
506 if (ch[0]=='#')
507 continue;
508 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
509 }
MG Mud User88f12472016-06-24 23:31:02 +0200510}
511
Arathorn78c08372019-12-11 20:14:23 +0100512// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200513protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200514{
515 seteuid(getuid());
516 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100517
Zesstra26aaf1a2020-08-07 19:10:39 +0200518 //TODO: weitere Mappings im MEMORY speichern, Savefile ersetzen.
519
Arathorn19459eb2019-11-30 00:45:51 +0100520 /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
521 gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
MG Mud User88f12472016-06-24 23:31:02 +0200522 Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
523 das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
524 Memory abgelegt. */
525
526 // Hab ich die noetigen Rechte im Memory?
Arathorn19459eb2019-11-30 00:45:51 +0100527 if (call_other(MEMORY, "HaveRights"))
528 {
MG Mud User88f12472016-06-24 23:31:02 +0200529 // Objektpointer laden
Dominik Schaeferfa564d52020-08-05 20:50:27 +0200530 channelH = ({mapping}) call_other(MEMORY, "Load", "History");
MG Mud User88f12472016-06-24 23:31:02 +0200531
532 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
Arathorn78c08372019-12-11 20:14:23 +0100533 if (!mappingp(channelH))
534 {
MG Mud User88f12472016-06-24 23:31:02 +0200535 // Zeiger erzeugen
536 channelH = ([]);
537 // und in den Memory schreiben
Arathorn19459eb2019-11-30 00:45:51 +0100538 call_other(MEMORY, "Save", "History", channelH);
MG Mud User88f12472016-06-24 23:31:02 +0200539 }
Arathorn19459eb2019-11-30 00:45:51 +0100540 }
541 else
542 {
MG Mud User88f12472016-06-24 23:31:02 +0200543 // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
544 channelH = ([]);
545 }
546
547 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100548 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
549
550 // <MasteR>-Ebene erstellen. Channeld wird Ebenenbesitzer und somit auch
551 // Zuhoerer, damit er auf Kommandos auf dieser Ebene reagieren kann.
552 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
553
MG Mud User88f12472016-06-24 23:31:02 +0200554 initialize();
Arathorn78c08372019-12-11 20:14:23 +0100555 users()->RegisterChannels();
556
557 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
558 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
559 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
560 // explizites call_other() auf this_object() gemacht, damit der
561 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
562 // einem externen.
MG Mud User88f12472016-06-24 23:31:02 +0200563 this_object()->send(CMNAME, this_object(),
Arathorn19459eb2019-11-30 00:45:51 +0100564 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
565 sizeof(channels),
Arathorn78c08372019-12-11 20:14:23 +0100566 CountUsers()));
MG Mud User88f12472016-06-24 23:31:02 +0200567}
568
Arathorn78c08372019-12-11 20:14:23 +0100569varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200570{
Zesstra26aaf1a2020-08-07 19:10:39 +0200571 //TODO reset nur 1-2mal am Tag mit etwas random.
572
573 // Cache bereinigen entsprechend dessen Timeout-Zeit (12 h)
574 // TODO: Zeit auf 2-3 Tage erhoehen.
575 // TODO 2: Zeit dynamisch machen und nur expiren, wenn mehr als n Eintraege.
576 // Zeit reduzieren, bis nur noch n/2 Eintraege verbleiben.
Zesstra8f5102c2020-08-08 12:51:52 +0200577 channelC = filter(channelC,
578 function int (string ch_name, <string|int>* data)
579 {
580 if (channelC[ch_name][2] + 43200 > time())
581 return 1;
582 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
583 m_delete(channelH, ch_name);
584 return 0;
585 });
Arathorn19459eb2019-11-30 00:45:51 +0100586
MG Mud User88f12472016-06-24 23:31:02 +0200587 if (save_me_soon)
588 {
Arathorn19459eb2019-11-30 00:45:51 +0100589 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200590 save_object(CHANNEL_SAVE);
591 }
592}
593
594// name() - define the name of this object.
Arathorn19459eb2019-11-30 00:45:51 +0100595string name()
596{
597 return CMNAME;
598}
599
600string Name()
601{
602 return CMNAME;
603}
MG Mud User88f12472016-06-24 23:31:02 +0200604
Arathorn78c08372019-12-11 20:14:23 +0100605#define CHAN_NAME(x) channels[x][I_NAME]
606#define MASTER_OB(x) channels[x][I_MASTER]
607#define ACC_CLOSURE(x) channels[x][I_ACCESS]
608
Zesstra78310012020-08-09 12:21:48 +0200609// Stellt sicher, dass einen Ebenen-Supervisor gibt.
610// Wenn dies nicht moeglich ist (z.b. leere Ebene), dann wird die Ebene
611// geloescht und 0 zurueckgegeben.
612private int assert_supervisor(string ch)
MG Mud User88f12472016-06-24 23:31:02 +0200613{
Arathorn78c08372019-12-11 20:14:23 +0100614 // Es ist keine Closure vorhanden, d.h. der Ebenenbesitzer wurde zerstoert.
615 if (!closurep(ACC_CLOSURE(ch)))
MG Mud User88f12472016-06-24 23:31:02 +0200616 {
Arathorn78c08372019-12-11 20:14:23 +0100617 // Wenn der Ebenenbesitzer als String eingetragen ist, versuchen wir,
618 // die Closure wiederherzustellen. Dabei wird das Objekt gleichzeitig
619 // neugeladen.
620 if (stringp(MASTER_OB(ch)))
MG Mud User88f12472016-06-24 23:31:02 +0200621 {
Arathorn78c08372019-12-11 20:14:23 +0100622 closure new_acc_cl;
623 string err = catch(new_acc_cl=
624 symbol_function("check_ch_access", MASTER_OB(ch));
625 publish);
626 /* Wenn sich die Closure fehlerfrei erstellen liess, dann wird sie als
627 neue Zugriffskontrolle eingetragen und auch der Ebenenbesitzer neu
628 gesetzt. */
Arathorn78c08372019-12-11 20:14:23 +0100629 if (!err)
630 {
631 ACC_CLOSURE(ch) = new_acc_cl;
Zesstracc2de822020-08-09 12:30:16 +0200632 // Der neue Ebenenbesitzer tritt auch gleich der Ebene bei.
633 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
634 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
635 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
636 // explizites call_other() auf this_object() gemacht, damit der
637 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
638 // einem externen.
639 //TODO: Rekursion ok...?
640 this_object()->join(ch, find_object(MASTER_OB(ch)));
Arathorn78c08372019-12-11 20:14:23 +0100641 }
642 else
643 {
Zesstracc2de822020-08-09 12:30:16 +0200644 log_file("CHANNEL", sprintf("[%s] Channel deleted. %O -> %O\n",
Arathorn78c08372019-12-11 20:14:23 +0100645 dtime(time()), MASTER_OB(ch), err));
646 m_delete(channels, ch);
647 return 0;
648 }
MG Mud User88f12472016-06-24 23:31:02 +0200649 }
Arathorn78c08372019-12-11 20:14:23 +0100650 // TODO: kaputte Objekte raussortieren, neuen Master bestimmen, wenn
651 // dieser nicht mehr existiert.
MG Mud User88f12472016-06-24 23:31:02 +0200652 }
Zesstra78310012020-08-09 12:21:48 +0200653 return 1;
654}
655
656// access() - check access by looking for the right argument types and
657// calling access closures respectively
658// SEE: new, join, leave, send, list, users
659// Note: <pl> is usually an object, only the master supplies a string during
660// runtime error handling.
661// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
662// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
663// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
664// als Erfolg und alles ueber >2 als privilegiert.)
665varargs private int access(string ch, object|string pl, string cmd,
666 string txt)
667{
668 if (!sizeof(ch))
669 return 0;
670
671 ch = lower_case(ch);
672 if(!pointerp(channels[ch]))
673 return 0;
674
675 if ( !previous_object(1) || !extern_call() ||
676 previous_object(1) == this_object() ||
677 (stringp(MASTER_OB(ch)) &&
678 previous_object(1) == find_object(MASTER_OB(ch))) ||
679 getuid(previous_object(1)) == ROOTID)
680 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200681
682 if (!objectp(pl) ||
683 ((previous_object(1) != pl) && (previous_object(1) != this_object())))
684 return 0;
685
686 if (IsBanned(pl, cmd))
687 return 0;
688
Zesstra78310012020-08-09 12:21:48 +0200689 if (!assert_supervisor(ch))
Arathorn739a4fa2020-08-06 21:52:58 +0200690 return 1;
691
692 return funcall(ACC_CLOSURE(ch), ch, pl, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200693}
694
Arathorn78c08372019-12-11 20:14:23 +0100695// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
696// <info> kann die statische Beschreibung der Ebene sein oder eine Closure,
697// die dynamisch aktualisierte Infos ausgibt.
698// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
699// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
700// eingeht.
701// check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
702// Nachricht gesendet werden darf oder nicht.
703
704// Ist keine Closure angegeben, wird die in diesem Objekt (Channeld)
705// definierte Funktion gleichen Namens verwendet.
MG Mud User88f12472016-06-24 23:31:02 +0200706#define IGNORE "^/xx"
707
Arathorn78c08372019-12-11 20:14:23 +0100708// TODO: KOMMENTAR
709//check may contain a closure
710// called when a join/leave/send/list/users message is received
711public varargs int new(string ch_name, object owner, string|closure info)
MG Mud User88f12472016-06-24 23:31:02 +0200712{
Arathorn78c08372019-12-11 20:14:23 +0100713 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
714 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
715 // fuer eine neue Ebene eintragen.)
716 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200717 return E_ACCESS_DENIED;
718
Arathorn78c08372019-12-11 20:14:23 +0100719 // Kein gescheiter Channelname angegeben.
720 if (!stringp(ch_name) || !sizeof(ch_name))
721 return E_ACCESS_DENIED;
722
723 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
724 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
725 return E_ACCESS_DENIED;
726
727 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
728 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
729 // seinen Objektnamen matcht.
730 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
731 return E_ACCESS_DENIED;
732
733 // Keine Infos mitgeliefert? Dann holen wir sie aus dem Cache.
Arathorn19459eb2019-11-30 00:45:51 +0100734 if (!info)
735 {
Arathorn78c08372019-12-11 20:14:23 +0100736 if (channelC[lower_case(ch_name)])
Arathorn19459eb2019-11-30 00:45:51 +0100737 {
Arathorn78c08372019-12-11 20:14:23 +0100738 ch_name = channelC[lower_case(ch_name)][0];
739 info = channelC[lower_case(ch_name)][1];
MG Mud User88f12472016-06-24 23:31:02 +0200740 }
Arathorn19459eb2019-11-30 00:45:51 +0100741 else
742 {
743 return E_ACCESS_DENIED;
744 }
MG Mud User88f12472016-06-24 23:31:02 +0200745 }
Arathorn19459eb2019-11-30 00:45:51 +0100746 else
747 {
Arathorn78c08372019-12-11 20:14:23 +0100748 channelC[lower_case(ch_name)] = ({ ch_name, info, time() });
Arathorn19459eb2019-11-30 00:45:51 +0100749 }
MG Mud User88f12472016-06-24 23:31:02 +0200750
Arathorn78c08372019-12-11 20:14:23 +0100751 object* pls = ({ owner });
752 m_add(channels, lower_case(ch_name),
753 ({ pls,
754 symbol_function("check_ch_access", owner) || #'check_ch_access,
755 info,
756 (!living(owner) && !clonep(owner) && owner != this_object()
757 ? object_name(owner)
758 : owner),
759 ch_name }));
MG Mud User88f12472016-06-24 23:31:02 +0200760
Arathorn78c08372019-12-11 20:14:23 +0100761 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
762 // nicht gibt.
763 if (!pointerp(channelH[lower_case(ch_name)]))
764 channelH[lower_case(ch_name)] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200765
Arathorn78c08372019-12-11 20:14:23 +0100766 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
767 if (owner != this_object())
MG Mud User88f12472016-06-24 23:31:02 +0200768 log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
Arathorn78c08372019-12-11 20:14:23 +0100769 dtime(time()), ch_name, owner, info));
Arathorn19459eb2019-11-30 00:45:51 +0100770
Arathorn78c08372019-12-11 20:14:23 +0100771 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
772 if (!owner->QueryProp(P_INVIS))
773 {
774 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
775 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
776 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
777 // explizites call_other() auf this_object() gemacht, damit der
778 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
779 // einem externen.
780 this_object()->send(CMNAME, owner,
781 "laesst die Ebene '" + ch_name + "' entstehen.", MSG_EMOTE);
782 }
Arathorn19459eb2019-11-30 00:45:51 +0100783
MG Mud User88f12472016-06-24 23:31:02 +0200784 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100785 save_me_soon = 1;
786 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200787}
788
Arathorn78c08372019-12-11 20:14:23 +0100789// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
790// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
791// zweimal beitreten.)
792public int join(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200793{
Arathorn78c08372019-12-11 20:14:23 +0100794 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200795 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
796 zu erzeugen, weil access() mit extern_call() und previous_object()
797 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
798 richtige ist. */
799 if (!funcall(#'access, ch, pl, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +0100800 return E_ACCESS_DENIED;
801
Arathorn78c08372019-12-11 20:14:23 +0100802 if (IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +0100803 return E_ALREADY_JOINED;
804
MG Mud User88f12472016-06-24 23:31:02 +0200805 channels[ch][I_MEMBER] += ({ pl });
Arathorn19459eb2019-11-30 00:45:51 +0100806 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200807}
808
Arathorn78c08372019-12-11 20:14:23 +0100809// Objekt <pl> verlaesst Ebene <ch>.
810// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
811// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
812// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
813// <pl> das Verlassen auf Grund eines Banns verboten sein.
814// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
815// noch ein Ebenenbesitzer eingetragen ist.
816public int leave(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200817{
Arathorn78c08372019-12-11 20:14:23 +0100818 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200819 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
820 zu erzeugen, weil access() mit extern_call() und previous_object()
821 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
822 richtige ist. */
823 if (!funcall(#'access, ch, pl, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +0100824 return E_ACCESS_DENIED;
825
MG Mud User88f12472016-06-24 23:31:02 +0200826 channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
Arathorn19459eb2019-11-30 00:45:51 +0100827
Arathorn78c08372019-12-11 20:14:23 +0100828 if (!IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +0100829 return E_NOT_MEMBER;
830
Arathorn78c08372019-12-11 20:14:23 +0100831 // Kontrolle an jemand anderen uebergeben, wenn der Ebenenbesitzer diese
832 // verlaesst.
Arathorn19459eb2019-11-30 00:45:51 +0100833 if (pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
MG Mud User88f12472016-06-24 23:31:02 +0200834 {
835 channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
Arathorn19459eb2019-11-30 00:45:51 +0100836
837 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +0100838 {
839 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
840 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
841 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
842 // explizites call_other() auf this_object() gemacht, damit der
843 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
844 // einem externen.
Arathorn19459eb2019-11-30 00:45:51 +0100845 this_object()->send(ch, pl, "uebergibt die Ebene an " +
846 channels[ch][I_MASTER]->name(WEN) + ".", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +0100847 }
MG Mud User88f12472016-06-24 23:31:02 +0200848 }
Arathorn78c08372019-12-11 20:14:23 +0100849 channels[ch][I_MEMBER] -= ({pl});
MG Mud User88f12472016-06-24 23:31:02 +0200850
Arathorn78c08372019-12-11 20:14:23 +0100851 // Ebene loeschen, wenn keiner zuhoert und auch kein Masterobjekt
852 // existiert.
853 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
854 // wird diese zerstoert, mit Meldung.
Arathorn19459eb2019-11-30 00:45:51 +0100855 if (!sizeof(channels[ch][I_MEMBER]) && !stringp(channels[ch][I_MASTER]))
MG Mud User88f12472016-06-24 23:31:02 +0200856 {
Arathorn78c08372019-12-11 20:14:23 +0100857 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Arathorn19459eb2019-11-30 00:45:51 +0100858 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +0100859 {
860 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
861 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
862 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
863 // explizites call_other() auf this_object() gemacht, damit der
864 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
865 // einem externen.
866 this_object()->send(CMNAME, pl,
867 "verlaesst als "+
868 (pl->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
869 " die Ebene '"+channels[ch][I_NAME]+"', worauf diese sich in "
870 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
871 }
Arathorn19459eb2019-11-30 00:45:51 +0100872 channelC[lower_case(ch)] =
873 ({ channels[ch][I_NAME], channels[ch][I_INFO], time() });
Arathorn78c08372019-12-11 20:14:23 +0100874
875 // Ebene loeschen
MG Mud User88f12472016-06-24 23:31:02 +0200876 m_delete(channels, lower_case(ch));
Zesstra8f5102c2020-08-08 12:51:52 +0200877 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
878 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
879 // channelC bereinigt wird.
Arathorn78c08372019-12-11 20:14:23 +0100880
MG Mud User88f12472016-06-24 23:31:02 +0200881 stats["dispose"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100882 save_me_soon = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200883 }
Arathorn19459eb2019-11-30 00:45:51 +0100884 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200885}
886
Arathorn78c08372019-12-11 20:14:23 +0100887// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
888// sofern <pl> dort senden darf.
889public varargs int send(string ch, object pl, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +0200890{
Arathorn78c08372019-12-11 20:14:23 +0100891 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200892 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
893 zu erzeugen, weil access() mit extern_call() und previous_object()
894 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
895 richtige ist. */
896 int a = funcall(#'access, ch, pl, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +0100897 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +0100898 return E_ACCESS_DENIED;
899
Zesstra26aaf1a2020-08-07 19:10:39 +0200900 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
901 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
902 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
903 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
904 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Arathorn78c08372019-12-11 20:14:23 +0100905 if (a < 2 && !IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +0100906 return E_NOT_MEMBER;
907
908 if (!msg || !stringp(msg) || !sizeof(msg))
909 return E_EMPTY_MESSAGE;
910
Arathorn78c08372019-12-11 20:14:23 +0100911 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
912 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
913 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
914 // Nachricht ebenfalls erhaelt.
915 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
916 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
917 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
918 // erzeugt und der Channeld als Besitzer angegeben.
919 // Die Aufrufkette ist dann wie folgt:
920 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
921 channels[ch][I_MEMBER]->ChannelMessage(
922 ({ channels[ch][I_NAME], pl, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +0100923
924 if (sizeof(channelH[ch]) > MAX_HIST_SIZE)
MG Mud User88f12472016-06-24 23:31:02 +0200925 channelH[ch] = channelH[ch][1..];
Arathorn19459eb2019-11-30 00:45:51 +0100926
927 channelH[ch] +=
928 ({ ({ channels[ch][I_NAME],
929 (stringp(pl)
930 ? pl
931 : (pl->QueryProp(P_INVIS)
932 ? "/(" + capitalize(getuid(pl)) + ")$"
933 : "")
934 + (pl->Name(WER, 2) || "<Unbekannt>")),
935 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
936 type }) });
Arathorn78c08372019-12-11 20:14:23 +0100937 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200938}
939
Arathorn78c08372019-12-11 20:14:23 +0100940// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
941// oder einen Integer-Fehlercode
942public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200943{
Arathorn78c08372019-12-11 20:14:23 +0100944 mapping chs = ([]);
945 foreach(string chname, <object*|closure|string|object>* chdata : channels)
946 {
Arathorn739a4fa2020-08-06 21:52:58 +0200947 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
948 zu erzeugen, weil access() mit extern_call() und previous_object()
949 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
950 richtige ist. */
951 if(funcall(#'access, chname, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +0100952 {
953 m_add(chs, chname, chdata);
954 chs[chname][I_MEMBER] = filter(chs[chname][I_MEMBER], #'objectp);
955 }
956 }
Arathorn19459eb2019-11-30 00:45:51 +0100957
958 if (!sizeof(chs))
959 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +0100960 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +0200961}
962
Arathorn78c08372019-12-11 20:14:23 +0100963// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
964// Rueckgabewerte:
965// - den gefundenen Namen als String
966// - String-Array, wenn es mehrere Treffer gibt
967// - 0, wenn es keinen Treffer gibt
968public string|string* find(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200969{
Arathorn78c08372019-12-11 20:14:23 +0100970 ch = lower_case(ch);
Arathorn19459eb2019-11-30 00:45:51 +0100971
Arathorn78c08372019-12-11 20:14:23 +0100972 // Suchstring <ch> muss Formatanforderung erfuellen;
973 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
974 // Wenn ja, muesste laut Manpage mehr geprueft werden:
975 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
976 // #$%&@<>-." Es wuerden also $%&@ fehlen.
977 if (!regmatch(ch, "^[<>a-z0-9#-]+$"))
978 return 0;
Arathorn19459eb2019-11-30 00:45:51 +0100979
Arathorn78c08372019-12-11 20:14:23 +0100980 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
981 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
982 // in das Suchergebnis aufgenommen wird.
983 string* chs = filter(m_indices(channels), function int (string chname) {
Arathorn739a4fa2020-08-06 21:52:58 +0200984 /* funcall() auf Closure-Operator, um einen neuen Eintrag
985 im Caller Stack zu erzeugen, weil access() mit
986 extern_call() und previous_object() arbeitet und
987 sichergestellt sein muss, dass das in jedem Fall das
988 richtige ist. */
Arathorn78c08372019-12-11 20:14:23 +0100989 return ( stringp(regmatch(chname, "^"+ch)) &&
Arathorn739a4fa2020-08-06 21:52:58 +0200990 funcall(#'access, chname, pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +0100991 });
Arathorn19459eb2019-11-30 00:45:51 +0100992
Arathorn78c08372019-12-11 20:14:23 +0100993 int num_channels = sizeof(chs);
994 if (num_channels > 1)
995 return chs;
996 else if (num_channels == 1)
997 return channels[chs[0]][I_NAME];
998 else
999 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001000}
1001
Arathorn78c08372019-12-11 20:14:23 +01001002// Ebenen-History abfragen.
1003public int|<int|string>** history(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001004{
Arathorn78c08372019-12-11 20:14:23 +01001005 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +02001006 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1007 zu erzeugen, weil access() mit extern_call() und previous_object()
1008 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1009 richtige ist. */
1010 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001011 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001012 else
1013 return channelH[ch];
MG Mud User88f12472016-06-24 23:31:02 +02001014}
1015
Arathorn78c08372019-12-11 20:14:23 +01001016// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
1017public int remove_channel(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001018{
Zesstra26aaf1a2020-08-07 19:10:39 +02001019 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001020 if (previous_object() != this_object())
1021 {
1022 if (!stringp(ch) ||
1023 pl != this_player() || this_player() != this_interactive() ||
1024 this_interactive() != previous_object() ||
1025 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001026 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001027 }
MG Mud User88f12472016-06-24 23:31:02 +02001028
Arathorn78c08372019-12-11 20:14:23 +01001029 if (member(channels, lower_case(ch)))
Arathorn19459eb2019-11-30 00:45:51 +01001030 {
Arathorn78c08372019-12-11 20:14:23 +01001031 // Einer geloeschten Ebene kann man nicht zuhoeren: Ebenenname aus der
1032 // Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-, als
1033 // auch temporaer ausgeschaltete Ebenen beruecksichtigt.
1034 foreach(object listener : channels[lower_case(ch)][I_MEMBER])
1035 {
1036 string* pl_chans = listener->QueryProp(P_CHANNELS);
1037 if (pointerp(pl_chans))
Arathorn19459eb2019-11-30 00:45:51 +01001038 {
Arathorn78c08372019-12-11 20:14:23 +01001039 listener->SetProp(P_CHANNELS, pl_chans-({lower_case(ch)}));
1040 }
1041 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
1042 if (pointerp(pl_chans))
1043 {
1044 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({lower_case(ch)}));
1045 }
1046 }
1047 // Anschliessend werden die Ebenendaten geloescht.
1048 m_delete(channels, lower_case(ch));
MG Mud User88f12472016-06-24 23:31:02 +02001049
Arathorn78c08372019-12-11 20:14:23 +01001050 // Zaehler fuer zerstoerte Ebenen in der Statistik erhoehen.
MG Mud User88f12472016-06-24 23:31:02 +02001051 stats["dispose"]++;
1052 }
Zesstra8f5102c2020-08-08 12:51:52 +02001053 // Dies auuserhalb des Blocks oben ermoeglicht es, inaktive Ebenen bzw.
1054 // deren Daten zu entfernen.
1055 m_delete(channelC, lower_case(ch));
1056 // In diesem Fall der gezielten Loeschung wird auch die History geloescht.
1057 m_delete(channelH, lower_case(ch));
Arathorn19459eb2019-11-30 00:45:51 +01001058
Arathorn19459eb2019-11-30 00:45:51 +01001059 save_me_soon = 1;
1060 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001061}
1062
Arathorn78c08372019-12-11 20:14:23 +01001063// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
1064public int clear_history(string ch)
MG Mud User88f12472016-06-24 23:31:02 +02001065{
Zesstra26aaf1a2020-08-07 19:10:39 +02001066 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001067 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001068 if (previous_object() != this_object())
1069 {
1070 if (!stringp(ch) ||
1071 this_player() != this_interactive() ||
1072 this_interactive() != previous_object() ||
1073 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001074 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001075 }
MG Mud User88f12472016-06-24 23:31:02 +02001076
Zesstra26aaf1a2020-08-07 19:10:39 +02001077 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1078 // aus dem mapping loeschen.)
Arathorn19459eb2019-11-30 00:45:51 +01001079 if (pointerp(channelH[lower_case(ch)]))
1080 channelH[lower_case(ch)] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001081
1082 return 0;
1083}