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

Comparing deliantra/server/lib/cf.pm (file contents):
Revision 1.314 by root, Mon Jul 23 16:53:15 2007 UTC vs.
Revision 1.362 by root, Mon Sep 10 12:44:06 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 ();
26use BDB (); 27use BDB ();
27use Data::Dumper; 28use Data::Dumper;
28use Digest::MD5; 29use Digest::MD5;
29use Fcntl; 30use Fcntl;
30use YAML::Syck (); 31use YAML::Syck ();
86our %CFG; 87our %CFG;
87 88
88our $UPTIME; $UPTIME ||= time; 89our $UPTIME; $UPTIME ||= time;
89our $RUNTIME; 90our $RUNTIME;
90 91
91our %PLAYER; # all users 92our (%PLAYER, %PLAYER_LOADING); # all users
92our %MAP; # all maps 93our (%MAP, %MAP_LOADING ); # all maps
93our $LINK_MAP; # the special {link} map, which is always available 94our $LINK_MAP; # the special {link} map, which is always available
94 95
95# used to convert map paths into valid unix filenames by replacing / by ∕ 96# used to convert map paths into valid unix filenames by replacing / by ∕
96our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons 97our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
97 98
186 $msg .= "\n" 187 $msg .= "\n"
187 unless $msg =~ /\n$/; 188 unless $msg =~ /\n$/;
188 189
189 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge; 190 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
190 191
191 utf8::encode $msg;
192 LOG llevError, $msg; 192 LOG llevError, $msg;
193 }; 193 };
194} 194}
195 195
196@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 196@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
271Wait until the given lock is available and then acquires it and returns 271Wait until the given lock is available and then acquires it and returns
272a Coro::guard object. If the guard object gets destroyed (goes out of scope, 272a Coro::guard object. If the guard object gets destroyed (goes out of scope,
273for example when the coroutine gets canceled), the lock is automatically 273for example when the coroutine gets canceled), the lock is automatically
274returned. 274returned.
275 275
276Locks are *not* recursive, locking from the same coro twice results in a
277deadlocked coro.
278
276Lock names should begin with a unique identifier (for example, cf::map::find 279Lock names should begin with a unique identifier (for example, cf::map::find
277uses map_find and cf::map::load uses map_load). 280uses map_find and cf::map::load uses map_load).
278 281
279=item $locked = cf::lock_active $string 282=item $locked = cf::lock_active $string
280 283
281Return true if the lock is currently active, i.e. somebody has locked it. 284Return true if the lock is currently active, i.e. somebody has locked it.
282 285
283=cut 286=cut
284 287
285our %LOCK; 288our %LOCK;
289our %LOCKER;#d#
286 290
287sub lock_wait($) { 291sub lock_wait($) {
288 my ($key) = @_; 292 my ($key) = @_;
293
294 if ($LOCKER{$key} == $Coro::current) {#d#
295 Carp::cluck "lock_wait($key) for already-acquired lock";#d#
296 return;#d#
297 }#d#
289 298
290 # wait for lock, if any 299 # wait for lock, if any
291 while ($LOCK{$key}) { 300 while ($LOCK{$key}) {
292 push @{ $LOCK{$key} }, $Coro::current; 301 push @{ $LOCK{$key} }, $Coro::current;
293 Coro::schedule; 302 Coro::schedule;
299 308
300 # wait, to be sure we are not locked 309 # wait, to be sure we are not locked
301 lock_wait $key; 310 lock_wait $key;
302 311
303 $LOCK{$key} = []; 312 $LOCK{$key} = [];
313 $LOCKER{$key} = $Coro::current;#d#
304 314
305 Coro::guard { 315 Coro::guard {
316 delete $LOCKER{$key};#d#
306 # wake up all waiters, to be on the safe side 317 # wake up all waiters, to be on the safe side
307 $_->ready for @{ delete $LOCK{$key} }; 318 $_->ready for @{ delete $LOCK{$key} };
308 } 319 }
309} 320}
310 321
322 }; 333 };
323 $TICK_WATCHER->stop; 334 $TICK_WATCHER->stop;
324 $guard 335 $guard
325} 336}
326 337
327=item cf::get_slot $time[, $priority] 338=item cf::get_slot $time[, $priority[, $name]]
328 339
329Allocate $time seconds of blocking CPU time at priority C<$priority>: 340Allocate $time seconds of blocking CPU time at priority C<$priority>:
330This call blocks and returns only when you have at least C<$time> seconds 341This 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. 342of cpu time till the next tick. The slot is only valid till the next cede.
343
344The optional C<$name> can be used to identify the job to run. It might be
345used for statistical purposes and should identify the same time-class.
332 346
333Useful for short background jobs. 347Useful for short background jobs.
334 348
335=cut 349=cut
336 350
363 Coro::schedule; 377 Coro::schedule;
364 } 378 }
365 } 379 }
366}; 380};
367 381
368sub get_slot($;$) { 382sub get_slot($;$$) {
369 my ($time, $pri) = @_; 383 my ($time, $pri, $name) = @_;
370 384
385 $time = $TICK * .6 if $time > $TICK * .6;
386 my $sig = new Coro::Signal;
387
371 push @SLOT_QUEUE, [$time, $pri, my $sig = new Coro::Signal]; 388 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
372 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE; 389 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
373 $SLOT_QUEUE->ready; 390 $SLOT_QUEUE->ready;
374 $sig->wait; 391 $sig->wait;
375} 392}
376 393
466Coro::Storable. May, of course, block. Note that the executed sub may 483Coro::Storable. May, of course, block. Note that the executed sub may
467never block itself or use any form of Event handling. 484never block itself or use any form of Event handling.
468 485
469=cut 486=cut
470 487
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(&@) { 488sub fork_call(&@) {
481 my ($cb, @args) = @_; 489 my ($cb, @args) = @_;
482 490
483# socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC 491 # we seemingly have to make a local copy of the whole thing,
484# or die "socketpair: $!"; 492 # otherwise perl prematurely frees the stuff :/
485 pipe my $fh1, my $fh2 493 # TODO: investigate and fix (likely this will be rather laborious)
486 or die "pipe: $!";
487 494
488 if (my $pid = fork) { 495 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; 496 reset_signals;
502 local $SIG{__WARN__}; 497 &$cb
503 local $SIG{__DIE__}; 498 }, @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 499
510 eval { 500 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} 501}
523 502
524=item $value = cf::db_get $family => $key 503=item $value = cf::db_get $family => $key
525 504
526Returns a single value from the environment database. 505Returns a single value from the environment database.
606 if (1) { 585 if (1) {
607 $md5 = 586 $md5 =
608 join "\x00", 587 join "\x00",
609 $processversion, 588 $processversion,
610 map { 589 map {
611 Coro::cede; 590 cf::cede_to_tick;
612 ($src->[$_], Digest::MD5::md5_hex $data[$_]) 591 ($src->[$_], Digest::MD5::md5_hex $data[$_])
613 } 0.. $#$src; 592 } 0.. $#$src;
614 593
615 594
616 my $dbmd5 = db_get cache => "$id/md5"; 595 my $dbmd5 = db_get cache => "$id/md5";
1033); 1012);
1034 1013
1035sub object_freezer_save { 1014sub object_freezer_save {
1036 my ($filename, $rdata, $objs) = @_; 1015 my ($filename, $rdata, $objs) = @_;
1037 1016
1017 my $guard = cf::lock_acquire "io";
1018
1038 sync_job { 1019 sync_job {
1039 if (length $$rdata) { 1020 if (length $$rdata) {
1021 utf8::decode (my $decname = $filename);
1040 warn sprintf "saving %s (%d,%d)\n", 1022 warn sprintf "saving %s (%d,%d)\n",
1041 $filename, length $$rdata, scalar @$objs; 1023 $decname, length $$rdata, scalar @$objs;
1042 1024
1043 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { 1025 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
1044 chmod SAVE_MODE, $fh; 1026 chmod SAVE_MODE, $fh;
1045 aio_write $fh, 0, (length $$rdata), $$rdata, 0; 1027 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1046 aio_fsync $fh if $cf::USE_FSYNC; 1028 aio_fsync $fh if $cf::USE_FSYNC;
1065 } 1047 }
1066 } else { 1048 } else {
1067 aio_unlink $filename; 1049 aio_unlink $filename;
1068 aio_unlink "$filename.pst"; 1050 aio_unlink "$filename.pst";
1069 } 1051 }
1070 } 1052 };
1053
1054 undef $guard;
1071} 1055}
1072 1056
1073sub object_freezer_as_string { 1057sub object_freezer_as_string {
1074 my ($rdata, $objs) = @_; 1058 my ($rdata, $objs) = @_;
1075 1059
1080 1064
1081sub object_thawer_load { 1065sub object_thawer_load {
1082 my ($filename) = @_; 1066 my ($filename) = @_;
1083 1067
1084 my ($data, $av); 1068 my ($data, $av);
1069
1070 my $guard = cf::lock_acquire "io";
1085 1071
1086 (aio_load $filename, $data) >= 0 1072 (aio_load $filename, $data) >= 0
1087 or return; 1073 or return;
1088 1074
1089 unless (aio_stat "$filename.pst") { 1075 unless (aio_stat "$filename.pst") {
1090 (aio_load "$filename.pst", $av) >= 0 1076 (aio_load "$filename.pst", $av) >= 0
1091 or return; 1077 or return;
1078
1079 undef $guard;
1092 $av = eval { (Storable::thaw $av)->{objs} }; 1080 $av = eval { (Storable::thaw $av)->{objs} };
1093 } 1081 }
1094 1082
1083 utf8::decode (my $decname = $filename);
1095 warn sprintf "loading %s (%d)\n", 1084 warn sprintf "loading %s (%d,%d)\n",
1096 $filename, length $data, scalar @{$av || []}; 1085 $decname, length $data, scalar @{$av || []};
1086
1097 return ($data, $av); 1087 ($data, $av)
1098} 1088}
1099 1089
1100=head2 COMMAND CALLBACKS 1090=head2 COMMAND CALLBACKS
1101 1091
1102=over 4 1092=over 4
1175 my ($pl, $buf) = @_; 1165 my ($pl, $buf) = @_;
1176 1166
1177 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) }; 1167 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1178 1168
1179 if (ref $msg) { 1169 if (ref $msg) {
1170 my ($type, $reply, @payload) =
1171 "ARRAY" eq ref $msg
1172 ? @$msg
1173 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1174
1175 my @reply;
1176
1180 if (my $cb = $EXTCMD{$msg->{msgtype}}) { 1177 if (my $cb = $EXTCMD{$type}) {
1181 if (my %reply = $cb->($pl, $msg)) { 1178 @reply = $cb->($pl, @payload);
1182 $pl->ext_reply ($msg->{msgid}, %reply);
1183 }
1184 } 1179 }
1180
1181 $pl->ext_reply ($reply, @reply)
1182 if $reply;
1183
1185 } else { 1184 } else {
1186 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1185 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1187 } 1186 }
1188 1187
1189 cf::override; 1188 cf::override;
1279 1278
1280=head3 cf::player 1279=head3 cf::player
1281 1280
1282=over 4 1281=over 4
1283 1282
1283=item cf::player::num_playing
1284
1285Returns the official number of playing players, as per the Crossfire metaserver rules.
1286
1287=cut
1288
1289sub num_playing {
1290 scalar grep
1291 $_->ob->map
1292 && !$_->hidden
1293 && !$_->ob->flag (cf::FLAG_WIZ),
1294 cf::player::list
1295}
1296
1284=item cf::player::find $login 1297=item cf::player::find $login
1285 1298
1286Returns the given player object, loading it if necessary (might block). 1299Returns the given player object, loading it if necessary (might block).
1287 1300
1288=cut 1301=cut
1323 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst"; 1336 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst";
1324 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata"; 1337 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata";
1325 aio_unlink +(playerdir $login) . "/$login.pl.pst"; 1338 aio_unlink +(playerdir $login) . "/$login.pl.pst";
1326 aio_unlink +(playerdir $login) . "/$login.pl"; 1339 aio_unlink +(playerdir $login) . "/$login.pl";
1327 1340
1328 my $pl = load_pl path $login 1341 my $f = new_from_file cf::object::thawer path $login
1329 or return; 1342 or return;
1343
1344 $f->next;
1345 my $pl = cf::player::load_pl $f
1346 or return;
1347 local $cf::PLAYER_LOADING{$login} = $pl;
1348 $f->resolve_delayed_derefs;
1330 $cf::PLAYER{$login} = $pl 1349 $cf::PLAYER{$login} = $pl
1331 } 1350 }
1332 } 1351 }
1333} 1352}
1334 1353
1344 1363
1345 aio_mkdir playerdir $pl, 0770; 1364 aio_mkdir playerdir $pl, 0770;
1346 $pl->{last_save} = $cf::RUNTIME; 1365 $pl->{last_save} = $cf::RUNTIME;
1347 1366
1348 $pl->save_pl ($path); 1367 $pl->save_pl ($path);
1349 Coro::cede; 1368 cf::cede_to_tick;
1350} 1369}
1351 1370
1352sub new($) { 1371sub new($) {
1353 my ($login) = @_; 1372 my ($login) = @_;
1354 1373
1358 $self->{deny_save} = 1; 1377 $self->{deny_save} = 1;
1359 1378
1360 $cf::PLAYER{$login} = $self; 1379 $cf::PLAYER{$login} = $self;
1361 1380
1362 $self 1381 $self
1382}
1383
1384=item $player->send_msg ($channel, $msg, $color, [extra...])
1385
1386=cut
1387
1388sub send_msg {
1389 my $ns = shift->ns
1390 or return;
1391 $ns->send_msg (@_);
1363} 1392}
1364 1393
1365=item $pl->quit_character 1394=item $pl->quit_character
1366 1395
1367Nukes the player without looking back. If logged in, the connection will 1396Nukes the player without looking back. If logged in, the connection will
1422 or return []; 1451 or return [];
1423 1452
1424 my @logins; 1453 my @logins;
1425 1454
1426 for my $login (@$dirs) { 1455 for my $login (@$dirs) {
1456 my $path = path $login;
1457
1458 # a .pst is a dead give-away for a valid player
1459 unless (-e "$path.pst") {
1427 my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next; 1460 my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
1428 aio_read $fh, 0, 512, my $buf, 0 or next; 1461 aio_read $fh, 0, 512, my $buf, 0 or next;
1429 $buf !~ /^password -------------$/m or next; # official not-valid tag 1462 $buf !~ /^password -------------$/m or next; # official not-valid tag
1463 }
1430 1464
1431 utf8::decode $login; 1465 utf8::decode $login;
1432 push @logins, $login; 1466 push @logins, $login;
1433 } 1467 }
1434 1468
1472sub expand_cfpod { 1506sub expand_cfpod {
1473 ((my $self), (local $_)) = @_; 1507 ((my $self), (local $_)) = @_;
1474 1508
1475 # escape & and < 1509 # escape & and <
1476 s/&/&amp;/g; 1510 s/&/&amp;/g;
1477 s/(?<![BIUGH])</&lt;/g; 1511 s/(?<![BIUGHT])</&lt;/g;
1478 1512
1479 # this is buggy, it needs to properly take care of nested <'s 1513 # this is buggy, it needs to properly take care of nested <'s
1480 1514
1481 1 while 1515 1 while
1482 # replace B<>, I<>, U<> etc. 1516 # replace B<>, I<>, U<> etc.
1483 s/B<([^\>]*)>/<b>$1<\/b>/ 1517 s/B<([^\>]*)>/<b>$1<\/b>/
1484 || s/I<([^\>]*)>/<i>$1<\/i>/ 1518 || s/I<([^\>]*)>/<i>$1<\/i>/
1485 || s/U<([^\>]*)>/<u>$1<\/u>/ 1519 || s/U<([^\>]*)>/<u>$1<\/u>/
1520 || s/T<([^\>]*)>/<big><b>$1<\/b><\/big>/
1486 # replace G<male|female> tags 1521 # replace G<male|female> tags
1487 || s{G<([^>|]*)\|([^>]*)>}{ 1522 || s{G<([^>|]*)\|([^>]*)>}{
1488 $self->gender ? $2 : $1 1523 $self->gender ? $2 : $1
1489 }ge 1524 }ge
1490 # replace H<hint text> 1525 # replace H<hint text>
1511sub hintmode { 1546sub hintmode {
1512 $_[0]{hintmode} = $_[1] if @_ > 1; 1547 $_[0]{hintmode} = $_[1] if @_ > 1;
1513 $_[0]{hintmode} 1548 $_[0]{hintmode}
1514} 1549}
1515 1550
1516=item $player->ext_reply ($msgid, %msg) 1551=item $player->ext_reply ($msgid, @msg)
1517 1552
1518Sends an ext reply to the player. 1553Sends an ext reply to the player.
1519 1554
1520=cut 1555=cut
1521 1556
1522sub ext_reply($$%) { 1557sub ext_reply($$@) {
1523 my ($self, $id, %msg) = @_; 1558 my ($self, $id, @msg) = @_;
1524 1559
1525 $msg{msgid} = $id; 1560 $self->ns->ext_reply ($id, @msg)
1526 $self->send ("ext " . $self->ns->{json_coder}->encode (\%msg));
1527} 1561}
1528 1562
1529=item $player->ext_event ($type, %msg) 1563=item $player->ext_msg ($type, @msg)
1530 1564
1531Sends an ext event to the client. 1565Sends an ext event to the client.
1532 1566
1533=cut 1567=cut
1534 1568
1535sub ext_event($$%) { 1569sub ext_msg($$@) {
1536 my ($self, $type, %msg) = @_; 1570 my ($self, $type, @msg) = @_;
1537 1571
1538 $self->ns->ext_event ($type, %msg); 1572 $self->ns->ext_msg ($type, @msg);
1539} 1573}
1540 1574
1541=head3 cf::region 1575=head3 cf::region
1542 1576
1543=over 4 1577=over 4
1759 1793
1760sub load_header_from($) { 1794sub load_header_from($) {
1761 my ($self, $path) = @_; 1795 my ($self, $path) = @_;
1762 1796
1763 utf8::encode $path; 1797 utf8::encode $path;
1764 #aio_open $path, O_RDONLY, 0 1798 my $f = new_from_file cf::object::thawer $path
1765 # or return;
1766
1767 $self->_load_header ($path)
1768 or return; 1799 or return;
1800
1801 $self->_load_header ($f)
1802 or return;
1803
1804 local $MAP_LOADING{$self->{path}} = $self;
1805 $f->resolve_delayed_derefs;
1769 1806
1770 $self->{load_path} = $path; 1807 $self->{load_path} = $path;
1771 1808
1772 1 1809 1
1773} 1810}
1827sub find { 1864sub find {
1828 my ($path, $origin) = @_; 1865 my ($path, $origin) = @_;
1829 1866
1830 $path = normalise $path, $origin && $origin->path; 1867 $path = normalise $path, $origin && $origin->path;
1831 1868
1869 cf::lock_wait "map_data:$path";#d#remove
1832 cf::lock_wait "map_find:$path"; 1870 cf::lock_wait "map_find:$path";
1833 1871
1834 $cf::MAP{$path} || do { 1872 $cf::MAP{$path} || do {
1835 my $guard = cf::lock_acquire "map_find:$path"; 1873 my $guard1 = cf::lock_acquire "map_find:$path";
1874 my $guard2 = cf::lock_acquire "map_data:$path"; # just for the fun of it
1875
1836 my $map = new_from_path cf::map $path 1876 my $map = new_from_path cf::map $path
1837 or return; 1877 or return;
1838 1878
1839 $map->{last_save} = $cf::RUNTIME; 1879 $map->{last_save} = $cf::RUNTIME;
1840 1880
1842 or return; 1882 or return;
1843 1883
1844 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?) 1884 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
1845 # doing this can freeze the server in a sync job, obviously 1885 # doing this can freeze the server in a sync job, obviously
1846 #$cf::WAIT_FOR_TICK->wait; 1886 #$cf::WAIT_FOR_TICK->wait;
1887 undef $guard1;
1888 undef $guard2;
1847 $map->reset; 1889 $map->reset;
1848 undef $guard;
1849 return find $path; 1890 return find $path;
1850 } 1891 }
1851 1892
1852 $cf::MAP{$path} = $map 1893 $cf::MAP{$path} = $map
1853 } 1894 }
1862 local $self->{deny_reset} = 1; # loading can take a long time 1903 local $self->{deny_reset} = 1; # loading can take a long time
1863 1904
1864 my $path = $self->{path}; 1905 my $path = $self->{path};
1865 1906
1866 { 1907 {
1867 my $guard = cf::lock_acquire "map_load:$path"; 1908 my $guard = cf::lock_acquire "map_data:$path";
1868 1909
1910 return unless $self->valid;
1869 return if $self->in_memory != cf::MAP_SWAPPED; 1911 return unless $self->in_memory == cf::MAP_SWAPPED;
1870 1912
1871 $self->in_memory (cf::MAP_LOADING); 1913 $self->in_memory (cf::MAP_LOADING);
1872 1914
1873 $self->alloc; 1915 $self->alloc;
1874 1916
1875 $self->pre_load; 1917 $self->pre_load;
1876 Coro::cede; 1918 cf::cede_to_tick;
1877 1919
1920 my $f = new_from_file cf::object::thawer $self->{load_path};
1921 $f->skip_block;
1878 $self->_load_objects ($self->{load_path}, 1) 1922 $self->_load_objects ($f)
1879 or return; 1923 or return;
1880 1924
1881 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) 1925 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1882 if delete $self->{load_original}; 1926 if delete $self->{load_original};
1883 1927
1884 if (my $uniq = $self->uniq_path) { 1928 if (my $uniq = $self->uniq_path) {
1885 utf8::encode $uniq; 1929 utf8::encode $uniq;
1886 if (aio_open $uniq, O_RDONLY, 0) { 1930 unless (aio_stat $uniq) {
1931 if (my $f = new_from_file cf::object::thawer $uniq) {
1887 $self->clear_unique_items; 1932 $self->clear_unique_items;
1888 $self->_load_objects ($uniq, 0); 1933 $self->_load_objects ($f);
1934 $f->resolve_delayed_derefs;
1935 }
1889 } 1936 }
1890 } 1937 }
1891 1938
1892 Coro::cede; 1939 $f->resolve_delayed_derefs;
1940
1941 cf::cede_to_tick;
1893 # now do the right thing for maps 1942 # now do the right thing for maps
1894 $self->link_multipart_objects; 1943 $self->link_multipart_objects;
1895 $self->difficulty ($self->estimate_difficulty) 1944 $self->difficulty ($self->estimate_difficulty)
1896 unless $self->difficulty; 1945 unless $self->difficulty;
1897 Coro::cede; 1946 cf::cede_to_tick;
1898 1947
1899 unless ($self->{deny_activate}) { 1948 unless ($self->{deny_activate}) {
1900 $self->decay_objects; 1949 $self->decay_objects;
1901 $self->fix_auto_apply; 1950 $self->fix_auto_apply;
1902 $self->update_buttons; 1951 $self->update_buttons;
1903 Coro::cede; 1952 cf::cede_to_tick;
1904 $self->set_darkness_map; 1953 $self->set_darkness_map;
1905 Coro::cede; 1954 cf::cede_to_tick;
1906 $self->activate; 1955 $self->activate;
1907 } 1956 }
1957
1958 $self->{last_save} = $cf::RUNTIME;
1959 $self->last_access ($cf::RUNTIME);
1908 1960
1909 $self->in_memory (cf::MAP_IN_MEMORY); 1961 $self->in_memory (cf::MAP_IN_MEMORY);
1910 } 1962 }
1911 1963
1912 $self->post_load; 1964 $self->post_load;
1923 1975
1924 $self 1976 $self
1925} 1977}
1926 1978
1927# find and load all maps in the 3x3 area around a map 1979# find and load all maps in the 3x3 area around a map
1928sub load_diag { 1980sub load_neighbours {
1929 my ($map) = @_; 1981 my ($map) = @_;
1930 1982
1931 my @diag; # diagonal neighbours 1983 my @neigh; # diagonal neighbours
1932 1984
1933 for (0 .. 3) { 1985 for (0 .. 3) {
1934 my $neigh = $map->tile_path ($_) 1986 my $neigh = $map->tile_path ($_)
1935 or next; 1987 or next;
1936 $neigh = find $neigh, $map 1988 $neigh = find $neigh, $map
1937 or next; 1989 or next;
1938 $neigh->load; 1990 $neigh->load;
1939 1991
1992 push @neigh,
1940 push @diag, [$neigh->tile_path (($_ + 3) % 4), $neigh], 1993 [$neigh->tile_path (($_ + 3) % 4), $neigh],
1941 [$neigh->tile_path (($_ + 1) % 4), $neigh]; 1994 [$neigh->tile_path (($_ + 1) % 4), $neigh];
1942 } 1995 }
1943 1996
1944 for (@diag) { 1997 for (grep defined $_->[0], @neigh) {
1998 my ($path, $origin) = @$_;
1945 my $neigh = find @$_ 1999 my $neigh = find $path, $origin
1946 or next; 2000 or next;
1947 $neigh->load; 2001 $neigh->load;
1948 } 2002 }
1949} 2003}
1950 2004
1955} 2009}
1956 2010
1957sub do_load_sync { 2011sub do_load_sync {
1958 my ($map) = @_; 2012 my ($map) = @_;
1959 2013
2014 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync"
2015 if $Coro::current == $Coro::main;
2016
1960 cf::sync_job { $map->load }; 2017 cf::sync_job { $map->load };
1961} 2018}
1962 2019
1963our %MAP_PREFETCH; 2020our %MAP_PREFETCH;
1964our $MAP_PREFETCHER = undef; 2021our $MAP_PREFETCHER = undef;
1965 2022
1966sub find_async { 2023sub find_async {
1967 my ($path, $origin) = @_; 2024 my ($path, $origin, $load) = @_;
1968 2025
1969 $path = normalise $path, $origin && $origin->{path}; 2026 $path = normalise $path, $origin && $origin->{path};
1970 2027
1971 if (my $map = $cf::MAP{$path}) { 2028 if (my $map = $cf::MAP{$path}) {
1972 return $map if $map->in_memory == cf::MAP_IN_MEMORY; 2029 return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
1973 } 2030 }
1974 2031
1975 undef $MAP_PREFETCH{$path}; 2032 $MAP_PREFETCH{$path} |= $load;
2033
1976 $MAP_PREFETCHER ||= cf::async { 2034 $MAP_PREFETCHER ||= cf::async {
1977 while (%MAP_PREFETCH) { 2035 while (%MAP_PREFETCH) {
1978 for my $path (keys %MAP_PREFETCH) { 2036 while (my ($k, $v) = each %MAP_PREFETCH) {
1979 if (my $map = find $path) { 2037 if (my $map = find $k) {
1980 $map->load; 2038 $map->load if $v;
1981 } 2039 }
1982 2040
1983 delete $MAP_PREFETCH{$path}; 2041 delete $MAP_PREFETCH{$k};
1984 } 2042 }
1985 } 2043 }
1986 undef $MAP_PREFETCHER; 2044 undef $MAP_PREFETCHER;
1987 }; 2045 };
1988 $MAP_PREFETCHER->prio (6); 2046 $MAP_PREFETCHER->prio (6);
1991} 2049}
1992 2050
1993sub save { 2051sub save {
1994 my ($self) = @_; 2052 my ($self) = @_;
1995 2053
1996 my $lock = cf::lock_acquire "map_data:" . $self->path; 2054 my $lock = cf::lock_acquire "map_data:$self->{path}";
1997 2055
1998 $self->{last_save} = $cf::RUNTIME; 2056 $self->{last_save} = $cf::RUNTIME;
1999 2057
2000 return unless $self->dirty; 2058 return unless $self->dirty;
2001 2059
2024 my ($self) = @_; 2082 my ($self) = @_;
2025 2083
2026 # save first because save cedes 2084 # save first because save cedes
2027 $self->save; 2085 $self->save;
2028 2086
2029 my $lock = cf::lock_acquire "map_data:" . $self->path; 2087 my $lock = cf::lock_acquire "map_data:$self->{path}";
2030 2088
2031 return if $self->players; 2089 return if $self->players;
2032 return if $self->in_memory != cf::MAP_IN_MEMORY; 2090 return if $self->in_memory != cf::MAP_IN_MEMORY;
2033 return if $self->{deny_save}; 2091 return if $self->{deny_save};
2034 2092
2093 $self->in_memory (cf::MAP_SWAPPED);
2094
2095 $self->deactivate;
2096 $_->clear_links_to ($self) for values %cf::MAP;
2035 $self->clear; 2097 $self->clear;
2036 $self->in_memory (cf::MAP_SWAPPED);
2037} 2098}
2038 2099
2039sub reset_at { 2100sub reset_at {
2040 my ($self) = @_; 2101 my ($self) = @_;
2041 2102
2073 if $uniq; 2134 if $uniq;
2074 } 2135 }
2075 2136
2076 delete $cf::MAP{$self->path}; 2137 delete $cf::MAP{$self->path};
2077 2138
2139 $self->deactivate;
2140 $_->clear_links_to ($self) for values %cf::MAP;
2078 $self->clear; 2141 $self->clear;
2079
2080 $_->clear_links_to ($self) for values %cf::MAP;
2081 2142
2082 $self->unlink_save; 2143 $self->unlink_save;
2083 $self->destroy; 2144 $self->destroy;
2084} 2145}
2085 2146
2086my $nuke_counter = "aaaa"; 2147my $nuke_counter = "aaaa";
2087 2148
2088sub nuke { 2149sub nuke {
2089 my ($self) = @_; 2150 my ($self) = @_;
2090 2151
2152 {
2153 my $lock = cf::lock_acquire "map_data:$self->{path}";
2154
2091 delete $cf::MAP{$self->path}; 2155 delete $cf::MAP{$self->path};
2092 2156
2093 $self->unlink_save; 2157 $self->unlink_save;
2094 2158
2095 bless $self, "cf::map"; 2159 bless $self, "cf::map";
2096 delete $self->{deny_reset}; 2160 delete $self->{deny_reset};
2097 $self->{deny_save} = 1; 2161 $self->{deny_save} = 1;
2098 $self->reset_timeout (1); 2162 $self->reset_timeout (1);
2099 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++)); 2163 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2100 2164
2101 $cf::MAP{$self->path} = $self; 2165 $cf::MAP{$self->path} = $self;
2166 }
2102 2167
2103 $self->reset; # polite request, might not happen 2168 $self->reset; # polite request, might not happen
2104} 2169}
2105 2170
2106=item $maps = cf::map::tmp_maps 2171=item $maps = cf::map::tmp_maps
2182 2247
2183sub inv_recursive { 2248sub inv_recursive {
2184 inv_recursive_ inv $_[0] 2249 inv_recursive_ inv $_[0]
2185} 2250}
2186 2251
2252=item $ref = $ob->ref
2253
2254creates and returns a persistent reference to an objetc that can be stored as a string.
2255
2256=item $ob = cf::object::deref ($refstring)
2257
2258returns the objetc referenced by refstring. may return undef when it cnanot find the object,
2259even if the object actually exists. May block.
2260
2261=cut
2262
2263sub deref {
2264 my ($ref) = @_;
2265
2266 # temporary compatibility#TODO#remove
2267 $ref =~ s{^<}{player/<};
2268
2269 if ($ref =~ m{^player\/(<1\.\d+>)/(.*)$}) {
2270 my ($uuid, $name) = ($1, $2);
2271 my $pl = $cf::PLAYER_LOADING{$name} || cf::player::find $name
2272 or return;
2273 $pl->ob->uuid eq $uuid
2274 or return;
2275
2276 $pl->ob
2277 } else {
2278 warn "$ref: cannot resolve object reference\n";
2279 undef
2280 }
2281}
2282
2187package cf; 2283package cf;
2188 2284
2189=back 2285=back
2190 2286
2191=head3 cf::object::player 2287=head3 cf::object::player
2213 2309
2214 } else { 2310 } else {
2215 my $pl = $self->contr; 2311 my $pl = $self->contr;
2216 2312
2217 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) { 2313 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2218 my $diag = $pl->{npc_dialog}; 2314 my $dialog = $pl->{npc_dialog};
2219 $diag->{pl}->ext_reply ( 2315 $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 2316
2226 } else { 2317 } else {
2227 $msg = $npc->name . " says: $msg" if $npc; 2318 $msg = $npc->name . " says: $msg" if $npc;
2228 $self->message ($msg, $flags); 2319 $self->message ($msg, $flags);
2229 } 2320 }
2230 } 2321 }
2322}
2323
2324=item $object->send_msg ($channel, $msg, $color, [extra...])
2325
2326=cut
2327
2328sub cf::object::send_msg {
2329 my $pl = shift->contr
2330 or return;
2331 $pl->send_msg (@_);
2231} 2332}
2232 2333
2233=item $player_object->may ("access") 2334=item $player_object->may ("access")
2234 2335
2235Returns wether the given player is authorized to access resource "access" 2336Returns wether the given player is authorized to access resource "access"
2314 # use -1 or undef as default coordinates, not 0, 0 2415 # use -1 or undef as default coordinates, not 0, 0
2315 ($x, $y) = ($map->enter_x, $map->enter_y) 2416 ($x, $y) = ($map->enter_x, $map->enter_y)
2316 if $x <=0 && $y <= 0; 2417 if $x <=0 && $y <= 0;
2317 2418
2318 $map->load; 2419 $map->load;
2319 $map->load_diag; 2420 $map->load_neighbours;
2320 2421
2321 return unless $self->contr->active; 2422 return unless $self->contr->active;
2322 $self->activate_recursive; 2423 $self->activate_recursive;
2323 2424
2324 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext 2425 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2495the message, with C<log> being the default. If C<$color> is negative, suppress 2596the message, with C<log> being the default. If C<$color> is negative, suppress
2496the message unless the client supports the msg packet. 2597the message unless the client supports the msg packet.
2497 2598
2498=cut 2599=cut
2499 2600
2601our %CHANNEL = (
2602 "c/identify" => {
2603 id => "identify",
2604 title => "Identify",
2605 reply => undef,
2606 tooltip => "Items recently identified",
2607 },
2608 "c/examine" => {
2609 id => "examine",
2610 title => "Examine",
2611 reply => undef,
2612 tooltip => "Signs and other items you examined",
2613 },
2614);
2615
2500sub cf::client::send_msg { 2616sub cf::client::send_msg {
2501 my ($self, $channel, $msg, $color, @extra) = @_; 2617 my ($self, $channel, $msg, $color, @extra) = @_;
2502 2618
2503 $msg = $self->pl->expand_cfpod ($msg); 2619 $msg = $self->pl->expand_cfpod ($msg);
2504 2620
2505 $color &= ~cf::NDI_UNIQUE; # just in case... 2621 $color &= cf::NDI_CLIENT_MASK; # just in case...
2622
2623 # check predefined channels, for the benefit of C
2624 $channel = $CHANNEL{$channel} if $CHANNEL{$channel};
2506 2625
2507 if (ref $channel) { 2626 if (ref $channel) {
2508 # send meta info to client, if not yet sent 2627 # send meta info to client, if not yet sent
2509 unless (exists $self->{channel}{$channel->{id}}) { 2628 unless (exists $self->{channel}{$channel->{id}}) {
2510 $self->{channel}{$channel->{id}} = $channel; 2629 $self->{channel}{$channel->{id}} = $channel;
2511 $self->ext_event (channel_info => %$channel); 2630 $self->ext_msg (channel_info => $channel)
2631 if $self->can_msg;
2512 } 2632 }
2513 2633
2514 $channel = $channel->{id}; 2634 $channel = $channel->{id};
2515 } 2635 }
2516 2636
2517 return unless @extra || length $msg; 2637 return unless @extra || length $msg;
2518 2638
2519 if ($self->can_msg) { 2639 if ($self->can_msg) {
2640 # default colour, mask it out
2641 $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
2642 if $color & cf::NDI_DEF;
2643
2520 $self->send_packet ("msg " . $self->{json_coder}->encode ([$color, $channel, $msg, @extra])); 2644 $self->send_packet ("msg " . $self->{json_coder}->encode (
2645 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
2521 } else { 2646 } 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) { 2647 if ($color >= 0) {
2648 # replace some tags by gcfclient-compatible ones
2649 for ($msg) {
2650 1 while
2651 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2652 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2653 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2654 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2655 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2656 }
2657
2658 $color &= cf::NDI_COLOR_MASK;
2659
2660 utf8::encode $msg;
2661
2533 if (0 && $msg =~ /\[/) { 2662 if (0 && $msg =~ /\[/) {
2663 # COMMAND/INFO
2534 $self->send_packet ("drawextinfo $color 4 0 $msg") 2664 $self->send_packet ("drawextinfo $color 10 8 $msg")
2535 } else { 2665 } else {
2536 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g; 2666 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2537 $self->send_packet ("drawinfo $color $msg") 2667 $self->send_packet ("drawinfo $color $msg")
2538 } 2668 }
2539 } 2669 }
2540 } 2670 }
2541} 2671}
2542 2672
2543=item $client->ext_event ($type, %msg) 2673=item $client->ext_msg ($type, @msg)
2544 2674
2545Sends an ext event to the client. 2675Sends an ext event to the client.
2546 2676
2547=cut 2677=cut
2548 2678
2549sub cf::client::ext_event($$%) { 2679sub cf::client::ext_msg($$@) {
2550 my ($self, $type, %msg) = @_; 2680 my ($self, $type, @msg) = @_;
2551 2681
2552 return unless $self->extcmd; 2682 if ($self->extcmd == 2) {
2553 2683 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2684 } elsif ($self->extcmd == 1) { # TODO: remove
2554 $msg{msgtype} = "event_$type"; 2685 push @msg, msgtype => "event_$type";
2555 $self->send_packet ("ext " . $self->{json_coder}->encode (\%msg)); 2686 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2687 }
2688}
2689
2690=item $client->ext_reply ($msgid, @msg)
2691
2692Sends an ext reply to the client.
2693
2694=cut
2695
2696sub cf::client::ext_reply($$@) {
2697 my ($self, $id, @msg) = @_;
2698
2699 if ($self->extcmd == 2) {
2700 $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2701 } elsif ($self->extcmd == 1) {
2702 #TODO: version 1, remove
2703 unshift @msg, msgtype => "reply", msgid => $id;
2704 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2705 }
2556} 2706}
2557 2707
2558=item $success = $client->query ($flags, "text", \&cb) 2708=item $success = $client->query ($flags, "text", \&cb)
2559 2709
2560Queues a query to the client, calling the given callback with 2710Queues a query to the client, calling the given callback with
2615 my ($ns, $buf) = @_; 2765 my ($ns, $buf) = @_;
2616 2766
2617 my $msg = eval { $ns->{json_coder}->decode ($buf) }; 2767 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2618 2768
2619 if (ref $msg) { 2769 if (ref $msg) {
2770 my ($type, $reply, @payload) =
2771 "ARRAY" eq ref $msg
2772 ? @$msg
2773 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2774
2775 my @reply;
2776
2620 if (my $cb = $EXTICMD{$msg->{msgtype}}) { 2777 if (my $cb = $EXTICMD{$type}) {
2621 if (my %reply = $cb->($ns, $msg)) { 2778 @reply = $cb->($ns, @payload);
2622 $reply{msgid} = $msg->{msgid};
2623 $ns->send ("ext " . $ns->{json_coder}->encode (\%reply));
2624 }
2625 } 2779 }
2780
2781 $ns->ext_reply ($reply, @reply)
2782 if $reply;
2783
2626 } else { 2784 } else {
2627 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n"; 2785 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2628 } 2786 }
2629 2787
2630 cf::override; 2788 cf::override;
2677our $safe = new Safe "safe"; 2835our $safe = new Safe "safe";
2678our $safe_hole = new Safe::Hole; 2836our $safe_hole = new Safe::Hole;
2679 2837
2680$SIG{FPE} = 'IGNORE'; 2838$SIG{FPE} = 'IGNORE';
2681 2839
2682$safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time)); 2840$safe->permit_only (Opcode::opset qw(
2841 :base_core :base_mem :base_orig :base_math
2842 grepstart grepwhile mapstart mapwhile
2843 sort time
2844));
2683 2845
2684# here we export the classes and methods available to script code 2846# here we export the classes and methods available to script code
2685 2847
2686=pod 2848=pod
2687 2849
2688The following functions and methods are available within a safe environment: 2850The following functions and methods are available within a safe environment:
2689 2851
2690 cf::object 2852 cf::object
2691 contr pay_amount pay_player map x y force_find force_add 2853 contr pay_amount pay_player map x y force_find force_add
2692 insert remove 2854 insert remove name archname title slaying race decrease_ob_nr
2693 2855
2694 cf::object::player 2856 cf::object::player
2695 player 2857 player
2696 2858
2697 cf::player 2859 cf::player
2702 2864
2703=cut 2865=cut
2704 2866
2705for ( 2867for (
2706 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y 2868 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
2869 insert remove inv name archname title slaying race
2707 insert remove)], 2870 decrease_ob_nr)],
2708 ["cf::object::player" => qw(player)], 2871 ["cf::object::player" => qw(player)],
2709 ["cf::player" => qw(peaceful)], 2872 ["cf::player" => qw(peaceful)],
2710 ["cf::map" => qw(trigger)], 2873 ["cf::map" => qw(trigger)],
2711) { 2874) {
2712 no strict 'refs'; 2875 no strict 'refs';
2788# the server's init and main functions 2951# the server's init and main functions
2789 2952
2790sub load_facedata($) { 2953sub load_facedata($) {
2791 my ($path) = @_; 2954 my ($path) = @_;
2792 2955
2956 # HACK to clear player env face cache, we need some signal framework
2957 # for this (global event?)
2958 %ext::player_env::MUSIC_FACE_CACHE = ();
2959
2960 my $enc = JSON::XS->new->utf8->canonical->relaxed;
2961
2793 warn "loading facedata from $path\n"; 2962 warn "loading facedata from $path\n";
2794 2963
2795 my $facedata; 2964 my $facedata;
2796 0 < aio_load $path, $facedata 2965 0 < aio_load $path, $facedata
2797 or die "$path: $!"; 2966 or die "$path: $!";
2798 2967
2799 $facedata = Coro::Storable::thaw $facedata; 2968 $facedata = Coro::Storable::thaw $facedata;
2800 2969
2801 $facedata->{version} == 2 2970 $facedata->{version} == 2
2802 or cf::cleanup "$path: version mismatch, cannot proceed."; 2971 or cf::cleanup "$path: version mismatch, cannot proceed.";
2972
2973 # patch in the exptable
2974 $facedata->{resource}{"res/exp_table"} = {
2975 type => FT_RSRC,
2976 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
2977 };
2978 cf::cede_to_tick;
2803 2979
2804 { 2980 {
2805 my $faces = $facedata->{faceinfo}; 2981 my $faces = $facedata->{faceinfo};
2806 2982
2807 while (my ($face, $info) = each %$faces) { 2983 while (my ($face, $info) = each %$faces) {
2808 my $idx = (cf::face::find $face) || cf::face::alloc $face; 2984 my $idx = (cf::face::find $face) || cf::face::alloc $face;
2809 cf::face::set_visibility $idx, $info->{visibility}; 2985 cf::face::set_visibility $idx, $info->{visibility};
2810 cf::face::set_magicmap $idx, $info->{magicmap}; 2986 cf::face::set_magicmap $idx, $info->{magicmap};
2811 cf::face::set_data $idx, 0, $info->{data32}, $info->{chksum32}; 2987 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
2812 cf::face::set_data $idx, 1, $info->{data64}, $info->{chksum64}; 2988 cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
2813 2989
2814 cf::cede_to_tick; 2990 cf::cede_to_tick;
2815 } 2991 }
2816 2992
2817 while (my ($face, $info) = each %$faces) { 2993 while (my ($face, $info) = each %$faces) {
2842 3018
2843 { 3019 {
2844 # TODO: for gcfclient pleasure, we should give resources 3020 # TODO: for gcfclient pleasure, we should give resources
2845 # that gcfclient doesn't grok a >10000 face index. 3021 # that gcfclient doesn't grok a >10000 face index.
2846 my $res = $facedata->{resource}; 3022 my $res = $facedata->{resource};
2847 my $enc = JSON::XS->new->utf8->canonical; 3023
3024 my $soundconf = delete $res->{"res/sound.conf"};
2848 3025
2849 while (my ($name, $info) = each %$res) { 3026 while (my ($name, $info) = each %$res) {
2850 my $meta = $enc->encode ({
2851 name => $name,
2852 type => $info->{type},
2853 copyright => $info->{copyright}, #TODO#
2854 });
2855
2856 my $idx = (cf::face::find $name) || cf::face::alloc $name; 3027 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3028 my $data;
2857 3029
2858 if ($name =~ /\.jpg$/) { 3030 if ($info->{type} & 1) {
2859 cf::face::set_data $idx, 0, $info->{data}, $info->{chksum};#d# temp hack 3031 # prepend meta info
2860 cf::face::set_data $idx, 1, $info->{data}, $info->{chksum};#d# temp hack 3032
3033 my $meta = $enc->encode ({
3034 name => $name,
3035 %{ $info->{meta} || {} },
3036 });
3037
3038 $data = pack "(w/a*)*", $meta, $info->{data};
2861 } else { 3039 } else {
2862 my $data = pack "(w/a*)*", $meta, $info->{data}; 3040 $data = $info->{data};
2863 my $chk = Digest::MD5::md5 "$info->{chksum},$meta"; # mangle data checksum and metadata
2864
2865 cf::face::set_type $idx, 1;
2866 cf::face::set_data $idx, 0, $data, $chk;
2867 } 3041 }
2868 3042
3043 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
3044 cf::face::set_type $idx, $info->{type};
3045
2869 cf::cede_to_tick; 3046 cf::cede_to_tick;
2870 } 3047 }
3048
3049 if ($soundconf) {
3050 $soundconf = $enc->decode (delete $soundconf->{data});
3051
3052 for (0 .. SOUND_CAST_SPELL_0 - 1) {
3053 my $sound = $soundconf->{compat}[$_]
3054 or next;
3055
3056 my $face = cf::face::find "sound/$sound->[1]";
3057 cf::sound::set $sound->[0] => $face;
3058 cf::sound::old_sound_index $_, $face; # gcfclient-compat
3059 }
3060
3061 while (my ($k, $v) = each %{$soundconf->{event}}) {
3062 my $face = cf::face::find "sound/$v";
3063 cf::sound::set $k => $face;
3064 }
3065 }
2871 } 3066 }
2872 3067
2873 1 3068 1
2874} 3069}
2875 3070
3071register_exticmd fx_want => sub {
3072 my ($ns, $want) = @_;
3073
3074 while (my ($k, $v) = each %$want) {
3075 $ns->fx_want ($k, $v);
3076 }
3077};
3078
2876sub reload_regions { 3079sub reload_regions {
3080 # HACK to clear player env face cache, we need some signal framework
3081 # for this (global event?)
3082 %ext::player_env::MUSIC_FACE_CACHE = ();
3083
2877 load_resource_file "$MAPDIR/regions" 3084 load_resource_file "$MAPDIR/regions"
2878 or die "unable to load regions file\n"; 3085 or die "unable to load regions file\n";
2879 3086
2880 for (cf::region::list) { 3087 for (cf::region::list) {
2881 $_->{match} = qr/$_->{match}/ 3088 $_->{match} = qr/$_->{match}/
2917 3124
2918sub init { 3125sub init {
2919 reload_resources; 3126 reload_resources;
2920} 3127}
2921 3128
2922sub cfg_load { 3129sub reload_config {
2923 open my $fh, "<:utf8", "$CONFDIR/config" 3130 open my $fh, "<:utf8", "$CONFDIR/config"
2924 or return; 3131 or return;
2925 3132
2926 local $/; 3133 local $/;
2927 *CFG = YAML::Syck::Load <$fh>; 3134 *CFG = YAML::Syck::Load <$fh>;
2947 (async { 3154 (async {
2948 Event::one_event; 3155 Event::one_event;
2949 })->prio (Coro::PRIO_MAX); 3156 })->prio (Coro::PRIO_MAX);
2950 }; 3157 };
2951 3158
2952 cfg_load; 3159 reload_config;
2953 db_init; 3160 db_init;
2954 load_extensions; 3161 load_extensions;
2955 3162
2956 $TICK_WATCHER->start; 3163 $TICK_WATCHER->start;
2957 Event::loop; 3164 Event::loop;
3150 warn "reloading cf.pm"; 3357 warn "reloading cf.pm";
3151 require cf; 3358 require cf;
3152 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 3359 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
3153 3360
3154 warn "loading config and database again"; 3361 warn "loading config and database again";
3155 cf::cfg_load; 3362 cf::reload_config;
3156 3363
3157 warn "loading extensions"; 3364 warn "loading extensions";
3158 cf::load_extensions; 3365 cf::load_extensions;
3159 3366
3160 warn "reattaching attachments to objects/players"; 3367 warn "reattaching attachments to objects/players";
3253 or die; 3460 or die;
3254 3461
3255 $map->width (50); 3462 $map->width (50);
3256 $map->height (50); 3463 $map->height (50);
3257 $map->alloc; 3464 $map->alloc;
3258 $map->_load_objects ("/tmp/x.map", 1); 3465 $map->_load_objects ("/tmp/x.map", 1); #TODO: does not work
3259 my $t = Event::time - $t; 3466 my $t = Event::time - $t;
3260 3467
3261 #next unless $t < 0.0013;#d# 3468 #next unless $t < 0.0013;#d#
3262 if ($t < $min) { 3469 if ($t < $min) {
3263 $min = $t; 3470 $min = $t;

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines