ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.24
Committed: Wed Jul 19 22:51:40 2006 UTC (17 years, 10 months ago) by root
Branch: MAIN
Changes since 1.23: +6 -3 lines
Log Message:
remove 'you cannot save on unholy ground' - no purpose is evident, and it is often annoying

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.18 use Event;
11 root 1.19 $Event::Eval = 1; # no idea why this is required, but it is
12 root 1.1
13     use strict;
14    
15     our %COMMAND;
16     our @EVENT;
17     our %PROP_TYPE;
18     our %PROP_IDX;
19    
20     BEGIN {
21     @EVENT = map lc, @EVENT;
22    
23     *CORE::GLOBAL::warn = sub {
24     my $msg = join "", @_;
25     $msg .= "\n"
26     unless $msg =~ /\n$/;
27    
28     print STDERR "cfperl: $msg";
29     LOG llevError, "cfperl: $msg";
30     };
31     }
32    
33 root 1.9 my %ignore_set = (MAP_PROP_PATH => 1); # I hate the plug-in api. Deeply!
34    
35 root 1.1 # generate property mutators
36     sub prop_gen {
37     my ($prefix, $class) = @_;
38    
39     no strict 'refs';
40    
41     for my $prop (keys %PROP_TYPE) {
42     $prop =~ /^\Q$prefix\E_(.*$)/ or next;
43     my $sub = lc $1;
44    
45     my $type = $PROP_TYPE{$prop};
46     my $idx = $PROP_IDX {$prop};
47    
48     *{"$class\::get_$sub"} = *{"$class\::$sub"} = sub {
49     $_[0]->get_property ($type, $idx)
50     };
51    
52     *{"$class\::set_$sub"} = sub {
53     $_[0]->set_property ($type, $idx, $_[1]);
54 root 1.9 } unless $ignore_set{$prop};
55 root 1.1 }
56     }
57    
58     # auto-generate most of the API
59    
60     prop_gen OBJECT_PROP => "cf::object";
61     # CFAPI_OBJECT_ANIMATION?
62     prop_gen PLAYER_PROP => "cf::object::player";
63    
64     prop_gen MAP_PROP => "cf::map";
65     prop_gen ARCH_PROP => "cf::arch";
66    
67     # guessed hierarchies
68    
69     @cf::object::player::ISA = 'cf::object';
70     @cf::object::map::ISA = 'cf::object';
71    
72 root 1.18 $Event::DIED = sub {
73     warn "error in event callback: @_";
74     };
75    
76 root 1.5 my %ext_pkg;
77 root 1.1 my @exts;
78     my @hook;
79     my %command;
80 root 1.15 my %extcmd;
81 root 1.1
82     sub inject_event {
83 root 1.14 my $extension = shift;
84     my $event_code = shift;
85 root 1.1
86 root 1.14 my $cb = $hook[$event_code]{$extension}
87 root 1.5 or return;
88    
89 root 1.14 &$cb
90 root 1.5 }
91    
92     sub inject_global_event {
93 root 1.12 my $event = shift;
94 root 1.5
95 root 1.12 my $cb = $hook[$event]
96 root 1.1 or return;
97    
98 root 1.12 List::Util::max map &$_, values %$cb
99 root 1.1 }
100    
101     sub inject_command {
102     my ($name, $obj, $params) = @_;
103    
104     for my $cmd (@{ $command{$name} }) {
105     $cmd->[1]->($obj, $params);
106     }
107    
108     -1
109     }
110    
111     sub register_command {
112     my ($name, $time, $cb) = @_;
113    
114     my $caller = caller;
115 root 1.16 #warn "registering command '$name/$time' to '$caller'";
116 root 1.4
117 root 1.1 push @{ $command{$name} }, [$time, $cb, $caller];
118     $COMMAND{"$name\000"} = List::Util::max map $_->[0], @{ $command{$name} };
119     }
120    
121 root 1.16 sub register_extcmd {
122     my ($name, $cb) = @_;
123    
124     my $caller = caller;
125     #warn "registering extcmd '$name' to '$caller'";
126    
127     $extcmd{$name} = [$cb, $caller];
128     }
129    
130 root 1.6 sub register {
131     my ($base, $pkg) = @_;
132    
133     for my $idx (0 .. $#EVENT) {
134     if (my $ref = $pkg->can ("on_$EVENT[$idx]")) {
135 root 1.16 #warn "registering $EVENT[$idx] hook to '$pkg'\n";
136 root 1.6 $hook[$idx]{$base} = $ref;
137     }
138     }
139     }
140    
141 root 1.1 sub load_extension {
142     my ($path) = @_;
143    
144     $path =~ /([^\/\\]+)\.ext$/ or die "$path";
145 root 1.5 my $base = $1;
146 root 1.1 my $pkg = $1;
147     $pkg =~ s/[^[:word:]]/_/g;
148     $pkg = "cf::ext::$pkg";
149    
150     warn "loading '$path' into '$pkg'\n";
151    
152     open my $fh, "<:utf8", $path
153     or die "$path: $!";
154    
155     my $source =
156     "package $pkg; use strict; use utf8;\n"
157     . "#line 1 \"$path\"\n{\n"
158     . (do { local $/; <$fh> })
159     . "\n};\n1";
160    
161     eval $source
162     or die "$path: $@";
163    
164     push @exts, $pkg;
165 root 1.5 $ext_pkg{$base} = $pkg;
166 root 1.1
167 root 1.6 # no strict 'refs';
168 root 1.23 # @{"$pkg\::ISA"} = ext::;
169 root 1.1
170 root 1.6 register $base, $pkg;
171 root 1.1 }
172    
173     sub unload_extension {
174     my ($pkg) = @_;
175    
176     warn "removing extension $pkg\n";
177    
178 root 1.21 if (my $cb = $pkg->can ("on_unload")) {
179     $cb->($pkg);
180     }
181    
182 root 1.1 # remove hooks
183     for my $idx (0 .. $#EVENT) {
184     delete $hook[$idx]{$pkg};
185     }
186    
187     # remove commands
188     for my $name (keys %command) {
189     my @cb = grep $_->[2] ne $pkg, @{ $command{$name} };
190    
191     if (@cb) {
192     $command{$name} = \@cb;
193     $COMMAND{"$name\000"} = List::Util::max map $_->[0], @cb;
194     } else {
195     delete $command{$name};
196     delete $COMMAND{"$name\000"};
197     }
198     }
199    
200 root 1.15 # remove extcmds
201 root 1.16 for my $name (grep $extcmd{$_}[1] eq $pkg, keys %extcmd) {
202     delete $extcmd{$name};
203 root 1.15 }
204    
205 root 1.1 Symbol::delete_package $pkg;
206     }
207    
208     sub load_extensions {
209     my $LIBDIR = maps_directory "perl";
210    
211     for my $ext (<$LIBDIR/*.ext>) {
212 root 1.3 next unless -r $ext;
213 root 1.2 eval {
214     load_extension $ext;
215     1
216     } or warn "$ext not loaded: $@";
217 root 1.1 }
218     }
219    
220     register_command "perl-reload", 0, sub {
221     my ($who, $arg) = @_;
222    
223     if ($who->flag (FLAG_WIZ)) {
224 root 1.3 $who->message ("reloading...");
225    
226 root 1.1 warn "reloading...\n";
227 root 1.4 eval {
228 root 1.20 $_->cancel for Event::all_watchers;
229    
230 root 1.4 unload_extension $_ for @exts;
231     delete $INC{"cf.pm"};
232    
233     # don't, removes xs symbols, too
234 root 1.21 #Symbol::delete_package __PACKAGE__;
235 root 1.4
236     require cf;
237     };
238     warn $@ if $@;
239     $who->message ($@) if $@;
240 root 1.1 warn "reloaded\n";
241 root 1.3
242     $who->message ("reloaded");
243     } else {
244     $who->message ("Intruder Alert!");
245 root 1.1 }
246     };
247    
248 root 1.8 #############################################################################
249 root 1.15 # extcmd framework, basically convert ext <id> <pkg> arg1 args
250     # into pkg::->on_extcmd_arg1 (...) while shortcutting a few
251    
252     sub on_extcmd {
253     my ($pl, $buf) = @_;
254    
255 root 1.16 my ($type) = $buf =~ s/^(\S+) // ? $1 : "";
256 root 1.15
257 root 1.16 $extcmd{$type}[0]->($pl, $buf)
258     if $extcmd{$type};
259 root 1.15 }
260    
261     #############################################################################
262 root 1.8 # load/save/clean perl data associated with a map
263    
264 root 1.7 *on_mapclean = sub {
265 root 1.13 my ($map) = @_;
266 root 1.7
267     my $path = $map->tmpname;
268     defined $path or return;
269    
270     unlink "$path.cfperl";
271     };
272    
273 root 1.6 *on_mapin =
274     *on_mapload = sub {
275 root 1.13 my ($map) = @_;
276 root 1.6
277     my $path = $map->tmpname;
278     $path = $map->path unless defined $path;
279    
280     open my $fh, "<:raw", "$path.cfperl"
281     or return; # no perl data
282    
283     my $data = Storable::thaw do { local $/; <$fh> };
284    
285     $data->{version} <= 1
286     or return; # too new
287    
288     $map->_set_obs ($data->{obs});
289     };
290    
291     *on_mapout = sub {
292 root 1.13 my ($map) = @_;
293 root 1.6
294     my $path = $map->tmpname;
295     $path = $map->path unless defined $path;
296    
297     my $obs = $map->_get_obs;
298    
299     if (defined $obs) {
300     open my $fh, ">:raw", "$path.cfperl"
301     or die "$path.cfperl: $!";
302    
303 root 1.8 stat $path;
304    
305     print $fh Storable::nfreeze {
306     size => (stat _)[7],
307     time => (stat _)[9],
308     version => 1,
309     obs => $obs,
310     };
311    
312     chmod SAVE_MODE, "$path.cfperl"; # very racy, but cf-compatible *g*
313     } else {
314     unlink "$path.cfperl";
315     }
316     };
317    
318     #############################################################################
319     # load/save perl data associated with player->ob objects
320    
321     *on_player_load = sub {
322 root 1.13 my ($ob, $path) = @_;
323 root 1.8
324 root 1.11 if (open my $fh, "<:raw", "$path.cfperl") {
325 root 1.8
326 root 1.11 #d##TODO#remove
327 root 1.8
328 root 1.11 my $data = Storable::thaw do { local $/; <$fh> };
329 root 1.8
330 root 1.11 $data->{version} <= 1
331     or return; # too new
332    
333     %$ob = %{$data->{ob}};
334     return;
335     }
336    
337     for my $o ($ob, $ob->inv) {
338     if (my $value = $o->get_ob_key_value ("_perl_data")) {
339     $o->set_ob_key_value ("_perl_data");
340    
341     %$o = %{ Storable::thaw pack "H*", $value };
342     }
343     }
344 root 1.8 };
345    
346     *on_player_save = sub {
347 root 1.13 my ($ob, $path) = @_;
348 root 1.8
349 root 1.11 $_->set_ob_key_value (_perl_data => unpack "H*", Storable::nfreeze $_)
350     for grep %$_, $ob, $ob->inv;
351    
352     unlink "$path.cfperl";#d##TODO#remove
353 root 1.6 };
354    
355 root 1.22 #############################################################################
356     # core extensions - in perl
357    
358 root 1.23 =item cf::player::exists $login
359    
360     Returns true when the given account exists.
361    
362     =cut
363    
364     sub cf::player::exists($) {
365     cf::player::find $_[0]
366     or -f sprintf "%s/%s/%s/%s.pl", cf::localdir, cf::playerdir, ($_[0]) x 2;
367     }
368    
369 root 1.22 # rough implementation of a future "reply" method that works
370     # with dialog boxes.
371 root 1.23 sub cf::object::player::reply($$$;$) {
372     my ($self, $npc, $msg, $flags) = @_;
373    
374     $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
375 root 1.22
376 root 1.24 if ($self->{record_replies}) {
377     push @{ $self->{record_replies} }, [$npc, $msg, $flags];
378     } else {
379     $msg = $npc->name . " says: $msg" if $npc;
380     $self->message ($msg, $flags);
381     }
382 root 1.22 }
383    
384     #############################################################################
385 root 1.23 # map scripting support
386    
387     our $safe = new Safe "ext";
388     our $safe_hole = new Safe::Hole;
389    
390     $SIG{FPE} = 'IGNORE';
391    
392     $safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time));
393    
394     # here we would export the classes and methods available to script code
395     #@ext::cf::object::player::ISA = @cf::object::player::ISA;
396     #@ext::cf::object::map::ISA = @cf::object::map::ISA;
397    
398     sub safe_eval($;@) {
399     my ($code, %vars) = @_;
400    
401     my $qcode = $code;
402     $qcode =~ s/"/‟/g; # not allowed in #line filenames
403     $qcode =~ s/\n/\\n/g;
404    
405     local $_;
406     local @ext::cf::_safe_eval_args = values %vars;
407    
408     $safe->reval (
409     "do {\n"
410     . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
411     . "#line 0 \"{$qcode}\"\n"
412     . $code
413     . "\n}"
414     )
415     }
416    
417     sub register_script_function {
418     my ($fun, $cb) = @_;
419    
420     no strict 'refs';
421     *{"ext::$fun"} = $safe_hole->wrap ($cb);
422     }
423    
424     #############################################################################
425 root 1.22 # initialisation
426    
427 root 1.6 register "<global>", __PACKAGE__;
428    
429 root 1.17 unshift @INC, maps_directory "perl";
430    
431 root 1.1 load_extensions;
432    
433     1
434