blob: ca70ac1dda768eadc3f7246a6eb80b6c84637b14 [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);
Zesstra9f552892020-09-29 01:00:10 +0200116private int assert_supervisor(struct channel_s ch);
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
Zesstra7a5ff0a2020-09-29 00:55:46 +0200423private void create_default_channels()
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!)
Zesstra7a5ff0a2020-09-29 00:55:46 +0200519 create_default_channels();
Zesstra9f552892020-09-29 01:00:10 +0200520 // Es muss fuer alle existierenden Ebenen die access_cl neu erstellt
521 // werden, die durch Neuladen des channeld verloren gingen, auch wenn die
522 // Ebenen im MEMORY lagen.
523 foreach(string chname, struct channel_s ch : channels)
524 {
525 if (ch.access_cl || !assert_supervisor(ch))
526 continue;
527 ch.access_cl = symbol_function("ch_check_access", ch.supervisor);
528 }
529
Zesstra10341b82020-09-12 13:02:54 +0200530 // <MasteR>-Ebene betreten, damit der channeld auf seine Kommandos auf
531 // dieser Ebene reagieren kann.
532 this_object()->join(CMNAME, this_object());
Arathorn78c08372019-12-11 20:14:23 +0100533
Zesstra2aeb6a82020-08-13 23:50:36 +0200534 // Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
535 // erledigen.
536 if (do_complete_init)
537 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200538 // Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
Zesstra10341b82020-09-12 13:02:54 +0200539 // leider Pech gehabt, falls das nicht das erste Laden nach Reboot war.
Zesstra2aeb6a82020-08-13 23:50:36 +0200540 users()->RegisterChannels();
541 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
542 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
543 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
544 // explizites call_other() auf this_object() gemacht, damit der
545 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
546 // einem externen.
547 this_object()->send(CMNAME, this_object(),
548 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
549 sizeof(channels),
550 CountUsers()));
551 }
552 else
553 {
554 this_object()->send(CMNAME, this_object(),
555 sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
556 sizeof(channels),
557 CountUsers()));
558 }
MG Mud User88f12472016-06-24 23:31:02 +0200559}
560
Arathorn78c08372019-12-11 20:14:23 +0100561varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200562{
Zesstra6ddbacb2020-08-13 21:43:28 +0200563 // Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
564 // zwischen 21.6h und 26.4h von jetzt.
565 set_next_reset(77760 + random(17280));
Zesstra26aaf1a2020-08-07 19:10:39 +0200566
Zesstra6ddbacb2020-08-13 21:43:28 +0200567 // inaktive Ebenen bereinigen
568 int timeout = INACTIVE_EXPIRE;
569 // Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
570 // inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
571 // bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
572 // Ebenen min. inaktiv bestehen.
573 while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
574 && timeout > MIN_INACTIVE_LIFETIME)
575 {
576 channelC = filter(channelC,
577 function int (string ch_name, mixed values)
Zesstra8f5102c2020-08-08 12:51:52 +0200578 {
Zesstra6ddbacb2020-08-13 21:43:28 +0200579 struct channel_base_s data = values[0];
580 int ts = values[1];
581 if (ts + timeout > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200582 return 1;
583 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
584 m_delete(channelH, ch_name);
585 return 0;
586 });
Zesstra6ddbacb2020-08-13 21:43:28 +0200587 // timeout halbieren und neu versuchen wenn noetig.
588 timeout /= 2;
589 }
590 // achja, speichern sollten wir uns ggf. auch noch.
MG Mud User88f12472016-06-24 23:31:02 +0200591 if (save_me_soon)
592 {
Arathorn19459eb2019-11-30 00:45:51 +0100593 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200594 save_object(CHANNEL_SAVE);
595 }
596}
597
Zesstra5856ada2020-08-13 21:52:47 +0200598varargs int remove(int silent)
599{
600 if (save_me_soon)
601 {
602 save_me_soon = 0;
603 save_object(CHANNEL_SAVE);
604 }
Zesstra90bf37e2020-09-28 21:32:33 +0200605 log_file("CHANNEL", sprintf("[%s] remove() durch %O gerufen. Speichern und "
606 "Ende.\n", dtime(time()),
607 this_interactive()||this_player()||previous_object()));
Zesstra5856ada2020-08-13 21:52:47 +0200608 destruct(this_object());
609 return 1;
610}
611
MG Mud User88f12472016-06-24 23:31:02 +0200612// name() - define the name of this object.
Zesstra10341b82020-09-12 13:02:54 +0200613public varargs string name(int casus,int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100614{
615 return CMNAME;
616}
617
Zesstra10341b82020-09-12 13:02:54 +0200618public varargs string Name(int casus, int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100619{
Zesstra10341b82020-09-12 13:02:54 +0200620 return capitalize(CMNAME);
Arathorn19459eb2019-11-30 00:45:51 +0100621}
MG Mud User88f12472016-06-24 23:31:02 +0200622
Zesstra28986e12020-08-09 12:44:26 +0200623// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200624// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200625private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200626{
627 if (IsChannelMember(ch, m))
628 return E_ALREADY_JOINED;
629
Zesstrab7720dc2020-08-11 22:14:18 +0200630 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200631 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200632}
633
Zesstra52d5f8a2020-08-12 00:39:15 +0200634private void remove_all_members(struct channel_s ch)
635{
636 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
637 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
638 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
639 string chname = lower_case(ch.name);
640 foreach(object listener : ch.members)
641 {
642 string* pl_chans = listener->QueryProp(P_CHANNELS);
643 if (pointerp(pl_chans))
644 {
645 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
646 }
647 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
648 if (pointerp(pl_chans))
649 {
650 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
651 }
652 }
653}
654
Zesstra6ddbacb2020-08-13 21:43:28 +0200655private void delete_channel(string chname, int force);
656
Zesstraf87cb772020-08-10 11:14:45 +0200657// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
658// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200659// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
660// anwesend sind.
661private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200662{
Zesstra6ddbacb2020-08-13 21:43:28 +0200663 // Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
664 if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
665 {
666 log_file("CHANNEL",
667 sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
668 "deaktiviert.\n", dtime(time()), chname));
669 this_object()->send(CMNAME, this_object(),
670 sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
671 "deaktiviert.\n", chname));
672 delete_channel(chname, force);
673 return;
674 }
Zesstrab7720dc2020-08-11 22:14:18 +0200675 chname = lower_case(chname);
676 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200677 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200678 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200679 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200680 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200681 if (sizeof(ch.members))
682 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200683 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
684 if (!force)
685 {
686 raise_error(
687 sprintf("Attempt to deactivate channel %s with listeners.\n",
688 ch.name));
689 }
690 else
691 {
692 remove_all_members(ch);
693 }
Zesstrab7720dc2020-08-11 22:14:18 +0200694 }
Zesstraf87cb772020-08-10 11:14:45 +0200695 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
696 // einloggt, der die Ebene abonniert hat.
Zesstraeaa18cc2020-09-28 21:28:21 +0200697#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
698 // Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
699 // richtige struct.
700 m_add(channelC, chname, to_struct(to_array(channels[chname])[0..3],
701 (<channel_base_s>)),
702 time());
703#else
Zesstrab7720dc2020-08-11 22:14:18 +0200704 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
705 time());
Zesstraeaa18cc2020-09-28 21:28:21 +0200706#endif
Zesstrab7720dc2020-08-11 22:14:18 +0200707 // aktive Ebene loeschen bzw. deaktivieren.
708 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200709 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
710 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
711 // channelC bereinigt wird.
712
713 stats["dispose"]++;
714 save_me_soon = 1;
715}
716
717// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200718// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
719// anwesend sind.
720private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200721{
Zesstrab7720dc2020-08-11 22:14:18 +0200722 chname = lower_case(chname);
723 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200724 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200725 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200726 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200727 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200728 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200729 {
730 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
731 if (!force)
732 {
733 raise_error(
734 sprintf("Attempt to delete channel %s with listeners.\n",
735 ch.name));
736 }
737 else
738 {
739 remove_all_members(ch);
740 }
741 }
Zesstraf87cb772020-08-10 11:14:45 +0200742 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200743 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200744 }
745 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstra6ddbacb2020-08-13 21:43:28 +0200746 m_delete(channelC, chname);
747 m_delete(channelH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200748 save_me_soon = 1;
749}
750
Zesstra5b7f2fc2020-08-10 02:09:13 +0200751// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
752// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200753private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200754{
755 if (!new_sv)
756 {
Zesstrab7720dc2020-08-11 22:14:18 +0200757 ch.members -= ({0});
758 if (sizeof(ch.members))
759 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200760 else
761 return 0; // kein neuer SV moeglich.
762 }
Zesstra9359fab2020-08-13 12:03:01 +0200763 // evtl. darf der supervisor aber nicht zu was anderes als dem creator
764 // wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
765 // Ebene ist.
766 if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
767 && new_sv != find_object(ch.creator))
768 || !IsChannelMember(ch, new_sv)
769 )
770 return 0;
771
Zesstrabf4f86d2020-08-12 00:56:17 +0200772 object old_sv = ch.supervisor;
773
Zesstrab7720dc2020-08-11 22:14:18 +0200774 ch.supervisor = new_sv;
Zesstra10341b82020-09-12 13:02:54 +0200775 ch.access_cl = symbol_function("ch_check_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200776
Zesstra5b7f2fc2020-08-10 02:09:13 +0200777 if (old_sv && new_sv
778 && !old_sv->QueryProp(P_INVIS)
779 && !new_sv->QueryProp(P_INVIS))
780 {
781 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
782 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
783 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
784 // explizites call_other() auf this_object() gemacht, damit der
785 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
786 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200787 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200788 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
789 MSG_EMOTE);
790 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200791 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200792 {
Zesstrab7720dc2020-08-11 22:14:18 +0200793 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200794 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
795 }
796 else if (new_sv && !new_sv->QueryProp(P_INVIS))
797 {
Zesstrab7720dc2020-08-11 22:14:18 +0200798 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200799 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
800 }
801 return 1;
802}
803
Zesstra56692c72020-08-09 13:03:10 +0200804// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
805// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200806// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
807// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200808// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200809private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200810{
Zesstra9359fab2020-08-13 12:03:01 +0200811 // Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
812 if (!ch.supervisor)
MG Mud User88f12472016-06-24 23:31:02 +0200813 {
Zesstra9359fab2020-08-13 12:03:01 +0200814 // Wenn der Wechsel des SV verboten ist, wird versucht, den
815 // urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
816 // machen.
817 if (ch.flags & CHF_FIXED_SUPERVISOR)
MG Mud User88f12472016-06-24 23:31:02 +0200818 {
Zesstra9359fab2020-08-13 12:03:01 +0200819 object sv;
820 string err=catch(sv=load_object(ch.creator);publish);
Arathorn78c08372019-12-11 20:14:23 +0100821 if (!err)
822 {
Zesstra9359fab2020-08-13 12:03:01 +0200823 // Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
824 // und kann dann wieder SV werden.
825 add_member(ch, sv);
826 if (!change_sv_object(ch, sv))
827 {
828 // ich wuesste nicht, was in change_sv_object in diesem Fall
829 // schiefgehen kann, daher einfach ein raise_error.
830 raise_error(sprintf("Supervisor von Channel %s konnte nicht "
831 "reaktiviert werden: %O\n",ch.name,sv));
832 }
Arathorn78c08372019-12-11 20:14:23 +0100833 }
Zesstra9359fab2020-08-13 12:03:01 +0200834 // wenns nicht geklappt hat, wird die Ebene deaktiviert.
Arathorn78c08372019-12-11 20:14:23 +0100835 else
836 {
Zesstra9359fab2020-08-13 12:03:01 +0200837 // Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
838 // urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
839 // solange der das nicht tut, ist weder die History zugaenglich, noch
840 // kann jemand sonst was damit machen. Wenn die inaktive Ebene
841 // irgendwann inkl. History expired wird, kann jemand anderes dann
842 // den Namen wieder verwenden und ein komplett neue Ebene erstellen.
843 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200844 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200845 sprintf("[%s] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200846 dtime(time()), ch.name, ch.supervisor, err));
Arathorn78c08372019-12-11 20:14:23 +0100847 return 0;
848 }
MG Mud User88f12472016-06-24 23:31:02 +0200849 }
Zesstra9359fab2020-08-13 12:03:01 +0200850 // Der normalfall ist aber, dass wir einfach einen supervisor aus dem
851 // Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
852 // change_sv_object().
853 else
Zesstra56692c72020-08-09 13:03:10 +0200854 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200855 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200856 {
Zesstra9359fab2020-08-13 12:03:01 +0200857 // wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
858 // Zuhoerer.
859 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200860 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200861 sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200862 dtime(time()), ch.name));
Zesstra5770ba62020-08-10 10:19:23 +0200863 return 0;
864 }
Zesstra56692c72020-08-09 13:03:10 +0200865 }
MG Mud User88f12472016-06-24 23:31:02 +0200866 }
Zesstra78310012020-08-09 12:21:48 +0200867 return 1;
868}
869
870// access() - check access by looking for the right argument types and
871// calling access closures respectively
872// SEE: new, join, leave, send, list, users
Zesstra78310012020-08-09 12:21:48 +0200873// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
874// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
875// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
876// als Erfolg und alles ueber >2 als privilegiert.)
Zesstra752ae7d2020-08-16 22:46:04 +0200877varargs private int access(struct channel_s ch, object user, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200878 string txt)
879{
Zesstra10341b82020-09-12 13:02:54 +0200880 if (!ch || !user)
Zesstra78310012020-08-09 12:21:48 +0200881 return 0;
882
Zesstra46c564e2020-09-11 21:52:29 +0200883 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
Zesstrafbfe6362020-08-09 13:30:21 +0200884 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra46c564e2020-09-11 21:52:29 +0200885 // Ausserdem duerfen sie auch alles andere machen unter Umgehung aller
886 // Supervisoren. (z.B. kann dieses Objekt sogar Meldungen im Namen anderer
887 // Objekte faken)
888 // Die Pruefung erfolgt absichtlich vor assert_supervisor(), damit der
Zesstra10341b82020-09-12 13:02:54 +0200889 // CHANNELD auch in temporaeren SV-losen Zustaenden was machen kann.
Bugfixd01f81b2021-03-25 19:09:12 +0100890 if ( !previous_object(1) || previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200891 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200892 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200893
Zesstra46c564e2020-09-11 21:52:29 +0200894 // Objekte duerfen keine Meldungen im Namen anderer Objekte faken, d.h. der
Zesstra10341b82020-09-12 13:02:54 +0200895 // vermeintliche <user> muss auch der Aufrufer sein. Ausser darf auch sonst
896 // kein Objekt was fuer ein anderes Objekt duerfen, sonst kann jemand z.B.
897 // eine History abfragen indem einfach ein anderes Objekt uebergeben wird.
898 if (previous_object(1) != user)
Zesstra825133d2020-10-02 21:02:08 +0200899 {
900 // Kurzfristiger Workaround: Whitelist fuer bestimmte Objekte, die im
901 // Namen von Spielern senden mit dem Spieler als Absender, z.B. die Leiche
902 // beim verspotten.
903 switch(object_name(previous_object(1)))
904 {
905 case "/std/corpse":
906 break;
907 default:
908 return 0;
909 }
910 }
Zesstra752ae7d2020-08-16 22:46:04 +0200911 if (IsBanned(user, cmd))
Arathorn739a4fa2020-08-06 21:52:58 +0200912 return 0;
913
Zesstra56692c72020-08-09 13:03:10 +0200914 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
915 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200916 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200917 return 0;
918 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200919 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200920 return 1;
921
Zesstra6fe46cd2020-08-09 13:12:15 +0200922 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
Zesstra10341b82020-09-12 13:02:54 +0200923 // fuer EM+ aber nur, wenn es das Default-SV-Objekt ist, damit
Zesstra6fe46cd2020-08-09 13:12:15 +0200924 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
Zesstra10341b82020-09-12 13:02:54 +0200925 // Default-SV koennen aber auch EM+ Zugriff verweigern.
926 if (IS_ARCH(previous_object(1))
927 && ch.supervisor != find_object(DEFAULTSV))
Zesstra6fe46cd2020-08-09 13:12:15 +0200928 return 1;
929
Zesstrac5e98282020-10-02 21:54:57 +0200930 return funcall(ch.access_cl, lower_case(ch.name), user, cmd, txt);
MG Mud User88f12472016-06-24 23:31:02 +0200931}
932
Arathorn78c08372019-12-11 20:14:23 +0100933// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200934// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100935// die dynamisch aktualisierte Infos ausgibt.
Zesstra10341b82020-09-12 13:02:54 +0200936// Das Objekt <owner> sollte eine Funktion ch_check_access() definieren, die
Arathorn78c08372019-12-11 20:14:23 +0100937// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
938// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200939#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200940public varargs int new(string ch_name, object owner, string|closure desc,
941 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200942{
Arathorn78c08372019-12-11 20:14:23 +0100943 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
944 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
945 // fuer eine neue Ebene eintragen.)
946 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200947 return E_ACCESS_DENIED;
948
Arathorn78c08372019-12-11 20:14:23 +0100949 // Kein gescheiter Channelname angegeben.
950 if (!stringp(ch_name) || !sizeof(ch_name))
951 return E_ACCESS_DENIED;
952
953 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
954 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
955 return E_ACCESS_DENIED;
956
957 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
958 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
959 // seinen Objektnamen matcht.
960 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
961 return E_ACCESS_DENIED;
962
Zesstraf20e8ba2020-08-12 01:56:30 +0200963 // Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
964 // existiert.
965 struct channel_base_s cbase = channelC[lower_case(ch_name)];
Zesstrab7720dc2020-08-11 22:14:18 +0200966 struct channel_s ch;
Zesstraf20e8ba2020-08-12 01:56:30 +0200967 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100968 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200969 // Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
970 // neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
971 // CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
972 // damit niemand inaktive Ebenen und deren History auf diesem Wege
973 // uebernehmen kann, d.h. den Supervisor ersetzen kann.
974 if ((cbase.flags & CHF_FIXED_SUPERVISOR)
975 && object_name(owner) != cbase.creator)
Arathorn19459eb2019-11-30 00:45:51 +0100976 return E_ACCESS_DENIED;
Zesstraf20e8ba2020-08-12 01:56:30 +0200977 // Alte Daten der Ebene uebernehmen
Zesstraeaa18cc2020-09-28 21:28:21 +0200978#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
979 // Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
980 // richtige struct.
981 ch = to_struct(to_array(cbase), (<channel_s>));
982#else
Zesstraf20e8ba2020-08-12 01:56:30 +0200983 ch = to_struct(cbase, (<channel_s>));
Zesstraeaa18cc2020-09-28 21:28:21 +0200984#endif
Zesstraf20e8ba2020-08-12 01:56:30 +0200985 // Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
986 if (desc)
987 ch.desc = desc;
988 // creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
989 // die Schreibweise (Gross-/Kleinschreibung) des Namens aus
990 // Konsistenzgruenden bei.
MG Mud User88f12472016-06-24 23:31:02 +0200991 }
Arathorn19459eb2019-11-30 00:45:51 +0100992 else
993 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200994 // Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
995 // Fehler...
996 if (!desc)
997 return E_ACCESS_DENIED;
998 // prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
999 // erzeugt.
Zesstrad9ec04b2020-08-11 23:47:03 +02001000 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
1001 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +01001002 }
MG Mud User88f12472016-06-24 23:31:02 +02001003
Zesstrab7720dc2020-08-11 22:14:18 +02001004 ch_name = lower_case(ch_name);
1005
1006 ch.members = ({ owner });
Zesstra9359fab2020-08-13 12:03:01 +02001007 ch.supervisor = owner;
Zesstra10341b82020-09-12 13:02:54 +02001008 // ch_check_access() dient der Zugriffskontrolle und entscheidet, ob die
Zesstrad9ec04b2020-08-11 23:47:03 +02001009 // Nachricht gesendet werden darf oder nicht.
Zesstra10341b82020-09-12 13:02:54 +02001010 ch.access_cl = symbol_function("ch_check_access", owner);
Zesstrab7720dc2020-08-11 22:14:18 +02001011
1012 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +02001013
Arathorn78c08372019-12-11 20:14:23 +01001014 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
1015 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +02001016 if (!pointerp(channelH[ch_name]))
1017 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001018
Zesstraf20e8ba2020-08-12 01:56:30 +02001019 // Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
1020 // jetzt auch weg.
1021 if (cbase)
1022 m_delete(channelC, ch_name);
1023
Arathorn78c08372019-12-11 20:14:23 +01001024 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
1025 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +02001026 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +02001027 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +01001028
Arathorn78c08372019-12-11 20:14:23 +01001029 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
1030 if (!owner->QueryProp(P_INVIS))
1031 {
1032 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1033 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1034 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1035 // explizites call_other() auf this_object() gemacht, damit der
1036 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1037 // einem externen.
1038 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +02001039 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +01001040 }
Arathorn19459eb2019-11-30 00:45:51 +01001041
MG Mud User88f12472016-06-24 23:31:02 +02001042 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +01001043 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001044}
1045
Arathorn78c08372019-12-11 20:14:23 +01001046// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
1047// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
1048// zweimal beitreten.)
Zesstra165157f2020-08-16 22:47:36 +02001049public int join(string chname, object joining)
MG Mud User88f12472016-06-24 23:31:02 +02001050{
Zesstrab7720dc2020-08-11 22:14:18 +02001051 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001052 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1053 zu erzeugen, weil access() mit extern_call() und previous_object()
1054 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1055 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001056 if (!funcall(#'access, ch, joining, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +01001057 return E_ACCESS_DENIED;
Zesstra10341b82020-09-12 13:02:54 +02001058 //TODO: Sollte der creator das Recht auf join haben, auch wenn der aktuelle
1059 //SV es verweigert? (s.u.)
Zesstra165157f2020-08-16 22:47:36 +02001060 int res = add_member(ch, joining);
Zesstrafb350dc2020-08-12 00:49:31 +02001061 if (res != 1)
1062 return res;
1063
Zesstra46c564e2020-09-11 21:52:29 +02001064 // Wenn <joining> der urspruengliche Ersteller der Ebene und kein
1065 // Spieler ist, wird es automatisch wieder zum Supervisor.
Zesstra165157f2020-08-16 22:47:36 +02001066 if (!query_once_interactive(joining)
1067 && object_name(joining) == ch.creator)
1068 change_sv_object(ch, joining);
Zesstra2b7ed1a2020-08-12 00:58:33 +02001069
Zesstrafb350dc2020-08-12 00:49:31 +02001070 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001071}
1072
Arathorn78c08372019-12-11 20:14:23 +01001073// Objekt <pl> verlaesst Ebene <ch>.
1074// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
1075// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
Zesstra10341b82020-09-12 13:02:54 +02001076// Dies ist in ch_check_access() so geregelt, allerdings koennte dem Objekt
Arathorn78c08372019-12-11 20:14:23 +01001077// <pl> das Verlassen auf Grund eines Banns verboten sein.
Zesstra10341b82020-09-12 13:02:54 +02001078// Wenn kein Zuhoerer mehr auf der Ebene ist, loest sie sich auf.
Zesstra165157f2020-08-16 22:47:36 +02001079public int leave(string chname, object leaving)
MG Mud User88f12472016-06-24 23:31:02 +02001080{
Zesstrab7720dc2020-08-11 22:14:18 +02001081 struct channel_s ch = channels[lower_case(chname)];
Zesstra0b4a5652020-09-23 00:40:37 +02001082 // Nicht-existierenden Ebenen soll das Spielerobjekt austragen, also tun wir
1083 // so, als sei das erfolgreich gewesen.
1084 if (!ch)
1085 return 0;
Zesstra877cb0a2020-08-10 02:10:21 +02001086
Zesstrab7720dc2020-08-11 22:14:18 +02001087 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +02001088
Zesstra165157f2020-08-16 22:47:36 +02001089 if (!IsChannelMember(ch, leaving))
Zesstra877cb0a2020-08-10 02:10:21 +02001090 return E_NOT_MEMBER;
1091
Arathorn739a4fa2020-08-06 21:52:58 +02001092 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1093 zu erzeugen, weil access() mit extern_call() und previous_object()
1094 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1095 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001096 if (!funcall(#'access, ch, leaving, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +01001097 return E_ACCESS_DENIED;
1098
Zesstrab7720dc2020-08-11 22:14:18 +02001099 // Dann mal den Zuhoerer raus.
Zesstra165157f2020-08-16 22:47:36 +02001100 ch.members -= ({leaving});
Zesstrae6d33852020-08-09 14:37:53 +02001101
Zesstra5b7f2fc2020-08-10 02:09:13 +02001102 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
1103 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +02001104 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +02001105 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001106 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1107 // diese verlassen hat. change_sv_object() waehlt per Default den
1108 // aeltesten Zuhoerer.
Zesstra165157f2020-08-16 22:47:36 +02001109 if (leaving == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001110 {
Zesstrabf4f86d2020-08-12 00:56:17 +02001111 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +01001112 }
MG Mud User88f12472016-06-24 23:31:02 +02001113 }
Zesstra137ea1c2020-08-10 02:15:20 +02001114 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1115 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1116 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1117 else
MG Mud User88f12472016-06-24 23:31:02 +02001118 {
Arathorn78c08372019-12-11 20:14:23 +01001119 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001120 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1121 // wird diese zerstoert, mit Meldung.
Zesstra165157f2020-08-16 22:47:36 +02001122 if (!leaving->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001123 {
1124 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1125 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1126 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1127 // explizites call_other() auf this_object() gemacht, damit der
1128 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1129 // einem externen.
Zesstra165157f2020-08-16 22:47:36 +02001130 this_object()->send(CMNAME, leaving,
Arathorn78c08372019-12-11 20:14:23 +01001131 "verlaesst als "+
Zesstra165157f2020-08-16 22:47:36 +02001132 (leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001133 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001134 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1135 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001136 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +02001137 }
Arathorn19459eb2019-11-30 00:45:51 +01001138 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001139}
1140
Arathorn78c08372019-12-11 20:14:23 +01001141// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1142// sofern <pl> dort senden darf.
Zesstra165157f2020-08-16 22:47:36 +02001143public varargs int send(string chname, object sender, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001144{
Zesstrab7720dc2020-08-11 22:14:18 +02001145 chname = lower_case(chname);
1146 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001147 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1148 zu erzeugen, weil access() mit extern_call() und previous_object()
1149 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1150 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001151 int a = funcall(#'access, ch, sender, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001152 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001153 return E_ACCESS_DENIED;
1154
Zesstra26aaf1a2020-08-07 19:10:39 +02001155 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
Zesstra10341b82020-09-12 13:02:54 +02001156 // Rueckgabewert von ch_check_access() entspricht, wenn die Aktion zugelassen
1157 // wird. access() liefert allerdings 2 fuer "privilegierte" Objekte (z.B.
Zesstra26aaf1a2020-08-07 19:10:39 +02001158 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1159 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Zesstra165157f2020-08-16 22:47:36 +02001160 if (a < 2 && !IsChannelMember(ch, sender))
Arathorn19459eb2019-11-30 00:45:51 +01001161 return E_NOT_MEMBER;
1162
1163 if (!msg || !stringp(msg) || !sizeof(msg))
1164 return E_EMPTY_MESSAGE;
1165
Arathorn78c08372019-12-11 20:14:23 +01001166 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1167 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1168 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1169 // Nachricht ebenfalls erhaelt.
1170 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1171 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1172 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1173 // erzeugt und der Channeld als Besitzer angegeben.
1174 // Die Aufrufkette ist dann wie folgt:
1175 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001176 (ch.members)->ChannelMessage(
Zesstra165157f2020-08-16 22:47:36 +02001177 ({ ch.name, sender, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001178
Zesstrab7720dc2020-08-11 22:14:18 +02001179 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1180 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001181
Zesstrab7720dc2020-08-11 22:14:18 +02001182 channelH[chname] +=
1183 ({ ({ ch.name,
Zesstra556c54d2020-08-16 22:50:10 +02001184 (sender->QueryProp(P_INVIS)
Zesstra165157f2020-08-16 22:47:36 +02001185 ? "/(" + capitalize(getuid(sender)) + ")$"
Arathorn19459eb2019-11-30 00:45:51 +01001186 : "")
Zesstra556c54d2020-08-16 22:50:10 +02001187 + (sender->Name(WER, 2) || "<Unbekannt>"),
Arathorn19459eb2019-11-30 00:45:51 +01001188 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1189 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001190 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001191}
1192
Arathorn78c08372019-12-11 20:14:23 +01001193// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1194// oder einen Integer-Fehlercode
1195public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001196{
Arathorn78c08372019-12-11 20:14:23 +01001197 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001198 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001199 {
Arathorn739a4fa2020-08-06 21:52:58 +02001200 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1201 zu erzeugen, weil access() mit extern_call() und previous_object()
1202 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1203 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001204 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001205 {
Zesstrab7720dc2020-08-11 22:14:18 +02001206 ch.members = filter(ch.members, #'objectp);
1207 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1208 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001209 }
1210 }
Arathorn19459eb2019-11-30 00:45:51 +01001211
1212 if (!sizeof(chs))
1213 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001214 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001215}
1216
Arathorn78c08372019-12-11 20:14:23 +01001217// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1218// Rueckgabewerte:
1219// - den gefundenen Namen als String
1220// - String-Array, wenn es mehrere Treffer gibt
1221// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001222public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001223{
Zesstrab7720dc2020-08-11 22:14:18 +02001224 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001225
Arathorn78c08372019-12-11 20:14:23 +01001226 // Suchstring <ch> muss Formatanforderung erfuellen;
1227 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1228 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1229 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1230 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001231 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001232 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001233
Arathorn78c08372019-12-11 20:14:23 +01001234 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1235 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1236 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001237 string* chs = filter(m_indices(channels), function int (string ch_n) {
Bugfix6b977972021-03-25 10:22:56 +01001238 /* Normalerweise benoetigen wir ein funcall() auf
1239 Closure-Operator, um einen neuen Eintrag im Caller Stack
1240 zu erzeugen, weil access() mit extern_call() und
1241 previous_object() arbeitet und sichergestellt sein muss,
1242 dass das in jedem Fall das richtige ist.
1243 In diesem Fall gibt es schon durch filter() einen neuen
1244 Eintrag, daher muss access() direkt gerufen werden, sonst
1245 ist previous_object(1) == this_object(). */
Zesstrab7720dc2020-08-11 22:14:18 +02001246 return ( stringp(regmatch(ch_n, "^"+chname)) &&
Bugfix0ae4af32021-03-26 11:54:03 +01001247 access(channels[ch_n], pl, C_FIND) );
Arathorn78c08372019-12-11 20:14:23 +01001248 });
Arathorn19459eb2019-11-30 00:45:51 +01001249
Arathorn78c08372019-12-11 20:14:23 +01001250 int num_channels = sizeof(chs);
1251 if (num_channels > 1)
1252 return chs;
1253 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001254 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001255 else
1256 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001257}
1258
Arathorn78c08372019-12-11 20:14:23 +01001259// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001260public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001261{
Zesstrab7720dc2020-08-11 22:14:18 +02001262 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001263 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1264 zu erzeugen, weil access() mit extern_call() und previous_object()
1265 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1266 richtige ist. */
1267 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001268 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001269 else
Zesstrab7720dc2020-08-11 22:14:18 +02001270 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001271}
1272
Arathorn78c08372019-12-11 20:14:23 +01001273// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001274public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001275{
Zesstra66f801d2020-09-24 21:00:13 +02001276 if (!member(channels, chname))
1277 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001278 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001279 if (previous_object() != this_object())
1280 {
Zesstrab7720dc2020-08-11 22:14:18 +02001281 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001282 pl != this_player() || this_player() != this_interactive() ||
1283 this_interactive() != previous_object() ||
1284 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001285 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001286 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001287
1288 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001289
Arathorn19459eb2019-11-30 00:45:51 +01001290 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001291}
1292
Arathorn78c08372019-12-11 20:14:23 +01001293// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001294public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001295{
Zesstra66f801d2020-09-24 21:00:13 +02001296 if (!member(channelH, chname))
1297 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001298 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001299 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001300 if (previous_object() != this_object())
1301 {
Zesstrab7720dc2020-08-11 22:14:18 +02001302 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001303 this_player() != this_interactive() ||
1304 this_interactive() != previous_object() ||
1305 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001306 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001307 }
Zesstrab7720dc2020-08-11 22:14:18 +02001308 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001309 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1310 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001311 if (pointerp(channelH[chname]))
1312 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001313
1314 return 0;
1315}
Zesstra66f801d2020-09-24 21:00:13 +02001316
1317// Aendert den Ersteller/Besitzer der Ebene.
1318// Achtung: das ist nicht das gleiche wie der aktuelle Supervisor!
1319public int transfer_ownership(string chname, object new_owner)
1320{
1321 struct channel_s ch = channels[lower_case(chname)];
1322 if (!ch)
1323 return E_ACCESS_DENIED;
1324 // Nur der aktuelle Besitzer oder EM+ darf die Ebene verschenken
1325 if (ch.creator != object_name(previous_object())
1326 && !IS_ARCH(previous_object()))
1327 return E_ACCESS_DENIED;
1328
1329 ch.creator = object_name(new_owner);
1330 return 1;
1331}
1332
1333// Aendert den Flags der Ebene.
1334public int change_channel_flags(string chname, int newflags)
1335{
1336 struct channel_s ch = channels[lower_case(chname)];
1337 if (!ch)
1338 return E_ACCESS_DENIED;
1339 // Nur der aktuelle Besitzer, Supervisor oder EM+ darf die Flags aendern.
1340 if (ch.supervisor != previous_object()
1341 && ch.creator != object_name(previous_object())
1342 && !IS_ARCH(previous_object()))
1343 return E_ACCESS_DENIED;
1344
1345 ch.flags = newflags;
1346 return 1;
1347}
1348