blob: 8b531e33651e43d4657f8c5809a0a04661f9ad48 [file] [log] [blame]
// channeld.c
//
// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
//
#pragma strong_types
#pragma no_shadow // keine Shadowing...
#pragma no_clone
#pragma no_inherit
#pragma save_types
#include <sys_debug.h>
#include <lpctypes.h>
#include <wizlevels.h>
#include <regexp.h>
#include <properties.h>
#include <config.h>
#include <language.h>
#define NEED_PROTOTYPES
#include "channel.h"
#define CHANNEL_SAVE "/p/daemon/save/channeld"
#define MEMORY "/secure/memory"
#define MAX_HIST_SIZE 200
#define MAX_CHANNELS 90
// Default-Zeit (in Sekunden), die eine Ebene und ihr History inaktiv sein darf,
// bevor sie expired wird. Das reduziert sich jedoch, falls es zuviele
// inaktive Ebenen gibt. Entspricht 30 Tagen.
#define INACTIVE_EXPIRE 2592000
// Aber eine Ebene darf min. solange inaktiv sein, bevor sie geloescht wird
#define MIN_INACTIVE_LIFETIME 3600
// max. Anzahl der inaktiven Ebenen. Wenn die Haelfte davon ueberschritten
// wird, wird mit zunehmend kleinerem CHANNEL_EXPIRE gearbeitet.
#define MAX_INACTIVE_CHANNELS 500
#define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
// Datenstrukturen fuer die Ebenen.
// Basisdaten, welche auch inaktive Ebenen in channelC haben
struct channel_base_s {
string name; // readable channelname, case-sensitive
string|closure desc; // stat. oder dyn. Beschreibung
string creator; // Ersteller der Ebene (Objektname), Besitzer
int flags; // Flags, die bestimmtes Verhalten steuern.
};
// Basisdaten + die von aktiven Ebenen
struct channel_s (channel_base_s) {
object supervisor; // aktueller Supervisor der Ebene
closure access_cl; // Closure fuer Zugriffsrechtepruefung
object *members; // Zuhoerer der Ebene
};
/* Ebenenliste und die zugehoerigen Daten in struct (<channel>).
channels = ([string channelname : (<channel_s>) ])
// HINWEIS: Bitte beachten, dass channels immer nur so manipuliert werden
// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
// Referenz in Memory und Channeld dieselbe ist.
*/
private nosave mapping channels;
/* Ebenenhistory
mapping channelH = ([ string channelname : ({ ({string channelname,
string sender,
string msg,
int msg_type}) }) ]) */
// channelH wird in create() geeignet initialisiert
// HINWEIS: Bitte beachten, dass channelH immer nur so manipuliert werden
// darf, dass keine Kopie erstellt wird, weder direkt noch implizit. Die
// History wird via Referenz in /secure/memory hinterlegt, damit sie einen
// Reload des Channeld ueberlebt. Das funktioniert aber nur, wenn die Mapping-
// Referenz in Memory und Channeld dieselbe ist.
private nosave mapping channelH;
/* Ebenen-Cache, enthaelt Daten zu inaktiven Ebenen.
mapping channelC = ([ string channelname : (<channel_base_s>);
int time() ])
Der Zeitstempel ist die letzte Aenderung, d.h. in der Regel des Ablegens in
channelC.
*/
private mapping channelC = ([:2]);
/* Liste von Spielern, fuer die ein Bann besteht, mit den verbotenen Kommandos
mapping channelB = ([ string playername : string* banned_command ]) */
private mapping channelB = ([]);
/* Timeout-Liste der Datenabfrage-Kommandos; die Timestamps werden verwendet,
um sicherzustellen, dass jedes Kommando max. 1x pro Minute benutzt werden
kann.
mapping Tcmd = ([ "lag": int timestamp,
"uptime": int timestamp,
"statistik": int timestamp]) */
private mapping Tcmd = ([]);
/* Globale channeld-Stats (Startzeit, geladen von, Anzahl erstellte und
zerstoerte Ebenen.
mapping stats = ([ "time" : int object_time(),
"boot" : string getuid(previous_object()),
"new" : int total_channels_created,
"disposed" : int total_channels_removed ]) */
// stats wird in create() geeignet initialisiert
private nosave mapping stats;
/* Flag, das anzeigt, dass Daten veraendert wurden und beim naechsten
Speicherevent das Savefile geschrieben werden soll.
Wird auf 0 oder 1 gesetzt. */
private nosave int save_me_soon;
// Private Prototypen
public int join(string chname, object joining);
private int assert_supervisor(struct channel_s ch);
/* CountUsers() zaehlt die Anzahl Abonnenten aller Ebenen. */
// TODO: Mapping- und Arrayvarianten bzgl. der Effizienz vergleichen
private int CountUsers()
{
object* userlist = ({});
foreach(string ch_name, struct channel_s ch : channels)
{
userlist += ch.members;
}
// Das Mapping dient dazu, dass jeder Eintrag nur einmal vorkommt.
return sizeof(mkmapping(userlist));
}
// Ist das Objekt <sender> Abonnent der Ebene <ch>?
private int IsChannelMember(struct channel_s ch, object sender)
{
return (member(ch.members, sender) != -1);
}
// Besteht fuer das Objekt <ob> ein Bann fuer die Ebenenfunktion <command>?
private int IsBanned(string|object ob, string command)
{
if (objectp(ob))
ob = getuid(ob);
return(pointerp(channelB[ob]) &&
member(channelB[ob], command) != -1);
}
private void banned(string plname, string* cmds, string res)
{
res += sprintf("%s [%s], ", capitalize(plname), implode(cmds, ","));
}
#define TIMEOUT (time() - 60)
// IsNotBlocked(): prueft fuer die Liste der uebergebenen Kommandos, ob
// die Zeitsperre fuer alle abgelaufen ist und sie ausgefuehrt werden duerfen.
// Dabei gilt jedes Kommando, dessen letzte Nutzung laenger als 60 s
// zurueckliegt, als "nicht gesperrt".
private int IsNotBlocked(string* cmd)
{
string* res = filter(cmd, function int (string str) {
return (Tcmd[str] < TIMEOUT);
});
// Wenn das Ergebnis-Array genauso gross ist wie das Eingabe-Array, dann
// sind alle Kommandos frei. Sie werden direkt gesperrt; return 1
// signalisiert dem Aufrufer, dass das Kommando ausgefuehrt werden darf.
if (sizeof(res) == sizeof(cmd)) {
foreach(string str : cmd) {
Tcmd[str] = time();
}
return 1;
}
return 0;
}
// Prueft, ob der gesendete Befehl <cmd> als gueltiges Kommando <check>
// zugelassen wird. Anforderungen:
// 1) <cmd> muss Teilstring von <check> sein
// 2) <cmd> muss am Anfang von <check> stehen
// 3) <cmd> darf nicht laenger sein als <check>
// 4) die Nutzung von <cmd> darf nur einmal pro Minute erfolgen
// Beispiel: check = "statistik", cmd = "stat" ist gueltig, nicht aber
// cmd = "statistiker" oder cmd = "tist"
// Wenn die Syntax zugelassen wird, wird anschliessend geprueft
private int IsValidChannelCommand(string cmd, string check) {
// Syntaxcheck (prueft Bedingungen 1 bis 3).
if ( strstr(check, cmd)==0 && sizeof(cmd) <= sizeof(check) ) {
string* cmd_to_check;
// Beim Kombi-Kommando "lust" muessen alle 3 Befehle gecheckt werden.
// Der Einfachheit halber werden auch Einzelkommandos als Array ueber-
// geben.
if ( cmd == "lust" )
cmd_to_check = ({"lag", "statistik", "uptime"});
else
cmd_to_check = ({cmd});
// Prueft die Zeitsperre (Bedingung 4).
return (IsNotBlocked(cmd_to_check));
}
return 0;
}
#define CH_NAME 0
#define CH_SENDER 1
#define CH_MSG 2
#define CH_MSG_TYPE 3
// Gibt die Channelmeldungen fuer die Kommandos up, stat, lag und bann des
// <MasteR>-Channels aus. Auszugebende Informationen werden in <ret> gesammelt
// und dieses per Callout an send() uebergeben.
// Argument: ({channel.name, object pl, string msg, int type})
// Funktion muss public sein, auch wenn der erste Check im Code das Gegenteil
// nahezulegen scheint, weil sie von send() per call_other() gerufen wird,
// was aber bei einer private oder protected Funktion nicht moeglich waere.
public void ChannelMessage(<string|object|int>* msg)
{
// Wir reagieren nur auf Meldungen, die wir uns selbst geschickt haben,
// aber nur dann, wenn sie auf der Ebene <MasteR> eingegangen sind.
if (msg[CH_SENDER] == this_object() || !stringp(msg[CH_MSG]) ||
msg[CH_NAME] != CMNAME || previous_object() != this_object())
return;
float* lag;
int max, rekord;
string ret;
string mesg = msg[CH_MSG];
if (IsValidChannelCommand(mesg, "hilfe"))
{
ret = "Folgende Kommandos gibt es: hilfe, lag, uptime, statistik, lust, "
"bann. Die Kommandos koennen abgekuerzt werden.";
}
else if (IsValidChannelCommand(mesg, "lag"))
{
lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
"%.1f%%/20s, %.1f%%/2s",
lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
// Erster Callout wird hier schon abgesetzt, um sicherzustellen, dass
// die Meldung in zwei Zeilen auf der Ebene erscheint.
call_out(#'send, 2, CMNAME, this_object(), ret);
ret = query_load_average();
}
else if (IsValidChannelCommand(mesg, "uptime"))
{
if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
{
sscanf(read_file("/etc/maxusers"), "%d %~s", max);
sscanf(read_file("/etc/maxusers.ever"), "%d %~s", rekord);
ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
"eingeloggt; das Maximum lag heute bei %d und der Rekord "
"bisher ist %d.", uptime(), sizeof(users()), max, rekord);
}
else
{
ret = "Diese Information liegt nicht vor.";
}
}
else if (IsValidChannelCommand(mesg, "statistik"))
{
ret = sprintf(
"Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv. "
"Der %s wurde das letzte mal am %s von %s neu gestartet. "
"Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.",
sizeof(channels), CountUsers(), CMNAME,
dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
}
// Ebenenaktion beginnt mit "bann"?
else if (strstr(mesg, "bann")==0)
{
string pl, cmd;
if (mesg == "bann")
{
if (sizeof(channelB))
{
ret = "Fuer folgende Spieler besteht ein Bann: ";
// Zwischenspeicher fuer die Einzeleintraege, um diese spaeter mit
// CountUp() in eine saubere Aufzaehlung umwandeln zu koennen.
string* banlist = ({});
foreach(string plname, string* banned_cmds : channelB) {
banlist += ({ sprintf("%s [%s]",
capitalize(plname), implode(banned_cmds, ", "))});
}
ret = CountUp(banlist);
}
else
{
ret = "Zur Zeit ist kein Bann aktiv.";
}
}
else
{
if (sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
IS_DEPUTY(msg[CH_SENDER]))
{
pl = lower_case(pl);
cmd = lower_case(cmd);
if (member(CMDS, cmd) != -1)
{
// Kein Eintrag fuer <pl> in der Bannliste vorhanden, dann anlegen;
// ist der Eintrag kein Array, ist ohnehin was faul, dann wird
// ueberschrieben.
if (!pointerp(channelB[pl]))
m_add(channelB, pl, ({}));
if (IsBanned(pl, cmd))
channelB[pl] -= ({ cmd });
else
channelB[pl] += ({ cmd });
ret = "Fuer '" + capitalize(pl) + "' besteht " +
(sizeof(channelB[pl])
? "folgender Bann: " + CountUp(channelB[pl]) + "."
: "kein Bann mehr.");
// Liste der gebannten Kommandos leer? Dann <pl> komplett austragen.
if (!sizeof(channelB[pl]))
m_delete(channelB, pl);
save_me_soon = 1;
}
else
{
ret = "Das Kommando '" + cmd + "' ist unbekannt. "
"Erlaubte Kommandos: "+ CountUp(CMDS);
}
}
else
{
if (IS_ARCH(msg[CH_SENDER]))
ret = "Syntax: bann <name> <kommando>";
}
}
}
else if (IsValidChannelCommand(mesg, "lust"))
{
lag = "/p/daemon/lag-o-daemon"->read_lag_data();
if (file_size("/etc/maxusers") > 0 && file_size("/etc/maxusers.ever"))
{
sscanf(read_file("/etc/maxusers"), "%d %~2s", max);
sscanf(read_file("/etc/maxusers.ever"), "%d %~s", rekord);
}
int t = time() - last_reboot_time();
// TODO: fuer solche Anwendungen ein separates Inheritfile bauen, da
// die Funktionalitaet oefter benoetigt wird als nur hier.
string up = "";
if (t >= 86400)
up += sprintf("%dT", t / 86400);
t %= 86400;
if (t >= 3600)
up += sprintf("%dh", t / 3600);
t %= 3600;
if (t > 60)
up += sprintf("%dm", t / 60);
up += sprintf("%ds", t % 60);
ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
lag[1], lag[2], up, sizeof(users()), max, rekord,
sizeof(channels), CountUsers());
}
else
{
return;
}
// Nur die Ausgabe starten, wenn ein Ausgabestring vorliegt. Es kann
// vorkommen, dass weiter oben keiner zugewiesen wird, weil die Bedingungen
// nicht erfuellt sind.
if (stringp(ret) && sizeof(ret))
call_out(#'send, 2, CMNAME, this_object(), ret);
}
// setup() -- set up a channel and register it
// arguments are stored in the following order:
// string* chinfo = ({ channel_name, receive_level, send_level,
// adminflags, channelflags, description,supervisor })
private void setup(string* chinfo)
{
string desc = "- Keine Beschreibung -";
object supervisor;
int chflags;
if (sizeof(chinfo) && sizeof(chinfo[0]) > 1 && chinfo[0][0] == '\\')
chinfo[0] = chinfo[0][1..];
// Wenn der channeld nur neugeladen wurde, aber das Mud nicht neugestartet,
// sind alle Ebenen noch da, weil sie im MEMORY liegen. D.h. ist die Ebene
// noch bekannt, muss nichts gemacht werden.
if (member(channels, lower_case(chinfo[0])))
return;
// Nur die Angabe des SV (Index 6) im initfile ist optional, alle Elemente
// davor muessen da sein.
if (sizeof(chinfo) < 6)
return;
// Bei genug Elementen schauen, ob der SV ladbar ist.
if (sizeof(chinfo) >= 7)
{
if (stringp(chinfo[6]) && sizeof(chinfo[6]))
catch(supervisor = load_object(chinfo[6]); publish);
}
// Aber falls kein SV angegeben wird oder das Objekt nicht ladbar war, wird
// ein Default-SV genutzt.
if (!supervisor)
supervisor = load_object(DEFAULTSV);
desc = chinfo[5];
chflags = to_int(chinfo[4]);
if (new(chinfo[0], supervisor, desc, chflags) == E_ACCESS_DENIED)
{
log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
dtime(time()), chinfo[0], supervisor));
}
return;
}
private void create_default_channels()
{
string ch_list;
#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
ch_list = read_file(object_name(this_object()) + ".init");
#else
ch_list = read_file(object_name(this_object()) + ".init.testmud");
#endif
if (!stringp(ch_list))
return;
// Channeldatensaetze erzeugen, dazu zuerst Datenfile in Zeilen zerlegen
// "Allgemein: 0: 0: 0:Allgemeine Unterhaltungsebene"
// Danach drueberlaufen und in Einzelfelder splitten, dabei gleich die
// Trennzeichen (Doppelpunkt, Tab und Space) rausfiltern.
foreach(string ch : old_explode(ch_list, "\n"))
{
if (ch[0]=='#')
continue;
setup( regexplode(ch, ":[ \t]*", RE_OMIT_DELIM) );
}
}
// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
protected void create()
{
int do_complete_init;
seteuid(getuid());
restore_object(CHANNEL_SAVE);
// Altes channelC aus Savefiles konvertieren...
if (widthof(channelC) == 1)
{
mapping new = m_allocate(sizeof(channelC), 2);
foreach(string chname, mixed arr: channelC)
{
struct channel_base_s ch = (<channel_base_s> name: arr[0],
desc: arr[1]);
// die anderen beiden Werte bleiben 0
m_add(new, chname, ch, arr[2]);
}
channelC = new;
}
/* Die aktiven Ebenen und die Channel-History wird nicht nur lokal sondern
* auch noch im Memory gespeichert, dadurch bleibt sie auch ueber ein Reload
* erhalten.
Der folgende Code versucht, den Zeiger auf die alten Mappings aus dem
Memory zu holen. Falls das nicht moeglich ist, wird ein neuer erzeugt und
gegebenenfalls fuer spaeter im Memory abgelegt. */
// Hab ich die noetigen Rechte im Memory?
if (MEMORY->HaveRights())
{
// channelH und channels laden
channels = ({mapping}) MEMORY->Load("Channels");
// Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
if (!mappingp(channels))
{
// Mapping erzeugen
channels = ([]);
// und Zeiger auf das Mapping in den Memory schreiben
MEMORY->Save("Channels", channels);
do_complete_init = 1;
}
// Und das gleiche fuer die History
channelH = ({mapping}) MEMORY->Load("History");
// Wenns nich geklappt hat, hat der Memory noch keinen Zeiger
if (!mappingp(channelH))
{
// Mapping erzeugen
channelH = ([]);
// und Zeiger auf das Mapping in den Memory schreiben
MEMORY->Save("History", channelH);
// In diesem Fall muessen die Ebenenhistories auch erzeugt werden, falls
// es aktive Ebenen gibt.
foreach(string chname: channels)
channelH[chname] = ({});
}
}
else
{
// Keine Rechte im Memory, dann liegt das nur lokal und ist bei
// remove/destruct weg.
channelH = ([]);
channels = ([]);
do_complete_init = 1;
}
stats = (["time": time(),
"boot": capitalize(getuid(previous_object()) || "<Unbekannt>")]);
// Das muss auch laufen, wenn wir die alten Ebenen aus dem MEMORY bekommen
// haben, weil es ja neue Ebenen geben koennte, die dann erstellt werden
// muessen (verschwundete werden aber nicht aufgeraeumt!)
create_default_channels();
// Es muss fuer alle existierenden Ebenen die access_cl neu erstellt
// werden, die durch Neuladen des channeld verloren gingen, auch wenn die
// Ebenen im MEMORY lagen.
foreach(string chname, struct channel_s ch : channels)
{
if (ch.access_cl || !assert_supervisor(ch))
continue;
ch.access_cl = symbol_function("ch_check_access", ch.supervisor);
}
// <MasteR>-Ebene betreten, damit der channeld auf seine Kommandos auf
// dieser Ebene reagieren kann.
this_object()->join(CMNAME, this_object());
// Wenn wir die alten Ebenen nicht aus MEMORY hatten, gibts noch Dinge zu
// erledigen.
if (do_complete_init)
{
// Spieler muessen die Ebenen abonnieren. NPC und andere Objekte haben
// leider Pech gehabt, falls das nicht das erste Laden nach Reboot war.
users()->RegisterChannels();
// Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
// erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
// interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
// explizites call_other() auf this_object() gemacht, damit der
// Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
// einem externen.
this_object()->send(CMNAME, this_object(),
sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
sizeof(channels),
CountUsers()));
}
else
{
this_object()->send(CMNAME, this_object(),
sprintf(CMNAME " neugeladen. %d Ebenen mit %d Teilnehmern sind aktiv.",
sizeof(channels),
CountUsers()));
}
}
varargs void reset()
{
// Im Durchschnitt 1 Tag: 21.6h + random(4.8h), d.h. naechster reset ist
// zwischen 21.6h und 26.4h von jetzt.
set_next_reset(77760 + random(17280));
// inaktive Ebenen bereinigen
int timeout = INACTIVE_EXPIRE;
// Wir behalten immer ungefaehr die Haelfte von MAX_INACTIVE_CHANNELS
// inaktive Ebenen. In jeder Iteration wird das erlaubte Timeout reduziert,
// bis genug inaktive Ebenen weg sind, aber MIN_INACTIVE_LIFETIME bleiben
// Ebenen min. inaktiv bestehen.
while (sizeof(channelC) > MAX_INACTIVE_CHANNELS/2
&& timeout > MIN_INACTIVE_LIFETIME)
{
channelC = filter(channelC,
function int (string ch_name, mixed values)
{
int ts = values[1];
if (ts + timeout > time())
return 1;
// Ebenendaten koennen weg, inkl. History, die also auch loeschen
m_delete(channelH, ch_name);
return 0;
});
// timeout halbieren und neu versuchen wenn noetig.
timeout /= 2;
}
// achja, speichern sollten wir uns ggf. auch noch.
if (save_me_soon)
{
save_me_soon = 0;
save_object(CHANNEL_SAVE);
}
}
varargs int remove(int silent)
{
if (save_me_soon)
{
save_me_soon = 0;
save_object(CHANNEL_SAVE);
}
log_file("CHANNEL", sprintf("[%s] remove() durch %O gerufen. Speichern und "
"Ende.\n", dtime(time()),
this_interactive()||this_player()||previous_object()));
destruct(this_object());
return 1;
}
// name() - define the name of this object.
public varargs string name(int casus,int demon)
{
return CMNAME;
}
public varargs string Name(int casus, int demon)
{
return capitalize(CMNAME);
}
// Low-level function for adding members without access checks
// return values < 0 are errors, success is 1.
private int add_member(struct channel_s ch, object m)
{
if (IsChannelMember(ch, m))
return E_ALREADY_JOINED;
ch.members += ({ m });
return 1;
}
private void remove_all_members(struct channel_s ch)
{
// Einer geloeschten/inaktiven Ebene kann man nicht zuhoeren: Ebenenname
// aus der Ebenenliste aller Mitglieder austragen. Dabei werden sowohl ein-,
// als auch temporaer ausgeschaltete Ebenen beruecksichtigt.
string chname = lower_case(ch.name);
foreach(object listener : ch.members)
{
string* pl_chans = listener->QueryProp(P_CHANNELS);
if (pointerp(pl_chans))
{
listener->SetProp(P_CHANNELS, pl_chans-({chname}));
}
pl_chans = listener->QueryProp(P_SWAP_CHANNELS);
if (pointerp(pl_chans))
{
listener->SetProp(P_SWAP_CHANNELS, pl_chans-({chname}));
}
}
}
private void delete_channel(string chname, int force);
// Deaktiviert eine Ebene, behaelt aber einige Stammdaten in channelC und die
// History, so dass sie spaeter reaktiviert werden kann.
// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
// anwesend sind.
private void deactivate_channel(string chname, int force)
{
// Wenn zuviele inaktive Ebenen, wird sie geloescht statt deaktiviert.
if (sizeof(channelC) > MAX_INACTIVE_CHANNELS)
{
log_file("CHANNEL",
sprintf("[%s] Zuviele inaktive Ebenen. Channel %s geloescht statt "
"deaktiviert.\n", dtime(time()), chname));
this_object()->send(CMNAME, this_object(),
sprintf("Zuviele inaktive Ebenen. Ebene %s geloescht statt "
"deaktiviert.\n", chname));
delete_channel(chname, force);
return;
}
chname = lower_case(chname);
struct channel_s ch = channels[chname];
// Deaktivieren kann man nur aktive Ebenen.
if (!structp(ch))
return;
// Falls sie noch Zuhoerer hat, muss man sich erstmal um die kuemmern.
if (sizeof(ch.members))
{
// ohne <force> nur Ebenen ohne Zuhoerer deaktivieren.
if (!force)
{
raise_error(
sprintf("Attempt to deactivate channel %s with listeners.\n",
ch.name));
}
else
{
remove_all_members(ch);
}
}
// Einige Daten merken, damit sie reaktiviert werden kann, wenn jemand
// einloggt, der die Ebene abonniert hat.
#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
// Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
// richtige struct.
m_add(channelC, chname, to_struct(to_array(channels[chname])[0..3],
(<channel_base_s>)),
time());
#else
m_add(channelC, chname, to_struct(channels[chname], (<channel_base_s>)),
time());
#endif
// aktive Ebene loeschen bzw. deaktivieren.
m_delete(channels, chname);
// History wird nicht geloescht, damit sie noch verfuegbar ist, wenn die
// Ebene spaeter nochmal neu erstellt wird. Sie wird dann bereinigt, wenn
// channelC bereinigt wird.
stats["dispose"]++;
save_me_soon = 1;
}
// Loescht eine Ebene vollstaendig inkl. Stammdaten und History.
// Wenn <force>, dann wird wie Ebene sogar deaktiviert, wenn noch Zuhoerer
// anwesend sind.
private void delete_channel(string chname, int force)
{
chname = lower_case(chname);
struct channel_s ch = channels[chname];
// Ist die Ebene noch aktiv?
if (ch)
{
// Und hat sie Zuhoerer?
if (sizeof(ch.members))
{
// ohne <force> nur Ebenen ohne Zuhoerer loeschen.
if (!force)
{
raise_error(
sprintf("Attempt to delete channel %s with listeners.\n",
ch.name));
}
else
{
remove_all_members(ch);
}
}
stats["dispose"]++;
m_delete(channels, chname);
}
// Ab hier das gleiche fuer aktive und inaktive Ebenen.
m_delete(channelC, chname);
m_delete(channelH, chname);
save_me_soon = 1;
}
// Aendert das Supervisor-Objekt einer Ebene, ggf. mit Meldung.
// Wenn kein neuer SV angegeben, wird der aelteste Zuhoerer gewaehlt.
private int change_sv_object(struct channel_s ch, object new_sv)
{
if (!new_sv)
{
ch.members -= ({0});
if (sizeof(ch.members))
new_sv = ch.members[0];
else
return 0; // kein neuer SV moeglich.
}
// evtl. darf der supervisor aber nicht zu was anderes als dem creator
// wechseln. Ausserdem darf niemand supervisor werden, der nicht auf der
// Ebene ist.
if ( ((ch.flags & CHF_FIXED_SUPERVISOR)
&& new_sv != find_object(ch.creator))
|| !IsChannelMember(ch, new_sv)
)
return 0;
object old_sv = ch.supervisor;
ch.supervisor = new_sv;
ch.access_cl = symbol_function("ch_check_access", new_sv);
if (old_sv && new_sv
&& !old_sv->QueryProp(P_INVIS)
&& !new_sv->QueryProp(P_INVIS))
{
// Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
// erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
// interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
// explizites call_other() auf this_object() gemacht, damit der
// Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
// einem externen.
this_object()->send(ch.name, old_sv,
sprintf("uebergibt die Ebene an %s.",new_sv->name(WEN)),
MSG_EMOTE);
}
else if (old_sv && !old_sv->QueryProp(P_INVIS))
{
this_object()->send(ch.name, old_sv,
"uebergibt die Ebene an jemand anderen.", MSG_EMOTE);
}
else if (new_sv && !new_sv->QueryProp(P_INVIS))
{
this_object()->send(ch.name, new_sv,
"uebernimmt die Ebene von jemand anderem.", MSG_EMOTE);
}
return 1;
}
// Stellt sicher, dass einen Ebenen-Supervisor gibt. Wenn dies nicht moeglich
// ist (z.b. leere Ebene), dann wird die Ebene geloescht und 0
// zurueckgegeben. Allerdings kann nach dieser Funktion sehr wohl die
// access_cl 0 sein, wenn der SV keine oeffentliche definiert! In diesem Fall
// wird access() den Zugriff immer erlauben.
private int assert_supervisor(struct channel_s ch)
{
// Wenn der supervisor nicht mehr existiert, muss ein neuer gesucht werden.
if (!ch.supervisor)
{
// Wenn der Wechsel des SV verboten ist, wird versucht, den
// urspruenglichen Ersteller neuzuladen und zum neuen, alten Supervisor zu
// machen.
if (ch.flags & CHF_FIXED_SUPERVISOR)
{
object sv;
string err=catch(sv=load_object(ch.creator);publish);
if (!err)
{
// Juchu, die richtige SV ist wieder da. Sie muss noch auf die Ebene
// und kann dann wieder SV werden.
add_member(ch, sv);
if (!change_sv_object(ch, sv))
{
// ich wuesste nicht, was in change_sv_object in diesem Fall
// schiefgehen kann, daher einfach ein raise_error.
raise_error(sprintf("Supervisor von Channel %s konnte nicht "
"reaktiviert werden: %O\n",ch.name,sv));
}
}
// wenns nicht geklappt hat, wird die Ebene deaktiviert.
else
{
// Die inaktive Ebene kann wegen CHF_FIXED_SUPERVISOR nur vom
// urspruenglichen Ersteller reaktiviert/neu erstellt werden. Und
// solange der das nicht tut, ist weder die History zugaenglich, noch
// kann jemand sonst was damit machen. Wenn die inaktive Ebene
// irgendwann inkl. History expired wird, kann jemand anderes dann
// den Namen wieder verwenden und ein komplett neue Ebene erstellen.
deactivate_channel(lower_case(ch.name), 1);
log_file("CHANNEL",
sprintf("[%s] Channel %s deaktiviert. SV-Fehler: %O -> %O\n",
dtime(time()), ch.name, ch.supervisor, err));
return 0;
}
}
// Der normalfall ist aber, dass wir einfach einen supervisor aus dem
// Kreise der Zuhoerer bestimmen und zwar den aeltesten. Das macht
// change_sv_object().
else
{
if (!change_sv_object(ch, 0))
{
// wenn das nicht klappt, Ebene deaktivieren, vermutlich hat sie keine
// Zuhoerer.
deactivate_channel(lower_case(ch.name), 1);
log_file("CHANNEL",
sprintf("[%s] Kein SV, deaktiviere channel %s.\n",
dtime(time()), ch.name));
return 0;
}
}
}
return 1;
}
// access() - check access by looking for the right argument types and
// calling access closures respectively
// SEE: new, join, leave, send, list, users
// Wertebereich: 0 fuer Zugriff verweigert, 1 fuer Zugriff erlaubt, 2 fuer
// Zugriff erlaubt fuer privilegierte Objekte, die senden duerfen ohne
// Zuhoerer zu sein. (Die Aufrufer akzeptieren aber auch alle negativen Werte
// als Erfolg und alles ueber >2 als privilegiert.)
varargs private int access(struct channel_s ch, object user, string cmd,
string txt)
{
if (!ch || !user)
return 0;
// Dieses Objekt und Root-Objekte duerfen auf der Ebene senden, ohne
// Mitglied zu sein. Das ist die Folge der zurueckgegebenen 2.
// Ausserdem duerfen sie auch alles andere machen unter Umgehung aller
// Supervisoren. (z.B. kann dieses Objekt sogar Meldungen im Namen anderer
// Objekte faken)
// Die Pruefung erfolgt absichtlich vor assert_supervisor(), damit der
// CHANNELD auch in temporaeren SV-losen Zustaenden was machen kann.
if ( !previous_object(1) || previous_object(1) == this_object() ||
getuid(previous_object(1)) == ROOTID)
return 2;
// Objekte duerfen keine Meldungen im Namen anderer Objekte faken, d.h. der
// vermeintliche <user> muss auch der Aufrufer sein. Ausser darf auch sonst
// kein Objekt was fuer ein anderes Objekt duerfen, sonst kann jemand z.B.
// eine History abfragen indem einfach ein anderes Objekt uebergeben wird.
if (previous_object(1) != user)
{
// Kurzfristiger Workaround: Whitelist fuer bestimmte Objekte, die im
// Namen von Spielern senden mit dem Spieler als Absender, z.B. die Leiche
// beim verspotten.
switch(object_name(previous_object(1)))
{
case "/std/corpse":
break;
default:
return 0;
}
}
if (IsBanned(user, cmd))
return 0;
// Wenn kein SV-Objekt mehr existiert und kein neues bestimmt werden konnte,
// wurde die Ebene ausfgeloest. In diesem Fall auch den Zugriff verweigern.
if (!assert_supervisor(ch))
return 0;
// Wenn closure jetzt dennoch 0, wird der Zugriff erlaubt.
if (!ch.access_cl)
return 1;
// Das SV-Objekt wird gefragt, ob der Zugriff erlaubt ist. Dieses erfolgt
// fuer EM+ aber nur, wenn es das Default-SV-Objekt ist, damit
// nicht beliebige SV-Objekt EMs den Zugriff verweigern koennen. Ebenen mit
// Default-SV koennen aber auch EM+ Zugriff verweigern.
if (IS_ARCH(previous_object(1))
&& ch.supervisor != find_object(DEFAULTSV))
return 1;
return funcall(ch.access_cl, lower_case(ch.name), user, cmd, txt);
}
// Neue Ebene <ch> erstellen mit <owner> als Ebenenbesitzer.
// <desc> kann die statische Beschreibung der Ebene sein oder eine Closure,
// die dynamisch aktualisierte Infos ausgibt.
// Das Objekt <owner> sollte eine Funktion ch_check_access() definieren, die
// gerufen wird, wenn eine Ebenenaktion vom Typ join/leave/send/list/users
// eingeht.
#define IGNORE "^/xx"
public varargs int new(string ch_name, object owner, string|closure desc,
int channel_flags)
{
// Kein Channelmaster angegeben, oder wir sind es selbst, aber der Aufruf
// kam von ausserhalb. (Nur der channeld selbst darf sich als Channelmaster
// fuer eine neue Ebene eintragen.)
if (!objectp(owner) || (owner == this_object() && extern_call()) )
return E_ACCESS_DENIED;
// Kein gescheiter Channelname angegeben.
if (!stringp(ch_name) || !sizeof(ch_name))
return E_ACCESS_DENIED;
// Channel schon vorhanden oder schon alle Channel-Slots belegt.
if (channels[lower_case(ch_name)] || sizeof(channels) >= MAX_CHANNELS)
return E_ACCESS_DENIED;
// Der angegebene Ebenenbesitzer darf keine Ebenen erstellen, wenn fuer ihn
// ein Bann auf die Aktion C_NEW besteht, oder das Ignore-Pattern auf
// seinen Objektnamen matcht.
if (IsBanned(owner,C_NEW) || regmatch(object_name(owner), IGNORE))
return E_ACCESS_DENIED;
// Zunaechst pruefen, ob eine alte, inaktive Ebene mit dem Namen noch
// existiert.
struct channel_base_s cbase = channelC[lower_case(ch_name)];
struct channel_s ch;
if (cbase)
{
// Wenn bei Reaktivierung von Ebenen (auch mit neuer Beschreibung *g*) der
// neue owner != dem urspruenglichen Ersteller der Ebene ist und das Flag
// CHF_FIXED_SUPERVISOR gesetzt ist, wird die Reaktivierung abgebrochen,
// damit niemand inaktive Ebenen und deren History auf diesem Wege
// uebernehmen kann, d.h. den Supervisor ersetzen kann.
if ((cbase.flags & CHF_FIXED_SUPERVISOR)
&& object_name(owner) != cbase.creator)
return E_ACCESS_DENIED;
// Alte Daten der Ebene uebernehmen
#if __VERSION_MINOR__ == 6 && __VERSION_MICRO__ < 4
// Workaround fuer Bug in to_struct: erst in array wandeln, dann in die
// richtige struct.
ch = to_struct(to_array(cbase), (<channel_s>));
#else
ch = to_struct(cbase, (<channel_s>));
#endif
// Wenn eine Beschreibung uebergeben, dann ersetzt sie jetzt die alte
if (desc)
ch.desc = desc;
// creator bleibt natuerlich bestehen. Die Flags auch. Wir behalten auch
// die Schreibweise (Gross-/Kleinschreibung) des Namens aus
// Konsistenzgruenden bei.
}
else
{
// Wenn keine Beschreibung und keine inaktive Ebene da ist, wirds nen
// Fehler...
if (!desc)
return E_ACCESS_DENIED;
// prima, alles da. Dann wird ein ganz frische neue Ebenenstruktur
// erzeugt.
ch = (<channel_s> name: ch_name, desc: desc, creator: object_name(owner),
flags: channel_flags);
}
ch_name = lower_case(ch_name);
ch.members = ({ owner });
ch.supervisor = owner;
// ch_check_access() dient der Zugriffskontrolle und entscheidet, ob die
// Nachricht gesendet werden darf oder nicht.
ch.access_cl = symbol_function("ch_check_access", owner);
m_add(channels, ch_name, ch);
// History fuer eine Ebene nur dann initialisieren, wenn es sie noch
// nicht gibt.
if (!pointerp(channelH[ch_name]))
channelH[ch_name] = ({});
// Datenstruktur einer ggf. inaktiven Ebene mit dem Namen in channelC kann
// jetzt auch weg.
if (cbase)
m_delete(channelC, ch_name);
// Erstellen neuer Ebenen loggen, wenn wir nicht selbst der Ersteller sind.
if (owner != this_object())
log_file("CHANNEL.new", sprintf("[%s] Neue Ebene %s: %O %O\n",
dtime(time()), ch.name, owner, desc));
// Erfolgsmeldung ausgeben, ausser bei unsichtbarem Ebenenbesitzer.
if (!owner->QueryProp(P_INVIS))
{
// Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
// erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
// interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
// explizites call_other() auf this_object() gemacht, damit der
// Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
// einem externen.
this_object()->send(CMNAME, owner,
"laesst die Ebene '" + ch.name + "' entstehen.", MSG_EMOTE);
}
stats["new"]++;
return (0);
}
// Objekt <pl> betritt Ebene <ch>. Dies wird zugelassen, wenn <pl> die
// Berechtigung hat und noch nicht Mitglied ist. (Man kann einer Ebene nicht
// zweimal beitreten.)
public int join(string chname, object joining)
{
struct channel_s ch = channels[lower_case(chname)];
/* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
zu erzeugen, weil access() mit extern_call() und previous_object()
arbeitet und sichergestellt sein muss, dass das in jedem Fall das
richtige ist. */
if (!funcall(#'access, ch, joining, C_JOIN))
return E_ACCESS_DENIED;
//TODO: Sollte der creator das Recht auf join haben, auch wenn der aktuelle
//SV es verweigert? (s.u.)
int res = add_member(ch, joining);
if (res != 1)
return res;
// Wenn <joining> der urspruengliche Ersteller der Ebene und kein
// Spieler ist, wird es automatisch wieder zum Supervisor.
if (!query_once_interactive(joining)
&& object_name(joining) == ch.creator)
change_sv_object(ch, joining);
return 0;
}
// Objekt <pl> verlaesst Ebene <ch>.
// Zugriffsrechte werden nur der Vollstaendigkeit halber geprueft; es duerfte
// normalerweise keinen Grund geben, das Verlassen einer Ebene zu verbieten.
// Dies ist in ch_check_access() so geregelt, allerdings koennte dem Objekt
// <pl> das Verlassen auf Grund eines Banns verboten sein.
// Wenn kein Zuhoerer mehr auf der Ebene ist, loest sie sich auf.
public int leave(string chname, object leaving)
{
struct channel_s ch = channels[lower_case(chname)];
// Nicht-existierenden Ebenen soll das Spielerobjekt austragen, also tun wir
// so, als sei das erfolgreich gewesen.
if (!ch)
return 0;
ch.members -= ({0}); // kaputte Objekte erstmal raus
if (!IsChannelMember(ch, leaving))
return E_NOT_MEMBER;
/* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
zu erzeugen, weil access() mit extern_call() und previous_object()
arbeitet und sichergestellt sein muss, dass das in jedem Fall das
richtige ist. */
if (!funcall(#'access, ch, leaving, C_LEAVE))
return E_ACCESS_DENIED;
// Dann mal den Zuhoerer raus.
ch.members -= ({leaving});
// Wenn auf der Ebene jetzt noch Objekte zuhoeren, muss ggf. der SV
// wechseln.
if (sizeof(ch.members))
{
// Kontrolle an jemand anderen uebergeben, wenn der Ebenensupervisor
// diese verlassen hat. change_sv_object() waehlt per Default den
// aeltesten Zuhoerer.
if (leaving == ch.supervisor)
{
change_sv_object(ch, 0);
}
}
// ansonsten Ebene loeschen, wenn keiner zuhoert.
// Kommentar: Supervisoren sind auch Zuhoerer auf der Ebene. Wenn keine
// Zuhoerer mehr, folglich auch kein Supervisor mehr da.
else
{
// Der Letzte macht das Licht aus, aber nur, wenn er nicht unsichtbar ist.
// Wenn Spieler, NPC, Clone oder Channeld als letztes die Ebene verlassen,
// wird diese zerstoert, mit Meldung.
if (!leaving->QueryProp(P_INVIS))
{
// Die Zugriffskontrolle auf die Ebenen wird von der Funktion access()
// erledigt. Weil sowohl externe Aufrufe aus dem Spielerobjekt, als auch
// interne Aufrufe aus diesem Objekt vorkommen koennen, wird hier ein
// explizites call_other() auf this_object() gemacht, damit der
// Caller-Stack bei dem internen Aufruf denselben Aufbau hat wie bei
// einem externen.
this_object()->send(CMNAME, leaving,
"verlaesst als "+
(leaving->QueryProp(P_GENDER) == 1 ? "Letzter" : "Letzte")+
" die Ebene '" + ch.name + "', worauf diese sich in "
"einem Blitz oktarinen Lichts aufloest.", MSG_EMOTE);
}
deactivate_channel(lower_case(ch.name),0);
}
return (0);
}
// Nachricht <msg> vom Typ <type> mit Absender <pl> auf der Ebene <ch> posten,
// sofern <pl> dort senden darf.
public varargs int send(string chname, object sender, string msg, int type)
{
chname = lower_case(chname);
struct channel_s ch = channels[chname];
/* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
zu erzeugen, weil access() mit extern_call() und previous_object()
arbeitet und sichergestellt sein muss, dass das in jedem Fall das
richtige ist. */
int a = funcall(#'access, ch, sender, C_SEND, msg);
if (!a)
return E_ACCESS_DENIED;
// a<2 bedeutet effektiv a==1 (weil a==0 oben rausfaellt), was dem
// Rueckgabewert von ch_check_access() entspricht, wenn die Aktion zugelassen
// wird. access() liefert allerdings 2 fuer "privilegierte" Objekte (z.B.
// ROOT-Objekte oder den channeld selber). Der Effekt ist, dass diese
// Objekte auf Ebenen senden duerfen, auf denen sie nicht zuhoeren.
if (a < 2 && !IsChannelMember(ch, sender))
return E_NOT_MEMBER;
if (!msg || !stringp(msg) || !sizeof(msg))
return E_EMPTY_MESSAGE;
// Jedem Mitglied der Ebene wird die Nachricht ueber die Funktion
// ChannelMessage() zugestellt. Der Channeld selbst hat ebenfalls eine
// Funktion dieses Namens, so dass er, falls er Mitglied der Ebene ist, die
// Nachricht ebenfalls erhaelt.
// Um die Kommandos der Ebene <MasteR> verarbeiten zu koennen, muss er
// demzufolge Mitglied dieser Ebene sein. Da Ebenenbesitzer automatisch
// auch Mitglied sind, wird die Ebene <MasteR> im create() mittels new()
// erzeugt und der Channeld als Besitzer angegeben.
// Die Aufrufkette ist dann wie folgt:
// Eingabe "-< xyz" => pl::ChannelParser() => send() => ChannelMessage()
(ch.members)->ChannelMessage(
({ ch.name, sender, msg, type}));
if (sizeof(channelH[chname]) > MAX_HIST_SIZE)
channelH[chname] = channelH[chname][1..];
channelH[chname] +=
({ ({ ch.name,
(sender->QueryProp(P_INVIS)
? "/(" + capitalize(getuid(sender)) + ")$"
: "")
+ (sender->Name(WER, 2) || "<Unbekannt>"),
msg + " <" + strftime("%a, %H:%M:%S") + ">\n",
type }) });
return (0);
}
// Gibt ein Mapping mit allen Ebenen aus, die das Objekt <pl> lesen kann,
// oder einen Integer-Fehlercode
public int|mapping list(object pl)
{
mapping chs = ([]);
foreach(string chname, struct channel_s ch : channels)
{
/* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
zu erzeugen, weil access() mit extern_call() und previous_object()
arbeitet und sichergestellt sein muss, dass das in jedem Fall das
richtige ist. */
if(funcall(#'access, ch, pl, C_LIST))
{
ch.members = filter(ch.members, #'objectp);
m_add(chs, chname, ({ch.members, ch.access_cl, ch.desc,
ch.supervisor, ch.name }) );
}
}
if (!sizeof(chs))
return E_ACCESS_DENIED;
return (chs);
}
// Ebene suchen, deren Name <ch> enthaelt, und auf der Objekt <pl> senden darf
// Rueckgabewerte:
// - den gefundenen Namen als String
// - String-Array, wenn es mehrere Treffer gibt
// - 0, wenn es keinen Treffer gibt
public string|string* find(string chname, object pl)
{
chname = lower_case(chname);
// Suchstring <ch> muss Formatanforderung erfuellen;
// TODO: soll das ein Check auf gueltigen Ebenennamen als Input sein?
// Wenn ja, muesste laut Manpage mehr geprueft werden:
// "Gueltige Namen setzen sich zusammen aus den Buchstaben a-z, A-Z sowie
// #$%&@<>-." Es wuerden also $%&@ fehlen.
if (!regmatch(chname, "^[<>a-z0-9#-]+$"))
return 0;
// Der Anfang des Ebenennamens muss dem Suchstring entsprechen und das
// Objekt <pl> muss auf dieser Ebene senden duerfen, damit der Ebenenname
// in das Suchergebnis aufgenommen wird.
string* chs = filter(m_indices(channels), function int (string ch_n) {
/* Normalerweise benoetigen wir ein funcall() auf
Closure-Operator, um einen neuen Eintrag im Caller Stack
zu erzeugen, weil access() mit extern_call() und
previous_object() arbeitet und sichergestellt sein muss,
dass das in jedem Fall das richtige ist.
In diesem Fall gibt es schon durch filter() einen neuen
Eintrag, daher muss access() direkt gerufen werden, sonst
ist previous_object(1) == this_object(). */
return ( stringp(regmatch(ch_n, "^"+chname)) &&
access(channels[ch_n], pl, C_FIND) );
});
int num_channels = sizeof(chs);
if (num_channels > 1)
return chs;
else if (num_channels == 1)
return chs[0];
else
return 0;
}
// Ebenen-History abfragen.
public int|<int|string>** history(string chname, object pl)
{
struct channel_s ch = channels[lower_case(chname)];
/* funcall() auf Closure-Operator, um einen neuen Eintrag im Caller Stack
zu erzeugen, weil access() mit extern_call() und previous_object()
arbeitet und sichergestellt sein muss, dass das in jedem Fall das
richtige ist. */
if (!funcall(#'access, ch, pl, C_JOIN))
return E_ACCESS_DENIED;
else
return channelH[chname];
}
// Wird aus der Shell gerufen, fuer das Erzmagier-Kommando "kill".
public int remove_channel(string chname, object pl)
{
if (!member(channels, chname))
return E_ACCESS_DENIED;
//TODO: integrieren in access()?
if (previous_object() != this_object())
{
if (!stringp(chname) ||
pl != this_player() || this_player() != this_interactive() ||
this_interactive() != previous_object() ||
!IS_ARCH(this_interactive()))
return E_ACCESS_DENIED;
}
delete_channel(lower_case(chname), 1);
return (0);
}
// Wird aus der Shell aufgerufen, fuer das Erzmagier-Kommando "clear".
public int clear_history(string chname)
{
if (!member(channelH, chname))
return E_ACCESS_DENIED;
//TODO: mit access() vereinigen?
// Sicherheitsabfragen
if (previous_object() != this_object())
{
if (!stringp(chname) ||
this_player() != this_interactive() ||
this_interactive() != previous_object() ||
!IS_ARCH(this_interactive()))
return E_ACCESS_DENIED;
}
chname=lower_case(chname);
// History des Channels loeschen (ohne die ebene als ganzes, daher Key nicht
// aus dem mapping loeschen.)
if (pointerp(channelH[chname]))
channelH[chname] = ({});
return 0;
}
// Aendert den Ersteller/Besitzer der Ebene.
// Achtung: das ist nicht das gleiche wie der aktuelle Supervisor!
public int transfer_ownership(string chname, object new_owner)
{
struct channel_s ch = channels[lower_case(chname)];
if (!ch)
return E_ACCESS_DENIED;
// Nur der aktuelle Besitzer oder EM+ darf die Ebene verschenken
if (ch.creator != object_name(previous_object())
&& !IS_ARCH(previous_object()))
return E_ACCESS_DENIED;
ch.creator = object_name(new_owner);
return 1;
}
// Aendert den Flags der Ebene.
public int change_channel_flags(string chname, int newflags)
{
struct channel_s ch = channels[lower_case(chname)];
if (!ch)
return E_ACCESS_DENIED;
// Nur der aktuelle Besitzer, Supervisor oder EM+ darf die Flags aendern.
if (ch.supervisor != previous_object()
&& ch.creator != object_name(previous_object())
&& !IS_ARCH(previous_object()))
return E_ACCESS_DENIED;
ch.flags = newflags;
return 1;
}