ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.158
Committed: Wed Jan 10 19:52:43 2007 UTC (17 years, 4 months ago) by root
Branch: MAIN
Changes since 1.157: +31 -4 lines
Log Message:
- implement cf::map::unique_maps ()
- support string overloading for cf::path objects
- minor cleanups

File Contents

# User Rev Content
1 root 1.1 package cf;
2    
3 root 1.96 use utf8;
4     use strict;
5    
6 root 1.1 use Symbol;
7     use List::Util;
8 root 1.6 use Storable;
9 root 1.23 use Opcode;
10     use Safe;
11     use Safe::Hole;
12 root 1.19
13 root 1.127 use Coro 3.3 ();
14 root 1.96 use Coro::Event;
15     use Coro::Timer;
16     use Coro::Signal;
17     use Coro::Semaphore;
18 root 1.105 use Coro::AIO;
19 root 1.96
20 root 1.154 use Data::Dumper;
21 root 1.108 use Digest::MD5;
22 root 1.105 use Fcntl;
23 root 1.145 use IO::AIO 2.32 ();
24 root 1.72 use YAML::Syck ();
25 root 1.32 use Time::HiRes;
26 root 1.96
27     use Event; $Event::Eval = 1; # no idea why this is required, but it is
28 root 1.1
29 root 1.155 sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
30    
31 root 1.72 # work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
32     $YAML::Syck::ImplicitUnicode = 1;
33    
34 root 1.139 $Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
35 root 1.1
36 root 1.85 our %COMMAND = ();
37     our %COMMAND_TIME = ();
38     our %EXTCMD = ();
39    
40 root 1.1 our @EVENT;
41 root 1.88 our $LIBDIR = datadir . "/ext";
42 root 1.1
43 root 1.35 our $TICK = MAX_TIME * 1e-6;
44     our $TICK_WATCHER;
45     our $NEXT_TICK;
46 root 1.103 our $NOW;
47 root 1.35
48 root 1.70 our %CFG;
49    
50 root 1.84 our $UPTIME; $UPTIME ||= time;
51 root 1.103 our $RUNTIME;
52    
53 root 1.143 our %PLAYER; # all users
54     our %MAP; # all maps
55 root 1.103 our $LINK_MAP; # the special {link} map
56 root 1.108 our $RANDOM_MAPS = cf::localdir . "/random";
57 root 1.143 our %EXT_CORO; # coroutines bound to extensions
58 root 1.103
59 root 1.155 our $WAIT_FOR_TICK; $WAIT_FOR_TICK ||= new Coro::Signal;
60     our $WAIT_FOR_TICK_ONE; $WAIT_FOR_TICK_ONE ||= new Coro::Signal;
61    
62 root 1.103 binmode STDOUT;
63     binmode STDERR;
64    
65     # read virtual server time, if available
66     unless ($RUNTIME || !-e cf::localdir . "/runtime") {
67     open my $fh, "<", cf::localdir . "/runtime"
68     or die "unable to read runtime file: $!";
69     $RUNTIME = <$fh> + 0.;
70     }
71    
72     mkdir cf::localdir;
73     mkdir cf::localdir . "/" . cf::playerdir;
74     mkdir cf::localdir . "/" . cf::tmpdir;
75     mkdir cf::localdir . "/" . cf::uniquedir;
76 root 1.108 mkdir $RANDOM_MAPS;
77 root 1.103
78 root 1.108 # a special map that is always available
79     our $LINK_MAP;
80 root 1.131 our $EMERGENCY_POSITION;
81 root 1.110
82 root 1.70 #############################################################################
83    
84     =head2 GLOBAL VARIABLES
85    
86     =over 4
87    
88 root 1.83 =item $cf::UPTIME
89    
90     The timestamp of the server start (so not actually an uptime).
91    
92 root 1.103 =item $cf::RUNTIME
93    
94     The time this server has run, starts at 0 and is increased by $cf::TICK on
95     every server tick.
96    
97 root 1.70 =item $cf::LIBDIR
98    
99     The perl library directory, where extensions and cf-specific modules can
100     be found. It will be added to C<@INC> automatically.
101    
102 root 1.103 =item $cf::NOW
103    
104     The time of the last (current) server tick.
105    
106 root 1.70 =item $cf::TICK
107    
108     The interval between server ticks, in seconds.
109    
110     =item %cf::CFG
111    
112     Configuration for the server, loaded from C</etc/crossfire/config>, or
113     from wherever your confdir points to.
114    
115 root 1.155 =item $cf::WAIT_FOR_TICK, $cf::WAIT_FOR_TICK_ONE
116    
117     These are Coro::Signal objects that are C<< ->broadcast >> (WAIT_FOR_TICK)
118     or C<< ->send >> (WAIT_FOR_TICK_ONE) on after normal server tick
119     processing has been done. Call C<< ->wait >> on them to maximise the
120     window of cpu time available, or simply to synchronise to the server tick.
121    
122 root 1.70 =back
123    
124     =cut
125    
126 root 1.1 BEGIN {
127     *CORE::GLOBAL::warn = sub {
128     my $msg = join "", @_;
129 root 1.103 utf8::encode $msg;
130    
131 root 1.1 $msg .= "\n"
132     unless $msg =~ /\n$/;
133    
134 root 1.146 LOG llevError, $msg;
135 root 1.1 };
136     }
137    
138 root 1.93 @safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
139     @safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
140     @safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
141     @safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
142     @safe::cf::map::ISA = @cf::map::ISA = 'cf::attachable';
143 root 1.41 @safe::cf::object::player::ISA = @cf::object::player::ISA = 'cf::object';
144 root 1.25
145 root 1.41 # we bless all objects into (empty) derived classes to force a method lookup
146 root 1.25 # within the Safe compartment.
147 root 1.86 for my $pkg (qw(
148 root 1.100 cf::global cf::attachable
149 root 1.86 cf::object cf::object::player
150 root 1.89 cf::client cf::player
151 root 1.86 cf::arch cf::living
152     cf::map cf::party cf::region
153     )) {
154 root 1.25 no strict 'refs';
155 root 1.41 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
156 root 1.25 }
157 root 1.1
158 root 1.18 $Event::DIED = sub {
159     warn "error in event callback: @_";
160     };
161    
162 root 1.5 my %ext_pkg;
163 root 1.1 my @exts;
164     my @hook;
165    
166 root 1.70 =head2 UTILITY FUNCTIONS
167    
168     =over 4
169    
170 root 1.154 =item dumpval $ref
171    
172 root 1.70 =cut
173 root 1.44
174 root 1.154 sub dumpval {
175     eval {
176     local $SIG{__DIE__};
177     my $d;
178     if (1) {
179     $d = new Data::Dumper([$_[0]], ["*var"]);
180     $d->Terse(1);
181     $d->Indent(2);
182     $d->Quotekeys(0);
183     $d->Useqq(1);
184     #$d->Bless(...);
185     $d->Seen($_[1]) if @_ > 1;
186     $d = $d->Dump();
187     }
188     $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
189     $d
190     } || "[unable to dump $_[0]: '$@']";
191     }
192    
193 root 1.45 use JSON::Syck (); # TODO# replace by JSON::PC once working
194 root 1.44
195 root 1.70 =item $ref = cf::from_json $json
196    
197     Converts a JSON string into the corresponding perl data structure.
198    
199     =cut
200    
201 root 1.45 sub from_json($) {
202     $JSON::Syck::ImplicitUnicode = 1; # work around JSON::Syck bugs
203     JSON::Syck::Load $_[0]
204 root 1.44 }
205    
206 root 1.70 =item $json = cf::to_json $ref
207    
208     Converts a perl data structure into its JSON representation.
209    
210     =cut
211    
212 root 1.45 sub to_json($) {
213     $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
214     JSON::Syck::Dump $_[0]
215 root 1.44 }
216    
217 root 1.120 =item cf::lock_wait $string
218    
219     Wait until the given lock is available. See cf::lock_acquire.
220    
221     =item my $lock = cf::lock_acquire $string
222    
223     Wait until the given lock is available and then acquires it and returns
224 root 1.135 a Coro::guard object. If the guard object gets destroyed (goes out of scope,
225 root 1.120 for example when the coroutine gets canceled), the lock is automatically
226     returned.
227    
228 root 1.133 Lock names should begin with a unique identifier (for example, cf::map::find
229     uses map_find and cf::map::load uses map_load).
230 root 1.120
231     =cut
232    
233     our %LOCK;
234    
235     sub lock_wait($) {
236     my ($key) = @_;
237    
238     # wait for lock, if any
239     while ($LOCK{$key}) {
240     push @{ $LOCK{$key} }, $Coro::current;
241     Coro::schedule;
242     }
243     }
244    
245     sub lock_acquire($) {
246     my ($key) = @_;
247    
248     # wait, to be sure we are not locked
249     lock_wait $key;
250    
251     $LOCK{$key} = [];
252    
253 root 1.135 Coro::guard {
254 root 1.120 # wake up all waiters, to be on the safe side
255     $_->ready for @{ delete $LOCK{$key} };
256     }
257     }
258    
259 root 1.133 sub freeze_mainloop {
260     return unless $TICK_WATCHER->is_active;
261    
262 root 1.135 my $guard = Coro::guard { $TICK_WATCHER->start };
263 root 1.133 $TICK_WATCHER->stop;
264     $guard
265     }
266    
267 root 1.140 =item cf::async { BLOCK }
268    
269     Currently the same as Coro::async_pool, meaning you cannot use
270     C<on_destroy>, C<join> or other gimmicks on these coroutines. The only
271     thing you are allowed to do is call C<prio> on it.
272    
273     =cut
274    
275     BEGIN { *async = \&Coro::async_pool }
276    
277 root 1.106 =item cf::sync_job { BLOCK }
278    
279     The design of crossfire+ requires that the main coro ($Coro::main) is
280     always able to handle events or runnable, as crossfire+ is only partly
281     reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
282    
283     If it must be done, put the blocking parts into C<sync_job>. This will run
284     the given BLOCK in another coroutine while waiting for the result. The
285     server will be frozen during this time, so the block should either finish
286     fast or be very important.
287    
288     =cut
289    
290 root 1.105 sub sync_job(&) {
291     my ($job) = @_;
292    
293     if ($Coro::current == $Coro::main) {
294 root 1.112 # this is the main coro, too bad, we have to block
295     # till the operation succeeds, freezing the server :/
296    
297 root 1.110 # TODO: use suspend/resume instead
298 root 1.112 # (but this is cancel-safe)
299 root 1.133 my $freeze_guard = freeze_mainloop;
300 root 1.112
301     my $busy = 1;
302     my @res;
303    
304 root 1.140 (async {
305 root 1.112 @res = eval { $job->() };
306     warn $@ if $@;
307     undef $busy;
308     })->prio (Coro::PRIO_MAX);
309    
310 root 1.105 while ($busy) {
311 root 1.141 Coro::cede or Event::one_event;
312 root 1.105 }
313 root 1.112
314     wantarray ? @res : $res[0]
315 root 1.105 } else {
316 root 1.112 # we are in another coroutine, how wonderful, everything just works
317    
318     $job->()
319 root 1.105 }
320     }
321    
322 root 1.140 =item $coro = cf::async_ext { BLOCK }
323 root 1.103
324 root 1.140 Like async, but this coro is automcatially being canceled when the
325     extension calling this is being unloaded.
326 root 1.103
327     =cut
328    
329 root 1.140 sub async_ext(&) {
330 root 1.103 my $cb = shift;
331    
332 root 1.140 my $coro = &Coro::async ($cb);
333 root 1.103
334     $coro->on_destroy (sub {
335     delete $EXT_CORO{$coro+0};
336     });
337     $EXT_CORO{$coro+0} = $coro;
338    
339     $coro
340     }
341    
342 root 1.108 sub write_runtime {
343     my $runtime = cf::localdir . "/runtime";
344    
345     my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
346     or return;
347    
348 root 1.112 my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock
349 root 1.108 (aio_write $fh, 0, (length $value), $value, 0) <= 0
350     and return;
351    
352     aio_fsync $fh
353     and return;
354    
355     close $fh
356     or return;
357    
358     aio_rename "$runtime~", $runtime
359     and return;
360    
361     1
362     }
363    
364 root 1.70 =back
365    
366 root 1.71 =cut
367    
368 root 1.44 #############################################################################
369 root 1.39
370 root 1.108 package cf::path;
371    
372 root 1.158 use overload
373     '""' => \&as_string;
374    
375 root 1.154 # used to convert map paths into valid unix filenames by repalcing / by ∕
376     our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
377    
378 root 1.108 sub new {
379     my ($class, $path, $base) = @_;
380    
381 root 1.110 $path = $path->as_string if ref $path;
382    
383 root 1.108 my $self = bless { }, $class;
384    
385 root 1.114 # {... are special paths that are not touched
386     # ?xxx/... are special absolute paths
387     # ?random/... random maps
388     # /! non-realised random map exit
389     # /... normal maps
390     # ~/... per-player maps without a specific player (DO NOT USE)
391     # ~user/... per-player map of a specific user
392    
393 root 1.158 $path =~ s/$PATH_SEP/\//go;
394    
395 root 1.114 if ($path =~ /^{/) {
396     # fine as it is
397     } elsif ($path =~ s{^\?random/}{}) {
398 root 1.109 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
399     $self->{random} = cf::from_json $data;
400 root 1.108 } else {
401     if ($path =~ s{^~([^/]+)?}{}) {
402     $self->{user_rel} = 1;
403    
404     if (defined $1) {
405     $self->{user} = $1;
406     } elsif ($base =~ m{^~([^/]+)/}) {
407     $self->{user} = $1;
408     } else {
409     warn "cannot resolve user-relative path without user <$path,$base>\n";
410     }
411     } elsif ($path =~ /^\//) {
412     # already absolute
413     } else {
414     $base =~ s{[^/]+/?$}{};
415     return $class->new ("$base/$path");
416     }
417    
418     for ($path) {
419     redo if s{/\.?/}{/};
420     redo if s{/[^/]+/\.\./}{/};
421     }
422     }
423    
424     $self->{path} = $path;
425    
426     $self
427     }
428    
429     # the name / primary key / in-game path
430     sub as_string {
431     my ($self) = @_;
432    
433     $self->{user_rel} ? "~$self->{user}$self->{path}"
434     : $self->{random} ? "?random/$self->{path}"
435     : $self->{path}
436     }
437    
438     # the displayed name, this is a one way mapping
439     sub visible_name {
440     my ($self) = @_;
441    
442 root 1.109 # if (my $rmp = $self->{random}) {
443     # # todo: be more intelligent about this
444     # "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
445     # } else {
446     $self->as_string
447     # }
448 root 1.108 }
449    
450     # escape the /'s in the path
451     sub _escaped_path {
452 root 1.154 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
453 root 1.108 $path
454     }
455    
456     # the original (read-only) location
457     sub load_path {
458     my ($self) = @_;
459    
460     sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
461     }
462    
463     # the temporary/swap location
464     sub save_path {
465     my ($self) = @_;
466    
467     $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
468 root 1.109 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
469 root 1.108 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
470     }
471    
472     # the unique path, might be eq to save_path
473     sub uniq_path {
474     my ($self) = @_;
475    
476     $self->{user_rel} || $self->{random}
477     ? undef
478     : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
479     }
480    
481     # return random map parameters, or undef
482     sub random_map_params {
483     my ($self) = @_;
484    
485     $self->{random}
486     }
487    
488     # this is somewhat ugly, but style maps do need special treatment
489     sub is_style_map {
490     $_[0]{path} =~ m{^/styles/}
491     }
492    
493     package cf;
494    
495     #############################################################################
496    
497 root 1.93 =head2 ATTACHABLE OBJECTS
498    
499 root 1.94 Many objects in crossfire are so-called attachable objects. That means you can
500     attach callbacks/event handlers (a collection of which is called an "attachment")
501     to it. All such attachable objects support the following methods.
502    
503     In the following description, CLASS can be any of C<global>, C<object>
504     C<player>, C<client> or C<map> (i.e. the attachable objects in
505     crossfire+).
506 root 1.55
507     =over 4
508    
509 root 1.94 =item $attachable->attach ($attachment, key => $value...)
510    
511     =item $attachable->detach ($attachment)
512    
513     Attach/detach a pre-registered attachment to a specific object and give it
514     the specified key/value pairs as arguments.
515    
516     Example, attach a minesweeper attachment to the given object, making it a
517     10x10 minesweeper game:
518 root 1.46
519 root 1.94 $obj->attach (minesweeper => width => 10, height => 10);
520 root 1.53
521 root 1.93 =item $bool = $attachable->attached ($name)
522 root 1.46
523 root 1.93 Checks wether the named attachment is currently attached to the object.
524 root 1.46
525 root 1.94 =item cf::CLASS->attach ...
526 root 1.46
527 root 1.94 =item cf::CLASS->detach ...
528 root 1.92
529 root 1.94 Define an anonymous attachment and attach it to all objects of the given
530     CLASS. See the next function for an explanation of its arguments.
531 root 1.92
532 root 1.93 You can attach to global events by using the C<cf::global> class.
533 root 1.92
534 root 1.94 Example, log all player logins:
535    
536     cf::player->attach (
537     on_login => sub {
538     my ($pl) = @_;
539     ...
540     },
541     );
542    
543     Example, attach to the jeweler skill:
544    
545     cf::object->attach (
546     type => cf::SKILL,
547     subtype => cf::SK_JEWELER,
548     on_use_skill => sub {
549     my ($sk, $ob, $part, $dir, $msg) = @_;
550     ...
551     },
552     );
553    
554     =item cf::CLASS::attachment $name, ...
555    
556     Register an attachment by C<$name> through which attachable objects of the
557     given CLASS can refer to this attachment.
558    
559     Some classes such as crossfire maps and objects can specify attachments
560     that are attached at load/instantiate time, thus the need for a name.
561    
562     These calls expect any number of the following handler/hook descriptions:
563 root 1.46
564     =over 4
565    
566     =item prio => $number
567    
568     Set the priority for all following handlers/hooks (unless overwritten
569     by another C<prio> setting). Lower priority handlers get executed
570     earlier. The default priority is C<0>, and many built-in handlers are
571     registered at priority C<-1000>, so lower priorities should not be used
572     unless you know what you are doing.
573    
574 root 1.93 =item type => $type
575    
576     (Only for C<< cf::object->attach >> calls), limits the attachment to the
577     given type of objects only (the additional parameter C<subtype> can be
578     used to further limit to the given subtype).
579    
580 root 1.46 =item on_I<event> => \&cb
581    
582     Call the given code reference whenever the named event happens (event is
583     something like C<instantiate>, C<apply>, C<use_skill> and so on, and which
584     handlers are recognised generally depends on the type of object these
585     handlers attach to).
586    
587     See F<include/eventinc.h> for the full list of events supported, and their
588     class.
589    
590     =item package => package::
591    
592     Look for sub functions of the name C<< on_I<event> >> in the given
593     package and register them. Only handlers for eevents supported by the
594     object/class are recognised.
595    
596     =back
597    
598 root 1.94 Example, define an attachment called "sockpuppet" that calls the given
599     event handler when a monster attacks:
600    
601     cf::object::attachment sockpuppet =>
602     on_skill_attack => sub {
603     my ($self, $victim) = @_;
604     ...
605     }
606     }
607    
608 root 1.96 =item $attachable->valid
609    
610     Just because you have a perl object does not mean that the corresponding
611     C-level object still exists. If you try to access an object that has no
612     valid C counterpart anymore you get an exception at runtime. This method
613     can be used to test for existence of the C object part without causing an
614     exception.
615    
616 root 1.39 =cut
617    
618 root 1.40 # the following variables are defined in .xs and must not be re-created
619 root 1.100 our @CB_GLOBAL = (); # registry for all global events
620     our @CB_ATTACHABLE = (); # registry for all attachables
621     our @CB_OBJECT = (); # all objects (should not be used except in emergency)
622     our @CB_PLAYER = ();
623     our @CB_CLIENT = ();
624     our @CB_TYPE = (); # registry for type (cf-object class) based events
625     our @CB_MAP = ();
626 root 1.39
627 root 1.45 my %attachment;
628    
629 root 1.93 sub _attach_cb($$$$) {
630     my ($registry, $event, $prio, $cb) = @_;
631 root 1.39
632     use sort 'stable';
633    
634     $cb = [$prio, $cb];
635    
636     @{$registry->[$event]} = sort
637     { $a->[0] cmp $b->[0] }
638     @{$registry->[$event] || []}, $cb;
639     }
640    
641 root 1.100 # hack
642     my %attachable_klass = map +($_ => 1), KLASS_OBJECT, KLASS_CLIENT, KLASS_PLAYER, KLASS_MAP;
643    
644 root 1.39 # attach handles attaching event callbacks
645     # the only thing the caller has to do is pass the correct
646     # registry (== where the callback attaches to).
647 root 1.93 sub _attach {
648 root 1.45 my ($registry, $klass, @arg) = @_;
649 root 1.39
650 root 1.93 my $object_type;
651 root 1.39 my $prio = 0;
652     my %cb_id = map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == $klass, 0 .. $#EVENT;
653    
654 root 1.100 #TODO: get rid of this hack
655     if ($attachable_klass{$klass}) {
656     %cb_id = (%cb_id, map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == KLASS_ATTACHABLE, 0 .. $#EVENT);
657     }
658    
659 root 1.45 while (@arg) {
660     my $type = shift @arg;
661 root 1.39
662     if ($type eq "prio") {
663 root 1.45 $prio = shift @arg;
664 root 1.39
665 root 1.93 } elsif ($type eq "type") {
666     $object_type = shift @arg;
667     $registry = $CB_TYPE[$object_type] ||= [];
668    
669     } elsif ($type eq "subtype") {
670     defined $object_type or Carp::croak "subtype specified without type";
671     my $object_subtype = shift @arg;
672     $registry = $CB_TYPE[$object_type + $object_subtype * NUM_SUBTYPES] ||= [];
673    
674 root 1.39 } elsif ($type eq "package") {
675 root 1.45 my $pkg = shift @arg;
676 root 1.39
677     while (my ($name, $id) = each %cb_id) {
678     if (my $cb = $pkg->can ($name)) {
679 root 1.93 _attach_cb $registry, $id, $prio, $cb;
680 root 1.39 }
681     }
682    
683     } elsif (exists $cb_id{$type}) {
684 root 1.93 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg;
685 root 1.39
686     } elsif (ref $type) {
687     warn "attaching objects not supported, ignoring.\n";
688    
689     } else {
690 root 1.45 shift @arg;
691 root 1.39 warn "attach argument '$type' not supported, ignoring.\n";
692     }
693     }
694     }
695    
696 root 1.93 sub _object_attach {
697 root 1.48 my ($obj, $name, %arg) = @_;
698 root 1.46
699 root 1.55 return if exists $obj->{_attachment}{$name};
700    
701 root 1.46 if (my $attach = $attachment{$name}) {
702     my $registry = $obj->registry;
703    
704 root 1.47 for (@$attach) {
705     my ($klass, @attach) = @$_;
706 root 1.93 _attach $registry, $klass, @attach;
707 root 1.47 }
708 root 1.46
709 root 1.48 $obj->{$name} = \%arg;
710 root 1.46 } else {
711     warn "object uses attachment '$name' that is not available, postponing.\n";
712     }
713    
714 root 1.50 $obj->{_attachment}{$name} = undef;
715 root 1.46 }
716    
717 root 1.93 sub cf::attachable::attach {
718     if (ref $_[0]) {
719     _object_attach @_;
720     } else {
721     _attach shift->_attach_registry, @_;
722     }
723 root 1.55 };
724 root 1.46
725 root 1.54 # all those should be optimised
726 root 1.93 sub cf::attachable::detach {
727 root 1.54 my ($obj, $name) = @_;
728 root 1.46
729 root 1.93 if (ref $obj) {
730     delete $obj->{_attachment}{$name};
731     reattach ($obj);
732     } else {
733     Carp::croak "cannot, currently, detach class attachments";
734     }
735 root 1.55 };
736    
737 root 1.93 sub cf::attachable::attached {
738 root 1.55 my ($obj, $name) = @_;
739    
740     exists $obj->{_attachment}{$name}
741 root 1.39 }
742    
743 root 1.100 for my $klass (qw(ATTACHABLE GLOBAL OBJECT PLAYER CLIENT MAP)) {
744 root 1.93 eval "#line " . __LINE__ . " 'cf.pm'
745     sub cf::\L$klass\E::_attach_registry {
746     (\\\@CB_$klass, KLASS_$klass)
747     }
748 root 1.45
749 root 1.93 sub cf::\L$klass\E::attachment {
750     my \$name = shift;
751 root 1.39
752 root 1.93 \$attachment{\$name} = [[KLASS_$klass, \@_]];
753     }
754     ";
755     die if $@;
756 root 1.52 }
757    
758 root 1.39 our $override;
759 root 1.45 our @invoke_results = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
760 root 1.39
761 root 1.45 sub override {
762     $override = 1;
763     @invoke_results = ();
764 root 1.39 }
765    
766 root 1.45 sub do_invoke {
767 root 1.39 my $event = shift;
768 root 1.40 my $callbacks = shift;
769 root 1.39
770 root 1.45 @invoke_results = ();
771    
772 root 1.39 local $override;
773    
774 root 1.40 for (@$callbacks) {
775 root 1.39 eval { &{$_->[1]} };
776    
777     if ($@) {
778     warn "$@";
779 root 1.64 warn "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n";
780 root 1.39 override;
781     }
782    
783     return 1 if $override;
784     }
785    
786     0
787     }
788    
789 root 1.96 =item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...)
790 root 1.55
791 root 1.96 =item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
792 root 1.55
793 root 1.96 Generate an object-specific event with the given arguments.
794 root 1.55
795 root 1.96 This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be
796 root 1.55 removed in future versions), and there is no public API to access override
797     results (if you must, access C<@cf::invoke_results> directly).
798    
799     =back
800    
801 root 1.71 =cut
802    
803 root 1.70 #############################################################################
804 root 1.45 # object support
805    
806 root 1.102 sub reattach {
807     # basically do the same as instantiate, without calling instantiate
808     my ($obj) = @_;
809    
810     my $registry = $obj->registry;
811    
812     @$registry = ();
813    
814     delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
815    
816     for my $name (keys %{ $obj->{_attachment} || {} }) {
817     if (my $attach = $attachment{$name}) {
818     for (@$attach) {
819     my ($klass, @attach) = @$_;
820     _attach $registry, $klass, @attach;
821     }
822     } else {
823     warn "object uses attachment '$name' that is not available, postponing.\n";
824     }
825     }
826     }
827    
828 root 1.100 cf::attachable->attach (
829     prio => -1000000,
830     on_instantiate => sub {
831     my ($obj, $data) = @_;
832 root 1.45
833 root 1.100 $data = from_json $data;
834 root 1.45
835 root 1.100 for (@$data) {
836     my ($name, $args) = @$_;
837 root 1.49
838 root 1.100 $obj->attach ($name, %{$args || {} });
839     }
840     },
841 root 1.102 on_reattach => \&reattach,
842 root 1.100 on_clone => sub {
843     my ($src, $dst) = @_;
844    
845     @{$dst->registry} = @{$src->registry};
846    
847     %$dst = %$src;
848    
849     %{$dst->{_attachment}} = %{$src->{_attachment}}
850     if exists $src->{_attachment};
851     },
852     );
853 root 1.45
854 root 1.46 sub object_freezer_save {
855 root 1.59 my ($filename, $rdata, $objs) = @_;
856 root 1.46
857 root 1.105 sync_job {
858     if (length $$rdata) {
859     warn sprintf "saving %s (%d,%d)\n",
860     $filename, length $$rdata, scalar @$objs;
861 root 1.60
862 root 1.105 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
863 root 1.60 chmod SAVE_MODE, $fh;
864 root 1.105 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
865     aio_fsync $fh;
866 root 1.60 close $fh;
867 root 1.105
868     if (@$objs) {
869     if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
870     chmod SAVE_MODE, $fh;
871     my $data = Storable::nfreeze { version => 1, objs => $objs };
872     aio_write $fh, 0, (length $data), $data, 0;
873     aio_fsync $fh;
874     close $fh;
875     aio_rename "$filename.pst~", "$filename.pst";
876     }
877     } else {
878     aio_unlink "$filename.pst";
879     }
880    
881     aio_rename "$filename~", $filename;
882 root 1.60 } else {
883 root 1.105 warn "FATAL: $filename~: $!\n";
884 root 1.60 }
885 root 1.59 } else {
886 root 1.105 aio_unlink $filename;
887     aio_unlink "$filename.pst";
888 root 1.59 }
889 root 1.45 }
890     }
891    
892 root 1.80 sub object_freezer_as_string {
893     my ($rdata, $objs) = @_;
894    
895     use Data::Dumper;
896    
897 root 1.81 $$rdata . Dumper $objs
898 root 1.80 }
899    
900 root 1.46 sub object_thawer_load {
901     my ($filename) = @_;
902    
903 root 1.105 my ($data, $av);
904 root 1.61
905 root 1.105 (aio_load $filename, $data) >= 0
906     or return;
907 root 1.61
908 root 1.105 unless (aio_stat "$filename.pst") {
909     (aio_load "$filename.pst", $av) >= 0
910     or return;
911 root 1.113 $av = eval { (Storable::thaw $av)->{objs} };
912 root 1.61 }
913 root 1.45
914 root 1.118 warn sprintf "loading %s (%d)\n",
915     $filename, length $data, scalar @{$av || []};#d#
916 root 1.105 return ($data, $av);
917 root 1.45 }
918    
919     #############################################################################
920 root 1.85 # command handling &c
921 root 1.39
922 root 1.85 =item cf::register_command $name => \&callback($ob,$args);
923 root 1.1
924 root 1.85 Register a callback for execution when the client sends the user command
925     $name.
926 root 1.5
927 root 1.85 =cut
928 root 1.5
929 root 1.85 sub register_command {
930     my ($name, $cb) = @_;
931 root 1.5
932 root 1.85 my $caller = caller;
933     #warn "registering command '$name/$time' to '$caller'";
934 root 1.1
935 root 1.85 push @{ $COMMAND{$name} }, [$caller, $cb];
936 root 1.1 }
937    
938 root 1.85 =item cf::register_extcmd $name => \&callback($pl,$packet);
939 root 1.1
940 root 1.85 Register a callbackf ro execution when the client sends an extcmd packet.
941 root 1.1
942 root 1.85 If the callback returns something, it is sent back as if reply was being
943     called.
944 root 1.1
945 root 1.85 =cut
946 root 1.1
947 root 1.16 sub register_extcmd {
948     my ($name, $cb) = @_;
949    
950     my $caller = caller;
951     #warn "registering extcmd '$name' to '$caller'";
952    
953 root 1.85 $EXTCMD{$name} = [$cb, $caller];
954 root 1.16 }
955    
956 root 1.93 cf::player->attach (
957 root 1.85 on_command => sub {
958     my ($pl, $name, $params) = @_;
959    
960     my $cb = $COMMAND{$name}
961     or return;
962    
963     for my $cmd (@$cb) {
964     $cmd->[1]->($pl->ob, $params);
965     }
966    
967     cf::override;
968     },
969     on_extcmd => sub {
970     my ($pl, $buf) = @_;
971    
972     my $msg = eval { from_json $buf };
973    
974     if (ref $msg) {
975     if (my $cb = $EXTCMD{$msg->{msgtype}}) {
976     if (my %reply = $cb->[0]->($pl, $msg)) {
977     $pl->ext_reply ($msg->{msgid}, %reply);
978     }
979     }
980     } else {
981     warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
982     }
983    
984     cf::override;
985     },
986 root 1.93 );
987 root 1.85
988 root 1.6 sub register {
989     my ($base, $pkg) = @_;
990    
991 root 1.45 #TODO
992 root 1.6 }
993    
994 root 1.1 sub load_extension {
995     my ($path) = @_;
996    
997     $path =~ /([^\/\\]+)\.ext$/ or die "$path";
998 root 1.5 my $base = $1;
999 root 1.1 my $pkg = $1;
1000     $pkg =~ s/[^[:word:]]/_/g;
1001 root 1.41 $pkg = "ext::$pkg";
1002 root 1.1
1003     warn "loading '$path' into '$pkg'\n";
1004    
1005     open my $fh, "<:utf8", $path
1006     or die "$path: $!";
1007    
1008     my $source =
1009     "package $pkg; use strict; use utf8;\n"
1010     . "#line 1 \"$path\"\n{\n"
1011     . (do { local $/; <$fh> })
1012     . "\n};\n1";
1013    
1014     eval $source
1015 root 1.82 or die $@ ? "$path: $@\n"
1016     : "extension disabled.\n";
1017 root 1.1
1018     push @exts, $pkg;
1019 root 1.5 $ext_pkg{$base} = $pkg;
1020 root 1.1
1021 root 1.6 # no strict 'refs';
1022 root 1.23 # @{"$pkg\::ISA"} = ext::;
1023 root 1.1
1024 root 1.6 register $base, $pkg;
1025 root 1.1 }
1026    
1027     sub unload_extension {
1028     my ($pkg) = @_;
1029    
1030     warn "removing extension $pkg\n";
1031    
1032     # remove hooks
1033 root 1.45 #TODO
1034     # for my $idx (0 .. $#PLUGIN_EVENT) {
1035     # delete $hook[$idx]{$pkg};
1036     # }
1037 root 1.1
1038     # remove commands
1039 root 1.85 for my $name (keys %COMMAND) {
1040     my @cb = grep $_->[0] ne $pkg, @{ $COMMAND{$name} };
1041 root 1.1
1042     if (@cb) {
1043 root 1.85 $COMMAND{$name} = \@cb;
1044 root 1.1 } else {
1045 root 1.85 delete $COMMAND{$name};
1046 root 1.1 }
1047     }
1048    
1049 root 1.15 # remove extcmds
1050 root 1.85 for my $name (grep $EXTCMD{$_}[1] eq $pkg, keys %EXTCMD) {
1051     delete $EXTCMD{$name};
1052 root 1.15 }
1053    
1054 root 1.43 if (my $cb = $pkg->can ("unload")) {
1055 elmex 1.31 eval {
1056     $cb->($pkg);
1057     1
1058     } or warn "$pkg unloaded, but with errors: $@";
1059     }
1060    
1061 root 1.1 Symbol::delete_package $pkg;
1062     }
1063    
1064     sub load_extensions {
1065     for my $ext (<$LIBDIR/*.ext>) {
1066 root 1.3 next unless -r $ext;
1067 root 1.2 eval {
1068     load_extension $ext;
1069     1
1070     } or warn "$ext not loaded: $@";
1071 root 1.1 }
1072     }
1073    
1074 root 1.8 #############################################################################
1075     # load/save/clean perl data associated with a map
1076    
1077 root 1.39 *cf::mapsupport::on_clean = sub {
1078 root 1.13 my ($map) = @_;
1079 root 1.7
1080     my $path = $map->tmpname;
1081     defined $path or return;
1082    
1083 root 1.46 unlink "$path.pst";
1084 root 1.7 };
1085    
1086 root 1.93 cf::map->attach (prio => -10000, package => cf::mapsupport::);
1087 root 1.39
1088 root 1.8 #############################################################################
1089 root 1.70
1090     =head2 CORE EXTENSIONS
1091    
1092     Functions and methods that extend core crossfire objects.
1093    
1094 root 1.143 =cut
1095    
1096     package cf::player;
1097    
1098 root 1.154 use Coro::AIO;
1099    
1100 root 1.95 =head3 cf::player
1101    
1102 root 1.70 =over 4
1103 root 1.22
1104 root 1.143 =item cf::player::find $login
1105 root 1.23
1106 root 1.143 Returns the given player object, loading it if necessary (might block).
1107 root 1.23
1108     =cut
1109    
1110 root 1.145 sub playerdir($) {
1111     cf::localdir
1112     . "/"
1113     . cf::playerdir
1114     . "/"
1115     . (ref $_[0] ? $_[0]->ob->name : $_[0])
1116     }
1117    
1118 root 1.143 sub path($) {
1119 root 1.145 my $login = ref $_[0] ? $_[0]->ob->name : $_[0];
1120    
1121     (playerdir $login) . "/$login.pl"
1122 root 1.143 }
1123    
1124     sub find_active($) {
1125     $cf::PLAYER{$_[0]}
1126     and $cf::PLAYER{$_[0]}->active
1127     and $cf::PLAYER{$_[0]}
1128     }
1129    
1130     sub exists($) {
1131     my ($login) = @_;
1132    
1133     $cf::PLAYER{$login}
1134     or cf::sync_job { !aio_stat $login }
1135     }
1136    
1137     sub find($) {
1138     return $cf::PLAYER{$_[0]} || do {
1139     my $login = $_[0];
1140    
1141     my $guard = cf::lock_acquire "user_find:$login";
1142    
1143 root 1.151 $cf::PLAYER{$_[0]} || do {
1144     my $pl = load_pl path $login
1145     or return;
1146     $cf::PLAYER{$login} = $pl
1147     }
1148     }
1149 root 1.143 }
1150    
1151     sub save($) {
1152     my ($pl) = @_;
1153    
1154     return if $pl->{deny_save};
1155    
1156     my $path = path $pl;
1157     my $guard = cf::lock_acquire "user_save:$path";
1158    
1159     return if $pl->{deny_save};
1160 root 1.146
1161 root 1.154 aio_mkdir playerdir $pl, 0770;
1162 root 1.143 $pl->{last_save} = $cf::RUNTIME;
1163    
1164     $pl->save_pl ($path);
1165     Coro::cede;
1166     }
1167    
1168     sub new($) {
1169     my ($login) = @_;
1170    
1171     my $self = create;
1172    
1173     $self->ob->name ($login);
1174     $self->{deny_save} = 1;
1175    
1176     $cf::PLAYER{$login} = $self;
1177    
1178     $self
1179 root 1.23 }
1180    
1181 root 1.154 =item $pl->quit_character
1182    
1183     Nukes the player without looking back. If logged in, the connection will
1184     be destroyed. May block for a long time.
1185    
1186     =cut
1187    
1188 root 1.145 sub quit_character {
1189     my ($pl) = @_;
1190    
1191     $pl->{deny_save} = 1;
1192     $pl->password ("*"); # this should lock out the player until we nuked the dir
1193    
1194     $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active;
1195     $pl->deactivate;
1196     $pl->invoke (cf::EVENT_PLAYER_QUIT);
1197     $pl->ns->destroy if $pl->ns;
1198    
1199     my $path = playerdir $pl;
1200     my $temp = "$path~$cf::RUNTIME~deleting~";
1201 root 1.154 aio_rename $path, $temp;
1202 root 1.150 delete $cf::PLAYER{$pl->ob->name};
1203     $pl->destroy;
1204     IO::AIO::aio_rmtree $temp;
1205 root 1.145 }
1206    
1207 root 1.154 =item cf::player::list_logins
1208    
1209     Returns am arrayref of all valid playernames in the system, can take a
1210     while and may block, so not sync_job-capable, ever.
1211    
1212     =cut
1213    
1214     sub list_logins {
1215     my $dirs = aio_readdir cf::localdir . "/" . cf::playerdir
1216     or return [];
1217    
1218     my @logins;
1219    
1220     for my $login (@$dirs) {
1221     my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next;
1222     aio_read $fh, 0, 512, my $buf, 0 or next;
1223 root 1.155 $buf !~ /^password -------------$/m or next; # official not-valid tag
1224 root 1.154
1225     utf8::decode $login;
1226     push @logins, $login;
1227     }
1228    
1229     \@logins
1230     }
1231    
1232     =item $player->maps
1233    
1234     Returns an arrayref of cf::path's of all maps that are private for this
1235     player. May block.
1236    
1237     =cut
1238    
1239     sub maps($) {
1240     my ($pl) = @_;
1241    
1242     my $files = aio_readdir playerdir $pl
1243     or return;
1244    
1245     my @paths;
1246    
1247     for (@$files) {
1248     utf8::decode $_;
1249     next if /\.(?:pl|pst)$/;
1250 root 1.158 next unless /^$PATH_SEP/o;
1251 root 1.154
1252     push @paths, new cf::path "~" . $pl->ob->name . "/" . $_;
1253     }
1254    
1255     \@paths
1256     }
1257    
1258 root 1.95 =item $player->ext_reply ($msgid, $msgtype, %msg)
1259    
1260     Sends an ext reply to the player.
1261    
1262     =cut
1263    
1264 root 1.143 sub ext_reply($$$%) {
1265 root 1.95 my ($self, $id, %msg) = @_;
1266    
1267     $msg{msgid} = $id;
1268    
1269 root 1.143 $self->send ("ext " . cf::to_json \%msg);
1270 root 1.95 }
1271    
1272 root 1.143 package cf;
1273    
1274 root 1.95 =back
1275    
1276 root 1.110
1277     =head3 cf::map
1278    
1279     =over 4
1280    
1281     =cut
1282    
1283     package cf::map;
1284    
1285     use Fcntl;
1286     use Coro::AIO;
1287    
1288 root 1.133 our $MAX_RESET = 3600;
1289     our $DEFAULT_RESET = 3000;
1290 root 1.110
1291     sub generate_random_map {
1292     my ($path, $rmp) = @_;
1293    
1294     # mit "rum" bekleckern, nicht
1295     cf::map::_create_random_map
1296     $path,
1297     $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1298     $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1299     $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1300     $rmp->{exit_on_final_map},
1301     $rmp->{xsize}, $rmp->{ysize},
1302     $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1303     $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1304     $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1305     $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1306     $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1307     (cf::region::find $rmp->{region})
1308     }
1309    
1310     # and all this just because we cannot iterate over
1311     # all maps in C++...
1312     sub change_all_map_light {
1313     my ($change) = @_;
1314    
1315 root 1.122 $_->change_map_light ($change)
1316     for grep $_->outdoor, values %cf::MAP;
1317 root 1.110 }
1318    
1319     sub try_load_header($) {
1320     my ($path) = @_;
1321    
1322     utf8::encode $path;
1323     aio_open $path, O_RDONLY, 0
1324     or return;
1325    
1326     my $map = cf::map::new
1327     or return;
1328    
1329 root 1.135 # for better error messages only, will be overwritten
1330     $map->path ($path);
1331    
1332 root 1.110 $map->load_header ($path)
1333     or return;
1334    
1335     $map->{load_path} = $path;
1336    
1337     $map
1338     }
1339    
1340 root 1.133 sub find;
1341     sub find {
1342 root 1.110 my ($path, $origin) = @_;
1343    
1344 root 1.133 #warn "find<$path,$origin>\n";#d#
1345 root 1.110
1346 root 1.112 $path = new cf::path $path, $origin && $origin->path;
1347 root 1.110 my $key = $path->as_string;
1348    
1349 root 1.120 cf::lock_wait "map_find:$key";
1350    
1351 root 1.110 $cf::MAP{$key} || do {
1352 root 1.120 my $guard = cf::lock_acquire "map_find:$key";
1353    
1354 root 1.110 # do it the slow way
1355     my $map = try_load_header $path->save_path;
1356    
1357 root 1.134 Coro::cede;
1358    
1359 root 1.110 if ($map) {
1360 root 1.132 $map->last_access ((delete $map->{last_access})
1361     || $cf::RUNTIME); #d#
1362 root 1.110 # safety
1363     $map->{instantiate_time} = $cf::RUNTIME
1364     if $map->{instantiate_time} > $cf::RUNTIME;
1365     } else {
1366     if (my $rmp = $path->random_map_params) {
1367     $map = generate_random_map $key, $rmp;
1368     } else {
1369     $map = try_load_header $path->load_path;
1370     }
1371    
1372     $map or return;
1373    
1374 root 1.111 $map->{load_original} = 1;
1375 root 1.110 $map->{instantiate_time} = $cf::RUNTIME;
1376 root 1.132 $map->last_access ($cf::RUNTIME);
1377 root 1.110 $map->instantiate;
1378    
1379     # per-player maps become, after loading, normal maps
1380     $map->per_player (0) if $path->{user_rel};
1381     }
1382    
1383     $map->path ($key);
1384     $map->{path} = $path;
1385 root 1.116 $map->{last_save} = $cf::RUNTIME;
1386 root 1.110
1387 root 1.134 Coro::cede;
1388    
1389 root 1.112 if ($map->should_reset) {
1390     $map->reset;
1391 root 1.123 undef $guard;
1392 root 1.133 $map = find $path
1393 root 1.124 or return;
1394 root 1.112 }
1395 root 1.110
1396     $cf::MAP{$key} = $map
1397     }
1398     }
1399    
1400     sub load {
1401     my ($self) = @_;
1402    
1403 root 1.120 my $path = $self->{path};
1404     my $guard = cf::lock_acquire "map_load:" . $path->as_string;
1405    
1406 root 1.110 return if $self->in_memory != cf::MAP_SWAPPED;
1407    
1408     $self->in_memory (cf::MAP_LOADING);
1409    
1410     $self->alloc;
1411     $self->load_objects ($self->{load_path}, 1)
1412     or return;
1413    
1414 root 1.112 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1415     if delete $self->{load_original};
1416 root 1.111
1417 root 1.110 if (my $uniq = $path->uniq_path) {
1418     utf8::encode $uniq;
1419     if (aio_open $uniq, O_RDONLY, 0) {
1420     $self->clear_unique_items;
1421     $self->load_objects ($uniq, 0);
1422     }
1423     }
1424    
1425 root 1.134 Coro::cede;
1426    
1427 root 1.110 # now do the right thing for maps
1428     $self->link_multipart_objects;
1429    
1430     if ($self->{path}->is_style_map) {
1431     $self->{deny_save} = 1;
1432     $self->{deny_reset} = 1;
1433     } else {
1434     $self->fix_auto_apply;
1435     $self->decay_objects;
1436     $self->update_buttons;
1437     $self->set_darkness_map;
1438     $self->difficulty ($self->estimate_difficulty)
1439     unless $self->difficulty;
1440     $self->activate;
1441     }
1442    
1443 root 1.134 Coro::cede;
1444    
1445 root 1.110 $self->in_memory (cf::MAP_IN_MEMORY);
1446     }
1447    
1448 root 1.157 # find and load all maps in the 3x3 area around a map
1449     sub load_diag {
1450     my ($map) = @_;
1451    
1452     my @diag; # diagonal neighbours
1453    
1454     for (0 .. 3) {
1455     my $neigh = $map->tile_path ($_)
1456     or next;
1457     $neigh = find $neigh, $map
1458     or next;
1459     $neigh->load;
1460    
1461     push @diag, [$neigh->tile_path (($_ + 3) % 4), $neigh],
1462     [$neigh->tile_path (($_ + 1) % 4), $neigh];
1463     }
1464    
1465     for (@diag) {
1466     my $neigh = find @$_
1467     or next;
1468     $neigh->load;
1469     }
1470     }
1471    
1472 root 1.133 sub find_sync {
1473 root 1.110 my ($path, $origin) = @_;
1474    
1475 root 1.157 cf::sync_job { find $path, $origin }
1476 root 1.133 }
1477    
1478     sub do_load_sync {
1479     my ($map) = @_;
1480 root 1.110
1481 root 1.133 cf::sync_job { $map->load };
1482 root 1.110 }
1483    
1484 root 1.157 our %MAP_PREFETCH;
1485     our $MAP_PREFETCHER = Coro::async {
1486     while () {
1487     while (%MAP_PREFETCH) {
1488     my $key = each %MAP_PREFETCH
1489     or next;
1490     my $path = delete $MAP_PREFETCH{$key};
1491    
1492     my $map = find $path
1493     or next;
1494     $map->load;
1495     }
1496     Coro::schedule;
1497     }
1498     };
1499    
1500     sub find_async {
1501     my ($path, $origin) = @_;
1502    
1503     $path = new cf::path $path, $origin && $origin->path;
1504     my $key = $path->as_string;
1505    
1506     if (my $map = $cf::MAP{$key}) {
1507     return $map if $map->in_memory == cf::MAP_IN_MEMORY;
1508     }
1509    
1510     $MAP_PREFETCH{$key} = $path;
1511     $MAP_PREFETCHER->ready;
1512    
1513     ()
1514     }
1515    
1516 root 1.110 sub save {
1517     my ($self) = @_;
1518    
1519 root 1.137 my $lock = cf::lock_acquire "map_data:" . $self->path;
1520    
1521 root 1.110 $self->{last_save} = $cf::RUNTIME;
1522    
1523     return unless $self->dirty;
1524    
1525 root 1.117 my $save = $self->{path}->save_path; utf8::encode $save;
1526     my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1527    
1528 root 1.110 $self->{load_path} = $save;
1529    
1530     return if $self->{deny_save};
1531    
1532 root 1.132 local $self->{last_access} = $self->last_access;#d#
1533    
1534 root 1.143 cf::async {
1535     $_->contr->save for $self->players;
1536     };
1537    
1538 root 1.110 if ($uniq) {
1539     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1540     $self->save_objects ($uniq, cf::IO_UNIQUES);
1541     } else {
1542     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1543     }
1544     }
1545    
1546     sub swap_out {
1547     my ($self) = @_;
1548    
1549 root 1.130 # save first because save cedes
1550     $self->save;
1551    
1552 root 1.137 my $lock = cf::lock_acquire "map_data:" . $self->path;
1553    
1554 root 1.110 return if $self->players;
1555     return if $self->in_memory != cf::MAP_IN_MEMORY;
1556     return if $self->{deny_save};
1557    
1558     $self->clear;
1559     $self->in_memory (cf::MAP_SWAPPED);
1560     }
1561    
1562 root 1.112 sub reset_at {
1563     my ($self) = @_;
1564 root 1.110
1565     # TODO: safety, remove and allow resettable per-player maps
1566 root 1.114 return 1e99 if $self->{path}{user_rel};
1567     return 1e99 if $self->{deny_reset};
1568 root 1.110
1569 root 1.112 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
1570 root 1.114 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
1571 root 1.110
1572 root 1.112 $time + $to
1573     }
1574    
1575     sub should_reset {
1576     my ($self) = @_;
1577    
1578     $self->reset_at <= $cf::RUNTIME
1579 root 1.111 }
1580    
1581     sub unlink_save {
1582     my ($self) = @_;
1583    
1584     utf8::encode (my $save = $self->{path}->save_path);
1585     aioreq_pri 3; IO::AIO::aio_unlink $save;
1586     aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1587 root 1.110 }
1588    
1589 root 1.113 sub rename {
1590     my ($self, $new_path) = @_;
1591    
1592     $self->unlink_save;
1593    
1594     delete $cf::MAP{$self->path};
1595     $self->{path} = new cf::path $new_path;
1596 root 1.114 $self->path ($self->{path}->as_string);
1597 root 1.113 $cf::MAP{$self->path} = $self;
1598    
1599     $self->save;
1600     }
1601    
1602 root 1.110 sub reset {
1603     my ($self) = @_;
1604    
1605 root 1.137 my $lock = cf::lock_acquire "map_data:" . $self->path;
1606    
1607 root 1.110 return if $self->players;
1608     return if $self->{path}{user_rel};#d#
1609    
1610     warn "resetting map ", $self->path;#d#
1611    
1612 root 1.111 delete $cf::MAP{$self->path};
1613 root 1.110
1614     $_->clear_links_to ($self) for values %cf::MAP;
1615    
1616 root 1.111 $self->unlink_save;
1617     $self->destroy;
1618 root 1.110 }
1619    
1620 root 1.114 my $nuke_counter = "aaaa";
1621    
1622     sub nuke {
1623     my ($self) = @_;
1624    
1625     $self->{deny_save} = 1;
1626     $self->reset_timeout (1);
1627     $self->rename ("{nuke}/" . ($nuke_counter++));
1628     $self->reset; # polite request, might not happen
1629     }
1630    
1631 root 1.110 sub customise_for {
1632     my ($map, $ob) = @_;
1633    
1634     if ($map->per_player) {
1635 root 1.133 return cf::map::find "~" . $ob->name . "/" . $map->{path}{path};
1636 root 1.110 }
1637    
1638     $map
1639     }
1640    
1641 root 1.158 =item cf::map::unique_maps
1642    
1643     Returns an arrayref of cf::path's of all shared maps that have
1644     instantiated unique items. May block.
1645    
1646     =cut
1647    
1648     sub unique_maps() {
1649     my $files = aio_readdir cf::localdir . "/" . cf::uniquedir
1650     or return;
1651    
1652     my @paths;
1653    
1654     for (@$files) {
1655     utf8::decode $_;
1656     next if /\.pst$/;
1657     next unless /^$PATH_SEP/o;
1658    
1659     push @paths, new cf::path $_;
1660     }
1661    
1662     \@paths
1663     }
1664    
1665 root 1.155 package cf;
1666    
1667     =back
1668    
1669     =head3 cf::object
1670    
1671     =cut
1672    
1673     package cf::object;
1674    
1675     =over 4
1676    
1677     =item $ob->inv_recursive
1678 root 1.110
1679 root 1.155 Returns the inventory of the object _and_ their inventories, recursively.
1680 root 1.110
1681 root 1.155 =cut
1682 root 1.144
1683 root 1.155 sub inv_recursive_;
1684     sub inv_recursive_ {
1685     map { $_, inv_recursive_ $_->inv } @_
1686     }
1687 root 1.110
1688 root 1.155 sub inv_recursive {
1689     inv_recursive_ inv $_[0]
1690 root 1.110 }
1691    
1692     package cf;
1693    
1694     =back
1695    
1696 root 1.95 =head3 cf::object::player
1697    
1698     =over 4
1699    
1700 root 1.79 =item $player_object->reply ($npc, $msg[, $flags])
1701 root 1.28
1702     Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
1703     can be C<undef>. Does the right thing when the player is currently in a
1704     dialogue with the given NPC character.
1705    
1706     =cut
1707    
1708 root 1.22 # rough implementation of a future "reply" method that works
1709     # with dialog boxes.
1710 root 1.95 #TODO: the first argument must go, split into a $npc->reply_to ( method
1711 root 1.23 sub cf::object::player::reply($$$;$) {
1712     my ($self, $npc, $msg, $flags) = @_;
1713    
1714     $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
1715 root 1.22
1716 root 1.24 if ($self->{record_replies}) {
1717     push @{ $self->{record_replies} }, [$npc, $msg, $flags];
1718     } else {
1719     $msg = $npc->name . " says: $msg" if $npc;
1720     $self->message ($msg, $flags);
1721     }
1722 root 1.22 }
1723    
1724 root 1.79 =item $player_object->may ("access")
1725    
1726     Returns wether the given player is authorized to access resource "access"
1727     (e.g. "command_wizcast").
1728    
1729     =cut
1730    
1731     sub cf::object::player::may {
1732     my ($self, $access) = @_;
1733    
1734     $self->flag (cf::FLAG_WIZ) ||
1735     (ref $cf::CFG{"may_$access"}
1736     ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
1737     : $cf::CFG{"may_$access"})
1738     }
1739 root 1.70
1740 root 1.115 =item $player_object->enter_link
1741    
1742     Freezes the player and moves him/her to a special map (C<{link}>).
1743    
1744     The player should be reaosnably safe there for short amounts of time. You
1745     I<MUST> call C<leave_link> as soon as possible, though.
1746    
1747     =item $player_object->leave_link ($map, $x, $y)
1748    
1749     Moves the player out of the specila link map onto the given map. If the
1750     map is not valid (or omitted), the player will be moved back to the
1751     location he/she was before the call to C<enter_link>, or, if that fails,
1752     to the emergency map position.
1753    
1754     Might block.
1755    
1756     =cut
1757    
1758 root 1.110 sub cf::object::player::enter_link {
1759     my ($self) = @_;
1760    
1761 root 1.120 $self->deactivate_recursive;
1762    
1763 root 1.110 return if $self->map == $LINK_MAP;
1764    
1765 root 1.120 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
1766 root 1.110 if $self->map;
1767    
1768     $self->enter_map ($LINK_MAP, 20, 20);
1769     }
1770    
1771     sub cf::object::player::leave_link {
1772     my ($self, $map, $x, $y) = @_;
1773    
1774     my $link_pos = delete $self->{_link_pos};
1775    
1776     unless ($map) {
1777     # restore original map position
1778     ($map, $x, $y) = @{ $link_pos || [] };
1779 root 1.133 $map = cf::map::find $map;
1780 root 1.110
1781     unless ($map) {
1782     ($map, $x, $y) = @$EMERGENCY_POSITION;
1783 root 1.133 $map = cf::map::find $map
1784 root 1.110 or die "FATAL: cannot load emergency map\n";
1785     }
1786     }
1787    
1788     ($x, $y) = (-1, -1)
1789     unless (defined $x) && (defined $y);
1790    
1791     # use -1 or undef as default coordinates, not 0, 0
1792     ($x, $y) = ($map->enter_x, $map->enter_y)
1793     if $x <=0 && $y <= 0;
1794    
1795     $map->load;
1796 root 1.157 $map->load_diag;
1797 root 1.110
1798 root 1.143 return unless $self->contr->active;
1799 root 1.110 $self->activate_recursive;
1800     $self->enter_map ($map, $x, $y);
1801     }
1802    
1803 root 1.120 cf::player->attach (
1804     on_logout => sub {
1805     my ($pl) = @_;
1806    
1807     # abort map switching before logout
1808     if ($pl->ob->{_link_pos}) {
1809     cf::sync_job {
1810     $pl->ob->leave_link
1811     };
1812     }
1813     },
1814     on_login => sub {
1815     my ($pl) = @_;
1816    
1817     # try to abort aborted map switching on player login :)
1818     # should happen only on crashes
1819     if ($pl->ob->{_link_pos}) {
1820     $pl->ob->enter_link;
1821 root 1.140 (async {
1822 root 1.120 # we need this sleep as the login has a concurrent enter_exit running
1823     # and this sleep increases chances of the player not ending up in scorn
1824 root 1.140 $pl->ob->reply (undef,
1825     "There was an internal problem at your last logout, "
1826     . "the server will try to bring you to your intended destination in a second.",
1827     cf::NDI_RED);
1828 root 1.120 Coro::Timer::sleep 1;
1829     $pl->ob->leave_link;
1830 root 1.139 })->prio (2);
1831 root 1.120 }
1832     },
1833     );
1834    
1835 root 1.136 =item $player_object->goto ($path, $x, $y)
1836 root 1.110
1837     =cut
1838    
1839 root 1.136 sub cf::object::player::goto {
1840 root 1.110 my ($self, $path, $x, $y) = @_;
1841    
1842 root 1.153 $path = new cf::path $path;
1843    
1844 root 1.110 $self->enter_link;
1845    
1846 root 1.140 (async {
1847 root 1.133 my $map = cf::map::find $path->as_string;
1848 root 1.110 $map = $map->customise_for ($self) if $map;
1849    
1850 root 1.119 # warn "entering ", $map->path, " at ($x, $y)\n"
1851     # if $map;
1852 root 1.110
1853 root 1.149 $map or $self->message ("The exit to '" . ($path->visible_name) . "' is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1854 root 1.115
1855 root 1.110 $self->leave_link ($map, $x, $y);
1856     })->prio (1);
1857     }
1858    
1859     =item $player_object->enter_exit ($exit_object)
1860    
1861     =cut
1862    
1863     sub parse_random_map_params {
1864     my ($spec) = @_;
1865    
1866     my $rmp = { # defaults
1867     xsize => 10,
1868     ysize => 10,
1869     };
1870    
1871     for (split /\n/, $spec) {
1872     my ($k, $v) = split /\s+/, $_, 2;
1873    
1874     $rmp->{lc $k} = $v if (length $k) && (length $v);
1875     }
1876    
1877     $rmp
1878     }
1879    
1880     sub prepare_random_map {
1881     my ($exit) = @_;
1882    
1883     # all this does is basically replace the /! path by
1884     # a new random map path (?random/...) with a seed
1885     # that depends on the exit object
1886    
1887     my $rmp = parse_random_map_params $exit->msg;
1888    
1889     if ($exit->map) {
1890     $rmp->{region} = $exit->map->region_name;
1891     $rmp->{origin_map} = $exit->map->path;
1892     $rmp->{origin_x} = $exit->x;
1893     $rmp->{origin_y} = $exit->y;
1894     }
1895    
1896     $rmp->{random_seed} ||= $exit->random_seed;
1897    
1898     my $data = cf::to_json $rmp;
1899     my $md5 = Digest::MD5::md5_hex $data;
1900    
1901     if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
1902     aio_write $fh, 0, (length $data), $data, 0;
1903    
1904     $exit->slaying ("?random/$md5");
1905     $exit->msg (undef);
1906     }
1907     }
1908    
1909     sub cf::object::player::enter_exit {
1910     my ($self, $exit) = @_;
1911    
1912     return unless $self->type == cf::PLAYER;
1913    
1914     $self->enter_link;
1915    
1916 root 1.140 (async {
1917 root 1.133 $self->deactivate_recursive; # just to be sure
1918 root 1.110 unless (eval {
1919     prepare_random_map $exit
1920     if $exit->slaying eq "/!";
1921    
1922     my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
1923 root 1.136 $self->goto ($path, $exit->stats->hp, $exit->stats->sp);
1924 root 1.110
1925     1;
1926     }) {
1927     $self->message ("Something went wrong deep within the crossfire server. "
1928     . "I'll try to bring you back to the map you were before. "
1929 root 1.158 . "Please report this to the dungeon master!",
1930 root 1.110 cf::NDI_UNIQUE | cf::NDI_RED);
1931    
1932     warn "ERROR in enter_exit: $@";
1933     $self->leave_link;
1934     }
1935     })->prio (1);
1936     }
1937    
1938 root 1.95 =head3 cf::client
1939    
1940     =over 4
1941    
1942     =item $client->send_drawinfo ($text, $flags)
1943    
1944     Sends a drawinfo packet to the client. Circumvents output buffering so
1945     should not be used under normal circumstances.
1946    
1947 root 1.70 =cut
1948    
1949 root 1.95 sub cf::client::send_drawinfo {
1950     my ($self, $text, $flags) = @_;
1951    
1952     utf8::encode $text;
1953     $self->send_packet (sprintf "drawinfo %d %s", $flags, $text);
1954     }
1955    
1956    
1957     =item $success = $client->query ($flags, "text", \&cb)
1958    
1959     Queues a query to the client, calling the given callback with
1960     the reply text on a reply. flags can be C<cf::CS_QUERY_YESNO>,
1961     C<cf::CS_QUERY_SINGLECHAR> or C<cf::CS_QUERY_HIDEINPUT> or C<0>.
1962    
1963     Queries can fail, so check the return code. Or don't, as queries will become
1964     reliable at some point in the future.
1965    
1966     =cut
1967    
1968     sub cf::client::query {
1969     my ($self, $flags, $text, $cb) = @_;
1970    
1971     return unless $self->state == ST_PLAYING
1972     || $self->state == ST_SETUP
1973     || $self->state == ST_CUSTOM;
1974    
1975     $self->state (ST_CUSTOM);
1976    
1977     utf8::encode $text;
1978     push @{ $self->{query_queue} }, [(sprintf "query %d %s", $flags, $text), $cb];
1979    
1980     $self->send_packet ($self->{query_queue}[0][0])
1981     if @{ $self->{query_queue} } == 1;
1982     }
1983    
1984     cf::client->attach (
1985     on_reply => sub {
1986     my ($ns, $msg) = @_;
1987    
1988     # this weird shuffling is so that direct followup queries
1989     # get handled first
1990 root 1.128 my $queue = delete $ns->{query_queue}
1991 root 1.129 or return; # be conservative, not sure how that can happen, but we saw a crash here
1992 root 1.95
1993     (shift @$queue)->[1]->($msg);
1994    
1995     push @{ $ns->{query_queue} }, @$queue;
1996    
1997     if (@{ $ns->{query_queue} } == @$queue) {
1998     if (@$queue) {
1999     $ns->send_packet ($ns->{query_queue}[0][0]);
2000     } else {
2001 root 1.98 $ns->state (ST_PLAYING) if $ns->state == ST_CUSTOM;
2002 root 1.95 }
2003     }
2004     },
2005     );
2006    
2007 root 1.140 =item $client->async (\&cb)
2008 root 1.96
2009     Create a new coroutine, running the specified callback. The coroutine will
2010     be automatically cancelled when the client gets destroyed (e.g. on logout,
2011     or loss of connection).
2012    
2013     =cut
2014    
2015 root 1.140 sub cf::client::async {
2016 root 1.96 my ($self, $cb) = @_;
2017    
2018 root 1.140 my $coro = &Coro::async ($cb);
2019 root 1.103
2020     $coro->on_destroy (sub {
2021 root 1.96 delete $self->{_coro}{$coro+0};
2022 root 1.103 });
2023 root 1.96
2024     $self->{_coro}{$coro+0} = $coro;
2025 root 1.103
2026     $coro
2027 root 1.96 }
2028    
2029     cf::client->attach (
2030     on_destroy => sub {
2031     my ($ns) = @_;
2032    
2033 root 1.97 $_->cancel for values %{ (delete $ns->{_coro}) || {} };
2034 root 1.96 },
2035     );
2036    
2037 root 1.95 =back
2038    
2039 root 1.70
2040     =head2 SAFE SCRIPTING
2041    
2042     Functions that provide a safe environment to compile and execute
2043     snippets of perl code without them endangering the safety of the server
2044     itself. Looping constructs, I/O operators and other built-in functionality
2045     is not available in the safe scripting environment, and the number of
2046 root 1.79 functions and methods that can be called is greatly reduced.
2047 root 1.70
2048     =cut
2049 root 1.23
2050 root 1.42 our $safe = new Safe "safe";
2051 root 1.23 our $safe_hole = new Safe::Hole;
2052    
2053     $SIG{FPE} = 'IGNORE';
2054    
2055     $safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time));
2056    
2057 root 1.25 # here we export the classes and methods available to script code
2058    
2059 root 1.70 =pod
2060    
2061     The following fucntions and emthods are available within a safe environment:
2062    
2063 elmex 1.91 cf::object contr pay_amount pay_player map
2064 root 1.70 cf::object::player player
2065     cf::player peaceful
2066 elmex 1.91 cf::map trigger
2067 root 1.70
2068     =cut
2069    
2070 root 1.25 for (
2071 elmex 1.91 ["cf::object" => qw(contr pay_amount pay_player map)],
2072 root 1.25 ["cf::object::player" => qw(player)],
2073     ["cf::player" => qw(peaceful)],
2074 elmex 1.91 ["cf::map" => qw(trigger)],
2075 root 1.25 ) {
2076     no strict 'refs';
2077     my ($pkg, @funs) = @$_;
2078 root 1.41 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
2079 root 1.25 for @funs;
2080     }
2081 root 1.23
2082 root 1.70 =over 4
2083    
2084     =item @retval = safe_eval $code, [var => value, ...]
2085    
2086     Compiled and executes the given perl code snippet. additional var/value
2087     pairs result in temporary local (my) scalar variables of the given name
2088     that are available in the code snippet. Example:
2089    
2090     my $five = safe_eval '$first + $second', first => 1, second => 4;
2091    
2092     =cut
2093    
2094 root 1.23 sub safe_eval($;@) {
2095     my ($code, %vars) = @_;
2096    
2097     my $qcode = $code;
2098     $qcode =~ s/"/‟/g; # not allowed in #line filenames
2099     $qcode =~ s/\n/\\n/g;
2100    
2101     local $_;
2102 root 1.41 local @safe::cf::_safe_eval_args = values %vars;
2103 root 1.23
2104 root 1.42 my $eval =
2105 root 1.23 "do {\n"
2106     . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
2107     . "#line 0 \"{$qcode}\"\n"
2108     . $code
2109     . "\n}"
2110 root 1.25 ;
2111    
2112     sub_generation_inc;
2113 root 1.42 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
2114 root 1.25 sub_generation_inc;
2115    
2116 root 1.42 if ($@) {
2117     warn "$@";
2118     warn "while executing safe code '$code'\n";
2119     warn "with arguments " . (join " ", %vars) . "\n";
2120     }
2121    
2122 root 1.25 wantarray ? @res : $res[0]
2123 root 1.23 }
2124    
2125 root 1.69 =item cf::register_script_function $function => $cb
2126    
2127     Register a function that can be called from within map/npc scripts. The
2128     function should be reasonably secure and should be put into a package name
2129     like the extension.
2130    
2131     Example: register a function that gets called whenever a map script calls
2132     C<rent::overview>, as used by the C<rent> extension.
2133    
2134     cf::register_script_function "rent::overview" => sub {
2135     ...
2136     };
2137    
2138     =cut
2139    
2140 root 1.23 sub register_script_function {
2141     my ($fun, $cb) = @_;
2142    
2143     no strict 'refs';
2144 root 1.41 *{"safe::$fun"} = $safe_hole->wrap ($cb);
2145 root 1.23 }
2146    
2147 root 1.70 =back
2148    
2149 root 1.71 =cut
2150    
2151 root 1.23 #############################################################################
2152 root 1.65
2153     =head2 EXTENSION DATABASE SUPPORT
2154    
2155     Crossfire maintains a very simple database for extension use. It can
2156     currently store anything that can be serialised using Storable, which
2157     excludes objects.
2158    
2159     The parameter C<$family> should best start with the name of the extension
2160     using it, it should be unique.
2161    
2162     =over 4
2163    
2164     =item $hashref = cf::db_get $family
2165    
2166     Return a hashref for use by the extension C<$family>, which can be
2167     modified. After modifications, you have to call C<cf::db_dirty> or
2168     C<cf::db_sync>.
2169    
2170     =item $value = cf::db_get $family => $key
2171    
2172     Returns a single value from the database
2173    
2174     =item cf::db_put $family => $hashref
2175    
2176     Stores the given family hashref into the database. Updates are delayed, if
2177     you want the data to be synced to disk immediately, use C<cf::db_sync>.
2178    
2179     =item cf::db_put $family => $key => $value
2180    
2181     Stores the given C<$value> in the family hash. Updates are delayed, if you
2182     want the data to be synced to disk immediately, use C<cf::db_sync>.
2183    
2184     =item cf::db_dirty
2185    
2186     Marks the database as dirty, to be updated at a later time.
2187    
2188     =item cf::db_sync
2189    
2190     Immediately write the database to disk I<if it is dirty>.
2191    
2192     =cut
2193    
2194 root 1.78 our $DB;
2195    
2196 root 1.65 {
2197 root 1.66 my $path = cf::localdir . "/database.pst";
2198 root 1.65
2199     sub db_load() {
2200 root 1.78 $DB = stat $path ? Storable::retrieve $path : { };
2201 root 1.65 }
2202    
2203     my $pid;
2204    
2205     sub db_save() {
2206     waitpid $pid, 0 if $pid;
2207 root 1.67 if (0 == ($pid = fork)) {
2208 root 1.78 $DB->{_meta}{version} = 1;
2209     Storable::nstore $DB, "$path~";
2210 root 1.65 rename "$path~", $path;
2211     cf::_exit 0 if defined $pid;
2212     }
2213     }
2214    
2215     my $dirty;
2216    
2217     sub db_sync() {
2218     db_save if $dirty;
2219     undef $dirty;
2220     }
2221    
2222 root 1.87 my $idle = Event->idle (min => $TICK * 2.8, max => 10, repeat => 0, data => WF_AUTOCANCEL, cb => sub {
2223 root 1.65 db_sync;
2224     });
2225    
2226     sub db_dirty() {
2227     $dirty = 1;
2228     $idle->start;
2229     }
2230    
2231     sub db_get($;$) {
2232     @_ >= 2
2233 root 1.78 ? $DB->{$_[0]}{$_[1]}
2234     : ($DB->{$_[0]} ||= { })
2235 root 1.65 }
2236    
2237     sub db_put($$;$) {
2238     if (@_ >= 3) {
2239 root 1.78 $DB->{$_[0]}{$_[1]} = $_[2];
2240 root 1.65 } else {
2241 root 1.78 $DB->{$_[0]} = $_[1];
2242 root 1.65 }
2243     db_dirty;
2244     }
2245 root 1.67
2246 root 1.93 cf::global->attach (
2247     prio => 10000,
2248 root 1.67 on_cleanup => sub {
2249     db_sync;
2250     },
2251 root 1.93 );
2252 root 1.65 }
2253    
2254     #############################################################################
2255 root 1.34 # the server's main()
2256    
2257 root 1.73 sub cfg_load {
2258 root 1.72 open my $fh, "<:utf8", cf::confdir . "/config"
2259     or return;
2260    
2261     local $/;
2262     *CFG = YAML::Syck::Load <$fh>;
2263 root 1.131
2264     $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
2265    
2266 root 1.139 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
2267     $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
2268    
2269 root 1.131 if (exists $CFG{mlockall}) {
2270     eval {
2271 root 1.147 $CFG{mlockall} ? eval "mlockall()" : eval "munlockall()"
2272 root 1.131 and die "WARNING: m(un)lockall failed: $!\n";
2273     };
2274     warn $@ if $@;
2275     }
2276 root 1.72 }
2277    
2278 root 1.39 sub main {
2279 root 1.108 # we must not ever block the main coroutine
2280     local $Coro::idle = sub {
2281 root 1.115 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
2282 root 1.140 async { Event::one_event };
2283 root 1.108 };
2284    
2285 root 1.73 cfg_load;
2286 root 1.65 db_load;
2287 root 1.61 load_extensions;
2288 root 1.34 Event::loop;
2289     }
2290    
2291     #############################################################################
2292 root 1.155 # initialisation and cleanup
2293    
2294     # install some emergency cleanup handlers
2295     BEGIN {
2296     for my $signal (qw(INT HUP TERM)) {
2297     Event->signal (
2298     data => WF_AUTOCANCEL,
2299     signal => $signal,
2300     cb => sub {
2301     cf::cleanup "SIG$signal";
2302     },
2303     );
2304     }
2305     }
2306    
2307 root 1.156 sub emergency_save() {
2308 root 1.155 my $freeze_guard = cf::freeze_mainloop;
2309    
2310     warn "enter emergency perl save\n";
2311    
2312     cf::sync_job {
2313     # use a peculiar iteration method to avoid tripping on perl
2314     # refcount bugs in for. also avoids problems with players
2315     # and maps saved/Destroyed asynchronously.
2316     warn "begin emergency player save\n";
2317     for my $login (keys %cf::PLAYER) {
2318     my $pl = $cf::PLAYER{$login} or next;
2319     $pl->valid or next;
2320     $pl->save;
2321     }
2322     warn "end emergency player save\n";
2323    
2324     warn "begin emergency map save\n";
2325     for my $path (keys %cf::MAP) {
2326     my $map = $cf::MAP{$path} or next;
2327     $map->valid or next;
2328     $map->save;
2329     }
2330     warn "end emergency map save\n";
2331     };
2332    
2333     warn "leave emergency perl save\n";
2334     }
2335 root 1.22
2336 root 1.111 sub reload() {
2337 root 1.106 # can/must only be called in main
2338     if ($Coro::current != $Coro::main) {
2339     warn "can only reload from main coroutine\n";
2340     return;
2341     }
2342    
2343 root 1.103 warn "reloading...";
2344    
2345 root 1.133 my $guard = freeze_mainloop;
2346 root 1.106 cf::emergency_save;
2347    
2348 root 1.103 eval {
2349 root 1.106 # if anything goes wrong in here, we should simply crash as we already saved
2350 root 1.65
2351     # cancel all watchers
2352 root 1.87 for (Event::all_watchers) {
2353     $_->cancel if $_->data & WF_AUTOCANCEL;
2354     }
2355 root 1.65
2356 root 1.103 # cancel all extension coros
2357     $_->cancel for values %EXT_CORO;
2358     %EXT_CORO = ();
2359    
2360 root 1.65 # unload all extensions
2361     for (@exts) {
2362 root 1.103 warn "unloading <$_>";
2363 root 1.65 unload_extension $_;
2364     }
2365    
2366     # unload all modules loaded from $LIBDIR
2367     while (my ($k, $v) = each %INC) {
2368     next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
2369    
2370 root 1.103 warn "removing <$k>";
2371 root 1.65 delete $INC{$k};
2372    
2373     $k =~ s/\.pm$//;
2374     $k =~ s/\//::/g;
2375    
2376     if (my $cb = $k->can ("unload_module")) {
2377     $cb->();
2378     }
2379    
2380     Symbol::delete_package $k;
2381     }
2382    
2383     # sync database to disk
2384     cf::db_sync;
2385 root 1.103 IO::AIO::flush;
2386 root 1.65
2387     # get rid of safe::, as good as possible
2388     Symbol::delete_package "safe::$_"
2389 root 1.103 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
2390 root 1.65
2391     # remove register_script_function callbacks
2392     # TODO
2393    
2394     # unload cf.pm "a bit"
2395     delete $INC{"cf.pm"};
2396    
2397     # don't, removes xs symbols, too,
2398     # and global variables created in xs
2399     #Symbol::delete_package __PACKAGE__;
2400    
2401     # reload cf.pm
2402 root 1.103 warn "reloading cf.pm";
2403 root 1.65 require cf;
2404 root 1.100 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
2405    
2406 root 1.73 # load config and database again
2407     cf::cfg_load;
2408 root 1.65 cf::db_load;
2409    
2410     # load extensions
2411 root 1.103 warn "load extensions";
2412 root 1.65 cf::load_extensions;
2413    
2414     # reattach attachments to objects
2415 root 1.103 warn "reattach";
2416 root 1.65 _global_reattach;
2417 root 1.144 reattach $_ for values %MAP;
2418 root 1.65 };
2419    
2420 root 1.106 if ($@) {
2421     warn $@;
2422     warn "error while reloading, exiting.";
2423     exit 1;
2424     }
2425    
2426     warn "reloaded successfully";
2427 root 1.65 };
2428    
2429 root 1.108 #############################################################################
2430    
2431     unless ($LINK_MAP) {
2432     $LINK_MAP = cf::map::new;
2433    
2434     $LINK_MAP->width (41);
2435     $LINK_MAP->height (41);
2436     $LINK_MAP->alloc;
2437     $LINK_MAP->path ("{link}");
2438     $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
2439     $LINK_MAP->in_memory (MAP_IN_MEMORY);
2440 root 1.110
2441     # dirty hack because... archetypes are not yet loaded
2442     Event->timer (
2443 root 1.142 after => 10,
2444 root 1.110 cb => sub {
2445     $_[0]->w->cancel;
2446    
2447     # provide some exits "home"
2448     my $exit = cf::object::new "exit";
2449    
2450     $exit->slaying ($EMERGENCY_POSITION->[0]);
2451     $exit->stats->hp ($EMERGENCY_POSITION->[1]);
2452     $exit->stats->sp ($EMERGENCY_POSITION->[2]);
2453    
2454     $LINK_MAP->insert ($exit->clone, 19, 19);
2455     $LINK_MAP->insert ($exit->clone, 19, 20);
2456     $LINK_MAP->insert ($exit->clone, 19, 21);
2457     $LINK_MAP->insert ($exit->clone, 20, 19);
2458     $LINK_MAP->insert ($exit->clone, 20, 21);
2459     $LINK_MAP->insert ($exit->clone, 21, 19);
2460     $LINK_MAP->insert ($exit->clone, 21, 20);
2461     $LINK_MAP->insert ($exit->clone, 21, 21);
2462    
2463     $exit->destroy;
2464     });
2465    
2466     $LINK_MAP->{deny_save} = 1;
2467     $LINK_MAP->{deny_reset} = 1;
2468    
2469     $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
2470 root 1.108 }
2471    
2472 root 1.85 register "<global>", __PACKAGE__;
2473    
2474 root 1.111 register_command "reload" => sub {
2475 root 1.65 my ($who, $arg) = @_;
2476    
2477     if ($who->flag (FLAG_WIZ)) {
2478 root 1.107 $who->message ("start of reload.");
2479 root 1.111 reload;
2480 root 1.107 $who->message ("end of reload.");
2481 root 1.65 }
2482     };
2483    
2484 root 1.27 unshift @INC, $LIBDIR;
2485 root 1.17
2486 root 1.35 $TICK_WATCHER = Event->timer (
2487 root 1.104 reentrant => 0,
2488     prio => 0,
2489     at => $NEXT_TICK || $TICK,
2490     data => WF_AUTOCANCEL,
2491     cb => sub {
2492 root 1.133 cf::server_tick; # one server iteration
2493     $RUNTIME += $TICK;
2494 root 1.35 $NEXT_TICK += $TICK;
2495    
2496 root 1.155 $WAIT_FOR_TICK->broadcast;
2497     $WAIT_FOR_TICK_ONE->send if $WAIT_FOR_TICK_ONE->awaited;
2498    
2499 root 1.78 # if we are delayed by four ticks or more, skip them all
2500 root 1.103 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
2501 root 1.35
2502     $TICK_WATCHER->at ($NEXT_TICK);
2503     $TICK_WATCHER->start;
2504     },
2505     );
2506    
2507 root 1.80 IO::AIO::max_poll_time $TICK * 0.2;
2508 root 1.77
2509 root 1.108 Event->io (
2510     fd => IO::AIO::poll_fileno,
2511     poll => 'r',
2512     prio => 5,
2513     data => WF_AUTOCANCEL,
2514     cb => \&IO::AIO::poll_cb,
2515     );
2516    
2517     Event->timer (
2518     data => WF_AUTOCANCEL,
2519     after => 0,
2520     interval => 10,
2521     cb => sub {
2522     (Coro::unblock_sub {
2523     write_runtime
2524     or warn "ERROR: unable to write runtime file: $!";
2525     })->();
2526     },
2527     );
2528 root 1.103
2529 root 1.125 END { cf::emergency_save }
2530    
2531 root 1.1 1
2532