blob: d7d902937b412d9c1c5348b24d79fd6c12b4120a [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// channeld.c
2//
3// $Id: channeld.c 9138 2015-02-03 21:46:56Z Zesstra $
4//
5
6#pragma strong_types
7#pragma no_shadow // keine Shadowing...
8#pragma no_clone
9#pragma no_inherit
10#pragma save_types
11
12#include <sys_debug.h>
13#include <lpctypes.h>
14#include <wizlevels.h>
15
16#include <properties.h>
17#include <config.h>
18#include <language.h>
19
20#define NEED_PROTOTYPES
21#include "channel.h"
22
23#define CMNAME "<MasteR>"
24#define CHANNEL_SAVE "/p/daemon/save/channeld"
25
26#define MEMORY "/secure/memory"
27
28// channels - contains the simple channel information
29// ([ channelname : ({ I_MEMBER, I_ACCESS, I_INFO, I_MASTER })...])
30private nosave mapping lowerch;
31private nosave mapping channels;
32private nosave mapping channelH;
33private nosave mapping stats;
34
35private mapping channelC;
36private mapping channelB;
37
38private int save_me_soon;
39
40// BEGIN OF THE CHANNEL MASTER ADMINISTRATIVE PART
41
42#define RECV 0
43#define SEND 1
44#define FLAG 2
45
46// Channel flags
47// Levelbeschraenkungen gegen Magierlevel (query_wiz_level) pruefen, nicht
48// P_LEVEL.
49#define F_WIZARD 1
50// Keine Gaeste. ;-)
51#define F_NOGUEST 2
52
53private nosave mapping admin = m_allocate(0, 3);
54
55int check(string ch, object pl, string cmd)
56{
57 int level;
58
59 if((admin[ch, FLAG] & F_NOGUEST) && pl->QueryGuest()) return 0;
60
61 if((admin[ch, FLAG] & F_WIZARD) && query_wiz_level(pl) < SEER_LVL) return 0;
62 level = (admin[ch, FLAG] & F_WIZARD
63 ? query_wiz_level(pl)
64 : pl->QueryProp(P_LEVEL));
65
66 switch(cmd)
67 {
68 case C_FIND:
69 case C_LIST:
70 case C_JOIN:
71 if(admin[ch, RECV] == -1) return 0;
72 if(admin[ch, RECV] <= level) return 1;
73 break;
74 case C_SEND:
75 if(admin[ch, SEND] == -1) return 0;
76 if(admin[ch, SEND] <= level) return 1;
77 break;
78 case C_LEAVE:
79 return 1;
80 default: break;
81 }
82 return(0);
83}
84
85private int CountUser(mapping l)
86{
Arathorn5ea15362018-11-15 22:22:05 +010087 mapping n = ([]);
88 walk_mapping(l, function void (string chan_name, mixed* chan_data) {
89 n += mkmapping(chan_data[I_MEMBER]);
90 });
MG Mud User88f12472016-06-24 23:31:02 +020091 return sizeof(n);
92}
93
94private void banned(string n, mixed cmds, string res)
95{
96 res += sprintf("%s [%s], ", capitalize(n), implode(cmds, ","));
97}
98
99private mapping Tcmd = ([]);
100#define TIMEOUT (time() - 60)
101
102void ChannelMessage(mixed msg)
103{
104 string ret, mesg;
105 mixed lag;
106 int max, rekord;
107 string tmp;
108 if(msg[1] == this_object() || !stringp(msg[2]) ||
109 msg[0] != CMNAME || previous_object() != this_object()) return;
110
111
112 mesg = lower_case(msg[2]);
113
114 if(!strstr("hilfe", mesg) && sizeof(mesg) <= 5)
115 ret = "Folgende Kommandos gibt es: hilfe, lag, up[time], statistik, bann";
116 else
117 if(!strstr("lag", mesg) && sizeof(mesg) <= 3)
118 {
119 if(Tcmd["lag"] > TIMEOUT) return;
120 Tcmd["lag"] = time();
121 lag = "/p/daemon/lag-o-daemon"->read_ext_lag_data();
122 ret = sprintf("Lag: %.1f%%/60, %.1f%%/15, %.1f%%/5, %.1f%%/1, "
123 "%.1f%%/20s, %.1f%%/2s",
124 lag[5], lag[4], lag[3], lag[2], lag[1], lag[0]);
125 call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
126 ret = query_load_average();
127 } else
128 if(!strstr("uptime", mesg) && sizeof(mesg) <= 6)
129 {
130 if(Tcmd["uptime"] > TIMEOUT) return;
131 Tcmd["uptime"] = time();
132 if(file_size("/etc/maxusers") <= 0) {
133 ret = "Diese Information liegt nicht vor.";
134 } else {
135 sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
136 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
137 ret = sprintf("Das MUD laeuft jetzt %s. Es sind momentan %d Spieler "
138 "eingeloggt; das Maximum lag heute bei %d und der Rekord "
139 "bisher ist %d.", uptime(), sizeof(users()), max, rekord);
140 }
141 } else
142 if(!strstr("statistik", mesg) && sizeof(mesg) <= 9)
143 {
144 if(Tcmd["statistik"] > TIMEOUT) return;
145 Tcmd["statistik"] = time();
146 ret = sprintf(
147 "Im Moment sind insgesamt %d Ebenen mit %d Teilnehmern aktiv.\n"
148 "Der %s wurde das letzte mal am %s von %s neu gestartet.\n"
149 "Seitdem wurden %d Ebenen neu erzeugt und %d zerstoert.\n",
150 sizeof(channels), CountUser(channels), CMNAME,
151 dtime(stats["time"]), stats["boot"], stats["new"], stats["dispose"]);
152 } else
153 if(!strstr(mesg, "bann"))
154 {
155 string pl, cmd;
156 if(mesg == "bann")
157 if(sizeof(channelB))
158 {
159 ret = "";
160 walk_mapping(channelB, #'banned/*'*/, &ret);
161 ret = "Fuer folgende Spieler besteht ein Bann: " + ret;
162 } else ret = "Zur Zeit ist kein Bann aktiv.";
163 else
164 {
165 if(sscanf(mesg, "bann %s %s", pl, cmd) == 2 &&
166 IS_DEPUTY(msg[1]))
167 {
168# define CMDS ({C_FIND, C_LIST, C_JOIN, C_LEAVE, C_SEND, C_NEW})
169 pl = lower_case(pl); cmd = lower_case(cmd);
170 if(member(CMDS, cmd) != -1)
171 {
172 if(!pointerp(channelB[pl]))
173 channelB[pl] = ({});
174 if(member(channelB[pl], cmd) != -1)
175 channelB[pl] -= ({ cmd });
176 else
177 channelB[pl] += ({ cmd });
178 ret = "Fuer '"+capitalize(pl)+"' besteht "
179 + (sizeof(channelB[pl]) ?
180 "folgender Bann: "+implode(channelB[pl], ", ") :
181 "kein Bann mehr.");
182 if(!sizeof(channelB[pl]))
183 channelB = m_copy_delete(channelB, pl);
184 save_object(CHANNEL_SAVE);
185 }
186 else ret = "Das Kommando '"+cmd+"' ist unbekannt. Erlaubte Kommandos: "
187 + implode(CMDS, ", ");
188 }
189 else
190 {
191 if(!IS_ARCH(msg[1])) return;
192 else ret = "Syntax: bann <name> <kommando>";
193 }
194 }
195 }
196 else if(mesg == "lust")
197 {
198 mixed t, up;
199 if(Tcmd["lag"] > TIMEOUT ||
200 Tcmd["statistik"] > TIMEOUT ||
201 Tcmd["uptime"] > TIMEOUT) return;
202 Tcmd["lag"] = time();
203 Tcmd["statistik"] = time();
204 Tcmd["uptime"] = time();
205 lag = "/p/daemon/lag-o-daemon"->read_lag_data();
206
207 sscanf(read_file("/etc/maxusers"), "%d %s", max, tmp);
208 sscanf(read_file("/etc/maxusers.ever"), "%d %s", rekord, tmp);
209
210 t=time()-last_reboot_time();
211 up="";
212 if(t >= 86400)
213 up += sprintf("%dT", t/86400);
214 if(t >= 3600)
215 up += sprintf("%dh", (t=t%86400)/3600);
216 if(t > 60)
217 up += sprintf("%dm", (t=t%3600)/60);
218 up += sprintf("%ds", t%60);
219
220 ret = sprintf("%.1f%%/15 %.1f%%/1 %s %d:%d:%d E:%d T:%d",
221 lag[1], lag[2], up, sizeof(users()), max, rekord,
222 sizeof(channels), CountUser(channels));
223 } else return;
224
225 call_out(#'send/*'*/, 2, CMNAME, this_object(), ret);
226}
227
228// setup() -- set up a channel and register it
229// arguments are stored in the following order:
230// ({ channel name,
231// receive level, send level,
232// flags,
233// description,
234// master obj
235// })
236private void setup(mixed c)
237{
238 closure cl;
239 object m;
240 string d;
241 d = "- Keine Beschreibung -";
242 m = this_object();
243 if(sizeof(c) && sizeof(c[0]) > 1 && c[0][0] == '\\')
244 c[0] = c[0][1..];
245
246 switch(sizeof(c))
247 {
248 case 6:
249 if(!stringp(c[5]) || !sizeof(c[5])
250 || (catch(m=load_object(c[5]);publish) || !objectp(m) ))
251 m = this_object();
252 case 5: d = stringp(c[4]) || closurep(c[4]) ? c[4] : d;
253 case 4: admin[c[0], FLAG] = to_int(c[3]);
254 case 3: admin[c[0], SEND] = to_int(c[2]);
255 case 2: admin[c[0], RECV] = to_int(c[1]);
256 break;
257 case 0:
258 default:
259 return;
260 }
261 switch(new(c[0], m, d))
262 {
263 case E_ACCESS_DENIED:
264 log_file("CHANNEL", sprintf("[%s] %s: %O: error, access denied\n",
265 dtime(time()), c[0], m));
266 break;
267 default:
268 break;
269 }
270 return;
271}
272
273void initialize()
274{
275 mixed tmp;
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200276#if !defined(__TESTMUD__) && MUDNAME=="MorgenGrauen"
MG Mud User88f12472016-06-24 23:31:02 +0200277 tmp = read_file(object_name(this_object())+".init");
Zesstra@Morgengrauen3b569c82016-07-18 20:22:08 +0200278#else
279 tmp = read_file(object_name(this_object())+".init.testmud");
280#endif
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200281 if (!stringp(tmp))
282 return;
MG Mud User88f12472016-06-24 23:31:02 +0200283 tmp = regexp(old_explode(tmp, "\n"), "^[^#]");
284 tmp = map(tmp, #'regexplode/*'*/, "[^:][^:]*$|[ \\t]*:[ \\t]*");
285 tmp = map(tmp, #'regexp/*'*/, "^[^: \\t]");
286 map(tmp, #'setup/*'*/);
287}
288
289// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
290
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200291protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200292{
293 seteuid(getuid());
294 restore_object(CHANNEL_SAVE);
295 if(!channelC) channelC = ([]);
296 if(!channelB) channelB = ([]);
297 channels = ([]);
298
299 /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
300 gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
301 Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
302 das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
303 Memory abgelegt. */
304
305 // Hab ich die noetigen Rechte im Memory?
306 if (call_other(MEMORY,"HaveRights")) {
307 // Objektpointer laden
308 channelH = (mixed) call_other(MEMORY,"Load","History");
309
310 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
311 if (!mappingp(channelH)){
312 // Zeiger erzeugen
313 channelH = ([]);
314 // und in den Memory schreiben
315 call_other(MEMORY,"Save","History",channelH);
316 }
317 } else {
318 // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
319 channelH = ([]);
320 }
321
322 stats = (["time": time(),
323 "boot": capitalize(getuid(previous_object())||"<Unbekannt>")]);
324 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
325 initialize();
326 map_objects(efun::users(), "RegisterChannels");
327 this_object()->send(CMNAME, this_object(),
328 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
329 sizeof(channels),
330 CountUser(channels)));
331}
332
333// reset() and cache_to() - Cache Timeout, remove timed out cached channels
334// SEE: new, send
335private int cache_to(string key, mapping m, int t)
336{
337 if(!pointerp(m[key]) || m[key][2] + 43200 > t) return 1;
338 return(0);
339}
340
341varargs void reset(int nonstd)
342{
343 channelC = filter_indices(channelC, #'cache_to/*'*/, channelC, time());
344 if (save_me_soon)
345 {
346 save_me_soon=0;
347 save_object(CHANNEL_SAVE);
348 }
349}
350
351// name() - define the name of this object.
352string name() { return CMNAME; }
353string Name() { return CMNAME; }
354
355// access() - check access by looking for the right argument types and
356// calling access closures respectively
357// SEE: new, join, leave, send, list, users
358// Note: <pl> is usually an object, only the master supplies a string during
359// runtime error handling.
360varargs private int access(mixed ch, mixed pl, string cmd, string txt)
361{
362 mixed co, m;
363
364 if(!stringp(ch) || !sizeof(ch = lower_case(ch)) || !channels[ch])
365 return 0;
366 if(!channels[ch][I_ACCESS]||!previous_object(1)||!extern_call()||
367 previous_object(1)==this_object()||
368 (stringp(channels[ch][I_MASTER])&&
369 previous_object(1)==find_object(channels[ch][I_MASTER]))||
370 getuid(previous_object(1)) == ROOTID)
371 return 2;
372 if(!objectp(pl) ||
373 ((previous_object(1)!=pl) &&(previous_object(1)!=this_object())))
374 return 0;
375 if(pointerp(channelB[getuid(pl)]) &&
376 member(channelB[getuid(pl)], cmd) != -1)
377 return 0;
378 if(stringp(channels[ch][I_MASTER]) &&
379 (!(m = find_object(channels[ch][I_MASTER])) ||
380 (!to_object(channels[ch][I_ACCESS]) ||
381 get_type_info(channels[ch][I_ACCESS])[1])))
382 {
383 string err;
384 if(!objectp(m)) err = catch(load_object(channels[ch][I_MASTER]);publish);
385 if(!err &&
386 ((!to_object(channels[ch][I_ACCESS]) ||
387 get_type_info(channels[ch][I_ACCESS])[1]) &&
388 !closurep(channels[ch][I_ACCESS] =
389 symbol_function("check",
390 find_object(channels[ch][I_MASTER])))))
391 {
392 log_file("CHANNEL", sprintf("[%s] %O -> %O\n",
393 dtime(time()), channels[ch][I_MASTER],
394 err));
395 channels = m_copy_delete(channels, ch);
396 return 0;
397 }
398 this_object()->join(ch, find_object(channels[ch][I_MASTER]));
399 }
400 if(closurep(channels[ch][I_ACCESS]))
401 return funcall(channels[ch][I_ACCESS],
402 channels[ch][I_NAME], pl, cmd, &txt);
403}
404
405// new() - create a new channel
406// a channel with name 'ch' is created, the player is the master
407// info may contain a string which describes the channel or a closure
408// to display up-to-date information, check may contain a closure
409// called when a join/leave/send/list/users message is received
410// SEE: access
411
412#define IGNORE "^/xx"
413
414varargs int new(string ch, object pl, mixed info)
415{
416 mixed pls;
417
418 if(!objectp(pl) || !stringp(ch) || !sizeof(ch) || channels[lower_case(ch)] ||
419 (pl == this_object() && extern_call()) ||
420 sizeof(channels) >= MAX_CHANNELS ||
421 sizeof(regexp(({ object_name(pl) }), IGNORE)) ||
422 (pointerp(channelB[getuid(pl)]) &&
423 member(channelB[getuid(pl)], C_NEW) != -1))
424 return E_ACCESS_DENIED;
425
426 if(!info) {
427 if(channelC[lower_case(ch)]) {
428 ch = channelC[lower_case(ch)][0];
429 info = channelC[lower_case(ch)][1];
430 }
431 else return E_ACCESS_DENIED;
432 }
433 else channelC[lower_case(ch)] = ({ ch, info, time() });
434
435 pls = ({ pl });
436
437 channels[lower_case(ch)] = ({ pls,
438 symbol_function("check", pl) ||
439 #'check/*'*/, info,
440 (!living(pl) &&
441 !clonep(pl) &&
442 pl != this_object()
443 ? object_name(pl)
444 : pl),
445 ch,
446 });
447
448 // ChannelH fuer einen Kanal nur dann initialisieren, wenn es sie noch nich gibt.
449 if ( !pointerp(channelH[lower_case(ch)]) )
450 channelH[lower_case(ch)] = ({});
451
452 if(pl != this_object())
453 log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
454 dtime(time()), ch, pl, info));
455 if(!pl->QueryProp(P_INVIS))
456 this_object()->send(CMNAME, pl,
457 "laesst die Ebene '"+ch+"' entstehen.", MSG_EMOTE);
458 stats["new"]++;
459
460 save_me_soon=1;
461 return(0);
462}
463
464// join() - join a channel
465// this function checks whether the player 'pl' is allowed to join
466// the channel 'ch' and add if successful, one cannot join a channel
467// twice
468// SEE: leave, access
469int join(string ch, object pl)
470{
471 if(!funcall(#'access,&ch, pl, C_JOIN)) return E_ACCESS_DENIED;
472 if(member(channels[ch][I_MEMBER], pl) != -1) return E_ALREADY_JOINED;
473 channels[ch][I_MEMBER] += ({ pl });
474 return(0);
475}
476
477// leave() - leave a channel
478// the access check in this function is just there for completeness
479// one should always be allowed to leave a channel.
480// if there are no players left on the channel it will vanish, unless
481// its master is this object.
482// SEE: join, access
483int leave(string ch, object pl)
484{
485 int pos;
486 if(!funcall(#'access,&ch, pl, C_LEAVE)) return E_ACCESS_DENIED;
487 channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
488 if((pos = member(channels[ch][I_MEMBER], pl)) == -1) return E_NOT_MEMBER;
489 if(pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
490 {
491 channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
492 if(!pl->QueryProp(P_INVIS))
493 this_object()->send(ch, pl, "uebergibt die Ebene an "
494 +channels[ch][I_MASTER]->name(WEN)+".", MSG_EMOTE);
495 }
496 channels[ch][I_MEMBER][pos..pos] = ({ });
497
498
499 if(!sizeof(channels[ch][I_MEMBER]) &&
500 !stringp(channels[ch][I_MASTER]))
501 {
502 // delete the channel that has no members
503 if(!pl->QueryProp(P_INVIS))
504 this_object()->send(CMNAME, pl,
505 "verlaesst als "
506 +(pl->QueryProp(P_GENDER) == 1 ? "Letzter" :
507 "Letzte")
508 +" die Ebene '"
509 +channels[ch][I_NAME]
510 +"', worauf diese sich in einem Blitz oktarinen "
511 +"Lichts aufloest.", MSG_EMOTE);
512 channelC[lower_case(ch)] = ({ channels[ch][I_NAME],
513 channels[ch][I_INFO], time() });
514 m_delete(channels, lower_case(ch));
515
516 // Wird ein Channel entfernt, wird auch seine History geloescht
517 channelH = m_copy_delete(channelH, lower_case(ch));
518
519 stats["dispose"]++;
520 save_me_soon=1;
521 }
522 return(0);
523}
524
525// send() - send a message to all recipients of the specified channel 'ch'
526// checks if 'pl' is allowed to send a message and sends if success-
527// ful a message with type 'type'
528// 'pl' must be an object, the message is attributed to it. e.g.
529// ignore checks use it. It can be != previous_object()
530// SEE: access, ch.h
531varargs int send(string ch, object pl, string msg, int type)
532{
533 int a;
534
535 if(!(a = funcall(#'access,&ch, pl, C_SEND, &msg))) return E_ACCESS_DENIED;
536 if(a < 2 && member(channels[ch][I_MEMBER], pl) == -1) return E_NOT_MEMBER;
537 if(!msg || !stringp(msg) || !sizeof(msg)) return E_EMPTY_MESSAGE;
538 map_objects(channels[ch][I_MEMBER],
539 "ChannelMessage", ({ channels[ch][I_NAME], pl, msg, type }));
540 if(sizeof(channelH[ch]) > MAX_HIST_SIZE)
541 channelH[ch] = channelH[ch][1..];
542 channelH[ch] += ({ ({ channels[ch][I_NAME],
543 (stringp(pl)
544 ? pl
545 : (pl->QueryProp(P_INVIS)
546 ? "/("+capitalize(getuid(pl))+")$" : "")
547 + (pl->Name(WER, 2) || "<Unbekannt>")),
548 msg+" <"+strftime("%a, %H:%M:%S")+">\n",
549 type }) });
550 return(0);
551}
552
553// list() - list all channels, that are at least receivable by 'pl'
554// returns a mapping,
555// SEE: access, channels
556private void clean(string n, mixed a) { a[0] -= ({ 0 }); }
557mixed list(object pl)
558{
559 mapping chs;
560
561 chs = filter_indices(channels, #'access/*'*/, pl, C_LIST);
562 walk_mapping(chs, #'clean/*'*/);
563 if(!sizeof(chs)) return E_ACCESS_DENIED;
564 return deep_copy(chs);
565}
566
567// find() - find a channel by its name (may be partial)
568// returns an array for multiple results and 0 for no matching name
569// SEE: access
570mixed find(string ch, object pl)
571{
572 mixed chs, s;
573 if(stringp(ch)) ch = lower_case(ch);
574 if( !sizeof(regexp(({ch}),"^[<>a-z0-9#-]*$")) ) return 0; // RUM
575 if(!sizeof(chs = regexp(m_indices(channels), "^"+ch+"$")))
576 chs = regexp(m_indices(channels), "^"+ch);
577 if((s = sizeof(chs)) > 1)
578 if(sizeof(chs = filter(chs, #'access/*'*/, pl, C_FIND)) == 1)
579 return channels[chs[0]][I_NAME];
580 else return chs;
581 return ((s && funcall(#'access,chs[0], pl, C_FIND)) ? channels[chs[0]][I_NAME] : 0);
582}
583
584// history() - get the history of a channel
585// SEE: access
586mixed history(string ch, object pl)
587{
588 if(!funcall(#'access,&ch, pl, C_JOIN))
589 return E_ACCESS_DENIED;
590 return deep_copy(channelH[ch]);
591}
592
593// remove - remove a channel (wird aus der Shell aufgerufen)
594// SEE: new
595mixed remove(string ch, object pl)
596{
597 mixed members;
598
599 if(previous_object() != this_object())
600 if(!stringp(ch) ||
601 pl != this_player() || this_player() != this_interactive() ||
602 this_interactive() != previous_object() ||
603 !IS_ARCH(this_interactive()))
604 return E_ACCESS_DENIED;
605
606 if(channels[lower_case(ch)]) {
607 channels[lower_case(ch)][I_MEMBER] =
608 filter_objects(channels[lower_case(ch)][I_MEMBER],
609 "QueryProp", P_CHANNELS);
610 map(channels[lower_case(ch)][I_MEMBER],
Arathorn5ea15362018-11-15 22:22:05 +0100611 function mixed (object listener) {
Zesstrab4dd5da2019-01-06 12:36:52 +0100612 string* chans = listener->QueryProp(P_CHANNELS);
Arathorn5ea15362018-11-15 22:22:05 +0100613 chans -= ({lower_case(ch)});
614 listener->SetProp(P_CHANNELS, chans);
615 });
MG Mud User88f12472016-06-24 23:31:02 +0200616 channels = m_copy_delete(channels, lower_case(ch));
617
618 // Wird ein Channel entfernt, wird auch seine History geloescht
619 if( pointerp(channelH[lower_case(ch)] ))
620 channelH = m_copy_delete(channelH, lower_case(ch));
621
622 stats["dispose"]++;
623 }
624 if(!channelC[lower_case(ch)])
625 return E_ACCESS_DENIED;
626 channelC = m_copy_delete(channelC, lower_case(ch));
627 save_me_soon=1;
628 return(0);
629}
630
631// Wird aus der Shell aufgerufen
632mixed clear_history(string ch)
633{
634 mixed members;
635 // Sicherheitsabfragen
636 if(previous_object() != this_object())
637 if(!stringp(ch) ||
638 this_player() != this_interactive() ||
639 this_interactive() != previous_object() ||
640 !IS_ARCH(this_interactive()))
641 return E_ACCESS_DENIED;
642
643 // History des Channels loeschen
644 if( pointerp(channelH[lower_case(ch)] ))
645 channelH[lower_case(ch)]=({});
646
647 return 0;
648}