ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cf.schmorp.de/server/lib/cf.pm
(Generate patch)

Comparing cf.schmorp.de/server/lib/cf.pm (file contents):
Revision 1.314 by root, Mon Jul 23 16:53:15 2007 UTC vs.
Revision 1.332 by root, Sat Aug 18 17:33:53 2007 UTC

10use Event; 10use Event;
11use Opcode; 11use Opcode;
12use Safe; 12use Safe;
13use Safe::Hole; 13use Safe::Hole;
14 14
15use Coro 3.61 (); 15use Coro 3.64 ();
16use Coro::State; 16use Coro::State;
17use Coro::Handle; 17use Coro::Handle;
18use Coro::Event; 18use Coro::Event;
19use Coro::Timer; 19use Coro::Timer;
20use Coro::Signal; 20use Coro::Signal;
21use Coro::Semaphore; 21use Coro::Semaphore;
22use Coro::AIO; 22use Coro::AIO;
23use Coro::Storable; 23use Coro::Storable;
24use Coro::Util ();
24 25
25use JSON::XS 1.4 (); 26use JSON::XS 1.4 ();
26use BDB (); 27use BDB ();
27use Data::Dumper; 28use Data::Dumper;
28use Digest::MD5; 29use Digest::MD5;
322 }; 323 };
323 $TICK_WATCHER->stop; 324 $TICK_WATCHER->stop;
324 $guard 325 $guard
325} 326}
326 327
327=item cf::get_slot $time[, $priority] 328=item cf::get_slot $time[, $priority[, $name]]
328 329
329Allocate $time seconds of blocking CPU time at priority C<$priority>: 330Allocate $time seconds of blocking CPU time at priority C<$priority>:
330This call blocks and returns only when you have at least C<$time> seconds 331This call blocks and returns only when you have at least C<$time> seconds
331of cpu time till the next tick. The slot is only valid till the next cede. 332of cpu time till the next tick. The slot is only valid till the next cede.
333
334The optional C<$name> can be used to identify the job to run. It might be
335used for statistical purposes and should identify the same time-class.
332 336
333Useful for short background jobs. 337Useful for short background jobs.
334 338
335=cut 339=cut
336 340
363 Coro::schedule; 367 Coro::schedule;
364 } 368 }
365 } 369 }
366}; 370};
367 371
368sub get_slot($;$) { 372sub get_slot($;$$) {
369 my ($time, $pri) = @_; 373 my ($time, $pri, $name) = @_;
370 374
375 $time = $TICK * .6 if $time > $TICK * .6;
376 my $sig = new Coro::Signal;
377
371 push @SLOT_QUEUE, [$time, $pri, my $sig = new Coro::Signal]; 378 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
372 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE; 379 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
373 $SLOT_QUEUE->ready; 380 $SLOT_QUEUE->ready;
374 $sig->wait; 381 $sig->wait;
375} 382}
376 383
466Coro::Storable. May, of course, block. Note that the executed sub may 473Coro::Storable. May, of course, block. Note that the executed sub may
467never block itself or use any form of Event handling. 474never block itself or use any form of Event handling.
468 475
469=cut 476=cut
470 477
471sub _store_scalar {
472 open my $fh, ">", \my $buf
473 or die "fork_call: cannot open fh-to-buf in child : $!";
474 Storable::store_fd $_[0], $fh;
475 close $fh;
476
477 $buf
478}
479
480sub fork_call(&@) { 478sub fork_call(&@) {
481 my ($cb, @args) = @_; 479 my ($cb, @args) = @_;
482 480
483# socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC 481 # we seemingly have to make a local copy of the whole thing,
484# or die "socketpair: $!"; 482 # otherwise perl prematurely frees the stuff :/
485 pipe my $fh1, my $fh2 483 # TODO: investigate and fix (liekly this will be rather laborious)
486 or die "pipe: $!";
487 484
488 if (my $pid = fork) { 485 my @res = Coro::Util::fork_eval {
489 close $fh2;
490
491 my $res = (Coro::Handle::unblock $fh1)->readline (undef);
492 warn "pst<$res>" unless $res =~ /^pst/;
493 $res = Coro::Storable::thaw $res;
494
495 waitpid $pid, 0; # should not block anymore, we expect the child to simply behave
496
497 Carp::confess $$res unless "ARRAY" eq ref $res;
498
499 return wantarray ? @$res : $res->[-1];
500 } else {
501 reset_signals; 486 reset_signals;
502 local $SIG{__WARN__}; 487 &$cb
503 local $SIG{__DIE__}; 488 }, @args;
504 # just in case, this hack effectively disables event
505 # in the child. cleaner and slower would be canceling all watchers,
506 # but this works for the time being.
507 local $Coro::idle;
508 $Coro::current->prio (Coro::PRIO_MAX);
509 489
510 eval { 490 wantarray ? @res : $res[-1]
511 close $fh1;
512
513 my @res = eval { $cb->(@args) };
514
515 syswrite $fh2, _store_scalar $@ ? \"$@" : \@res;
516 close $fh2;
517 };
518
519 warn $@ if $@;
520 _exit 0;
521 }
522} 491}
523 492
524=item $value = cf::db_get $family => $key 493=item $value = cf::db_get $family => $key
525 494
526Returns a single value from the environment database. 495Returns a single value from the environment database.
1175 my ($pl, $buf) = @_; 1144 my ($pl, $buf) = @_;
1176 1145
1177 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) }; 1146 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1178 1147
1179 if (ref $msg) { 1148 if (ref $msg) {
1149 my ($type, $reply, @payload) =
1150 "ARRAY" eq ref $msg
1151 ? @$msg
1152 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1153
1180 if (my $cb = $EXTCMD{$msg->{msgtype}}) { 1154 if (my $cb = $EXTCMD{$type}) {
1181 if (my %reply = $cb->($pl, $msg)) { 1155 my @reply = $cb->($pl, @payload);
1156
1182 $pl->ext_reply ($msg->{msgid}, %reply); 1157 $pl->ext_reply ($reply, @reply)
1183 } 1158 if $reply;
1184 } 1159 }
1185 } else { 1160 } else {
1186 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1161 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1187 } 1162 }
1188 1163
1358 $self->{deny_save} = 1; 1333 $self->{deny_save} = 1;
1359 1334
1360 $cf::PLAYER{$login} = $self; 1335 $cf::PLAYER{$login} = $self;
1361 1336
1362 $self 1337 $self
1338}
1339
1340=item $player->send_msg ($channel, $msg, $color, [extra...])
1341
1342=cut
1343
1344sub send_msg {
1345 my $ns = shift->ns
1346 or return;
1347 $ns->send_msg (@_);
1363} 1348}
1364 1349
1365=item $pl->quit_character 1350=item $pl->quit_character
1366 1351
1367Nukes the player without looking back. If logged in, the connection will 1352Nukes the player without looking back. If logged in, the connection will
1511sub hintmode { 1496sub hintmode {
1512 $_[0]{hintmode} = $_[1] if @_ > 1; 1497 $_[0]{hintmode} = $_[1] if @_ > 1;
1513 $_[0]{hintmode} 1498 $_[0]{hintmode}
1514} 1499}
1515 1500
1516=item $player->ext_reply ($msgid, %msg) 1501=item $player->ext_reply ($msgid, @msg)
1517 1502
1518Sends an ext reply to the player. 1503Sends an ext reply to the player.
1519 1504
1520=cut 1505=cut
1521 1506
1522sub ext_reply($$%) { 1507sub ext_reply($$@) {
1523 my ($self, $id, %msg) = @_; 1508 my ($self, $id, @msg) = @_;
1524 1509
1525 $msg{msgid} = $id; 1510 if ($self->ns->extcmd == 2) {
1511 $self->send ("ext " . $self->ns->{json_coder}->encode (["reply-$id", @msg]));
1512 } elsif ($self->ns->extcmd == 1) {
1513 #TODO: version 1, remove
1514 unshift @msg, msgtype => "reply", msgid => $id;
1526 $self->send ("ext " . $self->ns->{json_coder}->encode (\%msg)); 1515 $self->send ("ext " . $self->ns->{json_coder}->encode ({@msg}));
1516 }
1527} 1517}
1528 1518
1529=item $player->ext_event ($type, %msg) 1519=item $player->ext_msg ($type, @msg)
1530 1520
1531Sends an ext event to the client. 1521Sends an ext event to the client.
1532 1522
1533=cut 1523=cut
1534 1524
1535sub ext_event($$%) { 1525sub ext_msg($$@) {
1536 my ($self, $type, %msg) = @_; 1526 my ($self, $type, @msg) = @_;
1537 1527
1538 $self->ns->ext_event ($type, %msg); 1528 $self->ns->ext_msg ($type, @msg);
1539} 1529}
1540 1530
1541=head3 cf::region 1531=head3 cf::region
1542 1532
1543=over 4 1533=over 4
1904 $self->set_darkness_map; 1894 $self->set_darkness_map;
1905 Coro::cede; 1895 Coro::cede;
1906 $self->activate; 1896 $self->activate;
1907 } 1897 }
1908 1898
1899 $self->{last_save} = $cf::RUNTIME;
1900 $self->last_access ($cf::RUNTIME);
1901
1909 $self->in_memory (cf::MAP_IN_MEMORY); 1902 $self->in_memory (cf::MAP_IN_MEMORY);
1910 } 1903 }
1911 1904
1912 $self->post_load; 1905 $self->post_load;
1913} 1906}
2213 2206
2214 } else { 2207 } else {
2215 my $pl = $self->contr; 2208 my $pl = $self->contr;
2216 2209
2217 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) { 2210 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2218 my $diag = $pl->{npc_dialog}; 2211 my $dialog = $pl->{npc_dialog};
2219 $diag->{pl}->ext_reply ( 2212 $dialog->{pl}->ext_msg ($dialog->{id}, update => msg => $dialog->{pl}->expand_cfpod ($msg));
2220 $diag->{id},
2221 msgtype => "reply",
2222 msg => $diag->{pl}->expand_cfpod ($msg),
2223 add_topics => []
2224 );
2225 2213
2226 } else { 2214 } else {
2227 $msg = $npc->name . " says: $msg" if $npc; 2215 $msg = $npc->name . " says: $msg" if $npc;
2228 $self->message ($msg, $flags); 2216 $self->message ($msg, $flags);
2229 } 2217 }
2230 } 2218 }
2219}
2220
2221=item $object->send_msg ($channel, $msg, $color, [extra...])
2222
2223=cut
2224
2225sub cf::object::send_msg {
2226 my $pl = shift->contr
2227 or return;
2228 $pl->send_msg (@_);
2231} 2229}
2232 2230
2233=item $player_object->may ("access") 2231=item $player_object->may ("access")
2234 2232
2235Returns wether the given player is authorized to access resource "access" 2233Returns wether the given player is authorized to access resource "access"
2500sub cf::client::send_msg { 2498sub cf::client::send_msg {
2501 my ($self, $channel, $msg, $color, @extra) = @_; 2499 my ($self, $channel, $msg, $color, @extra) = @_;
2502 2500
2503 $msg = $self->pl->expand_cfpod ($msg); 2501 $msg = $self->pl->expand_cfpod ($msg);
2504 2502
2505 $color &= ~cf::NDI_UNIQUE; # just in case... 2503 $color &= cf::NDI_CLIENT_MASK; # just in case...
2506 2504
2507 if (ref $channel) { 2505 if (ref $channel) {
2508 # send meta info to client, if not yet sent 2506 # send meta info to client, if not yet sent
2509 unless (exists $self->{channel}{$channel->{id}}) { 2507 unless (exists $self->{channel}{$channel->{id}}) {
2510 $self->{channel}{$channel->{id}} = $channel; 2508 $self->{channel}{$channel->{id}} = $channel;
2511 $self->ext_event (channel_info => %$channel); 2509 $self->ext_msg (channel_info => $channel);
2512 } 2510 }
2513 2511
2514 $channel = $channel->{id}; 2512 $channel = $channel->{id};
2515 } 2513 }
2516 2514
2517 return unless @extra || length $msg; 2515 return unless @extra || length $msg;
2518 2516
2519 if ($self->can_msg) { 2517 if ($self->can_msg) {
2518 # default colour, mask it out
2519 $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
2520 if $color & cf::NDI_DEF;
2521
2520 $self->send_packet ("msg " . $self->{json_coder}->encode ([$color, $channel, $msg, @extra])); 2522 $self->send_packet ("msg " . $self->{json_coder}->encode (
2523 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
2521 } else { 2524 } else {
2522 # replace some tags by gcfclient-compatible ones
2523 for ($msg) {
2524 1 while
2525 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2526 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2527 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2528 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2529 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2530 }
2531
2532 if ($color >= 0) { 2525 if ($color >= 0) {
2526 # replace some tags by gcfclient-compatible ones
2527 for ($msg) {
2528 1 while
2529 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2530 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2531 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2532 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2533 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2534 }
2535
2536 $color &= cf::NDI_COLOR_MASK;
2537
2538 utf8::encode $msg;
2539
2533 if (0 && $msg =~ /\[/) { 2540 if (0 && $msg =~ /\[/) {
2541 # COMMAND/INFO
2534 $self->send_packet ("drawextinfo $color 4 0 $msg") 2542 $self->send_packet ("drawextinfo $color 10 8 $msg")
2535 } else { 2543 } else {
2536 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g; 2544 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2537 $self->send_packet ("drawinfo $color $msg") 2545 $self->send_packet ("drawinfo $color $msg")
2538 } 2546 }
2539 } 2547 }
2540 } 2548 }
2541} 2549}
2542 2550
2543=item $client->ext_event ($type, %msg) 2551=item $client->ext_msg ($type, @msg)
2544 2552
2545Sends an ext event to the client. 2553Sends an ext event to the client.
2546 2554
2547=cut 2555=cut
2548 2556
2549sub cf::client::ext_event($$%) { 2557sub cf::client::ext_msg($$@) {
2550 my ($self, $type, %msg) = @_; 2558 my ($self, $type, @msg) = @_;
2551 2559
2552 return unless $self->extcmd; 2560 my $extcmd = $self->extcmd;
2553 2561
2562 if ($extcmd == 2) {
2563 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2564 } elsif ($extcmd == 1) { # TODO: remove
2554 $msg{msgtype} = "event_$type"; 2565 push @msg, msgtype => "event_$type";
2555 $self->send_packet ("ext " . $self->{json_coder}->encode (\%msg)); 2566 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2567 }
2556} 2568}
2557 2569
2558=item $success = $client->query ($flags, "text", \&cb) 2570=item $success = $client->query ($flags, "text", \&cb)
2559 2571
2560Queues a query to the client, calling the given callback with 2572Queues a query to the client, calling the given callback with
2615 my ($ns, $buf) = @_; 2627 my ($ns, $buf) = @_;
2616 2628
2617 my $msg = eval { $ns->{json_coder}->decode ($buf) }; 2629 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2618 2630
2619 if (ref $msg) { 2631 if (ref $msg) {
2632 my ($type, $reply, @payload) =
2633 "ARRAY" eq ref $msg
2634 ? @$msg
2635 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2636
2620 if (my $cb = $EXTICMD{$msg->{msgtype}}) { 2637 if (my $cb = $EXTICMD{$type}) {
2621 if (my %reply = $cb->($ns, $msg)) { 2638 my @reply = $cb->($ns, @payload);
2622 $reply{msgid} = $msg->{msgid}; 2639
2623 $ns->send ("ext " . $ns->{json_coder}->encode (\%reply)); 2640 $ns->ext_reply ($reply, @reply)
2624 } 2641 if $reply;
2625 } 2642 }
2626 } else { 2643 } else {
2627 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n"; 2644 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2628 } 2645 }
2629 2646
2677our $safe = new Safe "safe"; 2694our $safe = new Safe "safe";
2678our $safe_hole = new Safe::Hole; 2695our $safe_hole = new Safe::Hole;
2679 2696
2680$SIG{FPE} = 'IGNORE'; 2697$SIG{FPE} = 'IGNORE';
2681 2698
2682$safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time)); 2699$safe->permit_only (Opcode::opset qw(
2700 :base_core :base_mem :base_orig :base_math
2701 grepstart grepwhile mapstart mapwhile
2702 sort time
2703));
2683 2704
2684# here we export the classes and methods available to script code 2705# here we export the classes and methods available to script code
2685 2706
2686=pod 2707=pod
2687 2708
2688The following functions and methods are available within a safe environment: 2709The following functions and methods are available within a safe environment:
2689 2710
2690 cf::object 2711 cf::object
2691 contr pay_amount pay_player map x y force_find force_add 2712 contr pay_amount pay_player map x y force_find force_add
2692 insert remove 2713 insert remove name archname title slaying race
2693 2714
2694 cf::object::player 2715 cf::object::player
2695 player 2716 player
2696 2717
2697 cf::player 2718 cf::player
2702 2723
2703=cut 2724=cut
2704 2725
2705for ( 2726for (
2706 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y 2727 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
2707 insert remove)], 2728 insert remove inv name archname title slaying race)],
2708 ["cf::object::player" => qw(player)], 2729 ["cf::object::player" => qw(player)],
2709 ["cf::player" => qw(peaceful)], 2730 ["cf::player" => qw(peaceful)],
2710 ["cf::map" => qw(trigger)], 2731 ["cf::map" => qw(trigger)],
2711) { 2732) {
2712 no strict 'refs'; 2733 no strict 'refs';
2844 # TODO: for gcfclient pleasure, we should give resources 2865 # TODO: for gcfclient pleasure, we should give resources
2845 # that gcfclient doesn't grok a >10000 face index. 2866 # that gcfclient doesn't grok a >10000 face index.
2846 my $res = $facedata->{resource}; 2867 my $res = $facedata->{resource};
2847 my $enc = JSON::XS->new->utf8->canonical; 2868 my $enc = JSON::XS->new->utf8->canonical;
2848 2869
2870 my $soundconf = delete $res->{"res/sound.conf"};
2871
2849 while (my ($name, $info) = each %$res) { 2872 while (my ($name, $info) = each %$res) {
2850 my $meta = $enc->encode ({ 2873 my $meta = $enc->encode ({
2851 name => $name, 2874 name => $name,
2852 type => $info->{type}, 2875 %{ $info->{meta} || {} },
2853 copyright => $info->{copyright}, #TODO#
2854 }); 2876 });
2855 2877
2856 my $idx = (cf::face::find $name) || cf::face::alloc $name; 2878 my $idx = (cf::face::find $name) || cf::face::alloc $name;
2857 2879
2858 if ($name =~ /\.jpg$/) { 2880 if ($info->{type} & 1) {
2859 cf::face::set_data $idx, 0, $info->{data}, $info->{chksum};#d# temp hack 2881 # prepend meta info
2860 cf::face::set_data $idx, 1, $info->{data}, $info->{chksum};#d# temp hack 2882
2861 } else {
2862 my $data = pack "(w/a*)*", $meta, $info->{data}; 2883 my $data = pack "(w/a*)*", $meta, $info->{data};
2863 my $chk = Digest::MD5::md5 "$info->{chksum},$meta"; # mangle data checksum and metadata 2884 my $chk = Digest::MD5::md5 "$info->{chksum},$meta"; # mangle data checksum and metadata
2864 2885
2865 cf::face::set_type $idx, 1;
2866 cf::face::set_data $idx, 0, $data, $chk; 2886 cf::face::set_data $idx, 0, $data, $chk;
2887 } else {
2888 cf::face::set_data $idx, 0, $info->{data}, $info->{chksum};
2867 } 2889 }
2868 2890
2891 cf::face::set_type $idx, $info->{type};
2892
2869 cf::cede_to_tick; 2893 cf::cede_to_tick;
2870 } 2894 }
2895
2896 if ($soundconf) {
2897 $soundconf = $enc->decode (delete $soundconf->{data});
2898
2899 for (0 .. SOUND_CAST_SPELL_0 - 1) {
2900 my $sound = $soundconf->{compat}[$_]
2901 or next;
2902
2903 my $face = cf::face::find "sound/$sound->[1]";
2904 cf::sound::set $sound->[0] => $face;
2905 cf::sound::old_sound_index $_, $face; # gcfclient-compat
2906 }
2907
2908 while (my ($k, $v) = each %{$soundconf->{event}}) {
2909 my $face = cf::face::find "sound/$v";
2910 cf::sound::set $k => $face;
2911 }
2912 }
2871 } 2913 }
2872 2914
2873 1 2915 1
2874} 2916}
2917
2918register_exticmd fx_want => sub {
2919 my ($ns, $want) = @_;
2920
2921 while (my ($k, $v) = each %$want) {
2922 $ns->fx_want ($k, $v);
2923 }
2924};
2875 2925
2876sub reload_regions { 2926sub reload_regions {
2877 load_resource_file "$MAPDIR/regions" 2927 load_resource_file "$MAPDIR/regions"
2878 or die "unable to load regions file\n"; 2928 or die "unable to load regions file\n";
2879 2929

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines