blob: b5db1aeee270f3b39e94bcb847b8bd6d04c7e25a [file] [log] [blame]
// MorgenGrauen MUDlib
//
// channel.c -- channel client
//
// $Id: channel.c 9404 2015-12-13 00:21:44Z Zesstra $
#pragma strong_types
#pragma save_types
#pragma range_check
#pragma no_clone
#define NEED_PROTOTYPES
#include <util.h>
#include <thing/properties.h>
#include <living/comm.h>
#include <player.h>
#include <player/comm.h>
#include <daemon.h>
#include <player/gmcp.h>
#undef NEED_PROTOTYPES
#include <wizlevels.h>
#include <defines.h>
#include <properties.h>
#include <sys_debug.h>
#include <regexp.h>
#define P_CHANNEL_SHORT "short_channels"
#define CHANNELCMDS "[#@%$&()<>a-zA-Z0-9\\-]"
#define DEFAULT_CHANNELS ({"Abenteuer", "Anfaenger", "Grats", "Tod", "ZT"})
#define DEFAULT_SHORTCUTS \
([ \
"b":"Abenteuer", \
"a":"Allgemein", \
"B":"Beileid", \
"q":"D-chat", \
"G":"Grats", \
"M":"Moerder", \
"h":"Seher", \
"T":"Tod", \
])
#define WIZARD_SHORTCUTS \
([ \
"P":"D-code", \
"D":"Debug", \
"O":"Intercode", \
"I":"Intermud", \
"m":"Magier", \
])
// TODO: <shortcut> wird eigentlich nur in getChannel() sinnvoll verwendet
// Koennte man an sich komplett einsparen und dort wie ueberall sonst auch
// einfach nur mit P_CHANNEL_SHORT arbeiten.
private nosave mapping shortcut;
private nosave int c_status;
void create()
{
Set(P_CHANNELS, SAVE, F_MODE);
Set(P_CHANNELS, DEFAULT_CHANNELS);
Set(P_SWAP_CHANNELS, SAVE, F_MODE);
Set(P_STD_CHANNEL, "Allgemein");
Set(P_STD_CHANNEL, SAVE, F_MODE);
Set(P_CHANNEL_SHORT, SAVE, F_MODE);
Set(P_CHANNEL_SHORT, DEFAULT_SHORTCUTS
+ (IS_LEARNER(ME) ? WIZARD_SHORTCUTS : ([])));
}
static <int|string>** _query_localcmds()
{
return ({({"-","ChannelParser", 1, 0}),
({"ebene", "ChannelAdmin", 0, 0}),
({"ebenen", "ChannelAdmin", 1, 0}),
});
}
string* RegisterChannels()
{
if (extern_call() &&
previous_object() != find_object(CHMASTER))
return 0;
c_status = 0;
shortcut = QueryProp(P_CHANNEL_SHORT);
SetProp(P_CHANNELS, map(QueryProp(P_CHANNELS) || ({}), #'lower_case));
closure cl = symbol_function("join", CHMASTER);
string* err;
if (closurep(cl))
{
err = filter(QueryProp(P_CHANNELS), cl, ME);
}
if (QueryProp(P_LEVEL) < 5)
return err;
// CHMASTER->new() gibt bei Erfolg 0 zurueck, d.h. es bleiben
// alle Elemente erhalten, deren Channel nicht erstellt werden konnten.
// Die werden an die Aufrufer zurueckgegeben.
return filter(err, "new", CHMASTER, ME);
}
string* RemoveChannels()
{
if (extern_call() &&
previous_object() != find_object(CHMASTER))
return 0;
string* err = ({});
if (!c_status)
c_status = 1;
else
return err;
closure cl = symbol_function("leave", CHMASTER);
if (closurep(cl))
{
err = filter(QueryProp(P_CHANNELS), cl, ME);
SetProp(P_CHANNELS, QueryProp(P_CHANNELS) - err);
}
return err;
}
varargs private string getName(string|object|closure who, int fall) {
// Objekt zur Closure raussuchen. <o> ist danach entweder jenes Objekt,
// oder aber <who> selbst, das Objekt oder String sein kann.
//
// query_closure_object() KANN auch 0 oder -1 zurueckgeben. In beiden
// Faellen fuehrt das hier zu einem Laufzeit-Typfehler, weil ein Integer
// bzw. die urspruengliche Closure zugewiesen wird. Diesen Fall abzufangen
// und separat zu behandeln, hilft nicht, weil es auch keine alternative
// Moeglichkeit gibt, <who> in verwendbare Daten umzuwandeln, denn das ist
// ja offenbar eine kaputte Closure. Dieser Fall ist ausserdem anscheinend
// so exotisch, dass wir vorerst auf Absicherungen mittels catch()
// verzichten.
string|object o = closurep(who) ? query_closure_object(who) : who;
// Ist es ein String, pruefen wir, ob es vielleicht ein Objektname ist,
// indem wir es in ein Objekt umzuwandeln versuchen. Schlaegt das fehl,
// schreiben wir <who> wieder zurueck, so dass <o> wieder der urspruengliche
// String ist.
if (stringp(o) && sizeof(o))
o = find_object(o) || who;
// Objekte
if (objectp(o))
{
// Magier sehen unsichtbare nicht nur als "Jemand"
if (o->QueryProp(P_INVIS) && IS_LEARNING(ME))
return "("+capitalize(getuid(o))+")";
// Froesche mit Namen versorgen.
if (o->QueryProp(P_FROG))
return "Frosch "+capitalize(getuid(o));
// Default (Unsichtbare als "Jemand" (s. Name()))
return o->Name(fall, 2)||"<Unbekannt>";
}
// Strings
else if (stringp(o) && sizeof(o))
{
if (o[0] == '/')
{
// unsichtbare Objekte...
int p = strstr(o, "$");
if (p != -1)
{
// Magier im Magiermodus kriegen den Realnamen, andere nicht.
if (IS_LEARNING(ME))
return o[1..p-1];
else
return o[p+1..];
}
else // doch nicht unsichtbar
return (fall == WESSEN ? o+"s" : o);
}
else
// nicht unsichtbar
return (fall == WESSEN ? o+"s" : o);
}
// Fall-through
return "<Unbekannt>";
}
// <nonint> unterdrueckt die Ausgabe an den Spieler und liefert den Text
// zurueck. Wird nur fuer die Ebenenhistory benutzt.
string ChannelMessage(<string|object|int>* msg, int nonint)
{
string channel_message;
string channel = msg[0];
// Wenn eine Ebenenmeldung ausgegeben werden soll, ist msg[1] ein Objekt,
// im Fall der History aber ein String. Daher wird <sender> als Union
// deklariert. Das ist unproblematisch, weil die beiden Datentypen
// komplett getrennte Wege nehmen: ein Objekt wird an ReceiveMsg()
// durchgereicht (Ebenenmeldung). Ein String wird direkt in die Meldung
// (History) eingebaut, diese an den ChannelParser() zurueckgegeben, der
// sie via More() ausgibt.
string|object sender = msg[1];
string message = msg[2];
int msg_type = msg[3];
if (previous_object() != find_object(CHMASTER) &&
previous_object() != ME )
return 0;
string sender_name = getName(sender, msg_type == MSG_GEMOTE ? WESSEN : WER);
int prepend_indent_flag =
QueryProp(P_MESSAGE_PREPEND) ? BS_PREPEND_INDENT : 0;
switch (msg_type)
{
case MSG_EMPTY:
channel_message= message+"\n";
break;
case MSG_GEMOTE:
case MSG_EMOTE:
channel_message = break_string(sender_name + " "+ message+"]", 78,
sprintf("[%s:", channel),
BS_INDENT_ONCE|prepend_indent_flag);
break;
case MSG_SAY:
default:
string presay = sprintf("[%s:%s] ", channel, sender_name);
channel_message = break_string(message, max(78,sizeof(presay)+10),
presay, prepend_indent_flag);
break;
}
if (nonint)
return channel_message;
// Wenn GMCP sich um Uebertragung der Nachricht kuemmert, wird ReceiveMsg()
// nicht mehr aufgerufen. getName leider nochmal aufrufen, weil GMCP den
// Namen im Nominativ braucht.
if (msg_type == MSG_GEMOTE)
sender_name = getName(sender, WER);
if (GMCP_Channel(channel_message, channel, sender_name) != 1)
{
// Der Ebenenname muss in Kleinbuchstaben uebergeben werden, damit die
// Ignorierepruefung funktioniert. Die ignorierestrings sind naemlich alle
// kleingeschrieben.
ReceiveMsg(channel_message,
MT_COMM|MT_FAR|MSG_DONT_STORE|MSG_DONT_WRAP|MSG_ALERT,
MA_CHANNEL"." + lower_case(channel), 0, sender);
}
return 0;
}
// Defines fuer den Zugriff auf die Channeldaten in der vom CHANNELD
// erhaltenen Kanalliste.
#define I_MEMBER 0
#define I_ACCESS 1
#define I_INFO 2
#define I_SUPERVISOR 3
#define I_NAME 4
private void createList(mapping ch_list, string* p_channels,
int show_only_subscribed) {
// Kopfzeile der Tabelle erstellen.
string listhead =
sprintf("%-12.12' 's [A] %|12' 's (%-3' 's) %-42.42s\n",
"Name", "Eigner", "Sp", "Beschreibung");
// Linie drunter.
listhead += ("-"*78 + "\n");
// Rest der Daten erstmal in einem Array zusammenstellen, um es
// anschliessend sortieren zu koennen.
string* entries = ({});
object* ch_members;
string|object ch_master;
string ch_real_name, ch_description;
closure|string ch_access;
closure|string ch_info;
string sh;
foreach(string chname, <object*|closure|string|object>* chdata : ch_list)
{
// Wenn nur abonnierte Ebenen aufgelistet werden sollen, dann alle
// ueberspringen, die nicht in P_CHANNELS stehen.
int is_subscribed = (member(p_channels, chname) > -1);
if (show_only_subscribed && !is_subscribed)
continue;
ch_members = chdata[I_MEMBER];
ch_master = chdata[I_SUPERVISOR];
ch_access = chdata[I_ACCESS];
ch_real_name = chdata[I_NAME];
ch_info = chdata[I_INFO];
sh = "";
// Ist eine Closure als I_INFO eingetragen, zu der es auch ein Objekt
// gibt, tragen wir deren Rueckgabewert als Beschreibung ein.
if (closurep(ch_info) && objectp(query_closure_object(ch_info))) {
ch_description = funcall(ch_info);
}
// Ist es ein String, wird er unveraendert uebernommen.
else if (stringp(ch_info)) {
ch_description = ch_info;
}
// Sollte nirgends etwas eingetragen sein, oder die Closure 0 zurueck-
// gegeben haben, gibt es eine Defaultbeschreibung. Man haette die
// Variable auch schon mit dem Default initialisieren koennen, der kann
// aber bei Rueckgabe von 0 aus der Closure wieder ueberschrieben werden.
// Daher passiert das erst hier.
ch_description ||= "- Keine Beschreibung -";
// Wir brauchen noch das vom Spieler festgelegte Kuerzel fuer die aktuell
// bearbeitete Ebene, falls vorhanden, um es in die Liste eintragen
// zu koennen.
foreach(string _sh_cut, string _chan_name : shortcut) {
if ( lower_case(_chan_name) == chname ) {
sh = _sh_cut;
break;
}
}
// Jetzt haben wir endlich alle Infos beisammen und koennen die
// Eintraege zusammenbauen.
entries += ({
sprintf("%-12.12'.'s %c[%-1.1s] %|12.12' 's (%-|3' 'd) %-42.42s\n",
ch_real_name,
is_subscribed ? '*' : ' ',
sh,
ch_master ? getName(ch_master) : getName(ch_access),
sizeof(ch_members),
ch_description
) });
}
// alphabetisch sortieren
entries = sort_array(entries, #'>);
More( listhead + // Listenkopf
implode(entries, "") + // Eintraege
"-"*78+"\n" ); // Linie drunter
}
private string|string* getChannel(string ch)
{
if (!sizeof(ch))
ch = QueryProp(P_STD_CHANNEL);
if (shortcut && shortcut[ch])
ch = shortcut[ch];
return CHMASTER->find(ch, ME);
}
int ChannelParser(string args)
{
string|string* ch;
int type, err;
string tmp;
notify_fail("Benutzung: -<Ebene>[ ]['|:|;]<Text>\n"
" -<Ebene>[+|-|?|!|*]\n"
" -?\n");
args = _unparsed_args();
string|string* cmd = query_verb();
if (!cmd && !args)
return 0;
if (!args)
args = "";
cmd = cmd[1..];
cmd = regexplode(cmd, "^" CHANNELCMDS "*" "([+-]|\\!|\\?|\\*)*");
if (sizeof(cmd) > 1)
{
//z.B. cmd= ({"","allgemein",":testet"})
if (sizeof(cmd[1]) > 1 && strstr("+-?!*", cmd[1][<1..<1]) > -1)
tmp = cmd[1][0..<2];
else
tmp = cmd[1];
if (cmd[1] != "?" && cmd[1] != "!")
{
ch = getChannel(tmp);
if (pointerp(ch))
{
notify_fail("Diese Angabe war nicht eindeutig! "
"Folgende Ebenen passen:\n"+
implode(ch, ", ")+"\n");
return 0;
}
else if (!ch)
{
notify_fail("Die Ebene '"+tmp+ "' gibt es nicht!\n");
return 0;
}
}
if (sizeof(cmd[1])) {
switch (cmd[1][<1]) {
case '+':
switch (CHMASTER->join(ch, ME))
{
case E_ACCESS_DENIED:
notify_fail("Du darfst an die Ebene '"+ch+"' nicht heran.\n");
return 0;
case E_ALREADY_JOINED:
notify_fail("Du hast diese Ebene schon betreten!\n");
return 0;
default:
break;
}
tell_object(ME,"Du betrittst die Ebene '"+ch+"'.\n");
if (member(QueryProp(P_CHANNELS), ch = lower_case(ch)) == -1)
SetProp(P_CHANNELS, QueryProp(P_CHANNELS) + ({ ch }));
return 1;
case '-':
switch (CHMASTER->leave(ch, ME))
{
case E_ACCESS_DENIED:
tell_object(ME,"Du kannst die Ebene '"+ch+"' nicht "
"verlassen.\n");
break;
case E_NOT_MEMBER:
tell_object(ME,"Wie willst Du eine Ebene verlassen, welche Du "
"nicht betreten hast?\n");
break;
default:
tell_object(ME,"Du verlaesst die Ebene '"+ch+"'.\n");
SetProp(P_CHANNELS,
QueryProp(P_CHANNELS) - ({ lower_case(ch), ch }));
break;
}
return 1;
case '!':
case '?':
mapping l = CHMASTER->list(ME);
if (mappingp(l))
{
// Es wurde ein Channel angegeben, dessen Infos gefragt sind.
if (stringp(ch) && sizeof(ch) && pointerp(l[ch = lower_case(ch)]))
{
<object*|closure|string|object>* chdata = l[ch];
string* m =
sort_array(map(chdata[I_MEMBER], #'getName, WER), #'>);
string wen;
switch(sizeof(m)) {
case 1: wen = "ein Gesicht"; break;
case 0: wen = "niemanden"; break;
// TODO: mittels Zahlwoerter-Objekt die Zahl als Text
// ausgeben?
default: wen = sprintf("%d Gesichter", sizeof(m)); break;
}
tell_object(ME,
chdata[I_NAME]+", "+funcall(chdata[I_INFO])+".\n" +
"Du siehst "+wen+" auf der Ebene '"+chdata[I_NAME]+"':\n"+
break_string(CountUp(m), 78) + getName(chdata[I_SUPERVISOR])
+ " hat das Sagen auf dieser Ebene.\n");
}
// kein Channel angegeben, dann Gesamtliste erzeugen
else
{
// Wenn nur die abonnierten angezeigt werden sollen
// (Kommando -!), wird die Bedingung im 3. Argument == 1, so
// dass createList() die reduzierte Tabelle erzeugt.
createList(l, QueryProp(P_CHANNELS), (cmd[1][<1] == '!'));
}
}
return 1;
case '*':
int|<int|string>** hist = CHMASTER->history(ch, ME);
if (!pointerp(hist) || !sizeof(hist))
{
tell_object(ME,"Es ist keine Geschichte fuer '"+ch+
"' verfuegbar.\n");
return 1;
}
int amount = to_int(cmd[2]);
if (amount <= 0 || amount >= sizeof(hist))
amount = sizeof(hist);
string txt = "Folgendes ist auf '"+ch+"' passiert:\n" +
implode(map(hist[<amount..], #'ChannelMessage, 1), "");
More(txt);
return 1;
default:
break;
}
}
}
cmd = implode(cmd[2..], "");
if (sizeof(cmd))
args = cmd + (sizeof(args) ? " " : "") + args;
// KOntrollchars ausfiltern.
args = regreplace(args, "[[:cntrl:]]", "", RE_PCRE|RE_GLOBAL);
if (!sizeof(args))
return 0;
//Wenn cmd leer ist: MSG_SAY
if (!sizeof(cmd))
{
type = MSG_SAY;
}
else
{
switch (cmd[0])
{
case ':' :
type = MSG_EMOTE;
args = args[1..];
break;
case ';' :
type = MSG_GEMOTE;
args = args[1..];
break;
case '\'':
args = args[1..];
// Der Fallthrough in default ist hier Absicht.
default :
type = MSG_SAY;
break;
}
}
if (!ch || !sizeof(ch))
ch = QueryProp(P_STD_CHANNEL);
err = CHMASTER->send(ch, ME, args, type);
if (err < 0)
{
err = CHMASTER->join(ch, ME);
if (!err)
{
ch = lower_case(ch);
if (member(QueryProp(P_CHANNELS), ch) == -1)
SetProp(P_CHANNELS, QueryProp(P_CHANNELS) + ({ ch }));
err = CHMASTER->send(ch, ME, args, type);
}
}
switch (err)
{
case E_ACCESS_DENIED:
notify_fail("Auf der Ebene '"+ch+"' darfst Du nichts sagen.\n");
return 0;
case E_NOT_MEMBER:
notify_fail("Du hast die Ebene '"+ch+"' nicht betreten!\n");
return 0;
}
return 1;
}
int ChannelAdmin(string args)
{
args = _unparsed_args();
string target_channel, descr, _sh_cut;
string|string* chans;
notify_fail("Benutzung: ebene <Abkuerzung>=<Ebene>\n"
" ebene <Abkuerzung>=\n"
" ebene abkuerzungen [standard]\n"
" ebene standard <Ebene>\n"
" ebene an|ein|aus\n"
+(QueryProp(P_LEVEL) >= 5 ?
" ebene neu <Name> <Bezeichnung>\n"
" ebene beschreibung <Name> <Beschreibung>\n" : "")
+(IS_ARCH(ME) ?
" ebene kill <Name>\n"
" ebene clear <Name>\n": ""));
if (!args || !sizeof(args))
return 0;
if (sscanf(args, "kill %s", target_channel) && IS_ARCH(ME))
{
chans = CHMASTER->find(target_channel, ME);
if (!chans)
{
notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
return 0;
}
else if (pointerp(chans))
{
notify_fail(
"Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
break_string(CountUp(chans), 78));
return 0;
}
else if (CHMASTER->remove_channel(target_channel, ME))
{
notify_fail("Die Ebene '"+target_channel+"' lies sich nicht "
"entfernen!\n");
return 0;
}
tell_object(ME,"Du entfernst die Ebene '"+target_channel+"'.\n");
return 1;
}
if (sscanf(args, "clear %s", target_channel) && IS_ARCH(ME))
{
chans = CHMASTER->find(target_channel, ME);
if (!chans)
{
notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
return 0;
}
else if (pointerp(chans))
{
notify_fail(
"Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
break_string(CountUp(chans), 78));
return 0;
}
else if (CHMASTER->clear_history(target_channel, ME) == E_ACCESS_DENIED)
{
notify_fail("Der Verlauf zur Ebene '"+target_channel+"' liess sich "
"nicht entfernen!\n");
return 0;
}
tell_object(ME,"Du entfernst den Verlauf zur Ebene '"+target_channel+
"'.\n");
return 1;
}
if (sscanf(args, "neu %s %s", target_channel, descr) == 2)
{
notify_fail(break_string("Neue Ebenen kannst Du erst erstellen, wenn "
"Du Spielerstufe 5 erreicht hast.", 78));
if (QueryProp(P_LEVEL) < 5)
return 0;
notify_fail("Der Name '"+target_channel+"' ist nicht konform!\n");
if (!sizeof(regexp(({target_channel}), "^" CHANNELCMDS CHANNELCMDS "*")))
return 0;
notify_fail("Der Name '"+target_channel+"' ist zu lang.\n");
if (sizeof(target_channel) > 20)
return 0;
notify_fail("Diese Ebene darfst du nicht erschaffen!\n");
if (CHMASTER->new(target_channel, ME, descr) == E_ACCESS_DENIED)
return 0;
tell_object(ME,"Du erschaffst die Ebene '"+target_channel+"'.\n");
SetProp(P_CHANNELS, QueryProp(P_CHANNELS) +
({lower_case(target_channel)}));
return 1;
}
if (sscanf(args, "beschreibung %s %s", target_channel, descr) == 2)
{
chans = CHMASTER->find(target_channel, ME);
if (!chans)
{
notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
return 0;
}
else if (pointerp(chans))
{
notify_fail(
"Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
break_string(CountUp(chans), 78));
return 0;
}
mapping ch = CHMASTER->list(ME);
notify_fail("Du bist nicht berechtigt, die Beschreibung der Ebene"
" '"+target_channel+"' zu aendern.\n");
if (ch[lower_case(chans)][I_SUPERVISOR] != ME)
return 0;
ch[lower_case(target_channel)][I_INFO] = descr;
tell_object(ME,"Die Ebene '"+target_channel+"' hat ab sofort die "
"Beschreibung:\n"+descr+"\n");
return 1;
}
if (sscanf(args, "%s=%s", _sh_cut, target_channel) == 2 &&
sizeof(target_channel))
{
chans = CHMASTER->find(target_channel, ME);
if (!chans)
{
notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
return 0;
}
else if (pointerp(chans))
{
notify_fail(
"Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
break_string(CountUp(chans), 78));
return 0;
}
mapping shortcut_list = QueryProp(P_CHANNEL_SHORT) || ([]);
m_add(shortcut_list, _sh_cut, chans);
SetProp(P_CHANNEL_SHORT, shortcut_list);
shortcut = QueryProp(P_CHANNEL_SHORT);
tell_object(ME,"'"+_sh_cut+"' wird jetzt als Abkuerzung fuer '"+chans+
"' anerkannt.\n");
return 1;
}
if (sscanf(args, "%s=", _sh_cut))
{
SetProp(P_CHANNEL_SHORT,
m_delete(QueryProp(P_CHANNEL_SHORT) || ([]), _sh_cut));
shortcut = QueryProp(P_CHANNEL_SHORT);
tell_object(ME,"Du loeschst die Abkuerzung '"+_sh_cut+"'.\n");
return 1;
}
if (args == "an" || args == "ein")
{
if (pointerp(QueryProp(P_SWAP_CHANNELS)))
SetProp(P_CHANNELS, QueryProp(P_SWAP_CHANNELS));
else
SetProp(P_CHANNELS, m_indices(CHMASTER->list(ME)));
// <excl> enthaelt die Channelnamen, deren Channel nicht erstellt wurden.
string* excl = RegisterChannels();
tell_object(ME,"Du schaltest folgende Ebenen ein:\n"+
break_string(CountUp(QueryProp(P_CHANNELS) - excl), 78));
SetProp(P_SWAP_CHANNELS, 0);
return 1;
}
if (args == "aus")
{
SetProp(P_SWAP_CHANNELS, QueryProp(P_CHANNELS));
RemoveChannels();
SetProp(P_CHANNELS, ({}));
tell_object(ME,"Du stellst die Ebenen ab.\n");
return 1;
}
string* pa = old_explode(args, " ");
if (strstr("abkuerzungen", pa[0]) == 0)
{
string txt = "";
if (sizeof(pa) > 1 && strstr("standard", pa[1]) == 0)
{
tell_object(ME,"Die Standardabkuerzungen werden gesetzt.\n");
SetProp(P_CHANNEL_SHORT,
DEFAULT_SHORTCUTS + (IS_LEARNER(ME) ? WIZARD_SHORTCUTS : ([])));
}
mapping scut = QueryProp(P_CHANNEL_SHORT);
string* abbreviations = m_indices(scut);
foreach (string abk : sort_array(abbreviations, #'>))
{
txt += sprintf("%5.5s = %s\n", abk, scut[abk]);
}
More( "Folgende Abkuerzungen sind definiert:\n"+
sprintf("%-78#s\n", txt) );
return 1;
}
if (strstr("standard", pa[0]) == 0)
{
if (sizeof(pa) < 2)
{
notify_fail("Benutzung: ebene standard <Ebene>\n"+
(QueryProp(P_STD_CHANNEL)
? "Momentan ist '"+QueryProp(P_STD_CHANNEL)+"' eingestellt.\n"
: "Es ist keine Standardebene eingestellt.\n"));
return 0;
}
chans = CHMASTER->find(pa[1], ME);
if (!chans)
{
notify_fail("Ebene '"+pa[1]+"' nicht gefunden!\n");
return 0;
}
else if (pointerp(chans))
{
notify_fail(
"Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
break_string(CountUp(chans), 78));
return 0;
}
tell_object(ME,"'"+chans+"' ist jetzt die Standardebene.\n");
SetProp(P_STD_CHANNEL, chans);
return 1;
}
return(0);
}