#! perl use Fcntl (); use Coro::AIO; our $HIGHSCORE_ENTRIES = $cf::CFG{highscore_entries} || 1000; # [name, title, exp, killer, where, hp, sp, gp, uuid] sub load_hiscore() { # if (0 <= aio_load "$cf::LOCALDIR/hiscore.json", my $json) { # return JSON::XS::decode_json $json; # } # try to convert old highscore file if (0 <= aio_load "$cf::LOCALDIR/highscore", my $data) { # warn "converting from old $cf::LOCALDIR/highscore file\n"; return [map [split /:/], split /\n/, $data]; } return []; } sub save_hiscore($) { my ($hiscore) = @_; my $fh = aio_open "$cf::LOCALDIR/highscore~", Fcntl::O_WRONLY | Fcntl::O_CREAT | Fcntl::O_TRUNC, 0644 or return; $hiscore = join "", map "$_\n", map +(join ":", @$_), @$hiscore; length $hiscore == aio_write $fh, 0, (length $hiscore), $hiscore, 0 or return; # always fsync - this file is important aio_fsync $fh and return; close $fh or return; aio_rename "$cf::LOCALDIR/highscore~", "$cf::LOCALDIR/highscore" and return; aio_pathsync "$cf::LOCALDIR"; cf::trace "saved highscore file.\n"; 1 } our $HEADER = " | rank | name | experience | reason | HP |mana |grace|\n"; our $SEP = " +------+--------------------|--------------|----------------------+-----+-----+-----+\n"; our $FORMAT = " | %4s | %-18.18s | %12s | %-20.20s | %3s | %3s | %3s |\n"; our $SCORE_CHANNEL = { id => "highscore", title => "Highscore", tooltip => "Your highscore ranking.", }; sub fmt($$) { my ($pos, $score) = @_; my ($name, $title, $exp, $killer, $map, $hp, $sp, $grace) = @$score; sprintf $FORMAT, defined $pos ? $pos + 1 : "-", $name, $exp, $killer, $hp, $sp, $grace } sub check($) { my ($ob) = @_; my $score = [ $ob->name, length $ob->title ? $ob->title : $ob->contr->title, $ob->stats->exp, $ob->contr->killer_name, $ob->map ? $ob->map->name || $ob->map->path : "", $ob->stats->maxhp, $ob->stats->maxsp, $ob->stats->maxgrace, $ob->uuid, int AE::now, ]; cf::async { my $guard = cf::lock_acquire "highscore:check"; # load hiscore, patch, save hiscore my $hiscore = load_hiscore; cf::get_slot 0.01, 0, "highscore check"; my ($pre, $ins, $save); pop @$hiscore while @$hiscore > $HIGHSCORE_ENTRIES; # find previous entry, and new position for (0 .. $#$hiscore) { $pre //= $_ if $hiscore->[$_][0] eq $score->[0]; $ins //= $_ if $hiscore->[$_][2] < $score->[2]; } cf::cede_to_tick; # we need an "interruptible" block... my $msg; if (defined $pre) { # we are already in the list if ($hiscore->[$pre][2] < $score->[2]) { $msg = "T\n\n" . $SEP . $HEADER . $SEP . (fmt $ins, $score) . (fmt $pre, $hiscore->[$pre]) . $SEP; splice @$hiscore, $pre, 1; splice @$hiscore, $ins, 0, $score; $save = 1; } else { $msg = "T\n\n" . $SEP . $HEADER . $SEP . (fmt $pre , $hiscore->[$pre]) . (fmt undef, $score) . $SEP; } } elsif (defined $ins or @$hiscore < $HIGHSCORE_ENTRIES) { $ins //= @$hiscore; $msg = "T\n\n" . $SEP . $HEADER . $SEP . (fmt $ins, $score) . $SEP; splice @$hiscore, $ins, 0, $score; $save = 1; } else { $msg = "T\n\n" . $SEP . $HEADER . $SEP . (fmt -1 + (scalar @$hiscore), $hiscore->[-1]) . (fmt undef, $score) . $SEP; } cf::info $msg;#d# remove once working stable $ob->send_msg ($SCORE_CHANNEL => $msg, cf::NDI_CLEAR); if ($save) { save_hiscore $hiscore or die "unable to write highscore file: $!"; } }; }