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.108 by root, Sun Dec 31 21:02:05 2006 UTC vs.
Revision 1.130 by root, Thu Jan 4 00:08:08 2007 UTC

8use Storable; 8use Storable;
9use Opcode; 9use Opcode;
10use Safe; 10use Safe;
11use Safe::Hole; 11use Safe::Hole;
12 12
13use Coro 3.3; 13use Coro 3.3 ();
14use Coro::Event; 14use Coro::Event;
15use Coro::Timer; 15use Coro::Timer;
16use Coro::Signal; 16use Coro::Signal;
17use Coro::Semaphore; 17use Coro::Semaphore;
18use Coro::AIO; 18use Coro::AIO;
71mkdir cf::localdir . "/" . cf::uniquedir; 71mkdir cf::localdir . "/" . cf::uniquedir;
72mkdir $RANDOM_MAPS; 72mkdir $RANDOM_MAPS;
73 73
74# a special map that is always available 74# a special map that is always available
75our $LINK_MAP; 75our $LINK_MAP;
76
77our $EMERGENCY_POSITION = $cf::CFG{emergency_position} || ["/world/world_105_115", 5, 37];
76 78
77############################################################################# 79#############################################################################
78 80
79=head2 GLOBAL VARIABLES 81=head2 GLOBAL VARIABLES
80 82
179sub to_json($) { 181sub to_json($) {
180 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs 182 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
181 JSON::Syck::Dump $_[0] 183 JSON::Syck::Dump $_[0]
182} 184}
183 185
186=item my $guard = cf::guard { BLOCK }
187
188Run the given callback when the guard object gets destroyed (useful for
189coroutine cancellations).
190
191You can call C<< ->cancel >> on the guard object to stop the block from
192being executed.
193
194=cut
195
196sub guard(&) {
197 bless \(my $cb = $_[0]), cf::guard::;
198}
199
200sub cf::guard::cancel {
201 ${$_[0]} = sub { };
202}
203
204sub cf::guard::DESTROY {
205 ${$_[0]}->();
206}
207
208=item cf::lock_wait $string
209
210Wait until the given lock is available. See cf::lock_acquire.
211
212=item my $lock = cf::lock_acquire $string
213
214Wait until the given lock is available and then acquires it and returns
215a guard object. If the guard object gets destroyed (goes out of scope,
216for example when the coroutine gets canceled), the lock is automatically
217returned.
218
219Lock names should begin with a unique identifier (for example, find_map
220uses map_find and load_map uses map_load).
221
222=cut
223
224our %LOCK;
225
226sub lock_wait($) {
227 my ($key) = @_;
228
229 # wait for lock, if any
230 while ($LOCK{$key}) {
231 push @{ $LOCK{$key} }, $Coro::current;
232 Coro::schedule;
233 }
234}
235
236sub lock_acquire($) {
237 my ($key) = @_;
238
239 # wait, to be sure we are not locked
240 lock_wait $key;
241
242 $LOCK{$key} = [];
243
244 cf::guard {
245 # wake up all waiters, to be on the safe side
246 $_->ready for @{ delete $LOCK{$key} };
247 }
248}
249
250=item cf::async { BLOCK }
251
252Like C<Coro::async>, but runs the given BLOCK in an eval and only logs the
253error instead of exiting the server in case of a problem.
254
255=cut
256
257sub async(&) {
258 my ($cb) = @_;
259
260 Coro::async {
261 eval { $cb->() };
262 warn $@ if $@;
263 }
264}
265
184=item cf::sync_job { BLOCK } 266=item cf::sync_job { BLOCK }
185 267
186The design of crossfire+ requires that the main coro ($Coro::main) is 268The design of crossfire+ requires that the main coro ($Coro::main) is
187always able to handle events or runnable, as crossfire+ is only partly 269always able to handle events or runnable, as crossfire+ is only partly
188reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable. 270reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
195=cut 277=cut
196 278
197sub sync_job(&) { 279sub sync_job(&) {
198 my ($job) = @_; 280 my ($job) = @_;
199 281
200 my $busy = 1;
201 my @res;
202
203 # TODO: use suspend/resume instead
204 local $FREEZE = 1;
205
206 my $coro = Coro::async {
207 @res = eval { $job->() };
208 warn $@ if $@;
209 undef $busy;
210 };
211
212 if ($Coro::current == $Coro::main) { 282 if ($Coro::current == $Coro::main) {
283 # this is the main coro, too bad, we have to block
284 # till the operation succeeds, freezing the server :/
285
286 # TODO: use suspend/resume instead
287 # (but this is cancel-safe)
288 local $FREEZE = 1;
289
290 my $busy = 1;
291 my @res;
292
293 (Coro::async {
294 @res = eval { $job->() };
295 warn $@ if $@;
296 undef $busy;
213 $coro->prio (Coro::PRIO_MAX); 297 })->prio (Coro::PRIO_MAX);
298
214 while ($busy) { 299 while ($busy) {
215 Coro::cede_notself; 300 Coro::cede_notself;
216 Event::one_event unless Coro::nready; 301 Event::one_event unless Coro::nready;
217 } 302 }
303
304 wantarray ? @res : $res[0]
218 } else { 305 } else {
219 $coro->join; 306 # we are in another coroutine, how wonderful, everything just works
307
308 $job->()
220 } 309 }
221
222 wantarray ? @res : $res[0]
223} 310}
224 311
225=item $coro = cf::coro { BLOCK } 312=item $coro = cf::coro { BLOCK }
226 313
227Creates and returns a new coro. This coro is automcatially being canceled 314Creates and returns a new coro. This coro is automcatially being canceled
230=cut 317=cut
231 318
232sub coro(&) { 319sub coro(&) {
233 my $cb = shift; 320 my $cb = shift;
234 321
235 my $coro; $coro = async { 322 my $coro = &cf::async ($cb);
236 eval {
237 $cb->();
238 };
239 warn $@ if $@;
240 };
241 323
242 $coro->on_destroy (sub { 324 $coro->on_destroy (sub {
243 delete $EXT_CORO{$coro+0}; 325 delete $EXT_CORO{$coro+0};
244 }); 326 });
245 $EXT_CORO{$coro+0} = $coro; 327 $EXT_CORO{$coro+0} = $coro;
251 my $runtime = cf::localdir . "/runtime"; 333 my $runtime = cf::localdir . "/runtime";
252 334
253 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 335 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
254 or return; 336 or return;
255 337
256 my $value = $cf::RUNTIME; 338 my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock
257 (aio_write $fh, 0, (length $value), $value, 0) <= 0 339 (aio_write $fh, 0, (length $value), $value, 0) <= 0
258 and return; 340 and return;
259 341
260 aio_fsync $fh 342 aio_fsync $fh
261 and return; 343 and return;
278package cf::path; 360package cf::path;
279 361
280sub new { 362sub new {
281 my ($class, $path, $base) = @_; 363 my ($class, $path, $base) = @_;
282 364
365 $path = $path->as_string if ref $path;
366
283 my $self = bless { }, $class; 367 my $self = bless { }, $class;
284 368
369 # {... are special paths that are not touched
370 # ?xxx/... are special absolute paths
371 # ?random/... random maps
372 # /! non-realised random map exit
373 # /... normal maps
374 # ~/... per-player maps without a specific player (DO NOT USE)
375 # ~user/... per-player map of a specific user
376
377 if ($path =~ /^{/) {
378 # fine as it is
285 if ($path =~ s{^\?random/}{}) { 379 } elsif ($path =~ s{^\?random/}{}) {
380 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
286 $self->{random} = cf::from_json $path; 381 $self->{random} = cf::from_json $data;
287 } else { 382 } else {
288 if ($path =~ s{^~([^/]+)?}{}) { 383 if ($path =~ s{^~([^/]+)?}{}) {
289 $self->{user_rel} = 1; 384 $self->{user_rel} = 1;
290 385
291 if (defined $1) { 386 if (defined $1) {
324 419
325# the displayed name, this is a one way mapping 420# the displayed name, this is a one way mapping
326sub visible_name { 421sub visible_name {
327 my ($self) = @_; 422 my ($self) = @_;
328 423
329 $self->{random} ? "?random/$self->{random}{origin_map}+$self->{random}{origin_x}+$self->{random}{origin_y}/$self->{random}{dungeon_level}" 424# if (my $rmp = $self->{random}) {
330 : $self->as_string 425# # todo: be more intelligent about this
426# "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
427# } else {
428 $self->as_string
429# }
331} 430}
332 431
333# escape the /'s in the path 432# escape the /'s in the path
334sub _escaped_path { 433sub _escaped_path {
335 # ∕ is U+2215 434 # ∕ is U+2215
347# the temporary/swap location 446# the temporary/swap location
348sub save_path { 447sub save_path {
349 my ($self) = @_; 448 my ($self) = @_;
350 449
351 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path 450 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
352 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, Digest::MD5::md5_hex $self->{path} 451 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
353 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path 452 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
354} 453}
355 454
356# the unique path, might be eq to save_path 455# the unique path, might be eq to save_path
357sub uniq_path { 456sub uniq_path {
790 or return; 889 or return;
791 890
792 unless (aio_stat "$filename.pst") { 891 unless (aio_stat "$filename.pst") {
793 (aio_load "$filename.pst", $av) >= 0 892 (aio_load "$filename.pst", $av) >= 0
794 or return; 893 or return;
795 $av = eval { (Storable::thaw <$av>)->{objs} }; 894 $av = eval { (Storable::thaw $av)->{objs} };
796 } 895 }
797 896
897 warn sprintf "loading %s (%d)\n",
898 $filename, length $data, scalar @{$av || []};#d#
798 return ($data, $av); 899 return ($data, $av);
799} 900}
800 901
801############################################################################# 902#############################################################################
802# command handling &c 903# command handling &c
1024 $self->send ("ext " . to_json \%msg); 1125 $self->send ("ext " . to_json \%msg);
1025} 1126}
1026 1127
1027=back 1128=back
1028 1129
1130
1131=head3 cf::map
1132
1133=over 4
1134
1135=cut
1136
1137package cf::map;
1138
1139use Fcntl;
1140use Coro::AIO;
1141
1142our $MAX_RESET = 7200;
1143our $DEFAULT_RESET = 3600;
1144
1145sub generate_random_map {
1146 my ($path, $rmp) = @_;
1147
1148 # mit "rum" bekleckern, nicht
1149 cf::map::_create_random_map
1150 $path,
1151 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1152 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1153 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1154 $rmp->{exit_on_final_map},
1155 $rmp->{xsize}, $rmp->{ysize},
1156 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1157 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1158 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1159 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1160 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1161 (cf::region::find $rmp->{region})
1162}
1163
1164# and all this just because we cannot iterate over
1165# all maps in C++...
1166sub change_all_map_light {
1167 my ($change) = @_;
1168
1169 $_->change_map_light ($change)
1170 for grep $_->outdoor, values %cf::MAP;
1171}
1172
1173sub try_load_header($) {
1174 my ($path) = @_;
1175
1176 utf8::encode $path;
1177 aio_open $path, O_RDONLY, 0
1178 or return;
1179
1180 my $map = cf::map::new
1181 or return;
1182
1183 $map->load_header ($path)
1184 or return;
1185
1186 $map->{load_path} = $path;
1187
1188 $map
1189}
1190
1191sub find_map;
1192sub find_map {
1193 my ($path, $origin) = @_;
1194
1195 #warn "find_map<$path,$origin>\n";#d#
1196
1197 $path = new cf::path $path, $origin && $origin->path;
1198 my $key = $path->as_string;
1199
1200 cf::lock_wait "map_find:$key";
1201
1202 $cf::MAP{$key} || do {
1203 my $guard = cf::lock_acquire "map_find:$key";
1204
1205 # do it the slow way
1206 my $map = try_load_header $path->save_path;
1207
1208 if ($map) {
1209 # safety
1210 $map->{instantiate_time} = $cf::RUNTIME
1211 if $map->{instantiate_time} > $cf::RUNTIME;
1212 } else {
1213 if (my $rmp = $path->random_map_params) {
1214 $map = generate_random_map $key, $rmp;
1215 } else {
1216 $map = try_load_header $path->load_path;
1217 }
1218
1219 $map or return;
1220
1221 $map->{load_original} = 1;
1222 $map->{instantiate_time} = $cf::RUNTIME;
1223 $map->instantiate;
1224
1225 # per-player maps become, after loading, normal maps
1226 $map->per_player (0) if $path->{user_rel};
1227 }
1228
1229 $map->path ($key);
1230 $map->{path} = $path;
1231 $map->{last_save} = $cf::RUNTIME;
1232 $map->last_access ($cf::RUNTIME);
1233
1234 if ($map->should_reset) {
1235 $map->reset;
1236 undef $guard;
1237 $map = find_map $path
1238 or return;
1239 }
1240
1241 $cf::MAP{$key} = $map
1242 }
1243}
1244
1245sub load {
1246 my ($self) = @_;
1247
1248 my $path = $self->{path};
1249 my $guard = cf::lock_acquire "map_load:" . $path->as_string;
1250
1251 return if $self->in_memory != cf::MAP_SWAPPED;
1252
1253 $self->in_memory (cf::MAP_LOADING);
1254
1255 $self->alloc;
1256 $self->load_objects ($self->{load_path}, 1)
1257 or return;
1258
1259 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1260 if delete $self->{load_original};
1261
1262 if (my $uniq = $path->uniq_path) {
1263 utf8::encode $uniq;
1264 if (aio_open $uniq, O_RDONLY, 0) {
1265 $self->clear_unique_items;
1266 $self->load_objects ($uniq, 0);
1267 }
1268 }
1269
1270 # now do the right thing for maps
1271 $self->link_multipart_objects;
1272
1273 if ($self->{path}->is_style_map) {
1274 $self->{deny_save} = 1;
1275 $self->{deny_reset} = 1;
1276 } else {
1277 $self->fix_auto_apply;
1278 $self->decay_objects;
1279 $self->update_buttons;
1280 $self->set_darkness_map;
1281 $self->difficulty ($self->estimate_difficulty)
1282 unless $self->difficulty;
1283 $self->activate;
1284 }
1285
1286 $self->in_memory (cf::MAP_IN_MEMORY);
1287}
1288
1289sub load_map_sync {
1290 my ($path, $origin) = @_;
1291
1292 #warn "load_map_sync<$path, $origin>\n";#d#
1293
1294 cf::sync_job {
1295 my $map = cf::map::find_map $path, $origin
1296 or return;
1297 $map->load;
1298 $map
1299 }
1300}
1301
1302sub save {
1303 my ($self) = @_;
1304
1305 $self->{last_save} = $cf::RUNTIME;
1306
1307 return unless $self->dirty;
1308
1309 my $save = $self->{path}->save_path; utf8::encode $save;
1310 my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1311
1312 $self->{load_path} = $save;
1313
1314 return if $self->{deny_save};
1315
1316 if ($uniq) {
1317 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1318 $self->save_objects ($uniq, cf::IO_UNIQUES);
1319 } else {
1320 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1321 }
1322}
1323
1324sub swap_out {
1325 my ($self) = @_;
1326
1327 # save first because save cedes
1328 $self->save;
1329
1330 return if $self->players;
1331 return if $self->in_memory != cf::MAP_IN_MEMORY;
1332 return if $self->{deny_save};
1333
1334 $self->clear;
1335 $self->in_memory (cf::MAP_SWAPPED);
1336}
1337
1338sub reset_at {
1339 my ($self) = @_;
1340
1341 # TODO: safety, remove and allow resettable per-player maps
1342 return 1e99 if $self->{path}{user_rel};
1343 return 1e99 if $self->{deny_reset};
1344
1345 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
1346 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
1347
1348 $time + $to
1349}
1350
1351sub should_reset {
1352 my ($self) = @_;
1353
1354 $self->reset_at <= $cf::RUNTIME
1355}
1356
1357sub unlink_save {
1358 my ($self) = @_;
1359
1360 utf8::encode (my $save = $self->{path}->save_path);
1361 aioreq_pri 3; IO::AIO::aio_unlink $save;
1362 aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1363}
1364
1365sub rename {
1366 my ($self, $new_path) = @_;
1367
1368 $self->unlink_save;
1369
1370 delete $cf::MAP{$self->path};
1371 $self->{path} = new cf::path $new_path;
1372 $self->path ($self->{path}->as_string);
1373 $cf::MAP{$self->path} = $self;
1374
1375 $self->save;
1376}
1377
1378sub reset {
1379 my ($self) = @_;
1380
1381 return if $self->players;
1382 return if $self->{path}{user_rel};#d#
1383
1384 warn "resetting map ", $self->path;#d#
1385
1386 delete $cf::MAP{$self->path};
1387
1388 $_->clear_links_to ($self) for values %cf::MAP;
1389
1390 $self->unlink_save;
1391 $self->destroy;
1392}
1393
1394my $nuke_counter = "aaaa";
1395
1396sub nuke {
1397 my ($self) = @_;
1398
1399 $self->{deny_save} = 1;
1400 $self->reset_timeout (1);
1401 $self->rename ("{nuke}/" . ($nuke_counter++));
1402 $self->reset; # polite request, might not happen
1403}
1404
1405sub customise_for {
1406 my ($map, $ob) = @_;
1407
1408 if ($map->per_player) {
1409 return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
1410 }
1411
1412 $map
1413}
1414
1415sub emergency_save {
1416 local $cf::FREEZE = 1;
1417
1418 warn "enter emergency map save\n";
1419
1420 cf::sync_job {
1421 warn "begin emergency map save\n";
1422 $_->save for values %cf::MAP;
1423 };
1424
1425 warn "end emergency map save\n";
1426}
1427
1428package cf;
1429
1430=back
1431
1432
1029=head3 cf::object::player 1433=head3 cf::object::player
1030 1434
1031=over 4 1435=over 4
1032 1436
1033=item $player_object->reply ($npc, $msg[, $flags]) 1437=item $player_object->reply ($npc, $msg[, $flags])
1068 (ref $cf::CFG{"may_$access"} 1472 (ref $cf::CFG{"may_$access"}
1069 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} 1473 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
1070 : $cf::CFG{"may_$access"}) 1474 : $cf::CFG{"may_$access"})
1071} 1475}
1072 1476
1477=item $player_object->enter_link
1478
1479Freezes the player and moves him/her to a special map (C<{link}>).
1480
1481The player should be reaosnably safe there for short amounts of time. You
1482I<MUST> call C<leave_link> as soon as possible, though.
1483
1484=item $player_object->leave_link ($map, $x, $y)
1485
1486Moves the player out of the specila link map onto the given map. If the
1487map is not valid (or omitted), the player will be moved back to the
1488location he/she was before the call to C<enter_link>, or, if that fails,
1489to the emergency map position.
1490
1491Might block.
1492
1493=cut
1494
1495sub cf::object::player::enter_link {
1496 my ($self) = @_;
1497
1498 $self->deactivate_recursive;
1499
1500 return if $self->map == $LINK_MAP;
1501
1502 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
1503 if $self->map;
1504
1505 $self->enter_map ($LINK_MAP, 20, 20);
1506}
1507
1508sub cf::object::player::leave_link {
1509 my ($self, $map, $x, $y) = @_;
1510
1511 my $link_pos = delete $self->{_link_pos};
1512
1513 unless ($map) {
1514 # restore original map position
1515 ($map, $x, $y) = @{ $link_pos || [] };
1516 $map = cf::map::find_map $map;
1517
1518 unless ($map) {
1519 ($map, $x, $y) = @$EMERGENCY_POSITION;
1520 $map = cf::map::find_map $map
1521 or die "FATAL: cannot load emergency map\n";
1522 }
1523 }
1524
1525 ($x, $y) = (-1, -1)
1526 unless (defined $x) && (defined $y);
1527
1528 # use -1 or undef as default coordinates, not 0, 0
1529 ($x, $y) = ($map->enter_x, $map->enter_y)
1530 if $x <=0 && $y <= 0;
1531
1532 $map->load;
1533
1534 $self->activate_recursive;
1535 $self->enter_map ($map, $x, $y);
1536}
1537
1538cf::player->attach (
1539 on_logout => sub {
1540 my ($pl) = @_;
1541
1542 # abort map switching before logout
1543 if ($pl->ob->{_link_pos}) {
1544 cf::sync_job {
1545 $pl->ob->leave_link
1546 };
1547 }
1548 },
1549 on_login => sub {
1550 my ($pl) = @_;
1551
1552 # try to abort aborted map switching on player login :)
1553 # should happen only on crashes
1554 if ($pl->ob->{_link_pos}) {
1555 $pl->ob->enter_link;
1556 cf::async {
1557 # we need this sleep as the login has a concurrent enter_exit running
1558 # and this sleep increases chances of the player not ending up in scorn
1559 Coro::Timer::sleep 1;
1560 $pl->ob->leave_link;
1561 };
1562 }
1563 },
1564);
1565
1566=item $player_object->goto_map ($path, $x, $y)
1567
1568=cut
1569
1570sub cf::object::player::goto_map {
1571 my ($self, $path, $x, $y) = @_;
1572
1573 $self->enter_link;
1574
1575 (cf::async {
1576 $path = new cf::path $path;
1577
1578 my $map = cf::map::find_map $path->as_string;
1579 $map = $map->customise_for ($self) if $map;
1580
1581# warn "entering ", $map->path, " at ($x, $y)\n"
1582# if $map;
1583
1584 $map or $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1585
1586 $self->leave_link ($map, $x, $y);
1587 })->prio (1);
1588}
1589
1590=item $player_object->enter_exit ($exit_object)
1591
1592=cut
1593
1594sub parse_random_map_params {
1595 my ($spec) = @_;
1596
1597 my $rmp = { # defaults
1598 xsize => 10,
1599 ysize => 10,
1600 };
1601
1602 for (split /\n/, $spec) {
1603 my ($k, $v) = split /\s+/, $_, 2;
1604
1605 $rmp->{lc $k} = $v if (length $k) && (length $v);
1606 }
1607
1608 $rmp
1609}
1610
1611sub prepare_random_map {
1612 my ($exit) = @_;
1613
1614 # all this does is basically replace the /! path by
1615 # a new random map path (?random/...) with a seed
1616 # that depends on the exit object
1617
1618 my $rmp = parse_random_map_params $exit->msg;
1619
1620 if ($exit->map) {
1621 $rmp->{region} = $exit->map->region_name;
1622 $rmp->{origin_map} = $exit->map->path;
1623 $rmp->{origin_x} = $exit->x;
1624 $rmp->{origin_y} = $exit->y;
1625 }
1626
1627 $rmp->{random_seed} ||= $exit->random_seed;
1628
1629 my $data = cf::to_json $rmp;
1630 my $md5 = Digest::MD5::md5_hex $data;
1631
1632 if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
1633 aio_write $fh, 0, (length $data), $data, 0;
1634
1635 $exit->slaying ("?random/$md5");
1636 $exit->msg (undef);
1637 }
1638}
1639
1640sub cf::object::player::enter_exit {
1641 my ($self, $exit) = @_;
1642
1643 return unless $self->type == cf::PLAYER;
1644
1645 $self->enter_link;
1646
1647 (cf::async {
1648 unless (eval {
1649 prepare_random_map $exit
1650 if $exit->slaying eq "/!";
1651
1652 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
1653 $self->goto_map ($path, $exit->stats->hp, $exit->stats->sp);
1654
1655 1;
1656 }) {
1657 $self->message ("Something went wrong deep within the crossfire server. "
1658 . "I'll try to bring you back to the map you were before. "
1659 . "Please report this to the dungeon master",
1660 cf::NDI_UNIQUE | cf::NDI_RED);
1661
1662 warn "ERROR in enter_exit: $@";
1663 $self->leave_link;
1664 }
1665 })->prio (1);
1666}
1667
1073=head3 cf::client 1668=head3 cf::client
1074 1669
1075=over 4 1670=over 4
1076 1671
1077=item $client->send_drawinfo ($text, $flags) 1672=item $client->send_drawinfo ($text, $flags)
1120 on_reply => sub { 1715 on_reply => sub {
1121 my ($ns, $msg) = @_; 1716 my ($ns, $msg) = @_;
1122 1717
1123 # this weird shuffling is so that direct followup queries 1718 # this weird shuffling is so that direct followup queries
1124 # get handled first 1719 # get handled first
1125 my $queue = delete $ns->{query_queue}; 1720 my $queue = delete $ns->{query_queue}
1721 or return; # be conservative, not sure how that can happen, but we saw a crash here
1126 1722
1127 (shift @$queue)->[1]->($msg); 1723 (shift @$queue)->[1]->($msg);
1128 1724
1129 push @{ $ns->{query_queue} }, @$queue; 1725 push @{ $ns->{query_queue} }, @$queue;
1130 1726
1147=cut 1743=cut
1148 1744
1149sub cf::client::coro { 1745sub cf::client::coro {
1150 my ($self, $cb) = @_; 1746 my ($self, $cb) = @_;
1151 1747
1152 my $coro; $coro = async { 1748 my $coro = &cf::async ($cb);
1153 eval {
1154 $cb->();
1155 };
1156 warn $@ if $@;
1157 };
1158 1749
1159 $coro->on_destroy (sub { 1750 $coro->on_destroy (sub {
1160 delete $self->{_coro}{$coro+0}; 1751 delete $self->{_coro}{$coro+0};
1161 }); 1752 });
1162 1753
1334 1925
1335{ 1926{
1336 my $path = cf::localdir . "/database.pst"; 1927 my $path = cf::localdir . "/database.pst";
1337 1928
1338 sub db_load() { 1929 sub db_load() {
1339 warn "loading database $path\n";#d# remove later
1340 $DB = stat $path ? Storable::retrieve $path : { }; 1930 $DB = stat $path ? Storable::retrieve $path : { };
1341 } 1931 }
1342 1932
1343 my $pid; 1933 my $pid;
1344 1934
1345 sub db_save() { 1935 sub db_save() {
1346 warn "saving database $path\n";#d# remove later
1347 waitpid $pid, 0 if $pid; 1936 waitpid $pid, 0 if $pid;
1348 if (0 == ($pid = fork)) { 1937 if (0 == ($pid = fork)) {
1349 $DB->{_meta}{version} = 1; 1938 $DB->{_meta}{version} = 1;
1350 Storable::nstore $DB, "$path~"; 1939 Storable::nstore $DB, "$path~";
1351 rename "$path~", $path; 1940 rename "$path~", $path;
1404} 1993}
1405 1994
1406sub main { 1995sub main {
1407 # we must not ever block the main coroutine 1996 # we must not ever block the main coroutine
1408 local $Coro::idle = sub { 1997 local $Coro::idle = sub {
1409 Carp::cluck "FATAL: Coro::idle was called, major BUG\n";#d# 1998 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
1410 (Coro::unblock_sub { 1999 (Coro::unblock_sub {
1411 Event::one_event; 2000 Event::one_event;
1412 })->(); 2001 })->();
1413 }; 2002 };
1414 2003
1419} 2008}
1420 2009
1421############################################################################# 2010#############################################################################
1422# initialisation 2011# initialisation
1423 2012
1424sub perl_reload() { 2013sub reload() {
1425 # can/must only be called in main 2014 # can/must only be called in main
1426 if ($Coro::current != $Coro::main) { 2015 if ($Coro::current != $Coro::main) {
1427 warn "can only reload from main coroutine\n"; 2016 warn "can only reload from main coroutine\n";
1428 return; 2017 return;
1429 } 2018 }
1522 $LINK_MAP->height (41); 2111 $LINK_MAP->height (41);
1523 $LINK_MAP->alloc; 2112 $LINK_MAP->alloc;
1524 $LINK_MAP->path ("{link}"); 2113 $LINK_MAP->path ("{link}");
1525 $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path"; 2114 $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
1526 $LINK_MAP->in_memory (MAP_IN_MEMORY); 2115 $LINK_MAP->in_memory (MAP_IN_MEMORY);
2116
2117 # dirty hack because... archetypes are not yet loaded
2118 Event->timer (
2119 after => 2,
2120 cb => sub {
2121 $_[0]->w->cancel;
2122
2123 # provide some exits "home"
2124 my $exit = cf::object::new "exit";
2125
2126 $exit->slaying ($EMERGENCY_POSITION->[0]);
2127 $exit->stats->hp ($EMERGENCY_POSITION->[1]);
2128 $exit->stats->sp ($EMERGENCY_POSITION->[2]);
2129
2130 $LINK_MAP->insert ($exit->clone, 19, 19);
2131 $LINK_MAP->insert ($exit->clone, 19, 20);
2132 $LINK_MAP->insert ($exit->clone, 19, 21);
2133 $LINK_MAP->insert ($exit->clone, 20, 19);
2134 $LINK_MAP->insert ($exit->clone, 20, 21);
2135 $LINK_MAP->insert ($exit->clone, 21, 19);
2136 $LINK_MAP->insert ($exit->clone, 21, 20);
2137 $LINK_MAP->insert ($exit->clone, 21, 21);
2138
2139 $exit->destroy;
2140 });
2141
2142 $LINK_MAP->{deny_save} = 1;
2143 $LINK_MAP->{deny_reset} = 1;
2144
2145 $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
1527} 2146}
1528 2147
1529register "<global>", __PACKAGE__; 2148register "<global>", __PACKAGE__;
1530 2149
1531register_command "perl-reload" => sub { 2150register_command "reload" => sub {
1532 my ($who, $arg) = @_; 2151 my ($who, $arg) = @_;
1533 2152
1534 if ($who->flag (FLAG_WIZ)) { 2153 if ($who->flag (FLAG_WIZ)) {
1535 $who->message ("start of reload."); 2154 $who->message ("start of reload.");
1536 perl_reload; 2155 reload;
1537 $who->message ("end of reload."); 2156 $who->message ("end of reload.");
1538 } 2157 }
1539}; 2158};
1540 2159
1541unshift @INC, $LIBDIR; 2160unshift @INC, $LIBDIR;
1581 or warn "ERROR: unable to write runtime file: $!"; 2200 or warn "ERROR: unable to write runtime file: $!";
1582 })->(); 2201 })->();
1583 }, 2202 },
1584); 2203);
1585 2204
2205END { cf::emergency_save }
2206
15861 22071
1587 2208

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines