ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.47
Committed: Sun Aug 27 13:02:04 2006 UTC (17 years, 9 months ago) by root
Branch: MAIN
Changes since 1.46: +23 -18 lines
Log Message:
some more fixes and refinements

File Contents

# User Rev Content
1 root 1.1 package cf;
2    
3     use Symbol;
4     use List::Util;
5 root 1.6 use Storable;
6 root 1.23 use Opcode;
7     use Safe;
8     use Safe::Hole;
9 root 1.19
10 root 1.32 use Time::HiRes;
11 root 1.18 use Event;
12 root 1.19 $Event::Eval = 1; # no idea why this is required, but it is
13 root 1.1
14     use strict;
15    
16 root 1.47 _reload_1;
17    
18 root 1.39 our %COMMAND = ();
19 root 1.1 our @EVENT;
20     our %PROP_TYPE;
21     our %PROP_IDX;
22 root 1.27 our $LIBDIR = maps_directory "perl";
23 root 1.1
24 root 1.35 our $TICK = MAX_TIME * 1e-6;
25     our $TICK_WATCHER;
26     our $NEXT_TICK;
27    
28 root 1.1 BEGIN {
29     *CORE::GLOBAL::warn = sub {
30     my $msg = join "", @_;
31     $msg .= "\n"
32     unless $msg =~ /\n$/;
33    
34     print STDERR "cfperl: $msg";
35     LOG llevError, "cfperl: $msg";
36     };
37     }
38    
39 root 1.9 my %ignore_set = (MAP_PROP_PATH => 1); # I hate the plug-in api. Deeply!
40    
41 root 1.1 # generate property mutators
42     sub prop_gen {
43     my ($prefix, $class) = @_;
44    
45     no strict 'refs';
46    
47     for my $prop (keys %PROP_TYPE) {
48     $prop =~ /^\Q$prefix\E_(.*$)/ or next;
49     my $sub = lc $1;
50    
51     my $type = $PROP_TYPE{$prop};
52     my $idx = $PROP_IDX {$prop};
53    
54     *{"$class\::get_$sub"} = *{"$class\::$sub"} = sub {
55     $_[0]->get_property ($type, $idx)
56     };
57    
58     *{"$class\::set_$sub"} = sub {
59     $_[0]->set_property ($type, $idx, $_[1]);
60 root 1.9 } unless $ignore_set{$prop};
61 root 1.1 }
62     }
63    
64     # auto-generate most of the API
65    
66     prop_gen OBJECT_PROP => "cf::object";
67     # CFAPI_OBJECT_ANIMATION?
68     prop_gen PLAYER_PROP => "cf::object::player";
69    
70     prop_gen MAP_PROP => "cf::map";
71     prop_gen ARCH_PROP => "cf::arch";
72    
73 root 1.41 @safe::cf::object::player::ISA = @cf::object::player::ISA = 'cf::object';
74 root 1.25
75 root 1.41 # we bless all objects into (empty) derived classes to force a method lookup
76 root 1.25 # within the Safe compartment.
77 root 1.45 for my $pkg (qw(cf::object cf::object::player cf::player cf::map cf::party cf::region cf::arch)) {
78 root 1.25 no strict 'refs';
79 root 1.41 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
80 root 1.25 }
81 root 1.1
82 root 1.18 $Event::DIED = sub {
83     warn "error in event callback: @_";
84     };
85    
86 root 1.5 my %ext_pkg;
87 root 1.1 my @exts;
88     my @hook;
89     my %command;
90 root 1.15 my %extcmd;
91 root 1.1
92 root 1.39 #############################################################################
93 root 1.45 # utility functions
94 root 1.44
95 root 1.45 use JSON::Syck (); # TODO# replace by JSON::PC once working
96 root 1.44
97 root 1.45 sub from_json($) {
98     $JSON::Syck::ImplicitUnicode = 1; # work around JSON::Syck bugs
99     JSON::Syck::Load $_[0]
100 root 1.44 }
101    
102 root 1.45 sub to_json($) {
103     $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
104     JSON::Syck::Dump $_[0]
105 root 1.44 }
106    
107     #############################################################################
108 root 1.39 # "new" plug-in system
109    
110 root 1.46 =item $object->attach ($attachment, ...)
111    
112     Attach a pre-registered attachment to an object.
113    
114     =item $player->attach ($attachment, ...)
115    
116     Attach a pre-registered attachment to a player.
117    
118     =item $map->attach ($attachment, ...) # not yet persistent
119    
120     Attach a pre-registered attachment to a map.
121 root 1.39
122 root 1.40 =item cf::attach_global ...
123 root 1.39
124 root 1.46 Attach handlers for global events.
125    
126     This and all following C<attach_*>-functions expect any number of the
127     following handler/hook descriptions:
128    
129     =over 4
130    
131     =item prio => $number
132    
133     Set the priority for all following handlers/hooks (unless overwritten
134     by another C<prio> setting). Lower priority handlers get executed
135     earlier. The default priority is C<0>, and many built-in handlers are
136     registered at priority C<-1000>, so lower priorities should not be used
137     unless you know what you are doing.
138    
139     =item on_I<event> => \&cb
140    
141     Call the given code reference whenever the named event happens (event is
142     something like C<instantiate>, C<apply>, C<use_skill> and so on, and which
143     handlers are recognised generally depends on the type of object these
144     handlers attach to).
145    
146     See F<include/eventinc.h> for the full list of events supported, and their
147     class.
148    
149     =item package => package::
150    
151     Look for sub functions of the name C<< on_I<event> >> in the given
152     package and register them. Only handlers for eevents supported by the
153     object/class are recognised.
154    
155     =back
156    
157 root 1.47 =item cf::attach_to_type $object_type, $subtype, ...
158 root 1.39
159 root 1.47 Attach handlers for a specific object type (e.g. TRANSPORT) and
160     subtype. If C<$subtype> is zero or undef, matches all objects of the given
161     type.
162 root 1.46
163 root 1.40 =item cf::attach_to_objects ...
164 root 1.39
165 root 1.46 Attach handlers to all objects. Do not use this except for debugging or
166     very rare events, as handlers are (obviously) called for I<all> objects in
167     the game.
168    
169 root 1.40 =item cf::attach_to_players ...
170 root 1.39
171 root 1.46 Attach handlers to all players.
172    
173 root 1.40 =item cf::attach_to_maps ...
174 root 1.39
175 root 1.46 Attach handlers to all maps.
176    
177 root 1.45 =item cf:register_attachment $name, ...
178    
179 root 1.39 =cut
180    
181 root 1.40 # the following variables are defined in .xs and must not be re-created
182 root 1.39 our @CB_GLOBAL = (); # registry for all global events
183 root 1.45 our @CB_OBJECT = (); # all objects (should not be used except in emergency)
184 root 1.40 our @CB_PLAYER = ();
185 root 1.39 our @CB_TYPE = (); # registry for type (cf-object class) based events
186 root 1.40 our @CB_MAP = ();
187 root 1.39
188 root 1.45 my %attachment;
189    
190 root 1.39 sub _attach_cb($\%$$$) {
191     my ($registry, $undo, $event, $prio, $cb) = @_;
192    
193     use sort 'stable';
194    
195     $cb = [$prio, $cb];
196    
197     @{$registry->[$event]} = sort
198     { $a->[0] cmp $b->[0] }
199     @{$registry->[$event] || []}, $cb;
200    
201     push @{$undo->{cb}}, [$event, $cb];
202     }
203    
204     # attach handles attaching event callbacks
205     # the only thing the caller has to do is pass the correct
206     # registry (== where the callback attaches to).
207 root 1.45 sub _attach(\@$@) {
208     my ($registry, $klass, @arg) = @_;
209 root 1.39
210     my $prio = 0;
211    
212     my %undo = (
213     registry => $registry,
214     cb => [],
215     );
216    
217     my %cb_id = map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == $klass, 0 .. $#EVENT;
218    
219 root 1.45 while (@arg) {
220     my $type = shift @arg;
221 root 1.39
222     if ($type eq "prio") {
223 root 1.45 $prio = shift @arg;
224 root 1.39
225     } elsif ($type eq "package") {
226 root 1.45 my $pkg = shift @arg;
227 root 1.39
228     while (my ($name, $id) = each %cb_id) {
229     if (my $cb = $pkg->can ($name)) {
230     _attach_cb $registry, %undo, $id, $prio, $cb;
231     }
232     }
233    
234     } elsif (exists $cb_id{$type}) {
235 root 1.45 _attach_cb $registry, %undo, $cb_id{$type}, $prio, shift @arg;
236 root 1.39
237     } elsif (ref $type) {
238     warn "attaching objects not supported, ignoring.\n";
239    
240     } else {
241 root 1.45 shift @arg;
242 root 1.39 warn "attach argument '$type' not supported, ignoring.\n";
243     }
244     }
245    
246     \%undo
247     }
248    
249 root 1.46 sub _attach_attachment {
250 root 1.47 my ($obj, $name, @args) = @_;
251 root 1.46
252     my $res;
253    
254     if (my $attach = $attachment{$name}) {
255     my $registry = $obj->registry;
256    
257 root 1.47 for (@$attach) {
258     my ($klass, @attach) = @$_;
259     $res = _attach @$registry, $klass, @attach;
260     }
261 root 1.46
262     if (my $cb = delete $registry->[EVENT_OBJECT_INSTANTIATE]) {
263     for (@$cb) {
264     eval { $_->[1]->($obj, @args); };
265     if ($@) {
266     warn "$@";
267     warn "... while processing '$name' instantiate with args <@args>.\n";
268     }
269     }
270     }
271     } else {
272     warn "object uses attachment '$name' that is not available, postponing.\n";
273     }
274    
275     push @{$obj->{_attachment}}, $name;
276    
277     $res->{attachment} = $name;
278     $res
279     }
280    
281 root 1.39 sub cf::object::attach {
282 root 1.46 my ($obj, $name, @args) = @_;
283    
284 root 1.47 _attach_attachment $obj, $name, @args;
285 root 1.46 }
286    
287     sub cf::player::attach {
288     my ($obj, $name, @args) = @_;
289    
290     _attach_attachment KLASS_PLAYER, $obj, $name, @args;
291     }
292    
293     sub cf::map::attach {
294     my ($obj, $name, @args) = @_;
295    
296     _attach_attachment KLASS_MAP, $obj, $name, @args;
297 root 1.39 }
298    
299     sub attach_global {
300     _attach @CB_GLOBAL, KLASS_GLOBAL, @_
301     }
302    
303 root 1.40 sub attach_to_type {
304 root 1.39 my $type = shift;
305 root 1.47 my $subtype = shift;
306 root 1.45
307 root 1.47 _attach @{$CB_TYPE[$type + $subtype * NUM_SUBTYPES]}, KLASS_OBJECT, @_
308 root 1.39 }
309    
310     sub attach_to_objects {
311 root 1.40 _attach @CB_OBJECT, KLASS_OBJECT, @_
312 root 1.39 }
313    
314     sub attach_to_players {
315 root 1.40 _attach @CB_PLAYER, KLASS_PLAYER, @_
316 root 1.39 }
317    
318     sub attach_to_maps {
319 root 1.40 _attach @CB_MAP, KLASS_MAP, @_
320 root 1.39 }
321    
322 root 1.45 sub register_attachment {
323     my $name = shift;
324    
325 root 1.47 $attachment{$name} = [[KLASS_OBJECT, @_]];
326 root 1.45 }
327    
328 root 1.39 our $override;
329 root 1.45 our @invoke_results = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
330 root 1.39
331 root 1.45 sub override {
332     $override = 1;
333     @invoke_results = ();
334 root 1.39 }
335    
336 root 1.45 sub do_invoke {
337 root 1.39 my $event = shift;
338 root 1.40 my $callbacks = shift;
339 root 1.39
340 root 1.45 @invoke_results = ();
341    
342 root 1.39 local $override;
343    
344 root 1.40 for (@$callbacks) {
345 root 1.39 eval { &{$_->[1]} };
346    
347     if ($@) {
348     warn "$@";
349     warn "... while processing $EVENT[$event][0] event, skipping processing altogether.\n";
350     override;
351     }
352    
353     return 1 if $override;
354     }
355    
356     0
357     }
358    
359     #############################################################################
360 root 1.45 # object support
361    
362     sub instantiate {
363     my ($obj, $data) = @_;
364    
365     $data = from_json $data;
366    
367     for (@$data) {
368 root 1.46 my ($name, $args) = @$_;
369     attach $obj, $name, @{$args || [] };
370     }
371     }
372    
373     # basically do the same as instantiate, without calling instantiate
374     sub reattach {
375     my ($obj) = @_;
376     my $registry = $obj->registry;
377 root 1.45
378 root 1.47 @$registry = ();
379    
380 root 1.46 for my $name (@{ $obj->{_attachment} }) {
381 root 1.45 if (my $attach = $attachment{$name}) {
382 root 1.47 for (@$attach) {
383     my ($klass, @attach) = @$_;
384     _attach @$registry, $klass, @attach;
385     }
386 root 1.45 } else {
387 root 1.46 warn "object uses attachment '$name' that is not available, postponing.\n";
388 root 1.45 }
389 root 1.46 }
390     }
391 root 1.45
392 root 1.46 sub object_freezer_save {
393     my ($filename, $objs) = @_;
394    
395     $filename .= ".pst";
396    
397     if (@$objs) {
398     open my $fh, ">:raw", "$filename~";
399     chmod $fh, SAVE_MODE;
400     syswrite $fh, Storable::nfreeze { version => 1, objs => $objs };
401     close $fh;
402     rename "$filename~", $filename;
403     } else {
404     unlink $filename;
405 root 1.45 }
406     }
407    
408 root 1.46 sub object_thawer_load {
409     my ($filename) = @_;
410    
411     open my $fh, "<:raw:perlio", "$filename.pst"
412     or return;
413 root 1.45
414 root 1.46 eval { local $/; (Storable::thaw <$fh>)->{objs} }
415 root 1.45 }
416    
417     attach_to_objects
418     prio => -1000000,
419     on_clone => sub {
420     my ($src, $dst) = @_;
421    
422     @{$dst->registry} = @{$src->registry};
423    
424     %$dst = %$src;
425    
426     $dst->{_attachment} = [@{ $src->{_attachment} }]
427     if exists $src->{_attachment};
428     },
429     ;
430    
431     #############################################################################
432 root 1.39 # old plug-in events
433    
434 root 1.1 sub inject_event {
435 root 1.14 my $extension = shift;
436     my $event_code = shift;
437 root 1.1
438 root 1.14 my $cb = $hook[$event_code]{$extension}
439 root 1.5 or return;
440    
441 root 1.14 &$cb
442 root 1.5 }
443    
444     sub inject_global_event {
445 root 1.12 my $event = shift;
446 root 1.5
447 root 1.12 my $cb = $hook[$event]
448 root 1.1 or return;
449    
450 root 1.12 List::Util::max map &$_, values %$cb
451 root 1.1 }
452    
453     sub inject_command {
454     my ($name, $obj, $params) = @_;
455    
456     for my $cmd (@{ $command{$name} }) {
457     $cmd->[1]->($obj, $params);
458     }
459    
460     -1
461     }
462    
463     sub register_command {
464     my ($name, $time, $cb) = @_;
465    
466     my $caller = caller;
467 root 1.16 #warn "registering command '$name/$time' to '$caller'";
468 root 1.4
469 root 1.1 push @{ $command{$name} }, [$time, $cb, $caller];
470     $COMMAND{"$name\000"} = List::Util::max map $_->[0], @{ $command{$name} };
471     }
472    
473 root 1.16 sub register_extcmd {
474     my ($name, $cb) = @_;
475    
476     my $caller = caller;
477     #warn "registering extcmd '$name' to '$caller'";
478    
479     $extcmd{$name} = [$cb, $caller];
480     }
481    
482 root 1.6 sub register {
483     my ($base, $pkg) = @_;
484    
485 root 1.45 #TODO
486 root 1.6 }
487    
488 root 1.1 sub load_extension {
489     my ($path) = @_;
490    
491     $path =~ /([^\/\\]+)\.ext$/ or die "$path";
492 root 1.5 my $base = $1;
493 root 1.1 my $pkg = $1;
494     $pkg =~ s/[^[:word:]]/_/g;
495 root 1.41 $pkg = "ext::$pkg";
496 root 1.1
497     warn "loading '$path' into '$pkg'\n";
498    
499     open my $fh, "<:utf8", $path
500     or die "$path: $!";
501    
502     my $source =
503     "package $pkg; use strict; use utf8;\n"
504     . "#line 1 \"$path\"\n{\n"
505     . (do { local $/; <$fh> })
506     . "\n};\n1";
507    
508     eval $source
509     or die "$path: $@";
510    
511     push @exts, $pkg;
512 root 1.5 $ext_pkg{$base} = $pkg;
513 root 1.1
514 root 1.6 # no strict 'refs';
515 root 1.23 # @{"$pkg\::ISA"} = ext::;
516 root 1.1
517 root 1.6 register $base, $pkg;
518 root 1.1 }
519    
520     sub unload_extension {
521     my ($pkg) = @_;
522    
523     warn "removing extension $pkg\n";
524    
525     # remove hooks
526 root 1.45 #TODO
527     # for my $idx (0 .. $#PLUGIN_EVENT) {
528     # delete $hook[$idx]{$pkg};
529     # }
530 root 1.1
531     # remove commands
532     for my $name (keys %command) {
533     my @cb = grep $_->[2] ne $pkg, @{ $command{$name} };
534    
535     if (@cb) {
536     $command{$name} = \@cb;
537     $COMMAND{"$name\000"} = List::Util::max map $_->[0], @cb;
538     } else {
539     delete $command{$name};
540     delete $COMMAND{"$name\000"};
541     }
542     }
543    
544 root 1.15 # remove extcmds
545 root 1.16 for my $name (grep $extcmd{$_}[1] eq $pkg, keys %extcmd) {
546     delete $extcmd{$name};
547 root 1.15 }
548    
549 root 1.43 if (my $cb = $pkg->can ("unload")) {
550 elmex 1.31 eval {
551     $cb->($pkg);
552     1
553     } or warn "$pkg unloaded, but with errors: $@";
554     }
555    
556 root 1.1 Symbol::delete_package $pkg;
557     }
558    
559     sub load_extensions {
560     my $LIBDIR = maps_directory "perl";
561    
562     for my $ext (<$LIBDIR/*.ext>) {
563 root 1.3 next unless -r $ext;
564 root 1.2 eval {
565     load_extension $ext;
566     1
567     } or warn "$ext not loaded: $@";
568 root 1.1 }
569     }
570    
571 root 1.36 sub _perl_reload(&) {
572     my ($msg) = @_;
573    
574     $msg->("reloading...");
575    
576     eval {
577     # 1. cancel all watchers
578     $_->cancel for Event::all_watchers;
579    
580     # 2. unload all extensions
581     for (@exts) {
582     $msg->("unloading <$_>");
583     unload_extension $_;
584     }
585    
586     # 3. unload all modules loaded from $LIBDIR
587     while (my ($k, $v) = each %INC) {
588     next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
589    
590     $msg->("removing <$k>");
591     delete $INC{$k};
592 root 1.1
593 root 1.36 $k =~ s/\.pm$//;
594     $k =~ s/\//::/g;
595 root 1.3
596 root 1.36 if (my $cb = $k->can ("unload_module")) {
597     $cb->();
598 root 1.27 }
599    
600 root 1.36 Symbol::delete_package $k;
601     }
602 root 1.27
603 root 1.41 # 4. get rid of safe::, as good as possible
604     Symbol::delete_package "safe::$_"
605 root 1.45 for qw(cf::object cf::object::player cf::player cf::map cf::party cf::region);
606 root 1.36
607     # 5. remove register_script_function callbacks
608     # TODO
609    
610     # 6. unload cf.pm "a bit"
611     delete $INC{"cf.pm"};
612    
613 root 1.41 # don't, removes xs symbols, too,
614     # and global variables created in xs
615 root 1.36 #Symbol::delete_package __PACKAGE__;
616    
617     # 7. reload cf.pm
618     $msg->("reloading cf.pm");
619     require cf;
620     };
621     $msg->($@) if $@;
622 root 1.27
623 root 1.36 $msg->("reloaded");
624     };
625 root 1.27
626 root 1.36 sub perl_reload() {
627     _perl_reload {
628     warn $_[0];
629     print "$_[0]\n";
630     };
631     }
632 root 1.27
633 root 1.36 register_command "perl-reload", 0, sub {
634     my ($who, $arg) = @_;
635 root 1.27
636 root 1.36 if ($who->flag (FLAG_WIZ)) {
637     _perl_reload {
638     warn $_[0];
639     $who->message ($_[0]);
640 root 1.4 };
641 root 1.1 }
642     };
643    
644 root 1.8 #############################################################################
645 root 1.28 # extcmd framework, basically convert ext <msg>
646 root 1.15 # into pkg::->on_extcmd_arg1 (...) while shortcutting a few
647    
648 root 1.44 attach_to_players
649 root 1.43 on_extcmd => sub {
650     my ($pl, $buf) = @_;
651    
652     my $msg = eval { from_json $buf };
653    
654     if (ref $msg) {
655     if (my $cb = $extcmd{$msg->{msgtype}}) {
656     if (my %reply = $cb->[0]->($pl, $msg)) {
657     $pl->ext_reply ($msg->{msgid}, %reply);
658     }
659 root 1.28 }
660 root 1.43 } else {
661     warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
662 root 1.28 }
663 root 1.15
664 root 1.43 cf::override;
665     },
666     ;
667 root 1.15
668     #############################################################################
669 root 1.8 # load/save/clean perl data associated with a map
670    
671 root 1.39 *cf::mapsupport::on_clean = sub {
672 root 1.13 my ($map) = @_;
673 root 1.7
674     my $path = $map->tmpname;
675     defined $path or return;
676    
677     unlink "$path.cfperl";
678 root 1.46 unlink "$path.pst";
679 root 1.7 };
680    
681 root 1.39 *cf::mapsupport::on_swapin =
682     *cf::mapsupport::on_load = sub {
683 root 1.13 my ($map) = @_;
684 root 1.6
685     my $path = $map->tmpname;
686     $path = $map->path unless defined $path;
687    
688     open my $fh, "<:raw", "$path.cfperl"
689     or return; # no perl data
690    
691     my $data = Storable::thaw do { local $/; <$fh> };
692    
693     $data->{version} <= 1
694     or return; # too new
695    
696     $map->_set_obs ($data->{obs});
697     };
698    
699 root 1.39 attach_to_maps prio => -10000, package => cf::mapsupport::;
700    
701 root 1.8 #############################################################################
702     # load/save perl data associated with player->ob objects
703    
704 root 1.33 sub all_objects(@) {
705     @_, map all_objects ($_->inv), @_
706     }
707    
708 root 1.39 attach_to_players
709     on_load => sub {
710     my ($pl, $path) = @_;
711    
712     for my $o (all_objects $pl->ob) {
713     if (my $value = $o->get_ob_key_value ("_perl_data")) {
714     $o->set_ob_key_value ("_perl_data");
715 root 1.8
716 root 1.39 %$o = %{ Storable::thaw pack "H*", $value };
717     }
718 root 1.11 }
719 root 1.39 },
720     ;
721 root 1.6
722 root 1.22 #############################################################################
723     # core extensions - in perl
724    
725 root 1.23 =item cf::player::exists $login
726    
727     Returns true when the given account exists.
728    
729     =cut
730    
731     sub cf::player::exists($) {
732     cf::player::find $_[0]
733     or -f sprintf "%s/%s/%s/%s.pl", cf::localdir, cf::playerdir, ($_[0]) x 2;
734     }
735    
736 root 1.28 =item $player->reply ($npc, $msg[, $flags])
737    
738     Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
739     can be C<undef>. Does the right thing when the player is currently in a
740     dialogue with the given NPC character.
741    
742     =cut
743    
744 root 1.22 # rough implementation of a future "reply" method that works
745     # with dialog boxes.
746 root 1.23 sub cf::object::player::reply($$$;$) {
747     my ($self, $npc, $msg, $flags) = @_;
748    
749     $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
750 root 1.22
751 root 1.24 if ($self->{record_replies}) {
752     push @{ $self->{record_replies} }, [$npc, $msg, $flags];
753     } else {
754     $msg = $npc->name . " says: $msg" if $npc;
755     $self->message ($msg, $flags);
756     }
757 root 1.22 }
758    
759 root 1.28 =item $player->ext_reply ($msgid, $msgtype, %msg)
760    
761     Sends an ext reply to the player.
762    
763     =cut
764    
765     sub cf::player::ext_reply($$$%) {
766     my ($self, $id, %msg) = @_;
767    
768     $msg{msgid} = $id;
769    
770     $self->send ("ext " . to_json \%msg);
771     }
772    
773 root 1.22 #############################################################################
774 root 1.23 # map scripting support
775    
776 root 1.42 our $safe = new Safe "safe";
777 root 1.23 our $safe_hole = new Safe::Hole;
778    
779     $SIG{FPE} = 'IGNORE';
780    
781     $safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time));
782    
783 root 1.25 # here we export the classes and methods available to script code
784    
785     for (
786 root 1.27 ["cf::object" => qw(contr pay_amount pay_player)],
787 root 1.25 ["cf::object::player" => qw(player)],
788     ["cf::player" => qw(peaceful)],
789     ) {
790     no strict 'refs';
791     my ($pkg, @funs) = @$_;
792 root 1.41 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
793 root 1.25 for @funs;
794     }
795 root 1.23
796     sub safe_eval($;@) {
797     my ($code, %vars) = @_;
798    
799     my $qcode = $code;
800     $qcode =~ s/"/‟/g; # not allowed in #line filenames
801     $qcode =~ s/\n/\\n/g;
802    
803     local $_;
804 root 1.41 local @safe::cf::_safe_eval_args = values %vars;
805 root 1.23
806 root 1.42 my $eval =
807 root 1.23 "do {\n"
808     . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
809     . "#line 0 \"{$qcode}\"\n"
810     . $code
811     . "\n}"
812 root 1.25 ;
813    
814     sub_generation_inc;
815 root 1.42 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
816 root 1.25 sub_generation_inc;
817    
818 root 1.42 if ($@) {
819     warn "$@";
820     warn "while executing safe code '$code'\n";
821     warn "with arguments " . (join " ", %vars) . "\n";
822     }
823    
824 root 1.25 wantarray ? @res : $res[0]
825 root 1.23 }
826    
827     sub register_script_function {
828     my ($fun, $cb) = @_;
829    
830     no strict 'refs';
831 root 1.41 *{"safe::$fun"} = $safe_hole->wrap ($cb);
832 root 1.23 }
833    
834     #############################################################################
835 root 1.34 # the server's main()
836    
837 root 1.39 sub main {
838 root 1.34 Event::loop;
839     }
840    
841     #############################################################################
842 root 1.22 # initialisation
843    
844 root 1.6 register "<global>", __PACKAGE__;
845    
846 root 1.27 unshift @INC, $LIBDIR;
847 root 1.17
848 root 1.1 load_extensions;
849    
850 root 1.35 $TICK_WATCHER = Event->timer (
851     prio => 1,
852     at => $NEXT_TICK || 1,
853     cb => sub {
854     cf::server_tick; # one server iteration
855    
856     my $NOW = Event::time;
857     $NEXT_TICK += $TICK;
858    
859 root 1.37 # if we are delayed by four ticks, skip them all
860     $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4;
861 root 1.35
862     $TICK_WATCHER->at ($NEXT_TICK);
863     $TICK_WATCHER->start;
864     },
865     );
866    
867 root 1.47 _reload_2;
868    
869 root 1.1 1
870