blob: 3dade1cca3870acc12321e9ecf64a65b361e1c6d [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// channeld.c
2//
3// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
4//
5
6#pragma strong_types
7#pragma no_shadow // keine Shadowing...
8#pragma no_clone
9#pragma no_inherit
10#pragma save_types
11
12#include <sys_debug.h>
13#include <lpctypes.h>
14#include <wizlevels.h>
Arathorn78c08372019-12-11 20:14:23 +010015#include <regexp.h>
MG Mud User88f12472016-06-24 23:31:02 +020016
17#include <properties.h>
18#include <config.h>
19#include <language.h>
20
21#define NEED_PROTOTYPES
22#include "channel.h"
23
Arathorn78c08372019-12-11 20:14:23 +010024#define CHANNEL_SAVE "/p/daemon/save/channeld"
25#define MEMORY "/secure/memory"
26#define MAX_HIST_SIZE 200
27#define MAX_CHANNELS 90
28#define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
MG Mud User88f12472016-06-24 23:31:02 +020029
Arathorn78c08372019-12-11 20:14:23 +010030
31/* Ebenenliste und die zugehoerigen Daten, z.B. Mitglieder oder Beschreibung
Arathorn19459eb2019-11-30 00:45:51 +010032 channels = ([string channelname : ({ ({object* members}),
33 closure access_rights,
Arathorn78c08372019-12-11 20:14:23 +010034 string channel_desc,
Arathorn19459eb2019-11-30 00:45:51 +010035 string|object master_object,
Arathorn78c08372019-12-11 20:14:23 +010036 string readable_channelname }) ]) */
37private nosave mapping channels = ([]);
Arathorn19459eb2019-11-30 00:45:51 +010038//private nosave mapping lowerch; // unused
39
Arathorn78c08372019-12-11 20:14:23 +010040/* Ebenenhistory
41 mapping channelH = ([ string channelname : ({ ({string channelname,
42 string sender,
43 string msg,
44 int msg_type}) }) ]) */
45// channelH wird in create() geeignet initialisiert
46// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
47// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
48// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
49// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
50// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020051private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010052
Arathorn78c08372019-12-11 20:14:23 +010053/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
54 zerstoerte Ebenen.
55 mapping stats = ([ "time" : int object_time(),
56 "boot" : string getuid(previous_object()),
Arathorn19459eb2019-11-30 00:45:51 +010057 "new" : int total_channels_created,
58 "disposed" : int total_channels_removed ]) */
Arathorn78c08372019-12-11 20:14:23 +010059// stats wird in create() geeignet initialisiert
MG Mud User88f12472016-06-24 23:31:02 +020060private nosave mapping stats;
61
Arathorn78c08372019-12-11 20:14:23 +010062/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Arathorn19459eb2019-11-30 00:45:51 +010063 mapping channelC = ([ string channelname : ({ string I_NAME,
64 string I_INFO,
65 int time() }) ]) */
Arathorn78c08372019-12-11 20:14:23 +010066private mapping channelC = ([]);
Arathorn19459eb2019-11-30 00:45:51 +010067
Arathorn78c08372019-12-11 20:14:23 +010068/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
69 mapping channelB = ([ string playername : string* banned_command ]) */
70private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020071
Arathorn78c08372019-12-11 20:14:23 +010072/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
73 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
74 kann.
75
Arathorn19459eb2019-11-30 00:45:51 +010076 mapping Tcmd = ([ "lag": int timestamp,
77 "uptime": int timestamp,
78 "statistik": int timestamp]) */
79private mapping Tcmd = ([]);
80
Arathorn78c08372019-12-11 20:14:23 +010081/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
82 Speicherevent das Savefile geschrieben werden soll.
83 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +020084private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +020085
Arathorn19459eb2019-11-30 00:45:51 +010086
MG Mud User88f12472016-06-24 23:31:02 +020087// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
88
Arathorn78c08372019-12-11 20:14:23 +010089// Indizes fuer Zugriffe auf das Mapping <admin>.
MG Mud User88f12472016-06-24 23:31:02 +020090#define RECV 0
91#define SEND 1
92#define FLAG 2
93
Arathorn78c08372019-12-11 20:14:23 +010094// Ebenenflags, gespeichert in admin[ch, FLAG]
95// F_WIZARD kennzeichnet reine Magierebenen
MG Mud User88f12472016-06-24 23:31:02 +020096#define F_WIZARD 1
Arathorn78c08372019-12-11 20:14:23 +010097// Ebenen, auf denen keine Gaeste erlaubt sind, sind mit F_NOGUEST markiert.
MG Mud User88f12472016-06-24 23:31:02 +020098#define F_NOGUEST 2
99
Arathorn78c08372019-12-11 20:14:23 +0100100/* Speichert Sende- und Empfangslevel sowie Flags zu den einzelnen Channeln.
101 Wird beim Laden des Masters via create() -> initalize() -> setup() mit den
102 Daten aus dem Init-File ./channeld.init befuellt.
103 mapping admin = ([ string channel_name : int RECV_LVL,
104 int SEND_LVL,
105 int FLAG ]) */
MG Mud User88f12472016-06-24 23:31:02 +0200106private nosave mapping admin = m_allocate(0, 3);
107
Arathorn78c08372019-12-11 20:14:23 +0100108// check_ch_access() prueft die Zugriffsberechtigungen auf Ebenen.
109//
110// Gibt 1 zurueck, wenn Aktion erlaubt, 0 sonst.
111// Wird von access() gerufen; access() gibt das Ergebnis von
112// check_ch_access() zurueck.
113//
114// Verlassen (C_LEAVE) ist immer erlaubt. Die anderen Aktionen sind in zwei
115// Gruppen eingeteilt:
116// 1) RECV. Die Aktionen dieser Gruppe sind Suchen (C_FIND), Auflisten
117// (C_LIST) und Betreten (C_JOIN).
118// 2) SEND. Die Aktion dieser Gruppe ist zur Zeit nur Senden (C_SEND).
119//
120// Aktionen werden zugelassen, wenn Spieler/MagierLevel groesser ist als die
121// fuer die jeweilige Aktionsgruppe RECV oder SEND festgelegte Stufe.
122// Handelt es sich um eine Magierebene (F_WIZARD), muss die Magierstufe
123// des Spielers groesser sein als die Mindeststufe der Ebene. Ansonsten
124// wird gegen den Spielerlevel geprueft.
125//
126// Wenn RECV_LVL oder SEND_LVL auf -1 gesetzt ist, sind die Aktionen der
127// jeweiligen Gruppen komplett geblockt.
MG Mud User88f12472016-06-24 23:31:02 +0200128
Arathorn739a4fa2020-08-06 21:52:58 +0200129public int check_ch_access(string ch, object pl, string cmd)
Arathorn78c08372019-12-11 20:14:23 +0100130{
131 // <pl> ist Gast, es sind aber keine Gaeste zugelassen? Koennen wir
132 // direkt ablehnen.
Arathorn19459eb2019-11-30 00:45:51 +0100133 if ((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest())
134 return 0;
135
Arathorn78c08372019-12-11 20:14:23 +0100136 // Ebenso auf Magier- oder Seherebenen, wenn ein Spieler anfragt, der
137 // noch kein Seher ist.
Arathorn19459eb2019-11-30 00:45:51 +0100138 if ((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL)
139 return 0;
140
Arathorn78c08372019-12-11 20:14:23 +0100141 // Ebene ist Magierebene? Dann werden alle Stufenlimits gegen Magierlevel
142 // geprueft, ansonsten gegen Spielerlevel.
143 int level = (admin[ch, FLAG] & F_WIZARD
144 ? query_wiz_level(pl)
145 : pl->QueryProp(P_LEVEL));
MG Mud User88f12472016-06-24 23:31:02 +0200146
Arathorn19459eb2019-11-30 00:45:51 +0100147 switch (cmd)
MG Mud User88f12472016-06-24 23:31:02 +0200148 {
Arathorn19459eb2019-11-30 00:45:51 +0100149 case C_FIND:
150 case C_LIST:
151 case C_JOIN:
152 if (admin[ch, RECV] == -1)
153 return 0;
154 if (admin[ch, RECV] <= level)
155 return 1;
156 break;
157
158 case C_SEND:
159 if (admin[ch, SEND] == -1)
160 return 0;
161 if (admin[ch, SEND] <= level)
162 return 1;
163 break;
164
Arathorn78c08372019-12-11 20:14:23 +0100165 // Verlassen ist immer erlaubt
Arathorn19459eb2019-11-30 00:45:51 +0100166 case C_LEAVE:
167 return 1;
168
169 default:
170 break;
MG Mud User88f12472016-06-24 23:31:02 +0200171 }
Arathorn19459eb2019-11-30 00:45:51 +0100172 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200173}
174
Arathorn78c08372019-12-11 20:14:23 +0100175/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
176// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
177private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200178{
Arathorn78c08372019-12-11 20:14:23 +0100179 object* userlist = ({});
180 foreach(string ch_name, mixed* ch_data : channels)
181 {
182 userlist += ch_data[I_MEMBER];
183 }
184 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
185 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200186}
187
Arathorn78c08372019-12-11 20:14:23 +0100188// Ist das Objekt <sender> Abonnent der Ebene <ch>?
189private int IsChannelMember(string ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200190{
Arathorn78c08372019-12-11 20:14:23 +0100191 return (member(channels[ch][I_MEMBER], sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200192}
193
Arathorn78c08372019-12-11 20:14:23 +0100194// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
195private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200196{
Arathorn78c08372019-12-11 20:14:23 +0100197 if (objectp(ob))
198 ob = getuid(ob);
199 return(pointerp(channelB[ob]) &&
200 member(channelB[ob], command) != -1);
201}
MG Mud User88f12472016-06-24 23:31:02 +0200202
Arathorn78c08372019-12-11 20:14:23 +0100203private void banned(string plname, string* cmds, string res)
204{
205 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
206}
207
208#define TIMEOUT (time() - 60)
209
210// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
211// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
212// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
213// zurueckliegt, als "nicht gesperrt".
214private int IsNotBlocked(string* cmd)
215{
216 string* res = filter(cmd, function int (string str) {
217 return (Tcmd[str] < TIMEOUT);
218 });
219 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
220 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
221 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
222 if (sizeof(res) == sizeof(cmd)) {
223 foreach(string str : cmd) {
224 Tcmd[str] = time();
225 }
226 return 1;
227 }
228 return 0;
229}
230
231// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
232// zugelassen wird. Anforderungen:
233// 1) <cmd> muss Teilstring von <check> sein
234// 2) <cmd> muss am Anfang von <check> stehen
235// 3) <cmd> darf nicht laenger sein als <check>
236// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
237// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
238// cmd = "statistiker" oder cmd = "tist"
239// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
240private int IsValidChannelCommand(string cmd, string check) {
241 // Syntaxcheck (prueft Bedingungen 1 bis 3).
242 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
243 string* cmd_to_check;
244 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
245 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
246 // geben.
247 if ( cmd == "lust" )
248 cmd_to_check = ({"lag", "statistik", "uptime"});
249 else
250 cmd_to_check = ({cmd});
251 // Prueft die Zeitsperre (Bedingung 4).
252 return (IsNotBlocked(cmd_to_check));
253 }
254 return 0;
255}
256
257#define CH_NAME 0
258#define CH_SENDER 1
259#define CH_MSG 2
260#define CH_MSG_TYPE 3
261// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
262// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
263// und dieses per Callout an send() uebergeben.
264// Argument: ({string channels[ch][I_NAME], object pl, string msg, int type})
265// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
266// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
267// was aber bei einer private oder protected Funktion nicht moeglich waere.
268public void ChannelMessage(<string|object|int>* msg)
269{
270 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
271 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
272 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
273 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100274 return;
MG Mud User88f12472016-06-24 23:31:02 +0200275
Arathorn78c08372019-12-11 20:14:23 +0100276 float* lag;
277 int max, rekord;
278 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200279 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200280
Arathorn78c08372019-12-11 20:14:23 +0100281 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200282 {
Arathorn78c08372019-12-11 20:14:23 +0100283 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
284 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100285 }
Arathorn78c08372019-12-11 20:14:23 +0100286 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100287 {
MG Mud User88f12472016-06-24 23:31:02 +0200288 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
289 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100290 "%.1f%%/20s, %.1f%%/2s",
291 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100292 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
293 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100294 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200295 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100296 }
Arathorn78c08372019-12-11 20:14:23 +0100297 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200298 {
Arathorn78c08372019-12-11 20:14:23 +0100299 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100300 {
Arathorn78c08372019-12-11 20:14:23 +0100301 string unused;
302 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
303 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
304 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
305 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
306 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100307 }
308 else
309 {
Arathorn78c08372019-12-11 20:14:23 +0100310 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200311 }
Arathorn19459eb2019-11-30 00:45:51 +0100312 }
Arathorn78c08372019-12-11 20:14:23 +0100313 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200314 {
MG Mud User88f12472016-06-24 23:31:02 +0200315 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100316 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
317 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
318 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
319 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100320 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
321 }
Arathorn78c08372019-12-11 20:14:23 +0100322 // Ebenenaktion beginnt mit "bann"?
323 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200324 {
325 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100326
327 if (mesg == "bann")
328 {
329 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200330 {
Arathorn78c08372019-12-11 20:14:23 +0100331 ret = "Fuer folgende Spieler besteht ein Bann: ";
332 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
333 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
334 string* banlist = ({});
335 foreach(string plname, string* banned_cmds : channelB) {
336 banlist += ({ sprintf("%s [%s]",
337 capitalize(plname), implode(banned_cmds, ", "))});
338 }
339 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200340 }
341 else
342 {
Arathorn19459eb2019-11-30 00:45:51 +0100343 ret = "Zur Zeit ist kein Bann aktiv.";
344 }
345 }
346 else
347 {
Arathorn78c08372019-12-11 20:14:23 +0100348 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
349 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100350 {
351 pl = lower_case(pl);
352 cmd = lower_case(cmd);
353
354 if (member(CMDS, cmd) != -1)
355 {
Arathorn78c08372019-12-11 20:14:23 +0100356 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
357 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
358 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100359 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100360 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100361
Arathorn78c08372019-12-11 20:14:23 +0100362 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100363 channelB[pl] -= ({ cmd });
364 else
365 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100366
Arathorn78c08372019-12-11 20:14:23 +0100367 ret = "Fuer '" + capitalize(pl) + "' besteht " +
368 (sizeof(channelB[pl])
369 // TODO: implode() -> CountUp()?
370 ? "folgender Bann: " + implode(channelB[pl], ", ") + "."
371 : "kein Bann mehr.");
372
373 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100374 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100375 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100376
Zesstraa2db5522020-08-11 22:14:55 +0200377 //TODO: save_me_soon=1 sollte auch reichen...
Arathorn19459eb2019-11-30 00:45:51 +0100378 save_object(CHANNEL_SAVE);
379 }
380 else
381 {
382 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100383 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100384 }
385 }
386 else
387 {
Arathorn78c08372019-12-11 20:14:23 +0100388 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100389 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200390 }
391 }
392 }
Arathorn78c08372019-12-11 20:14:23 +0100393 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200394 {
MG Mud User88f12472016-06-24 23:31:02 +0200395 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100396 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
397 {
398 string unused;
399 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
400 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
401 }
MG Mud User88f12472016-06-24 23:31:02 +0200402
Arathorn78c08372019-12-11 20:14:23 +0100403 int t = time() - last_reboot_time();
404
405 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
406 // die Funktionalitaet oefter benoetigt wird als nur hier.
407 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100408 if (t >= 86400)
409 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200410
Arathorn78c08372019-12-11 20:14:23 +0100411 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100412 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100413 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100414
Arathorn78c08372019-12-11 20:14:23 +0100415 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100416 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100417 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100418
419 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100420
MG Mud User88f12472016-06-24 23:31:02 +0200421 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100422 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100423 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100424 }
425 else
426 {
427 return;
428 }
MG Mud User88f12472016-06-24 23:31:02 +0200429
Arathorn78c08372019-12-11 20:14:23 +0100430 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
431 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
432 // nicht erfuellt sind.
433 if (stringp(ret) && sizeof(ret))
434 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200435}
436
437// setup() -- set up a channel and register it
438// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100439// string* chinfo = ({ channel_name, receive_level, send_level,
440// flags, description, masterobj })
441private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200442{
Arathorn78c08372019-12-11 20:14:23 +0100443 string desc = "- Keine Beschreibung -";
444 object chmaster = this_object();
Arathorn19459eb2019-11-30 00:45:51 +0100445
Arathorn78c08372019-12-11 20:14:23 +0100446 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
447 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200448
Arathorn78c08372019-12-11 20:14:23 +0100449 switch (sizeof(chinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200450 {
Arathorn78c08372019-12-11 20:14:23 +0100451 // Alle Fallthroughs in dem switch() sind Absicht.
Arathorn19459eb2019-11-30 00:45:51 +0100452 case 6:
Arathorn78c08372019-12-11 20:14:23 +0100453 if (stringp(chinfo[5]) && sizeof(chinfo[5]))
454 catch(chmaster = load_object(chinfo[5]); publish);
455 if (!objectp(chmaster))
456 chmaster = this_object();
Arathorn19459eb2019-11-30 00:45:51 +0100457
458 case 5:
Arathorn78c08372019-12-11 20:14:23 +0100459 if (stringp(chinfo[4]) || closurep(chinfo[4]))
460 desc = chinfo[4];
Zesstra26aaf1a2020-08-07 19:10:39 +0200461 // Die admin-Daten sind nicht fuer die Ebene wichtig, nur fuer die
462 // check_ch_access().
Arathorn19459eb2019-11-30 00:45:51 +0100463 case 4:
Arathorn739a4fa2020-08-06 21:52:58 +0200464 admin[lower_case(chinfo[0]), FLAG] = to_int(chinfo[3]);
Arathorn19459eb2019-11-30 00:45:51 +0100465
466 case 3:
Arathorn739a4fa2020-08-06 21:52:58 +0200467 admin[lower_case(chinfo[0]), SEND] = to_int(chinfo[2]);
Arathorn19459eb2019-11-30 00:45:51 +0100468
469 case 2:
Arathorn739a4fa2020-08-06 21:52:58 +0200470 admin[lower_case(chinfo[0]), RECV] = to_int(chinfo[1]);
Arathorn19459eb2019-11-30 00:45:51 +0100471 break;
472
473 case 0:
474 default:
475 return;
MG Mud User88f12472016-06-24 23:31:02 +0200476 }
Arathorn19459eb2019-11-30 00:45:51 +0100477
Arathorn78c08372019-12-11 20:14:23 +0100478 if (new(chinfo[0], chmaster, desc) == E_ACCESS_DENIED)
MG Mud User88f12472016-06-24 23:31:02 +0200479 {
Arathorn78c08372019-12-11 20:14:23 +0100480 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
481 dtime(time()), chinfo[0], chmaster));
MG Mud User88f12472016-06-24 23:31:02 +0200482 }
483 return;
484}
485
Arathorn78c08372019-12-11 20:14:23 +0100486private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200487{
Arathorn78c08372019-12-11 20:14:23 +0100488 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200489#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100490 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200491#else
Arathorn78c08372019-12-11 20:14:23 +0100492 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200493#endif
Arathorn19459eb2019-11-30 00:45:51 +0100494
Arathorn78c08372019-12-11 20:14:23 +0100495 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200496 return;
Arathorn19459eb2019-11-30 00:45:51 +0100497
Arathorn78c08372019-12-11 20:14:23 +0100498 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
499 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
500 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
501 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
502 foreach(string ch : old_explode(ch_list, "\n"))
503 {
504 if (ch[0]=='#')
505 continue;
506 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
507 }
MG Mud User88f12472016-06-24 23:31:02 +0200508}
509
Arathorn78c08372019-12-11 20:14:23 +0100510// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200511protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200512{
513 seteuid(getuid());
514 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100515
Zesstra26aaf1a2020-08-07 19:10:39 +0200516 //TODO: weitere Mappings im MEMORY speichern, Savefile ersetzen.
517
Arathorn19459eb2019-11-30 00:45:51 +0100518 /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
519 gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
MG Mud User88f12472016-06-24 23:31:02 +0200520 Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
521 das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
522 Memory abgelegt. */
523
524 // Hab ich die noetigen Rechte im Memory?
Arathorn19459eb2019-11-30 00:45:51 +0100525 if (call_other(MEMORY, "HaveRights"))
526 {
MG Mud User88f12472016-06-24 23:31:02 +0200527 // Objektpointer laden
Dominik Schaeferfa564d52020-08-05 20:50:27 +0200528 channelH = ({mapping}) call_other(MEMORY, "Load", "History");
MG Mud User88f12472016-06-24 23:31:02 +0200529
530 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
Arathorn78c08372019-12-11 20:14:23 +0100531 if (!mappingp(channelH))
532 {
MG Mud User88f12472016-06-24 23:31:02 +0200533 // Zeiger erzeugen
534 channelH = ([]);
535 // und in den Memory schreiben
Arathorn19459eb2019-11-30 00:45:51 +0100536 call_other(MEMORY, "Save", "History", channelH);
MG Mud User88f12472016-06-24 23:31:02 +0200537 }
Arathorn19459eb2019-11-30 00:45:51 +0100538 }
539 else
540 {
MG Mud User88f12472016-06-24 23:31:02 +0200541 // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
542 channelH = ([]);
543 }
544
545 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100546 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
547
548 // <MasteR>-Ebene erstellen. Channeld wird Ebenenbesitzer und somit auch
549 // Zuhoerer, damit er auf Kommandos auf dieser Ebene reagieren kann.
550 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
551
MG Mud User88f12472016-06-24 23:31:02 +0200552 initialize();
Arathorn78c08372019-12-11 20:14:23 +0100553 users()->RegisterChannels();
554
555 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
556 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
557 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
558 // explizites call_other() auf this_object() gemacht, damit der
559 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
560 // einem externen.
MG Mud User88f12472016-06-24 23:31:02 +0200561 this_object()->send(CMNAME, this_object(),
Arathorn19459eb2019-11-30 00:45:51 +0100562 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
563 sizeof(channels),
Arathorn78c08372019-12-11 20:14:23 +0100564 CountUsers()));
MG Mud User88f12472016-06-24 23:31:02 +0200565}
566
Arathorn78c08372019-12-11 20:14:23 +0100567varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200568{
Zesstra26aaf1a2020-08-07 19:10:39 +0200569 //TODO reset nur 1-2mal am Tag mit etwas random.
570
571 // Cache bereinigen entsprechend dessen Timeout-Zeit (12 h)
572 // TODO: Zeit auf 2-3 Tage erhoehen.
573 // TODO 2: Zeit dynamisch machen und nur expiren, wenn mehr als n Eintraege.
574 // Zeit reduzieren, bis nur noch n/2 Eintraege verbleiben.
Zesstra8f5102c2020-08-08 12:51:52 +0200575 channelC = filter(channelC,
576 function int (string ch_name, <string|int>* data)
577 {
578 if (channelC[ch_name][2] + 43200 > time())
579 return 1;
580 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
581 m_delete(channelH, ch_name);
582 return 0;
583 });
Arathorn19459eb2019-11-30 00:45:51 +0100584
MG Mud User88f12472016-06-24 23:31:02 +0200585 if (save_me_soon)
586 {
Arathorn19459eb2019-11-30 00:45:51 +0100587 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200588 save_object(CHANNEL_SAVE);
589 }
590}
591
592// name() - define the name of this object.
Arathorn19459eb2019-11-30 00:45:51 +0100593string name()
594{
595 return CMNAME;
596}
597
598string Name()
599{
600 return CMNAME;
601}
MG Mud User88f12472016-06-24 23:31:02 +0200602
Arathorn78c08372019-12-11 20:14:23 +0100603#define CHAN_NAME(x) channels[x][I_NAME]
604#define MASTER_OB(x) channels[x][I_MASTER]
605#define ACC_CLOSURE(x) channels[x][I_ACCESS]
606
MG Mud User88f12472016-06-24 23:31:02 +0200607// access() - check access by looking for the right argument types and
608// calling access closures respectively
609// SEE: new, join, leave, send, list, users
610// Note: <pl> is usually an object, only the master supplies a string during
611// runtime error handling.
Zesstra26aaf1a2020-08-07 19:10:39 +0200612// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
613// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
614// Zuhoerer zu sein.
Arathorn78c08372019-12-11 20:14:23 +0100615varargs private int access(string ch, object|string pl, string cmd,
616 string txt)
MG Mud User88f12472016-06-24 23:31:02 +0200617{
Arathorn739a4fa2020-08-06 21:52:58 +0200618 if (!sizeof(ch))
MG Mud User88f12472016-06-24 23:31:02 +0200619 return 0;
Arathorn19459eb2019-11-30 00:45:51 +0100620
Arathorn739a4fa2020-08-06 21:52:58 +0200621 ch = lower_case(ch);
622 if(!pointerp(channels[ch]))
623 return 0;
624
625 if ( !previous_object(1) || !extern_call() ||
626 previous_object(1) == this_object() ||
627 (stringp(MASTER_OB(ch)) &&
628 previous_object(1) == find_object(MASTER_OB(ch))) ||
629 getuid(previous_object(1)) == ROOTID)
MG Mud User88f12472016-06-24 23:31:02 +0200630 return 2;
Arathorn19459eb2019-11-30 00:45:51 +0100631
Arathorn78c08372019-12-11 20:14:23 +0100632 // Es ist keine Closure vorhanden, d.h. der Ebenenbesitzer wurde zerstoert.
633 if (!closurep(ACC_CLOSURE(ch)))
MG Mud User88f12472016-06-24 23:31:02 +0200634 {
Arathorn78c08372019-12-11 20:14:23 +0100635 // Wenn der Ebenenbesitzer als String eingetragen ist, versuchen wir,
636 // die Closure wiederherzustellen. Dabei wird das Objekt gleichzeitig
637 // neugeladen.
638 if (stringp(MASTER_OB(ch)))
MG Mud User88f12472016-06-24 23:31:02 +0200639 {
Arathorn78c08372019-12-11 20:14:23 +0100640 closure new_acc_cl;
641 string err = catch(new_acc_cl=
642 symbol_function("check_ch_access", MASTER_OB(ch));
643 publish);
644 /* Wenn sich die Closure fehlerfrei erstellen liess, dann wird sie als
645 neue Zugriffskontrolle eingetragen und auch der Ebenenbesitzer neu
646 gesetzt. */
Arathorn78c08372019-12-11 20:14:23 +0100647 if (!err)
648 {
649 ACC_CLOSURE(ch) = new_acc_cl;
Arathorn78c08372019-12-11 20:14:23 +0100650 }
651 else
652 {
653 log_file("CHANNEL", sprintf("[%s] %O -> %O\n",
654 dtime(time()), MASTER_OB(ch), err));
655 m_delete(channels, ch);
656 return 0;
657 }
MG Mud User88f12472016-06-24 23:31:02 +0200658 }
Arathorn78c08372019-12-11 20:14:23 +0100659 // TODO: kaputte Objekte raussortieren, neuen Master bestimmen, wenn
660 // dieser nicht mehr existiert.
Arathorn19459eb2019-11-30 00:45:51 +0100661
Arathorn78c08372019-12-11 20:14:23 +0100662 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
663 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
664 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
665 // explizites call_other() auf this_object() gemacht, damit der
666 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
667 // einem externen.
668
669 // Der neue Ebenenbesitzer tritt auch gleich der Ebene bei.
670 this_object()->join(ch, find_object(MASTER_OB(ch)));
MG Mud User88f12472016-06-24 23:31:02 +0200671 }
Arathorn739a4fa2020-08-06 21:52:58 +0200672
673 if (!objectp(pl) ||
674 ((previous_object(1) != pl) && (previous_object(1) != this_object())))
675 return 0;
676
677 if (IsBanned(pl, cmd))
678 return 0;
679
680 if (!ACC_CLOSURE(ch))
681 return 1;
682
683 return funcall(ACC_CLOSURE(ch), ch, pl, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200684}
685
Arathorn78c08372019-12-11 20:14:23 +0100686// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
687// <info> kann die statische Beschreibung der Ebene sein oder eine Closure,
688// die dynamisch aktualisierte Infos ausgibt.
689// Das Objekt <owner> kann eine Funktion check_ch_access() definieren, die
690// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
691// eingeht.
692// check_ch_access() dient der Zugriffskontrolle und entscheidet, ob die
693// Nachricht gesendet werden darf oder nicht.
694
695// Ist keine Closure angegeben, wird die in diesem Objekt (Channeld)
696// definierte Funktion gleichen Namens verwendet.
MG Mud User88f12472016-06-24 23:31:02 +0200697#define IGNORE "^/xx"
698
Arathorn78c08372019-12-11 20:14:23 +0100699// TODO: KOMMENTAR
700//check may contain a closure
701// called when a join/leave/send/list/users message is received
702public varargs int new(string ch_name, object owner, string|closure info)
MG Mud User88f12472016-06-24 23:31:02 +0200703{
Arathorn78c08372019-12-11 20:14:23 +0100704 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
705 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
706 // fuer eine neue Ebene eintragen.)
707 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200708 return E_ACCESS_DENIED;
709
Arathorn78c08372019-12-11 20:14:23 +0100710 // Kein gescheiter Channelname angegeben.
711 if (!stringp(ch_name) || !sizeof(ch_name))
712 return E_ACCESS_DENIED;
713
714 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
715 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
716 return E_ACCESS_DENIED;
717
718 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
719 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
720 // seinen Objektnamen matcht.
721 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
722 return E_ACCESS_DENIED;
723
724 // Keine Infos mitgeliefert? Dann holen wir sie aus dem Cache.
Arathorn19459eb2019-11-30 00:45:51 +0100725 if (!info)
726 {
Arathorn78c08372019-12-11 20:14:23 +0100727 if (channelC[lower_case(ch_name)])
Arathorn19459eb2019-11-30 00:45:51 +0100728 {
Arathorn78c08372019-12-11 20:14:23 +0100729 ch_name = channelC[lower_case(ch_name)][0];
730 info = channelC[lower_case(ch_name)][1];
MG Mud User88f12472016-06-24 23:31:02 +0200731 }
Arathorn19459eb2019-11-30 00:45:51 +0100732 else
733 {
734 return E_ACCESS_DENIED;
735 }
MG Mud User88f12472016-06-24 23:31:02 +0200736 }
Arathorn19459eb2019-11-30 00:45:51 +0100737 else
738 {
Arathorn78c08372019-12-11 20:14:23 +0100739 channelC[lower_case(ch_name)] = ({ ch_name, info, time() });
Arathorn19459eb2019-11-30 00:45:51 +0100740 }
MG Mud User88f12472016-06-24 23:31:02 +0200741
Arathorn78c08372019-12-11 20:14:23 +0100742 object* pls = ({ owner });
743 m_add(channels, lower_case(ch_name),
744 ({ pls,
745 symbol_function("check_ch_access", owner) || #'check_ch_access,
746 info,
747 (!living(owner) && !clonep(owner) && owner != this_object()
748 ? object_name(owner)
749 : owner),
750 ch_name }));
MG Mud User88f12472016-06-24 23:31:02 +0200751
Arathorn78c08372019-12-11 20:14:23 +0100752 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
753 // nicht gibt.
754 if (!pointerp(channelH[lower_case(ch_name)]))
755 channelH[lower_case(ch_name)] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200756
Arathorn78c08372019-12-11 20:14:23 +0100757 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
758 if (owner != this_object())
MG Mud User88f12472016-06-24 23:31:02 +0200759 log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
Arathorn78c08372019-12-11 20:14:23 +0100760 dtime(time()), ch_name, owner, info));
Arathorn19459eb2019-11-30 00:45:51 +0100761
Arathorn78c08372019-12-11 20:14:23 +0100762 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
763 if (!owner->QueryProp(P_INVIS))
764 {
765 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
766 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
767 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
768 // explizites call_other() auf this_object() gemacht, damit der
769 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
770 // einem externen.
771 this_object()->send(CMNAME, owner,
772 "laesst die Ebene '" + ch_name + "' entstehen.", MSG_EMOTE);
773 }
Arathorn19459eb2019-11-30 00:45:51 +0100774
MG Mud User88f12472016-06-24 23:31:02 +0200775 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100776 save_me_soon = 1;
777 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200778}
779
Arathorn78c08372019-12-11 20:14:23 +0100780// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
781// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
782// zweimal beitreten.)
783public int join(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200784{
Arathorn78c08372019-12-11 20:14:23 +0100785 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200786 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
787 zu erzeugen, weil access() mit extern_call() und previous_object()
788 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
789 richtige ist. */
790 if (!funcall(#'access, ch, pl, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +0100791 return E_ACCESS_DENIED;
792
Arathorn78c08372019-12-11 20:14:23 +0100793 if (IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +0100794 return E_ALREADY_JOINED;
795
MG Mud User88f12472016-06-24 23:31:02 +0200796 channels[ch][I_MEMBER] += ({ pl });
Arathorn19459eb2019-11-30 00:45:51 +0100797 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200798}
799
Arathorn78c08372019-12-11 20:14:23 +0100800// Objekt <pl> verlaesst Ebene <ch>.
801// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
802// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
803// Dies ist in check_ch_access() so geregelt, allerdings koennte dem Objekt
804// <pl> das Verlassen auf Grund eines Banns verboten sein.
805// Wenn kein Spieler mehr auf der Ebene ist, loest sie sich auf, sofern nicht
806// noch ein Ebenenbesitzer eingetragen ist.
807public int leave(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200808{
Arathorn78c08372019-12-11 20:14:23 +0100809 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200810 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
811 zu erzeugen, weil access() mit extern_call() und previous_object()
812 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
813 richtige ist. */
814 if (!funcall(#'access, ch, pl, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +0100815 return E_ACCESS_DENIED;
816
MG Mud User88f12472016-06-24 23:31:02 +0200817 channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
Arathorn19459eb2019-11-30 00:45:51 +0100818
Arathorn78c08372019-12-11 20:14:23 +0100819 if (!IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +0100820 return E_NOT_MEMBER;
821
Arathorn78c08372019-12-11 20:14:23 +0100822 // Kontrolle an jemand anderen uebergeben, wenn der Ebenenbesitzer diese
823 // verlaesst.
Arathorn19459eb2019-11-30 00:45:51 +0100824 if (pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
MG Mud User88f12472016-06-24 23:31:02 +0200825 {
826 channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
Arathorn19459eb2019-11-30 00:45:51 +0100827
828 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +0100829 {
830 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
831 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
832 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
833 // explizites call_other() auf this_object() gemacht, damit der
834 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
835 // einem externen.
Arathorn19459eb2019-11-30 00:45:51 +0100836 this_object()->send(ch, pl, "uebergibt die Ebene an " +
837 channels[ch][I_MASTER]->name(WEN) + ".", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +0100838 }
MG Mud User88f12472016-06-24 23:31:02 +0200839 }
Arathorn78c08372019-12-11 20:14:23 +0100840 channels[ch][I_MEMBER] -= ({pl});
MG Mud User88f12472016-06-24 23:31:02 +0200841
Arathorn78c08372019-12-11 20:14:23 +0100842 // Ebene loeschen, wenn keiner zuhoert und auch kein Masterobjekt
843 // existiert.
844 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
845 // wird diese zerstoert, mit Meldung.
Arathorn19459eb2019-11-30 00:45:51 +0100846 if (!sizeof(channels[ch][I_MEMBER]) && !stringp(channels[ch][I_MASTER]))
MG Mud User88f12472016-06-24 23:31:02 +0200847 {
Arathorn78c08372019-12-11 20:14:23 +0100848 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Arathorn19459eb2019-11-30 00:45:51 +0100849 if (!pl->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +0100850 {
851 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
852 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
853 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
854 // explizites call_other() auf this_object() gemacht, damit der
855 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
856 // einem externen.
857 this_object()->send(CMNAME, pl,
858 "verlaesst als "+
859 (pl->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
860 " die Ebene '"+channels[ch][I_NAME]+"', worauf diese sich in "
861 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
862 }
Arathorn19459eb2019-11-30 00:45:51 +0100863 channelC[lower_case(ch)] =
864 ({ channels[ch][I_NAME], channels[ch][I_INFO], time() });
Arathorn78c08372019-12-11 20:14:23 +0100865
866 // Ebene loeschen
MG Mud User88f12472016-06-24 23:31:02 +0200867 m_delete(channels, lower_case(ch));
Zesstra8f5102c2020-08-08 12:51:52 +0200868 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
869 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
870 // channelC bereinigt wird.
Arathorn78c08372019-12-11 20:14:23 +0100871
MG Mud User88f12472016-06-24 23:31:02 +0200872 stats["dispose"]++;
Arathorn19459eb2019-11-30 00:45:51 +0100873 save_me_soon = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200874 }
Arathorn19459eb2019-11-30 00:45:51 +0100875 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200876}
877
Arathorn78c08372019-12-11 20:14:23 +0100878// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
879// sofern <pl> dort senden darf.
880public varargs int send(string ch, object pl, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +0200881{
Arathorn78c08372019-12-11 20:14:23 +0100882 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200883 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
884 zu erzeugen, weil access() mit extern_call() und previous_object()
885 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
886 richtige ist. */
887 int a = funcall(#'access, ch, pl, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +0100888 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +0100889 return E_ACCESS_DENIED;
890
Zesstra26aaf1a2020-08-07 19:10:39 +0200891 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
892 // Rueckgabewert von check_ch_access() entspricht, wenn die Aktion zugelassen
893 // wird. access() allerdings 2 fuer "privilegierte" Objekte (z.B.
894 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
895 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Arathorn78c08372019-12-11 20:14:23 +0100896 if (a < 2 && !IsChannelMember(ch, pl))
Arathorn19459eb2019-11-30 00:45:51 +0100897 return E_NOT_MEMBER;
898
899 if (!msg || !stringp(msg) || !sizeof(msg))
900 return E_EMPTY_MESSAGE;
901
Arathorn78c08372019-12-11 20:14:23 +0100902 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
903 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
904 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
905 // Nachricht ebenfalls erhaelt.
906 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
907 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
908 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
909 // erzeugt und der Channeld als Besitzer angegeben.
910 // Die Aufrufkette ist dann wie folgt:
911 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
912 channels[ch][I_MEMBER]->ChannelMessage(
913 ({ channels[ch][I_NAME], pl, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +0100914
915 if (sizeof(channelH[ch]) > MAX_HIST_SIZE)
MG Mud User88f12472016-06-24 23:31:02 +0200916 channelH[ch] = channelH[ch][1..];
Arathorn19459eb2019-11-30 00:45:51 +0100917
918 channelH[ch] +=
919 ({ ({ channels[ch][I_NAME],
920 (stringp(pl)
921 ? pl
922 : (pl->QueryProp(P_INVIS)
923 ? "/(" + capitalize(getuid(pl)) + ")$"
924 : "")
925 + (pl->Name(WER, 2) || "<Unbekannt>")),
926 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
927 type }) });
Arathorn78c08372019-12-11 20:14:23 +0100928 return (0);
MG Mud User88f12472016-06-24 23:31:02 +0200929}
930
Arathorn78c08372019-12-11 20:14:23 +0100931// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
932// oder einen Integer-Fehlercode
933public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200934{
Arathorn78c08372019-12-11 20:14:23 +0100935 mapping chs = ([]);
936 foreach(string chname, <object*|closure|string|object>* chdata : channels)
937 {
Arathorn739a4fa2020-08-06 21:52:58 +0200938 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
939 zu erzeugen, weil access() mit extern_call() und previous_object()
940 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
941 richtige ist. */
942 if(funcall(#'access, chname, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +0100943 {
944 m_add(chs, chname, chdata);
945 chs[chname][I_MEMBER] = filter(chs[chname][I_MEMBER], #'objectp);
946 }
947 }
Arathorn19459eb2019-11-30 00:45:51 +0100948
949 if (!sizeof(chs))
950 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +0100951 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +0200952}
953
Arathorn78c08372019-12-11 20:14:23 +0100954// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
955// Rueckgabewerte:
956// - den gefundenen Namen als String
957// - String-Array, wenn es mehrere Treffer gibt
958// - 0, wenn es keinen Treffer gibt
959public string|string* find(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200960{
Arathorn78c08372019-12-11 20:14:23 +0100961 ch = lower_case(ch);
Arathorn19459eb2019-11-30 00:45:51 +0100962
Arathorn78c08372019-12-11 20:14:23 +0100963 // Suchstring <ch> muss Formatanforderung erfuellen;
964 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
965 // Wenn ja, muesste laut Manpage mehr geprueft werden:
966 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
967 // #$%&@<>-." Es wuerden also $%&@ fehlen.
968 if (!regmatch(ch, "^[<>a-z0-9#-]+$"))
969 return 0;
Arathorn19459eb2019-11-30 00:45:51 +0100970
Arathorn78c08372019-12-11 20:14:23 +0100971 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
972 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
973 // in das Suchergebnis aufgenommen wird.
974 string* chs = filter(m_indices(channels), function int (string chname) {
Arathorn739a4fa2020-08-06 21:52:58 +0200975 /* funcall() auf Closure-Operator, um einen neuen Eintrag
976 im Caller Stack zu erzeugen, weil access() mit
977 extern_call() und previous_object() arbeitet und
978 sichergestellt sein muss, dass das in jedem Fall das
979 richtige ist. */
Arathorn78c08372019-12-11 20:14:23 +0100980 return ( stringp(regmatch(chname, "^"+ch)) &&
Arathorn739a4fa2020-08-06 21:52:58 +0200981 funcall(#'access, chname, pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +0100982 });
Arathorn19459eb2019-11-30 00:45:51 +0100983
Arathorn78c08372019-12-11 20:14:23 +0100984 int num_channels = sizeof(chs);
985 if (num_channels > 1)
986 return chs;
987 else if (num_channels == 1)
988 return channels[chs[0]][I_NAME];
989 else
990 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200991}
992
Arathorn78c08372019-12-11 20:14:23 +0100993// Ebenen-History abfragen.
994public int|<int|string>** history(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +0200995{
Arathorn78c08372019-12-11 20:14:23 +0100996 ch = lower_case(ch);
Arathorn739a4fa2020-08-06 21:52:58 +0200997 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
998 zu erzeugen, weil access() mit extern_call() und previous_object()
999 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1000 richtige ist. */
1001 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001002 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001003 else
1004 return channelH[ch];
MG Mud User88f12472016-06-24 23:31:02 +02001005}
1006
Arathorn78c08372019-12-11 20:14:23 +01001007// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
1008public int remove_channel(string ch, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001009{
Zesstra26aaf1a2020-08-07 19:10:39 +02001010 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001011 if (previous_object() != this_object())
1012 {
1013 if (!stringp(ch) ||
1014 pl != this_player() || this_player() != this_interactive() ||
1015 this_interactive() != previous_object() ||
1016 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001017 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001018 }
MG Mud User88f12472016-06-24 23:31:02 +02001019
Arathorn78c08372019-12-11 20:14:23 +01001020 if (member(channels, lower_case(ch)))
Arathorn19459eb2019-11-30 00:45:51 +01001021 {
Arathorn78c08372019-12-11 20:14:23 +01001022 // Einer geloeschten Ebene kann man nicht zuhoeren: Ebenenname aus der
1023 // Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-, als
1024 // auch temporaer ausgeschaltete Ebenen beruecksichtigt.
1025 foreach(object listener : channels[lower_case(ch)][I_MEMBER])
1026 {
1027 string* pl_chans = listener->QueryProp(P_CHANNELS);
1028 if (pointerp(pl_chans))
Arathorn19459eb2019-11-30 00:45:51 +01001029 {
Arathorn78c08372019-12-11 20:14:23 +01001030 listener->SetProp(P_CHANNELS, pl_chans-({lower_case(ch)}));
1031 }
1032 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
1033 if (pointerp(pl_chans))
1034 {
1035 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({lower_case(ch)}));
1036 }
1037 }
1038 // Anschliessend werden die Ebenendaten geloescht.
1039 m_delete(channels, lower_case(ch));
MG Mud User88f12472016-06-24 23:31:02 +02001040
Arathorn78c08372019-12-11 20:14:23 +01001041 // Zaehler fuer zerstoerte Ebenen in der Statistik erhoehen.
MG Mud User88f12472016-06-24 23:31:02 +02001042 stats["dispose"]++;
1043 }
Zesstra8f5102c2020-08-08 12:51:52 +02001044 // Dies auuserhalb des Blocks oben ermoeglicht es, inaktive Ebenen bzw.
1045 // deren Daten zu entfernen.
1046 m_delete(channelC, lower_case(ch));
1047 // In diesem Fall der gezielten Loeschung wird auch die History geloescht.
1048 m_delete(channelH, lower_case(ch));
Arathorn19459eb2019-11-30 00:45:51 +01001049
Arathorn19459eb2019-11-30 00:45:51 +01001050 save_me_soon = 1;
1051 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001052}
1053
Arathorn78c08372019-12-11 20:14:23 +01001054// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
1055public int clear_history(string ch)
MG Mud User88f12472016-06-24 23:31:02 +02001056{
Zesstra26aaf1a2020-08-07 19:10:39 +02001057 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001058 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001059 if (previous_object() != this_object())
1060 {
1061 if (!stringp(ch) ||
1062 this_player() != this_interactive() ||
1063 this_interactive() != previous_object() ||
1064 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001065 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001066 }
MG Mud User88f12472016-06-24 23:31:02 +02001067
Zesstra26aaf1a2020-08-07 19:10:39 +02001068 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1069 // aus dem mapping loeschen.)
Arathorn19459eb2019-11-30 00:45:51 +01001070 if (pointerp(channelH[lower_case(ch)]))
1071 channelH[lower_case(ch)] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001072
1073 return 0;
1074}