--- deliantra/server/lib/cf.pm 2012/11/20 14:30:22 1.609 +++ deliantra/server/lib/cf.pm 2018/11/17 23:40:02 1.624 @@ -1,6 +1,7 @@ # # This file is part of Deliantra, the Roguelike Realtime MMORPG. # +# Copyright (©) 2018 Marc Alexander Lehmann / the Deliantra team # Copyright (©) 2006,2007,2008,2009,2010,2011,2012 Marc Alexander Lehmann / Robin Redeker / the Deliantra team # # Deliantra is free software: you can redistribute it and/or modify it under @@ -59,6 +60,7 @@ use Data::Dumper; use Fcntl; use YAML::XS (); +use CBOR::XS (); use IO::AIO (); use Compress::LZF; use Digest::MD5 (); @@ -227,7 +229,7 @@ The time this server has run, starts at 0 and is increased by $cf::TICK on every server tick. -=item $cf::CONFDIR $cf::DATADIR $cf::LIBDIR $cf::PODDIR +=item $cf::CONFDIR $cf::DATADIR $cf::LIBDIR $cf::PODDIR $cf::MAPDIR $cf::LOCALDIR $cf::TMPDIR $cf::UNIQUEDIR $cf::PLAYERDIR $cf::RANDOMDIR $cf::BDBDIR @@ -466,6 +468,20 @@ fork_call { YAML::XS::Load $_[0] } @_ } +=item $scalar = cf::decode_cbor $scalar + +Same as CBOR::XS::decode_cbor, but takes server ticks into account, so +blocks. For small amounts of data, C is the better +alternative. + +=cut + +sub decode_cbor($) { + # we assume 10mb/s minimum decoding speed (on a ~2ghz machine) + cf::get_slot +(length $_[0]) / 10_000_000, 0, "decode_cbor"; + CBOR::XS::decode_cbor $_[0] +} + =item $scalar = cf::unlzf $scalar Same as Compress::LZF::compress, but takes server ticks into account, so @@ -512,7 +528,7 @@ =item cf::lock_wait $string -Wait until the given lock is available. See cf::lock_acquire. +Wait until the given lock is available. See cf::lock_acquire. =item my $lock = cf::lock_acquire $string @@ -599,6 +615,8 @@ while () { next_job: + Coro::cede; + my $avail = cf::till_tick; for (0 .. $#SLOT_QUEUE) { @@ -606,7 +624,6 @@ $busy = 0; my $job = splice @SLOT_QUEUE, $_, 1, (); $job->[2]->send; - Coro::cede; goto next_job; } else { $SLOT_QUEUE[$_][0] *= $SLOT_DECAY; @@ -614,8 +631,7 @@ } if (@SLOT_QUEUE) { - # we do not use wait_for_tick() as it returns immediately when tick is inactive - $WAIT_FOR_TICK->wait; + wait_for_tick; } else { $busy = 0; Coro::schedule; @@ -651,7 +667,7 @@ =item cf::sync_job { BLOCK } The design of Deliantra requires that the main coroutine ($Coro::main) -is always able to handle events or runnable, as Deliantra is only +is always able to handle events or is runnable, as Deliantra is only partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable. @@ -689,7 +705,7 @@ if (Coro::nready) { Coro::cede_notself; } else { - EV::loop EV::LOOP_ONESHOT; + EV::run EV::RUN_ONCE; } } @@ -2386,9 +2402,9 @@ $Coro::current->{desc} = "map prefetcher"; while (%MAP_PREFETCH) { - while (my ($k, $v) = each %MAP_PREFETCH) { + for my $k (keys %MAP_PREFETCH) { if (my $map = find $k) { - $map->load if $v; + $map->load if $MAP_PREFETCH{$k}; } delete $MAP_PREFETCH{$k}; @@ -2744,14 +2760,39 @@ Returns wether the given player is authorized to access resource "access" (e.g. "command_wizcast"). +This is implemented by checking a config setting of C where +C is replaced by the access string. The following alternatives are +possible (and are tested in order): + +=over 4 + +=item * Player is DM + +The request will succeed. + +=item * may_access is an array reference + +If either the player nickname or UUID is in the array, the request will +succeed, otherwise it will fail. + +=item * may_access is a true value + +The request will succeed. + +=item * may_access is missing or false + +The request will fail. + +=back + =cut sub cf::object::player::may { my ($self, $access) = @_; - $self->flag (cf::FLAG_WIZ) || + $self->flag (cf::FLAG_WIZ) || (ref $cf::CFG{"may_$access"} - ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} + ? scalar grep $self->name eq $_ || $self->uuid eq $_, @{$cf::CFG{"may_$access"}} : $cf::CFG{"may_$access"}) } @@ -2764,7 +2805,13 @@ though, as the player cannot control the character while it is on the link map. -Will never block. +This method will never block, which is the whole reaosn for it's +existance: you can I put a player onto the link map, which is the +only place to put objects that is guaranteed to exist. + +A typical usage pattern is to call C synchronously from the +server, then start a new thread, do your blocking stuff there and then +call C from that thread. =item $player_object->leave_link ($map, $x, $y) @@ -3126,7 +3173,7 @@ id => "infobox", title => "Body Parts", reply => undef, - tooltip => "Shows which body parts you posess and are available", + tooltip => "Shows which body parts you possess and are available", }, "c/statistics" => { id => "infobox", @@ -3282,16 +3329,18 @@ =cut -our @COMMAND_FACES; # [normal commands, emotes, dm commands] +our %COMMAND_FACE; sub cf::client::update_command_faces { my ($self) = @_; - my @faces = ( - $COMMAND_FACES[0], - $COMMAND_FACES[1], - $self->pl->ob->flag (cf::FLAG_WIZ) ? $COMMAND_FACES[2] : () - ); + my @faces = grep $_, + $COMMAND_FACE{preferred}, + $COMMAND_FACE{standard}, + $COMMAND_FACE{skill}, + $self->pl->ob->flag (cf::FLAG_WIZ) ? $COMMAND_FACE{dm} : (), + $COMMAND_FACE{emote}, + ; $self->send_face ($_) for @faces; @@ -3300,12 +3349,19 @@ $self->ext_msg (command_list => @faces); } -=item cf::client::set_command_faces $normal_commands, $emotes, $dm_commands +=item cf::client::set_command_face $type, $commands =cut -sub cf::client::set_command_faces { - @COMMAND_FACES = @_; +sub cf::client::set_command_face { + my ($type, $list) = @_; + + my $idx = &cf::face::set ( #d# ugly forward reference + "command_list/$type" => cf::FT_RSRC, + JSON::XS->new->utf8->encode ([ sort @$list ]) + ); + + $COMMAND_FACE{$type} = $idx; } cf::client->attach ( @@ -3781,6 +3837,20 @@ JSON::XS->new->utf8->canonical->encode ( [map [cf::spellpathnames ($_)], 0 .. NRSPELLPATHS - 1] ); + + # command completion + my @commands; + + for (0..cf::arch::skillvec_size - 1) { + my $skill = cf::arch::skillvec $_; + my $name = $skill->name; + my $flags = cf::skill_flags $skill->subtype; + + push @commands, "ready_skill $name" if $flags & (SF_COMBAT | SF_RANGED | SF_GRACE); + push @commands, "use_skill $name" if $flags & (SF_USE | SF_AUTARK | SF_GRACE); + } + + cf::client::set_command_face skill => \@commands; } sub reload_treasures { @@ -3863,11 +3933,11 @@ } sub main_loop { - trace "EV::loop starting\n"; + trace "EV::run starting\n"; if (1) { - EV::loop; + EV::run; } - trace "EV::loop returned\n"; + trace "EV::run returned\n"; goto &main_loop unless $REALLY_UNLOOP; } @@ -3875,7 +3945,8 @@ cf::init_globals; # initialise logging LOG llevInfo, "Welcome to Deliantra, v" . VERSION; - LOG llevInfo, "Copyright (C) 2005-2012 Marc Alexander Lehmann / Robin Redeker / the Deliantra team."; + LOG llevInfo, "Copyright (C) 2017-2018 Marc Alexander Lehmann / the Deliantra team."; + LOG llevInfo, "Copyright (C) 2005-2016 Marc Alexander Lehmann / Robin Redeker / the Deliantra team."; LOG llevInfo, "Copyright (C) 1994 Mark Wedel."; LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen."; @@ -3886,7 +3957,7 @@ Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d# (async { $Coro::current->{desc} = "IDLE BUG HANDLER"; - EV::loop EV::LOOP_ONESHOT; + EV::run EV::RUN_ONCE; })->prio (Coro::PRIO_MAX); };