blob: 5ede60f7f2c9b3ca7517cef22b260286042c7fbb [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
Zesstra6ddbacb2020-08-13 21:43:28 +020028// Default-Zeit (in Sekunden), die eine Ebene und ihr History inaktiv sein darf,
29// bevor sie expired wird. Das reduziert sich jedoch, falls es zuviele
30// inaktive Ebenen gibt. Entspricht 30 Tagen.
31#define INACTIVE_EXPIRE 2592000
32// Aber eine Ebene darf min. solange inaktiv sein, bevor sie geloescht wird
33#define MIN_INACTIVE_LIFETIME 3600
34// max. Anzahl der inaktiven Ebenen. Wenn die Haelfte davon ueberschritten
35// wird, wird mit zunehmend kleinerem CHANNEL_EXPIRE gearbeitet.
36#define MAX_INACTIVE_CHANNELS 500
Arathorn78c08372019-12-11 20:14:23 +010037#define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
MG Mud User88f12472016-06-24 23:31:02 +020038
Arathorn78c08372019-12-11 20:14:23 +010039
Zesstrab7720dc2020-08-11 22:14:18 +020040// Datenstrukturen fuer die Ebenen.
41// Basisdaten, welche auch inaktive Ebenen in channelC haben
42struct channel_base_s {
43 string name; // readable channelname, case-sensitive
44 string|closure desc; // stat. oder dyn. Beschreibung
Zesstra66f801d2020-09-24 21:00:13 +020045 string creator; // Ersteller der Ebene (Objektname), Besitzer
Zesstrad9ec04b2020-08-11 23:47:03 +020046 int flags; // Flags, die bestimmtes Verhalten steuern.
Zesstrab7720dc2020-08-11 22:14:18 +020047};
48
49// Basisdaten + die von aktiven Ebenen
50struct channel_s (channel_base_s) {
Zesstra9359fab2020-08-13 12:03:01 +020051 object supervisor; // aktueller Supervisor der Ebene
52 closure access_cl; // Closure fuer Zugriffsrechtepruefung
53 object *members; // Zuhoerer der Ebene
Zesstrab7720dc2020-08-11 22:14:18 +020054};
55
56/* Ebenenliste und die zugehoerigen Daten in struct (<channel>).
57 channels = ([string channelname : (<channel_s>) ])
Zesstra2aeb6a82020-08-13 23:50:36 +020058// HINWEIS: Bitte beachten, dass channels immer nur so manipuliert werden
59// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
60// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
61// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
62// Referenz in Memory und Channeld dieselbe ist.
Zesstra78310012020-08-09 12:21:48 +020063 */
Zesstra2aeb6a82020-08-13 23:50:36 +020064private nosave mapping channels;
Arathorn19459eb2019-11-30 00:45:51 +010065
Arathorn78c08372019-12-11 20:14:23 +010066/* Ebenenhistory
67 mapping channelH = ([ string channelname : ({ ({string channelname,
68 string sender,
69 string msg,
70 int msg_type}) }) ]) */
71// channelH wird in create() geeignet initialisiert
72// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
73// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
74// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
75// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
76// Referenz in Memory und Channeld dieselbe ist.
MG Mud User88f12472016-06-24 23:31:02 +020077private nosave mapping channelH;
Arathorn19459eb2019-11-30 00:45:51 +010078
Arathorn78c08372019-12-11 20:14:23 +010079/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +020080 mapping channelC = ([ string channelname : (<channel_base_s>);
81 int time() ])
82 Der Zeitstempel ist die letzte Aenderung, d.h. in der Regel des Ablegens in
83 channelC.
84 */
85private mapping channelC = ([:2]);
Arathorn19459eb2019-11-30 00:45:51 +010086
Arathorn78c08372019-12-11 20:14:23 +010087/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
88 mapping channelB = ([ string playername : string* banned_command ]) */
89private mapping channelB = ([]);
MG Mud User88f12472016-06-24 23:31:02 +020090
Arathorn78c08372019-12-11 20:14:23 +010091/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
92 um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
93 kann.
94
Arathorn19459eb2019-11-30 00:45:51 +010095 mapping Tcmd = ([ "lag": int timestamp,
96 "uptime": int timestamp,
97 "statistik": int timestamp]) */
98private mapping Tcmd = ([]);
99
Zesstra2aeb6a82020-08-13 23:50:36 +0200100/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
101 zerstoerte Ebenen.
102 mapping stats = ([ "time" : int object_time(),
103 "boot" : string getuid(previous_object()),
104 "new" : int total_channels_created,
105 "disposed" : int total_channels_removed ]) */
106// stats wird in create() geeignet initialisiert
107private nosave mapping stats;
108
Arathorn78c08372019-12-11 20:14:23 +0100109/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
110 Speicherevent das Savefile geschrieben werden soll.
111 Wird auf 0 oder 1 gesetzt. */
Zesstraa2db5522020-08-11 22:14:55 +0200112private nosave int save_me_soon;
MG Mud User88f12472016-06-24 23:31:02 +0200113
Zesstra46c564e2020-09-11 21:52:29 +0200114// Private Prototypen
115public int join(string chname, object joining);
116
MG Mud User88f12472016-06-24 23:31:02 +0200117
Arathorn78c08372019-12-11 20:14:23 +0100118/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
119// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
120private int CountUsers()
MG Mud User88f12472016-06-24 23:31:02 +0200121{
Arathorn78c08372019-12-11 20:14:23 +0100122 object* userlist = ({});
Zesstrab7720dc2020-08-11 22:14:18 +0200123 foreach(string ch_name, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +0100124 {
Zesstrab7720dc2020-08-11 22:14:18 +0200125 userlist += ch.members;
Arathorn78c08372019-12-11 20:14:23 +0100126 }
127 // Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
128 return sizeof(mkmapping(userlist));
MG Mud User88f12472016-06-24 23:31:02 +0200129}
130
Arathorn78c08372019-12-11 20:14:23 +0100131// Ist das Objekt <sender> Abonnent der Ebene <ch>?
Zesstrab7720dc2020-08-11 22:14:18 +0200132private int IsChannelMember(struct channel_s ch, object sender)
MG Mud User88f12472016-06-24 23:31:02 +0200133{
Zesstrab7720dc2020-08-11 22:14:18 +0200134 return (member(ch.members, sender) != -1);
MG Mud User88f12472016-06-24 23:31:02 +0200135}
136
Arathorn78c08372019-12-11 20:14:23 +0100137// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
138private int IsBanned(string|object ob, string command)
MG Mud User88f12472016-06-24 23:31:02 +0200139{
Arathorn78c08372019-12-11 20:14:23 +0100140 if (objectp(ob))
141 ob = getuid(ob);
142 return(pointerp(channelB[ob]) &&
143 member(channelB[ob], command) != -1);
144}
MG Mud User88f12472016-06-24 23:31:02 +0200145
Arathorn78c08372019-12-11 20:14:23 +0100146private void banned(string plname, string* cmds, string res)
147{
148 res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
149}
150
151#define TIMEOUT (time() - 60)
152
153// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
154// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
155// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
156// zurueckliegt, als "nicht gesperrt".
157private int IsNotBlocked(string* cmd)
158{
159 string* res = filter(cmd, function int (string str) {
160 return (Tcmd[str] < TIMEOUT);
161 });
162 // Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
163 // sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
164 // signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
165 if (sizeof(res) == sizeof(cmd)) {
166 foreach(string str : cmd) {
167 Tcmd[str] = time();
168 }
169 return 1;
170 }
171 return 0;
172}
173
174// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
175// zugelassen wird. Anforderungen:
176// 1) <cmd> muss Teilstring von <check> sein
177// 2) <cmd> muss am Anfang von <check> stehen
178// 3) <cmd> darf nicht laenger sein als <check>
179// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
180// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
181// cmd = "statistiker" oder cmd = "tist"
182// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
183private int IsValidChannelCommand(string cmd, string check) {
184 // Syntaxcheck (prueft Bedingungen 1 bis 3).
185 if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
186 string* cmd_to_check;
187 // Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
188 // Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
189 // geben.
190 if ( cmd == "lust" )
191 cmd_to_check = ({"lag", "statistik", "uptime"});
192 else
193 cmd_to_check = ({cmd});
194 // Prueft die Zeitsperre (Bedingung 4).
195 return (IsNotBlocked(cmd_to_check));
196 }
197 return 0;
198}
199
200#define CH_NAME 0
201#define CH_SENDER 1
202#define CH_MSG 2
203#define CH_MSG_TYPE 3
204// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
205// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
206// und dieses per Callout an send() uebergeben.
Zesstrab7720dc2020-08-11 22:14:18 +0200207// Argument: ({channel.name, object pl, string msg, int type})
Arathorn78c08372019-12-11 20:14:23 +0100208// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
209// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
210// was aber bei einer private oder protected Funktion nicht moeglich waere.
211public void ChannelMessage(<string|object|int>* msg)
212{
213 // Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
214 // aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
215 if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
216 msg[CH_NAME] != CMNAME || previous_object() != this_object())
Arathorn19459eb2019-11-30 00:45:51 +0100217 return;
MG Mud User88f12472016-06-24 23:31:02 +0200218
Arathorn78c08372019-12-11 20:14:23 +0100219 float* lag;
220 int max, rekord;
221 string ret;
Arathorn739a4fa2020-08-06 21:52:58 +0200222 string mesg = msg[CH_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200223
Arathorn78c08372019-12-11 20:14:23 +0100224 if (IsValidChannelCommand(mesg, "hilfe"))
MG Mud User88f12472016-06-24 23:31:02 +0200225 {
Arathorn78c08372019-12-11 20:14:23 +0100226 ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
227 "bann. Die Kommandos koennen abgekuerzt werden.";
Arathorn19459eb2019-11-30 00:45:51 +0100228 }
Arathorn78c08372019-12-11 20:14:23 +0100229 else if (IsValidChannelCommand(mesg, "lag"))
Arathorn19459eb2019-11-30 00:45:51 +0100230 {
MG Mud User88f12472016-06-24 23:31:02 +0200231 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
232 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
Arathorn19459eb2019-11-30 00:45:51 +0100233 "%.1f%%/20s, %.1f%%/2s",
234 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
Arathorn78c08372019-12-11 20:14:23 +0100235 // Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
236 // die Meldung in zwei Zeilen auf der Ebene erscheint.
Arathorn19459eb2019-11-30 00:45:51 +0100237 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200238 ret = query_load_average();
Arathorn19459eb2019-11-30 00:45:51 +0100239 }
Arathorn78c08372019-12-11 20:14:23 +0100240 else if (IsValidChannelCommand(mesg, "uptime"))
MG Mud User88f12472016-06-24 23:31:02 +0200241 {
Arathorn78c08372019-12-11 20:14:23 +0100242 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
Arathorn19459eb2019-11-30 00:45:51 +0100243 {
Arathorn78c08372019-12-11 20:14:23 +0100244 string unused;
245 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
246 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
247 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
248 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
249 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100250 }
251 else
252 {
Arathorn78c08372019-12-11 20:14:23 +0100253 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200254 }
Arathorn19459eb2019-11-30 00:45:51 +0100255 }
Arathorn78c08372019-12-11 20:14:23 +0100256 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200257 {
MG Mud User88f12472016-06-24 23:31:02 +0200258 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100259 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
260 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
261 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
262 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100263 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
264 }
Arathorn78c08372019-12-11 20:14:23 +0100265 // Ebenenaktion beginnt mit "bann"?
266 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200267 {
268 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100269
270 if (mesg == "bann")
271 {
272 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200273 {
Arathorn78c08372019-12-11 20:14:23 +0100274 ret = "Fuer folgende Spieler besteht ein Bann: ";
275 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
276 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
277 string* banlist = ({});
278 foreach(string plname, string* banned_cmds : channelB) {
279 banlist += ({ sprintf("%s [%s]",
280 capitalize(plname), implode(banned_cmds, ", "))});
281 }
282 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200283 }
284 else
285 {
Arathorn19459eb2019-11-30 00:45:51 +0100286 ret = "Zur Zeit ist kein Bann aktiv.";
287 }
288 }
289 else
290 {
Arathorn78c08372019-12-11 20:14:23 +0100291 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
292 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100293 {
294 pl = lower_case(pl);
295 cmd = lower_case(cmd);
296
297 if (member(CMDS, cmd) != -1)
298 {
Arathorn78c08372019-12-11 20:14:23 +0100299 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
300 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
301 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100302 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100303 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100304
Arathorn78c08372019-12-11 20:14:23 +0100305 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100306 channelB[pl] -= ({ cmd });
307 else
308 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100309
Arathorn78c08372019-12-11 20:14:23 +0100310 ret = "Fuer '" + capitalize(pl) + "' besteht " +
311 (sizeof(channelB[pl])
Zesstraf5f10122020-08-13 21:47:33 +0200312 ? "folgender Bann: " + CountUp(channelB[pl]) + "."
Arathorn78c08372019-12-11 20:14:23 +0100313 : "kein Bann mehr.");
314
315 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100316 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100317 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100318
Zesstra18f2ad62020-08-13 21:45:57 +0200319 save_me_soon = 1;
Arathorn19459eb2019-11-30 00:45:51 +0100320 }
321 else
322 {
323 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100324 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100325 }
326 }
327 else
328 {
Arathorn78c08372019-12-11 20:14:23 +0100329 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100330 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200331 }
332 }
333 }
Arathorn78c08372019-12-11 20:14:23 +0100334 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200335 {
MG Mud User88f12472016-06-24 23:31:02 +0200336 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100337 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
338 {
339 string unused;
340 sscanf(read_file("/etc/maxusers"), "%d %s", max, unused);
341 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, unused);
342 }
MG Mud User88f12472016-06-24 23:31:02 +0200343
Arathorn78c08372019-12-11 20:14:23 +0100344 int t = time() - last_reboot_time();
345
346 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
347 // die Funktionalitaet oefter benoetigt wird als nur hier.
348 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100349 if (t >= 86400)
350 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200351
Arathorn78c08372019-12-11 20:14:23 +0100352 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100353 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100354 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100355
Arathorn78c08372019-12-11 20:14:23 +0100356 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100357 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100358 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100359
360 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100361
MG Mud User88f12472016-06-24 23:31:02 +0200362 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100363 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100364 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100365 }
366 else
367 {
368 return;
369 }
MG Mud User88f12472016-06-24 23:31:02 +0200370
Arathorn78c08372019-12-11 20:14:23 +0100371 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
372 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
373 // nicht erfuellt sind.
374 if (stringp(ret) && sizeof(ret))
375 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200376}
377
378// setup() -- set up a channel and register it
379// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100380// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrad9ec04b2020-08-11 23:47:03 +0200381// adminflags, channelflags, description,supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100382private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200383{
Arathorn78c08372019-12-11 20:14:23 +0100384 string desc = "- Keine Beschreibung -";
Zesstra10341b82020-09-12 13:02:54 +0200385 object supervisor;
Zesstrad9ec04b2020-08-11 23:47:03 +0200386 int chflags;
Arathorn19459eb2019-11-30 00:45:51 +0100387
Arathorn78c08372019-12-11 20:14:23 +0100388 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
389 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200390
Zesstra2aeb6a82020-08-13 23:50:36 +0200391 // Wenn der channeld nur neugeladen wurde, aber das Mud nicht neugestartet,
Zesstra10341b82020-09-12 13:02:54 +0200392 // sind alle Ebenen noch da, weil sie im MEMORY liegen. D.h. ist die Ebene
393 // noch bekannt, muss nichts gemacht werden.
394 if (member(channels, lower_case(chinfo[0])))
395 return;
396
397 // Nur die Angabe des SV (Index 6) im initfile ist optional, alle Elemente
398 // davor muessen da sein.
399 if (sizeof(chinfo) < 6)
400 return;
401 // Bei genug Elementen schauen, ob der SV ladbar ist.
402 if (sizeof(chinfo) >= 7)
MG Mud User88f12472016-06-24 23:31:02 +0200403 {
Zesstra10341b82020-09-12 13:02:54 +0200404 if (stringp(chinfo[6]) && sizeof(chinfo[6]))
405 catch(supervisor = load_object(chinfo[6]); publish);
MG Mud User88f12472016-06-24 23:31:02 +0200406 }
Zesstra10341b82020-09-12 13:02:54 +0200407 // Aber falls kein SV angegeben wird oder das Objekt nicht ladbar war, wird
408 // ein Default-SV genutzt.
409 if (!supervisor)
410 supervisor = load_object(DEFAULTSV);
411
412 desc = chinfo[5];
413 chflags = to_int(chinfo[4]);
414
415 if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
Zesstra46c564e2020-09-11 21:52:29 +0200416 {
Zesstra10341b82020-09-12 13:02:54 +0200417 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
418 dtime(time()), chinfo[0], supervisor));
Zesstra46c564e2020-09-11 21:52:29 +0200419 }
MG Mud User88f12472016-06-24 23:31:02 +0200420 return;
421}
422
Arathorn78c08372019-12-11 20:14:23 +0100423private void initialize()
MG Mud User88f12472016-06-24 23:31:02 +0200424{
Arathorn78c08372019-12-11 20:14:23 +0100425 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200426#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100427 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200428#else
Arathorn78c08372019-12-11 20:14:23 +0100429 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200430#endif
Arathorn19459eb2019-11-30 00:45:51 +0100431
Arathorn78c08372019-12-11 20:14:23 +0100432 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200433 return;
Arathorn19459eb2019-11-30 00:45:51 +0100434
Arathorn78c08372019-12-11 20:14:23 +0100435 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
436 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
437 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
438 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
439 foreach(string ch : old_explode(ch_list, "\n"))
440 {
441 if (ch[0]=='#')
442 continue;
443 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
444 }
MG Mud User88f12472016-06-24 23:31:02 +0200445}
446
Arathorn78c08372019-12-11 20:14:23 +0100447// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200448protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200449{
Zesstra2aeb6a82020-08-13 23:50:36 +0200450 int do_complete_init;
451
MG Mud User88f12472016-06-24 23:31:02 +0200452 seteuid(getuid());
453 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100454
Zesstrab7720dc2020-08-11 22:14:18 +0200455 // Altes channelC aus Savefiles konvertieren...
456 if (widthof(channelC) == 1)
457 {
458 mapping new = m_allocate(sizeof(channelC), 2);
459 foreach(string chname, mixed arr: channelC)
460 {
461 struct channel_base_s ch = (<channel_base_s> name: arr[0],
462 desc: arr[1]);
463 // die anderen beiden Werte bleiben 0
464 m_add(new, chname, ch, arr[2]);
465 }
466 channelC = new;
467 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200468
Zesstra2aeb6a82020-08-13 23:50:36 +0200469 /* Die aktiven Ebenen und die Channel-History wird nicht nur lokal sondern
470 * auch noch im Memory gespeichert, dadurch bleibt sie auch ueber ein Reload
471 * erhalten.
472 Der folgende Code versucht, den Zeiger auf die alten Mappings aus dem
473 Memory zu holen. Falls das nicht moeglich ist, wird ein neuer erzeugt und
474 gegebenenfalls fuer spaeter im Memory abgelegt. */
MG Mud User88f12472016-06-24 23:31:02 +0200475 // Hab ich die noetigen Rechte im Memory?
Zesstra2aeb6a82020-08-13 23:50:36 +0200476 if (MEMORY->HaveRights())
Arathorn19459eb2019-11-30 00:45:51 +0100477 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200478 // channelH und channels laden
479 channels = ({mapping}) MEMORY->Load("Channels");
480 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
481 if (!mappingp(channels))
482 {
483 // Mapping erzeugen
484 channels = ([]);
485 // und Zeiger auf das Mapping in den Memory schreiben
486 MEMORY->Save("Channels", channels);
487 do_complete_init = 1;
488 }
489 // Und das gleiche fuer die History
490 channelH = ({mapping}) MEMORY->Load("History");
491 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
Arathorn78c08372019-12-11 20:14:23 +0100492 if (!mappingp(channelH))
493 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200494 // Mapping erzeugen
MG Mud User88f12472016-06-24 23:31:02 +0200495 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200496 // und Zeiger auf das Mapping in den Memory schreiben
497 MEMORY->Save("History", channelH);
498 // In diesem Fall muessen die Ebenenhistories auch erzeugt werden, falls
499 // es aktive Ebenen gibt.
500 foreach(string chname: channels)
501 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200502 }
Arathorn19459eb2019-11-30 00:45:51 +0100503 }
504 else
505 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200506 // Keine Rechte im Memory, dann liegt das nur lokal und ist bei
507 // remove/destruct weg.
MG Mud User88f12472016-06-24 23:31:02 +0200508 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200509 channels = ([]);
510 do_complete_init = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200511 }
512
513 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100514 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
515
Zesstra2aeb6a82020-08-13 23:50:36 +0200516 // Das muss auch laufen, wenn wir die alten Ebenen aus dem MEMORY bekommen
Zesstra10341b82020-09-12 13:02:54 +0200517 // haben, weil es ja neue Ebenen geben koennte, die dann erstellt werden
518 // muessen (verschwundete werden aber nicht aufgeraeumt!)
MG Mud User88f12472016-06-24 23:31:02 +0200519 initialize();
Zesstra10341b82020-09-12 13:02:54 +0200520 // <MasteR>-Ebene betreten, damit der channeld auf seine Kommandos auf
521 // dieser Ebene reagieren kann.
522 this_object()->join(CMNAME, this_object());
Arathorn78c08372019-12-11 20:14:23 +0100523
Zesstra2aeb6a82020-08-13 23:50:36 +0200524 // Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
525 // erledigen.
526 if (do_complete_init)
527 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200528 // Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
Zesstra10341b82020-09-12 13:02:54 +0200529 // leider Pech gehabt, falls das nicht das erste Laden nach Reboot war.
Zesstra2aeb6a82020-08-13 23:50:36 +0200530 users()->RegisterChannels();
531 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
532 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
533 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
534 // explizites call_other() auf this_object() gemacht, damit der
535 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
536 // einem externen.
537 this_object()->send(CMNAME, this_object(),
538 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
539 sizeof(channels),
540 CountUsers()));
541 }
542 else
543 {
544 this_object()->send(CMNAME, this_object(),
545 sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
546 sizeof(channels),
547 CountUsers()));
548 }
MG Mud User88f12472016-06-24 23:31:02 +0200549}
550
Arathorn78c08372019-12-11 20:14:23 +0100551varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200552{
Zesstra6ddbacb2020-08-13 21:43:28 +0200553 // Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
554 // zwischen 21.6h und 26.4h von jetzt.
555 set_next_reset(77760 + random(17280));
Zesstra26aaf1a2020-08-07 19:10:39 +0200556
Zesstra6ddbacb2020-08-13 21:43:28 +0200557 // inaktive Ebenen bereinigen
558 int timeout = INACTIVE_EXPIRE;
559 // Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
560 // inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
561 // bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
562 // Ebenen min. inaktiv bestehen.
563 while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
564 && timeout > MIN_INACTIVE_LIFETIME)
565 {
566 channelC = filter(channelC,
567 function int (string ch_name, mixed values)
Zesstra8f5102c2020-08-08 12:51:52 +0200568 {
Zesstra6ddbacb2020-08-13 21:43:28 +0200569 struct channel_base_s data = values[0];
570 int ts = values[1];
571 if (ts + timeout > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200572 return 1;
573 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
574 m_delete(channelH, ch_name);
575 return 0;
576 });
Zesstra6ddbacb2020-08-13 21:43:28 +0200577 // timeout halbieren und neu versuchen wenn noetig.
578 timeout /= 2;
579 }
580 // achja, speichern sollten wir uns ggf. auch noch.
MG Mud User88f12472016-06-24 23:31:02 +0200581 if (save_me_soon)
582 {
Arathorn19459eb2019-11-30 00:45:51 +0100583 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200584 save_object(CHANNEL_SAVE);
585 }
586}
587
Zesstra5856ada2020-08-13 21:52:47 +0200588varargs int remove(int silent)
589{
590 if (save_me_soon)
591 {
592 save_me_soon = 0;
593 save_object(CHANNEL_SAVE);
594 }
Zesstra90bf37e2020-09-28 21:32:33 +0200595 log_file("CHANNEL", sprintf("[%s] remove() durch %O gerufen. Speichern und "
596 "Ende.\n", dtime(time()),
597 this_interactive()||this_player()||previous_object()));
Zesstra5856ada2020-08-13 21:52:47 +0200598 destruct(this_object());
599 return 1;
600}
601
MG Mud User88f12472016-06-24 23:31:02 +0200602// name() - define the name of this object.
Zesstra10341b82020-09-12 13:02:54 +0200603public varargs string name(int casus,int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100604{
605 return CMNAME;
606}
607
Zesstra10341b82020-09-12 13:02:54 +0200608public varargs string Name(int casus, int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100609{
Zesstra10341b82020-09-12 13:02:54 +0200610 return capitalize(CMNAME);
Arathorn19459eb2019-11-30 00:45:51 +0100611}
MG Mud User88f12472016-06-24 23:31:02 +0200612
Zesstra28986e12020-08-09 12:44:26 +0200613// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200614// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200615private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200616{
617 if (IsChannelMember(ch, m))
618 return E_ALREADY_JOINED;
619
Zesstrab7720dc2020-08-11 22:14:18 +0200620 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200621 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200622}
623
Zesstra52d5f8a2020-08-12 00:39:15 +0200624private void remove_all_members(struct channel_s ch)
625{
626 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
627 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
628 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
629 string chname = lower_case(ch.name);
630 foreach(object listener : ch.members)
631 {
632 string* pl_chans = listener->QueryProp(P_CHANNELS);
633 if (pointerp(pl_chans))
634 {
635 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
636 }
637 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
638 if (pointerp(pl_chans))
639 {
640 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
641 }
642 }
643}
644
Zesstra6ddbacb2020-08-13 21:43:28 +0200645private void delete_channel(string chname, int force);
646
Zesstraf87cb772020-08-10 11:14:45 +0200647// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
648// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200649// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
650// anwesend sind.
651private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200652{
Zesstra6ddbacb2020-08-13 21:43:28 +0200653 // Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
654 if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
655 {
656 log_file("CHANNEL",
657 sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
658 "deaktiviert.\n", dtime(time()), chname));
659 this_object()->send(CMNAME, this_object(),
660 sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
661 "deaktiviert.\n", chname));
662 delete_channel(chname, force);
663 return;
664 }
Zesstrab7720dc2020-08-11 22:14:18 +0200665 chname = lower_case(chname);
666 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200667 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200668 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200669 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200670 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200671 if (sizeof(ch.members))
672 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200673 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
674 if (!force)
675 {
676 raise_error(
677 sprintf("Attempt to deactivate channel %s with listeners.\n",
678 ch.name));
679 }
680 else
681 {
682 remove_all_members(ch);
683 }
Zesstrab7720dc2020-08-11 22:14:18 +0200684 }
Zesstraf87cb772020-08-10 11:14:45 +0200685 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
686 // einloggt, der die Ebene abonniert hat.
Zesstraeaa18cc2020-09-28 21:28:21 +0200687#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
688 // Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
689 // richtige struct.
690 m_add(channelC, chname, to_struct(to_array(channels[chname])[0..3],
691 (<channel_base_s>)),
692 time());
693#else
Zesstrab7720dc2020-08-11 22:14:18 +0200694 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
695 time());
Zesstraeaa18cc2020-09-28 21:28:21 +0200696#endif
Zesstrab7720dc2020-08-11 22:14:18 +0200697 // aktive Ebene loeschen bzw. deaktivieren.
698 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200699 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
700 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
701 // channelC bereinigt wird.
702
703 stats["dispose"]++;
704 save_me_soon = 1;
705}
706
707// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200708// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
709// anwesend sind.
710private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200711{
Zesstrab7720dc2020-08-11 22:14:18 +0200712 chname = lower_case(chname);
713 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200714 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200715 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200716 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200717 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200718 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200719 {
720 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
721 if (!force)
722 {
723 raise_error(
724 sprintf("Attempt to delete channel %s with listeners.\n",
725 ch.name));
726 }
727 else
728 {
729 remove_all_members(ch);
730 }
731 }
Zesstraf87cb772020-08-10 11:14:45 +0200732 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200733 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200734 }
735 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstra6ddbacb2020-08-13 21:43:28 +0200736 m_delete(channelC, chname);
737 m_delete(channelH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200738 save_me_soon = 1;
739}
740
Zesstra5b7f2fc2020-08-10 02:09:13 +0200741// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
742// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200743private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200744{
745 if (!new_sv)
746 {
Zesstrab7720dc2020-08-11 22:14:18 +0200747 ch.members -= ({0});
748 if (sizeof(ch.members))
749 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200750 else
751 return 0; // kein neuer SV moeglich.
752 }
Zesstra9359fab2020-08-13 12:03:01 +0200753 // evtl. darf der supervisor aber nicht zu was anderes als dem creator
754 // wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
755 // Ebene ist.
756 if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
757 && new_sv != find_object(ch.creator))
758 || !IsChannelMember(ch, new_sv)
759 )
760 return 0;
761
Zesstrabf4f86d2020-08-12 00:56:17 +0200762 object old_sv = ch.supervisor;
763
Zesstrab7720dc2020-08-11 22:14:18 +0200764 ch.supervisor = new_sv;
Zesstra10341b82020-09-12 13:02:54 +0200765 ch.access_cl = symbol_function("ch_check_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200766
Zesstra5b7f2fc2020-08-10 02:09:13 +0200767 if (old_sv && new_sv
768 && !old_sv->QueryProp(P_INVIS)
769 && !new_sv->QueryProp(P_INVIS))
770 {
771 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
772 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
773 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
774 // explizites call_other() auf this_object() gemacht, damit der
775 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
776 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200777 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200778 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
779 MSG_EMOTE);
780 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200781 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200782 {
Zesstrab7720dc2020-08-11 22:14:18 +0200783 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200784 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
785 }
786 else if (new_sv && !new_sv->QueryProp(P_INVIS))
787 {
Zesstrab7720dc2020-08-11 22:14:18 +0200788 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200789 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
790 }
791 return 1;
792}
793
Zesstra56692c72020-08-09 13:03:10 +0200794// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
795// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200796// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
797// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200798// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200799private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200800{
Zesstra9359fab2020-08-13 12:03:01 +0200801 // Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
802 if (!ch.supervisor)
MG Mud User88f12472016-06-24 23:31:02 +0200803 {
Zesstra9359fab2020-08-13 12:03:01 +0200804 // Wenn der Wechsel des SV verboten ist, wird versucht, den
805 // urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
806 // machen.
807 if (ch.flags & CHF_FIXED_SUPERVISOR)
MG Mud User88f12472016-06-24 23:31:02 +0200808 {
Zesstra9359fab2020-08-13 12:03:01 +0200809 object sv;
810 string err=catch(sv=load_object(ch.creator);publish);
Arathorn78c08372019-12-11 20:14:23 +0100811 if (!err)
812 {
Zesstra9359fab2020-08-13 12:03:01 +0200813 // Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
814 // und kann dann wieder SV werden.
815 add_member(ch, sv);
816 if (!change_sv_object(ch, sv))
817 {
818 // ich wuesste nicht, was in change_sv_object in diesem Fall
819 // schiefgehen kann, daher einfach ein raise_error.
820 raise_error(sprintf("Supervisor von Channel %s konnte nicht "
821 "reaktiviert werden: %O\n",ch.name,sv));
822 }
Arathorn78c08372019-12-11 20:14:23 +0100823 }
Zesstra9359fab2020-08-13 12:03:01 +0200824 // wenns nicht geklappt hat, wird die Ebene deaktiviert.
Arathorn78c08372019-12-11 20:14:23 +0100825 else
826 {
Zesstra9359fab2020-08-13 12:03:01 +0200827 // Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
828 // urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
829 // solange der das nicht tut, ist weder die History zugaenglich, noch
830 // kann jemand sonst was damit machen. Wenn die inaktive Ebene
831 // irgendwann inkl. History expired wird, kann jemand anderes dann
832 // den Namen wieder verwenden und ein komplett neue Ebene erstellen.
833 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200834 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200835 sprintf("[%s] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200836 dtime(time()), ch.name, ch.supervisor, err));
Arathorn78c08372019-12-11 20:14:23 +0100837 return 0;
838 }
MG Mud User88f12472016-06-24 23:31:02 +0200839 }
Zesstra9359fab2020-08-13 12:03:01 +0200840 // Der normalfall ist aber, dass wir einfach einen supervisor aus dem
841 // Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
842 // change_sv_object().
843 else
Zesstra56692c72020-08-09 13:03:10 +0200844 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200845 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200846 {
Zesstra9359fab2020-08-13 12:03:01 +0200847 // wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
848 // Zuhoerer.
849 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200850 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200851 sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200852 dtime(time()), ch.name));
Zesstra5770ba62020-08-10 10:19:23 +0200853 return 0;
854 }
Zesstra56692c72020-08-09 13:03:10 +0200855 }
MG Mud User88f12472016-06-24 23:31:02 +0200856 }
Zesstra78310012020-08-09 12:21:48 +0200857 return 1;
858}
859
860// access() - check access by looking for the right argument types and
861// calling access closures respectively
862// SEE: new, join, leave, send, list, users
Zesstra78310012020-08-09 12:21:48 +0200863// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
864// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
865// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
866// als Erfolg und alles ueber >2 als privilegiert.)
Zesstra752ae7d2020-08-16 22:46:04 +0200867varargs private int access(struct channel_s ch, object user, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200868 string txt)
869{
Zesstra10341b82020-09-12 13:02:54 +0200870 if (!ch || !user)
Zesstra78310012020-08-09 12:21:48 +0200871 return 0;
872
Zesstra46c564e2020-09-11 21:52:29 +0200873 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
Zesstrafbfe6362020-08-09 13:30:21 +0200874 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra46c564e2020-09-11 21:52:29 +0200875 // Ausserdem duerfen sie auch alles andere machen unter Umgehung aller
876 // Supervisoren. (z.B. kann dieses Objekt sogar Meldungen im Namen anderer
877 // Objekte faken)
878 // Die Pruefung erfolgt absichtlich vor assert_supervisor(), damit der
Zesstra10341b82020-09-12 13:02:54 +0200879 // CHANNELD auch in temporaeren SV-losen Zustaenden was machen kann.
Zesstra78310012020-08-09 12:21:48 +0200880 if ( !previous_object(1) || !extern_call() ||
881 previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200882 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200883 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200884
Zesstra46c564e2020-09-11 21:52:29 +0200885 // Objekte duerfen keine Meldungen im Namen anderer Objekte faken, d.h. der
Zesstra10341b82020-09-12 13:02:54 +0200886 // vermeintliche <user> muss auch der Aufrufer sein. Ausser darf auch sonst
887 // kein Objekt was fuer ein anderes Objekt duerfen, sonst kann jemand z.B.
888 // eine History abfragen indem einfach ein anderes Objekt uebergeben wird.
889 if (previous_object(1) != user)
Arathorn739a4fa2020-08-06 21:52:58 +0200890 return 0;
891
Zesstra752ae7d2020-08-16 22:46:04 +0200892 if (IsBanned(user, cmd))
Arathorn739a4fa2020-08-06 21:52:58 +0200893 return 0;
894
Zesstra56692c72020-08-09 13:03:10 +0200895 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
896 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200897 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200898 return 0;
899 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200900 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200901 return 1;
902
Zesstra6fe46cd2020-08-09 13:12:15 +0200903 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
Zesstra10341b82020-09-12 13:02:54 +0200904 // fuer EM+ aber nur, wenn es das Default-SV-Objekt ist, damit
Zesstra6fe46cd2020-08-09 13:12:15 +0200905 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
Zesstra10341b82020-09-12 13:02:54 +0200906 // Default-SV koennen aber auch EM+ Zugriff verweigern.
907 if (IS_ARCH(previous_object(1))
908 && ch.supervisor != find_object(DEFAULTSV))
Zesstra6fe46cd2020-08-09 13:12:15 +0200909 return 1;
910
Zesstra752ae7d2020-08-16 22:46:04 +0200911 return funcall(ch.access_cl, lower_case(ch.name), user, cmd, &txt);
MG Mud User88f12472016-06-24 23:31:02 +0200912}
913
Arathorn78c08372019-12-11 20:14:23 +0100914// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200915// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100916// die dynamisch aktualisierte Infos ausgibt.
Zesstra10341b82020-09-12 13:02:54 +0200917// Das Objekt <owner> sollte eine Funktion ch_check_access() definieren, die
Arathorn78c08372019-12-11 20:14:23 +0100918// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
919// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200920#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200921public varargs int new(string ch_name, object owner, string|closure desc,
922 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200923{
Arathorn78c08372019-12-11 20:14:23 +0100924 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
925 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
926 // fuer eine neue Ebene eintragen.)
927 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200928 return E_ACCESS_DENIED;
929
Arathorn78c08372019-12-11 20:14:23 +0100930 // Kein gescheiter Channelname angegeben.
931 if (!stringp(ch_name) || !sizeof(ch_name))
932 return E_ACCESS_DENIED;
933
934 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
935 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
936 return E_ACCESS_DENIED;
937
938 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
939 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
940 // seinen Objektnamen matcht.
941 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
942 return E_ACCESS_DENIED;
943
Zesstraf20e8ba2020-08-12 01:56:30 +0200944 // Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
945 // existiert.
946 struct channel_base_s cbase = channelC[lower_case(ch_name)];
Zesstrab7720dc2020-08-11 22:14:18 +0200947 struct channel_s ch;
Zesstraf20e8ba2020-08-12 01:56:30 +0200948 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100949 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200950 // Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
951 // neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
952 // CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
953 // damit niemand inaktive Ebenen und deren History auf diesem Wege
954 // uebernehmen kann, d.h. den Supervisor ersetzen kann.
955 if ((cbase.flags & CHF_FIXED_SUPERVISOR)
956 && object_name(owner) != cbase.creator)
Arathorn19459eb2019-11-30 00:45:51 +0100957 return E_ACCESS_DENIED;
Zesstraf20e8ba2020-08-12 01:56:30 +0200958 // Alte Daten der Ebene uebernehmen
Zesstraeaa18cc2020-09-28 21:28:21 +0200959#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
960 // Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
961 // richtige struct.
962 ch = to_struct(to_array(cbase), (<channel_s>));
963#else
Zesstraf20e8ba2020-08-12 01:56:30 +0200964 ch = to_struct(cbase, (<channel_s>));
Zesstraeaa18cc2020-09-28 21:28:21 +0200965#endif
Zesstraf20e8ba2020-08-12 01:56:30 +0200966 // Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
967 if (desc)
968 ch.desc = desc;
969 // creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
970 // die Schreibweise (Gross-/Kleinschreibung) des Namens aus
971 // Konsistenzgruenden bei.
MG Mud User88f12472016-06-24 23:31:02 +0200972 }
Arathorn19459eb2019-11-30 00:45:51 +0100973 else
974 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200975 // Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
976 // Fehler...
977 if (!desc)
978 return E_ACCESS_DENIED;
979 // prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
980 // erzeugt.
Zesstrad9ec04b2020-08-11 23:47:03 +0200981 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
982 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +0100983 }
MG Mud User88f12472016-06-24 23:31:02 +0200984
Zesstrab7720dc2020-08-11 22:14:18 +0200985 ch_name = lower_case(ch_name);
986
987 ch.members = ({ owner });
Zesstra9359fab2020-08-13 12:03:01 +0200988 ch.supervisor = owner;
Zesstra10341b82020-09-12 13:02:54 +0200989 // ch_check_access() dient der Zugriffskontrolle und entscheidet, ob die
Zesstrad9ec04b2020-08-11 23:47:03 +0200990 // Nachricht gesendet werden darf oder nicht.
Zesstra10341b82020-09-12 13:02:54 +0200991 ch.access_cl = symbol_function("ch_check_access", owner);
Zesstrab7720dc2020-08-11 22:14:18 +0200992
993 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +0200994
Arathorn78c08372019-12-11 20:14:23 +0100995 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
996 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +0200997 if (!pointerp(channelH[ch_name]))
998 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200999
Zesstraf20e8ba2020-08-12 01:56:30 +02001000 // Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
1001 // jetzt auch weg.
1002 if (cbase)
1003 m_delete(channelC, ch_name);
1004
Arathorn78c08372019-12-11 20:14:23 +01001005 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
1006 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +02001007 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +02001008 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +01001009
Arathorn78c08372019-12-11 20:14:23 +01001010 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
1011 if (!owner->QueryProp(P_INVIS))
1012 {
1013 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1014 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1015 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1016 // explizites call_other() auf this_object() gemacht, damit der
1017 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1018 // einem externen.
1019 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +02001020 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +01001021 }
Arathorn19459eb2019-11-30 00:45:51 +01001022
MG Mud User88f12472016-06-24 23:31:02 +02001023 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +01001024 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001025}
1026
Arathorn78c08372019-12-11 20:14:23 +01001027// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
1028// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
1029// zweimal beitreten.)
Zesstra165157f2020-08-16 22:47:36 +02001030public int join(string chname, object joining)
MG Mud User88f12472016-06-24 23:31:02 +02001031{
Zesstrab7720dc2020-08-11 22:14:18 +02001032 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001033 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1034 zu erzeugen, weil access() mit extern_call() und previous_object()
1035 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1036 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001037 if (!funcall(#'access, ch, joining, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +01001038 return E_ACCESS_DENIED;
Zesstra10341b82020-09-12 13:02:54 +02001039 //TODO: Sollte der creator das Recht auf join haben, auch wenn der aktuelle
1040 //SV es verweigert? (s.u.)
Zesstra165157f2020-08-16 22:47:36 +02001041 int res = add_member(ch, joining);
Zesstrafb350dc2020-08-12 00:49:31 +02001042 if (res != 1)
1043 return res;
1044
Zesstra46c564e2020-09-11 21:52:29 +02001045 // Wenn <joining> der urspruengliche Ersteller der Ebene und kein
1046 // Spieler ist, wird es automatisch wieder zum Supervisor.
Zesstra165157f2020-08-16 22:47:36 +02001047 if (!query_once_interactive(joining)
1048 && object_name(joining) == ch.creator)
1049 change_sv_object(ch, joining);
Zesstra2b7ed1a2020-08-12 00:58:33 +02001050
Zesstrafb350dc2020-08-12 00:49:31 +02001051 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001052}
1053
Arathorn78c08372019-12-11 20:14:23 +01001054// Objekt <pl> verlaesst Ebene <ch>.
1055// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
1056// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
Zesstra10341b82020-09-12 13:02:54 +02001057// Dies ist in ch_check_access() so geregelt, allerdings koennte dem Objekt
Arathorn78c08372019-12-11 20:14:23 +01001058// <pl> das Verlassen auf Grund eines Banns verboten sein.
Zesstra10341b82020-09-12 13:02:54 +02001059// Wenn kein Zuhoerer mehr auf der Ebene ist, loest sie sich auf.
Zesstra165157f2020-08-16 22:47:36 +02001060public int leave(string chname, object leaving)
MG Mud User88f12472016-06-24 23:31:02 +02001061{
Zesstrab7720dc2020-08-11 22:14:18 +02001062 struct channel_s ch = channels[lower_case(chname)];
Zesstra0b4a5652020-09-23 00:40:37 +02001063 // Nicht-existierenden Ebenen soll das Spielerobjekt austragen, also tun wir
1064 // so, als sei das erfolgreich gewesen.
1065 if (!ch)
1066 return 0;
Zesstra877cb0a2020-08-10 02:10:21 +02001067
Zesstrab7720dc2020-08-11 22:14:18 +02001068 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +02001069
Zesstra165157f2020-08-16 22:47:36 +02001070 if (!IsChannelMember(ch, leaving))
Zesstra877cb0a2020-08-10 02:10:21 +02001071 return E_NOT_MEMBER;
1072
Arathorn739a4fa2020-08-06 21:52:58 +02001073 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1074 zu erzeugen, weil access() mit extern_call() und previous_object()
1075 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1076 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001077 if (!funcall(#'access, ch, leaving, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +01001078 return E_ACCESS_DENIED;
1079
Zesstrab7720dc2020-08-11 22:14:18 +02001080 // Dann mal den Zuhoerer raus.
Zesstra165157f2020-08-16 22:47:36 +02001081 ch.members -= ({leaving});
Zesstrae6d33852020-08-09 14:37:53 +02001082
Zesstra5b7f2fc2020-08-10 02:09:13 +02001083 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
1084 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +02001085 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +02001086 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001087 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1088 // diese verlassen hat. change_sv_object() waehlt per Default den
1089 // aeltesten Zuhoerer.
Zesstra165157f2020-08-16 22:47:36 +02001090 if (leaving == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001091 {
Zesstrabf4f86d2020-08-12 00:56:17 +02001092 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +01001093 }
MG Mud User88f12472016-06-24 23:31:02 +02001094 }
Zesstra137ea1c2020-08-10 02:15:20 +02001095 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1096 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1097 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1098 else
MG Mud User88f12472016-06-24 23:31:02 +02001099 {
Arathorn78c08372019-12-11 20:14:23 +01001100 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001101 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1102 // wird diese zerstoert, mit Meldung.
Zesstra165157f2020-08-16 22:47:36 +02001103 if (!leaving->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001104 {
1105 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1106 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1107 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1108 // explizites call_other() auf this_object() gemacht, damit der
1109 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1110 // einem externen.
Zesstra165157f2020-08-16 22:47:36 +02001111 this_object()->send(CMNAME, leaving,
Arathorn78c08372019-12-11 20:14:23 +01001112 "verlaesst als "+
Zesstra165157f2020-08-16 22:47:36 +02001113 (leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001114 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001115 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1116 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001117 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +02001118 }
Arathorn19459eb2019-11-30 00:45:51 +01001119 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001120}
1121
Arathorn78c08372019-12-11 20:14:23 +01001122// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1123// sofern <pl> dort senden darf.
Zesstra165157f2020-08-16 22:47:36 +02001124public varargs int send(string chname, object sender, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001125{
Zesstrab7720dc2020-08-11 22:14:18 +02001126 chname = lower_case(chname);
1127 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001128 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1129 zu erzeugen, weil access() mit extern_call() und previous_object()
1130 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1131 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001132 int a = funcall(#'access, ch, sender, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001133 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001134 return E_ACCESS_DENIED;
1135
Zesstra26aaf1a2020-08-07 19:10:39 +02001136 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
Zesstra10341b82020-09-12 13:02:54 +02001137 // Rueckgabewert von ch_check_access() entspricht, wenn die Aktion zugelassen
1138 // wird. access() liefert allerdings 2 fuer "privilegierte" Objekte (z.B.
Zesstra26aaf1a2020-08-07 19:10:39 +02001139 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1140 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Zesstra165157f2020-08-16 22:47:36 +02001141 if (a < 2 && !IsChannelMember(ch, sender))
Arathorn19459eb2019-11-30 00:45:51 +01001142 return E_NOT_MEMBER;
1143
1144 if (!msg || !stringp(msg) || !sizeof(msg))
1145 return E_EMPTY_MESSAGE;
1146
Arathorn78c08372019-12-11 20:14:23 +01001147 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1148 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1149 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1150 // Nachricht ebenfalls erhaelt.
1151 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1152 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1153 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1154 // erzeugt und der Channeld als Besitzer angegeben.
1155 // Die Aufrufkette ist dann wie folgt:
1156 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001157 (ch.members)->ChannelMessage(
Zesstra165157f2020-08-16 22:47:36 +02001158 ({ ch.name, sender, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001159
Zesstrab7720dc2020-08-11 22:14:18 +02001160 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1161 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001162
Zesstrab7720dc2020-08-11 22:14:18 +02001163 channelH[chname] +=
1164 ({ ({ ch.name,
Zesstra556c54d2020-08-16 22:50:10 +02001165 (sender->QueryProp(P_INVIS)
Zesstra165157f2020-08-16 22:47:36 +02001166 ? "/(" + capitalize(getuid(sender)) + ")$"
Arathorn19459eb2019-11-30 00:45:51 +01001167 : "")
Zesstra556c54d2020-08-16 22:50:10 +02001168 + (sender->Name(WER, 2) || "<Unbekannt>"),
Arathorn19459eb2019-11-30 00:45:51 +01001169 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1170 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001171 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001172}
1173
Arathorn78c08372019-12-11 20:14:23 +01001174// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1175// oder einen Integer-Fehlercode
1176public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001177{
Arathorn78c08372019-12-11 20:14:23 +01001178 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001179 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001180 {
Arathorn739a4fa2020-08-06 21:52:58 +02001181 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1182 zu erzeugen, weil access() mit extern_call() und previous_object()
1183 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1184 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001185 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001186 {
Zesstrab7720dc2020-08-11 22:14:18 +02001187 ch.members = filter(ch.members, #'objectp);
1188 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1189 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001190 }
1191 }
Arathorn19459eb2019-11-30 00:45:51 +01001192
1193 if (!sizeof(chs))
1194 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001195 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001196}
1197
Arathorn78c08372019-12-11 20:14:23 +01001198// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1199// Rueckgabewerte:
1200// - den gefundenen Namen als String
1201// - String-Array, wenn es mehrere Treffer gibt
1202// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001203public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001204{
Zesstrab7720dc2020-08-11 22:14:18 +02001205 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001206
Arathorn78c08372019-12-11 20:14:23 +01001207 // Suchstring <ch> muss Formatanforderung erfuellen;
1208 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1209 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1210 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1211 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001212 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001213 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001214
Arathorn78c08372019-12-11 20:14:23 +01001215 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1216 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1217 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001218 string* chs = filter(m_indices(channels), function int (string ch_n) {
Arathorn739a4fa2020-08-06 21:52:58 +02001219 /* funcall() auf Closure-Operator, um einen neuen Eintrag
1220 im Caller Stack zu erzeugen, weil access() mit
1221 extern_call() und previous_object() arbeitet und
1222 sichergestellt sein muss, dass das in jedem Fall das
1223 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001224 return ( stringp(regmatch(ch_n, "^"+chname)) &&
1225 funcall(#'access, channels[ch_n], pl, C_SEND) );
Arathorn78c08372019-12-11 20:14:23 +01001226 });
Arathorn19459eb2019-11-30 00:45:51 +01001227
Arathorn78c08372019-12-11 20:14:23 +01001228 int num_channels = sizeof(chs);
1229 if (num_channels > 1)
1230 return chs;
1231 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001232 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001233 else
1234 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001235}
1236
Arathorn78c08372019-12-11 20:14:23 +01001237// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001238public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001239{
Zesstrab7720dc2020-08-11 22:14:18 +02001240 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001241 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1242 zu erzeugen, weil access() mit extern_call() und previous_object()
1243 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1244 richtige ist. */
1245 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001246 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001247 else
Zesstrab7720dc2020-08-11 22:14:18 +02001248 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001249}
1250
Arathorn78c08372019-12-11 20:14:23 +01001251// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001252public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001253{
Zesstra66f801d2020-09-24 21:00:13 +02001254 if (!member(channels, chname))
1255 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001256 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001257 if (previous_object() != this_object())
1258 {
Zesstrab7720dc2020-08-11 22:14:18 +02001259 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001260 pl != this_player() || this_player() != this_interactive() ||
1261 this_interactive() != previous_object() ||
1262 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001263 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001264 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001265
1266 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001267
Arathorn19459eb2019-11-30 00:45:51 +01001268 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001269}
1270
Arathorn78c08372019-12-11 20:14:23 +01001271// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001272public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001273{
Zesstra66f801d2020-09-24 21:00:13 +02001274 if (!member(channelH, chname))
1275 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001276 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001277 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001278 if (previous_object() != this_object())
1279 {
Zesstrab7720dc2020-08-11 22:14:18 +02001280 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001281 this_player() != this_interactive() ||
1282 this_interactive() != previous_object() ||
1283 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001284 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001285 }
Zesstrab7720dc2020-08-11 22:14:18 +02001286 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001287 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1288 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001289 if (pointerp(channelH[chname]))
1290 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001291
1292 return 0;
1293}
Zesstra66f801d2020-09-24 21:00:13 +02001294
1295// Aendert den Ersteller/Besitzer der Ebene.
1296// Achtung: das ist nicht das gleiche wie der aktuelle Supervisor!
1297public int transfer_ownership(string chname, object new_owner)
1298{
1299 struct channel_s ch = channels[lower_case(chname)];
1300 if (!ch)
1301 return E_ACCESS_DENIED;
1302 // Nur der aktuelle Besitzer oder EM+ darf die Ebene verschenken
1303 if (ch.creator != object_name(previous_object())
1304 && !IS_ARCH(previous_object()))
1305 return E_ACCESS_DENIED;
1306
1307 ch.creator = object_name(new_owner);
1308 return 1;
1309}
1310
1311// Aendert den Flags der Ebene.
1312public int change_channel_flags(string chname, int newflags)
1313{
1314 struct channel_s ch = channels[lower_case(chname)];
1315 if (!ch)
1316 return E_ACCESS_DENIED;
1317 // Nur der aktuelle Besitzer, Supervisor oder EM+ darf die Flags aendern.
1318 if (ch.supervisor != previous_object()
1319 && ch.creator != object_name(previous_object())
1320 && !IS_ARCH(previous_object()))
1321 return E_ACCESS_DENIED;
1322
1323 ch.flags = newflags;
1324 return 1;
1325}
1326