blob: 72fd23fff0802e075a3afe44462d5409ca34a0e6 [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
Zesstra0caa8e42020-08-11 22:51:59 +020030// Standard-Ebenen-Supervisor erben, Variablen nosave, die sollen hier nicht
31// gespeichert werden.
32nosave variables inherit "/std/channel_supervisor";
Arathorn78c08372019-12-11 20:14:23 +010033
Zesstrab7720dc2020-08-11 22:14:18 +020034// Datenstrukturen fuer die Ebenen.
35// Basisdaten, welche auch inaktive Ebenen in channelC haben
36struct channel_base_s {
37 string name; // readable channelname, case-sensitive
38 string|closure desc; // stat. oder dyn. Beschreibung
Zesstrad9ec04b2020-08-11 23:47:03 +020039 string creator; // Ersteller der Ebene (Objektname), Original-SV
40 int flags; // Flags, die bestimmtes Verhalten steuern.
Zesstrab7720dc2020-08-11 22:14:18 +020041};
42
43// Basisdaten + die von aktiven Ebenen
44struct channel_s (channel_base_s) {
45 object|string supervisor; // aktueller Supervisor der Ebene
46 closure access_cl; // Closure fuer Zugriffsrechtepruefung
47 object *members; // Zuhoerer der Ebene
48};
49
50/* Ebenenliste und die zugehoerigen Daten in struct (<channel>).
51 channels = ([string channelname : (<channel_s>) ])
Zesstra78310012020-08-09 12:21:48 +020052 */
Arathorn78c08372019-12-11 20:14:23 +010053private nosave mapping channels = ([]);
Arathorn19459eb2019-11-30 00:45:51 +010054
Arathorn78c08372019-12-11 20:14:23 +010055/* Ebenenhistory
56 mapping channelH = ([ string channelname : ({ ({string channelname,
57 string sender,
58 string msg,
59 int msg_type}) }) ]) */
60// channelH wird in create() geeignet initialisiert
61// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
62// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
63// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
64// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
65// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020066private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010067
Arathorn78c08372019-12-11 20:14:23 +010068/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
69 zerstoerte Ebenen.
70 mapping stats = ([ "time" : int object_time(),
71 "boot" : string getuid(previous_object()),
Arathorn19459eb2019-11-30 00:45:51 +010072 "new" : int total_channels_created,
73 "disposed" : int total_channels_removed ]) */
Arathorn78c08372019-12-11 20:14:23 +010074// stats wird in create() geeignet initialisiert
MG Mud User88f12472016-06-24 23:31:02 +020075private nosave mapping stats;
76
Arathorn78c08372019-12-11 20:14:23 +010077/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +020078 mapping channelC = ([ string channelname : (<channel_base_s>);
79 int time() ])
80 Der Zeitstempel ist die letzte Aenderung, d.h. in der Regel des Ablegens in
81 channelC.
82 */
83private mapping channelC = ([:2]);
Arathorn19459eb2019-11-30 00:45:51 +010084
Arathorn78c08372019-12-11 20:14:23 +010085/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
86 mapping channelB = ([ string playername : string* banned_command ]) */
87private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020088
Arathorn78c08372019-12-11 20:14:23 +010089/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
90 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
91 kann.
92
Arathorn19459eb2019-11-30 00:45:51 +010093 mapping Tcmd = ([ "lag": int timestamp,
94 "uptime": int timestamp,
95 "statistik": int timestamp]) */
96private mapping Tcmd = ([]);
97
Arathorn78c08372019-12-11 20:14:23 +010098/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
99 Speicherevent das Savefile geschrieben werden soll.
100 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +0200101private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +0200102
103// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
104
Arathorn78c08372019-12-11 20:14:23 +0100105/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
106// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
107private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200108{
Arathorn78c08372019-12-11 20:14:23 +0100109 object* userlist = ({});
Zesstrab7720dc2020-08-11 22:14:18 +0200110 foreach(string ch_name, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +0100111 {
Zesstrab7720dc2020-08-11 22:14:18 +0200112 userlist += ch.members;
Arathorn78c08372019-12-11 20:14:23 +0100113 }
114 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
115 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200116}
117
Arathorn78c08372019-12-11 20:14:23 +0100118// Ist das Objekt <sender> Abonnent der Ebene <ch>?
Zesstrab7720dc2020-08-11 22:14:18 +0200119private int IsChannelMember(struct channel_s ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200120{
Zesstrab7720dc2020-08-11 22:14:18 +0200121 return (member(ch.members, sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200122}
123
Arathorn78c08372019-12-11 20:14:23 +0100124// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
125private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200126{
Arathorn78c08372019-12-11 20:14:23 +0100127 if (objectp(ob))
128 ob = getuid(ob);
129 return(pointerp(channelB[ob]) &&
130 member(channelB[ob], command) != -1);
131}
MG Mud User88f12472016-06-24 23:31:02 +0200132
Arathorn78c08372019-12-11 20:14:23 +0100133private void banned(string plname, string* cmds, string res)
134{
135 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
136}
137
138#define TIMEOUT (time() - 60)
139
140// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
141// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
142// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
143// zurueckliegt, als "nicht gesperrt".
144private int IsNotBlocked(string* cmd)
145{
146 string* res = filter(cmd, function int (string str) {
147 return (Tcmd[str] < TIMEOUT);
148 });
149 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
150 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
151 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
152 if (sizeof(res) == sizeof(cmd)) {
153 foreach(string str : cmd) {
154 Tcmd[str] = time();
155 }
156 return 1;
157 }
158 return 0;
159}
160
161// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
162// zugelassen wird. Anforderungen:
163// 1) <cmd> muss Teilstring von <check> sein
164// 2) <cmd> muss am Anfang von <check> stehen
165// 3) <cmd> darf nicht laenger sein als <check>
166// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
167// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
168// cmd = "statistiker" oder cmd = "tist"
169// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
170private int IsValidChannelCommand(string cmd, string check) {
171 // Syntaxcheck (prueft Bedingungen 1 bis 3).
172 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
173 string* cmd_to_check;
174 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
175 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
176 // geben.
177 if ( cmd == "lust" )
178 cmd_to_check = ({"lag", "statistik", "uptime"});
179 else
180 cmd_to_check = ({cmd});
181 // Prueft die Zeitsperre (Bedingung 4).
182 return (IsNotBlocked(cmd_to_check));
183 }
184 return 0;
185}
186
187#define CH_NAME 0
188#define CH_SENDER 1
189#define CH_MSG 2
190#define CH_MSG_TYPE 3
191// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
192// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
193// und dieses per Callout an send() uebergeben.
Zesstrab7720dc2020-08-11 22:14:18 +0200194// Argument: ({channel.name, object pl, string msg, int type})
Arathorn78c08372019-12-11 20:14:23 +0100195// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
196// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
197// was aber bei einer private oder protected Funktion nicht moeglich waere.
198public void ChannelMessage(<string|object|int>* msg)
199{
200 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
201 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
202 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
203 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100204 return;
MG Mud User88f12472016-06-24 23:31:02 +0200205
Arathorn78c08372019-12-11 20:14:23 +0100206 float* lag;
207 int max, rekord;
208 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200209 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200210
Arathorn78c08372019-12-11 20:14:23 +0100211 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200212 {
Arathorn78c08372019-12-11 20:14:23 +0100213 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
214 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100215 }
Arathorn78c08372019-12-11 20:14:23 +0100216 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100217 {
MG Mud User88f12472016-06-24 23:31:02 +0200218 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
219 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100220 "%.1f%%/20s, %.1f%%/2s",
221 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100222 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
223 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100224 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200225 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100226 }
Arathorn78c08372019-12-11 20:14:23 +0100227 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200228 {
Arathorn78c08372019-12-11 20:14:23 +0100229 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100230 {
Arathorn78c08372019-12-11 20:14:23 +0100231 string unused;
232 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
233 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
234 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
235 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
236 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100237 }
238 else
239 {
Arathorn78c08372019-12-11 20:14:23 +0100240 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200241 }
Arathorn19459eb2019-11-30 00:45:51 +0100242 }
Arathorn78c08372019-12-11 20:14:23 +0100243 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200244 {
MG Mud User88f12472016-06-24 23:31:02 +0200245 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100246 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
247 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
248 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
249 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100250 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
251 }
Arathorn78c08372019-12-11 20:14:23 +0100252 // Ebenenaktion beginnt mit "bann"?
253 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200254 {
255 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100256
257 if (mesg == "bann")
258 {
259 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200260 {
Arathorn78c08372019-12-11 20:14:23 +0100261 ret = "Fuer folgende Spieler besteht ein Bann: ";
262 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
263 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
264 string* banlist = ({});
265 foreach(string plname, string* banned_cmds : channelB) {
266 banlist += ({ sprintf("%s [%s]",
267 capitalize(plname), implode(banned_cmds, ", "))});
268 }
269 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200270 }
271 else
272 {
Arathorn19459eb2019-11-30 00:45:51 +0100273 ret = "Zur Zeit ist kein Bann aktiv.";
274 }
275 }
276 else
277 {
Arathorn78c08372019-12-11 20:14:23 +0100278 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
279 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100280 {
281 pl = lower_case(pl);
282 cmd = lower_case(cmd);
283
284 if (member(CMDS, cmd) != -1)
285 {
Arathorn78c08372019-12-11 20:14:23 +0100286 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
287 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
288 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100289 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100290 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100291
Arathorn78c08372019-12-11 20:14:23 +0100292 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100293 channelB[pl] -= ({ cmd });
294 else
295 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100296
Arathorn78c08372019-12-11 20:14:23 +0100297 ret = "Fuer '" + capitalize(pl) + "' besteht " +
298 (sizeof(channelB[pl])
299 // TODO: implode() -> CountUp()?
300 ? "folgender Bann: " + implode(channelB[pl], ", ") + "."
301 : "kein Bann mehr.");
302
303 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100304 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100305 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100306
Zesstraa2db5522020-08-11 22:14:55 +0200307 //TODO: save_me_soon=1 sollte auch reichen...
Arathorn19459eb2019-11-30 00:45:51 +0100308 save_object(CHANNEL_SAVE);
309 }
310 else
311 {
312 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100313 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100314 }
315 }
316 else
317 {
Arathorn78c08372019-12-11 20:14:23 +0100318 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100319 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200320 }
321 }
322 }
Arathorn78c08372019-12-11 20:14:23 +0100323 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200324 {
MG Mud User88f12472016-06-24 23:31:02 +0200325 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100326 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
327 {
328 string unused;
329 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
330 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
331 }
MG Mud User88f12472016-06-24 23:31:02 +0200332
Arathorn78c08372019-12-11 20:14:23 +0100333 int t = time() - last_reboot_time();
334
335 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
336 // die Funktionalitaet oefter benoetigt wird als nur hier.
337 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100338 if (t >= 86400)
339 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200340
Arathorn78c08372019-12-11 20:14:23 +0100341 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100342 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100343 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100344
Arathorn78c08372019-12-11 20:14:23 +0100345 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100346 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100347 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100348
349 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100350
MG Mud User88f12472016-06-24 23:31:02 +0200351 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100352 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100353 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100354 }
355 else
356 {
357 return;
358 }
MG Mud User88f12472016-06-24 23:31:02 +0200359
Arathorn78c08372019-12-11 20:14:23 +0100360 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
361 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
362 // nicht erfuellt sind.
363 if (stringp(ret) && sizeof(ret))
364 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200365}
366
367// setup() -- set up a channel and register it
368// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100369// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrad9ec04b2020-08-11 23:47:03 +0200370// adminflags, channelflags, description,supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100371private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200372{
Arathorn78c08372019-12-11 20:14:23 +0100373 string desc = "- Keine Beschreibung -";
Zesstrae19391f2020-08-09 13:40:12 +0200374 object supervisor = this_object();
Zesstra0caa8e42020-08-11 22:51:59 +0200375 int sv_recv, sv_send, sv_flags; // an den Supervisor weiterreichen
Zesstrad9ec04b2020-08-11 23:47:03 +0200376 int chflags;
Arathorn19459eb2019-11-30 00:45:51 +0100377
Arathorn78c08372019-12-11 20:14:23 +0100378 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
379 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200380
Arathorn78c08372019-12-11 20:14:23 +0100381 switch (sizeof(chinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200382 {
Arathorn78c08372019-12-11 20:14:23 +0100383 // Alle Fallthroughs in dem switch() sind Absicht.
Zesstra0caa8e42020-08-11 22:51:59 +0200384 default:
Zesstrad9ec04b2020-08-11 23:47:03 +0200385 if (stringp(chinfo[6]) && sizeof(chinfo[6]))
386 catch(supervisor = load_object(chinfo[6]); publish);
Zesstrae19391f2020-08-09 13:40:12 +0200387 if (!objectp(supervisor))
388 supervisor = this_object();
Zesstrad9ec04b2020-08-11 23:47:03 +0200389 case 6:
390 if (stringp(chinfo[5]))
391 desc = chinfo[5];
Arathorn19459eb2019-11-30 00:45:51 +0100392 case 5:
Zesstrad9ec04b2020-08-11 23:47:03 +0200393 chflags = to_int(chinfo[4]);
Arathorn19459eb2019-11-30 00:45:51 +0100394 case 4:
Zesstra0caa8e42020-08-11 22:51:59 +0200395 sv_flags = to_int(chinfo[3]);
Arathorn19459eb2019-11-30 00:45:51 +0100396 case 3:
Zesstra0caa8e42020-08-11 22:51:59 +0200397 sv_send = to_int(chinfo[2]);
Arathorn19459eb2019-11-30 00:45:51 +0100398 case 2:
Zesstra0caa8e42020-08-11 22:51:59 +0200399 sv_recv = to_int(chinfo[1]);
Arathorn19459eb2019-11-30 00:45:51 +0100400 break;
401
402 case 0:
Zesstra0caa8e42020-08-11 22:51:59 +0200403 case 1:
Arathorn19459eb2019-11-30 00:45:51 +0100404 return;
MG Mud User88f12472016-06-24 23:31:02 +0200405 }
Zesstra0caa8e42020-08-11 22:51:59 +0200406 // Zugriffsrechte im channel_supervisor konfigurieren. (Kann auch dieses
407 // Objekt selber sein...)
408 supervisor->ch_supervisor_setup(lower_case(chinfo[0]), sv_recv,
409 sv_send, sv_flags);
Arathorn19459eb2019-11-30 00:45:51 +0100410
Zesstrad9ec04b2020-08-11 23:47:03 +0200411 if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
MG Mud User88f12472016-06-24 23:31:02 +0200412 {
Arathorn78c08372019-12-11 20:14:23 +0100413 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
Zesstrae19391f2020-08-09 13:40:12 +0200414 dtime(time()), chinfo[0], supervisor));
MG Mud User88f12472016-06-24 23:31:02 +0200415 }
416 return;
417}
418
Arathorn78c08372019-12-11 20:14:23 +0100419private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200420{
Arathorn78c08372019-12-11 20:14:23 +0100421 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200422#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100423 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200424#else
Arathorn78c08372019-12-11 20:14:23 +0100425 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200426#endif
Arathorn19459eb2019-11-30 00:45:51 +0100427
Arathorn78c08372019-12-11 20:14:23 +0100428 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200429 return;
Arathorn19459eb2019-11-30 00:45:51 +0100430
Arathorn78c08372019-12-11 20:14:23 +0100431 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
432 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
433 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
434 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
435 foreach(string ch : old_explode(ch_list, "\n"))
436 {
437 if (ch[0]=='#')
438 continue;
439 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
440 }
MG Mud User88f12472016-06-24 23:31:02 +0200441}
442
Arathorn78c08372019-12-11 20:14:23 +0100443// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200444protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200445{
446 seteuid(getuid());
447 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100448
Zesstrab7720dc2020-08-11 22:14:18 +0200449 // Altes channelC aus Savefiles konvertieren...
450 if (widthof(channelC) == 1)
451 {
452 mapping new = m_allocate(sizeof(channelC), 2);
453 foreach(string chname, mixed arr: channelC)
454 {
455 struct channel_base_s ch = (<channel_base_s> name: arr[0],
456 desc: arr[1]);
457 // die anderen beiden Werte bleiben 0
458 m_add(new, chname, ch, arr[2]);
459 }
460 channelC = new;
461 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200462 //TODO: weitere Mappings im MEMORY speichern, Savefile ersetzen.
463
Arathorn19459eb2019-11-30 00:45:51 +0100464 /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
465 gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
MG Mud User88f12472016-06-24 23:31:02 +0200466 Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
467 das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
468 Memory abgelegt. */
469
470 // Hab ich die noetigen Rechte im Memory?
Arathorn19459eb2019-11-30 00:45:51 +0100471 if (call_other(MEMORY, "HaveRights"))
472 {
MG Mud User88f12472016-06-24 23:31:02 +0200473 // Objektpointer laden
Dominik Schaeferfa564d52020-08-05 20:50:27 +0200474 channelH = ({mapping}) call_other(MEMORY, "Load", "History");
MG Mud User88f12472016-06-24 23:31:02 +0200475
476 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
Arathorn78c08372019-12-11 20:14:23 +0100477 if (!mappingp(channelH))
478 {
MG Mud User88f12472016-06-24 23:31:02 +0200479 // Zeiger erzeugen
480 channelH = ([]);
481 // und in den Memory schreiben
Arathorn19459eb2019-11-30 00:45:51 +0100482 call_other(MEMORY, "Save", "History", channelH);
MG Mud User88f12472016-06-24 23:31:02 +0200483 }
Arathorn19459eb2019-11-30 00:45:51 +0100484 }
485 else
486 {
MG Mud User88f12472016-06-24 23:31:02 +0200487 // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
488 channelH = ([]);
489 }
490
491 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100492 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
493
494 // <MasteR>-Ebene erstellen. Channeld wird Ebenenbesitzer und somit auch
495 // Zuhoerer, damit er auf Kommandos auf dieser Ebene reagieren kann.
496 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
497
MG Mud User88f12472016-06-24 23:31:02 +0200498 initialize();
Arathorn78c08372019-12-11 20:14:23 +0100499 users()->RegisterChannels();
500
501 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
502 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
503 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
504 // explizites call_other() auf this_object() gemacht, damit der
505 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
506 // einem externen.
MG Mud User88f12472016-06-24 23:31:02 +0200507 this_object()->send(CMNAME, this_object(),
Arathorn19459eb2019-11-30 00:45:51 +0100508 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
509 sizeof(channels),
Arathorn78c08372019-12-11 20:14:23 +0100510 CountUsers()));
MG Mud User88f12472016-06-24 23:31:02 +0200511}
512
Arathorn78c08372019-12-11 20:14:23 +0100513varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200514{
Zesstra26aaf1a2020-08-07 19:10:39 +0200515 //TODO reset nur 1-2mal am Tag mit etwas random.
516
517 // Cache bereinigen entsprechend dessen Timeout-Zeit (12 h)
518 // TODO: Zeit auf 2-3 Tage erhoehen.
519 // TODO 2: Zeit dynamisch machen und nur expiren, wenn mehr als n Eintraege.
520 // Zeit reduzieren, bis nur noch n/2 Eintraege verbleiben.
Zesstra8f5102c2020-08-08 12:51:52 +0200521 channelC = filter(channelC,
Zesstrab7720dc2020-08-11 22:14:18 +0200522 function int (string ch_name, struct channel_base_s data, int ts)
Zesstra8f5102c2020-08-08 12:51:52 +0200523 {
Zesstrab7720dc2020-08-11 22:14:18 +0200524 if (ts + 43200 > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200525 return 1;
526 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
527 m_delete(channelH, ch_name);
528 return 0;
529 });
Arathorn19459eb2019-11-30 00:45:51 +0100530
MG Mud User88f12472016-06-24 23:31:02 +0200531 if (save_me_soon)
532 {
Arathorn19459eb2019-11-30 00:45:51 +0100533 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200534 save_object(CHANNEL_SAVE);
535 }
536}
537
538// name() - define the name of this object.
Arathorn19459eb2019-11-30 00:45:51 +0100539string name()
540{
541 return CMNAME;
542}
543
544string Name()
545{
546 return CMNAME;
547}
MG Mud User88f12472016-06-24 23:31:02 +0200548
Zesstra28986e12020-08-09 12:44:26 +0200549// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200550// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200551private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200552{
553 if (IsChannelMember(ch, m))
554 return E_ALREADY_JOINED;
555
Zesstrab7720dc2020-08-11 22:14:18 +0200556 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200557 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200558}
559
Zesstra52d5f8a2020-08-12 00:39:15 +0200560private void remove_all_members(struct channel_s ch)
561{
562 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
563 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
564 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
565 string chname = lower_case(ch.name);
566 foreach(object listener : ch.members)
567 {
568 string* pl_chans = listener->QueryProp(P_CHANNELS);
569 if (pointerp(pl_chans))
570 {
571 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
572 }
573 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
574 if (pointerp(pl_chans))
575 {
576 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
577 }
578 }
579}
580
Zesstraf87cb772020-08-10 11:14:45 +0200581// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
582// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200583// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
584// anwesend sind.
585private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200586{
Zesstrab7720dc2020-08-11 22:14:18 +0200587 chname = lower_case(chname);
588 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200589 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200590 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200591 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200592 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200593 if (sizeof(ch.members))
594 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200595 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
596 if (!force)
597 {
598 raise_error(
599 sprintf("Attempt to deactivate channel %s with listeners.\n",
600 ch.name));
601 }
602 else
603 {
604 remove_all_members(ch);
605 }
Zesstrab7720dc2020-08-11 22:14:18 +0200606 }
Zesstraf87cb772020-08-10 11:14:45 +0200607 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
608 // einloggt, der die Ebene abonniert hat.
Zesstrab7720dc2020-08-11 22:14:18 +0200609 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
610 time());
Zesstraf87cb772020-08-10 11:14:45 +0200611
Zesstrab7720dc2020-08-11 22:14:18 +0200612 // aktive Ebene loeschen bzw. deaktivieren.
613 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200614 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
615 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
616 // channelC bereinigt wird.
617
618 stats["dispose"]++;
619 save_me_soon = 1;
620}
621
622// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200623// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
624// anwesend sind.
625private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200626{
Zesstrab7720dc2020-08-11 22:14:18 +0200627 chname = lower_case(chname);
628 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200629 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200630 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200631 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200632 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200633 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200634 {
635 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
636 if (!force)
637 {
638 raise_error(
639 sprintf("Attempt to delete channel %s with listeners.\n",
640 ch.name));
641 }
642 else
643 {
644 remove_all_members(ch);
645 }
646 }
Zesstraf87cb772020-08-10 11:14:45 +0200647 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200648 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200649 }
650 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200651 m_delete(channelsC, chname);
652 m_delete(channelsH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200653 save_me_soon = 1;
654}
655
Zesstra5b7f2fc2020-08-10 02:09:13 +0200656// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
657// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200658private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200659{
660 if (!new_sv)
661 {
Zesstrab7720dc2020-08-11 22:14:18 +0200662 ch.members -= ({0});
663 if (sizeof(ch.members))
664 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200665 else
666 return 0; // kein neuer SV moeglich.
667 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200668 object old_sv = ch.supervisor;
669
Zesstrab7720dc2020-08-11 22:14:18 +0200670 ch.supervisor = new_sv;
671 //TODO angleichen an new() !
672 ch.access_cl = symbol_function("check_ch_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200673
Zesstra5b7f2fc2020-08-10 02:09:13 +0200674 if (old_sv && new_sv
675 && !old_sv->QueryProp(P_INVIS)
676 && !new_sv->QueryProp(P_INVIS))
677 {
678 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
679 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
680 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
681 // explizites call_other() auf this_object() gemacht, damit der
682 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
683 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200684 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200685 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
686 MSG_EMOTE);
687 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200688 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200689 {
Zesstrab7720dc2020-08-11 22:14:18 +0200690 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200691 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
692 }
693 else if (new_sv && !new_sv->QueryProp(P_INVIS))
694 {
Zesstrab7720dc2020-08-11 22:14:18 +0200695 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200696 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
697 }
698 return 1;
699}
700
Zesstra56692c72020-08-09 13:03:10 +0200701// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
702// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200703// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
704// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200705// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200706private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200707{
Zesstrab7720dc2020-08-11 22:14:18 +0200708 //Es ist keine Closure vorhanden, d.h. der Ebenenbesitzer wurde zerstoert.
Zesstra5770ba62020-08-10 10:19:23 +0200709 //TODO: es ist nicht so selten, dass die Closure 0 ist, d.h. der Code laeuft
710 //haeufig unnoetig!
Zesstrab7720dc2020-08-11 22:14:18 +0200711 if (!closurep(ch.access_cl))
MG Mud User88f12472016-06-24 23:31:02 +0200712 {
Arathorn78c08372019-12-11 20:14:23 +0100713 // Wenn der Ebenenbesitzer als String eingetragen ist, versuchen wir,
714 // die Closure wiederherzustellen. Dabei wird das Objekt gleichzeitig
Zesstrae19391f2020-08-09 13:40:12 +0200715 // neugeladen und eingetragen.
Zesstrab7720dc2020-08-11 22:14:18 +0200716 if (stringp(ch.supervisor))
MG Mud User88f12472016-06-24 23:31:02 +0200717 {
Arathorn78c08372019-12-11 20:14:23 +0100718 closure new_acc_cl;
Zesstrab7720dc2020-08-11 22:14:18 +0200719 // TODO: angleichen an new()!
Arathorn78c08372019-12-11 20:14:23 +0100720 string err = catch(new_acc_cl=
Zesstrab7720dc2020-08-11 22:14:18 +0200721 symbol_function("check_ch_access", ch->supervisor);
Arathorn78c08372019-12-11 20:14:23 +0100722 publish);
Zesstra56692c72020-08-09 13:03:10 +0200723 /* Wenn das SV-Objekt neu geladen werden konnte, wird es als Mitglied
724 * eingetragen. Auch die Closure wird neu eingetragen, allerdings kann
725 * sie 0 sein, wenn das SV-Objekt keine oeffentliche check_ch_access()
726 * mehr definiert. In diesem Fall gibt es zwar ein SV-Objekt, aber keine
727 * Zugriffrechte(pruefung) mehr. */
Arathorn78c08372019-12-11 20:14:23 +0100728 if (!err)
729 {
Zesstrab7720dc2020-08-11 22:14:18 +0200730 ch.access_cl = new_acc_cl;
Zesstra28986e12020-08-09 12:44:26 +0200731 // Der neue Ebenenbesitzer tritt auch gleich der Ebene bei. Hierbei
732 // erfolgt keine Pruefung des Zugriffsrechtes (ist ja unsinnig, weil
733 // er sich ja selber genehmigen koennte), und auch um eine Rekursion
734 // zu vermeiden.
Zesstrab7720dc2020-08-11 22:14:18 +0200735 add_member(ch, find_object(ch.supervisor));
Zesstra56692c72020-08-09 13:03:10 +0200736 // Rueckgabewert ist 1, ein neues SV-Objekt ist eingetragen.
Arathorn78c08372019-12-11 20:14:23 +0100737 }
738 else
739 {
Zesstra5770ba62020-08-10 10:19:23 +0200740 log_file("CHANNEL",
741 sprintf("[%s] Channel %s deleted. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200742 dtime(time()), ch.name, ch.supervisor, err));
Zesstraf87cb772020-08-10 11:14:45 +0200743 // Dies ist ein richtiges Loeschen, weil nicht-ladbare SV koennen bei
744 // Deaktivierung zu einer lesbaren History fuehren.
Zesstrab7720dc2020-08-11 22:14:18 +0200745 delete_channel(ch.name);
Arathorn78c08372019-12-11 20:14:23 +0100746 return 0;
747 }
MG Mud User88f12472016-06-24 23:31:02 +0200748 }
Zesstrab7720dc2020-08-11 22:14:18 +0200749 else if (!objectp(ch.supervisor))
Zesstra56692c72020-08-09 13:03:10 +0200750 {
751 // In diesem Fall muss ein neues SV-Objekt gesucht und ggf. eingetragen
Zesstra5770ba62020-08-10 10:19:23 +0200752 // werden. change_sv_object nimmt das aelteste Mitglied der Ebene.
Zesstrabf4f86d2020-08-12 00:56:17 +0200753 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200754 {
755 // wenn das nicht klappt, Ebene aufloesen
756 log_file("CHANNEL",
Zesstraf87cb772020-08-10 11:14:45 +0200757 sprintf("[%s] Deactivating channel %s without SV.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200758 dtime(time()), ch.name));
759 deactivate_channel(ch.name);
Zesstra5770ba62020-08-10 10:19:23 +0200760 return 0;
761 }
Zesstra56692c72020-08-09 13:03:10 +0200762 }
MG Mud User88f12472016-06-24 23:31:02 +0200763 }
Zesstra78310012020-08-09 12:21:48 +0200764 return 1;
765}
766
767// access() - check access by looking for the right argument types and
768// calling access closures respectively
769// SEE: new, join, leave, send, list, users
770// Note: <pl> is usually an object, only the master supplies a string during
771// runtime error handling.
772// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
773// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
774// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
775// als Erfolg und alles ueber >2 als privilegiert.)
Zesstrab7720dc2020-08-11 22:14:18 +0200776varargs private int access(struct channel_s ch, object|string pl, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200777 string txt)
778{
Zesstrab7720dc2020-08-11 22:14:18 +0200779 if (!ch)
Zesstra78310012020-08-09 12:21:48 +0200780 return 0;
781
Zesstrafbfe6362020-08-09 13:30:21 +0200782 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
783 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra78310012020-08-09 12:21:48 +0200784 if ( !previous_object(1) || !extern_call() ||
785 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200786 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200787 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200788
Zesstra56692c72020-08-09 13:03:10 +0200789 // Nur dieses Objekt darf Meldungen im Namen anderer Objekte faken,
790 // ansonsten muss <pl> der Aufrufer sein.
Arathorn739a4fa2020-08-06 21:52:58 +0200791 if (!objectp(pl) ||
792 ((previous_object(1) != pl) && (previous_object(1) != this_object())))
793 return 0;
794
795 if (IsBanned(pl, cmd))
796 return 0;
797
Zesstra56692c72020-08-09 13:03:10 +0200798 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
799 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200800 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200801 return 0;
802 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200803 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200804 return 1;
805
Zesstra6fe46cd2020-08-09 13:12:15 +0200806 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
807 // fuer EM+ aber nur, wenn der CHANNELD selber das SV-Objekt ist, damit
808 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
809 // CHANNELD als SV koennen aber natuerlich auch EM+ Zugriff verweigern.
810 if (IS_ARCH(previous_object(1))
Zesstrab7720dc2020-08-11 22:14:18 +0200811 && find_object(ch.supervisor) != this_object())
Zesstra6fe46cd2020-08-09 13:12:15 +0200812 return 1;
813
Zesstrab7720dc2020-08-11 22:14:18 +0200814 return funcall(ch.access_cl, lower_case(ch.name), pl, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200815}
816
Arathorn78c08372019-12-11 20:14:23 +0100817// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200818// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100819// die dynamisch aktualisierte Infos ausgibt.
820// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
821// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
822// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200823#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200824public varargs int new(string ch_name, object owner, string|closure desc,
825 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200826{
Arathorn78c08372019-12-11 20:14:23 +0100827 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
828 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
829 // fuer eine neue Ebene eintragen.)
830 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200831 return E_ACCESS_DENIED;
832
Arathorn78c08372019-12-11 20:14:23 +0100833 // Kein gescheiter Channelname angegeben.
834 if (!stringp(ch_name) || !sizeof(ch_name))
835 return E_ACCESS_DENIED;
836
837 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
838 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
839 return E_ACCESS_DENIED;
840
841 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
842 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
843 // seinen Objektnamen matcht.
844 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
845 return E_ACCESS_DENIED;
846
Zesstrab7720dc2020-08-11 22:14:18 +0200847 struct channel_s ch;
Zesstra7da4d692020-08-10 11:17:54 +0200848 // Keine Beschreibung mitgeliefert? Dann holen wir sie aus dem Cache.
849 if (!desc)
Arathorn19459eb2019-11-30 00:45:51 +0100850 {
Zesstrab7720dc2020-08-11 22:14:18 +0200851 struct channel_base_s cbase = channelC[lower_case(ch_name)];
852 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100853 {
Zesstrab7720dc2020-08-11 22:14:18 +0200854 ch = to_struct(cbase, (<channel_s>));
MG Mud User88f12472016-06-24 23:31:02 +0200855 }
Arathorn19459eb2019-11-30 00:45:51 +0100856 else
857 {
858 return E_ACCESS_DENIED;
859 }
MG Mud User88f12472016-06-24 23:31:02 +0200860 }
Arathorn19459eb2019-11-30 00:45:51 +0100861 else
862 {
Zesstrad9ec04b2020-08-11 23:47:03 +0200863 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
864 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +0100865 }
MG Mud User88f12472016-06-24 23:31:02 +0200866
Zesstrab7720dc2020-08-11 22:14:18 +0200867 ch_name = lower_case(ch_name);
868
869 ch.members = ({ owner });
870 ch.supervisor = (!living(owner) && !clonep(owner) && owner != this_object())
Arathorn78c08372019-12-11 20:14:23 +0100871 ? object_name(owner)
Zesstrab7720dc2020-08-11 22:14:18 +0200872 : owner;
873 //TODO: Ist das wirklich eine gute Idee, eine Access-Closure zu
874 //bauen, die *nicht* im Supervisor liegt? IMHO nein! Es ist ein
875 //merkwuerdiges Konzept, dass der channeld Rechte fuer ne Ebene
876 //pruefen soll, die nen anderes Objekt als Supervisor haben.
Zesstrad9ec04b2020-08-11 23:47:03 +0200877 // check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
878 // Nachricht gesendet werden darf oder nicht.
Zesstrab7720dc2020-08-11 22:14:18 +0200879 ch.access_cl = symbol_function("check_ch_access", owner) || #'check_ch_access;
880
881 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +0200882
Arathorn78c08372019-12-11 20:14:23 +0100883 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
884 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +0200885 if (!pointerp(channelH[ch_name]))
886 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200887
Arathorn78c08372019-12-11 20:14:23 +0100888 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
889 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +0200890 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200891 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +0100892
Arathorn78c08372019-12-11 20:14:23 +0100893 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
894 if (!owner->QueryProp(P_INVIS))
895 {
896 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
897 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
898 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
899 // explizites call_other() auf this_object() gemacht, damit der
900 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
901 // einem externen.
902 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +0200903 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +0100904 }
Arathorn19459eb2019-11-30 00:45:51 +0100905
MG Mud User88f12472016-06-24 23:31:02 +0200906 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100907 save_me_soon = 1;
908 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200909}
910
Arathorn78c08372019-12-11 20:14:23 +0100911// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
912// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
913// zweimal beitreten.)
Zesstrab7720dc2020-08-11 22:14:18 +0200914public int join(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200915{
Zesstrab7720dc2020-08-11 22:14:18 +0200916 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +0200917 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
918 zu erzeugen, weil access() mit extern_call() und previous_object()
919 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
920 richtige ist. */
921 if (!funcall(#'access, ch, pl, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +0100922 return E_ACCESS_DENIED;
923
Zesstrafb350dc2020-08-12 00:49:31 +0200924 int res = add_member(ch, pl);
925 if (res != 1)
926 return res;
927
928 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200929}
930
Arathorn78c08372019-12-11 20:14:23 +0100931// Objekt <pl> verlaesst Ebene <ch>.
932// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
933// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
934// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
935// <pl> das Verlassen auf Grund eines Banns verboten sein.
936// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
937// noch ein Ebenenbesitzer eingetragen ist.
Zesstrab7720dc2020-08-11 22:14:18 +0200938public int leave(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200939{
Zesstrab7720dc2020-08-11 22:14:18 +0200940 struct channel_s ch = channels[lower_case(chname)];
Zesstra877cb0a2020-08-10 02:10:21 +0200941
Zesstrab7720dc2020-08-11 22:14:18 +0200942 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +0200943
944 if (!IsChannelMember(ch, pl))
945 return E_NOT_MEMBER;
946
Arathorn739a4fa2020-08-06 21:52:58 +0200947 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
948 zu erzeugen, weil access() mit extern_call() und previous_object()
949 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
950 richtige ist. */
951 if (!funcall(#'access, ch, pl, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +0100952 return E_ACCESS_DENIED;
953
Zesstrab7720dc2020-08-11 22:14:18 +0200954 // Dann mal den Zuhoerer raus.
955 ch.members -= ({pl});
Zesstrae6d33852020-08-09 14:37:53 +0200956
Zesstra5b7f2fc2020-08-10 02:09:13 +0200957 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
958 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +0200959 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +0200960 {
Zesstra5b7f2fc2020-08-10 02:09:13 +0200961 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
962 // diese verlassen hat. change_sv_object() waehlt per Default den
963 // aeltesten Zuhoerer.
Zesstrab7720dc2020-08-11 22:14:18 +0200964 if (pl == ch.supervisor
965 || object_name(pl) == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +0100966 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200967 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +0100968 }
MG Mud User88f12472016-06-24 23:31:02 +0200969 }
Zesstra137ea1c2020-08-10 02:15:20 +0200970 // ansonsten Ebene loeschen, wenn keiner zuhoert.
971 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
972 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
973 else
MG Mud User88f12472016-06-24 23:31:02 +0200974 {
Arathorn78c08372019-12-11 20:14:23 +0100975 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +0200976 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
977 // wird diese zerstoert, mit Meldung.
Arathorn19459eb2019-11-30 00:45:51 +0100978 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +0100979 {
980 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
981 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
982 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
983 // explizites call_other() auf this_object() gemacht, damit der
984 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
985 // einem externen.
986 this_object()->send(CMNAME, pl,
987 "verlaesst als "+
988 (pl->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +0200989 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +0100990 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
991 }
Zesstra52d5f8a2020-08-12 00:39:15 +0200992 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +0200993 }
Arathorn19459eb2019-11-30 00:45:51 +0100994 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200995}
996
Arathorn78c08372019-12-11 20:14:23 +0100997// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
998// sofern <pl> dort senden darf.
Zesstrab7720dc2020-08-11 22:14:18 +0200999public varargs int send(string chname, object pl, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001000{
Zesstrab7720dc2020-08-11 22:14:18 +02001001 chname = lower_case(chname);
1002 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001003 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1004 zu erzeugen, weil access() mit extern_call() und previous_object()
1005 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1006 richtige ist. */
1007 int a = funcall(#'access, ch, pl, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001008 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001009 return E_ACCESS_DENIED;
1010
Zesstra26aaf1a2020-08-07 19:10:39 +02001011 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
1012 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
1013 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
1014 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1015 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Arathorn78c08372019-12-11 20:14:23 +01001016 if (a < 2 && !IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +01001017 return E_NOT_MEMBER;
1018
1019 if (!msg || !stringp(msg) || !sizeof(msg))
1020 return E_EMPTY_MESSAGE;
1021
Arathorn78c08372019-12-11 20:14:23 +01001022 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1023 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1024 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1025 // Nachricht ebenfalls erhaelt.
1026 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1027 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1028 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1029 // erzeugt und der Channeld als Besitzer angegeben.
1030 // Die Aufrufkette ist dann wie folgt:
1031 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001032 (ch.members)->ChannelMessage(
1033 ({ ch.name, pl, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001034
Zesstrab7720dc2020-08-11 22:14:18 +02001035 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1036 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001037
Zesstrab7720dc2020-08-11 22:14:18 +02001038 channelH[chname] +=
1039 ({ ({ ch.name,
Arathorn19459eb2019-11-30 00:45:51 +01001040 (stringp(pl)
1041 ? pl
1042 : (pl->QueryProp(P_INVIS)
1043 ? "/(" + capitalize(getuid(pl)) + ")$"
1044 : "")
1045 + (pl->Name(WER, 2) || "<Unbekannt>")),
1046 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1047 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001048 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001049}
1050
Arathorn78c08372019-12-11 20:14:23 +01001051// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1052// oder einen Integer-Fehlercode
1053public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001054{
Arathorn78c08372019-12-11 20:14:23 +01001055 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001056 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001057 {
Arathorn739a4fa2020-08-06 21:52:58 +02001058 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1059 zu erzeugen, weil access() mit extern_call() und previous_object()
1060 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1061 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001062 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001063 {
Zesstrab7720dc2020-08-11 22:14:18 +02001064 ch.members = filter(ch.members, #'objectp);
1065 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1066 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001067 }
1068 }
Arathorn19459eb2019-11-30 00:45:51 +01001069
1070 if (!sizeof(chs))
1071 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001072 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001073}
1074
Arathorn78c08372019-12-11 20:14:23 +01001075// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1076// Rueckgabewerte:
1077// - den gefundenen Namen als String
1078// - String-Array, wenn es mehrere Treffer gibt
1079// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001080public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001081{
Zesstrab7720dc2020-08-11 22:14:18 +02001082 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001083
Arathorn78c08372019-12-11 20:14:23 +01001084 // Suchstring <ch> muss Formatanforderung erfuellen;
1085 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1086 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1087 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1088 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001089 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001090 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001091
Arathorn78c08372019-12-11 20:14:23 +01001092 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1093 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1094 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001095 string* chs = filter(m_indices(channels), function int (string ch_n) {
Arathorn739a4fa2020-08-06 21:52:58 +02001096 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1097 im Caller Stack zu erzeugen, weil access() mit
1098 extern_call() und previous_object() arbeitet und
1099 sichergestellt sein muss, dass das in jedem Fall das
1100 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001101 return ( stringp(regmatch(ch_n, "^"+chname)) &&
1102 funcall(#'access, channels[ch_n], pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001103 });
Arathorn19459eb2019-11-30 00:45:51 +01001104
Arathorn78c08372019-12-11 20:14:23 +01001105 int num_channels = sizeof(chs);
1106 if (num_channels > 1)
1107 return chs;
1108 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001109 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001110 else
1111 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001112}
1113
Arathorn78c08372019-12-11 20:14:23 +01001114// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001115public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001116{
Zesstrab7720dc2020-08-11 22:14:18 +02001117 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001118 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1119 zu erzeugen, weil access() mit extern_call() und previous_object()
1120 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1121 richtige ist. */
1122 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001123 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001124 else
Zesstrab7720dc2020-08-11 22:14:18 +02001125 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001126}
1127
Arathorn78c08372019-12-11 20:14:23 +01001128// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001129public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001130{
Zesstra26aaf1a2020-08-07 19:10:39 +02001131 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001132 if (previous_object() != this_object())
1133 {
Zesstrab7720dc2020-08-11 22:14:18 +02001134 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001135 pl != this_player() || this_player() != this_interactive() ||
1136 this_interactive() != previous_object() ||
1137 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001138 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001139 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001140
1141 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001142
Arathorn19459eb2019-11-30 00:45:51 +01001143 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001144}
1145
Arathorn78c08372019-12-11 20:14:23 +01001146// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001147public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001148{
Zesstra26aaf1a2020-08-07 19:10:39 +02001149 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001150 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001151 if (previous_object() != this_object())
1152 {
Zesstrab7720dc2020-08-11 22:14:18 +02001153 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001154 this_player() != this_interactive() ||
1155 this_interactive() != previous_object() ||
1156 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001157 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001158 }
Zesstrab7720dc2020-08-11 22:14:18 +02001159 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001160 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1161 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001162 if (pointerp(channelH[chname]))
1163 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001164
1165 return 0;
1166}