ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.52
Committed: Mon Aug 28 16:52:51 2006 UTC (17 years, 9 months ago) by root
Branch: MAIN
Changes since 1.51: +16 -2 lines
Log Message:
disable old-style plug-ins, implement attach-field for map headers and map attachments

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