ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/schmorplog.ext
Revision: 1.33
Committed: Sat Feb 4 00:43:39 2012 UTC (12 years, 3 months ago) by root
Branch: MAIN
Changes since 1.32: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.22 #! perl # depends=irc mandatory
2 root 1.1
3 root 1.17 # statistics-package
4    
5 root 1.12 use Fcntl;
6     use Coro::AIO;
7    
8 root 1.31 CONF EXPORT_RECENTLOG = undef
9 root 1.32 CONF EXPORT_RECENTLOG_INTERVAL = 300
10 root 1.31
11     #############################################################################
12     # stuffs
13    
14 root 1.26 our %PLAYERSEEN;
15    
16 root 1.31 # EV, because we call ->start
17 root 1.30 our $UPDATE_LOGINS = EV::idle sub {
18 root 1.11 $_[0]->stop;
19    
20 root 1.12 cf::async {
21     my ($status, @pl) = ext::commands::who_listing;
22 root 1.1
23 root 1.12 my $fh = aio_open "$LOCALDIR/usercount", O_WRONLY | O_CREAT | O_TRUNC, 0644
24     or return;
25     aio_write $fh, 0, undef, scalar @pl, 0;
26     aio_close $fh;
27    
28     my $who;
29     for ($status, @pl) {
30     s/[<&]//g;
31     $who .= "$_<br />\n";
32     }
33    
34     my $fh = aio_open "$LOCALDIR/userlisting.html", O_WRONLY | O_CREAT | O_TRUNC, 0644
35     or return;
36     aio_write $fh, 0, undef, $who, 0;
37     aio_close $fh;
38     };
39 root 1.11 };
40 root 1.1
41 root 1.14 cf::object->attach (
42     on_kill => sub {
43     my ($ob, $hitter) = @_;
44    
45     return unless $hitter;
46     $hitter = $hitter->outer_owner;
47     my $pl = $hitter->contr
48     or return;
49    
50 root 1.16 ++$hitter->{stats_kill}{$ob->name};
51 root 1.14 },
52     );
53    
54 root 1.3 cf::player->attach (
55 root 1.1 on_login => sub {
56     my ($pl) = @_;
57 root 1.26
58 root 1.19 $pl->ob->kv_set (schmorplog_last_login => time);
59 root 1.1
60 root 1.19 $pl->ob->kv_set (schmorplog_login_count =>
61     1 + $pl->ob->kv_get ("schmorplog_login_count"));
62 root 1.1
63 root 1.24 $pl->ob->kv_set (schmorplog_client => $pl->ns->{who_version});
64 root 1.1
65 root 1.26 my $name = $pl->ob->name;
66 root 1.1
67 root 1.26 ext::irc::do_notice $name . " logged in";
68    
69     undef $PLAYERSEEN{$name};
70 root 1.1 $UPDATE_LOGINS->start;
71 root 1.5
72 root 1.28 cf::trace "LOGIN: ", $pl->ob->name, " from ", $pl->ns->host;
73 root 1.1 },
74     on_logout => sub {
75 root 1.6 my ($pl, $cleanly) = @_;
76 root 1.19 $pl->ob->kv_set (schmorplog_last_logout => time);
77 root 1.26 ext::irc::do_notice $pl->ob->name . " left";
78 root 1.1
79     $UPDATE_LOGINS->start;
80 root 1.5
81 root 1.28 cf::trace "LOGOUT: ", $pl->ob->name, " from ", $pl->ns->host, " ($cleanly)";
82 root 1.1 },
83     on_birth => sub {
84     my ($pl) = @_;
85 root 1.19 $pl->ob->kv_set (schmorplog_birthdate => time);
86 root 1.26 ext::irc::do_notice $pl->ob->name . " was just born";
87 root 1.5
88 root 1.28 cf::trace "BIRTH: ", $pl->ob->name, " from ", $pl->ns->host;
89 root 1.1 },
90     on_quit => sub {
91     my ($pl) = @_;
92 root 1.26 ext::irc::do_notice $pl->ob->name . " quit the game";
93 root 1.5
94 root 1.28 cf::trace "QUIT: ", $pl->ob->name, " from ", $pl->ns->host;
95 root 1.1 },
96     on_death => sub {
97     my ($pl) = @_;
98 root 1.22
99 root 1.26 my $msg = $pl->expand_cfpod ($pl->ob->name . " was killed by " . $pl->killer_name . ".");
100     ext::irc::do_notice $msg;
101 root 1.1
102 root 1.14 ++$pl->ob->{stats_death}{$pl->killer_name};
103    
104 root 1.19 $pl->ob->kv_set (schmorplog_death_count =>
105     1 + $pl->ob->kv_get ("schmorplog_death_count"));
106 root 1.22
107     $_->send_msg ("" => $msg, cf::NDI_VERBATIM)
108     for cf::player::list;
109 root 1.1 },
110     on_load => sub {
111     my ($pl, $path) = @_;
112 root 1.19 $pl->ob->kv_set (schmorplog_last_load => time);
113 root 1.1 },
114     on_save => sub {
115     my ($pl, $path) = @_;
116 root 1.19 $pl->ob->kv_set (schmorplog_last_save => time);
117 root 1.1 },
118 root 1.3 );
119 root 1.1
120 root 1.17 cf::register_script_function "statistician::talk" => sub {
121     my ($who, $msg, $npc) = @_;
122    
123     my ($cmd, $args) = split /\s+/, $msg, 2;
124    
125     $args ||= $who->name;
126    
127     if ($cmd eq "deaths") {
128     cf::async {
129     my $pl = cf::player::find $args
130     or return $who->reply ($npc, "I don't know any person named '$args'.");
131    
132     my $s = $pl->ob->{stats_death}
133     or return $who->reply ($npc, "$args didn't die even once.");
134    
135 root 1.23 my $rep = "$args died a total of " . $pl->ob->kv_get ("schmorplog_death_count") . " times, among them:\n\n";
136 root 1.17
137     for (sort { $s->{$b} <=> $s->{$a} } keys %$s) {
138 root 1.23 $rep .= sprintf " C<%4d> time(s) due to %s.\n", $s->{$_}, $_;
139 root 1.17 }
140    
141     $who->reply ($npc, $rep);
142     };
143     } elsif ($cmd eq "kills") {
144     cf::async {
145     my $pl = cf::player::find $args
146     or return $who->reply ($npc, "I don't know any person named '$args'.");
147    
148     my $s = $pl->ob->{stats_kill}
149     or return $who->reply ($npc, "I don't know of I<anything> that $args has killed so far.");
150    
151 root 1.23 my $rep = sprintf "Here is a list of all the kills I know about:\n\n";
152     for (sort { $s->{$b} <=> $s->{$a} } keys %$s) {
153     $rep .= sprintf " C<%6s> %s\n", $s->{$_}, $_;
154 root 1.17 }
155    
156     $who->reply ($npc, $rep);
157     };
158     } elsif ($cmd eq "hi") {
159     $who->reply ($npc, "Hello!\n\n"
160     . "I am a statistician, I keep statistics about all people here.\n\n"
161 root 1.23 . " - To know how often somebody died, ask C<deaths> I<playername>\n"
162     . " - To know how many kills somebody scored, ask C<kills> I<playername>");
163 root 1.17 } else {
164     $who->reply ($npc, "No idea what you want of me, how about saying 'hi' first?");
165     }
166    
167     $cmd = lc $cmd;
168     };
169    
170 root 1.21 # log "crash" messages, i.e. client exit reasons
171     cf::register_exticmd clientlog => sub {
172     my ($ns, $msg) = @_;
173    
174     my $name = $ns->pl && $ns->pl->ob ? $ns->pl->ob->name : "<unknown>";
175    
176     $msg =~ y/\x0a\x20-\x7f//cd;
177     $msg =~ s/\s+$//;
178    
179 root 1.28 cf::error sprintf "clientlog [%s/%s]: %s\n", $ns->host, $name, $msg;
180 root 1.21
181     ()
182     };
183    
184 root 1.31 #############################################################################
185 root 1.26 # log max playercount every minute
186    
187     our $STATSDIR = "$LOCALDIR/maxplayers";
188    
189     mkdir $STATSDIR;
190    
191     our $WRITE_MAXPLAYERS = EV::periodic 0, 60, undef, sub {
192     my $now = AE::now;
193     my $cnt = scalar keys %PLAYERSEEN;
194    
195     %PLAYERSEEN = map +($_->ob->name => undef), grep $_->ns, cf::player::list;
196    
197     my @time = gmtime $now;
198    
199     my $path = sprintf "%s/%04d-%02d-%02d", $STATSDIR, $time[5] + 1900, $time[4] + 1, $time[3];
200     my $offs = $time [2] * 60 + $time [1];
201    
202     $cnt++;
203     $cnt = 254 if $cnt > 254;
204     $cnt = chr $cnt;
205    
206     IO::AIO::aio_open $path, O_WRONLY | O_CREAT, 0666, sub {
207     my $fh = shift
208     or return;
209    
210 root 1.27 # the truncate is 1440 extra syscalls, but saves 1439
211     # slow metadata updates.
212     IO::AIO::aio_truncate $fh, 1440, sub {
213     IO::AIO::aio_write $fh, $offs, 1, $cnt, 0, sub {
214     IO::AIO::aio_close $fh;
215     };
216 root 1.26 };
217     };
218     };
219    
220 root 1.31 #############################################################################
221     # export recentlog
222    
223     our %RECENT;
224    
225     our $update_w;
226     our %need_update;
227    
228     sub _update_login {
229     my ($login) = @_;
230    
231     my $path = (cf::player::playerdir $login) . "/playerdata";
232    
233     if (0 >= aio_load $path, my $data) {
234     delete $RECENT{$login};
235     } else {
236     local $_ = $data;
237    
238     my $birthdate = /^schmorplog_birthdate (\S+)$/m ? $1 : undef;
239     my $login_count = /^schmorplog_login_count (\S+)$/m ? $1 : 0;
240     my $death_count = /^schmorplog_death_count (\S+)$/m ? $1 : 0;
241     my $last_save = /^schmorplog_last_save (\S+)$/m ? $1 : undef;
242     my $last_login = /^schmorplog_last_login (\S+)$/m ? $1 : undef;
243     my $last_logout = /^schmorplog_last_logout (\S+)$/m ? $1 : undef;
244     my $client = /^schmorplog_client (.*)$/m ? $1 : "?";
245     my $map = /^map (.*)$/m ? $1 : "?";
246    
247     return unless $last_login;
248    
249     $last_logout = $last_save if $last_save > $last_logout && $last_login > $last_logout && $last_save < $NOW - 10 * 60;
250     $last_logout = undef if $last_logout < $last_login;
251    
252     return unless $last_login > $NOW - 86400 * ($login_count * 7 + 10);
253    
254     # next if $count < 3 && $login < $NOW - 86400*2;
255     $RECENT{$login} = [$login, $birthdate, $last_login, $login_count, $last_logout, $client, $death_count, $map];
256     }
257     }
258    
259     sub _update {
260     cf::async_ext {
261     $Coro::current->nice (1);
262     $Coro::current->{desc} = "recentlog updater";
263    
264 root 1.32 Coro::AnyEvent::sleep 5; # grace time to allow file-saves
265    
266 root 1.31 my $lock = cf::lock_acquire "export_recentlog";
267    
268 root 1.33 my $t0 = EV::now;
269    
270 root 1.31 while (%need_update) {
271     for (keys %need_update) {
272     delete $need_update{$_};
273    
274     _update_login $_;
275     }
276     }
277    
278 root 1.32 undef $update_w;
279    
280 root 1.31 cf::get_slot 0.1, 0, "recentlog serialise";
281    
282     my $NOW = $cf::NOW;
283    
284     cf::replace_file $EXPORT_RECENTLOG, cf::encode_json {
285     version => 1,
286     date => $NOW,
287     data => [
288     sort { ($b->[4] || $NOW) <=> ($a->[4] || $NOW) }
289     values %RECENT
290     ],
291 root 1.32 } or warn "$EXPORT_RECENTLOG: $!";
292 root 1.31
293     cf::trace "recentlog updated (", EV::now - $t0, "s).\n";
294     };
295     }
296    
297     sub update {
298     return unless defined $EXPORT_RECENTLOG;
299    
300     $update_w ||= AE::timer $EXPORT_RECENTLOG_INTERVAL, 0, \&_update;
301     }
302    
303     sub reload {
304     return unless defined $EXPORT_RECENTLOG;
305    
306     my $lock = cf::lock_acquire "export_recentlog";
307    
308     cf::async_ext {
309     $lock;
310    
311     $Coro::current->{desc} = "recentlog reloader";
312    
313     undef $need_update{$_}
314     for @{ +cf::player::list_logins };
315    
316     _update;
317     };
318     }
319    
320     cf::player->attach (
321 root 1.32 on_login => sub { undef $need_update{$_[0]->ob->name}; update },
322     on_logout => sub { undef $need_update{$_[0]->ob->name}; update },
323     on_birth => sub { undef $need_update{$_[0]->ob->name}; update },
324     on_death => sub { undef $need_update{$_[0]->ob->name}; update },
325     # on_load => sub { undef $need_update{$_[0]->ob->name}; update },
326     # on_save => sub { undef $need_update{$_[0]->ob->name}; update },
327 root 1.31 );
328    
329     cf::post_init {
330     reload;
331     };
332