blob: c87fef1276b6a4e90c0eff6e8d0485c1e7fb4294 [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
Zesstrab7720dc2020-08-11 22:14:18 +020031// Datenstrukturen fuer die Ebenen.
32// Basisdaten, welche auch inaktive Ebenen in channelC haben
33struct channel_base_s {
34 string name; // readable channelname, case-sensitive
35 string|closure desc; // stat. oder dyn. Beschreibung
36 string creator; // Ersteller der Ebene (Objektname)
37// int flags; // Flags, die weiteres Verhalten steuern.
38};
39
40// Basisdaten + die von aktiven Ebenen
41struct channel_s (channel_base_s) {
42 object|string supervisor; // aktueller Supervisor der Ebene
43 closure access_cl; // Closure fuer Zugriffsrechtepruefung
44 object *members; // Zuhoerer der Ebene
45};
46
47/* Ebenenliste und die zugehoerigen Daten in struct (<channel>).
48 channels = ([string channelname : (<channel_s>) ])
Zesstra78310012020-08-09 12:21:48 +020049 */
Arathorn78c08372019-12-11 20:14:23 +010050private nosave mapping channels = ([]);
Arathorn19459eb2019-11-30 00:45:51 +010051
Arathorn78c08372019-12-11 20:14:23 +010052/* Ebenenhistory
53 mapping channelH = ([ string channelname : ({ ({string channelname,
54 string sender,
55 string msg,
56 int msg_type}) }) ]) */
57// channelH wird in create() geeignet initialisiert
58// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
59// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
60// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
61// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
62// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020063private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010064
Arathorn78c08372019-12-11 20:14:23 +010065/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
66 zerstoerte Ebenen.
67 mapping stats = ([ "time" : int object_time(),
68 "boot" : string getuid(previous_object()),
Arathorn19459eb2019-11-30 00:45:51 +010069 "new" : int total_channels_created,
70 "disposed" : int total_channels_removed ]) */
Arathorn78c08372019-12-11 20:14:23 +010071// stats wird in create() geeignet initialisiert
MG Mud User88f12472016-06-24 23:31:02 +020072private nosave mapping stats;
73
Arathorn78c08372019-12-11 20:14:23 +010074/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +020075 mapping channelC = ([ string channelname : (<channel_base_s>);
76 int time() ])
77 Der Zeitstempel ist die letzte Aenderung, d.h. in der Regel des Ablegens in
78 channelC.
79 */
80private mapping channelC = ([:2]);
Arathorn19459eb2019-11-30 00:45:51 +010081
Arathorn78c08372019-12-11 20:14:23 +010082/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
83 mapping channelB = ([ string playername : string* banned_command ]) */
84private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020085
Arathorn78c08372019-12-11 20:14:23 +010086/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
87 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
88 kann.
89
Arathorn19459eb2019-11-30 00:45:51 +010090 mapping Tcmd = ([ "lag": int timestamp,
91 "uptime": int timestamp,
92 "statistik": int timestamp]) */
93private mapping Tcmd = ([]);
94
Arathorn78c08372019-12-11 20:14:23 +010095/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
96 Speicherevent das Savefile geschrieben werden soll.
97 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +020098private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +020099
Arathorn19459eb2019-11-30 00:45:51 +0100100
MG Mud User88f12472016-06-24 23:31:02 +0200101// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
102
Arathorn78c08372019-12-11 20:14:23 +0100103// Indizes fuer Zugriffe auf das Mapping <admin>.
MG Mud User88f12472016-06-24 23:31:02 +0200104#define RECV 0
105#define SEND 1
106#define FLAG 2
107
Arathorn78c08372019-12-11 20:14:23 +0100108// Ebenenflags, gespeichert in admin[ch, FLAG]
109// F_WIZARD kennzeichnet reine Magierebenen
MG Mud User88f12472016-06-24 23:31:02 +0200110#define F_WIZARD 1
Arathorn78c08372019-12-11 20:14:23 +0100111// Ebenen, auf denen keine Gaeste erlaubt sind, sind mit F_NOGUEST markiert.
MG Mud User88f12472016-06-24 23:31:02 +0200112#define F_NOGUEST 2
113
Arathorn78c08372019-12-11 20:14:23 +0100114/* Speichert Sende- und Empfangslevel sowie Flags zu den einzelnen Channeln.
Zesstrab7720dc2020-08-11 22:14:18 +0200115 * Diese werden aber nur ausgewertet, wenn der Channeld die Rechtepruefung
116 * fuer die jeweilige Ebene macht, d.h. in der Praxis bei Ebenen, bei denen
117 * der CHANNELD der Supervisor ist.
118 * Deswegen sind diese Daten auch nicht in der struct (<channel_s>) drin.
119 * Wird beim Laden des Masters via create() -> initalize() -> setup() mit den
120 * Daten aus dem Init-File ./channeld.init befuellt.
Arathorn78c08372019-12-11 20:14:23 +0100121 mapping admin = ([ string channel_name : int RECV_LVL,
122 int SEND_LVL,
123 int FLAG ]) */
MG Mud User88f12472016-06-24 23:31:02 +0200124private nosave mapping admin = m_allocate(0, 3);
125
Arathorn78c08372019-12-11 20:14:23 +0100126// check_ch_access() prueft die Zugriffsberechtigungen auf Ebenen.
127//
128// Gibt 1 zurueck, wenn Aktion erlaubt, 0 sonst.
129// Wird von access() gerufen; access() gibt das Ergebnis von
130// check_ch_access() zurueck.
131//
132// Verlassen (C_LEAVE) ist immer erlaubt. Die anderen Aktionen sind in zwei
133// Gruppen eingeteilt:
134// 1) RECV. Die Aktionen dieser Gruppe sind Suchen (C_FIND), Auflisten
135// (C_LIST) und Betreten (C_JOIN).
136// 2) SEND. Die Aktion dieser Gruppe ist zur Zeit nur Senden (C_SEND).
137//
138// Aktionen werden zugelassen, wenn Spieler/MagierLevel groesser ist als die
139// fuer die jeweilige Aktionsgruppe RECV oder SEND festgelegte Stufe.
140// Handelt es sich um eine Magierebene (F_WIZARD), muss die Magierstufe
141// des Spielers groesser sein als die Mindeststufe der Ebene. Ansonsten
142// wird gegen den Spielerlevel geprueft.
143//
144// Wenn RECV_LVL oder SEND_LVL auf -1 gesetzt ist, sind die Aktionen der
145// jeweiligen Gruppen komplett geblockt.
MG Mud User88f12472016-06-24 23:31:02 +0200146
Arathorn739a4fa2020-08-06 21:52:58 +0200147public int check_ch_access(string ch, object pl, string cmd)
Arathorn78c08372019-12-11 20:14:23 +0100148{
149 // <pl> ist Gast, es sind aber keine Gaeste zugelassen? Koennen wir
150 // direkt ablehnen.
Arathorn19459eb2019-11-30 00:45:51 +0100151 if ((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest())
152 return 0;
153
Arathorn78c08372019-12-11 20:14:23 +0100154 // Ebenso auf Magier- oder Seherebenen, wenn ein Spieler anfragt, der
155 // noch kein Seher ist.
Arathorn19459eb2019-11-30 00:45:51 +0100156 if ((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL)
157 return 0;
158
Arathorn78c08372019-12-11 20:14:23 +0100159 // Ebene ist Magierebene? Dann werden alle Stufenlimits gegen Magierlevel
160 // geprueft, ansonsten gegen Spielerlevel.
161 int level = (admin[ch, FLAG] & F_WIZARD
162 ? query_wiz_level(pl)
163 : pl->QueryProp(P_LEVEL));
MG Mud User88f12472016-06-24 23:31:02 +0200164
Arathorn19459eb2019-11-30 00:45:51 +0100165 switch (cmd)
MG Mud User88f12472016-06-24 23:31:02 +0200166 {
Arathorn19459eb2019-11-30 00:45:51 +0100167 case C_FIND:
168 case C_LIST:
169 case C_JOIN:
170 if (admin[ch, RECV] == -1)
171 return 0;
172 if (admin[ch, RECV] <= level)
173 return 1;
174 break;
175
176 case C_SEND:
177 if (admin[ch, SEND] == -1)
178 return 0;
179 if (admin[ch, SEND] <= level)
180 return 1;
181 break;
182
Arathorn78c08372019-12-11 20:14:23 +0100183 // Verlassen ist immer erlaubt
Arathorn19459eb2019-11-30 00:45:51 +0100184 case C_LEAVE:
185 return 1;
186
187 default:
188 break;
MG Mud User88f12472016-06-24 23:31:02 +0200189 }
Arathorn19459eb2019-11-30 00:45:51 +0100190 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200191}
192
Arathorn78c08372019-12-11 20:14:23 +0100193/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
194// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
195private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200196{
Arathorn78c08372019-12-11 20:14:23 +0100197 object* userlist = ({});
Zesstrab7720dc2020-08-11 22:14:18 +0200198 foreach(string ch_name, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +0100199 {
Zesstrab7720dc2020-08-11 22:14:18 +0200200 userlist += ch.members;
Arathorn78c08372019-12-11 20:14:23 +0100201 }
202 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
203 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200204}
205
Arathorn78c08372019-12-11 20:14:23 +0100206// Ist das Objekt <sender> Abonnent der Ebene <ch>?
Zesstrab7720dc2020-08-11 22:14:18 +0200207private int IsChannelMember(struct channel_s ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200208{
Zesstrab7720dc2020-08-11 22:14:18 +0200209 return (member(ch.members, sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200210}
211
Arathorn78c08372019-12-11 20:14:23 +0100212// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
213private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200214{
Arathorn78c08372019-12-11 20:14:23 +0100215 if (objectp(ob))
216 ob = getuid(ob);
217 return(pointerp(channelB[ob]) &&
218 member(channelB[ob], command) != -1);
219}
MG Mud User88f12472016-06-24 23:31:02 +0200220
Arathorn78c08372019-12-11 20:14:23 +0100221private void banned(string plname, string* cmds, string res)
222{
223 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
224}
225
226#define TIMEOUT (time() - 60)
227
228// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
229// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
230// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
231// zurueckliegt, als "nicht gesperrt".
232private int IsNotBlocked(string* cmd)
233{
234 string* res = filter(cmd, function int (string str) {
235 return (Tcmd[str] < TIMEOUT);
236 });
237 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
238 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
239 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
240 if (sizeof(res) == sizeof(cmd)) {
241 foreach(string str : cmd) {
242 Tcmd[str] = time();
243 }
244 return 1;
245 }
246 return 0;
247}
248
249// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
250// zugelassen wird. Anforderungen:
251// 1) <cmd> muss Teilstring von <check> sein
252// 2) <cmd> muss am Anfang von <check> stehen
253// 3) <cmd> darf nicht laenger sein als <check>
254// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
255// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
256// cmd = "statistiker" oder cmd = "tist"
257// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
258private int IsValidChannelCommand(string cmd, string check) {
259 // Syntaxcheck (prueft Bedingungen 1 bis 3).
260 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
261 string* cmd_to_check;
262 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
263 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
264 // geben.
265 if ( cmd == "lust" )
266 cmd_to_check = ({"lag", "statistik", "uptime"});
267 else
268 cmd_to_check = ({cmd});
269 // Prueft die Zeitsperre (Bedingung 4).
270 return (IsNotBlocked(cmd_to_check));
271 }
272 return 0;
273}
274
275#define CH_NAME 0
276#define CH_SENDER 1
277#define CH_MSG 2
278#define CH_MSG_TYPE 3
279// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
280// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
281// und dieses per Callout an send() uebergeben.
Zesstrab7720dc2020-08-11 22:14:18 +0200282// Argument: ({channel.name, object pl, string msg, int type})
Arathorn78c08372019-12-11 20:14:23 +0100283// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
284// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
285// was aber bei einer private oder protected Funktion nicht moeglich waere.
286public void ChannelMessage(<string|object|int>* msg)
287{
288 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
289 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
290 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
291 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100292 return;
MG Mud User88f12472016-06-24 23:31:02 +0200293
Arathorn78c08372019-12-11 20:14:23 +0100294 float* lag;
295 int max, rekord;
296 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200297 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200298
Arathorn78c08372019-12-11 20:14:23 +0100299 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200300 {
Arathorn78c08372019-12-11 20:14:23 +0100301 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
302 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100303 }
Arathorn78c08372019-12-11 20:14:23 +0100304 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100305 {
MG Mud User88f12472016-06-24 23:31:02 +0200306 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
307 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100308 "%.1f%%/20s, %.1f%%/2s",
309 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100310 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
311 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100312 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200313 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100314 }
Arathorn78c08372019-12-11 20:14:23 +0100315 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200316 {
Arathorn78c08372019-12-11 20:14:23 +0100317 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100318 {
Arathorn78c08372019-12-11 20:14:23 +0100319 string unused;
320 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
321 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
322 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
323 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
324 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100325 }
326 else
327 {
Arathorn78c08372019-12-11 20:14:23 +0100328 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200329 }
Arathorn19459eb2019-11-30 00:45:51 +0100330 }
Arathorn78c08372019-12-11 20:14:23 +0100331 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200332 {
MG Mud User88f12472016-06-24 23:31:02 +0200333 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100334 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
335 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
336 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
337 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100338 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
339 }
Arathorn78c08372019-12-11 20:14:23 +0100340 // Ebenenaktion beginnt mit "bann"?
341 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200342 {
343 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100344
345 if (mesg == "bann")
346 {
347 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200348 {
Arathorn78c08372019-12-11 20:14:23 +0100349 ret = "Fuer folgende Spieler besteht ein Bann: ";
350 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
351 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
352 string* banlist = ({});
353 foreach(string plname, string* banned_cmds : channelB) {
354 banlist += ({ sprintf("%s [%s]",
355 capitalize(plname), implode(banned_cmds, ", "))});
356 }
357 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200358 }
359 else
360 {
Arathorn19459eb2019-11-30 00:45:51 +0100361 ret = "Zur Zeit ist kein Bann aktiv.";
362 }
363 }
364 else
365 {
Arathorn78c08372019-12-11 20:14:23 +0100366 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
367 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100368 {
369 pl = lower_case(pl);
370 cmd = lower_case(cmd);
371
372 if (member(CMDS, cmd) != -1)
373 {
Arathorn78c08372019-12-11 20:14:23 +0100374 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
375 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
376 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100377 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100378 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100379
Arathorn78c08372019-12-11 20:14:23 +0100380 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100381 channelB[pl] -= ({ cmd });
382 else
383 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100384
Arathorn78c08372019-12-11 20:14:23 +0100385 ret = "Fuer '" + capitalize(pl) + "' besteht " +
386 (sizeof(channelB[pl])
387 // TODO: implode() -> CountUp()?
388 ? "folgender Bann: " + implode(channelB[pl], ", ") + "."
389 : "kein Bann mehr.");
390
391 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100392 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100393 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100394
Zesstraa2db5522020-08-11 22:14:55 +0200395 //TODO: save_me_soon=1 sollte auch reichen...
Arathorn19459eb2019-11-30 00:45:51 +0100396 save_object(CHANNEL_SAVE);
397 }
398 else
399 {
400 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100401 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100402 }
403 }
404 else
405 {
Arathorn78c08372019-12-11 20:14:23 +0100406 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100407 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200408 }
409 }
410 }
Arathorn78c08372019-12-11 20:14:23 +0100411 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200412 {
MG Mud User88f12472016-06-24 23:31:02 +0200413 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100414 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
415 {
416 string unused;
417 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
418 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
419 }
MG Mud User88f12472016-06-24 23:31:02 +0200420
Arathorn78c08372019-12-11 20:14:23 +0100421 int t = time() - last_reboot_time();
422
423 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
424 // die Funktionalitaet oefter benoetigt wird als nur hier.
425 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100426 if (t >= 86400)
427 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200428
Arathorn78c08372019-12-11 20:14:23 +0100429 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100430 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100431 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100432
Arathorn78c08372019-12-11 20:14:23 +0100433 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100434 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100435 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100436
437 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100438
MG Mud User88f12472016-06-24 23:31:02 +0200439 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100440 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100441 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100442 }
443 else
444 {
445 return;
446 }
MG Mud User88f12472016-06-24 23:31:02 +0200447
Arathorn78c08372019-12-11 20:14:23 +0100448 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
449 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
450 // nicht erfuellt sind.
451 if (stringp(ret) && sizeof(ret))
452 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200453}
454
455// setup() -- set up a channel and register it
456// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100457// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrae19391f2020-08-09 13:40:12 +0200458// flags, description, supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100459private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200460{
Arathorn78c08372019-12-11 20:14:23 +0100461 string desc = "- Keine Beschreibung -";
Zesstrae19391f2020-08-09 13:40:12 +0200462 object supervisor = this_object();
Arathorn19459eb2019-11-30 00:45:51 +0100463
Arathorn78c08372019-12-11 20:14:23 +0100464 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
465 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200466
Arathorn78c08372019-12-11 20:14:23 +0100467 switch (sizeof(chinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200468 {
Arathorn78c08372019-12-11 20:14:23 +0100469 // Alle Fallthroughs in dem switch() sind Absicht.
Arathorn19459eb2019-11-30 00:45:51 +0100470 case 6:
Arathorn78c08372019-12-11 20:14:23 +0100471 if (stringp(chinfo[5]) && sizeof(chinfo[5]))
Zesstrae19391f2020-08-09 13:40:12 +0200472 catch(supervisor = load_object(chinfo[5]); publish);
473 if (!objectp(supervisor))
474 supervisor = this_object();
Arathorn19459eb2019-11-30 00:45:51 +0100475
476 case 5:
Arathorn78c08372019-12-11 20:14:23 +0100477 if (stringp(chinfo[4]) || closurep(chinfo[4]))
478 desc = chinfo[4];
Zesstra26aaf1a2020-08-07 19:10:39 +0200479 // Die admin-Daten sind nicht fuer die Ebene wichtig, nur fuer die
480 // check_ch_access().
Arathorn19459eb2019-11-30 00:45:51 +0100481 case 4:
Arathorn739a4fa2020-08-06 21:52:58 +0200482 admin[lower_case(chinfo[0]), FLAG] = to_int(chinfo[3]);
Arathorn19459eb2019-11-30 00:45:51 +0100483
484 case 3:
Arathorn739a4fa2020-08-06 21:52:58 +0200485 admin[lower_case(chinfo[0]), SEND] = to_int(chinfo[2]);
Arathorn19459eb2019-11-30 00:45:51 +0100486
487 case 2:
Arathorn739a4fa2020-08-06 21:52:58 +0200488 admin[lower_case(chinfo[0]), RECV] = to_int(chinfo[1]);
Arathorn19459eb2019-11-30 00:45:51 +0100489 break;
490
491 case 0:
492 default:
493 return;
MG Mud User88f12472016-06-24 23:31:02 +0200494 }
Arathorn19459eb2019-11-30 00:45:51 +0100495
Zesstrae19391f2020-08-09 13:40:12 +0200496 if (new(chinfo[0], supervisor, desc) == E_ACCESS_DENIED)
MG Mud User88f12472016-06-24 23:31:02 +0200497 {
Arathorn78c08372019-12-11 20:14:23 +0100498 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
Zesstrae19391f2020-08-09 13:40:12 +0200499 dtime(time()), chinfo[0], supervisor));
MG Mud User88f12472016-06-24 23:31:02 +0200500 }
501 return;
502}
503
Arathorn78c08372019-12-11 20:14:23 +0100504private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200505{
Arathorn78c08372019-12-11 20:14:23 +0100506 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200507#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100508 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200509#else
Arathorn78c08372019-12-11 20:14:23 +0100510 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200511#endif
Arathorn19459eb2019-11-30 00:45:51 +0100512
Arathorn78c08372019-12-11 20:14:23 +0100513 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200514 return;
Arathorn19459eb2019-11-30 00:45:51 +0100515
Arathorn78c08372019-12-11 20:14:23 +0100516 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
517 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
518 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
519 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
520 foreach(string ch : old_explode(ch_list, "\n"))
521 {
522 if (ch[0]=='#')
523 continue;
524 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
525 }
MG Mud User88f12472016-06-24 23:31:02 +0200526}
527
Arathorn78c08372019-12-11 20:14:23 +0100528// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200529protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200530{
531 seteuid(getuid());
532 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100533
Zesstrab7720dc2020-08-11 22:14:18 +0200534 // Altes channelC aus Savefiles konvertieren...
535 if (widthof(channelC) == 1)
536 {
537 mapping new = m_allocate(sizeof(channelC), 2);
538 foreach(string chname, mixed arr: channelC)
539 {
540 struct channel_base_s ch = (<channel_base_s> name: arr[0],
541 desc: arr[1]);
542 // die anderen beiden Werte bleiben 0
543 m_add(new, chname, ch, arr[2]);
544 }
545 channelC = new;
546 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200547 //TODO: weitere Mappings im MEMORY speichern, Savefile ersetzen.
548
Arathorn19459eb2019-11-30 00:45:51 +0100549 /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
550 gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
MG Mud User88f12472016-06-24 23:31:02 +0200551 Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
552 das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
553 Memory abgelegt. */
554
555 // Hab ich die noetigen Rechte im Memory?
Arathorn19459eb2019-11-30 00:45:51 +0100556 if (call_other(MEMORY, "HaveRights"))
557 {
MG Mud User88f12472016-06-24 23:31:02 +0200558 // Objektpointer laden
Dominik Schaeferfa564d52020-08-05 20:50:27 +0200559 channelH = ({mapping}) call_other(MEMORY, "Load", "History");
MG Mud User88f12472016-06-24 23:31:02 +0200560
561 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
Arathorn78c08372019-12-11 20:14:23 +0100562 if (!mappingp(channelH))
563 {
MG Mud User88f12472016-06-24 23:31:02 +0200564 // Zeiger erzeugen
565 channelH = ([]);
566 // und in den Memory schreiben
Arathorn19459eb2019-11-30 00:45:51 +0100567 call_other(MEMORY, "Save", "History", channelH);
MG Mud User88f12472016-06-24 23:31:02 +0200568 }
Arathorn19459eb2019-11-30 00:45:51 +0100569 }
570 else
571 {
MG Mud User88f12472016-06-24 23:31:02 +0200572 // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
573 channelH = ([]);
574 }
575
576 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100577 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
578
579 // <MasteR>-Ebene erstellen. Channeld wird Ebenenbesitzer und somit auch
580 // Zuhoerer, damit er auf Kommandos auf dieser Ebene reagieren kann.
581 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
582
MG Mud User88f12472016-06-24 23:31:02 +0200583 initialize();
Arathorn78c08372019-12-11 20:14:23 +0100584 users()->RegisterChannels();
585
586 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
587 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
588 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
589 // explizites call_other() auf this_object() gemacht, damit der
590 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
591 // einem externen.
MG Mud User88f12472016-06-24 23:31:02 +0200592 this_object()->send(CMNAME, this_object(),
Arathorn19459eb2019-11-30 00:45:51 +0100593 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
594 sizeof(channels),
Arathorn78c08372019-12-11 20:14:23 +0100595 CountUsers()));
MG Mud User88f12472016-06-24 23:31:02 +0200596}
597
Arathorn78c08372019-12-11 20:14:23 +0100598varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200599{
Zesstra26aaf1a2020-08-07 19:10:39 +0200600 //TODO reset nur 1-2mal am Tag mit etwas random.
601
602 // Cache bereinigen entsprechend dessen Timeout-Zeit (12 h)
603 // TODO: Zeit auf 2-3 Tage erhoehen.
604 // TODO 2: Zeit dynamisch machen und nur expiren, wenn mehr als n Eintraege.
605 // Zeit reduzieren, bis nur noch n/2 Eintraege verbleiben.
Zesstra8f5102c2020-08-08 12:51:52 +0200606 channelC = filter(channelC,
Zesstrab7720dc2020-08-11 22:14:18 +0200607 function int (string ch_name, struct channel_base_s data, int ts)
Zesstra8f5102c2020-08-08 12:51:52 +0200608 {
Zesstrab7720dc2020-08-11 22:14:18 +0200609 if (ts + 43200 > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200610 return 1;
611 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
612 m_delete(channelH, ch_name);
613 return 0;
614 });
Arathorn19459eb2019-11-30 00:45:51 +0100615
MG Mud User88f12472016-06-24 23:31:02 +0200616 if (save_me_soon)
617 {
Arathorn19459eb2019-11-30 00:45:51 +0100618 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200619 save_object(CHANNEL_SAVE);
620 }
621}
622
623// name() - define the name of this object.
Arathorn19459eb2019-11-30 00:45:51 +0100624string name()
625{
626 return CMNAME;
627}
628
629string Name()
630{
631 return CMNAME;
632}
MG Mud User88f12472016-06-24 23:31:02 +0200633
Zesstra28986e12020-08-09 12:44:26 +0200634// Low-level function for adding members without access checks
Zesstrab7720dc2020-08-11 22:14:18 +0200635private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200636{
637 if (IsChannelMember(ch, m))
638 return E_ALREADY_JOINED;
639
Zesstrab7720dc2020-08-11 22:14:18 +0200640 ch.members += ({ m });
Zesstra28986e12020-08-09 12:44:26 +0200641 return 0;
642}
643
Zesstraf87cb772020-08-10 11:14:45 +0200644// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
645// History, so dass sie spaeter reaktiviert werden kann.
Zesstrab7720dc2020-08-11 22:14:18 +0200646private void deactivate_channel(string chname)
Zesstraf87cb772020-08-10 11:14:45 +0200647{
Zesstrab7720dc2020-08-11 22:14:18 +0200648 chname = lower_case(chname);
649 struct channel_s ch = channels[chname];
650 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200651 return;
Zesstrab7720dc2020-08-11 22:14:18 +0200652
653 // nur Ebenen ohne Zuhoerer deaktivieren.
654 if (sizeof(ch.members))
655 {
656 raise_error(
657 sprintf("[%s] Attempt to deactivate channel %s with listeners.\n",
658 dtime(), ch.name));
659 }
Zesstraf87cb772020-08-10 11:14:45 +0200660 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
661 // einloggt, der die Ebene abonniert hat.
Zesstrab7720dc2020-08-11 22:14:18 +0200662 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
663 time());
Zesstraf87cb772020-08-10 11:14:45 +0200664
Zesstrab7720dc2020-08-11 22:14:18 +0200665 // aktive Ebene loeschen bzw. deaktivieren.
666 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200667 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
668 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
669 // channelC bereinigt wird.
670
671 stats["dispose"]++;
672 save_me_soon = 1;
673}
674
675// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstrab7720dc2020-08-11 22:14:18 +0200676private void delete_channel(string chname)
Zesstraf87cb772020-08-10 11:14:45 +0200677{
Zesstrab7720dc2020-08-11 22:14:18 +0200678 chname = lower_case(chname);
679 struct channel_s ch = channels[chname];
680 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200681 {
682 // nur Ebenen ohne Zuhoerer loeschen. (Wenn der Aufrufer auch andere
683 // loeschen will, muss er vorher selber die Ebene leer raeumen, s.
684 // Kommandofunktion remove_channel().
Zesstrab7720dc2020-08-11 22:14:18 +0200685 if (sizeof(ch.members))
Zesstraf87cb772020-08-10 11:14:45 +0200686 raise_error(
687 sprintf("[%s] Attempt to delete channel %s with listeners.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200688 dtime(), ch.name));
Zesstraf87cb772020-08-10 11:14:45 +0200689 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200690 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200691 }
692 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200693 m_delete(channelsC, chname);
694 m_delete(channelsH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200695 save_me_soon = 1;
696}
697
Zesstra5b7f2fc2020-08-10 02:09:13 +0200698// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
699// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrab7720dc2020-08-11 22:14:18 +0200700private int change_sv_object(struct channel_s ch, object old_sv, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200701{
702 if (!new_sv)
703 {
Zesstrab7720dc2020-08-11 22:14:18 +0200704 ch.members -= ({0});
705 if (sizeof(ch.members))
706 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200707 else
708 return 0; // kein neuer SV moeglich.
709 }
Zesstrab7720dc2020-08-11 22:14:18 +0200710 ch.supervisor = new_sv;
711 //TODO angleichen an new() !
712 ch.access_cl = symbol_function("check_ch_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200713
Zesstra5b7f2fc2020-08-10 02:09:13 +0200714 if (old_sv && new_sv
715 && !old_sv->QueryProp(P_INVIS)
716 && !new_sv->QueryProp(P_INVIS))
717 {
718 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
719 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
720 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
721 // explizites call_other() auf this_object() gemacht, damit der
722 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
723 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200724 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200725 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
726 MSG_EMOTE);
727 }
728 else if (old_svn && !old_sv->QueryProp(P_INVIS))
729 {
Zesstrab7720dc2020-08-11 22:14:18 +0200730 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200731 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
732 }
733 else if (new_sv && !new_sv->QueryProp(P_INVIS))
734 {
Zesstrab7720dc2020-08-11 22:14:18 +0200735 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200736 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
737 }
738 return 1;
739}
740
Zesstra56692c72020-08-09 13:03:10 +0200741// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
742// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200743// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
744// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200745// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200746private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200747{
Zesstrab7720dc2020-08-11 22:14:18 +0200748 //Es ist keine Closure vorhanden, d.h. der Ebenenbesitzer wurde zerstoert.
Zesstra5770ba62020-08-10 10:19:23 +0200749 //TODO: es ist nicht so selten, dass die Closure 0 ist, d.h. der Code laeuft
750 //haeufig unnoetig!
Zesstrab7720dc2020-08-11 22:14:18 +0200751 if (!closurep(ch.access_cl))
MG Mud User88f12472016-06-24 23:31:02 +0200752 {
Arathorn78c08372019-12-11 20:14:23 +0100753 // Wenn der Ebenenbesitzer als String eingetragen ist, versuchen wir,
754 // die Closure wiederherzustellen. Dabei wird das Objekt gleichzeitig
Zesstrae19391f2020-08-09 13:40:12 +0200755 // neugeladen und eingetragen.
Zesstrab7720dc2020-08-11 22:14:18 +0200756 if (stringp(ch.supervisor))
MG Mud User88f12472016-06-24 23:31:02 +0200757 {
Arathorn78c08372019-12-11 20:14:23 +0100758 closure new_acc_cl;
Zesstrab7720dc2020-08-11 22:14:18 +0200759 // TODO: angleichen an new()!
Arathorn78c08372019-12-11 20:14:23 +0100760 string err = catch(new_acc_cl=
Zesstrab7720dc2020-08-11 22:14:18 +0200761 symbol_function("check_ch_access", ch->supervisor);
Arathorn78c08372019-12-11 20:14:23 +0100762 publish);
Zesstra56692c72020-08-09 13:03:10 +0200763 /* Wenn das SV-Objekt neu geladen werden konnte, wird es als Mitglied
764 * eingetragen. Auch die Closure wird neu eingetragen, allerdings kann
765 * sie 0 sein, wenn das SV-Objekt keine oeffentliche check_ch_access()
766 * mehr definiert. In diesem Fall gibt es zwar ein SV-Objekt, aber keine
767 * Zugriffrechte(pruefung) mehr. */
Arathorn78c08372019-12-11 20:14:23 +0100768 if (!err)
769 {
Zesstrab7720dc2020-08-11 22:14:18 +0200770 ch.access_cl = new_acc_cl;
Zesstra28986e12020-08-09 12:44:26 +0200771 // Der neue Ebenenbesitzer tritt auch gleich der Ebene bei. Hierbei
772 // erfolgt keine Pruefung des Zugriffsrechtes (ist ja unsinnig, weil
773 // er sich ja selber genehmigen koennte), und auch um eine Rekursion
774 // zu vermeiden.
Zesstrab7720dc2020-08-11 22:14:18 +0200775 add_member(ch, find_object(ch.supervisor));
Zesstra56692c72020-08-09 13:03:10 +0200776 // Rueckgabewert ist 1, ein neues SV-Objekt ist eingetragen.
Arathorn78c08372019-12-11 20:14:23 +0100777 }
778 else
779 {
Zesstra5770ba62020-08-10 10:19:23 +0200780 log_file("CHANNEL",
781 sprintf("[%s] Channel %s deleted. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200782 dtime(time()), ch.name, ch.supervisor, err));
Zesstraf87cb772020-08-10 11:14:45 +0200783 // Dies ist ein richtiges Loeschen, weil nicht-ladbare SV koennen bei
784 // Deaktivierung zu einer lesbaren History fuehren.
Zesstrab7720dc2020-08-11 22:14:18 +0200785 delete_channel(ch.name);
Arathorn78c08372019-12-11 20:14:23 +0100786 return 0;
787 }
MG Mud User88f12472016-06-24 23:31:02 +0200788 }
Zesstrab7720dc2020-08-11 22:14:18 +0200789 else if (!objectp(ch.supervisor))
Zesstra56692c72020-08-09 13:03:10 +0200790 {
791 // In diesem Fall muss ein neues SV-Objekt gesucht und ggf. eingetragen
Zesstra5770ba62020-08-10 10:19:23 +0200792 // werden. change_sv_object nimmt das aelteste Mitglied der Ebene.
793 if (!change_sv_object(ch, pl, 0))
794 {
795 // wenn das nicht klappt, Ebene aufloesen
796 log_file("CHANNEL",
Zesstraf87cb772020-08-10 11:14:45 +0200797 sprintf("[%s] Deactivating channel %s without SV.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200798 dtime(time()), ch.name));
799 deactivate_channel(ch.name);
Zesstra5770ba62020-08-10 10:19:23 +0200800 return 0;
801 }
Zesstra56692c72020-08-09 13:03:10 +0200802 }
MG Mud User88f12472016-06-24 23:31:02 +0200803 }
Zesstra78310012020-08-09 12:21:48 +0200804 return 1;
805}
806
807// access() - check access by looking for the right argument types and
808// calling access closures respectively
809// SEE: new, join, leave, send, list, users
810// Note: <pl> is usually an object, only the master supplies a string during
811// runtime error handling.
812// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
813// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
814// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
815// als Erfolg und alles ueber >2 als privilegiert.)
Zesstrab7720dc2020-08-11 22:14:18 +0200816varargs private int access(struct channel_s ch, object|string pl, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200817 string txt)
818{
Zesstrab7720dc2020-08-11 22:14:18 +0200819 if (!ch)
Zesstra78310012020-08-09 12:21:48 +0200820 return 0;
821
Zesstrafbfe6362020-08-09 13:30:21 +0200822 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
823 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra78310012020-08-09 12:21:48 +0200824 if ( !previous_object(1) || !extern_call() ||
825 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200826 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200827 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200828
Zesstra56692c72020-08-09 13:03:10 +0200829 // Nur dieses Objekt darf Meldungen im Namen anderer Objekte faken,
830 // ansonsten muss <pl> der Aufrufer sein.
Arathorn739a4fa2020-08-06 21:52:58 +0200831 if (!objectp(pl) ||
832 ((previous_object(1) != pl) && (previous_object(1) != this_object())))
833 return 0;
834
835 if (IsBanned(pl, cmd))
836 return 0;
837
Zesstra56692c72020-08-09 13:03:10 +0200838 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
839 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200840 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200841 return 0;
842 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200843 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200844 return 1;
845
Zesstra6fe46cd2020-08-09 13:12:15 +0200846 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
847 // fuer EM+ aber nur, wenn der CHANNELD selber das SV-Objekt ist, damit
848 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
849 // CHANNELD als SV koennen aber natuerlich auch EM+ Zugriff verweigern.
850 if (IS_ARCH(previous_object(1))
Zesstrab7720dc2020-08-11 22:14:18 +0200851 && find_object(ch.supervisor) != this_object())
Zesstra6fe46cd2020-08-09 13:12:15 +0200852 return 1;
853
Zesstrab7720dc2020-08-11 22:14:18 +0200854 return funcall(ch.access_cl, lower_case(ch.name), pl, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200855}
856
Arathorn78c08372019-12-11 20:14:23 +0100857// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200858// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100859// die dynamisch aktualisierte Infos ausgibt.
860// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
861// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
862// eingeht.
863// check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
864// Nachricht gesendet werden darf oder nicht.
MG Mud User88f12472016-06-24 23:31:02 +0200865#define IGNORE "^/xx"
866
Arathorn78c08372019-12-11 20:14:23 +0100867// TODO: KOMMENTAR
868//check may contain a closure
869// called when a join/leave/send/list/users message is received
Zesstra7da4d692020-08-10 11:17:54 +0200870public varargs int new(string ch_name, object owner, string|closure desc)
MG Mud User88f12472016-06-24 23:31:02 +0200871{
Arathorn78c08372019-12-11 20:14:23 +0100872 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
873 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
874 // fuer eine neue Ebene eintragen.)
875 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200876 return E_ACCESS_DENIED;
877
Arathorn78c08372019-12-11 20:14:23 +0100878 // Kein gescheiter Channelname angegeben.
879 if (!stringp(ch_name) || !sizeof(ch_name))
880 return E_ACCESS_DENIED;
881
882 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
883 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
884 return E_ACCESS_DENIED;
885
886 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
887 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
888 // seinen Objektnamen matcht.
889 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
890 return E_ACCESS_DENIED;
891
Zesstrab7720dc2020-08-11 22:14:18 +0200892 struct channel_s ch;
Zesstra7da4d692020-08-10 11:17:54 +0200893 // Keine Beschreibung mitgeliefert? Dann holen wir sie aus dem Cache.
894 if (!desc)
Arathorn19459eb2019-11-30 00:45:51 +0100895 {
Zesstrab7720dc2020-08-11 22:14:18 +0200896 struct channel_base_s cbase = channelC[lower_case(ch_name)];
897 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100898 {
Zesstrab7720dc2020-08-11 22:14:18 +0200899 ch = to_struct(cbase, (<channel_s>));
MG Mud User88f12472016-06-24 23:31:02 +0200900 }
Arathorn19459eb2019-11-30 00:45:51 +0100901 else
902 {
903 return E_ACCESS_DENIED;
904 }
MG Mud User88f12472016-06-24 23:31:02 +0200905 }
Arathorn19459eb2019-11-30 00:45:51 +0100906 else
907 {
Zesstrab7720dc2020-08-11 22:14:18 +0200908 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner)
909 );
Arathorn19459eb2019-11-30 00:45:51 +0100910 }
MG Mud User88f12472016-06-24 23:31:02 +0200911
Zesstrab7720dc2020-08-11 22:14:18 +0200912 ch_name = lower_case(ch_name);
913
914 ch.members = ({ owner });
915 ch.supervisor = (!living(owner) && !clonep(owner) && owner != this_object())
Arathorn78c08372019-12-11 20:14:23 +0100916 ? object_name(owner)
Zesstrab7720dc2020-08-11 22:14:18 +0200917 : owner;
918 //TODO: Ist das wirklich eine gute Idee, eine Access-Closure zu
919 //bauen, die *nicht* im Supervisor liegt? IMHO nein! Es ist ein
920 //merkwuerdiges Konzept, dass der channeld Rechte fuer ne Ebene
921 //pruefen soll, die nen anderes Objekt als Supervisor haben.
922 ch.access_cl = symbol_function("check_ch_access", owner) || #'check_ch_access;
923
924 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +0200925
Arathorn78c08372019-12-11 20:14:23 +0100926 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
927 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +0200928 if (!pointerp(channelH[ch_name]))
929 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200930
Arathorn78c08372019-12-11 20:14:23 +0100931 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
932 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +0200933 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200934 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +0100935
Arathorn78c08372019-12-11 20:14:23 +0100936 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
937 if (!owner->QueryProp(P_INVIS))
938 {
939 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
940 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
941 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
942 // explizites call_other() auf this_object() gemacht, damit der
943 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
944 // einem externen.
945 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +0200946 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +0100947 }
Arathorn19459eb2019-11-30 00:45:51 +0100948
MG Mud User88f12472016-06-24 23:31:02 +0200949 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100950 save_me_soon = 1;
951 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200952}
953
Arathorn78c08372019-12-11 20:14:23 +0100954// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
955// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
956// zweimal beitreten.)
Zesstrab7720dc2020-08-11 22:14:18 +0200957public int join(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200958{
Zesstrab7720dc2020-08-11 22:14:18 +0200959 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +0200960 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
961 zu erzeugen, weil access() mit extern_call() und previous_object()
962 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
963 richtige ist. */
964 if (!funcall(#'access, ch, pl, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +0100965 return E_ACCESS_DENIED;
966
Zesstra28986e12020-08-09 12:44:26 +0200967 return add_member(ch, pl);
MG Mud User88f12472016-06-24 23:31:02 +0200968}
969
Arathorn78c08372019-12-11 20:14:23 +0100970// Objekt <pl> verlaesst Ebene <ch>.
971// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
972// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
973// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
974// <pl> das Verlassen auf Grund eines Banns verboten sein.
975// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
976// noch ein Ebenenbesitzer eingetragen ist.
Zesstrab7720dc2020-08-11 22:14:18 +0200977public int leave(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200978{
Zesstrab7720dc2020-08-11 22:14:18 +0200979 struct channel_s ch = channels[lower_case(chname)];
Zesstra877cb0a2020-08-10 02:10:21 +0200980
Zesstrab7720dc2020-08-11 22:14:18 +0200981 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +0200982
983 if (!IsChannelMember(ch, pl))
984 return E_NOT_MEMBER;
985
Arathorn739a4fa2020-08-06 21:52:58 +0200986 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
987 zu erzeugen, weil access() mit extern_call() und previous_object()
988 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
989 richtige ist. */
990 if (!funcall(#'access, ch, pl, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +0100991 return E_ACCESS_DENIED;
992
Zesstrab7720dc2020-08-11 22:14:18 +0200993 // Dann mal den Zuhoerer raus.
994 ch.members -= ({pl});
Zesstrae6d33852020-08-09 14:37:53 +0200995
Zesstra5b7f2fc2020-08-10 02:09:13 +0200996 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
997 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +0200998 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +0200999 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001000 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1001 // diese verlassen hat. change_sv_object() waehlt per Default den
1002 // aeltesten Zuhoerer.
Zesstrab7720dc2020-08-11 22:14:18 +02001003 if (pl == ch.supervisor
1004 || object_name(pl) == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001005 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001006 change_sv_object(ch, pl, 0);
Arathorn78c08372019-12-11 20:14:23 +01001007 }
MG Mud User88f12472016-06-24 23:31:02 +02001008 }
Zesstra137ea1c2020-08-10 02:15:20 +02001009 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1010 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1011 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1012 else
MG Mud User88f12472016-06-24 23:31:02 +02001013 {
Arathorn78c08372019-12-11 20:14:23 +01001014 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001015 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1016 // wird diese zerstoert, mit Meldung.
Arathorn19459eb2019-11-30 00:45:51 +01001017 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001018 {
1019 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1020 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1021 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1022 // explizites call_other() auf this_object() gemacht, damit der
1023 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1024 // einem externen.
1025 this_object()->send(CMNAME, pl,
1026 "verlaesst als "+
1027 (pl->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001028 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001029 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1030 }
Zesstrab7720dc2020-08-11 22:14:18 +02001031 deactivate_channel(lower_case(ch.name));
MG Mud User88f12472016-06-24 23:31:02 +02001032 }
Arathorn19459eb2019-11-30 00:45:51 +01001033 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001034}
1035
Arathorn78c08372019-12-11 20:14:23 +01001036// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1037// sofern <pl> dort senden darf.
Zesstrab7720dc2020-08-11 22:14:18 +02001038public varargs int send(string chname, object pl, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001039{
Zesstrab7720dc2020-08-11 22:14:18 +02001040 chname = lower_case(chname);
1041 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001042 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1043 zu erzeugen, weil access() mit extern_call() und previous_object()
1044 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1045 richtige ist. */
1046 int a = funcall(#'access, ch, pl, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001047 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001048 return E_ACCESS_DENIED;
1049
Zesstra26aaf1a2020-08-07 19:10:39 +02001050 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
1051 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
1052 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
1053 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1054 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Arathorn78c08372019-12-11 20:14:23 +01001055 if (a < 2 && !IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +01001056 return E_NOT_MEMBER;
1057
1058 if (!msg || !stringp(msg) || !sizeof(msg))
1059 return E_EMPTY_MESSAGE;
1060
Arathorn78c08372019-12-11 20:14:23 +01001061 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1062 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1063 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1064 // Nachricht ebenfalls erhaelt.
1065 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1066 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1067 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1068 // erzeugt und der Channeld als Besitzer angegeben.
1069 // Die Aufrufkette ist dann wie folgt:
1070 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001071 (ch.members)->ChannelMessage(
1072 ({ ch.name, pl, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001073
Zesstrab7720dc2020-08-11 22:14:18 +02001074 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1075 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001076
Zesstrab7720dc2020-08-11 22:14:18 +02001077 channelH[chname] +=
1078 ({ ({ ch.name,
Arathorn19459eb2019-11-30 00:45:51 +01001079 (stringp(pl)
1080 ? pl
1081 : (pl->QueryProp(P_INVIS)
1082 ? "/(" + capitalize(getuid(pl)) + ")$"
1083 : "")
1084 + (pl->Name(WER, 2) || "<Unbekannt>")),
1085 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1086 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001087 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001088}
1089
Arathorn78c08372019-12-11 20:14:23 +01001090// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1091// oder einen Integer-Fehlercode
1092public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001093{
Arathorn78c08372019-12-11 20:14:23 +01001094 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001095 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001096 {
Arathorn739a4fa2020-08-06 21:52:58 +02001097 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1098 zu erzeugen, weil access() mit extern_call() und previous_object()
1099 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1100 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001101 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001102 {
Zesstrab7720dc2020-08-11 22:14:18 +02001103 ch.members = filter(ch.members, #'objectp);
1104 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1105 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001106 }
1107 }
Arathorn19459eb2019-11-30 00:45:51 +01001108
1109 if (!sizeof(chs))
1110 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001111 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001112}
1113
Arathorn78c08372019-12-11 20:14:23 +01001114// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1115// Rueckgabewerte:
1116// - den gefundenen Namen als String
1117// - String-Array, wenn es mehrere Treffer gibt
1118// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001119public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001120{
Zesstrab7720dc2020-08-11 22:14:18 +02001121 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001122
Arathorn78c08372019-12-11 20:14:23 +01001123 // Suchstring <ch> muss Formatanforderung erfuellen;
1124 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1125 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1126 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1127 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001128 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001129 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001130
Arathorn78c08372019-12-11 20:14:23 +01001131 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1132 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1133 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001134 string* chs = filter(m_indices(channels), function int (string ch_n) {
Arathorn739a4fa2020-08-06 21:52:58 +02001135 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1136 im Caller Stack zu erzeugen, weil access() mit
1137 extern_call() und previous_object() arbeitet und
1138 sichergestellt sein muss, dass das in jedem Fall das
1139 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001140 return ( stringp(regmatch(ch_n, "^"+chname)) &&
1141 funcall(#'access, channels[ch_n], pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001142 });
Arathorn19459eb2019-11-30 00:45:51 +01001143
Arathorn78c08372019-12-11 20:14:23 +01001144 int num_channels = sizeof(chs);
1145 if (num_channels > 1)
1146 return chs;
1147 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001148 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001149 else
1150 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001151}
1152
Arathorn78c08372019-12-11 20:14:23 +01001153// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001154public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001155{
Zesstrab7720dc2020-08-11 22:14:18 +02001156 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001157 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1158 zu erzeugen, weil access() mit extern_call() und previous_object()
1159 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1160 richtige ist. */
1161 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001162 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001163 else
Zesstrab7720dc2020-08-11 22:14:18 +02001164 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001165}
1166
Arathorn78c08372019-12-11 20:14:23 +01001167// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001168public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001169{
Zesstrab7720dc2020-08-11 22:14:18 +02001170 chname = lower_case(chname);
1171 struct channel_s ch = channels[chname];
1172
Zesstra26aaf1a2020-08-07 19:10:39 +02001173 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001174 if (previous_object() != this_object())
1175 {
Zesstrab7720dc2020-08-11 22:14:18 +02001176 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001177 pl != this_player() || this_player() != this_interactive() ||
1178 this_interactive() != previous_object() ||
1179 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001180 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001181 }
Zesstraf87cb772020-08-10 11:14:45 +02001182 // Wenn die Ebene aktiv ist (d.h. Zuhoerer hat), muessen die erst
1183 // runtergeworfen werden.
Zesstrab7720dc2020-08-11 22:14:18 +02001184 if (ch)
Arathorn19459eb2019-11-30 00:45:51 +01001185 {
Arathorn78c08372019-12-11 20:14:23 +01001186 // Einer geloeschten Ebene kann man nicht zuhoeren: Ebenenname aus der
1187 // Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-, als
1188 // auch temporaer ausgeschaltete Ebenen beruecksichtigt.
Zesstrab7720dc2020-08-11 22:14:18 +02001189 foreach(object listener : ch.members)
Arathorn78c08372019-12-11 20:14:23 +01001190 {
1191 string* pl_chans = listener->QueryProp(P_CHANNELS);
1192 if (pointerp(pl_chans))
Arathorn19459eb2019-11-30 00:45:51 +01001193 {
Zesstrab7720dc2020-08-11 22:14:18 +02001194 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
Arathorn78c08372019-12-11 20:14:23 +01001195 }
1196 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
1197 if (pointerp(pl_chans))
1198 {
Zesstrab7720dc2020-08-11 22:14:18 +02001199 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
Arathorn78c08372019-12-11 20:14:23 +01001200 }
1201 }
MG Mud User88f12472016-06-24 23:31:02 +02001202 }
Zesstraf87cb772020-08-10 11:14:45 +02001203 // Dies auserhalb des Blocks oben ermoeglicht es, inaktive Ebenen bzw.
Zesstra8f5102c2020-08-08 12:51:52 +02001204 // deren Daten zu entfernen.
Zesstrab7720dc2020-08-11 22:14:18 +02001205 delete_channel(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001206
Arathorn19459eb2019-11-30 00:45:51 +01001207 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001208}
1209
Arathorn78c08372019-12-11 20:14:23 +01001210// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001211public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001212{
Zesstra26aaf1a2020-08-07 19:10:39 +02001213 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001214 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001215 if (previous_object() != this_object())
1216 {
Zesstrab7720dc2020-08-11 22:14:18 +02001217 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001218 this_player() != this_interactive() ||
1219 this_interactive() != previous_object() ||
1220 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001221 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001222 }
Zesstrab7720dc2020-08-11 22:14:18 +02001223 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001224 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1225 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001226 if (pointerp(channelH[chname]))
1227 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001228
1229 return 0;
1230}