blob: 9167e6adf771e89e000eeca4f7e0a41735f7f891 [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
Arathorn78c08372019-12-11 20:14:23 +0100250// string n: Channelname
251// <object*|closure|string|object>* a: Channeldaten
252// string* m: P_CHANNELS
253// string* l: Ausgabeliste.
254private void OLDcreateList(string n, <object*|closure|string|object>* a,
255 string* m, string* l)
MG Mud User88f12472016-06-24 23:31:02 +0200256{
Arathorn78c08372019-12-11 20:14:23 +0100257 // Wir suchen das vom Spieler festgelegte Kuerzel fuer die aktuell
258 // bearbeitete Ebene, falls vorhanden.
Arathorn00764692019-11-27 22:09:31 +0100259 string sh = "";
Arathorn78c08372019-12-11 20:14:23 +0100260 foreach(string sc, string chan : shortcut) {
261 if ( lower_case(chan) == n ) {
262 sh = sc;
263 break;
264 }
265 }
Arathorn00764692019-11-27 22:09:31 +0100266
MG Mud User88f12472016-06-24 23:31:02 +0200267 l += ({ sprintf("%-12.12'.'s %c[%-1.1s] %|12.12' 's (%-|3' 'd) %-42.42s\n",
Arathorn78c08372019-12-11 20:14:23 +0100268 a[I_NAME],
269 (member(m, n) != -1
270 ? '*'
271 : ' '),
272 sh,
273 a[I_MASTER]
274 ? getName(a[I_MASTER])
275 : getName(a[I_ACCESS]),
276 sizeof(a[I_MEMBER] - ({ find_object(CHMASTER) })),
277 (closurep(a[I_INFO]) && objectp(query_closure_object(a[I_INFO])))
278 ? funcall(a[I_INFO]) || "- Keine Beschreibung -"
279 : (stringp(a[I_INFO])
280 ? a[I_INFO]
281 : "- Keine Beschreibung -")) });
MG Mud User88f12472016-06-24 23:31:02 +0200282}
283
Arathorn78c08372019-12-11 20:14:23 +0100284private void createList(mapping ch_list, string* p_channels,
285 int show_only_subscribed) {
286 // Kopfzeile der Tabelle erstellen.
287 string listhead =
288 sprintf("%-12.12' 's [A] %|12' 's (%-3' 's) %-42.42s\n",
289 "Name", "Eigner", "Sp", "Beschreibung");
290 // Linie drunter.
291 listhead += ("-"*78 + "\n");
292
293 // Rest der Daten erstmal in einem Array zusammenstellen, um es
294 // anschliessend sortieren zu koennen.
295 string* entries = ({});
296
297 object* ch_members;
298 string|object ch_master;
299 string ch_real_name, ch_description;
300 closure|string ch_access;
301 closure|string ch_info;
302 string sh;
303
304 foreach(string chname, <object*|closure|string|object>* chdata : ch_list)
305 {
306 // Wenn nur abonnierte Ebenen aufgelistet werden sollen, dann alle
307 // ueberspringen, die nicht in P_CHANNELS stehen.
308 int is_subscribed = (member(p_channels, chname) > -1);
309 if (show_only_subscribed && !is_subscribed)
310 continue;
311
312 ch_members = chdata[I_MEMBER];
313 ch_master = chdata[I_MASTER];
314 ch_access = chdata[I_ACCESS];
315 ch_real_name = chdata[I_NAME];
316 ch_info = chdata[I_INFO];
317 sh = "";
318
319 // Ist eine Closure als I_INFO eingetragen, zu der es auch ein Objekt
320 // gibt, tragen wir deren Rueckgabewert als Beschreibung ein.
321 if (closurep(ch_info) && objectp(query_closure_object(ch_info))) {
322 ch_description = funcall(ch_info);
323 }
324 // Ist es ein String, wird er unveraendert uebernommen.
325 else if (stringp(ch_info)) {
326 ch_description = ch_info;
327 }
328 // Sollte nirgends etwas eingetragen sein, oder die Closure 0 zurueck-
329 // gegeben haben, gibt es eine Defaultbeschreibung. Man haette die
330 // Variable auch schon mit dem Default initialisieren koennen, der kann
331 // aber bei Rueckgabe von 0 aus der Closure wieder ueberschrieben werden.
332 // Daher passiert das erst hier.
333 ch_description ||= "- Keine Beschreibung -";
334
335 // Wir brauchen noch das vom Spieler festgelegte Kuerzel fuer die aktuell
336 // bearbeitete Ebene, falls vorhanden, um es in die Liste eintragen
337 // zu koennen.
338 foreach(string _sh_cut, string _chan_name : shortcut) {
339 if ( lower_case(_chan_name) == chname ) {
340 sh = _sh_cut;
341 break;
342 }
343 }
344
345 // Jetzt haben wir endlich alle Infos beisammen und koennen die
346 // Eintraege zusammenbauen.
347 entries += ({
348 sprintf("%-12.12'.'s %c[%-1.1s] %|12.12' 's (%-|3' 'd) %-42.42s\n",
349 ch_real_name,
350 is_subscribed ? '*' : ' ',
351 sh,
352 ch_master ? getName(ch_master) : getName(ch_access),
353 sizeof(ch_members - ({ find_object(CHMASTER) })),
354 ch_description
355 ) });
356 }
357
358 // alphabetisch sortieren
359 entries = sort_array(entries, #'>);
360
361 More( listhead + // Listenkopf
362 implode(entries, "") + // Eintraege
363 "-"*78+"\n" ); // Linie drunter
364}
365
366private string|string* getChannel(string ch)
MG Mud User88f12472016-06-24 23:31:02 +0200367{
Arathorn85de7602019-11-28 23:04:03 +0100368 if (!sizeof(ch))
Arathorn00764692019-11-27 22:09:31 +0100369 ch = QueryProp(P_STD_CHANNEL);
Arathorn85de7602019-11-28 23:04:03 +0100370 if (shortcut && shortcut[ch])
Arathorn00764692019-11-27 22:09:31 +0100371 ch = shortcut[ch];
Arathorn78c08372019-12-11 20:14:23 +0100372 return CHMASTER->find(ch, ME);
MG Mud User88f12472016-06-24 23:31:02 +0200373}
374
MG Mud User88f12472016-06-24 23:31:02 +0200375int ChannelParser(string args)
376{
Arathorn78c08372019-12-11 20:14:23 +0100377 string|string* ch;
MG Mud User88f12472016-06-24 23:31:02 +0200378 int pos, type, err;
Arathorn78c08372019-12-11 20:14:23 +0100379 string tmp;
MG Mud User88f12472016-06-24 23:31:02 +0200380 notify_fail("Benutzung: -<Ebene>[ ]['|:|;]<Text>\n"
381 " -<Ebene>[+|-|?|!|*]\n"
382 " -?\n");
Arathorn78c08372019-12-11 20:14:23 +0100383
384 args = _unparsed_args(1);
385 string|string* cmd = query_verb();
Arathorn85de7602019-11-28 23:04:03 +0100386 if (!cmd && !args)
387 return 0;
388
389 if (!args)
390 args = "";
391
MG Mud User88f12472016-06-24 23:31:02 +0200392 cmd = cmd[1..];
Arathorn78c08372019-12-11 20:14:23 +0100393 cmd = regexplode(cmd, "^" CHANNELCMDS "*" "([+-]|\\!|\\?|\\*)*");
Arathorn85de7602019-11-28 23:04:03 +0100394 if (sizeof(cmd) > 1)
MG Mud User88f12472016-06-24 23:31:02 +0200395 {
396 //z.B. cmd= ({"","allgemein",":testet"})
Arathorn85de7602019-11-28 23:04:03 +0100397 if (sizeof(cmd[1]) > 1 && strstr("+-?!*", cmd[1][<1..<1]) > -1)
MG Mud User88f12472016-06-24 23:31:02 +0200398 tmp = cmd[1][0..<2];
399 else
400 tmp = cmd[1];
Arathorn85de7602019-11-28 23:04:03 +0100401
402 if (cmd[1] != "?" && cmd[1] != "!")
Zesstra57a693e2019-01-06 22:08:24 +0100403 {
404 ch = getChannel(tmp);
Arathorn85de7602019-11-28 23:04:03 +0100405 if (pointerp(ch))
MG Mud User88f12472016-06-24 23:31:02 +0200406 {
407 notify_fail("Diese Angabe war nicht eindeutig! "
Arathorn78c08372019-12-11 20:14:23 +0100408 "Folgende Ebenen passen:\n"+
409 implode(ch, ", ")+"\n");
MG Mud User88f12472016-06-24 23:31:02 +0200410 return 0;
411 }
Arathorn85de7602019-11-28 23:04:03 +0100412 else if (!ch)
413 {
414 notify_fail("Die Ebene '"+tmp+ "' gibt es nicht!\n");
415 return 0;
416 }
Zesstra57a693e2019-01-06 22:08:24 +0100417 }
Arathorn85de7602019-11-28 23:04:03 +0100418
MG Mud User88f12472016-06-24 23:31:02 +0200419 if (sizeof(cmd[1])) {
Arathorn85de7602019-11-28 23:04:03 +0100420 switch (cmd[1][<1]) {
421 case '+':
Arathorn78c08372019-12-11 20:14:23 +0100422 switch (CHMASTER->join(ch, ME))
Arathorn85de7602019-11-28 23:04:03 +0100423 {
424 case E_ACCESS_DENIED:
425 notify_fail("Du darfst an die Ebene '"+ch+"' nicht heran.\n");
426 return 0;
427 case E_ALREADY_JOINED:
428 notify_fail("Du hast diese Ebene schon betreten!\n");
429 return 0;
430 default:
431 break;
432 }
Arathorn78c08372019-12-11 20:14:23 +0100433 tell_object(ME,"Du betrittst die Ebene '"+ch+"'.\n");
Arathorn85de7602019-11-28 23:04:03 +0100434 if (member(QueryProp(P_CHANNELS), ch = lower_case(ch)) == -1)
435 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) + ({ ch }));
436 return 1;
437
438 case '-':
Arathorn78c08372019-12-11 20:14:23 +0100439 switch (CHMASTER->leave(ch, ME))
Arathorn85de7602019-11-28 23:04:03 +0100440 {
441 case E_ACCESS_DENIED:
Arathorn78c08372019-12-11 20:14:23 +0100442 tell_object(ME,"Du kannst die Ebene '"+ch+"' nicht "
443 "verlassen.\n");
Arathorn85de7602019-11-28 23:04:03 +0100444 break;
445 case E_NOT_MEMBER:
Arathorn78c08372019-12-11 20:14:23 +0100446 tell_object(ME,"Wie willst Du eine Ebene verlassen, welche Du "
447 "nicht betreten hast?\n");
Arathorn85de7602019-11-28 23:04:03 +0100448 break;
449 default:
Arathorn78c08372019-12-11 20:14:23 +0100450 tell_object(ME,"Du verlaesst die Ebene '"+ch+"'.\n");
Arathorn85de7602019-11-28 23:04:03 +0100451 SetProp(P_CHANNELS,
452 QueryProp(P_CHANNELS) - ({ lower_case(ch), ch }));
453 break;
454 }
455 return 1;
456
457 case '!':
458 case '?':
Arathorn78c08372019-12-11 20:14:23 +0100459 mapping l = CHMASTER->list(ME);
460 if (mappingp(l))
Arathorn85de7602019-11-28 23:04:03 +0100461 {
Arathorn78c08372019-12-11 20:14:23 +0100462 // Es wurde ein Channel angegeben, dessen Infos gefragt sind.
Arathorn85de7602019-11-28 23:04:03 +0100463 if (stringp(ch) && sizeof(ch) && pointerp(l[ch = lower_case(ch)]))
464 {
Arathorn78c08372019-12-11 20:14:23 +0100465 <object*|closure|string|object>* chdata = l[ch];
466 string* m =
467 sort_array(map(chdata[I_MEMBER], #'getName, WER), #'>) -
468 ({ CMNAME });
469
470 string wen;
471 switch(sizeof(m)) {
472 case 1: wen = "ein Gesicht"; break;
473 case 0: wen = "niemanden"; break;
474 // TODO: mittels Zahlwoerter-Objekt die Zahl als Text
475 // ausgeben?
476 default: wen = sprintf("%d Gesichter", sizeof(m)); break;
477 }
478
479 tell_object(ME,
480 chdata[I_NAME]+", "+funcall(chdata[I_INFO])+".\n" +
481 "Du siehst "+wen+" auf der Ebene '"+chdata[I_NAME]+"':\n"+
482 break_string(CountUp(m), 78) +
483 (chdata[I_MASTER] ? getName(chdata[I_MASTER])
484 : getName(chdata[I_ACCESS], WER))+
485 " hat das Sagen auf dieser Ebene.\n");
Arathorn85de7602019-11-28 23:04:03 +0100486 }
Arathorn78c08372019-12-11 20:14:23 +0100487 // kein Channel angegeben, dann Gesamtliste erzeugen
Arathorn85de7602019-11-28 23:04:03 +0100488 else
489 {
Arathorn78c08372019-12-11 20:14:23 +0100490 // Wenn nur die abonnierten angezeigt werden sollen
491 // (Kommando -!), wird die Bedingung im 3. Argument == 1, so
492 // dass createList() die reduzierte Tabelle erzeugt.
493 createList(l, QueryProp(P_CHANNELS), (cmd[1][<1] == '!'));
Arathorn85de7602019-11-28 23:04:03 +0100494 }
495 }
496 return 1;
497
498 case '*':
Arathorn78c08372019-12-11 20:14:23 +0100499 int|<int|string>** hist = CHMASTER->history(ch, ME);
Arathorn85de7602019-11-28 23:04:03 +0100500 if (!pointerp(hist) || !sizeof(hist))
501 {
Arathorn78c08372019-12-11 20:14:23 +0100502 tell_object(ME,"Es ist keine Geschichte fuer '"+ch+
503 "' verfuegbar.\n");
Arathorn85de7602019-11-28 23:04:03 +0100504 return 1;
505 }
506
Arathorn85de7602019-11-28 23:04:03 +0100507 int amount = to_int(cmd[2]);
508 if (amount <= 0 || amount >= sizeof(hist))
509 amount = sizeof(hist);
510
Arathorn78c08372019-12-11 20:14:23 +0100511 string txt = "Folgendes ist auf '"+ch+"' passiert:\n" +
512 implode(map(hist[<amount..], #'ChannelMessage, 1), "");
MG Mud User88f12472016-06-24 23:31:02 +0200513 More(txt);
Arathorn85de7602019-11-28 23:04:03 +0100514 return 1;
515
516 default:
517 break;
Zesstra57a693e2019-01-06 22:08:24 +0100518 }
MG Mud User88f12472016-06-24 23:31:02 +0200519 }
520 }
Arathorn85de7602019-11-28 23:04:03 +0100521
Arathorn78c08372019-12-11 20:14:23 +0100522 cmd = implode(cmd[2..], "");
523 if (sizeof(cmd))
Arathorn85de7602019-11-28 23:04:03 +0100524 args = cmd + (sizeof(args) ? " " : "") + args;
MG Mud User88f12472016-06-24 23:31:02 +0200525
526 // KOntrollchars ausfiltern.
Arathorn78c08372019-12-11 20:14:23 +0100527 args = regreplace(args, "[[:cntrl:]]", "", RE_PCRE|RE_GLOBAL);
Arathorn85de7602019-11-28 23:04:03 +0100528 if (!sizeof(args))
529 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200530
531 //Wenn cmd leer ist: MSG_SAY
Arathorn85de7602019-11-28 23:04:03 +0100532 if (!sizeof(cmd))
Arathorn78c08372019-12-11 20:14:23 +0100533 {
Arathorn85de7602019-11-28 23:04:03 +0100534 type = MSG_SAY;
Arathorn78c08372019-12-11 20:14:23 +0100535 }
Arathorn85de7602019-11-28 23:04:03 +0100536 else
537 {
538 switch (cmd[0])
MG Mud User88f12472016-06-24 23:31:02 +0200539 {
Arathorn85de7602019-11-28 23:04:03 +0100540 case ':' :
541 type = MSG_EMOTE;
542 args = args[1..];
543 break;
544 case ';' :
545 type = MSG_GEMOTE;
546 args = args[1..];
547 break;
548 case '\'':
549 args = args[1..];
550 // Der Fallthrough in default ist hier Absicht.
551 default :
552 type = MSG_SAY;
553 break;
MG Mud User88f12472016-06-24 23:31:02 +0200554 }
555 }
Arathorn85de7602019-11-28 23:04:03 +0100556
557 if (!ch || !sizeof(ch))
558 ch = QueryProp(P_STD_CHANNEL);
559
Arathorn78c08372019-12-11 20:14:23 +0100560 err = CHMASTER->send(ch, ME, args, type);
Arathorn85de7602019-11-28 23:04:03 +0100561 if (err < 0)
562 {
Arathorn78c08372019-12-11 20:14:23 +0100563 err = CHMASTER->join(ch, ME);
Arathorn85de7602019-11-28 23:04:03 +0100564 if (!err)
MG Mud User88f12472016-06-24 23:31:02 +0200565 {
Arathorn78c08372019-12-11 20:14:23 +0100566 ch = lower_case(ch);
567 if (member(QueryProp(P_CHANNELS), ch) == -1)
MG Mud User88f12472016-06-24 23:31:02 +0200568 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) + ({ ch }));
Arathorn78c08372019-12-11 20:14:23 +0100569 err = CHMASTER->send(ch, ME, args, type);
MG Mud User88f12472016-06-24 23:31:02 +0200570 }
Arathorn85de7602019-11-28 23:04:03 +0100571 }
MG Mud User88f12472016-06-24 23:31:02 +0200572
Arathorn85de7602019-11-28 23:04:03 +0100573 switch (err)
MG Mud User88f12472016-06-24 23:31:02 +0200574 {
Arathorn85de7602019-11-28 23:04:03 +0100575 case E_ACCESS_DENIED:
576 notify_fail("Auf der Ebene '"+ch+"' darfst Du nichts sagen.\n");
577 return 0;
578 case E_NOT_MEMBER:
579 notify_fail("Du hast die Ebene '"+ch+"' nicht betreten!\n");
580 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200581 }
582 return 1;
583}
584
585int ChannelAdmin(string args)
586{
Arathorn85de7602019-11-28 23:04:03 +0100587 args = _unparsed_args();
588
Arathorn78c08372019-12-11 20:14:23 +0100589 string target_channel, descr, _sh_cut;
590 string|string* chans;
MG Mud User88f12472016-06-24 23:31:02 +0200591 notify_fail("Benutzung: ebene <Abkuerzung>=<Ebene>\n"
592 " ebene <Abkuerzung>=\n"
593 " ebene abkuerzungen [standard]\n"
594 " ebene standard <Ebene>\n"
595 " ebene an|ein|aus\n"
596 +(QueryProp(P_LEVEL) >= 5 ?
597 " ebene neu <Name> <Bezeichnung>\n"
Arathorn85de7602019-11-28 23:04:03 +0100598 " ebene beschreibung <Name> <Beschreibung>\n" : "")
Arathorn78c08372019-12-11 20:14:23 +0100599 +(IS_ARCH(ME) ?
Arathorn85de7602019-11-28 23:04:03 +0100600 " ebene kill <Name>\n"
601 " ebene clear <Name>\n": ""));
602
603 if (!args || !sizeof(args))
604 return 0;
605
Arathorn78c08372019-12-11 20:14:23 +0100606 if (sscanf(args, "kill %s", target_channel) && IS_ARCH(ME))
MG Mud User88f12472016-06-24 23:31:02 +0200607 {
Arathorn78c08372019-12-11 20:14:23 +0100608 chans = CHMASTER->find(target_channel, ME);
Arathorn85de7602019-11-28 23:04:03 +0100609
Arathorn78c08372019-12-11 20:14:23 +0100610 if (!chans)
611 {
612 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
Arathorn85de7602019-11-28 23:04:03 +0100613 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200614 }
Arathorn78c08372019-12-11 20:14:23 +0100615 else if (pointerp(chans))
616 {
617 notify_fail(
618 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
619 break_string(CountUp(chans), 78));
620 return 0;
621 }
622 else if (CHMASTER->remove_channel(target_channel, ME))
623 {
624 notify_fail("Die Ebene '"+target_channel+"' lies sich nicht "
Arathorn85de7602019-11-28 23:04:03 +0100625 "entfernen!\n");
626 return 0;
MG Mud User88f12472016-06-24 23:31:02 +0200627 }
Arathorn85de7602019-11-28 23:04:03 +0100628
Arathorn78c08372019-12-11 20:14:23 +0100629 tell_object(ME,"Du entfernst die Ebene '"+target_channel+"'.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200630 return 1;
631 }
Arathorn85de7602019-11-28 23:04:03 +0100632
Arathorn78c08372019-12-11 20:14:23 +0100633 if (sscanf(args, "clear %s", target_channel) && IS_ARCH(ME))
MG Mud User88f12472016-06-24 23:31:02 +0200634 {
Arathorn78c08372019-12-11 20:14:23 +0100635 chans = CHMASTER->find(target_channel, ME);
636
637 if (!chans)
638 {
639 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
640 return 0;
641 }
642 else if (pointerp(chans))
643 {
644 notify_fail(
645 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
646 break_string(CountUp(chans), 78));
647 return 0;
648 }
649 else if (CHMASTER->clear_history(target_channel, ME) == E_ACCESS_DENIED)
650 {
651 notify_fail("Der Verlauf zur Ebene '"+target_channel+"' liess sich "
652 "nicht entfernen!\n");
653 return 0;
654 }
655
656 tell_object(ME,"Du entfernst den Verlauf zur Ebene '"+target_channel+
657 "'.\n");
658 return 1;
659 }
660
661 if (sscanf(args, "neu %s %s", target_channel, descr) == 2)
662 {
663 notify_fail(break_string("Neue Ebenen kannst Du erst erstellen, wenn "
664 "Du Spielerstufe 5 erreicht hast.", 78));
Arathorn85de7602019-11-28 23:04:03 +0100665 if (QueryProp(P_LEVEL) < 5)
Arathorn85de7602019-11-28 23:04:03 +0100666 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100667
Arathorn78c08372019-12-11 20:14:23 +0100668 notify_fail("Der Name '"+target_channel+"' ist nicht konform!\n");
669 if (!sizeof(regexp(({target_channel}), "^" CHANNELCMDS CHANNELCMDS "*")))
Arathorn85de7602019-11-28 23:04:03 +0100670 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100671
Arathorn78c08372019-12-11 20:14:23 +0100672 notify_fail("Der Name '"+target_channel+"' ist zu lang.\n");
673 if (sizeof(target_channel) > 20)
Arathorn85de7602019-11-28 23:04:03 +0100674 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100675
Arathorn78c08372019-12-11 20:14:23 +0100676 notify_fail("Diese Ebene darfst du nicht erschaffen!\n");
677 if (CHMASTER->new(target_channel, ME, descr) == E_ACCESS_DENIED)
Arathorn85de7602019-11-28 23:04:03 +0100678 return 0;
Arathorn78c08372019-12-11 20:14:23 +0100679
680 tell_object(ME,"Du erschaffst die Ebene '"+target_channel+"'.\n");
681 SetProp(P_CHANNELS, QueryProp(P_CHANNELS) +
682 ({lower_case(target_channel)}));
Arathorn85de7602019-11-28 23:04:03 +0100683 return 1;
MG Mud User88f12472016-06-24 23:31:02 +0200684 }
Arathorn85de7602019-11-28 23:04:03 +0100685
Arathorn78c08372019-12-11 20:14:23 +0100686 if (sscanf(args, "beschreibung %s %s", target_channel, descr) == 2)
MG Mud User88f12472016-06-24 23:31:02 +0200687 {
Arathorn78c08372019-12-11 20:14:23 +0100688 chans = CHMASTER->find(target_channel, ME);
689
690 if (!chans)
Arathorn85de7602019-11-28 23:04:03 +0100691 {
Arathorn78c08372019-12-11 20:14:23 +0100692 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
693 return 0;
694 }
695 else if (pointerp(chans))
696 {
697 notify_fail(
698 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
699 break_string(CountUp(chans), 78));
Arathorn85de7602019-11-28 23:04:03 +0100700 return 0;
701 }
702
Arathorn78c08372019-12-11 20:14:23 +0100703 mapping ch = CHMASTER->list(ME);
704 notify_fail("Du bist nicht berechtigt, die Beschreibung der Ebene"
705 " '"+target_channel+"' zu aendern.\n");
706 if (ch[lower_case(chans)][I_MASTER] != ME)
Arathorn85de7602019-11-28 23:04:03 +0100707 return 0;
Arathorn78c08372019-12-11 20:14:23 +0100708
709 ch[lower_case(target_channel)][I_INFO] = descr;
710 tell_object(ME,"Die Ebene '"+target_channel+"' hat ab sofort die "
711 "Beschreibung:\n"+descr+"\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=%s", _sh_cut, target_channel) == 2 &&
716 sizeof(target_channel))
MG Mud User88f12472016-06-24 23:31:02 +0200717 {
Arathorn78c08372019-12-11 20:14:23 +0100718 chans = CHMASTER->find(target_channel, ME);
719
720 if (!chans)
Arathorn85de7602019-11-28 23:04:03 +0100721 {
Arathorn78c08372019-12-11 20:14:23 +0100722 notify_fail("Ebene '"+target_channel+"' nicht gefunden!\n");
723 return 0;
724 }
725 else if (pointerp(chans))
726 {
727 notify_fail(
728 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
729 break_string(CountUp(chans), 78));
Arathorn85de7602019-11-28 23:04:03 +0100730 return 0;
731 }
732
Arathorn78c08372019-12-11 20:14:23 +0100733 mapping shortcut_list = QueryProp(P_CHANNEL_SHORT) || ([]);
734 m_add(shortcut_list, _sh_cut, chans);
735 SetProp(P_CHANNEL_SHORT, shortcut_list);
Arathorn85de7602019-11-28 23:04:03 +0100736
MG Mud User88f12472016-06-24 23:31:02 +0200737 shortcut = QueryProp(P_CHANNEL_SHORT);
Arathorn85de7602019-11-28 23:04:03 +0100738
Arathorn78c08372019-12-11 20:14:23 +0100739 tell_object(ME,"'"+_sh_cut+"' wird jetzt als Abkuerzung fuer '"+chans+
740 "' anerkannt.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200741 return 1;
742 }
Arathorn85de7602019-11-28 23:04:03 +0100743
Arathorn78c08372019-12-11 20:14:23 +0100744 if (sscanf(args, "%s=", _sh_cut))
MG Mud User88f12472016-06-24 23:31:02 +0200745 {
Arathorn85de7602019-11-28 23:04:03 +0100746 SetProp(P_CHANNEL_SHORT,
Arathorn78c08372019-12-11 20:14:23 +0100747 m_delete(QueryProp(P_CHANNEL_SHORT) || ([]), _sh_cut));
MG Mud User88f12472016-06-24 23:31:02 +0200748 shortcut = QueryProp(P_CHANNEL_SHORT);
Arathorn78c08372019-12-11 20:14:23 +0100749 tell_object(ME,"Du loeschst die Abkuerzung '"+_sh_cut+"'.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200750 return 1;
751 }
Arathorn85de7602019-11-28 23:04:03 +0100752
753 if (args == "an" || args == "ein")
MG Mud User88f12472016-06-24 23:31:02 +0200754 {
Arathorn85de7602019-11-28 23:04:03 +0100755 if (pointerp(QueryProp(P_SWAP_CHANNELS)))
MG Mud User88f12472016-06-24 23:31:02 +0200756 SetProp(P_CHANNELS, QueryProp(P_SWAP_CHANNELS));
757 else
Arathorn78c08372019-12-11 20:14:23 +0100758 SetProp(P_CHANNELS, m_indices(CHMASTER->list(ME)));
Arathorn85de7602019-11-28 23:04:03 +0100759
760 // <excl> enthaelt die Channelnamen, deren Channel nicht erstellt wurden.
761 string* excl = RegisterChannels();
Arathorn78c08372019-12-11 20:14:23 +0100762 tell_object(ME,"Du schaltest folgende Ebenen ein:\n"+
763 break_string(CountUp(QueryProp(P_CHANNELS) - excl), 78));
MG Mud User88f12472016-06-24 23:31:02 +0200764 SetProp(P_SWAP_CHANNELS, 0);
765 return 1;
766 }
Arathorn85de7602019-11-28 23:04:03 +0100767
768 if (args == "aus")
MG Mud User88f12472016-06-24 23:31:02 +0200769 {
770 SetProp(P_SWAP_CHANNELS, QueryProp(P_CHANNELS));
771 RemoveChannels();
772 SetProp(P_CHANNELS, ({}));
Arathorn78c08372019-12-11 20:14:23 +0100773 tell_object(ME,"Du stellst die Ebenen ab.\n");
MG Mud User88f12472016-06-24 23:31:02 +0200774 return 1;
775 }
Arathorn85de7602019-11-28 23:04:03 +0100776
Arathorn00764692019-11-27 22:09:31 +0100777 string* pa = old_explode(args, " ");
Arathorn78c08372019-12-11 20:14:23 +0100778 if (strstr("abkuerzungen", pa[0]) == 0)
MG Mud User88f12472016-06-24 23:31:02 +0200779 {
Arathorn85de7602019-11-28 23:04:03 +0100780 string txt = "";
Arathorn78c08372019-12-11 20:14:23 +0100781 if (sizeof(pa) > 1 && strstr("standard", pa[1]) == 0)
MG Mud User88f12472016-06-24 23:31:02 +0200782 {
Arathorn78c08372019-12-11 20:14:23 +0100783 tell_object(ME,"Die Standardabkuerzungen werden gesetzt.\n");
784 SetProp(P_CHANNEL_SHORT,
785 DEFAULT_SHORTCUTS + (IS_LEARNER(ME) ? WIZARD_SHORTCUTS : ([])));
MG Mud User88f12472016-06-24 23:31:02 +0200786 }
Arathorn78c08372019-12-11 20:14:23 +0100787
788 mapping scut = QueryProp(P_CHANNEL_SHORT);
789 string* abbreviations = m_indices(scut);
790 foreach (string abk : sort_array(abbreviations, #'>))
791 {
792 txt += sprintf("%5.5s = %s\n", abk, scut[abk]);
Arathorndc28afc2018-11-26 22:20:59 +0100793 }
Arathorn78c08372019-12-11 20:14:23 +0100794 More( "Folgende Abkuerzungen sind definiert:\n"+
795 sprintf("%-78#s\n", txt) );
MG Mud User88f12472016-06-24 23:31:02 +0200796 return 1;
797 }
Arathorn85de7602019-11-28 23:04:03 +0100798
Arathorn78c08372019-12-11 20:14:23 +0100799 if (strstr("standard", pa[0]) == 0)
Arathorn85de7602019-11-28 23:04:03 +0100800 {
801 if (sizeof(pa) < 2)
802 {
Arathorn78c08372019-12-11 20:14:23 +0100803 notify_fail("Benutzung: ebene standard <Ebene>\n"+
804 (QueryProp(P_STD_CHANNEL)
805 ? "Momentan ist '"+QueryProp(P_STD_CHANNEL)+"' eingestellt.\n"
806 : "Es ist keine Standardebene eingestellt.\n"));
Arathorn85de7602019-11-28 23:04:03 +0100807 return 0;
808 }
Arathorn78c08372019-12-11 20:14:23 +0100809
810 chans = CHMASTER->find(pa[1], ME);
811 if (!chans)
Arathorn85de7602019-11-28 23:04:03 +0100812 {
Arathorn78c08372019-12-11 20:14:23 +0100813 notify_fail("Ebene '"+pa[1]+"' nicht gefunden!\n");
814 return 0;
Arathorn85de7602019-11-28 23:04:03 +0100815 }
Arathorn78c08372019-12-11 20:14:23 +0100816 else if (pointerp(chans))
817 {
818 notify_fail(
819 "Das war keine eindeutige Angabe! Folgende Ebenen passen:\n"+
820 break_string(CountUp(chans), 78));
821 return 0;
822 }
823
824 tell_object(ME,"'"+chans+"' ist jetzt die Standardebene.\n");
825 SetProp(P_STD_CHANNEL, chans);
826 return 1;
Arathorn85de7602019-11-28 23:04:03 +0100827 }
MG Mud User88f12472016-06-24 23:31:02 +0200828 return(0);
829}