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