blob: 02b0fcc6897f775c0a6fff02aca49b1640327276 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// master/misc.c -- Diverses: (T)Banish, Projektverwaltung, Levelaufstieg, ...
4//
5// $Id: misc.c 9467 2016-02-19 19:48:24Z Zesstra $
6
7#pragma strict_types,rtt_checks
8
9#include "/sys/functionlist.h"
10#include "/sys/lpctypes.h"
11#include "/sys/object_info.h"
12#include "/sys/interactive_info.h"
13
14#include "/secure/master.h"
15#include "/mail/post.h"
16#include "/sys/thing/language.h"
17#include "/sys/thing/description.h"
18
19// Fuer CIDR-Notatio im sbanish
20#include "/secure/master/cidr.c"
21
22static mixed *banished;
23static mapping tbanished, sbanished;
24static string *deputies;
25
26// TODO: muss ggf. Fakeobjekt erzeugen+uebergeben, wenn sender kein object
27protected void send_channel_msg(string channel,mixed sendername,string msg)
28{
29 object sender;
30 if (objectp(sendername))
31 sender=sendername;
32 else
33 {
34 // wenn kein Objekt uebergeben, erstmal schauen, ob ein Spielerobject
35 // existiert... Wenn ja, das nehmen.
36 sender = call_sefun("find_player", sendername)
37 || call_sefun("find_netdead", sendername);
38 if (!objectp(sender))
39 {
40 // sonst faken wir eins. *seufz*
41 sender=clone_object("/p/daemon/namefake");
42 sender->SetProp(P_NAME, sendername);
43 sender->SetProp(P_ARTICLE,0);
44 // Dieses Objekt zerstoert sich nach 3s automatisch.
45 }
46 }
47 CHMASTER->send(channel, sender, msg);
48}
49
50static string *explode_files(string file) {
51 string data;
52 mixed *exploded;
53 int i;
54
55 data=read_file(file);
56 if (!data || !stringp(data) || data == "") return ({});
57 exploded = efun::explode(data,"\n");
58 for (i=sizeof(exploded);i--;)
59 if (!stringp(exploded[i]) || exploded[i]=="" || exploded[i][0]=='#')
60 exploded[i]=0;
61 exploded-=({0});
62 printf("%-30s: %3d Objekt%s\n",file,i=sizeof(exploded),(i==1?"":"e"));
63 return exploded;
64}
65
66void UpdateTBanish();
67
68mixed QueryBanished(string str){
69 int i;
70
71 if (!str) return 0;
72 if (!pointerp(banished)) return 0;
73 for (i=sizeof(banished)-1;i>=0;i--)
74 if (sizeof(regexp(({str}),"^"+banished[i][0]+"$")))
75 {
76 if (!banished[i][1] || banished[i][1]=="")
77 return "Dieser Name ist gesperrt.";
78 else
79 return banished[i][1];
80 }
81 return 0;
82}
83
84mixed QueryTBanished(string str) {
85 int i;
86
87 if (!str || !mappingp(tbanished) || !(i=tbanished[str]))
88 return 0;
89
90 if (i == -1 || i > time())
91 return sprintf("Es gibt schon einen Spieler diesen Namens.\n"
92 +"Allerdings kann er/sie erst am %s wieder ins Mud kommen.\n",
93 (i == -1 ? "St. Nimmerleinstag" :
94 call_sefun("dtime",i)[0..16]));
95
96// Ansonsten: die Zeit ist abgelaufen, Spieler darf wieder...
97 m_delete(tbanished, str);
98 UpdateTBanish();
99 return 0;
100}
101
102void ReloadBanishFile(){
103 int i, t;
104 string s1, s2, *s;
105
106 banished = efun::explode( read_file("/secure/BANISH") || "", "\n" );
107 banished = banished - ({""});
108 for ( i = sizeof(banished); i--; ){
109 s = efun::explode( banished[i], " " );
110 s1 = s[0];
111 s2 = implode( s[1..], " " );
112 banished[i] = ({ s1, s2 });
113 }
114
115 if ( !mappingp(tbanished) ){
116 tbanished = ([]);
117
118 s = efun::explode( read_file("/secure/TBANISH") || "", "\n" );
119 s -= ({""});
120
121 for ( i = sizeof(s); i--; ){
122 sscanf( s[i], "%s:%d", s1, t );
123 tbanished += ([ s1 : t ]);
124 }
125 }
126
127 if ( !mappingp(sbanished) ){
128 sbanished = m_allocate(3, 2);
129
130 s = efun::explode( read_file("/secure/SITEBANISH") || "", "\n" );
131 s -= ({""});
132
133 for ( i = sizeof(s); i--; ) {
134 int int_ip;
135 sscanf( s[i], "%s:%d:%s", s1, t, s2 );
136 int_ip = IPv4_addr2int(s1);
137 m_add(sbanished, int_ip, t, s2);
138 }
139 }
140}
141
142int IsDeputy(mixed name)
143{
144 if ( IS_ARCH(name) )
145 return 1;
146
147 if ( objectp(name) )
148 name = getuid(name);
149
150 if ( member( deputies || ({}), name ) >= 0 )
151 return 1;
152
153 return 0;
154}
155
156
157varargs void BanishName( string name, string reason, int force )
158{
159 string *names;
160 int i;
161
162 if ( PO != TO && call_sefun("secure_level") < LORD_LVL
163 && !IsDeputy( call_sefun("secure_euid") ) )
164 return;
165
166 if ( !stringp(name) || !sizeof(name) )
167 return;
168
169 name = lower_case(name);
170
171 if ( !reason || !stringp(reason) )
172 reason = "";
173
174 if ( QueryBanished(name) && lower_case(reason) != "loeschen" ){
175 write("Der Name ist schon gebannt.\n");
176 return;
177 }
178
179 if ( !force && file_size(SAVEPATH+name[0..0]+"/"+name+".o") > 0 ){
180 write("Es existiert bereits ein Spieler dieses Namens.\n");
181 return;
182 }
183
184/* if (!("/secure/login"->valid_name(name))) return;*/
185 if ( lower_case(reason) != "loeschen" ){
186 names = ({ name + " " + reason });
187
188 for ( i = sizeof(banished); i--; )
189 names += ({ banished[i][0] + " " + banished[i][1] });
190 }
191 else{
192 names = ({});
193
194 for ( i = sizeof(banished); i--; )
195 if ( banished[i][0] != name )
196 names += ({ banished[i][0] + " " + banished[i][1] });
197 }
198
199 names = sort_array( names, #'> );
200 rm("/secure/BANISH");
201 write_file( "/secure/BANISH", implode(names, "\n") );
202 write( "Okay, '"+capitalize(name)+"' ist jetzt "+
203 (lower_case(reason) == "loeschen" ? "nicht mehr " : "")+"gebanisht.\n" );
204 ReloadBanishFile();
205}
206
207void UpdateTBanish()
208{
209 int i;
210 string *names;
211
212 for (i=sizeof(names = sort_array(m_indices(tbanished), #'</*'*/))-1;i>=0;i--)
213 names[i] = sprintf("%s:%d", names[i], tbanished[names[i]]);
214
215 rm("/secure/TBANISH");
216 write_file("/secure/TBANISH", implode(names, "\n"));
217}
218
219void UpdateSBanish()
220{
221 int i;
222 mapping lines = m_allocate(sizeof(sbanished),0);
223
224 foreach(int ip, int tstamp, string user : sbanished) {
225 m_add(lines, sprintf("%s:%d:%s",
226 IPv4_int2addr(ip), tstamp, user));
227 }
228
229 write_file( "/secure/SITEBANISH",
230 implode( sort_array (m_indices(lines), #'<), "\n" ), 1);
231}
232
233int TBanishName(string name, int days)
234{
235 int t;
236
237 if ( (getuid(TI) != name) &&
238 !IsDeputy( call_sefun("secure_euid") ) )
239 return 0;
240
241 if (days && QueryTBanished(name)){
242 write("Der Name ist schon gebannt.\n");
243 return 0;
244 }
245
246 if (file_size(SAVEPATH+name[0..0]+"/"+name+".o")<=0){
247 write("Es existiert kein Spieler dieses Namens!\n");
248 return 0;
249 }
250
251 if (!days && member(tbanished, name))
252 m_delete(tbanished, name);
253 else {
254 if (!mappingp(tbanished))
255 tbanished = ([]);
256 if (days <= -1)
257 t = -1;
258 else
259 t = (time()/86400)*86400 + days*86400;
260 tbanished += ([ name : t ]);
261 }
262
263 UpdateTBanish();
264 return 1;
265}
266
267
268mixed QuerySBanished( string ip )
269{
270 int save_me, site;
271 string banished_meldung =
272 "\nSorry, von Deiner Adresse kamen ein paar Idioten, die "
273 "ausschliesslich\nAerger machen wollten. Deshalb haben wir "
274 "die Moeglichkeit,\neinfach neue Charaktere "
275 "anzulegen, kurzzeitig fuer diese Adresse gesperrt.\n\n"
276 "Falls Du bei uns spielen moechtest, schicke bitte eine Email "
277 "an\n\n mud@mg.mud.de\n\n"
278 "mit der Bitte, einen Charakter fuer Dich anzulegen.\n" ;
279
280 if ( !ip || !stringp(ip) || !mappingp(sbanished) || !sizeof(sbanished) )
281 return 0;
282
283 foreach(site, int tstamp: sbanished) {
284 if ( tstamp > 0 && tstamp < time() ) {
285 m_delete( sbanished, site );
286 save_me=1;
287 }
288 }
289 if (save_me)
290 UpdateSBanish();
291
292 if ( !sizeof(sbanished) )
293 return 0;
294
295 site = IPv4_addr2int(ip);
296 if (!site)
297 return 0;
298 // direkt drin?
299 if (member(sbanished, site))
300 return banished_meldung;
301 // oder Netz dieser IP gesperrt?
302 foreach(int locked_site : sbanished) {
303 if ((locked_site & site) == locked_site)
304 return banished_meldung;
305 }
306
307 return 0;
308}
309
310
311private int _sbanished_by( int key, string name )
312{
313 return sbanished[key, 1] == name;
314}
315
316
317mixed SiteBanish( string ip, int days )
318{
319 string *s, tmp, euid;
320 int t, level;
321
322 euid = call_sefun("secure_euid");
323 level = call_sefun("secure_level");
324
325 // Unter L26 gibt's gar nix.
326 if ( level <= DOMAINMEMBER_LVL )
327 return -1;
328
329 // Die Liste der gesperrten IPs anschauen darf jeder ab L26.
330 if ( !ip && !days )
331 return copy(sbanished);
332
333
334 if ( !stringp(ip) || !intp(days) )
335 return 0;
336
337 if ( days && QuerySBanished(ip) ){
338 write( "Diese Adresse ist schon gebannt.\n" );
339 return 0;
340 }
341
342 if ( !days ) {
343 int int_ip = IPv4_addr2int(ip);
344
345 if( member(sbanished, int_ip) ){
346 // Fremde Sitebanishs duerfen nur Deputies loeschen.
347 if ( sbanished[int_ip, 1] != euid && !IsDeputy(euid) )
348 return -1;
349
350 call_sefun("log_file", "ARCH/SBANISH",
351 sprintf( "%s: %s hebt die Sperrung der Adresse %s von %s "
352 + "auf.\n",
353 ctime(time()), capitalize(euid), ip,
354 capitalize(sbanished[int_ip, 1]) ) );
355
356 m_delete( sbanished, int_ip );
357 }
358 else
359 return 0;
360 }
361 else {
362 // Alles, was nicht Deputy ist, darf nur fuer einen Tag sbanishen.
363 if ( days != 1 && !IsDeputy(euid) )
364 return -1;
365
366 // Nur Deputies duerfen mehr als 10 Sperrungen vornehmen.
367 if ( sizeof(filter_indices(sbanished, #'_sbanished_by, euid)) >= 10
368 && !IsDeputy(euid) )
369 return -2;
370
371 int int_ip = IPv4_addr2int(ip);
372
373 if(!int_ip) {
374 write( "Ungueltige Adresse!\n" );
375 return 0;
376 }
377
378 // L26 duerfen exakt eine IP sperren, RMs ganze Class C-Subnetze
379 // und Deputies auch Class B-Subnetze.
380 int nsize=IPv4_net_size(ip);
381 if ( nsize > 1 && level < LORD_LVL
382 || nsize > 255 && !IsDeputy(euid) )
383 return -1;
384
385 if ( !mappingp(sbanished) )
386 sbanished = m_allocate(1, 2);
387
388 if ( days < 0 )
389 t = -1;
390 else
391 t = (time() / 86400) * 86400 + days * 86400;
392
393 m_add(sbanished, int_ip, t, euid);
394
395 call_sefun("log_file", "ARCH/SBANISH",
396 sprintf( "%s: %s sperrt die Adresse %s %s.\n",
397 ctime(time()), capitalize(euid),
398 ip,
399 days > 0 ? (days > 1 ? "fuer " + days + " Tage"
400 : "fuer einen Tag")
401 : "bis zum St. Nimmerleinstag" ) );
402 }
403
404 UpdateSBanish();
405 return 1;
406}
407
408static void CheckDeputyRights()
409{
410 object ob;
411 mixed *ginfo;
412
413 // Lese- und Schreibberechtigungen fuer die Rubrik 'polizei' setzen
414 call_other( "/secure/news", "???" );
415 ob = find_object("secure/news");
416 ginfo = ((mixed)ob->GetGroup("polizei"))[5..6];
417 ob->RemoveAllowed( "polizei", 0, ginfo[0], ginfo[1] );
418 ob->AddAllowed( "polizei", 0, deputies, deputies );
419 LoadDeputyFileList();
420}
421
422int ReloadDeputyFile()
423{
424 deputies = efun::explode( read_file("/secure/DEPUTIES") || "", "\n" );
425 deputies -= ({""});
426 deputies = map( deputies, #'lower_case/*'*/ );
427 call_out( "CheckDeputyRights", 2 );
428 return(1);
429}
430
431
432static int create_home(string owner, int level)
433{
434 string def_castle;
435 string dest, castle, wizard;
436 object player;
437 string *domains;
438 int i;
439
440 player = call_sefun("find_player",owner);
441 if (!player)
442 return -5;
443 domains=get_domain_homes(owner);
444 if (!sizeof(domains) && level >= DOMAINMEMBER_LVL)
445 {
446 tell_object(player,"Du gehoerst noch keiner Region an !\n");
447 return -6;
448 }
449 tell_object(player,"Du bist Mitarbeiter der folgenden Regionen:\n");
450 for (i=0;i<sizeof(domains);i++)
451 {
452 if (i) tell_object(player,", ");
453 tell_object(player,domains[i]);
454 }
455 tell_object(player,".\n");
456 update_wiz_level(owner, level);
457 wizard = "/players/" + owner;
458 castle = "/players/" + owner + "/workroom.c";
459 if (file_size(wizard) == -1) {
460 tell_object(player, "Verzeichnis " + wizard + " angelegt\n");
461 mkdir(wizard);
462 }
463 dest = object_name(environment(player));
464 def_castle = read_file("/std/def_workroom.c");
465 if (file_size(castle) > 0) {
466 tell_object(player, "Du HATTEST ja schon ein Arbeitszimmer !\n");
467 } else {
468 if (write_file(castle, def_castle))
469 {
470 tell_object(player, "Arbeitszimmer " + castle + " erzeugt.\n");
471 // Arbeitszimmer als Home setzen
472 player->SetProp(P_START_HOME,castle[0..<3]);
473 }
474 else
475 tell_object(player, "Arbeitszimmer konnte nicht erzeugt werden !\n");
476 }
477 return 1;
478}
479
480// Sendet dem befoerderten Magier eine Hilfemail zu.
481protected void SendWizardHelpMail(string name, int level) {
482
483 object pl=call_sefun("find_player",name);
484 if (!objectp(pl)) return;
485
486 string file=sprintf("%sinfo_ml%d", WIZ_HELP_MAIL_DIR, level);
487 // wenn kein Hilfetext fuer den Level da ist: raus
488 if (file_size(file) <= 0)
489 return;
490
491 string subject = read_file(file,1,1);
492 string text = call_sefun("replace_personal",
493 read_file(file,2), ({pl}));
494
495 mixed mail = ({"Merlin", "<Master>", name, 0, 0, subject,
496 call_sefun("dtime",time()),
497 MUDNAME+time(), text });
498 MAILDEMON->DeliverMail(mail, 0);
499}
500
501int allowed_advance_wizlevel(mixed ob)
502{
503 string what;
504
505 if (objectp(ob) && geteuid(ob)==ROOTID) return 1;
506
507 if (!stringp(ob))
508 what=efun::explode(object_name(ob),"#")[0];
509 else
510 what=ob;
511
512 if (what=="/secure/merlin") return 1;
513
514 return 0;
515}
516
517int advance_wizlevel(string name, int level)
518{
519 int answer;
520 mixed *user;
521
522 if (!allowed_advance_wizlevel(PO))
523 return -1;
524
525 if (level>80) return -2;
526
527 if (!find_userinfo(name)) return -3;
528
529 user=get_full_userinfo(name);
530
531 if (user[USER_LEVEL+1]>level) return -4;
532
533 if (user[USER_LEVEL+1]==level) return 1;
534
535 if (level>=10 && level<20)
536 {
537 update_wiz_level(name, level);
538 SendWizardHelpMail(name, level);
539 return 1;
540 }
541 if (level>=20 && user[USER_LEVEL+1]<21)
542 {
543 answer = create_home(name, level);
544 if ( answer > 0 ) {
545 answer = update_wiz_level(name, level);
546 SendWizardHelpMail(name, level);
547 }
548 return answer;
549 }
550
551 update_wiz_level(name, level);
552 SendWizardHelpMail(name, level);
553
554 return 1;
555}
556
557void restart_heart_beat(object heart_beat)
558{
559 if (heart_beat) heart_beat->_restart_beat();
560}
561
562int renew_player_object(mixed who)
563{
564 object newob;
565 object *obs, *obs2;
566 mixed err;
567 string ob_name;
568 object *armours, weapon;
569 object tp;
570 int i,active,j;
571
572 if (stringp(who))
573 {
574 who=call_sefun("find_player",who);
575 if (!who)
576 {
577 who=call_sefun("find_netdead",who);
578 if (!who)
579 return -1;
580 }
581 }
582 if (!objectp(who))
583 return -2;
584 if (!object_info(who, OI_ONCE_INTERACTIVE))
585 return -3;
586 if (who->QueryGuest())
587 {
588 printf("Can't renew guests!\n");
589 return -6;
590 }
591 active=interactive(who);
592 printf("OK, renewing %O\n",who);
593 seteuid(geteuid(who));
594 err=catch(newob=clone_object(query_player_object(getuid(who))); publish);
595 seteuid(getuid(TO));
596 if (err)
597 {
598 printf("%O\n",err);
599 return -4;
600 }
601 if (!newob)
602 return -5;
603 /* Ok, the object is here now ... lets go for it ... */
604 who->save_me(0);
605 /* SSL ip weiterreichen */
606 if( call_sefun("query_ip_number", who) != efun::interactive_info(who,II_IP_NUMBER) )
607 {
608 newob->set_realip( call_sefun("query_ip_number",who) );
609 }
610 efun::configure_object(previous_object(), OC_COMMANDS_ENABLED, 0);
611 efun::set_this_player(0);
612 armours=(object *)who->QueryProp(P_ARMOURS);
613 weapon=(object)who->QueryProp(P_WEAPON);
614
615 if ( previous_object() && object_name(previous_object()) == "/secure/merlin" )
616 send_channel_msg("Debug",
617 previous_object(),
618 sprintf("RENEWING: %O %O\n",newob,who));
619 else
620 send_channel_msg("Entwicklung",
621 previous_object(),
622 sprintf("RENEWING: %O %O\n",newob,who));
623
624 ob_name=explode(object_name(newob),"#")[0];
625 if (sizeof(ob_name)>11 && ob_name[0..11]=="/std/shells/")
626 ob_name=ob_name[11..];
627 ob_name=ob_name+":"+getuid((object)who);
628 if (active)
629 exec(newob,who);
630 if (active && (interactive(who)||!interactive(newob)))
631 {
632 send_channel_msg("Debug",previous_object(),
633 "ERROR: still active !\n");
634 newob->remove();
635 return 0;
636 }
637// newob->start_player(capitalize(getuid(who)),who->_query_my_ip());
638 funcall(
639 bind_lambda(
640 unbound_lambda( ({'x, 'y}),
641 ({#'call_other/*'*/,
642 newob,
643 "start_player",
644 'x, 'y
645 })
646 ), who
647 ),
648 capitalize(getuid(who)), who->_query_my_ip() );
649
650 newob->move(environment(who),M_TPORT|M_NOCHECK|M_NO_SHOW|M_SILENT
651 |M_NO_ATTACK);
652 obs=all_inventory(who);
653 foreach(object tob: all_inventory(who)) {
654 if (!tob->QueryProp(P_AUTOLOADOBJ))
655 {
656 // kein Autoloader...
657 foreach(object ob: deep_inventory(tob))
658 {
659 // aber enthaltene Autoloader entsorgen...
660 if (ob->QueryProp(P_AUTOLOADOBJ))
661 {
662 catch(ob->remove();publish);
663 if (ob) destruct(ob);
664 }
665 }
666 // objekt ohne die AL bewegen.
667 catch(tob->move(newob,M_NOCHECK);publish);
668 }
669 else {
670 // Inhalt von Autoloadern retten.
671 // neue instanz des ALs im neuen Objekt.
672 object new_al_instance = present_clone(tob, newob);
673 foreach(object ob: deep_inventory(tob)) {
674 if (ob->QueryProp(P_AUTOLOADOBJ)) {
675 // autoloader in Autoloadern zerstoeren...
676 catch(ob->remove(1);publish);
677 if (ob) destruct(ob);
678 }
679 // alle nicht autoloader in die AL-Instanz im neuen Objekt oder
680 // notfalls ins Inv.
681 else {
682 if (objectp(new_al_instance))
683 catch(ob->move(new_al_instance, M_NOCHECK);publish);
684 else
685 catch(ob->move(newob, M_NOCHECK);publish);
686 }
687 }
688 // Autoloader zerstoeren. Wird nicht vom Spielerobjekt im remove()
689 // gemacht, wenn nicht NODROP.
690 catch(tob->remove(1);publish);
691 if (tob) destruct(tob);
692 }
693 }
694 who->remove();
695 if ( objectp(who) )
696 destruct(who);
697 rename_object(newob,ob_name);
698 newob->__reload_explore();
699 tp=this_player();
700 efun::set_this_player(newob);
701 if (objectp(weapon))
702 weapon->DoWield();
703 if (pointerp(armours))
704 for (i=sizeof(armours)-1;i>=0;i--)
705 if (objectp(armours[i]))
706 armours[i]->do_wear("alles");
707 efun::set_this_player(tp);
708 //Rueckgabewert noetig, weil Funktion vom Typ 'int'
709 return(1);
710}
711
712mixed __query_variable(object ob, string var)
713{
714 if (!PO || !IS_ARCH(geteuid(PO)) || !this_interactive() ||
715 !IS_ARCH(this_interactive()) || getuid(ob)==ROOTID )
716 {
717 write("Du bist kein EM oder Gott!\n");
718 return 0;
719 }
720 if (efun::object_info(ob, OI_ONCE_INTERACTIVE) && (PO!=ob) &&
721 (var=="history" || var=="hist2"))
722 send_channel_msg("Snoop", previous_object(),
723 sprintf("%s -> %s (history).",
724 capitalize(getuid(PO)),capitalize(getuid(ob))));
725
726 call_sefun("log_file", "ARCH/QV",
727 sprintf("%s: %O inquires var %s in %O\n",
728 ctime(time()),this_interactive(),var,ob) );
729 mixed res = variable_list(ob, RETURN_FUNCTION_NAME|RETURN_FUNCTION_FLAGS|
730 RETURN_FUNCTION_TYPE|RETURN_VARIABLE_VALUE);
731 int index = member(res,var);
732 if (index > -1)
733 {
734 return ({res[index],res[index+1],res[index+2],res[index+3]});
735 }
736 return 0;
737}
738
739void RestartBeats()
740{
741 int i,size,counter;
742 object ob;
743 mixed *list;
744 string file,obname,fun;
745
746 "/secure/simul_efun"->StopCallOut(0);
747 write("CALL_OUT-Restart in progress !\n");
748 filter(users(),#'tell_object,"CALL_OUT-Restart in progress !\n");
749 size=file_size("log/call_out_stop");
750 if (size<=0)
751 return;
752 file="";
753 counter=0;
754 while (counter<size)
755 {
756 file+=read_bytes("log/call_out_stop",counter,
757 (size-(counter+=40000)>0?counter:size));
758 }
759 list=explode(file,"__(CUT HERE)__\n");
760 list=list[<1..];
761 list=explode(list[0],"\n")-({""});
762 for (i=sizeof(list)-1;i>=0;i--)
763 if (sscanf(list[i],"%s \"%s\"",obname,fun)==2 && ob=find_object(obname))
764 {
765 write(sprintf("%O -> %s\n",ob,fun));
766 catch(ob->__restart(fun);publish);
767 }
768 write("CALL_OUT-Restart completed !\n");
769 filter(users(),#'tell_object,"CALL_OUT-Restart completed !\n");
770 rename("log/call_out_stop","log/call_out_stop.old");
771}
772