blob: 6b917f145eb15a04e07748e27a17d92e4fa30d76 [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
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200284 if (!stringp(tmp))
285 return;
MG Mud User88f12472016-06-24 23:31:02 +0200286 tmp = regexp(old_explode(tmp, "\n"), "^[^#]");
287 tmp = map(tmp, #'regexplode/*'*/, "[^:][^:]*$|[ \\t]*:[ \\t]*");
288 tmp = map(tmp, #'regexp/*'*/, "^[^: \\t]");
289 map(tmp, #'setup/*'*/);
290}
291
292// BEGIN OF THE CHANNEL MASTER IMPLEMENTATION
293
Zesstra@Morgengrauen2b229372016-07-20 23:59:54 +0200294protected void create()
MG Mud User88f12472016-06-24 23:31:02 +0200295{
296 seteuid(getuid());
297 restore_object(CHANNEL_SAVE);
298 if(!channelC) channelC = ([]);
299 if(!channelB) channelB = ([]);
300 channels = ([]);
301
302 /* Die Channel-History wird nicht nur lokal sondern auch noch im Memory
303 gespeichert, dadurch bleibt sie auch ueber ein Reload erhalten.
304 Der folgende Code versucht, den Zeiger aus dem Memory zu holen. Falls
305 das nicht moeglich ist, wird ein neuer erzeugt und gegebenenfalls im
306 Memory abgelegt. */
307
308 // Hab ich die noetigen Rechte im Memory?
309 if (call_other(MEMORY,"HaveRights")) {
310 // Objektpointer laden
311 channelH = (mixed) call_other(MEMORY,"Load","History");
312
313 // Wenns nich geklappt hat, hat der Memory noch keinen Zeiger, dann
314 if (!mappingp(channelH)){
315 // Zeiger erzeugen
316 channelH = ([]);
317 // und in den Memory schreiben
318 call_other(MEMORY,"Save","History",channelH);
319 }
320 } else {
321 // Keine Rechte im Memory, dann wird mit einem lokalen Zeiger gearbeitet.
322 channelH = ([]);
323 }
324
325 stats = (["time": time(),
326 "boot": capitalize(getuid(previous_object())||"<Unbekannt>")]);
327 new(CMNAME, this_object(), "Zentrale Informationen zu den Ebenen");
328 initialize();
329 map_objects(efun::users(), "RegisterChannels");
330 this_object()->send(CMNAME, this_object(),
331 sprintf("%d Ebenen mit %d Teilnehmern initialisiert.",
332 sizeof(channels),
333 CountUser(channels)));
334}
335
336// reset() and cache_to() - Cache Timeout, remove timed out cached channels
337// SEE: new, send
338private int cache_to(string key, mapping m, int t)
339{
340 if(!pointerp(m[key]) || m[key][2] + 43200 > t) return 1;
341 return(0);
342}
343
344varargs void reset(int nonstd)
345{
346 channelC = filter_indices(channelC, #'cache_to/*'*/, channelC, time());
347 if (save_me_soon)
348 {
349 save_me_soon=0;
350 save_object(CHANNEL_SAVE);
351 }
352}
353
354// name() - define the name of this object.
355string name() { return CMNAME; }
356string Name() { return CMNAME; }
357
358// access() - check access by looking for the right argument types and
359// calling access closures respectively
360// SEE: new, join, leave, send, list, users
361// Note: <pl> is usually an object, only the master supplies a string during
362// runtime error handling.
363varargs private int access(mixed ch, mixed pl, string cmd, string txt)
364{
365 mixed co, m;
366
367 if(!stringp(ch) || !sizeof(ch = lower_case(ch)) || !channels[ch])
368 return 0;
369 if(!channels[ch][I_ACCESS]||!previous_object(1)||!extern_call()||
370 previous_object(1)==this_object()||
371 (stringp(channels[ch][I_MASTER])&&
372 previous_object(1)==find_object(channels[ch][I_MASTER]))||
373 getuid(previous_object(1)) == ROOTID)
374 return 2;
375 if(!objectp(pl) ||
376 ((previous_object(1)!=pl) &&(previous_object(1)!=this_object())))
377 return 0;
378 if(pointerp(channelB[getuid(pl)]) &&
379 member(channelB[getuid(pl)], cmd) != -1)
380 return 0;
381 if(stringp(channels[ch][I_MASTER]) &&
382 (!(m = find_object(channels[ch][I_MASTER])) ||
383 (!to_object(channels[ch][I_ACCESS]) ||
384 get_type_info(channels[ch][I_ACCESS])[1])))
385 {
386 string err;
387 if(!objectp(m)) err = catch(load_object(channels[ch][I_MASTER]);publish);
388 if(!err &&
389 ((!to_object(channels[ch][I_ACCESS]) ||
390 get_type_info(channels[ch][I_ACCESS])[1]) &&
391 !closurep(channels[ch][I_ACCESS] =
392 symbol_function("check",
393 find_object(channels[ch][I_MASTER])))))
394 {
395 log_file("CHANNEL", sprintf("[%s] %O -> %O\n",
396 dtime(time()), channels[ch][I_MASTER],
397 err));
398 channels = m_copy_delete(channels, ch);
399 return 0;
400 }
401 this_object()->join(ch, find_object(channels[ch][I_MASTER]));
402 }
403 if(closurep(channels[ch][I_ACCESS]))
404 return funcall(channels[ch][I_ACCESS],
405 channels[ch][I_NAME], pl, cmd, &txt);
406}
407
408// new() - create a new channel
409// a channel with name 'ch' is created, the player is the master
410// info may contain a string which describes the channel or a closure
411// to display up-to-date information, check may contain a closure
412// called when a join/leave/send/list/users message is received
413// SEE: access
414
415#define IGNORE "^/xx"
416
417varargs int new(string ch, object pl, mixed info)
418{
419 mixed pls;
420
421 if(!objectp(pl) || !stringp(ch) || !sizeof(ch) || channels[lower_case(ch)] ||
422 (pl == this_object() && extern_call()) ||
423 sizeof(channels) >= MAX_CHANNELS ||
424 sizeof(regexp(({ object_name(pl) }), IGNORE)) ||
425 (pointerp(channelB[getuid(pl)]) &&
426 member(channelB[getuid(pl)], C_NEW) != -1))
427 return E_ACCESS_DENIED;
428
429 if(!info) {
430 if(channelC[lower_case(ch)]) {
431 ch = channelC[lower_case(ch)][0];
432 info = channelC[lower_case(ch)][1];
433 }
434 else return E_ACCESS_DENIED;
435 }
436 else channelC[lower_case(ch)] = ({ ch, info, time() });
437
438 pls = ({ pl });
439
440 channels[lower_case(ch)] = ({ pls,
441 symbol_function("check", pl) ||
442 #'check/*'*/, info,
443 (!living(pl) &&
444 !clonep(pl) &&
445 pl != this_object()
446 ? object_name(pl)
447 : pl),
448 ch,
449 });
450
451 // ChannelH fuer einen Kanal nur dann initialisieren, wenn es sie noch nich gibt.
452 if ( !pointerp(channelH[lower_case(ch)]) )
453 channelH[lower_case(ch)] = ({});
454
455 if(pl != this_object())
456 log_file("CHANNEL.new", sprintf("[%s] %O: %O %O\n",
457 dtime(time()), ch, pl, info));
458 if(!pl->QueryProp(P_INVIS))
459 this_object()->send(CMNAME, pl,
460 "laesst die Ebene '"+ch+"' entstehen.", MSG_EMOTE);
461 stats["new"]++;
462
463 save_me_soon=1;
464 return(0);
465}
466
467// join() - join a channel
468// this function checks whether the player 'pl' is allowed to join
469// the channel 'ch' and add if successful, one cannot join a channel
470// twice
471// SEE: leave, access
472int join(string ch, object pl)
473{
474 if(!funcall(#'access,&ch, pl, C_JOIN)) return E_ACCESS_DENIED;
475 if(member(channels[ch][I_MEMBER], pl) != -1) return E_ALREADY_JOINED;
476 channels[ch][I_MEMBER] += ({ pl });
477 return(0);
478}
479
480// leave() - leave a channel
481// the access check in this function is just there for completeness
482// one should always be allowed to leave a channel.
483// if there are no players left on the channel it will vanish, unless
484// its master is this object.
485// SEE: join, access
486int leave(string ch, object pl)
487{
488 int pos;
489 if(!funcall(#'access,&ch, pl, C_LEAVE)) return E_ACCESS_DENIED;
490 channels[ch][I_MEMBER] -= ({0}); // kaputte Objekte erstmal raus
491 if((pos = member(channels[ch][I_MEMBER], pl)) == -1) return E_NOT_MEMBER;
492 if(pl == channels[ch][I_MASTER] && sizeof(channels[ch][I_MEMBER]) > 1)
493 {
494 channels[ch][I_MASTER] = channels[ch][I_MEMBER][1];
495 if(!pl->QueryProp(P_INVIS))
496 this_object()->send(ch, pl, "uebergibt die Ebene an "
497 +channels[ch][I_MASTER]->name(WEN)+".", MSG_EMOTE);
498 }
499 channels[ch][I_MEMBER][pos..pos] = ({ });
500
501
502 if(!sizeof(channels[ch][I_MEMBER]) &&
503 !stringp(channels[ch][I_MASTER]))
504 {
505 // delete the channel that has no members
506 if(!pl->QueryProp(P_INVIS))
507 this_object()->send(CMNAME, pl,
508 "verlaesst als "
509 +(pl->QueryProp(P_GENDER) == 1 ? "Letzter" :
510 "Letzte")
511 +" die Ebene '"
512 +channels[ch][I_NAME]
513 +"', worauf diese sich in einem Blitz oktarinen "
514 +"Lichts aufloest.", MSG_EMOTE);
515 channelC[lower_case(ch)] = ({ channels[ch][I_NAME],
516 channels[ch][I_INFO], time() });
517 m_delete(channels, lower_case(ch));
518
519 // Wird ein Channel entfernt, wird auch seine History geloescht
520 channelH = m_copy_delete(channelH, lower_case(ch));
521
522 stats["dispose"]++;
523 save_me_soon=1;
524 }
525 return(0);
526}
527
528// send() - send a message to all recipients of the specified channel 'ch'
529// checks if 'pl' is allowed to send a message and sends if success-
530// ful a message with type 'type'
531// 'pl' must be an object, the message is attributed to it. e.g.
532// ignore checks use it. It can be != previous_object()
533// SEE: access, ch.h
534varargs int send(string ch, object pl, string msg, int type)
535{
536 int a;
537
538 if(!(a = funcall(#'access,&ch, pl, C_SEND, &msg))) return E_ACCESS_DENIED;
539 if(a < 2 && member(channels[ch][I_MEMBER], pl) == -1) return E_NOT_MEMBER;
540 if(!msg || !stringp(msg) || !sizeof(msg)) return E_EMPTY_MESSAGE;
541 map_objects(channels[ch][I_MEMBER],
542 "ChannelMessage", ({ channels[ch][I_NAME], pl, msg, type }));
543 if(sizeof(channelH[ch]) > MAX_HIST_SIZE)
544 channelH[ch] = channelH[ch][1..];
545 channelH[ch] += ({ ({ channels[ch][I_NAME],
546 (stringp(pl)
547 ? pl
548 : (pl->QueryProp(P_INVIS)
549 ? "/("+capitalize(getuid(pl))+")$" : "")
550 + (pl->Name(WER, 2) || "<Unbekannt>")),
551 msg+" <"+strftime("%a, %H:%M:%S")+">\n",
552 type }) });
553 return(0);
554}
555
556// list() - list all channels, that are at least receivable by 'pl'
557// returns a mapping,
558// SEE: access, channels
559private void clean(string n, mixed a) { a[0] -= ({ 0 }); }
560mixed list(object pl)
561{
562 mapping chs;
563
564 chs = filter_indices(channels, #'access/*'*/, pl, C_LIST);
565 walk_mapping(chs, #'clean/*'*/);
566 if(!sizeof(chs)) return E_ACCESS_DENIED;
567 return deep_copy(chs);
568}
569
570// find() - find a channel by its name (may be partial)
571// returns an array for multiple results and 0 for no matching name
572// SEE: access
573mixed find(string ch, object pl)
574{
575 mixed chs, s;
576 if(stringp(ch)) ch = lower_case(ch);
577 if( !sizeof(regexp(({ch}),"^[<>a-z0-9#-]*$")) ) return 0; // RUM
578 if(!sizeof(chs = regexp(m_indices(channels), "^"+ch+"$")))
579 chs = regexp(m_indices(channels), "^"+ch);
580 if((s = sizeof(chs)) > 1)
581 if(sizeof(chs = filter(chs, #'access/*'*/, pl, C_FIND)) == 1)
582 return channels[chs[0]][I_NAME];
583 else return chs;
584 return ((s && funcall(#'access,chs[0], pl, C_FIND)) ? channels[chs[0]][I_NAME] : 0);
585}
586
587// history() - get the history of a channel
588// SEE: access
589mixed history(string ch, object pl)
590{
591 if(!funcall(#'access,&ch, pl, C_JOIN))
592 return E_ACCESS_DENIED;
593 return deep_copy(channelH[ch]);
594}
595
596// remove - remove a channel (wird aus der Shell aufgerufen)
597// SEE: new
598mixed remove(string ch, object pl)
599{
600 mixed members;
601
602 if(previous_object() != this_object())
603 if(!stringp(ch) ||
604 pl != this_player() || this_player() != this_interactive() ||
605 this_interactive() != previous_object() ||
606 !IS_ARCH(this_interactive()))
607 return E_ACCESS_DENIED;
608
609 if(channels[lower_case(ch)]) {
610 channels[lower_case(ch)][I_MEMBER] =
611 filter_objects(channels[lower_case(ch)][I_MEMBER],
612 "QueryProp", P_CHANNELS);
613 map(channels[lower_case(ch)][I_MEMBER],
614 lambda(({'u/*'*/}), ({#'call_other/*'*/, 'u, /*'*/
615 "SetProp", P_CHANNELS,
616 ({#'-/*'*/,
617 ({#'call_other/*'*/, 'u, /*'*/
618 "QueryProp", P_CHANNELS}),
619 '({ lower_case(ch) })/*'*/,})
620 })));
621 channels = m_copy_delete(channels, lower_case(ch));
622
623 // Wird ein Channel entfernt, wird auch seine History geloescht
624 if( pointerp(channelH[lower_case(ch)] ))
625 channelH = m_copy_delete(channelH, lower_case(ch));
626
627 stats["dispose"]++;
628 }
629 if(!channelC[lower_case(ch)])
630 return E_ACCESS_DENIED;
631 channelC = m_copy_delete(channelC, lower_case(ch));
632 save_me_soon=1;
633 return(0);
634}
635
636// Wird aus der Shell aufgerufen
637mixed clear_history(string ch)
638{
639 mixed members;
640 // Sicherheitsabfragen
641 if(previous_object() != this_object())
642 if(!stringp(ch) ||
643 this_player() != this_interactive() ||
644 this_interactive() != previous_object() ||
645 !IS_ARCH(this_interactive()))
646 return E_ACCESS_DENIED;
647
648 // History des Channels loeschen
649 if( pointerp(channelH[lower_case(ch)] ))
650 channelH[lower_case(ch)]=({});
651
652 return 0;
653}