blob: cf8d85aaec71517781ac86040d2b2679a22f25cf [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// channel.c -- channel client
4//
5// $Id: channel.c 9404 2015-12-13 00:21:44Z Zesstra $
6#pragma strong_types
7#pragma save_types
8#pragma range_check
9#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020010
11#define NEED_PROTOTYPES
12#include <util.h>
13#include <thing/properties.h>
14#include <living/comm.h>
15#include <player.h>
16#include <player/comm.h>
17#include <daemon.h>
18#include <player/gmcp.h>
19#undef NEED_PROTOTYPES
20
21#include <wizlevels.h>
22#include <defines.h>
23#include <properties.h>
24#include <sys_debug.h>
25#include <regexp.h>
26
MG Mud User88f12472016-06-24 23:31:02 +020027#define P_CHANNEL_SHORT "short_channels"
28
29#define CHANNELCMDS "[#@%$&()<>a-zA-Z0-9\\-]"
30
Arathorn78c08372019-12-11 20:14:23 +010031#define DEFAULT_CHANNELS ({"Abenteuer", "Anfaenger", "Grats", "Tod", "ZT"})
MG Mud User88f12472016-06-24 23:31:02 +020032#define DEFAULT_SHORTCUTS \
33([ \
34 "b":"Abenteuer", \
35 "a":"Allgemein", \
36 "B":"Beileid", \
37 "q":"D-chat", \
38 "G":"Grats", \
39 "M":"Moerder", \
40 "h":"Seher", \
41 "T":"Tod", \
42])
43
44#define WIZARD_SHORTCUTS \
45([ \
46 "P":"D-code", \
47 "D":"Debug", \
48 "O":"Intercode", \
49 "I":"Intermud", \
50 "m":"Magier", \
51])
52
Arathorn78c08372019-12-11 20:14:23 +010053// TODO: <shortcut> wird eigentlich nur in getChannel() sinnvoll verwendet
54// Koennte man an sich komplett einsparen und dort wie ueberall sonst auch
55// einfach nur mit P_CHANNEL_SHORT arbeiten.
MG Mud User88f12472016-06-24 23:31:02 +020056private nosave mapping shortcut;
57private nosave int c_status;
58
59void create()
60{
61 Set(P_CHANNELS, SAVE, F_MODE);
62 Set(P_CHANNELS, DEFAULT_CHANNELS);
63 Set(P_SWAP_CHANNELS, SAVE, F_MODE);
64 Set(P_STD_CHANNEL, "Allgemein");
65 Set(P_STD_CHANNEL, SAVE, F_MODE);
66 Set(P_CHANNEL_SHORT, SAVE, F_MODE);
67 Set(P_CHANNEL_SHORT, DEFAULT_SHORTCUTS
Arathorn78c08372019-12-11 20:14:23 +010068 + (IS_LEARNER(ME) ? WIZARD_SHORTCUTS : ([])));
MG Mud User88f12472016-06-24 23:31:02 +020069}
70
Arathorn00764692019-11-27 22:09:31 +010071static <int|string>** _query_localcmds()
MG Mud User88f12472016-06-24 23:31:02 +020072{
73 return ({({"-","ChannelParser", 1, 0}),
74 ({"ebene", "ChannelAdmin", 0, 0}),
75 ({"ebenen", "ChannelAdmin", 1, 0}),
76 });
77}
78
Arathorn00764692019-11-27 22:09:31 +010079string* RegisterChannels()
MG Mud User88f12472016-06-24 23:31:02 +020080{
Arathorn85de7602019-11-28 23:04:03 +010081 if (extern_call() &&
82 previous_object() != find_object(CHMASTER))
Zesstraf1137e82019-11-27 23:37:30 +010083 return 0;
84
MG Mud User88f12472016-06-24 23:31:02 +020085 c_status = 0;
86 shortcut = QueryProp(P_CHANNEL_SHORT);
Arathorn85de7602019-11-28 23:04:03 +010087 SetProp(P_CHANNELS, map(QueryProp(P_CHANNELS) || ({}), #'lower_case));
88
89 closure cl = symbol_function("join", CHMASTER);
90 string* err;
91 if (closurep(cl))
92 {
Arathorn78c08372019-12-11 20:14:23 +010093 err = filter(QueryProp(P_CHANNELS), cl, ME);
MG Mud User88f12472016-06-24 23:31:02 +020094 }
Arathorn85de7602019-11-28 23:04:03 +010095 if (QueryProp(P_LEVEL) < 5)
96 return err;
97
98 // CHMASTER->new() gibt bei Erfolg 0 zurueck, d.h. es bleiben
99 // alle Elemente erhalten, deren Channel nicht erstellt werden konnten.
100 // Die werden an die Aufrufer zurueckgegeben.
Arathorn78c08372019-12-11 20:14:23 +0100101 return filter(err, "new", CHMASTER, ME);
MG Mud User88f12472016-06-24 23:31:02 +0200102}
103
Arathorn00764692019-11-27 22:09:31 +0100104string* RemoveChannels()
MG Mud User88f12472016-06-24 23:31:02 +0200105{
Arathorn85de7602019-11-28 23:04:03 +0100106 if (extern_call() &&
107 previous_object() != find_object(CHMASTER))
Zesstraf1137e82019-11-27 23:37:30 +0100108 return 0;
109
Arathorn85de7602019-11-28 23:04:03 +0100110 string* err = ({});
111
112 if (!c_status)
113 c_status = 1;
114 else
115 return err;
116
117 closure cl = symbol_function("leave", CHMASTER);
118 if (closurep(cl))
119 {
Arathorn78c08372019-12-11 20:14:23 +0100120 err = filter(QueryProp(P_CHANNELS), cl, ME);
Arathorn85de7602019-11-28 23:04:03 +0100121 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) - err);
MG Mud User88f12472016-06-24 23:31:02 +0200122 }
123 return err;
124}
125
Arathorn78c08372019-12-11 20:14:23 +0100126varargs private string getName(string|object|closure who, int fall) {
127 // Objekt zur Closure raussuchen. <o> ist danach entweder jenes Objekt,
128 // oder aber <who> selbst, das Objekt oder String sein kann.
129 //
130 // query_closure_object() KANN auch 0 oder -1 zurueckgeben. In beiden
131 // Faellen fuehrt das hier zu einem Laufzeit-Typfehler, weil ein Integer
132 // bzw. die urspruengliche Closure zugewiesen wird. Diesen Fall abzufangen
133 // und separat zu behandeln, hilft nicht, weil es auch keine alternative
134 // Moeglichkeit gibt, <who> in verwendbare Daten umzuwandeln, denn das ist
135 // ja offenbar eine kaputte Closure. Dieser Fall ist ausserdem anscheinend
136 // so exotisch, dass wir vorerst auf Absicherungen mittels catch()
137 // verzichten.
138 string|object o = closurep(who) ? query_closure_object(who) : who;
139
140 // Ist es ein String, pruefen wir, ob es vielleicht ein Objektname ist,
141 // indem wir es in ein Objekt umzuwandeln versuchen. Schlaegt das fehl,
142 // schreiben wir <who> wieder zurueck, so dass <o> wieder der urspruengliche
143 // String ist.
144 if (stringp(o) && sizeof(o))
145 o = find_object(o) || who;
Arathorn85de7602019-11-28 23:04:03 +0100146
MG Mud User88f12472016-06-24 23:31:02 +0200147 // Objekte
Arathorn85de7602019-11-28 23:04:03 +0100148 if (objectp(o))
149 {
MG Mud User88f12472016-06-24 23:31:02 +0200150 // Magier sehen unsichtbare nicht nur als "Jemand"
Arathorn78c08372019-12-11 20:14:23 +0100151 if (o->QueryProp(P_INVIS) && IS_LEARNING(ME))
MG Mud User88f12472016-06-24 23:31:02 +0200152 return "("+capitalize(getuid(o))+")";
153 // Froesche mit Namen versorgen.
154 if (o->QueryProp(P_FROG))
155 return "Frosch "+capitalize(getuid(o));
156 // Default (Unsichtbare als "Jemand" (s. Name()))
157 return o->Name(fall, 2)||"<Unbekannt>";
158 }
159 // Strings
Arathorn85de7602019-11-28 23:04:03 +0100160 else if (stringp(o) && sizeof(o))
161 {
162 if (o[0] == '/')
163 {
MG Mud User88f12472016-06-24 23:31:02 +0200164 // unsichtbare Objekte...
165 int p = strstr(o, "$");
Arathorn85de7602019-11-28 23:04:03 +0100166 if (p != -1)
167 {
168 // Magier im Magiermodus kriegen den Realnamen, andere nicht.
Arathorn78c08372019-12-11 20:14:23 +0100169 if (IS_LEARNING(ME))
Arathorn85de7602019-11-28 23:04:03 +0100170 return o[1..p-1];
171 else
172 return o[p+1..];
MG Mud User88f12472016-06-24 23:31:02 +0200173 }
Arathorn85de7602019-11-28 23:04:03 +0100174 else // doch nicht unsichtbar
175 return (fall == WESSEN ? o+"s" : o);
MG Mud User88f12472016-06-24 23:31:02 +0200176 }
177 else
178 // nicht unsichtbar
179 return (fall == WESSEN ? o+"s" : o);
180 }
181 // Fall-through
182 return "<Unbekannt>";
183}
184
Arathorn69d6ddd2019-11-25 21:06:34 +0100185// <nonint> unterdrueckt die Ausgabe an den Spieler und liefert den Text
Arathorn85de7602019-11-28 23:04:03 +0100186// zurueck. Wird nur fuer die Ebenenhistory benutzt.
Arathorn69d6ddd2019-11-25 21:06:34 +0100187string ChannelMessage(<string|object|int>* msg, int nonint)
MG Mud User88f12472016-06-24 23:31:02 +0200188{
189 string channel_message;
Arathorn85de7602019-11-28 23:04:03 +0100190 string channel = msg[0];
Arathorn69d6ddd2019-11-25 21:06:34 +0100191
192 // Wenn eine Ebenenmeldung ausgegeben werden soll, ist msg[1] ein Objekt,
193 // im Fall der History aber ein String. Daher wird <sender> als Union
194 // deklariert. Das ist unproblematisch, weil die beiden Datentypen
195 // komplett getrennte Wege nehmen: ein Objekt wird an ReceiveMsg()
196 // durchgereicht (Ebenenmeldung). Ein String wird direkt in die Meldung
197 // (History) eingebaut, diese an den ChannelParser() zurueckgegeben, der
198 // sie via More() ausgibt.
Arathorn85de7602019-11-28 23:04:03 +0100199 string|object sender = msg[1];
200 string message = msg[2];
MG Mud User88f12472016-06-24 23:31:02 +0200201 int msg_type = msg[3];
202
Arathorn85de7602019-11-28 23:04:03 +0100203 if (previous_object() != find_object(CHMASTER) &&
204 previous_object() != ME )
205 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200206
207 string sender_name = getName(sender, msg_type == MSG_GEMOTE ? WESSEN : WER);
Arathorn85de7602019-11-28 23:04:03 +0100208 int prepend_indent_flag =
209 QueryProp(P_MESSAGE_PREPEND) ? BS_PREPEND_INDENT : 0;
210
211 switch (msg_type)
212 {
213 case MSG_EMPTY:
214 channel_message= message+"\n";
215 break;
216 case MSG_GEMOTE:
217 case MSG_EMOTE:
218 channel_message = break_string(sender_name + " "+ message+"]", 78,
219 sprintf("[%s:", channel),
220 BS_INDENT_ONCE|prepend_indent_flag);
221 break;
222 case MSG_SAY:
223 default:
Arathorn78c08372019-12-11 20:14:23 +0100224 string presay = sprintf("[%s:%s] ", channel, sender_name);
Arathorn85de7602019-11-28 23:04:03 +0100225 channel_message = break_string(message, max(78,sizeof(presay)+10),
226 presay, prepend_indent_flag);
227 break;
MG Mud User88f12472016-06-24 23:31:02 +0200228 }
Arathorn85de7602019-11-28 23:04:03 +0100229
230 if (nonint)
MG Mud User88f12472016-06-24 23:31:02 +0200231 return channel_message;
232
233 // Wenn GMCP sich um Uebertragung der Nachricht kuemmert, wird ReceiveMsg()
Zesstra7ccec732019-01-06 22:10:09 +0100234 // nicht mehr aufgerufen. getName leider nochmal aufrufen, weil GMCP den
235 // Namen im Nominativ braucht.
236 if (msg_type == MSG_GEMOTE)
237 sender_name = getName(sender, WER);
MG Mud User88f12472016-06-24 23:31:02 +0200238 if (GMCP_Channel(channel_message, channel, sender_name) != 1)
239 {
240 // Der Ebenenname muss in Kleinbuchstaben uebergeben werden, damit die
241 // Ignorierepruefung funktioniert. Die ignorierestrings sind naemlich alle
242 // kleingeschrieben.
Arathorn85de7602019-11-28 23:04:03 +0100243 ReceiveMsg(channel_message,
MG Mud User88f12472016-06-24 23:31:02 +0200244 MT_COMM|MT_FAR|MSG_DONT_STORE|MSG_DONT_WRAP,
245 MA_CHANNEL"." + lower_case(channel), 0, sender);
246 }
247 return 0;
248}
249
Zesstra60df9112020-08-18 00:01:58 +0200250// Defines fuer den Zugriff auf die Channeldaten in der vom CHANNELD
251// erhaltenen Kanalliste.
252#define I_MEMBER 0
253#define I_ACCESS 1
254#define I_INFO 2
255#define I_SUPERVISOR 3
256#define I_NAME 4
MG Mud User88f12472016-06-24 23:31:02 +0200257
Arathorn78c08372019-12-11 20:14:23 +0100258private void createList(mapping ch_list, string* p_channels,
259 int show_only_subscribed) {
260 // Kopfzeile der Tabelle erstellen.
261 string listhead =
262 sprintf("%-12.12' 's [A] %|12' 's (%-3' 's) %-42.42s\n",
263 "Name", "Eigner", "Sp", "Beschreibung");
264 // Linie drunter.
265 listhead += ("-"*78 + "\n");
266
267 // Rest der Daten erstmal in einem Array zusammenstellen, um es
268 // anschliessend sortieren zu koennen.
269 string* entries = ({});
270
271 object* ch_members;
272 string|object ch_master;
273 string ch_real_name, ch_description;
274 closure|string ch_access;
275 closure|string ch_info;
276 string sh;
277
278 foreach(string chname, <object*|closure|string|object>* chdata : ch_list)
279 {
280 // Wenn nur abonnierte Ebenen aufgelistet werden sollen, dann alle
281 // ueberspringen, die nicht in P_CHANNELS stehen.
282 int is_subscribed = (member(p_channels, chname) > -1);
283 if (show_only_subscribed && !is_subscribed)
284 continue;
285
286 ch_members = chdata[I_MEMBER];
Zesstra60df9112020-08-18 00:01:58 +0200287 ch_master = chdata[I_SUPERVISOR];
Arathorn78c08372019-12-11 20:14:23 +0100288 ch_access = chdata[I_ACCESS];
289 ch_real_name = chdata[I_NAME];
290 ch_info = chdata[I_INFO];
291 sh = "";
292
293 // Ist eine Closure als I_INFO eingetragen, zu der es auch ein Objekt
294 // gibt, tragen wir deren Rueckgabewert als Beschreibung ein.
295 if (closurep(ch_info) && objectp(query_closure_object(ch_info))) {
296 ch_description = funcall(ch_info);
297 }
298 // Ist es ein String, wird er unveraendert uebernommen.
299 else if (stringp(ch_info)) {
300 ch_description = ch_info;
301 }
302 // Sollte nirgends etwas eingetragen sein, oder die Closure 0 zurueck-
303 // gegeben haben, gibt es eine Defaultbeschreibung. Man haette die
304 // Variable auch schon mit dem Default initialisieren koennen, der kann
305 // aber bei Rueckgabe von 0 aus der Closure wieder ueberschrieben werden.
306 // Daher passiert das erst hier.
307 ch_description ||= "- Keine Beschreibung -";
308
309 // Wir brauchen noch das vom Spieler festgelegte Kuerzel fuer die aktuell
310 // bearbeitete Ebene, falls vorhanden, um es in die Liste eintragen
311 // zu koennen.
312 foreach(string _sh_cut, string _chan_name : shortcut) {
313 if ( lower_case(_chan_name) == chname ) {
314 sh = _sh_cut;
315 break;
316 }
317 }
318
319 // Jetzt haben wir endlich alle Infos beisammen und koennen die
320 // Eintraege zusammenbauen.
321 entries += ({
322 sprintf("%-12.12'.'s %c[%-1.1s] %|12.12' 's (%-|3' 'd) %-42.42s\n",
323 ch_real_name,
324 is_subscribed ? '*' : ' ',
325 sh,
326 ch_master ? getName(ch_master) : getName(ch_access),
Zesstrae40de3a2020-09-24 00:12:12 +0200327 sizeof(ch_members),
Arathorn78c08372019-12-11 20:14:23 +0100328 ch_description
329 ) });
330 }
331
332 // alphabetisch sortieren
333 entries = sort_array(entries, #'>);
334
335 More( listhead + // Listenkopf
336 implode(entries, "") + // Eintraege
337 "-"*78+"\n" ); // Linie drunter
338}
339
340private string|string* getChannel(string ch)
MG Mud User88f12472016-06-24 23:31:02 +0200341{
Arathorn85de7602019-11-28 23:04:03 +0100342 if (!sizeof(ch))
Arathorn00764692019-11-27 22:09:31 +0100343 ch = QueryProp(P_STD_CHANNEL);
Arathorn85de7602019-11-28 23:04:03 +0100344 if (shortcut && shortcut[ch])
Arathorn00764692019-11-27 22:09:31 +0100345 ch = shortcut[ch];
Arathorn78c08372019-12-11 20:14:23 +0100346 return CHMASTER->find(ch, ME);
MG Mud User88f12472016-06-24 23:31:02 +0200347}
348
MG Mud User88f12472016-06-24 23:31:02 +0200349int ChannelParser(string args)
350{
Arathorn78c08372019-12-11 20:14:23 +0100351 string|string* ch;
MG Mud User88f12472016-06-24 23:31:02 +0200352 int pos, type, err;
Arathorn78c08372019-12-11 20:14:23 +0100353 string tmp;
MG Mud User88f12472016-06-24 23:31:02 +0200354 notify_fail("Benutzung: -<Ebene>[ ]['|:|;]<Text>\n"
355 " -<Ebene>[+|-|?|!|*]\n"
356 " -?\n");
Arathorn78c08372019-12-11 20:14:23 +0100357
Arathornb794a192020-08-06 21:51:28 +0200358 args = _unparsed_args();
Arathorn78c08372019-12-11 20:14:23 +0100359 string|string* cmd = query_verb();
Arathorn85de7602019-11-28 23:04:03 +0100360 if (!cmd && !args)
361 return 0;
362
363 if (!args)
364 args = "";
365
MG Mud User88f12472016-06-24 23:31:02 +0200366 cmd = cmd[1..];
Arathorn78c08372019-12-11 20:14:23 +0100367 cmd = regexplode(cmd, "^" CHANNELCMDS "*" "([+-]|\\!|\\?|\\*)*");
Arathorn85de7602019-11-28 23:04:03 +0100368 if (sizeof(cmd) > 1)
MG Mud User88f12472016-06-24 23:31:02 +0200369 {
370 //z.B. cmd= ({"","allgemein",":testet"})
Arathorn85de7602019-11-28 23:04:03 +0100371 if (sizeof(cmd[1]) > 1 && strstr("+-?!*", cmd[1][<1..<1]) > -1)
MG Mud User88f12472016-06-24 23:31:02 +0200372 tmp = cmd[1][0..<2];
373 else
374 tmp = cmd[1];
Arathorn85de7602019-11-28 23:04:03 +0100375
376 if (cmd[1] != "?" && cmd[1] != "!")
Zesstra57a693e2019-01-06 22:08:24 +0100377 {
378 ch = getChannel(tmp);
Arathorn85de7602019-11-28 23:04:03 +0100379 if (pointerp(ch))
MG Mud User88f12472016-06-24 23:31:02 +0200380 {
381 notify_fail("Diese Angabe war nicht eindeutig! "
Arathorn78c08372019-12-11 20:14:23 +0100382 "Folgende Ebenen passen:\n"+
383 implode(ch, ", ")+"\n");
MG Mud User88f12472016-06-24 23:31:02 +0200384 return 0;
385 }
Arathorn85de7602019-11-28 23:04:03 +0100386 else if (!ch)
387 {
388 notify_fail("Die Ebene '"+tmp+ "' gibt es nicht!\n");
389 return 0;
390 }
Zesstra57a693e2019-01-06 22:08:24 +0100391 }
Arathorn85de7602019-11-28 23:04:03 +0100392
MG Mud User88f12472016-06-24 23:31:02 +0200393 if (sizeof(cmd[1])) {
Arathorn85de7602019-11-28 23:04:03 +0100394 switch (cmd[1][<1]) {
395 case '+':
Arathorn78c08372019-12-11 20:14:23 +0100396 switch (CHMASTER->join(ch, ME))
Arathorn85de7602019-11-28 23:04:03 +0100397 {
398 case E_ACCESS_DENIED:
399 notify_fail("Du darfst an die Ebene '"+ch+"' nicht heran.\n");
400 return 0;
401 case E_ALREADY_JOINED:
402 notify_fail("Du hast diese Ebene schon betreten!\n");
403 return 0;
404 default:
405 break;
406 }
Arathorn78c08372019-12-11 20:14:23 +0100407 tell_object(ME,"Du betrittst die Ebene '"+ch+"'.\n");
Arathorn85de7602019-11-28 23:04:03 +0100408 if (member(QueryProp(P_CHANNELS), ch = lower_case(ch)) == -1)
409 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) + ({ ch }));
410 return 1;
411
412 case '-':
Arathorn78c08372019-12-11 20:14:23 +0100413 switch (CHMASTER->leave(ch, ME))
Arathorn85de7602019-11-28 23:04:03 +0100414 {
415 case E_ACCESS_DENIED:
Arathorn78c08372019-12-11 20:14:23 +0100416 tell_object(ME,"Du kannst die Ebene '"+ch+"' nicht "
417 "verlassen.\n");
Arathorn85de7602019-11-28 23:04:03 +0100418 break;
419 case E_NOT_MEMBER:
Arathorn78c08372019-12-11 20:14:23 +0100420 tell_object(ME,"Wie willst Du eine Ebene verlassen, welche Du "
421 "nicht betreten hast?\n");
Arathorn85de7602019-11-28 23:04:03 +0100422 break;
423 default:
Arathorn78c08372019-12-11 20:14:23 +0100424 tell_object(ME,"Du verlaesst die Ebene '"+ch+"'.\n");
Arathorn85de7602019-11-28 23:04:03 +0100425 SetProp(P_CHANNELS,
426 QueryProp(P_CHANNELS) - ({ lower_case(ch), ch }));
427 break;
428 }
429 return 1;
430
431 case '!':
432 case '?':
Arathorn78c08372019-12-11 20:14:23 +0100433 mapping l = CHMASTER->list(ME);
434 if (mappingp(l))
Arathorn85de7602019-11-28 23:04:03 +0100435 {
Arathorn78c08372019-12-11 20:14:23 +0100436 // Es wurde ein Channel angegeben, dessen Infos gefragt sind.
Arathorn85de7602019-11-28 23:04:03 +0100437 if (stringp(ch) && sizeof(ch) && pointerp(l[ch = lower_case(ch)]))
438 {
Arathorn78c08372019-12-11 20:14:23 +0100439 <object*|closure|string|object>* chdata = l[ch];
440 string* m =
Zesstrae40de3a2020-09-24 00:12:12 +0200441 sort_array(map(chdata[I_MEMBER], #'getName, WER), #'>);
Arathorn78c08372019-12-11 20:14:23 +0100442
443 string wen;
444 switch(sizeof(m)) {
445 case 1: wen = "ein Gesicht"; break;
446 case 0: wen = "niemanden"; break;
447 // TODO: mittels Zahlwoerter-Objekt die Zahl als Text
448 // ausgeben?
449 default: wen = sprintf("%d Gesichter", sizeof(m)); break;
450 }
451
452 tell_object(ME,
453 chdata[I_NAME]+", "+funcall(chdata[I_INFO])+".\n" +
454 "Du siehst "+wen+" auf der Ebene '"+chdata[I_NAME]+"':\n"+
Zesstra60df9112020-08-18 00:01:58 +0200455 break_string(CountUp(m), 78) + getName(chdata[I_SUPERVISOR])
456 + " hat das Sagen auf dieser Ebene.\n");
Arathorn85de7602019-11-28 23:04:03 +0100457 }
Arathorn78c08372019-12-11 20:14:23 +0100458 // kein Channel angegeben, dann Gesamtliste erzeugen
Arathorn85de7602019-11-28 23:04:03 +0100459 else
460 {
Arathorn78c08372019-12-11 20:14:23 +0100461 // Wenn nur die abonnierten angezeigt werden sollen
462 // (Kommando -!), wird die Bedingung im 3. Argument == 1, so
463 // dass createList() die reduzierte Tabelle erzeugt.
464 createList(l, QueryProp(P_CHANNELS), (cmd[1][<1] == '!'));
Arathorn85de7602019-11-28 23:04:03 +0100465 }
466 }
467 return 1;
468
469 case '*':
Arathorn78c08372019-12-11 20:14:23 +0100470 int|<int|string>** hist = CHMASTER->history(ch, ME);
Arathorn85de7602019-11-28 23:04:03 +0100471 if (!pointerp(hist) || !sizeof(hist))
472 {
Arathorn78c08372019-12-11 20:14:23 +0100473 tell_object(ME,"Es ist keine Geschichte fuer '"+ch+
474 "' verfuegbar.\n");
Arathorn85de7602019-11-28 23:04:03 +0100475 return 1;
476 }
477
Arathorn85de7602019-11-28 23:04:03 +0100478 int amount = to_int(cmd[2]);
479 if (amount <= 0 || amount >= sizeof(hist))
480 amount = sizeof(hist);
481
Arathorn78c08372019-12-11 20:14:23 +0100482 string txt = "Folgendes ist auf '"+ch+"' passiert:\n" +
483 implode(map(hist[<amount..], #'ChannelMessage, 1), "");
MG Mud User88f12472016-06-24 23:31:02 +0200484 More(txt);
Arathorn85de7602019-11-28 23:04:03 +0100485 return 1;
486
487 default:
488 break;
Zesstra57a693e2019-01-06 22:08:24 +0100489 }
MG Mud User88f12472016-06-24 23:31:02 +0200490 }
491 }
Arathorn85de7602019-11-28 23:04:03 +0100492
Arathorn78c08372019-12-11 20:14:23 +0100493 cmd = implode(cmd[2..], "");
494 if (sizeof(cmd))
Arathorn85de7602019-11-28 23:04:03 +0100495 args = cmd + (sizeof(args) ? " " : "") + args;
MG Mud User88f12472016-06-24 23:31:02 +0200496
497 // KOntrollchars ausfiltern.
Arathorn78c08372019-12-11 20:14:23 +0100498 args = regreplace(args, "[[:cntrl:]]", "", RE_PCRE|RE_GLOBAL);
Arathorn85de7602019-11-28 23:04:03 +0100499 if (!sizeof(args))
500 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200501
502 //Wenn cmd leer ist: MSG_SAY
Arathorn85de7602019-11-28 23:04:03 +0100503 if (!sizeof(cmd))
Arathorn78c08372019-12-11 20:14:23 +0100504 {
Arathorn85de7602019-11-28 23:04:03 +0100505 type = MSG_SAY;
Arathorn78c08372019-12-11 20:14:23 +0100506 }
Arathorn85de7602019-11-28 23:04:03 +0100507 else
508 {
509 switch (cmd[0])
MG Mud User88f12472016-06-24 23:31:02 +0200510 {
Arathorn85de7602019-11-28 23:04:03 +0100511 case ':' :
512 type = MSG_EMOTE;
513 args = args[1..];
514 break;
515 case ';' :
516 type = MSG_GEMOTE;
517 args = args[1..];
518 break;
519 case '\'':
520 args = args[1..];
521 // Der Fallthrough in default ist hier Absicht.
522 default :
523 type = MSG_SAY;
524 break;
MG Mud User88f12472016-06-24 23:31:02 +0200525 }
526 }
Arathorn85de7602019-11-28 23:04:03 +0100527
528 if (!ch || !sizeof(ch))
529 ch = QueryProp(P_STD_CHANNEL);
530
Arathorn78c08372019-12-11 20:14:23 +0100531 err = CHMASTER->send(ch, ME, args, type);
Arathorn85de7602019-11-28 23:04:03 +0100532 if (err < 0)
533 {
Arathorn78c08372019-12-11 20:14:23 +0100534 err = CHMASTER->join(ch, ME);
Arathorn85de7602019-11-28 23:04:03 +0100535 if (!err)
MG Mud User88f12472016-06-24 23:31:02 +0200536 {
Arathorn78c08372019-12-11 20:14:23 +0100537 ch = lower_case(ch);
538 if (member(QueryProp(P_CHANNELS), ch) == -1)
MG Mud User88f12472016-06-24 23:31:02 +0200539 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) + ({ ch }));
Arathorn78c08372019-12-11 20:14:23 +0100540 err = CHMASTER->send(ch, ME, args, type);
MG Mud User88f12472016-06-24 23:31:02 +0200541 }
Arathorn85de7602019-11-28 23:04:03 +0100542 }
MG Mud User88f12472016-06-24 23:31:02 +0200543
Arathorn85de7602019-11-28 23:04:03 +0100544 switch (err)
MG Mud User88f12472016-06-24 23:31:02 +0200545 {
Arathorn85de7602019-11-28 23:04:03 +0100546 case E_ACCESS_DENIED:
547 notify_fail("Auf der Ebene '"+ch+"' darfst Du nichts sagen.\n");
548 return 0;
549 case E_NOT_MEMBER:
550 notify_fail("Du hast die Ebene '"+ch+"' nicht betreten!\n");
551 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200552 }
553 return 1;
554}
555
556int ChannelAdmin(string args)
557{
Arathorn85de7602019-11-28 23:04:03 +0100558 args = _unparsed_args();
559
Arathorn78c08372019-12-11 20:14:23 +0100560 string target_channel, descr, _sh_cut;
561 string|string* chans;
MG Mud User88f12472016-06-24 23:31:02 +0200562 notify_fail("Benutzung: ebene <Abkuerzung>=<Ebene>\n"
563 " ebene <Abkuerzung>=\n"
564 " ebene abkuerzungen [standard]\n"
565 " ebene standard <Ebene>\n"
566 " ebene an|ein|aus\n"
567 +(QueryProp(P_LEVEL) >= 5 ?
568 " ebene neu <Name> <Bezeichnung>\n"
Arathorn85de7602019-11-28 23:04:03 +0100569 " ebene beschreibung <Name> <Beschreibung>\n" : "")
Arathorn78c08372019-12-11 20:14:23 +0100570 +(IS_ARCH(ME) ?
Arathorn85de7602019-11-28 23:04:03 +0100571 " ebene kill <Name>\n"
572 " ebene clear <Name>\n": ""));
573
574 if (!args || !sizeof(args))
575 return 0;
576
Arathorn78c08372019-12-11 20:14:23 +0100577 if (sscanf(args, "kill %s", target_channel) && IS_ARCH(ME))
MG Mud User88f12472016-06-24 23:31:02 +0200578 {
Arathorn78c08372019-12-11 20:14:23 +0100579 chans = CHMASTER->find(target_channel, ME);
Arathorn85de7602019-11-28 23:04:03 +0100580
Arathorn78c08372019-12-11 20:14:23 +0100581 if (!chans)
582 {
583 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
Arathorn85de7602019-11-28 23:04:03 +0100584 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200585 }
Arathorn78c08372019-12-11 20:14:23 +0100586 else if (pointerp(chans))
587 {
588 notify_fail(
589 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
590 break_string(CountUp(chans), 78));
591 return 0;
592 }
593 else if (CHMASTER->remove_channel(target_channel, ME))
594 {
595 notify_fail("Die Ebene '"+target_channel+"' lies sich nicht "
Arathorn85de7602019-11-28 23:04:03 +0100596 "entfernen!\n");
597 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200598 }
Arathorn85de7602019-11-28 23:04:03 +0100599
Arathorn78c08372019-12-11 20:14:23 +0100600 tell_object(ME,"Du entfernst die Ebene '"+target_channel+"'.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200601 return 1;
602 }
Arathorn85de7602019-11-28 23:04:03 +0100603
Arathorn78c08372019-12-11 20:14:23 +0100604 if (sscanf(args, "clear %s", target_channel) && IS_ARCH(ME))
MG Mud User88f12472016-06-24 23:31:02 +0200605 {
Arathorn78c08372019-12-11 20:14:23 +0100606 chans = CHMASTER->find(target_channel, ME);
607
608 if (!chans)
609 {
610 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
611 return 0;
612 }
613 else if (pointerp(chans))
614 {
615 notify_fail(
616 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
617 break_string(CountUp(chans), 78));
618 return 0;
619 }
620 else if (CHMASTER->clear_history(target_channel, ME) == E_ACCESS_DENIED)
621 {
622 notify_fail("Der Verlauf zur Ebene '"+target_channel+"' liess sich "
623 "nicht entfernen!\n");
624 return 0;
625 }
626
627 tell_object(ME,"Du entfernst den Verlauf zur Ebene '"+target_channel+
628 "'.\n");
629 return 1;
630 }
631
632 if (sscanf(args, "neu %s %s", target_channel, descr) == 2)
633 {
634 notify_fail(break_string("Neue Ebenen kannst Du erst erstellen, wenn "
635 "Du Spielerstufe 5 erreicht hast.", 78));
Arathorn85de7602019-11-28 23:04:03 +0100636 if (QueryProp(P_LEVEL) < 5)
Arathorn85de7602019-11-28 23:04:03 +0100637 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100638
Arathorn78c08372019-12-11 20:14:23 +0100639 notify_fail("Der Name '"+target_channel+"' ist nicht konform!\n");
640 if (!sizeof(regexp(({target_channel}), "^" CHANNELCMDS CHANNELCMDS "*")))
Arathorn85de7602019-11-28 23:04:03 +0100641 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100642
Arathorn78c08372019-12-11 20:14:23 +0100643 notify_fail("Der Name '"+target_channel+"' ist zu lang.\n");
644 if (sizeof(target_channel) > 20)
Arathorn85de7602019-11-28 23:04:03 +0100645 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100646
Arathorn78c08372019-12-11 20:14:23 +0100647 notify_fail("Diese Ebene darfst du nicht erschaffen!\n");
648 if (CHMASTER->new(target_channel, ME, descr) == E_ACCESS_DENIED)
Arathorn85de7602019-11-28 23:04:03 +0100649 return 0;
Arathorn78c08372019-12-11 20:14:23 +0100650
651 tell_object(ME,"Du erschaffst die Ebene '"+target_channel+"'.\n");
652 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) +
653 ({lower_case(target_channel)}));
Arathorn85de7602019-11-28 23:04:03 +0100654 return 1;
MG Mud User88f12472016-06-24 23:31:02 +0200655 }
Arathorn85de7602019-11-28 23:04:03 +0100656
Arathorn78c08372019-12-11 20:14:23 +0100657 if (sscanf(args, "beschreibung %s %s", target_channel, descr) == 2)
MG Mud User88f12472016-06-24 23:31:02 +0200658 {
Arathorn78c08372019-12-11 20:14:23 +0100659 chans = CHMASTER->find(target_channel, ME);
660
661 if (!chans)
Arathorn85de7602019-11-28 23:04:03 +0100662 {
Arathorn78c08372019-12-11 20:14:23 +0100663 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
664 return 0;
665 }
666 else if (pointerp(chans))
667 {
668 notify_fail(
669 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
670 break_string(CountUp(chans), 78));
Arathorn85de7602019-11-28 23:04:03 +0100671 return 0;
672 }
673
Arathorn78c08372019-12-11 20:14:23 +0100674 mapping ch = CHMASTER->list(ME);
675 notify_fail("Du bist nicht berechtigt, die Beschreibung der Ebene"
676 " '"+target_channel+"' zu aendern.\n");
Zesstra60df9112020-08-18 00:01:58 +0200677 if (ch[lower_case(chans)][I_SUPERVISOR] != ME)
Arathorn85de7602019-11-28 23:04:03 +0100678 return 0;
Arathorn78c08372019-12-11 20:14:23 +0100679
680 ch[lower_case(target_channel)][I_INFO] = descr;
681 tell_object(ME,"Die Ebene '"+target_channel+"' hat ab sofort die "
682 "Beschreibung:\n"+descr+"\n");
MG Mud User88f12472016-06-24 23:31:02 +0200683 return 1;
684 }
Arathorn85de7602019-11-28 23:04:03 +0100685
Arathorn78c08372019-12-11 20:14:23 +0100686 if (sscanf(args, "%s=%s", _sh_cut, target_channel) == 2 &&
687 sizeof(target_channel))
MG Mud User88f12472016-06-24 23:31:02 +0200688 {
Arathorn78c08372019-12-11 20:14:23 +0100689 chans = CHMASTER->find(target_channel, ME);
690
691 if (!chans)
Arathorn85de7602019-11-28 23:04:03 +0100692 {
Arathorn78c08372019-12-11 20:14:23 +0100693 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
694 return 0;
695 }
696 else if (pointerp(chans))
697 {
698 notify_fail(
699 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
700 break_string(CountUp(chans), 78));
Arathorn85de7602019-11-28 23:04:03 +0100701 return 0;
702 }
703
Arathorn78c08372019-12-11 20:14:23 +0100704 mapping shortcut_list = QueryProp(P_CHANNEL_SHORT) || ([]);
705 m_add(shortcut_list, _sh_cut, chans);
706 SetProp(P_CHANNEL_SHORT, shortcut_list);
Arathorn85de7602019-11-28 23:04:03 +0100707
MG Mud User88f12472016-06-24 23:31:02 +0200708 shortcut = QueryProp(P_CHANNEL_SHORT);
Arathorn85de7602019-11-28 23:04:03 +0100709
Arathorn78c08372019-12-11 20:14:23 +0100710 tell_object(ME,"'"+_sh_cut+"' wird jetzt als Abkuerzung fuer '"+chans+
711 "' anerkannt.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200712 return 1;
713 }
Arathorn85de7602019-11-28 23:04:03 +0100714
Arathorn78c08372019-12-11 20:14:23 +0100715 if (sscanf(args, "%s=", _sh_cut))
MG Mud User88f12472016-06-24 23:31:02 +0200716 {
Arathorn85de7602019-11-28 23:04:03 +0100717 SetProp(P_CHANNEL_SHORT,
Arathorn78c08372019-12-11 20:14:23 +0100718 m_delete(QueryProp(P_CHANNEL_SHORT) || ([]), _sh_cut));
MG Mud User88f12472016-06-24 23:31:02 +0200719 shortcut = QueryProp(P_CHANNEL_SHORT);
Arathorn78c08372019-12-11 20:14:23 +0100720 tell_object(ME,"Du loeschst die Abkuerzung '"+_sh_cut+"'.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200721 return 1;
722 }
Arathorn85de7602019-11-28 23:04:03 +0100723
724 if (args == "an" || args == "ein")
MG Mud User88f12472016-06-24 23:31:02 +0200725 {
Arathorn85de7602019-11-28 23:04:03 +0100726 if (pointerp(QueryProp(P_SWAP_CHANNELS)))
MG Mud User88f12472016-06-24 23:31:02 +0200727 SetProp(P_CHANNELS, QueryProp(P_SWAP_CHANNELS));
728 else
Arathorn78c08372019-12-11 20:14:23 +0100729 SetProp(P_CHANNELS, m_indices(CHMASTER->list(ME)));
Arathorn85de7602019-11-28 23:04:03 +0100730
731 // <excl> enthaelt die Channelnamen, deren Channel nicht erstellt wurden.
732 string* excl = RegisterChannels();
Arathorn78c08372019-12-11 20:14:23 +0100733 tell_object(ME,"Du schaltest folgende Ebenen ein:\n"+
734 break_string(CountUp(QueryProp(P_CHANNELS) - excl), 78));
MG Mud User88f12472016-06-24 23:31:02 +0200735 SetProp(P_SWAP_CHANNELS, 0);
736 return 1;
737 }
Arathorn85de7602019-11-28 23:04:03 +0100738
739 if (args == "aus")
MG Mud User88f12472016-06-24 23:31:02 +0200740 {
741 SetProp(P_SWAP_CHANNELS, QueryProp(P_CHANNELS));
742 RemoveChannels();
743 SetProp(P_CHANNELS, ({}));
Arathorn78c08372019-12-11 20:14:23 +0100744 tell_object(ME,"Du stellst die Ebenen ab.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200745 return 1;
746 }
Arathorn85de7602019-11-28 23:04:03 +0100747
Arathorn00764692019-11-27 22:09:31 +0100748 string* pa = old_explode(args, " ");
Arathorn78c08372019-12-11 20:14:23 +0100749 if (strstr("abkuerzungen", pa[0]) == 0)
MG Mud User88f12472016-06-24 23:31:02 +0200750 {
Arathorn85de7602019-11-28 23:04:03 +0100751 string txt = "";
Arathorn78c08372019-12-11 20:14:23 +0100752 if (sizeof(pa) > 1 && strstr("standard", pa[1]) == 0)
MG Mud User88f12472016-06-24 23:31:02 +0200753 {
Arathorn78c08372019-12-11 20:14:23 +0100754 tell_object(ME,"Die Standardabkuerzungen werden gesetzt.\n");
755 SetProp(P_CHANNEL_SHORT,
756 DEFAULT_SHORTCUTS + (IS_LEARNER(ME) ? WIZARD_SHORTCUTS : ([])));
MG Mud User88f12472016-06-24 23:31:02 +0200757 }
Arathorn78c08372019-12-11 20:14:23 +0100758
759 mapping scut = QueryProp(P_CHANNEL_SHORT);
760 string* abbreviations = m_indices(scut);
761 foreach (string abk : sort_array(abbreviations, #'>))
762 {
763 txt += sprintf("%5.5s = %s\n", abk, scut[abk]);
Arathorndc28afc2018-11-26 22:20:59 +0100764 }
Arathorn78c08372019-12-11 20:14:23 +0100765 More( "Folgende Abkuerzungen sind definiert:\n"+
766 sprintf("%-78#s\n", txt) );
MG Mud User88f12472016-06-24 23:31:02 +0200767 return 1;
768 }
Arathorn85de7602019-11-28 23:04:03 +0100769
Arathorn78c08372019-12-11 20:14:23 +0100770 if (strstr("standard", pa[0]) == 0)
Arathorn85de7602019-11-28 23:04:03 +0100771 {
772 if (sizeof(pa) < 2)
773 {
Arathorn78c08372019-12-11 20:14:23 +0100774 notify_fail("Benutzung: ebene standard <Ebene>\n"+
775 (QueryProp(P_STD_CHANNEL)
776 ? "Momentan ist '"+QueryProp(P_STD_CHANNEL)+"' eingestellt.\n"
777 : "Es ist keine Standardebene eingestellt.\n"));
Arathorn85de7602019-11-28 23:04:03 +0100778 return 0;
779 }
Arathorn78c08372019-12-11 20:14:23 +0100780
781 chans = CHMASTER->find(pa[1], ME);
782 if (!chans)
Arathorn85de7602019-11-28 23:04:03 +0100783 {
Arathorn78c08372019-12-11 20:14:23 +0100784 notify_fail("Ebene '"+pa[1]+"' nicht gefunden!\n");
785 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100786 }
Arathorn78c08372019-12-11 20:14:23 +0100787 else if (pointerp(chans))
788 {
789 notify_fail(
790 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
791 break_string(CountUp(chans), 78));
792 return 0;
793 }
794
795 tell_object(ME,"'"+chans+"' ist jetzt die Standardebene.\n");
796 SetProp(P_STD_CHANNEL, chans);
797 return 1;
Arathorn85de7602019-11-28 23:04:03 +0100798 }
MG Mud User88f12472016-06-24 23:31:02 +0200799 return(0);
800}