blob: 8b531e33651e43d4657f8c5809a0a04661f9ad48 [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 {
Arathorn69748cf2021-03-28 12:47:55 +0200244 sscanf(read_file("/etc/maxusers"), "%d %~s", max);
245 sscanf(read_file("/etc/maxusers.ever"), "%d %~s", rekord);
Arathorn78c08372019-12-11 20:14:23 +0100246 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
247 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
248 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
Arathorn19459eb2019-11-30 00:45:51 +0100249 }
250 else
251 {
Arathorn78c08372019-12-11 20:14:23 +0100252 ret = "Diese Information liegt nicht vor.";
MG Mud User88f12472016-06-24 23:31:02 +0200253 }
Arathorn19459eb2019-11-30 00:45:51 +0100254 }
Arathorn78c08372019-12-11 20:14:23 +0100255 else if (IsValidChannelCommand(mesg, "statistik"))
MG Mud User88f12472016-06-24 23:31:02 +0200256 {
MG Mud User88f12472016-06-24 23:31:02 +0200257 ret = sprintf(
Arathorn78c08372019-12-11 20:14:23 +0100258 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
259 "Der %s wurde das letzte mal am %s von %s neu gestartet. "
260 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
261 sizeof(channels), CountUsers(), CMNAME,
Arathorn19459eb2019-11-30 00:45:51 +0100262 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
263 }
Arathorn78c08372019-12-11 20:14:23 +0100264 // Ebenenaktion beginnt mit "bann"?
265 else if (strstr(mesg, "bann")==0)
MG Mud User88f12472016-06-24 23:31:02 +0200266 {
267 string pl, cmd;
Arathorn19459eb2019-11-30 00:45:51 +0100268
269 if (mesg == "bann")
270 {
271 if (sizeof(channelB))
MG Mud User88f12472016-06-24 23:31:02 +0200272 {
Arathorn78c08372019-12-11 20:14:23 +0100273 ret = "Fuer folgende Spieler besteht ein Bann: ";
274 // Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
275 // CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
276 string* banlist = ({});
277 foreach(string plname, string* banned_cmds : channelB) {
278 banlist += ({ sprintf("%s [%s]",
279 capitalize(plname), implode(banned_cmds, ", "))});
280 }
281 ret = CountUp(banlist);
MG Mud User88f12472016-06-24 23:31:02 +0200282 }
283 else
284 {
Arathorn19459eb2019-11-30 00:45:51 +0100285 ret = "Zur Zeit ist kein Bann aktiv.";
286 }
287 }
288 else
289 {
Arathorn78c08372019-12-11 20:14:23 +0100290 if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
291 IS_DEPUTY(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100292 {
293 pl = lower_case(pl);
294 cmd = lower_case(cmd);
295
296 if (member(CMDS, cmd) != -1)
297 {
Arathorn78c08372019-12-11 20:14:23 +0100298 // Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
299 // ist der Eintrag kein Array, ist ohnehin was faul, dann wird
300 // ueberschrieben.
Arathorn19459eb2019-11-30 00:45:51 +0100301 if (!pointerp(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100302 m_add(channelB, pl, ({}));
Arathorn19459eb2019-11-30 00:45:51 +0100303
Arathorn78c08372019-12-11 20:14:23 +0100304 if (IsBanned(pl, cmd))
Arathorn19459eb2019-11-30 00:45:51 +0100305 channelB[pl] -= ({ cmd });
306 else
307 channelB[pl] += ({ cmd });
Arathorn19459eb2019-11-30 00:45:51 +0100308
Arathorn78c08372019-12-11 20:14:23 +0100309 ret = "Fuer '" + capitalize(pl) + "' besteht " +
310 (sizeof(channelB[pl])
Zesstraf5f10122020-08-13 21:47:33 +0200311 ? "folgender Bann: " + CountUp(channelB[pl]) + "."
Arathorn78c08372019-12-11 20:14:23 +0100312 : "kein Bann mehr.");
313
314 // Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
Arathorn19459eb2019-11-30 00:45:51 +0100315 if (!sizeof(channelB[pl]))
Arathorn78c08372019-12-11 20:14:23 +0100316 m_delete(channelB, pl);
Arathorn19459eb2019-11-30 00:45:51 +0100317
Zesstra18f2ad62020-08-13 21:45:57 +0200318 save_me_soon = 1;
Arathorn19459eb2019-11-30 00:45:51 +0100319 }
320 else
321 {
322 ret = "Das Kommando '" + cmd + "' ist unbekannt. "
Arathorn78c08372019-12-11 20:14:23 +0100323 "Erlaubte Kommandos: "+ CountUp(CMDS);
Arathorn19459eb2019-11-30 00:45:51 +0100324 }
325 }
326 else
327 {
Arathorn78c08372019-12-11 20:14:23 +0100328 if (IS_ARCH(msg[CH_SENDER]))
Arathorn19459eb2019-11-30 00:45:51 +0100329 ret = "Syntax: bann <name> <kommando>";
MG Mud User88f12472016-06-24 23:31:02 +0200330 }
331 }
332 }
Arathorn78c08372019-12-11 20:14:23 +0100333 else if (IsValidChannelCommand(mesg, "lust"))
MG Mud User88f12472016-06-24 23:31:02 +0200334 {
MG Mud User88f12472016-06-24 23:31:02 +0200335 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
Arathorn78c08372019-12-11 20:14:23 +0100336 if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
337 {
Arathorn69748cf2021-03-28 12:47:55 +0200338 sscanf(read_file("/etc/maxusers"), "%d %~2s", max);
339 sscanf(read_file("/etc/maxusers.ever"), "%d %~s", rekord);
Arathorn78c08372019-12-11 20:14:23 +0100340 }
MG Mud User88f12472016-06-24 23:31:02 +0200341
Arathorn78c08372019-12-11 20:14:23 +0100342 int t = time() - last_reboot_time();
343
344 // TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
345 // die Funktionalitaet oefter benoetigt wird als nur hier.
346 string up = "";
Arathorn19459eb2019-11-30 00:45:51 +0100347 if (t >= 86400)
348 up += sprintf("%dT", t / 86400);
MG Mud User88f12472016-06-24 23:31:02 +0200349
Arathorn78c08372019-12-11 20:14:23 +0100350 t %= 86400;
Arathorn19459eb2019-11-30 00:45:51 +0100351 if (t >= 3600)
Arathorn78c08372019-12-11 20:14:23 +0100352 up += sprintf("%dh", t / 3600);
Arathorn19459eb2019-11-30 00:45:51 +0100353
Arathorn78c08372019-12-11 20:14:23 +0100354 t %= 3600;
Arathorn19459eb2019-11-30 00:45:51 +0100355 if (t > 60)
Arathorn78c08372019-12-11 20:14:23 +0100356 up += sprintf("%dm", t / 60);
Arathorn19459eb2019-11-30 00:45:51 +0100357
358 up += sprintf("%ds", t % 60);
Arathorn78c08372019-12-11 20:14:23 +0100359
MG Mud User88f12472016-06-24 23:31:02 +0200360 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
Arathorn19459eb2019-11-30 00:45:51 +0100361 lag[1], lag[2], up, sizeof(users()), max, rekord,
Arathorn78c08372019-12-11 20:14:23 +0100362 sizeof(channels), CountUsers());
Arathorn19459eb2019-11-30 00:45:51 +0100363 }
364 else
365 {
366 return;
367 }
MG Mud User88f12472016-06-24 23:31:02 +0200368
Arathorn78c08372019-12-11 20:14:23 +0100369 // Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
370 // vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
371 // nicht erfuellt sind.
372 if (stringp(ret) && sizeof(ret))
373 call_out(#'send, 2, CMNAME, this_object(), ret);
MG Mud User88f12472016-06-24 23:31:02 +0200374}
375
376// setup() -- set up a channel and register it
377// arguments are stored in the following order:
Arathorn78c08372019-12-11 20:14:23 +0100378// string* chinfo = ({ channel_name, receive_level, send_level,
Zesstrad9ec04b2020-08-11 23:47:03 +0200379// adminflags, channelflags, description,supervisor })
Arathorn78c08372019-12-11 20:14:23 +0100380private void setup(string* chinfo)
MG Mud User88f12472016-06-24 23:31:02 +0200381{
Arathorn78c08372019-12-11 20:14:23 +0100382 string desc = "- Keine Beschreibung -";
Zesstra10341b82020-09-12 13:02:54 +0200383 object supervisor;
Zesstrad9ec04b2020-08-11 23:47:03 +0200384 int chflags;
Arathorn19459eb2019-11-30 00:45:51 +0100385
Arathorn78c08372019-12-11 20:14:23 +0100386 if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
387 chinfo[0] = chinfo[0][1..];
MG Mud User88f12472016-06-24 23:31:02 +0200388
Zesstra2aeb6a82020-08-13 23:50:36 +0200389 // Wenn der channeld nur neugeladen wurde, aber das Mud nicht neugestartet,
Zesstra10341b82020-09-12 13:02:54 +0200390 // sind alle Ebenen noch da, weil sie im MEMORY liegen. D.h. ist die Ebene
391 // noch bekannt, muss nichts gemacht werden.
392 if (member(channels, lower_case(chinfo[0])))
393 return;
394
395 // Nur die Angabe des SV (Index 6) im initfile ist optional, alle Elemente
396 // davor muessen da sein.
397 if (sizeof(chinfo) < 6)
398 return;
399 // Bei genug Elementen schauen, ob der SV ladbar ist.
400 if (sizeof(chinfo) >= 7)
MG Mud User88f12472016-06-24 23:31:02 +0200401 {
Zesstra10341b82020-09-12 13:02:54 +0200402 if (stringp(chinfo[6]) && sizeof(chinfo[6]))
403 catch(supervisor = load_object(chinfo[6]); publish);
MG Mud User88f12472016-06-24 23:31:02 +0200404 }
Zesstra10341b82020-09-12 13:02:54 +0200405 // Aber falls kein SV angegeben wird oder das Objekt nicht ladbar war, wird
406 // ein Default-SV genutzt.
407 if (!supervisor)
408 supervisor = load_object(DEFAULTSV);
409
410 desc = chinfo[5];
411 chflags = to_int(chinfo[4]);
412
413 if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
Zesstra46c564e2020-09-11 21:52:29 +0200414 {
Zesstra10341b82020-09-12 13:02:54 +0200415 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
416 dtime(time()), chinfo[0], supervisor));
Zesstra46c564e2020-09-11 21:52:29 +0200417 }
MG Mud User88f12472016-06-24 23:31:02 +0200418 return;
419}
420
Zesstra7a5ff0a2020-09-29 00:55:46 +0200421private void create_default_channels()
MG Mud User88f12472016-06-24 23:31:02 +0200422{
Arathorn78c08372019-12-11 20:14:23 +0100423 string ch_list;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200424#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
Arathorn78c08372019-12-11 20:14:23 +0100425 ch_list = read_file(object_name(this_object()) + ".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200426#else
Arathorn78c08372019-12-11 20:14:23 +0100427 ch_list = read_file(object_name(this_object()) + ".init.testmud");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200428#endif
Arathorn19459eb2019-11-30 00:45:51 +0100429
Arathorn78c08372019-12-11 20:14:23 +0100430 if (!stringp(ch_list))
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200431 return;
Arathorn19459eb2019-11-30 00:45:51 +0100432
Arathorn78c08372019-12-11 20:14:23 +0100433 // Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
434 // "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
435 // Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
436 // Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
437 foreach(string ch : old_explode(ch_list, "\n"))
438 {
439 if (ch[0]=='#')
440 continue;
441 setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
442 }
MG Mud User88f12472016-06-24 23:31:02 +0200443}
444
Arathorn78c08372019-12-11 20:14:23 +0100445// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200446protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200447{
Zesstra2aeb6a82020-08-13 23:50:36 +0200448 int do_complete_init;
449
MG Mud User88f12472016-06-24 23:31:02 +0200450 seteuid(getuid());
451 restore_object(CHANNEL_SAVE);
Arathorn19459eb2019-11-30 00:45:51 +0100452
Zesstrab7720dc2020-08-11 22:14:18 +0200453 // Altes channelC aus Savefiles konvertieren...
454 if (widthof(channelC) == 1)
455 {
456 mapping new = m_allocate(sizeof(channelC), 2);
457 foreach(string chname, mixed arr: channelC)
458 {
459 struct channel_base_s ch = (<channel_base_s> name: arr[0],
460 desc: arr[1]);
461 // die anderen beiden Werte bleiben 0
462 m_add(new, chname, ch, arr[2]);
463 }
464 channelC = new;
465 }
Zesstra26aaf1a2020-08-07 19:10:39 +0200466
Zesstra2aeb6a82020-08-13 23:50:36 +0200467 /* Die aktiven Ebenen und die Channel-History wird nicht nur lokal sondern
468 * auch noch im Memory gespeichert, dadurch bleibt sie auch ueber ein Reload
469 * erhalten.
470 Der folgende Code versucht, den Zeiger auf die alten Mappings aus dem
471 Memory zu holen. Falls das nicht moeglich ist, wird ein neuer erzeugt und
472 gegebenenfalls fuer spaeter im Memory abgelegt. */
MG Mud User88f12472016-06-24 23:31:02 +0200473 // Hab ich die noetigen Rechte im Memory?
Zesstra2aeb6a82020-08-13 23:50:36 +0200474 if (MEMORY->HaveRights())
Arathorn19459eb2019-11-30 00:45:51 +0100475 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200476 // channelH und channels laden
477 channels = ({mapping}) MEMORY->Load("Channels");
478 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
479 if (!mappingp(channels))
480 {
481 // Mapping erzeugen
482 channels = ([]);
483 // und Zeiger auf das Mapping in den Memory schreiben
484 MEMORY->Save("Channels", channels);
485 do_complete_init = 1;
486 }
487 // Und das gleiche fuer die History
488 channelH = ({mapping}) MEMORY->Load("History");
489 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
Arathorn78c08372019-12-11 20:14:23 +0100490 if (!mappingp(channelH))
491 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200492 // Mapping erzeugen
MG Mud User88f12472016-06-24 23:31:02 +0200493 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200494 // und Zeiger auf das Mapping in den Memory schreiben
495 MEMORY->Save("History", channelH);
496 // In diesem Fall muessen die Ebenenhistories auch erzeugt werden, falls
497 // es aktive Ebenen gibt.
498 foreach(string chname: channels)
499 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +0200500 }
Arathorn19459eb2019-11-30 00:45:51 +0100501 }
502 else
503 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200504 // Keine Rechte im Memory, dann liegt das nur lokal und ist bei
505 // remove/destruct weg.
MG Mud User88f12472016-06-24 23:31:02 +0200506 channelH = ([]);
Zesstra2aeb6a82020-08-13 23:50:36 +0200507 channels = ([]);
508 do_complete_init = 1;
MG Mud User88f12472016-06-24 23:31:02 +0200509 }
510
511 stats = (["time": time(),
Arathorn78c08372019-12-11 20:14:23 +0100512 "boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
513
Zesstra2aeb6a82020-08-13 23:50:36 +0200514 // Das muss auch laufen, wenn wir die alten Ebenen aus dem MEMORY bekommen
Zesstra10341b82020-09-12 13:02:54 +0200515 // haben, weil es ja neue Ebenen geben koennte, die dann erstellt werden
516 // muessen (verschwundete werden aber nicht aufgeraeumt!)
Zesstra7a5ff0a2020-09-29 00:55:46 +0200517 create_default_channels();
Zesstra9f552892020-09-29 01:00:10 +0200518 // Es muss fuer alle existierenden Ebenen die access_cl neu erstellt
519 // werden, die durch Neuladen des channeld verloren gingen, auch wenn die
520 // Ebenen im MEMORY lagen.
521 foreach(string chname, struct channel_s ch : channels)
522 {
523 if (ch.access_cl || !assert_supervisor(ch))
524 continue;
525 ch.access_cl = symbol_function("ch_check_access", ch.supervisor);
526 }
527
Zesstra10341b82020-09-12 13:02:54 +0200528 // <MasteR>-Ebene betreten, damit der channeld auf seine Kommandos auf
529 // dieser Ebene reagieren kann.
530 this_object()->join(CMNAME, this_object());
Arathorn78c08372019-12-11 20:14:23 +0100531
Zesstra2aeb6a82020-08-13 23:50:36 +0200532 // Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
533 // erledigen.
534 if (do_complete_init)
535 {
Zesstra2aeb6a82020-08-13 23:50:36 +0200536 // Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
Zesstra10341b82020-09-12 13:02:54 +0200537 // leider Pech gehabt, falls das nicht das erste Laden nach Reboot war.
Zesstra2aeb6a82020-08-13 23:50:36 +0200538 users()->RegisterChannels();
539 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
540 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
541 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
542 // explizites call_other() auf this_object() gemacht, damit der
543 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
544 // einem externen.
545 this_object()->send(CMNAME, this_object(),
546 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
547 sizeof(channels),
548 CountUsers()));
549 }
550 else
551 {
552 this_object()->send(CMNAME, this_object(),
553 sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
554 sizeof(channels),
555 CountUsers()));
556 }
MG Mud User88f12472016-06-24 23:31:02 +0200557}
558
Arathorn78c08372019-12-11 20:14:23 +0100559varargs void reset()
MG Mud User88f12472016-06-24 23:31:02 +0200560{
Zesstra6ddbacb2020-08-13 21:43:28 +0200561 // Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
562 // zwischen 21.6h und 26.4h von jetzt.
563 set_next_reset(77760 + random(17280));
Zesstra26aaf1a2020-08-07 19:10:39 +0200564
Zesstra6ddbacb2020-08-13 21:43:28 +0200565 // inaktive Ebenen bereinigen
566 int timeout = INACTIVE_EXPIRE;
567 // Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
568 // inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
569 // bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
570 // Ebenen min. inaktiv bestehen.
571 while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
572 && timeout > MIN_INACTIVE_LIFETIME)
573 {
574 channelC = filter(channelC,
575 function int (string ch_name, mixed values)
Zesstra8f5102c2020-08-08 12:51:52 +0200576 {
Zesstra6ddbacb2020-08-13 21:43:28 +0200577 int ts = values[1];
578 if (ts + timeout > time())
Zesstra8f5102c2020-08-08 12:51:52 +0200579 return 1;
580 // Ebenendaten koennen weg, inkl. History, die also auch loeschen
581 m_delete(channelH, ch_name);
582 return 0;
583 });
Zesstra6ddbacb2020-08-13 21:43:28 +0200584 // timeout halbieren und neu versuchen wenn noetig.
585 timeout /= 2;
586 }
587 // achja, speichern sollten wir uns ggf. auch noch.
MG Mud User88f12472016-06-24 23:31:02 +0200588 if (save_me_soon)
589 {
Arathorn19459eb2019-11-30 00:45:51 +0100590 save_me_soon = 0;
MG Mud User88f12472016-06-24 23:31:02 +0200591 save_object(CHANNEL_SAVE);
592 }
593}
594
Zesstra5856ada2020-08-13 21:52:47 +0200595varargs int remove(int silent)
596{
597 if (save_me_soon)
598 {
599 save_me_soon = 0;
600 save_object(CHANNEL_SAVE);
601 }
Zesstra90bf37e2020-09-28 21:32:33 +0200602 log_file("CHANNEL", sprintf("[%s] remove() durch %O gerufen. Speichern und "
603 "Ende.\n", dtime(time()),
604 this_interactive()||this_player()||previous_object()));
Zesstra5856ada2020-08-13 21:52:47 +0200605 destruct(this_object());
606 return 1;
607}
608
MG Mud User88f12472016-06-24 23:31:02 +0200609// name() - define the name of this object.
Zesstra10341b82020-09-12 13:02:54 +0200610public varargs string name(int casus,int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100611{
612 return CMNAME;
613}
614
Zesstra10341b82020-09-12 13:02:54 +0200615public varargs string Name(int casus, int demon)
Arathorn19459eb2019-11-30 00:45:51 +0100616{
Zesstra10341b82020-09-12 13:02:54 +0200617 return capitalize(CMNAME);
Arathorn19459eb2019-11-30 00:45:51 +0100618}
MG Mud User88f12472016-06-24 23:31:02 +0200619
Zesstra28986e12020-08-09 12:44:26 +0200620// Low-level function for adding members without access checks
Zesstrafb350dc2020-08-12 00:49:31 +0200621// return values < 0 are errors, success is 1.
Zesstrab7720dc2020-08-11 22:14:18 +0200622private int add_member(struct channel_s ch, object m)
Zesstra28986e12020-08-09 12:44:26 +0200623{
624 if (IsChannelMember(ch, m))
625 return E_ALREADY_JOINED;
626
Zesstrab7720dc2020-08-11 22:14:18 +0200627 ch.members += ({ m });
Zesstrafb350dc2020-08-12 00:49:31 +0200628 return 1;
Zesstra28986e12020-08-09 12:44:26 +0200629}
630
Zesstra52d5f8a2020-08-12 00:39:15 +0200631private void remove_all_members(struct channel_s ch)
632{
633 // Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
634 // aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
635 // als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
636 string chname = lower_case(ch.name);
637 foreach(object listener : ch.members)
638 {
639 string* pl_chans = listener->QueryProp(P_CHANNELS);
640 if (pointerp(pl_chans))
641 {
642 listener->SetProp(P_CHANNELS, pl_chans-({chname}));
643 }
644 pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
645 if (pointerp(pl_chans))
646 {
647 listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
648 }
649 }
650}
651
Zesstra6ddbacb2020-08-13 21:43:28 +0200652private void delete_channel(string chname, int force);
653
Zesstraf87cb772020-08-10 11:14:45 +0200654// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
655// History, so dass sie spaeter reaktiviert werden kann.
Zesstra52d5f8a2020-08-12 00:39:15 +0200656// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
657// anwesend sind.
658private void deactivate_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200659{
Zesstra6ddbacb2020-08-13 21:43:28 +0200660 // Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
661 if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
662 {
663 log_file("CHANNEL",
664 sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
665 "deaktiviert.\n", dtime(time()), chname));
666 this_object()->send(CMNAME, this_object(),
667 sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
668 "deaktiviert.\n", chname));
669 delete_channel(chname, force);
670 return;
671 }
Zesstrab7720dc2020-08-11 22:14:18 +0200672 chname = lower_case(chname);
673 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200674 // Deaktivieren kann man nur aktive Ebenen.
Zesstrab7720dc2020-08-11 22:14:18 +0200675 if (!structp(ch))
Zesstraf87cb772020-08-10 11:14:45 +0200676 return;
Zesstra52d5f8a2020-08-12 00:39:15 +0200677 // Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
Zesstrab7720dc2020-08-11 22:14:18 +0200678 if (sizeof(ch.members))
679 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200680 // ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
681 if (!force)
682 {
683 raise_error(
684 sprintf("Attempt to deactivate channel %s with listeners.\n",
685 ch.name));
686 }
687 else
688 {
689 remove_all_members(ch);
690 }
Zesstrab7720dc2020-08-11 22:14:18 +0200691 }
Zesstraf87cb772020-08-10 11:14:45 +0200692 // Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
693 // einloggt, der die Ebene abonniert hat.
Zesstraeaa18cc2020-09-28 21:28:21 +0200694#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
695 // Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
696 // richtige struct.
697 m_add(channelC, chname, to_struct(to_array(channels[chname])[0..3],
698 (<channel_base_s>)),
699 time());
700#else
Zesstrab7720dc2020-08-11 22:14:18 +0200701 m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
702 time());
Zesstraeaa18cc2020-09-28 21:28:21 +0200703#endif
Zesstrab7720dc2020-08-11 22:14:18 +0200704 // aktive Ebene loeschen bzw. deaktivieren.
705 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200706 // History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
707 // Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
708 // channelC bereinigt wird.
709
710 stats["dispose"]++;
711 save_me_soon = 1;
712}
713
714// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
Zesstra52d5f8a2020-08-12 00:39:15 +0200715// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
716// anwesend sind.
717private void delete_channel(string chname, int force)
Zesstraf87cb772020-08-10 11:14:45 +0200718{
Zesstrab7720dc2020-08-11 22:14:18 +0200719 chname = lower_case(chname);
720 struct channel_s ch = channels[chname];
Zesstra52d5f8a2020-08-12 00:39:15 +0200721 // Ist die Ebene noch aktiv?
Zesstrab7720dc2020-08-11 22:14:18 +0200722 if (ch)
Zesstraf87cb772020-08-10 11:14:45 +0200723 {
Zesstra52d5f8a2020-08-12 00:39:15 +0200724 // Und hat sie Zuhoerer?
Zesstrab7720dc2020-08-11 22:14:18 +0200725 if (sizeof(ch.members))
Zesstra52d5f8a2020-08-12 00:39:15 +0200726 {
727 // ohne <force> nur Ebenen ohne Zuhoerer loeschen.
728 if (!force)
729 {
730 raise_error(
731 sprintf("Attempt to delete channel %s with listeners.\n",
732 ch.name));
733 }
734 else
735 {
736 remove_all_members(ch);
737 }
738 }
Zesstraf87cb772020-08-10 11:14:45 +0200739 stats["dispose"]++;
Zesstrab7720dc2020-08-11 22:14:18 +0200740 m_delete(channels, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200741 }
742 // Ab hier das gleiche fuer aktive und inaktive Ebenen.
Zesstra6ddbacb2020-08-13 21:43:28 +0200743 m_delete(channelC, chname);
744 m_delete(channelH, chname);
Zesstraf87cb772020-08-10 11:14:45 +0200745 save_me_soon = 1;
746}
747
Zesstra5b7f2fc2020-08-10 02:09:13 +0200748// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
749// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
Zesstrabf4f86d2020-08-12 00:56:17 +0200750private int change_sv_object(struct channel_s ch, object new_sv)
Zesstra5b7f2fc2020-08-10 02:09:13 +0200751{
752 if (!new_sv)
753 {
Zesstrab7720dc2020-08-11 22:14:18 +0200754 ch.members -= ({0});
755 if (sizeof(ch.members))
756 new_sv = ch.members[0];
Zesstra5b7f2fc2020-08-10 02:09:13 +0200757 else
758 return 0; // kein neuer SV moeglich.
759 }
Zesstra9359fab2020-08-13 12:03:01 +0200760 // evtl. darf der supervisor aber nicht zu was anderes als dem creator
761 // wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
762 // Ebene ist.
763 if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
764 && new_sv != find_object(ch.creator))
765 || !IsChannelMember(ch, new_sv)
766 )
767 return 0;
768
Zesstrabf4f86d2020-08-12 00:56:17 +0200769 object old_sv = ch.supervisor;
770
Zesstrab7720dc2020-08-11 22:14:18 +0200771 ch.supervisor = new_sv;
Zesstra10341b82020-09-12 13:02:54 +0200772 ch.access_cl = symbol_function("ch_check_access", new_sv);
Zesstra0c69c2d2020-08-10 02:27:20 +0200773
Zesstra5b7f2fc2020-08-10 02:09:13 +0200774 if (old_sv && new_sv
775 && !old_sv->QueryProp(P_INVIS)
776 && !new_sv->QueryProp(P_INVIS))
777 {
778 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
779 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
780 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
781 // explizites call_other() auf this_object() gemacht, damit der
782 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
783 // einem externen.
Zesstrab7720dc2020-08-11 22:14:18 +0200784 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200785 sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
786 MSG_EMOTE);
787 }
Zesstrabf4f86d2020-08-12 00:56:17 +0200788 else if (old_sv && !old_sv->QueryProp(P_INVIS))
Zesstra5b7f2fc2020-08-10 02:09:13 +0200789 {
Zesstrab7720dc2020-08-11 22:14:18 +0200790 this_object()->send(ch.name, old_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200791 "uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
792 }
793 else if (new_sv && !new_sv->QueryProp(P_INVIS))
794 {
Zesstrab7720dc2020-08-11 22:14:18 +0200795 this_object()->send(ch.name, new_sv,
Zesstra5b7f2fc2020-08-10 02:09:13 +0200796 "uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
797 }
798 return 1;
799}
800
Zesstra56692c72020-08-09 13:03:10 +0200801// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
802// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
Zesstrab7720dc2020-08-11 22:14:18 +0200803// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
804// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
Zesstra56692c72020-08-09 13:03:10 +0200805// wird access() den Zugriff immer erlauben.
Zesstrab7720dc2020-08-11 22:14:18 +0200806private int assert_supervisor(struct channel_s ch)
MG Mud User88f12472016-06-24 23:31:02 +0200807{
Zesstra9359fab2020-08-13 12:03:01 +0200808 // Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
809 if (!ch.supervisor)
MG Mud User88f12472016-06-24 23:31:02 +0200810 {
Zesstra9359fab2020-08-13 12:03:01 +0200811 // Wenn der Wechsel des SV verboten ist, wird versucht, den
812 // urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
813 // machen.
814 if (ch.flags & CHF_FIXED_SUPERVISOR)
MG Mud User88f12472016-06-24 23:31:02 +0200815 {
Zesstra9359fab2020-08-13 12:03:01 +0200816 object sv;
817 string err=catch(sv=load_object(ch.creator);publish);
Arathorn78c08372019-12-11 20:14:23 +0100818 if (!err)
819 {
Zesstra9359fab2020-08-13 12:03:01 +0200820 // Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
821 // und kann dann wieder SV werden.
822 add_member(ch, sv);
823 if (!change_sv_object(ch, sv))
824 {
825 // ich wuesste nicht, was in change_sv_object in diesem Fall
826 // schiefgehen kann, daher einfach ein raise_error.
827 raise_error(sprintf("Supervisor von Channel %s konnte nicht "
828 "reaktiviert werden: %O\n",ch.name,sv));
829 }
Arathorn78c08372019-12-11 20:14:23 +0100830 }
Zesstra9359fab2020-08-13 12:03:01 +0200831 // wenns nicht geklappt hat, wird die Ebene deaktiviert.
Arathorn78c08372019-12-11 20:14:23 +0100832 else
833 {
Zesstra9359fab2020-08-13 12:03:01 +0200834 // Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
835 // urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
836 // solange der das nicht tut, ist weder die History zugaenglich, noch
837 // kann jemand sonst was damit machen. Wenn die inaktive Ebene
838 // irgendwann inkl. History expired wird, kann jemand anderes dann
839 // den Namen wieder verwenden und ein komplett neue Ebene erstellen.
840 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200841 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200842 sprintf("[%s] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200843 dtime(time()), ch.name, ch.supervisor, err));
Arathorn78c08372019-12-11 20:14:23 +0100844 return 0;
845 }
MG Mud User88f12472016-06-24 23:31:02 +0200846 }
Zesstra9359fab2020-08-13 12:03:01 +0200847 // Der normalfall ist aber, dass wir einfach einen supervisor aus dem
848 // Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
849 // change_sv_object().
850 else
Zesstra56692c72020-08-09 13:03:10 +0200851 {
Zesstrabf4f86d2020-08-12 00:56:17 +0200852 if (!change_sv_object(ch, 0))
Zesstra5770ba62020-08-10 10:19:23 +0200853 {
Zesstra9359fab2020-08-13 12:03:01 +0200854 // wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
855 // Zuhoerer.
856 deactivate_channel(lower_case(ch.name), 1);
Zesstra5770ba62020-08-10 10:19:23 +0200857 log_file("CHANNEL",
Zesstra9359fab2020-08-13 12:03:01 +0200858 sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
Zesstrab7720dc2020-08-11 22:14:18 +0200859 dtime(time()), ch.name));
Zesstra5770ba62020-08-10 10:19:23 +0200860 return 0;
861 }
Zesstra56692c72020-08-09 13:03:10 +0200862 }
MG Mud User88f12472016-06-24 23:31:02 +0200863 }
Zesstra78310012020-08-09 12:21:48 +0200864 return 1;
865}
866
867// access() - check access by looking for the right argument types and
868// calling access closures respectively
869// SEE: new, join, leave, send, list, users
Zesstra78310012020-08-09 12:21:48 +0200870// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
871// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
872// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
873// als Erfolg und alles ueber >2 als privilegiert.)
Zesstra752ae7d2020-08-16 22:46:04 +0200874varargs private int access(struct channel_s ch, object user, string cmd,
Zesstra78310012020-08-09 12:21:48 +0200875 string txt)
876{
Zesstra10341b82020-09-12 13:02:54 +0200877 if (!ch || !user)
Zesstra78310012020-08-09 12:21:48 +0200878 return 0;
879
Zesstra46c564e2020-09-11 21:52:29 +0200880 // Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
Zesstrafbfe6362020-08-09 13:30:21 +0200881 // Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
Zesstra46c564e2020-09-11 21:52:29 +0200882 // Ausserdem duerfen sie auch alles andere machen unter Umgehung aller
883 // Supervisoren. (z.B. kann dieses Objekt sogar Meldungen im Namen anderer
884 // Objekte faken)
885 // Die Pruefung erfolgt absichtlich vor assert_supervisor(), damit der
Zesstra10341b82020-09-12 13:02:54 +0200886 // CHANNELD auch in temporaeren SV-losen Zustaenden was machen kann.
Bugfixd01f81b2021-03-25 19:09:12 +0100887 if ( !previous_object(1) || previous_object(1) == this_object() ||
Zesstrafbfe6362020-08-09 13:30:21 +0200888 getuid(previous_object(1)) == ROOTID)
Zesstra78310012020-08-09 12:21:48 +0200889 return 2;
Arathorn739a4fa2020-08-06 21:52:58 +0200890
Zesstra46c564e2020-09-11 21:52:29 +0200891 // Objekte duerfen keine Meldungen im Namen anderer Objekte faken, d.h. der
Zesstra10341b82020-09-12 13:02:54 +0200892 // vermeintliche <user> muss auch der Aufrufer sein. Ausser darf auch sonst
893 // kein Objekt was fuer ein anderes Objekt duerfen, sonst kann jemand z.B.
894 // eine History abfragen indem einfach ein anderes Objekt uebergeben wird.
895 if (previous_object(1) != user)
Zesstra825133d2020-10-02 21:02:08 +0200896 {
897 // Kurzfristiger Workaround: Whitelist fuer bestimmte Objekte, die im
898 // Namen von Spielern senden mit dem Spieler als Absender, z.B. die Leiche
899 // beim verspotten.
900 switch(object_name(previous_object(1)))
901 {
902 case "/std/corpse":
903 break;
904 default:
905 return 0;
906 }
907 }
Zesstra752ae7d2020-08-16 22:46:04 +0200908 if (IsBanned(user, cmd))
Arathorn739a4fa2020-08-06 21:52:58 +0200909 return 0;
910
Zesstra56692c72020-08-09 13:03:10 +0200911 // Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
912 // wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
Zesstra78310012020-08-09 12:21:48 +0200913 if (!assert_supervisor(ch))
Zesstra56692c72020-08-09 13:03:10 +0200914 return 0;
915 // Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
Zesstrab7720dc2020-08-11 22:14:18 +0200916 if (!ch.access_cl)
Arathorn739a4fa2020-08-06 21:52:58 +0200917 return 1;
918
Zesstra6fe46cd2020-08-09 13:12:15 +0200919 // Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
Zesstra10341b82020-09-12 13:02:54 +0200920 // fuer EM+ aber nur, wenn es das Default-SV-Objekt ist, damit
Zesstra6fe46cd2020-08-09 13:12:15 +0200921 // nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
Zesstra10341b82020-09-12 13:02:54 +0200922 // Default-SV koennen aber auch EM+ Zugriff verweigern.
923 if (IS_ARCH(previous_object(1))
924 && ch.supervisor != find_object(DEFAULTSV))
Zesstra6fe46cd2020-08-09 13:12:15 +0200925 return 1;
926
Zesstrac5e98282020-10-02 21:54:57 +0200927 return funcall(ch.access_cl, lower_case(ch.name), user, cmd, txt);
MG Mud User88f12472016-06-24 23:31:02 +0200928}
929
Arathorn78c08372019-12-11 20:14:23 +0100930// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
Zesstrab7720dc2020-08-11 22:14:18 +0200931// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
Arathorn78c08372019-12-11 20:14:23 +0100932// die dynamisch aktualisierte Infos ausgibt.
Zesstra10341b82020-09-12 13:02:54 +0200933// Das Objekt <owner> sollte eine Funktion ch_check_access() definieren, die
Arathorn78c08372019-12-11 20:14:23 +0100934// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
935// eingeht.
MG Mud User88f12472016-06-24 23:31:02 +0200936#define IGNORE "^/xx"
Zesstrad9ec04b2020-08-11 23:47:03 +0200937public varargs int new(string ch_name, object owner, string|closure desc,
938 int channel_flags)
MG Mud User88f12472016-06-24 23:31:02 +0200939{
Arathorn78c08372019-12-11 20:14:23 +0100940 // Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
941 // kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
942 // fuer eine neue Ebene eintragen.)
943 if (!objectp(owner) || (owner == this_object() && extern_call()) )
MG Mud User88f12472016-06-24 23:31:02 +0200944 return E_ACCESS_DENIED;
945
Arathorn78c08372019-12-11 20:14:23 +0100946 // Kein gescheiter Channelname angegeben.
947 if (!stringp(ch_name) || !sizeof(ch_name))
948 return E_ACCESS_DENIED;
949
950 // Channel schon vorhanden oder schon alle Channel-Slots belegt.
951 if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
952 return E_ACCESS_DENIED;
953
954 // Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
955 // ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
956 // seinen Objektnamen matcht.
957 if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
958 return E_ACCESS_DENIED;
959
Zesstraf20e8ba2020-08-12 01:56:30 +0200960 // Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
961 // existiert.
962 struct channel_base_s cbase = channelC[lower_case(ch_name)];
Zesstrab7720dc2020-08-11 22:14:18 +0200963 struct channel_s ch;
Zesstraf20e8ba2020-08-12 01:56:30 +0200964 if (cbase)
Arathorn19459eb2019-11-30 00:45:51 +0100965 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200966 // Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
967 // neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
968 // CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
969 // damit niemand inaktive Ebenen und deren History auf diesem Wege
970 // uebernehmen kann, d.h. den Supervisor ersetzen kann.
971 if ((cbase.flags & CHF_FIXED_SUPERVISOR)
972 && object_name(owner) != cbase.creator)
Arathorn19459eb2019-11-30 00:45:51 +0100973 return E_ACCESS_DENIED;
Zesstraf20e8ba2020-08-12 01:56:30 +0200974 // Alte Daten der Ebene uebernehmen
Zesstraeaa18cc2020-09-28 21:28:21 +0200975#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
976 // Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
977 // richtige struct.
978 ch = to_struct(to_array(cbase), (<channel_s>));
979#else
Zesstraf20e8ba2020-08-12 01:56:30 +0200980 ch = to_struct(cbase, (<channel_s>));
Zesstraeaa18cc2020-09-28 21:28:21 +0200981#endif
Zesstraf20e8ba2020-08-12 01:56:30 +0200982 // Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
983 if (desc)
984 ch.desc = desc;
985 // creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
986 // die Schreibweise (Gross-/Kleinschreibung) des Namens aus
987 // Konsistenzgruenden bei.
MG Mud User88f12472016-06-24 23:31:02 +0200988 }
Arathorn19459eb2019-11-30 00:45:51 +0100989 else
990 {
Zesstraf20e8ba2020-08-12 01:56:30 +0200991 // Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
992 // Fehler...
993 if (!desc)
994 return E_ACCESS_DENIED;
995 // prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
996 // erzeugt.
Zesstrad9ec04b2020-08-11 23:47:03 +0200997 ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
998 flags: channel_flags);
Arathorn19459eb2019-11-30 00:45:51 +0100999 }
MG Mud User88f12472016-06-24 23:31:02 +02001000
Zesstrab7720dc2020-08-11 22:14:18 +02001001 ch_name = lower_case(ch_name);
1002
1003 ch.members = ({ owner });
Zesstra9359fab2020-08-13 12:03:01 +02001004 ch.supervisor = owner;
Zesstra10341b82020-09-12 13:02:54 +02001005 // ch_check_access() dient der Zugriffskontrolle und entscheidet, ob die
Zesstrad9ec04b2020-08-11 23:47:03 +02001006 // Nachricht gesendet werden darf oder nicht.
Zesstra10341b82020-09-12 13:02:54 +02001007 ch.access_cl = symbol_function("ch_check_access", owner);
Zesstrab7720dc2020-08-11 22:14:18 +02001008
1009 m_add(channels, ch_name, ch);
MG Mud User88f12472016-06-24 23:31:02 +02001010
Arathorn78c08372019-12-11 20:14:23 +01001011 // History fuer eine Ebene nur dann initialisieren, wenn es sie noch
1012 // nicht gibt.
Zesstrab7720dc2020-08-11 22:14:18 +02001013 if (!pointerp(channelH[ch_name]))
1014 channelH[ch_name] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001015
Zesstraf20e8ba2020-08-12 01:56:30 +02001016 // Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
1017 // jetzt auch weg.
1018 if (cbase)
1019 m_delete(channelC, ch_name);
1020
Arathorn78c08372019-12-11 20:14:23 +01001021 // Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
1022 if (owner != this_object())
Zesstra5770ba62020-08-10 10:19:23 +02001023 log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
Zesstrab7720dc2020-08-11 22:14:18 +02001024 dtime(time()), ch.name, owner, desc));
Arathorn19459eb2019-11-30 00:45:51 +01001025
Arathorn78c08372019-12-11 20:14:23 +01001026 // Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
1027 if (!owner->QueryProp(P_INVIS))
1028 {
1029 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1030 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1031 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1032 // explizites call_other() auf this_object() gemacht, damit der
1033 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1034 // einem externen.
1035 this_object()->send(CMNAME, owner,
Zesstrab7720dc2020-08-11 22:14:18 +02001036 "laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
Arathorn78c08372019-12-11 20:14:23 +01001037 }
Arathorn19459eb2019-11-30 00:45:51 +01001038
MG Mud User88f12472016-06-24 23:31:02 +02001039 stats["new"]++;
Arathorn19459eb2019-11-30 00:45:51 +01001040 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001041}
1042
Arathorn78c08372019-12-11 20:14:23 +01001043// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
1044// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
1045// zweimal beitreten.)
Zesstra165157f2020-08-16 22:47:36 +02001046public int join(string chname, object joining)
MG Mud User88f12472016-06-24 23:31:02 +02001047{
Zesstrab7720dc2020-08-11 22:14:18 +02001048 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001049 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1050 zu erzeugen, weil access() mit extern_call() und previous_object()
1051 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1052 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001053 if (!funcall(#'access, ch, joining, C_JOIN))
Arathorn19459eb2019-11-30 00:45:51 +01001054 return E_ACCESS_DENIED;
Zesstra10341b82020-09-12 13:02:54 +02001055 //TODO: Sollte der creator das Recht auf join haben, auch wenn der aktuelle
1056 //SV es verweigert? (s.u.)
Zesstra165157f2020-08-16 22:47:36 +02001057 int res = add_member(ch, joining);
Zesstrafb350dc2020-08-12 00:49:31 +02001058 if (res != 1)
1059 return res;
1060
Zesstra46c564e2020-09-11 21:52:29 +02001061 // Wenn <joining> der urspruengliche Ersteller der Ebene und kein
1062 // Spieler ist, wird es automatisch wieder zum Supervisor.
Zesstra165157f2020-08-16 22:47:36 +02001063 if (!query_once_interactive(joining)
1064 && object_name(joining) == ch.creator)
1065 change_sv_object(ch, joining);
Zesstra2b7ed1a2020-08-12 00:58:33 +02001066
Zesstrafb350dc2020-08-12 00:49:31 +02001067 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001068}
1069
Arathorn78c08372019-12-11 20:14:23 +01001070// Objekt <pl> verlaesst Ebene <ch>.
1071// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
1072// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
Zesstra10341b82020-09-12 13:02:54 +02001073// Dies ist in ch_check_access() so geregelt, allerdings koennte dem Objekt
Arathorn78c08372019-12-11 20:14:23 +01001074// <pl> das Verlassen auf Grund eines Banns verboten sein.
Zesstra10341b82020-09-12 13:02:54 +02001075// Wenn kein Zuhoerer mehr auf der Ebene ist, loest sie sich auf.
Zesstra165157f2020-08-16 22:47:36 +02001076public int leave(string chname, object leaving)
MG Mud User88f12472016-06-24 23:31:02 +02001077{
Zesstrab7720dc2020-08-11 22:14:18 +02001078 struct channel_s ch = channels[lower_case(chname)];
Zesstra0b4a5652020-09-23 00:40:37 +02001079 // Nicht-existierenden Ebenen soll das Spielerobjekt austragen, also tun wir
1080 // so, als sei das erfolgreich gewesen.
1081 if (!ch)
1082 return 0;
Zesstra877cb0a2020-08-10 02:10:21 +02001083
Zesstrab7720dc2020-08-11 22:14:18 +02001084 ch.members -= ({0}); // kaputte Objekte erstmal raus
Zesstra877cb0a2020-08-10 02:10:21 +02001085
Zesstra165157f2020-08-16 22:47:36 +02001086 if (!IsChannelMember(ch, leaving))
Zesstra877cb0a2020-08-10 02:10:21 +02001087 return E_NOT_MEMBER;
1088
Arathorn739a4fa2020-08-06 21:52:58 +02001089 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1090 zu erzeugen, weil access() mit extern_call() und previous_object()
1091 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1092 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001093 if (!funcall(#'access, ch, leaving, C_LEAVE))
Arathorn19459eb2019-11-30 00:45:51 +01001094 return E_ACCESS_DENIED;
1095
Zesstrab7720dc2020-08-11 22:14:18 +02001096 // Dann mal den Zuhoerer raus.
Zesstra165157f2020-08-16 22:47:36 +02001097 ch.members -= ({leaving});
Zesstrae6d33852020-08-09 14:37:53 +02001098
Zesstra5b7f2fc2020-08-10 02:09:13 +02001099 // Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
1100 // wechseln.
Zesstrab7720dc2020-08-11 22:14:18 +02001101 if (sizeof(ch.members))
MG Mud User88f12472016-06-24 23:31:02 +02001102 {
Zesstra5b7f2fc2020-08-10 02:09:13 +02001103 // Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
1104 // diese verlassen hat. change_sv_object() waehlt per Default den
1105 // aeltesten Zuhoerer.
Zesstra165157f2020-08-16 22:47:36 +02001106 if (leaving == ch.supervisor)
Arathorn78c08372019-12-11 20:14:23 +01001107 {
Zesstrabf4f86d2020-08-12 00:56:17 +02001108 change_sv_object(ch, 0);
Arathorn78c08372019-12-11 20:14:23 +01001109 }
MG Mud User88f12472016-06-24 23:31:02 +02001110 }
Zesstra137ea1c2020-08-10 02:15:20 +02001111 // ansonsten Ebene loeschen, wenn keiner zuhoert.
1112 // Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
1113 // Zuhoerer mehr, folglich auch kein Supervisor mehr da.
1114 else
MG Mud User88f12472016-06-24 23:31:02 +02001115 {
Arathorn78c08372019-12-11 20:14:23 +01001116 // Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
Zesstra137ea1c2020-08-10 02:15:20 +02001117 // Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
1118 // wird diese zerstoert, mit Meldung.
Zesstra165157f2020-08-16 22:47:36 +02001119 if (!leaving->QueryProp(P_INVIS))
Arathorn78c08372019-12-11 20:14:23 +01001120 {
1121 // Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
1122 // erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
1123 // interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
1124 // explizites call_other() auf this_object() gemacht, damit der
1125 // Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
1126 // einem externen.
Zesstra165157f2020-08-16 22:47:36 +02001127 this_object()->send(CMNAME, leaving,
Arathorn78c08372019-12-11 20:14:23 +01001128 "verlaesst als "+
Zesstra165157f2020-08-16 22:47:36 +02001129 (leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
Zesstrab7720dc2020-08-11 22:14:18 +02001130 " die Ebene '" + ch.name + "', worauf diese sich in "
Arathorn78c08372019-12-11 20:14:23 +01001131 "einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
1132 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001133 deactivate_channel(lower_case(ch.name),0);
MG Mud User88f12472016-06-24 23:31:02 +02001134 }
Arathorn19459eb2019-11-30 00:45:51 +01001135 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001136}
1137
Arathorn78c08372019-12-11 20:14:23 +01001138// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
1139// sofern <pl> dort senden darf.
Zesstra165157f2020-08-16 22:47:36 +02001140public varargs int send(string chname, object sender, string msg, int type)
MG Mud User88f12472016-06-24 23:31:02 +02001141{
Zesstrab7720dc2020-08-11 22:14:18 +02001142 chname = lower_case(chname);
1143 struct channel_s ch = channels[chname];
Arathorn739a4fa2020-08-06 21:52:58 +02001144 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1145 zu erzeugen, weil access() mit extern_call() und previous_object()
1146 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1147 richtige ist. */
Zesstra165157f2020-08-16 22:47:36 +02001148 int a = funcall(#'access, ch, sender, C_SEND, msg);
Arathorn78c08372019-12-11 20:14:23 +01001149 if (!a)
Arathorn19459eb2019-11-30 00:45:51 +01001150 return E_ACCESS_DENIED;
1151
Zesstra26aaf1a2020-08-07 19:10:39 +02001152 // a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
Zesstra10341b82020-09-12 13:02:54 +02001153 // Rueckgabewert von ch_check_access() entspricht, wenn die Aktion zugelassen
1154 // wird. access() liefert allerdings 2 fuer "privilegierte" Objekte (z.B.
Zesstra26aaf1a2020-08-07 19:10:39 +02001155 // ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
1156 // Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
Zesstra165157f2020-08-16 22:47:36 +02001157 if (a < 2 && !IsChannelMember(ch, sender))
Arathorn19459eb2019-11-30 00:45:51 +01001158 return E_NOT_MEMBER;
1159
1160 if (!msg || !stringp(msg) || !sizeof(msg))
1161 return E_EMPTY_MESSAGE;
1162
Arathorn78c08372019-12-11 20:14:23 +01001163 // Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
1164 // ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
1165 // Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
1166 // Nachricht ebenfalls erhaelt.
1167 // Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
1168 // demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
1169 // auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
1170 // erzeugt und der Channeld als Besitzer angegeben.
1171 // Die Aufrufkette ist dann wie folgt:
1172 // Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
Zesstrab7720dc2020-08-11 22:14:18 +02001173 (ch.members)->ChannelMessage(
Zesstra165157f2020-08-16 22:47:36 +02001174 ({ ch.name, sender, msg, type}));
Arathorn19459eb2019-11-30 00:45:51 +01001175
Zesstrab7720dc2020-08-11 22:14:18 +02001176 if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
1177 channelH[chname] = channelH[chname][1..];
Arathorn19459eb2019-11-30 00:45:51 +01001178
Zesstrab7720dc2020-08-11 22:14:18 +02001179 channelH[chname] +=
1180 ({ ({ ch.name,
Zesstra556c54d2020-08-16 22:50:10 +02001181 (sender->QueryProp(P_INVIS)
Zesstra165157f2020-08-16 22:47:36 +02001182 ? "/(" + capitalize(getuid(sender)) + ")$"
Arathorn19459eb2019-11-30 00:45:51 +01001183 : "")
Zesstra556c54d2020-08-16 22:50:10 +02001184 + (sender->Name(WER, 2) || "<Unbekannt>"),
Arathorn19459eb2019-11-30 00:45:51 +01001185 msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
1186 type }) });
Arathorn78c08372019-12-11 20:14:23 +01001187 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001188}
1189
Arathorn78c08372019-12-11 20:14:23 +01001190// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
1191// oder einen Integer-Fehlercode
1192public int|mapping list(object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001193{
Arathorn78c08372019-12-11 20:14:23 +01001194 mapping chs = ([]);
Zesstrab7720dc2020-08-11 22:14:18 +02001195 foreach(string chname, struct channel_s ch : channels)
Arathorn78c08372019-12-11 20:14:23 +01001196 {
Arathorn739a4fa2020-08-06 21:52:58 +02001197 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1198 zu erzeugen, weil access() mit extern_call() und previous_object()
1199 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1200 richtige ist. */
Zesstrab7720dc2020-08-11 22:14:18 +02001201 if(funcall(#'access, ch, pl, C_LIST))
Arathorn78c08372019-12-11 20:14:23 +01001202 {
Zesstrab7720dc2020-08-11 22:14:18 +02001203 ch.members = filter(ch.members, #'objectp);
1204 m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
1205 ch.supervisor, ch.name }) );
Arathorn78c08372019-12-11 20:14:23 +01001206 }
1207 }
Arathorn19459eb2019-11-30 00:45:51 +01001208
1209 if (!sizeof(chs))
1210 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001211 return (chs);
MG Mud User88f12472016-06-24 23:31:02 +02001212}
1213
Arathorn78c08372019-12-11 20:14:23 +01001214// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
1215// Rueckgabewerte:
1216// - den gefundenen Namen als String
1217// - String-Array, wenn es mehrere Treffer gibt
1218// - 0, wenn es keinen Treffer gibt
Zesstrab7720dc2020-08-11 22:14:18 +02001219public string|string* find(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001220{
Zesstrab7720dc2020-08-11 22:14:18 +02001221 chname = lower_case(chname);
Arathorn19459eb2019-11-30 00:45:51 +01001222
Arathorn78c08372019-12-11 20:14:23 +01001223 // Suchstring <ch> muss Formatanforderung erfuellen;
1224 // TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
1225 // Wenn ja, muesste laut Manpage mehr geprueft werden:
1226 // "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
1227 // #$%&@<>-." Es wuerden also $%&@ fehlen.
Zesstrab7720dc2020-08-11 22:14:18 +02001228 if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
Arathorn78c08372019-12-11 20:14:23 +01001229 return 0;
Arathorn19459eb2019-11-30 00:45:51 +01001230
Arathorn78c08372019-12-11 20:14:23 +01001231 // Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
1232 // Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
1233 // in das Suchergebnis aufgenommen wird.
Zesstrab7720dc2020-08-11 22:14:18 +02001234 string* chs = filter(m_indices(channels), function int (string ch_n) {
Bugfix6b977972021-03-25 10:22:56 +01001235 /* Normalerweise benoetigen wir ein funcall() auf
1236 Closure-Operator, um einen neuen Eintrag im Caller Stack
1237 zu erzeugen, weil access() mit extern_call() und
1238 previous_object() arbeitet und sichergestellt sein muss,
1239 dass das in jedem Fall das richtige ist.
1240 In diesem Fall gibt es schon durch filter() einen neuen
1241 Eintrag, daher muss access() direkt gerufen werden, sonst
1242 ist previous_object(1) == this_object(). */
Zesstrab7720dc2020-08-11 22:14:18 +02001243 return ( stringp(regmatch(ch_n, "^"+chname)) &&
Bugfix0ae4af32021-03-26 11:54:03 +01001244 access(channels[ch_n], pl, C_FIND) );
Arathorn78c08372019-12-11 20:14:23 +01001245 });
Arathorn19459eb2019-11-30 00:45:51 +01001246
Arathorn78c08372019-12-11 20:14:23 +01001247 int num_channels = sizeof(chs);
1248 if (num_channels > 1)
1249 return chs;
1250 else if (num_channels == 1)
Zesstrab7720dc2020-08-11 22:14:18 +02001251 return chs[0];
Arathorn78c08372019-12-11 20:14:23 +01001252 else
1253 return 0;
MG Mud User88f12472016-06-24 23:31:02 +02001254}
1255
Arathorn78c08372019-12-11 20:14:23 +01001256// Ebenen-History abfragen.
Zesstrab7720dc2020-08-11 22:14:18 +02001257public int|<int|string>** history(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001258{
Zesstrab7720dc2020-08-11 22:14:18 +02001259 struct channel_s ch = channels[lower_case(chname)];
Arathorn739a4fa2020-08-06 21:52:58 +02001260 /* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
1261 zu erzeugen, weil access() mit extern_call() und previous_object()
1262 arbeitet und sichergestellt sein muss, dass das in jedem Fall das
1263 richtige ist. */
1264 if (!funcall(#'access, ch, pl, C_JOIN))
MG Mud User88f12472016-06-24 23:31:02 +02001265 return E_ACCESS_DENIED;
Arathorn78c08372019-12-11 20:14:23 +01001266 else
Zesstrab7720dc2020-08-11 22:14:18 +02001267 return channelH[chname];
MG Mud User88f12472016-06-24 23:31:02 +02001268}
1269
Arathorn78c08372019-12-11 20:14:23 +01001270// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
Zesstrab7720dc2020-08-11 22:14:18 +02001271public int remove_channel(string chname, object pl)
MG Mud User88f12472016-06-24 23:31:02 +02001272{
Zesstra66f801d2020-09-24 21:00:13 +02001273 if (!member(channels, chname))
1274 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001275 //TODO: integrieren in access()?
Arathorn19459eb2019-11-30 00:45:51 +01001276 if (previous_object() != this_object())
1277 {
Zesstrab7720dc2020-08-11 22:14:18 +02001278 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001279 pl != this_player() || this_player() != this_interactive() ||
1280 this_interactive() != previous_object() ||
1281 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001282 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001283 }
Zesstra52d5f8a2020-08-12 00:39:15 +02001284
1285 delete_channel(lower_case(chname), 1);
Arathorn19459eb2019-11-30 00:45:51 +01001286
Arathorn19459eb2019-11-30 00:45:51 +01001287 return (0);
MG Mud User88f12472016-06-24 23:31:02 +02001288}
1289
Arathorn78c08372019-12-11 20:14:23 +01001290// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
Zesstrab7720dc2020-08-11 22:14:18 +02001291public int clear_history(string chname)
MG Mud User88f12472016-06-24 23:31:02 +02001292{
Zesstra66f801d2020-09-24 21:00:13 +02001293 if (!member(channelH, chname))
1294 return E_ACCESS_DENIED;
Zesstra26aaf1a2020-08-07 19:10:39 +02001295 //TODO: mit access() vereinigen?
MG Mud User88f12472016-06-24 23:31:02 +02001296 // Sicherheitsabfragen
Arathorn19459eb2019-11-30 00:45:51 +01001297 if (previous_object() != this_object())
1298 {
Zesstrab7720dc2020-08-11 22:14:18 +02001299 if (!stringp(chname) ||
Arathorn19459eb2019-11-30 00:45:51 +01001300 this_player() != this_interactive() ||
1301 this_interactive() != previous_object() ||
1302 !IS_ARCH(this_interactive()))
MG Mud User88f12472016-06-24 23:31:02 +02001303 return E_ACCESS_DENIED;
Arathorn19459eb2019-11-30 00:45:51 +01001304 }
Zesstrab7720dc2020-08-11 22:14:18 +02001305 chname=lower_case(chname);
Zesstra26aaf1a2020-08-07 19:10:39 +02001306 // History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
1307 // aus dem mapping loeschen.)
Zesstrab7720dc2020-08-11 22:14:18 +02001308 if (pointerp(channelH[chname]))
1309 channelH[chname] = ({});
MG Mud User88f12472016-06-24 23:31:02 +02001310
1311 return 0;
1312}
Zesstra66f801d2020-09-24 21:00:13 +02001313
1314// Aendert den Ersteller/Besitzer der Ebene.
1315// Achtung: das ist nicht das gleiche wie der aktuelle Supervisor!
1316public int transfer_ownership(string chname, object new_owner)
1317{
1318 struct channel_s ch = channels[lower_case(chname)];
1319 if (!ch)
1320 return E_ACCESS_DENIED;
1321 // Nur der aktuelle Besitzer oder EM+ darf die Ebene verschenken
1322 if (ch.creator != object_name(previous_object())
1323 && !IS_ARCH(previous_object()))
1324 return E_ACCESS_DENIED;
1325
1326 ch.creator = object_name(new_owner);
1327 return 1;
1328}
1329
1330// Aendert den Flags der Ebene.
1331public int change_channel_flags(string chname, int newflags)
1332{
1333 struct channel_s ch = channels[lower_case(chname)];
1334 if (!ch)
1335 return E_ACCESS_DENIED;
1336 // Nur der aktuelle Besitzer, Supervisor oder EM+ darf die Flags aendern.
1337 if (ch.supervisor != previous_object()
1338 && ch.creator != object_name(previous_object())
1339 && !IS_ARCH(previous_object()))
1340 return E_ACCESS_DENIED;
1341
1342 ch.flags = newflags;
1343 return 1;
1344}
1345