ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.50
Committed: Mon Aug 28 07:07:42 2006 UTC (17 years, 8 months ago) by root
Branch: MAIN
Changes since 1.49: +6 -5 lines
Log Message:
better but more wasteful serialisation support, perl can invoke events, fixes

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.50 for my $pkg (qw(cf::object cf::object::player cf::player cf::map cf::party cf::region cf::arch cf::living)) {
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.48 =item $object->attach ($attachment, key => $value...)
111 root 1.46
112     Attach a pre-registered attachment to an object.
113    
114 root 1.48 =item $player->attach ($attachment, key => $value...)
115 root 1.46
116     Attach a pre-registered attachment to a player.
117    
118 root 1.48 =item $map->attach ($attachment, key => $value...) # not yet persistent
119 root 1.46
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.48 my ($obj, $name, %arg) = @_;
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 root 1.48 $obj->{$name} = \%arg;
263 root 1.46 } else {
264     warn "object uses attachment '$name' that is not available, postponing.\n";
265     }
266    
267 root 1.50 $obj->{_attachment}{$name} = undef;
268 root 1.46
269     $res->{attachment} = $name;
270     $res
271     }
272    
273 root 1.39 sub cf::object::attach {
274 root 1.48 my ($obj, $name, %arg) = @_;
275 root 1.46
276 root 1.48 _attach_attachment $obj, $name, %arg;
277 root 1.46 }
278    
279     sub cf::player::attach {
280 root 1.48 my ($obj, $name, %arg) = @_;
281 root 1.46
282 root 1.48 _attach_attachment KLASS_PLAYER, $obj, $name, %arg;
283 root 1.46 }
284    
285     sub cf::map::attach {
286 root 1.48 my ($obj, $name, %arg) = @_;
287 root 1.46
288 root 1.48 _attach_attachment KLASS_MAP, $obj, $name, %arg;
289 root 1.39 }
290    
291     sub attach_global {
292     _attach @CB_GLOBAL, KLASS_GLOBAL, @_
293     }
294    
295 root 1.40 sub attach_to_type {
296 root 1.39 my $type = shift;
297 root 1.47 my $subtype = shift;
298 root 1.45
299 root 1.47 _attach @{$CB_TYPE[$type + $subtype * NUM_SUBTYPES]}, KLASS_OBJECT, @_
300 root 1.39 }
301    
302     sub attach_to_objects {
303 root 1.40 _attach @CB_OBJECT, KLASS_OBJECT, @_
304 root 1.39 }
305    
306     sub attach_to_players {
307 root 1.40 _attach @CB_PLAYER, KLASS_PLAYER, @_
308 root 1.39 }
309    
310     sub attach_to_maps {
311 root 1.40 _attach @CB_MAP, KLASS_MAP, @_
312 root 1.39 }
313    
314 root 1.45 sub register_attachment {
315     my $name = shift;
316    
317 root 1.47 $attachment{$name} = [[KLASS_OBJECT, @_]];
318 root 1.45 }
319    
320 root 1.39 our $override;
321 root 1.45 our @invoke_results = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
322 root 1.39
323 root 1.45 sub override {
324     $override = 1;
325     @invoke_results = ();
326 root 1.39 }
327    
328 root 1.45 sub do_invoke {
329 root 1.39 my $event = shift;
330 root 1.40 my $callbacks = shift;
331 root 1.39
332 root 1.45 @invoke_results = ();
333    
334 root 1.39 local $override;
335    
336 root 1.40 for (@$callbacks) {
337 root 1.39 eval { &{$_->[1]} };
338    
339     if ($@) {
340     warn "$@";
341     warn "... while processing $EVENT[$event][0] event, skipping processing altogether.\n";
342     override;
343     }
344    
345     return 1 if $override;
346     }
347    
348     0
349     }
350    
351     #############################################################################
352 root 1.45 # object support
353    
354     sub instantiate {
355     my ($obj, $data) = @_;
356    
357     $data = from_json $data;
358    
359     for (@$data) {
360 root 1.46 my ($name, $args) = @$_;
361 root 1.49
362     $obj->attach ($name, %{$args || {} });
363 root 1.46 }
364     }
365    
366     # basically do the same as instantiate, without calling instantiate
367     sub reattach {
368     my ($obj) = @_;
369     my $registry = $obj->registry;
370 root 1.45
371 root 1.47 @$registry = ();
372    
373 root 1.50 delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
374 root 1.49
375 root 1.50 for my $name (keys %{ $obj->{_attachment} || {} }) {
376 root 1.45 if (my $attach = $attachment{$name}) {
377 root 1.47 for (@$attach) {
378     my ($klass, @attach) = @$_;
379     _attach @$registry, $klass, @attach;
380     }
381 root 1.45 } else {
382 root 1.46 warn "object uses attachment '$name' that is not available, postponing.\n";
383 root 1.45 }
384 root 1.46 }
385     }
386 root 1.45
387 root 1.46 sub object_freezer_save {
388     my ($filename, $objs) = @_;
389    
390     $filename .= ".pst";
391    
392     if (@$objs) {
393     open my $fh, ">:raw", "$filename~";
394     chmod $fh, SAVE_MODE;
395     syswrite $fh, Storable::nfreeze { version => 1, objs => $objs };
396     close $fh;
397     rename "$filename~", $filename;
398     } else {
399     unlink $filename;
400 root 1.45 }
401     }
402    
403 root 1.46 sub object_thawer_load {
404     my ($filename) = @_;
405    
406     open my $fh, "<:raw:perlio", "$filename.pst"
407     or return;
408 root 1.45
409 root 1.46 eval { local $/; (Storable::thaw <$fh>)->{objs} }
410 root 1.45 }
411    
412     attach_to_objects
413     prio => -1000000,
414     on_clone => sub {
415     my ($src, $dst) = @_;
416    
417     @{$dst->registry} = @{$src->registry};
418    
419     %$dst = %$src;
420    
421 root 1.50 %{$dst->{_attachment}} = %{$src->{_attachment}}
422 root 1.45 if exists $src->{_attachment};
423     },
424     ;
425    
426     #############################################################################
427 root 1.39 # old plug-in events
428    
429 root 1.1 sub inject_event {
430 root 1.14 my $extension = shift;
431     my $event_code = shift;
432 root 1.1
433 root 1.14 my $cb = $hook[$event_code]{$extension}
434 root 1.5 or return;
435    
436 root 1.14 &$cb
437 root 1.5 }
438    
439     sub inject_global_event {
440 root 1.12 my $event = shift;
441 root 1.5
442 root 1.12 my $cb = $hook[$event]
443 root 1.1 or return;
444    
445 root 1.12 List::Util::max map &$_, values %$cb
446 root 1.1 }
447    
448     sub inject_command {
449     my ($name, $obj, $params) = @_;
450    
451     for my $cmd (@{ $command{$name} }) {
452     $cmd->[1]->($obj, $params);
453     }
454    
455     -1
456     }
457    
458     sub register_command {
459     my ($name, $time, $cb) = @_;
460    
461     my $caller = caller;
462 root 1.16 #warn "registering command '$name/$time' to '$caller'";
463 root 1.4
464 root 1.1 push @{ $command{$name} }, [$time, $cb, $caller];
465     $COMMAND{"$name\000"} = List::Util::max map $_->[0], @{ $command{$name} };
466     }
467    
468 root 1.16 sub register_extcmd {
469     my ($name, $cb) = @_;
470    
471     my $caller = caller;
472     #warn "registering extcmd '$name' to '$caller'";
473    
474     $extcmd{$name} = [$cb, $caller];
475     }
476    
477 root 1.6 sub register {
478     my ($base, $pkg) = @_;
479    
480 root 1.45 #TODO
481 root 1.6 }
482    
483 root 1.1 sub load_extension {
484     my ($path) = @_;
485    
486     $path =~ /([^\/\\]+)\.ext$/ or die "$path";
487 root 1.5 my $base = $1;
488 root 1.1 my $pkg = $1;
489     $pkg =~ s/[^[:word:]]/_/g;
490 root 1.41 $pkg = "ext::$pkg";
491 root 1.1
492     warn "loading '$path' into '$pkg'\n";
493    
494     open my $fh, "<:utf8", $path
495     or die "$path: $!";
496    
497     my $source =
498     "package $pkg; use strict; use utf8;\n"
499     . "#line 1 \"$path\"\n{\n"
500     . (do { local $/; <$fh> })
501     . "\n};\n1";
502    
503     eval $source
504     or die "$path: $@";
505    
506     push @exts, $pkg;
507 root 1.5 $ext_pkg{$base} = $pkg;
508 root 1.1
509 root 1.6 # no strict 'refs';
510 root 1.23 # @{"$pkg\::ISA"} = ext::;
511 root 1.1
512 root 1.6 register $base, $pkg;
513 root 1.1 }
514    
515     sub unload_extension {
516     my ($pkg) = @_;
517    
518     warn "removing extension $pkg\n";
519    
520     # remove hooks
521 root 1.45 #TODO
522     # for my $idx (0 .. $#PLUGIN_EVENT) {
523     # delete $hook[$idx]{$pkg};
524     # }
525 root 1.1
526     # remove commands
527     for my $name (keys %command) {
528     my @cb = grep $_->[2] ne $pkg, @{ $command{$name} };
529    
530     if (@cb) {
531     $command{$name} = \@cb;
532     $COMMAND{"$name\000"} = List::Util::max map $_->[0], @cb;
533     } else {
534     delete $command{$name};
535     delete $COMMAND{"$name\000"};
536     }
537     }
538    
539 root 1.15 # remove extcmds
540 root 1.16 for my $name (grep $extcmd{$_}[1] eq $pkg, keys %extcmd) {
541     delete $extcmd{$name};
542 root 1.15 }
543    
544 root 1.43 if (my $cb = $pkg->can ("unload")) {
545 elmex 1.31 eval {
546     $cb->($pkg);
547     1
548     } or warn "$pkg unloaded, but with errors: $@";
549     }
550    
551 root 1.1 Symbol::delete_package $pkg;
552     }
553    
554     sub load_extensions {
555     my $LIBDIR = maps_directory "perl";
556    
557     for my $ext (<$LIBDIR/*.ext>) {
558 root 1.3 next unless -r $ext;
559 root 1.2 eval {
560     load_extension $ext;
561     1
562     } or warn "$ext not loaded: $@";
563 root 1.1 }
564     }
565    
566 root 1.36 sub _perl_reload(&) {
567     my ($msg) = @_;
568    
569     $msg->("reloading...");
570    
571     eval {
572     # 1. cancel all watchers
573     $_->cancel for Event::all_watchers;
574    
575     # 2. unload all extensions
576     for (@exts) {
577     $msg->("unloading <$_>");
578     unload_extension $_;
579     }
580    
581     # 3. unload all modules loaded from $LIBDIR
582     while (my ($k, $v) = each %INC) {
583     next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
584    
585     $msg->("removing <$k>");
586     delete $INC{$k};
587 root 1.1
588 root 1.36 $k =~ s/\.pm$//;
589     $k =~ s/\//::/g;
590 root 1.3
591 root 1.36 if (my $cb = $k->can ("unload_module")) {
592     $cb->();
593 root 1.27 }
594    
595 root 1.36 Symbol::delete_package $k;
596     }
597 root 1.27
598 root 1.41 # 4. get rid of safe::, as good as possible
599     Symbol::delete_package "safe::$_"
600 root 1.45 for qw(cf::object cf::object::player cf::player cf::map cf::party cf::region);
601 root 1.36
602     # 5. remove register_script_function callbacks
603     # TODO
604    
605     # 6. unload cf.pm "a bit"
606     delete $INC{"cf.pm"};
607    
608 root 1.41 # don't, removes xs symbols, too,
609     # and global variables created in xs
610 root 1.36 #Symbol::delete_package __PACKAGE__;
611    
612     # 7. reload cf.pm
613     $msg->("reloading cf.pm");
614     require cf;
615     };
616     $msg->($@) if $@;
617 root 1.27
618 root 1.36 $msg->("reloaded");
619     };
620 root 1.27
621 root 1.36 sub perl_reload() {
622     _perl_reload {
623     warn $_[0];
624     print "$_[0]\n";
625     };
626     }
627 root 1.27
628 root 1.36 register_command "perl-reload", 0, sub {
629     my ($who, $arg) = @_;
630 root 1.27
631 root 1.36 if ($who->flag (FLAG_WIZ)) {
632     _perl_reload {
633     warn $_[0];
634     $who->message ($_[0]);
635 root 1.4 };
636 root 1.1 }
637     };
638    
639 root 1.8 #############################################################################
640 root 1.28 # extcmd framework, basically convert ext <msg>
641 root 1.15 # into pkg::->on_extcmd_arg1 (...) while shortcutting a few
642    
643 root 1.44 attach_to_players
644 root 1.43 on_extcmd => sub {
645     my ($pl, $buf) = @_;
646    
647     my $msg = eval { from_json $buf };
648    
649     if (ref $msg) {
650     if (my $cb = $extcmd{$msg->{msgtype}}) {
651     if (my %reply = $cb->[0]->($pl, $msg)) {
652     $pl->ext_reply ($msg->{msgid}, %reply);
653     }
654 root 1.28 }
655 root 1.43 } else {
656     warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
657 root 1.28 }
658 root 1.15
659 root 1.43 cf::override;
660     },
661     ;
662 root 1.15
663     #############################################################################
664 root 1.8 # load/save/clean perl data associated with a map
665    
666 root 1.39 *cf::mapsupport::on_clean = sub {
667 root 1.13 my ($map) = @_;
668 root 1.7
669     my $path = $map->tmpname;
670     defined $path or return;
671    
672     unlink "$path.cfperl";
673 root 1.46 unlink "$path.pst";
674 root 1.7 };
675    
676 root 1.39 *cf::mapsupport::on_swapin =
677     *cf::mapsupport::on_load = sub {
678 root 1.13 my ($map) = @_;
679 root 1.6
680     my $path = $map->tmpname;
681     $path = $map->path unless defined $path;
682    
683     open my $fh, "<:raw", "$path.cfperl"
684     or return; # no perl data
685    
686     my $data = Storable::thaw do { local $/; <$fh> };
687    
688     $data->{version} <= 1
689     or return; # too new
690    
691     $map->_set_obs ($data->{obs});
692 root 1.50 $map->invoke (EVENT_MAP_UPGRADE);
693 root 1.6 };
694    
695 root 1.39 attach_to_maps prio => -10000, package => cf::mapsupport::;
696    
697 root 1.8 #############################################################################
698     # load/save perl data associated with player->ob objects
699    
700 root 1.33 sub all_objects(@) {
701     @_, map all_objects ($_->inv), @_
702     }
703    
704 root 1.39 attach_to_players
705     on_load => sub {
706     my ($pl, $path) = @_;
707    
708     for my $o (all_objects $pl->ob) {
709     if (my $value = $o->get_ob_key_value ("_perl_data")) {
710     $o->set_ob_key_value ("_perl_data");
711 root 1.8
712 root 1.39 %$o = %{ Storable::thaw pack "H*", $value };
713     }
714 root 1.11 }
715 root 1.39 },
716     ;
717 root 1.6
718 root 1.22 #############################################################################
719     # core extensions - in perl
720    
721 root 1.23 =item cf::player::exists $login
722    
723     Returns true when the given account exists.
724    
725     =cut
726    
727     sub cf::player::exists($) {
728     cf::player::find $_[0]
729     or -f sprintf "%s/%s/%s/%s.pl", cf::localdir, cf::playerdir, ($_[0]) x 2;
730     }
731    
732 root 1.28 =item $player->reply ($npc, $msg[, $flags])
733    
734     Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
735     can be C<undef>. Does the right thing when the player is currently in a
736     dialogue with the given NPC character.
737    
738     =cut
739    
740 root 1.22 # rough implementation of a future "reply" method that works
741     # with dialog boxes.
742 root 1.23 sub cf::object::player::reply($$$;$) {
743     my ($self, $npc, $msg, $flags) = @_;
744    
745     $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
746 root 1.22
747 root 1.24 if ($self->{record_replies}) {
748     push @{ $self->{record_replies} }, [$npc, $msg, $flags];
749     } else {
750     $msg = $npc->name . " says: $msg" if $npc;
751     $self->message ($msg, $flags);
752     }
753 root 1.22 }
754    
755 root 1.28 =item $player->ext_reply ($msgid, $msgtype, %msg)
756    
757     Sends an ext reply to the player.
758    
759     =cut
760    
761     sub cf::player::ext_reply($$$%) {
762     my ($self, $id, %msg) = @_;
763    
764     $msg{msgid} = $id;
765    
766     $self->send ("ext " . to_json \%msg);
767     }
768    
769 root 1.22 #############################################################################
770 root 1.23 # map scripting support
771    
772 root 1.42 our $safe = new Safe "safe";
773 root 1.23 our $safe_hole = new Safe::Hole;
774    
775     $SIG{FPE} = 'IGNORE';
776    
777     $safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time));
778    
779 root 1.25 # here we export the classes and methods available to script code
780    
781     for (
782 root 1.27 ["cf::object" => qw(contr pay_amount pay_player)],
783 root 1.25 ["cf::object::player" => qw(player)],
784     ["cf::player" => qw(peaceful)],
785     ) {
786     no strict 'refs';
787     my ($pkg, @funs) = @$_;
788 root 1.41 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
789 root 1.25 for @funs;
790     }
791 root 1.23
792     sub safe_eval($;@) {
793     my ($code, %vars) = @_;
794    
795     my $qcode = $code;
796     $qcode =~ s/"/‟/g; # not allowed in #line filenames
797     $qcode =~ s/\n/\\n/g;
798    
799     local $_;
800 root 1.41 local @safe::cf::_safe_eval_args = values %vars;
801 root 1.23
802 root 1.42 my $eval =
803 root 1.23 "do {\n"
804     . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
805     . "#line 0 \"{$qcode}\"\n"
806     . $code
807     . "\n}"
808 root 1.25 ;
809    
810     sub_generation_inc;
811 root 1.42 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
812 root 1.25 sub_generation_inc;
813    
814 root 1.42 if ($@) {
815     warn "$@";
816     warn "while executing safe code '$code'\n";
817     warn "with arguments " . (join " ", %vars) . "\n";
818     }
819    
820 root 1.25 wantarray ? @res : $res[0]
821 root 1.23 }
822    
823     sub register_script_function {
824     my ($fun, $cb) = @_;
825    
826     no strict 'refs';
827 root 1.41 *{"safe::$fun"} = $safe_hole->wrap ($cb);
828 root 1.23 }
829    
830     #############################################################################
831 root 1.34 # the server's main()
832    
833 root 1.39 sub main {
834 root 1.34 Event::loop;
835     }
836    
837     #############################################################################
838 root 1.22 # initialisation
839    
840 root 1.6 register "<global>", __PACKAGE__;
841    
842 root 1.27 unshift @INC, $LIBDIR;
843 root 1.17
844 root 1.1 load_extensions;
845    
846 root 1.35 $TICK_WATCHER = Event->timer (
847     prio => 1,
848     at => $NEXT_TICK || 1,
849     cb => sub {
850     cf::server_tick; # one server iteration
851    
852     my $NOW = Event::time;
853     $NEXT_TICK += $TICK;
854    
855 root 1.37 # if we are delayed by four ticks, skip them all
856     $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4;
857 root 1.35
858     $TICK_WATCHER->at ($NEXT_TICK);
859     $TICK_WATCHER->start;
860     },
861     );
862    
863 root 1.47 _reload_2;
864    
865 root 1.1 1
866