blob: 692505114d8811e644a6a427f9dd7a921971d4e8 [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,
Zesstrae19391f2020-08-09 13:40:12 +020035 string|object supervisor,
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,
Zesstrae19391f2020-08-09 13:40:12 +0200442// flags, description, supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100443private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200444{
Arathorn78c08372019-12-11 20:14:23 +0100445 string desc = "- Keine Beschreibung -";
Zesstrae19391f2020-08-09 13:40:12 +0200446 object supervisor = 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]))
Zesstrae19391f2020-08-09 13:40:12 +0200456 catch(supervisor = load_object(chinfo[5]); publish);
457 if (!objectp(supervisor))
458 supervisor = 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
Zesstrae19391f2020-08-09 13:40:12 +0200480 if (new(chinfo[0], supervisor, 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",
Zesstrae19391f2020-08-09 13:40:12 +0200483 dtime(time()), chinfo[0], supervisor));
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
Zesstra28986e12020-08-09 12:44:26 +0200605// Low-level function for adding members without access checks
606private int add_member(string ch, object m)
607{
608 if (IsChannelMember(ch, m))
609 return E_ALREADY_JOINED;
610
611 channels[ch][I_MEMBER] += ({ m });
612 return 0;
613}
614
Zesstraf87cb772020-08-10 11:14:45 +0200615// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
616// History, so dass sie spaeter reaktiviert werden kann.
617private void deactivate_channel(string ch)
618{
619 ch = lower_case(ch);
620 if (!member(channels, ch))
621 return;
622 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
623 // einloggt, der die Ebene abonniert hat.
624 channelC[lower_case(ch)] =
625 ({ channels[ch][I_NAME], channels[ch][I_INFO], time() });
626
627 // Ebene loeschen bzw. deaktivieren.
628 m_delete(channels, lower_case(ch));
629 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
630 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
631 // channelC bereinigt wird.
632
633 stats["dispose"]++;
634 save_me_soon = 1;
635}
636
637// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
638private void delete_channel(string ch)
639{
640 ch = lower_case(ch);
641 if (member(channels, ch))
642 {
643 // nur Ebenen ohne Zuhoerer loeschen. (Wenn der Aufrufer auch andere
644 // loeschen will, muss er vorher selber die Ebene leer raeumen, s.
645 // Kommandofunktion remove_channel().
646 if (sizeof(channels[ch][I_MEMBER]))
647 raise_error(
648 sprintf("[%s] Attempt to delete channel %s with listeners.\n",
649 dtime(), ch));
650 stats["dispose"]++;
651 }
652 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
653 m_delete(channels, ch);
654 m_delete(channelsC, ch);
655 m_delete(channelsH, ch);
656 save_me_soon = 1;
657}
658
Zesstra5b7f2fc2020-08-10 02:09:13 +0200659
Arathorn78c08372019-12-11 20:14:23 +0100660#define CHAN_NAME(x) channels[x][I_NAME]
Zesstrae19391f2020-08-09 13:40:12 +0200661#define SVISOR_OB(x) channels[x][I_SUPERVISOR]
Arathorn78c08372019-12-11 20:14:23 +0100662#define ACC_CLOSURE(x) channels[x][I_ACCESS]
663
Zesstra5b7f2fc2020-08-10 02:09:13 +0200664// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
665// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
666private int change_sv_object(string ch, object old_sv, object new_sv)
667{
668 if (!new_sv)
669 {
670 channels[ch][I_MEMBER] -= ({0});
671 if (sizeof(channels[ch][I_MEMBER]))
672 new_sv = channels[ch][I_MEMBER][0];
673 else
674 return 0; // kein neuer SV moeglich.
675 }
676 SVISOR_OB(ch) = new_sv;
Zesstra0c69c2d2020-08-10 02:27:20 +0200677 ACC_CLOSURE(ch) = symbol_function("check_ch_access", new_sv);
678
Zesstra5b7f2fc2020-08-10 02:09:13 +0200679 if (old_sv && new_sv
680 && !old_sv->QueryProp(P_INVIS)
681 && !new_sv->QueryProp(P_INVIS))
682 {
683 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
684 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
685 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
686 // explizites call_other() auf this_object() gemacht, damit der
687 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
688 // einem externen.
689 this_object()->send(ch, old_sv,
690 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
691 MSG_EMOTE);
692 }
693 else if (old_svn && !old_sv->QueryProp(P_INVIS))
694 {
695 this_object()->send(ch, old_sv,
696 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
697 }
698 else if (new_sv && !new_sv->QueryProp(P_INVIS))
699 {
700 this_object()->send(ch, new_sv,
701 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
702 }
703 return 1;
704}
705
Zesstra56692c72020-08-09 13:03:10 +0200706// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
707// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
708// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die I_ACCESS
709// closure 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
710// wird access() den Zugriff immer erlauben.
Zesstra78310012020-08-09 12:21:48 +0200711private int assert_supervisor(string ch)
MG Mud User88f12472016-06-24 23:31:02 +0200712{
Arathorn78c08372019-12-11 20:14:23 +0100713 // Es ist keine Closure vorhanden, d.h. der Ebenenbesitzer wurde zerstoert.
Zesstra5770ba62020-08-10 10:19:23 +0200714 //TODO: es ist nicht so selten, dass die Closure 0 ist, d.h. der Code laeuft
715 //haeufig unnoetig!
Arathorn78c08372019-12-11 20:14:23 +0100716 if (!closurep(ACC_CLOSURE(ch)))
MG Mud User88f12472016-06-24 23:31:02 +0200717 {
Arathorn78c08372019-12-11 20:14:23 +0100718 // Wenn der Ebenenbesitzer als String eingetragen ist, versuchen wir,
719 // die Closure wiederherzustellen. Dabei wird das Objekt gleichzeitig
Zesstrae19391f2020-08-09 13:40:12 +0200720 // neugeladen und eingetragen.
721 if (stringp(SVISOR_OB(ch)))
MG Mud User88f12472016-06-24 23:31:02 +0200722 {
Arathorn78c08372019-12-11 20:14:23 +0100723 closure new_acc_cl;
724 string err = catch(new_acc_cl=
Zesstrae19391f2020-08-09 13:40:12 +0200725 symbol_function("check_ch_access", SVISOR_OB(ch));
Arathorn78c08372019-12-11 20:14:23 +0100726 publish);
Zesstra56692c72020-08-09 13:03:10 +0200727 /* Wenn das SV-Objekt neu geladen werden konnte, wird es als Mitglied
728 * eingetragen. Auch die Closure wird neu eingetragen, allerdings kann
729 * sie 0 sein, wenn das SV-Objekt keine oeffentliche check_ch_access()
730 * mehr definiert. In diesem Fall gibt es zwar ein SV-Objekt, aber keine
731 * Zugriffrechte(pruefung) mehr. */
Arathorn78c08372019-12-11 20:14:23 +0100732 if (!err)
733 {
734 ACC_CLOSURE(ch) = new_acc_cl;
Zesstra28986e12020-08-09 12:44:26 +0200735 // Der neue Ebenenbesitzer tritt auch gleich der Ebene bei. Hierbei
736 // erfolgt keine Pruefung des Zugriffsrechtes (ist ja unsinnig, weil
737 // er sich ja selber genehmigen koennte), und auch um eine Rekursion
738 // zu vermeiden.
Zesstrae19391f2020-08-09 13:40:12 +0200739 add_member(ch, find_object(SVISOR_OB(ch)));
Zesstra56692c72020-08-09 13:03:10 +0200740 // Rueckgabewert ist 1, ein neues SV-Objekt ist eingetragen.
Arathorn78c08372019-12-11 20:14:23 +0100741 }
742 else
743 {
Zesstra5770ba62020-08-10 10:19:23 +0200744 log_file("CHANNEL",
745 sprintf("[%s] Channel %s deleted. SV-Fehler: %O -> %O\n",
746 dtime(time()), ch, SVISOR_OB(ch), err));
Zesstraf87cb772020-08-10 11:14:45 +0200747 // Dies ist ein richtiges Loeschen, weil nicht-ladbare SV koennen bei
748 // Deaktivierung zu einer lesbaren History fuehren.
749 delete_channel(ch);
Arathorn78c08372019-12-11 20:14:23 +0100750 return 0;
751 }
MG Mud User88f12472016-06-24 23:31:02 +0200752 }
Zesstra5770ba62020-08-10 10:19:23 +0200753 else if (!objectp(SVISOR_OB(ch)))
Zesstra56692c72020-08-09 13:03:10 +0200754 {
755 // In diesem Fall muss ein neues SV-Objekt gesucht und ggf. eingetragen
Zesstra5770ba62020-08-10 10:19:23 +0200756 // werden. change_sv_object nimmt das aelteste Mitglied der Ebene.
757 if (!change_sv_object(ch, pl, 0))
758 {
759 // wenn das nicht klappt, Ebene aufloesen
760 log_file("CHANNEL",
Zesstraf87cb772020-08-10 11:14:45 +0200761 sprintf("[%s] Deactivating channel %s without SV.\n",
Zesstra5770ba62020-08-10 10:19:23 +0200762 dtime(time()), ch));
Zesstraf87cb772020-08-10 11:14:45 +0200763 deactivate_channel(ch);
Zesstra5770ba62020-08-10 10:19:23 +0200764 return 0;
765 }
Zesstra56692c72020-08-09 13:03:10 +0200766 }
MG Mud User88f12472016-06-24 23:31:02 +0200767 }
Zesstra78310012020-08-09 12:21:48 +0200768 return 1;
769}
770
771// access() - check access by looking for the right argument types and
772// calling access closures respectively
773// SEE: new, join, leave, send, list, users
774// Note: <pl> is usually an object, only the master supplies a string during
775// runtime error handling.
776// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
777// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
778// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
779// als Erfolg und alles ueber >2 als privilegiert.)
780varargs private int access(string ch, object|string pl, string cmd,
781 string txt)
782{
783 if (!sizeof(ch))
784 return 0;
785
786 ch = lower_case(ch);
787 if(!pointerp(channels[ch]))
788 return 0;
789
Zesstrafbfe6362020-08-09 13:30:21 +0200790 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
791 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra78310012020-08-09 12:21:48 +0200792 if ( !previous_object(1) || !extern_call() ||
793 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200794 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200795 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200796
Zesstra56692c72020-08-09 13:03:10 +0200797 // Nur dieses Objekt darf Meldungen im Namen anderer Objekte faken,
798 // ansonsten muss <pl> der Aufrufer sein.
Arathorn739a4fa2020-08-06 21:52:58 +0200799 if (!objectp(pl) ||
800 ((previous_object(1) != pl) && (previous_object(1) != this_object())))
801 return 0;
802
803 if (IsBanned(pl, cmd))
804 return 0;
805
Zesstra56692c72020-08-09 13:03:10 +0200806 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
807 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200808 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200809 return 0;
810 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
811 if (!ACC_CLOSURE(ch))
Arathorn739a4fa2020-08-06 21:52:58 +0200812 return 1;
813
Zesstra6fe46cd2020-08-09 13:12:15 +0200814 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
815 // fuer EM+ aber nur, wenn der CHANNELD selber das SV-Objekt ist, damit
816 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
817 // CHANNELD als SV koennen aber natuerlich auch EM+ Zugriff verweigern.
818 if (IS_ARCH(previous_object(1))
Zesstrae19391f2020-08-09 13:40:12 +0200819 && find_object(SVISOR_OB(ch)) != this_object())
Zesstra6fe46cd2020-08-09 13:12:15 +0200820 return 1;
821
Arathorn739a4fa2020-08-06 21:52:58 +0200822 return funcall(ACC_CLOSURE(ch), ch, pl, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200823}
824
Arathorn78c08372019-12-11 20:14:23 +0100825// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
826// <info> kann die statische Beschreibung der Ebene sein oder eine Closure,
827// die dynamisch aktualisierte Infos ausgibt.
828// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
829// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
830// eingeht.
831// check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
832// Nachricht gesendet werden darf oder nicht.
833
834// Ist keine Closure angegeben, wird die in diesem Objekt (Channeld)
835// definierte Funktion gleichen Namens verwendet.
MG Mud User88f12472016-06-24 23:31:02 +0200836#define IGNORE "^/xx"
837
Arathorn78c08372019-12-11 20:14:23 +0100838// TODO: KOMMENTAR
839//check may contain a closure
840// called when a join/leave/send/list/users message is received
Zesstra7da4d692020-08-10 11:17:54 +0200841public varargs int new(string ch_name, object owner, string|closure desc)
MG Mud User88f12472016-06-24 23:31:02 +0200842{
Arathorn78c08372019-12-11 20:14:23 +0100843 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
844 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
845 // fuer eine neue Ebene eintragen.)
846 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200847 return E_ACCESS_DENIED;
848
Arathorn78c08372019-12-11 20:14:23 +0100849 // Kein gescheiter Channelname angegeben.
850 if (!stringp(ch_name) || !sizeof(ch_name))
851 return E_ACCESS_DENIED;
852
853 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
854 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
855 return E_ACCESS_DENIED;
856
857 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
858 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
859 // seinen Objektnamen matcht.
860 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
861 return E_ACCESS_DENIED;
862
Zesstra7da4d692020-08-10 11:17:54 +0200863 // Keine Beschreibung mitgeliefert? Dann holen wir sie aus dem Cache.
864 if (!desc)
Arathorn19459eb2019-11-30 00:45:51 +0100865 {
Arathorn78c08372019-12-11 20:14:23 +0100866 if (channelC[lower_case(ch_name)])
Arathorn19459eb2019-11-30 00:45:51 +0100867 {
Arathorn78c08372019-12-11 20:14:23 +0100868 ch_name = channelC[lower_case(ch_name)][0];
Zesstra7da4d692020-08-10 11:17:54 +0200869 desc = channelC[lower_case(ch_name)][1];
MG Mud User88f12472016-06-24 23:31:02 +0200870 }
Arathorn19459eb2019-11-30 00:45:51 +0100871 else
872 {
873 return E_ACCESS_DENIED;
874 }
MG Mud User88f12472016-06-24 23:31:02 +0200875 }
Arathorn19459eb2019-11-30 00:45:51 +0100876 else
877 {
Zesstra7da4d692020-08-10 11:17:54 +0200878 channelC[lower_case(ch_name)] = ({ ch_name, desc, time() });
Arathorn19459eb2019-11-30 00:45:51 +0100879 }
MG Mud User88f12472016-06-24 23:31:02 +0200880
Arathorn78c08372019-12-11 20:14:23 +0100881 object* pls = ({ owner });
882 m_add(channels, lower_case(ch_name),
883 ({ pls,
884 symbol_function("check_ch_access", owner) || #'check_ch_access,
Zesstra7da4d692020-08-10 11:17:54 +0200885 desc,
Arathorn78c08372019-12-11 20:14:23 +0100886 (!living(owner) && !clonep(owner) && owner != this_object()
887 ? object_name(owner)
888 : owner),
889 ch_name }));
MG Mud User88f12472016-06-24 23:31:02 +0200890
Arathorn78c08372019-12-11 20:14:23 +0100891 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
892 // nicht gibt.
893 if (!pointerp(channelH[lower_case(ch_name)]))
894 channelH[lower_case(ch_name)] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200895
Arathorn78c08372019-12-11 20:14:23 +0100896 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
897 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +0200898 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstra7da4d692020-08-10 11:17:54 +0200899 dtime(time()), ch_name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +0100900
Arathorn78c08372019-12-11 20:14:23 +0100901 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
902 if (!owner->QueryProp(P_INVIS))
903 {
904 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
905 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
906 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
907 // explizites call_other() auf this_object() gemacht, damit der
908 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
909 // einem externen.
910 this_object()->send(CMNAME, owner,
911 "laesst die Ebene '" + ch_name + "' entstehen.", MSG_EMOTE);
912 }
Arathorn19459eb2019-11-30 00:45:51 +0100913
MG Mud User88f12472016-06-24 23:31:02 +0200914 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100915 save_me_soon = 1;
916 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200917}
918
Arathorn78c08372019-12-11 20:14:23 +0100919// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
920// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
921// zweimal beitreten.)
922public int join(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200923{
Arathorn78c08372019-12-11 20:14:23 +0100924 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200925 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
926 zu erzeugen, weil access() mit extern_call() und previous_object()
927 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
928 richtige ist. */
929 if (!funcall(#'access, ch, pl, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +0100930 return E_ACCESS_DENIED;
931
Zesstra28986e12020-08-09 12:44:26 +0200932 return add_member(ch, pl);
MG Mud User88f12472016-06-24 23:31:02 +0200933}
934
Arathorn78c08372019-12-11 20:14:23 +0100935// Objekt <pl> verlaesst Ebene <ch>.
936// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
937// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
938// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
939// <pl> das Verlassen auf Grund eines Banns verboten sein.
940// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
941// noch ein Ebenenbesitzer eingetragen ist.
942public int leave(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200943{
Arathorn78c08372019-12-11 20:14:23 +0100944 ch = lower_case(ch);
Zesstra877cb0a2020-08-10 02:10:21 +0200945
946 channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
947
948 if (!IsChannelMember(ch, pl))
949 return E_NOT_MEMBER;
950
Arathorn739a4fa2020-08-06 21:52:58 +0200951 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
952 zu erzeugen, weil access() mit extern_call() und previous_object()
953 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
954 richtige ist. */
955 if (!funcall(#'access, ch, pl, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +0100956 return E_ACCESS_DENIED;
957
Zesstrae6d33852020-08-09 14:37:53 +0200958 // Erstmal den Zuhoerer raus.
959 channels[ch][I_MEMBER] -= ({pl});
960
Zesstra5b7f2fc2020-08-10 02:09:13 +0200961 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
962 // wechseln.
963 if (sizeof(channels[ch][I_MEMBER]))
MG Mud User88f12472016-06-24 23:31:02 +0200964 {
Zesstra5b7f2fc2020-08-10 02:09:13 +0200965 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
966 // diese verlassen hat. change_sv_object() waehlt per Default den
967 // aeltesten Zuhoerer.
Zesstra77121862020-08-10 02:23:53 +0200968 if (pl == channels[ch][I_SUPERVISOR]
969 || object_name(pl) == channels[ch][I_SUPERVISOR])
Arathorn78c08372019-12-11 20:14:23 +0100970 {
Zesstra5b7f2fc2020-08-10 02:09:13 +0200971 change_sv_object(ch, pl, 0);
Arathorn78c08372019-12-11 20:14:23 +0100972 }
MG Mud User88f12472016-06-24 23:31:02 +0200973 }
Zesstra137ea1c2020-08-10 02:15:20 +0200974 // ansonsten Ebene loeschen, wenn keiner zuhoert.
975 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
976 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
977 else
MG Mud User88f12472016-06-24 23:31:02 +0200978 {
Arathorn78c08372019-12-11 20:14:23 +0100979 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +0200980 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
981 // wird diese zerstoert, mit Meldung.
Arathorn19459eb2019-11-30 00:45:51 +0100982 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +0100983 {
984 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
985 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
986 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
987 // explizites call_other() auf this_object() gemacht, damit der
988 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
989 // einem externen.
990 this_object()->send(CMNAME, pl,
991 "verlaesst als "+
992 (pl->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
993 " die Ebene '"+channels[ch][I_NAME]+"', worauf diese sich in "
994 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
995 }
Zesstraf87cb772020-08-10 11:14:45 +0200996 deactivate_channel(ch);
MG Mud User88f12472016-06-24 23:31:02 +0200997 }
Arathorn19459eb2019-11-30 00:45:51 +0100998 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200999}
1000
Arathorn78c08372019-12-11 20:14:23 +01001001// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1002// sofern <pl> dort senden darf.
1003public varargs int send(string ch, object pl, string msg, int type)
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 int a = funcall(#'access, ch, pl, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001011 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001012 return E_ACCESS_DENIED;
1013
Zesstra26aaf1a2020-08-07 19:10:39 +02001014 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
1015 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
1016 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
1017 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1018 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Arathorn78c08372019-12-11 20:14:23 +01001019 if (a < 2 && !IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +01001020 return E_NOT_MEMBER;
1021
1022 if (!msg || !stringp(msg) || !sizeof(msg))
1023 return E_EMPTY_MESSAGE;
1024
Arathorn78c08372019-12-11 20:14:23 +01001025 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1026 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1027 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1028 // Nachricht ebenfalls erhaelt.
1029 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1030 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1031 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1032 // erzeugt und der Channeld als Besitzer angegeben.
1033 // Die Aufrufkette ist dann wie folgt:
1034 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
1035 channels[ch][I_MEMBER]->ChannelMessage(
1036 ({ channels[ch][I_NAME], pl, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001037
1038 if (sizeof(channelH[ch]) > MAX_HIST_SIZE)
MG Mud User88f12472016-06-24 23:31:02 +02001039 channelH[ch] = channelH[ch][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001040
1041 channelH[ch] +=
1042 ({ ({ channels[ch][I_NAME],
1043 (stringp(pl)
1044 ? pl
1045 : (pl->QueryProp(P_INVIS)
1046 ? "/(" + capitalize(getuid(pl)) + ")$"
1047 : "")
1048 + (pl->Name(WER, 2) || "<Unbekannt>")),
1049 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1050 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001051 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001052}
1053
Arathorn78c08372019-12-11 20:14:23 +01001054// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1055// oder einen Integer-Fehlercode
1056public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001057{
Arathorn78c08372019-12-11 20:14:23 +01001058 mapping chs = ([]);
1059 foreach(string chname, <object*|closure|string|object>* chdata : channels)
1060 {
Arathorn739a4fa2020-08-06 21:52:58 +02001061 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1062 zu erzeugen, weil access() mit extern_call() und previous_object()
1063 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1064 richtige ist. */
1065 if(funcall(#'access, chname, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001066 {
1067 m_add(chs, chname, chdata);
1068 chs[chname][I_MEMBER] = filter(chs[chname][I_MEMBER], #'objectp);
1069 }
1070 }
Arathorn19459eb2019-11-30 00:45:51 +01001071
1072 if (!sizeof(chs))
1073 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001074 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001075}
1076
Arathorn78c08372019-12-11 20:14:23 +01001077// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1078// Rueckgabewerte:
1079// - den gefundenen Namen als String
1080// - String-Array, wenn es mehrere Treffer gibt
1081// - 0, wenn es keinen Treffer gibt
1082public string|string* find(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001083{
Arathorn78c08372019-12-11 20:14:23 +01001084 ch = lower_case(ch);
Arathorn19459eb2019-11-30 00:45:51 +01001085
Arathorn78c08372019-12-11 20:14:23 +01001086 // Suchstring <ch> muss Formatanforderung erfuellen;
1087 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1088 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1089 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1090 // #$%&@<>-." Es wuerden also $%&@ fehlen.
1091 if (!regmatch(ch, "^[<>a-z0-9#-]+$"))
1092 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001093
Arathorn78c08372019-12-11 20:14:23 +01001094 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1095 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1096 // in das Suchergebnis aufgenommen wird.
1097 string* chs = filter(m_indices(channels), function int (string chname) {
Arathorn739a4fa2020-08-06 21:52:58 +02001098 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1099 im Caller Stack zu erzeugen, weil access() mit
1100 extern_call() und previous_object() arbeitet und
1101 sichergestellt sein muss, dass das in jedem Fall das
1102 richtige ist. */
Arathorn78c08372019-12-11 20:14:23 +01001103 return ( stringp(regmatch(chname, "^"+ch)) &&
Arathorn739a4fa2020-08-06 21:52:58 +02001104 funcall(#'access, chname, pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001105 });
Arathorn19459eb2019-11-30 00:45:51 +01001106
Arathorn78c08372019-12-11 20:14:23 +01001107 int num_channels = sizeof(chs);
1108 if (num_channels > 1)
1109 return chs;
1110 else if (num_channels == 1)
1111 return channels[chs[0]][I_NAME];
1112 else
1113 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001114}
1115
Arathorn78c08372019-12-11 20:14:23 +01001116// Ebenen-History abfragen.
1117public int|<int|string>** history(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001118{
Arathorn78c08372019-12-11 20:14:23 +01001119 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +02001120 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1121 zu erzeugen, weil access() mit extern_call() und previous_object()
1122 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1123 richtige ist. */
1124 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001125 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001126 else
1127 return channelH[ch];
MG Mud User88f12472016-06-24 23:31:02 +02001128}
1129
Arathorn78c08372019-12-11 20:14:23 +01001130// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
1131public int remove_channel(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001132{
Zesstra26aaf1a2020-08-07 19:10:39 +02001133 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001134 if (previous_object() != this_object())
1135 {
1136 if (!stringp(ch) ||
1137 pl != this_player() || this_player() != this_interactive() ||
1138 this_interactive() != previous_object() ||
1139 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001140 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001141 }
MG Mud User88f12472016-06-24 23:31:02 +02001142
Zesstraf87cb772020-08-10 11:14:45 +02001143 // Wenn die Ebene aktiv ist (d.h. Zuhoerer hat), muessen die erst
1144 // runtergeworfen werden.
Arathorn78c08372019-12-11 20:14:23 +01001145 if (member(channels, lower_case(ch)))
Arathorn19459eb2019-11-30 00:45:51 +01001146 {
Arathorn78c08372019-12-11 20:14:23 +01001147 // Einer geloeschten Ebene kann man nicht zuhoeren: Ebenenname aus der
1148 // Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-, als
1149 // auch temporaer ausgeschaltete Ebenen beruecksichtigt.
1150 foreach(object listener : channels[lower_case(ch)][I_MEMBER])
1151 {
1152 string* pl_chans = listener->QueryProp(P_CHANNELS);
1153 if (pointerp(pl_chans))
Arathorn19459eb2019-11-30 00:45:51 +01001154 {
Arathorn78c08372019-12-11 20:14:23 +01001155 listener->SetProp(P_CHANNELS, pl_chans-({lower_case(ch)}));
1156 }
1157 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
1158 if (pointerp(pl_chans))
1159 {
1160 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({lower_case(ch)}));
1161 }
1162 }
MG Mud User88f12472016-06-24 23:31:02 +02001163 }
Zesstraf87cb772020-08-10 11:14:45 +02001164 // Dies auserhalb des Blocks oben ermoeglicht es, inaktive Ebenen bzw.
Zesstra8f5102c2020-08-08 12:51:52 +02001165 // deren Daten zu entfernen.
Zesstraf87cb772020-08-10 11:14:45 +02001166 delete_channel(ch);
Arathorn19459eb2019-11-30 00:45:51 +01001167
Arathorn19459eb2019-11-30 00:45:51 +01001168 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001169}
1170
Arathorn78c08372019-12-11 20:14:23 +01001171// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
1172public int clear_history(string ch)
MG Mud User88f12472016-06-24 23:31:02 +02001173{
Zesstra26aaf1a2020-08-07 19:10:39 +02001174 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001175 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001176 if (previous_object() != this_object())
1177 {
1178 if (!stringp(ch) ||
1179 this_player() != this_interactive() ||
1180 this_interactive() != previous_object() ||
1181 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001182 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001183 }
MG Mud User88f12472016-06-24 23:31:02 +02001184
Zesstra26aaf1a2020-08-07 19:10:39 +02001185 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1186 // aus dem mapping loeschen.)
Arathorn19459eb2019-11-30 00:45:51 +01001187 if (pointerp(channelH[lower_case(ch)]))
1188 channelH[lower_case(ch)] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001189
1190 return 0;
1191}