ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/schmorplog.ext
(Generate patch)

Comparing deliantra/server/ext/schmorplog.ext (file contents):
Revision 1.8 by root, Wed Apr 18 17:32:06 2007 UTC vs.
Revision 1.34 by root, Sat Feb 4 01:12:33 2012 UTC

1#! perl 1#! perl # depends=irc mandatory
2 2
3our $UPDATE_LOGINS = Event->idle (repeat => 0, min => 1, data => cf::WF_AUTOCANCEL, cb => Coro::unblock_sub { 3# statistics-package
4
5use Fcntl;
6use Coro::AIO;
7
8CONF EXPORT_RECENTLOG = undef
9CONF EXPORT_RECENTLOG_INTERVAL = 300
10
11#############################################################################
12# stuffs
13
14our %PLAYERSEEN;
15
16# EV, because we call ->start
17our $UPDATE_LOGINS = EV::idle sub {
18 $_[0]->stop;
19
20 cf::async {
4 my ($status, @pl) = ext::commands::who_listing; 21 my ($status, @pl) = ext::commands::who_listing;
5 22
6 #TODO: write files asynchronously 23 my $fh = aio_open "$LOCALDIR/usercount", O_WRONLY | O_CREAT | O_TRUNC, 0644
7 open my $fh, ">", "$LOCALDIR/usercount"; 24 or return;
8 print $fh scalar @pl; 25 aio_write $fh, 0, undef, scalar @pl, 0;
26 aio_close $fh;
9 27
10 open my $fh, ">", "$LOCALDIR/userlisting.html"; 28 my $who;
11 for ($status, @pl) { 29 for ($status, @pl) {
12 s/[<&]//g; 30 s/[<&]//g;
13 print $fh "$_<br />\n"; 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;
14 } 38 };
15}); 39};
40
41cf::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 ++$hitter->{stats_kill}{$ob->name};
51 },
52);
16 53
17cf::player->attach ( 54cf::player->attach (
18 on_login => sub { 55 on_login => sub {
19 my ($pl) = @_; 56 my ($pl) = @_;
57
20 $pl->ob->set_ob_key_value (schmorplog_last_login => time); 58 $pl->ob->kv_set (schmorplog_last_login => time);
21 59
22 $pl->ob->set_ob_key_value (schmorplog_login_count => 60 $pl->ob->kv_set (schmorplog_login_count =>
23 1 + $pl->ob->get_ob_key_value ("schmorplog_login_count")); 61 1 + $pl->ob->kv_get ("schmorplog_login_count"));
24 62
25 (my $client = $pl->ns->version) =~ s/\n/\\n/g; 63 $pl->ob->kv_set (schmorplog_client => $pl->ns->{who_version});
26 64
27 $pl->ob->set_ob_key_value (schmorplog_client => $client); 65 my $name = $pl->ob->name;
28 66
29 ext::schmorp_irc::do_notice (sprintf "%s logged in", $pl->ob->name); 67 ext::irc::do_notice $name . " logged in";
30 68
69 undef $PLAYERSEEN{$name};
31 $UPDATE_LOGINS->start; 70 $UPDATE_LOGINS->start;
32 71
33 warn "LOGIN: ", $pl->ob->name, " from ", $pl->ns->host; 72 cf::trace "LOGIN: ", $pl->ob->name, " from ", $pl->ns->host;
34 }, 73 },
35 on_logout => sub { 74 on_logout => sub {
36 my ($pl, $cleanly) = @_; 75 my ($pl, $cleanly) = @_;
37 $pl->ob->set_ob_key_value (schmorplog_last_logout => time); 76 $pl->ob->kv_set (schmorplog_last_logout => time);
38 ext::schmorp_irc::do_notice (sprintf "%s left", $pl->ob->name); 77 ext::irc::do_notice $pl->ob->name . " left";
39 78
40 $UPDATE_LOGINS->start; 79 $UPDATE_LOGINS->start;
41 80
42 warn "LOGOUT: ", $pl->ob->name, " from ", $pl->ns->host, " ($cleanly)"; 81 cf::trace "LOGOUT: ", $pl->ob->name, " from ", $pl->ns->host, " ($cleanly)";
43 }, 82 },
44 on_birth => sub { 83 on_birth => sub {
45 my ($pl) = @_; 84 my ($pl) = @_;
46 ext::schmorp_irc::do_notice (sprintf "%s was just born", $pl->ob->name);
47 $pl->ob->set_ob_key_value (schmorplog_birthdate => time); 85 $pl->ob->kv_set (schmorplog_birthdate => time);
86 ext::irc::do_notice $pl->ob->name . " was just born";
48 87
49 warn "BIRTH: ", $pl->ob->name, " from ", $pl->ns->host; 88 cf::trace "BIRTH: ", $pl->ob->name, " from ", $pl->ns->host;
50 }, 89 },
51 on_quit => sub { 90 on_quit => sub {
52 my ($pl) = @_; 91 my ($pl) = @_;
53 ext::schmorp_irc::do_notice (sprintf "%s quit the game", $pl->ob->name); 92 ext::irc::do_notice $pl->ob->name . " quit the game";
54 93
55 warn "QUIT: ", $pl->ob->name, " from ", $pl->ns->host; 94 cf::trace "QUIT: ", $pl->ob->name, " from ", $pl->ns->host;
56 }, 95 },
57 on_death => sub { 96 on_death => sub {
58 my ($pl) = @_; 97 my ($pl) = @_;
59 ext::schmorp_irc::do_notice (sprintf "%s was killed by %s", $pl->ob->name, $pl->killer);
60 98
99 my $msg = $pl->expand_cfpod ($pl->ob->name . " was killed by " . $pl->killer_name . ".");
100 ext::irc::do_notice $msg;
101
102 ++$pl->ob->{stats_death}{$pl->killer_name};
103
61 $pl->ob->set_ob_key_value (schmorplog_death_count => 104 $pl->ob->kv_set (schmorplog_death_count =>
62 1 + $pl->ob->get_ob_key_value ("schmorplog_death_count")); 105 1 + $pl->ob->kv_get ("schmorplog_death_count"));
106
107 $_->send_msg ("" => $msg, cf::NDI_VERBATIM)
108 for cf::player::list;
63 }, 109 },
64 on_load => sub { 110 on_load => sub {
65 my ($pl, $path) = @_; 111 my ($pl, $path) = @_;
66 $pl->ob->set_ob_key_value (schmorplog_last_load => time); 112 $pl->ob->kv_set (schmorplog_last_load => time);
67 }, 113 },
68 on_save => sub { 114 on_save => sub {
69 my ($pl, $path) = @_; 115 my ($pl, $path) = @_;
70 $pl->ob->set_ob_key_value (schmorplog_last_save => time); 116 $pl->ob->kv_set (schmorplog_last_save => time);
71 }, 117 },
72); 118);
73 119
120cf::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 my $rep = "$args died a total of " . $pl->ob->kv_get ("schmorplog_death_count") . " times, among them:\n\n";
136
137 for (sort { $s->{$b} <=> $s->{$a} } keys %$s) {
138 $rep .= sprintf " C<%4d> time(s) due to %s.\n", $s->{$_}, $_;
139 }
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 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 }
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 . " - 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 } 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# log "crash" messages, i.e. client exit reasons
171cf::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 cf::error sprintf "clientlog [%s/%s]: %s\n", $ns->host, $name, $msg;
180
181 ()
182};
183
184#############################################################################
185# log max playercount every minute
186
187our $STATSDIR = "$LOCALDIR/maxplayers";
188
189mkdir $STATSDIR;
190
191our $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 # 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 };
217 };
218};
219
220#############################################################################
221# export recentlog
222
223our %RECENT;
224our $EXPORT_RECENTLOG_GRACE = 120;
225
226our $update_w;
227our %need_update;
228
229sub _update_login {
230 my ($login) = @_;
231
232 my $path = (cf::player::playerdir $login) . "/playerdata";
233
234 if (0 >= aio_load $path, my $data) {
235 delete $RECENT{$login};
236 } else {
237 local $_ = $data;
238
239 my $birthdate = /^schmorplog_birthdate (\S+)$/m ? $1 : undef;
240 my $login_count = /^schmorplog_login_count (\S+)$/m ? $1 : 0;
241 my $death_count = /^schmorplog_death_count (\S+)$/m ? $1 : 0;
242 my $last_save = /^schmorplog_last_save (\S+)$/m ? $1 : undef;
243 my $last_login = /^schmorplog_last_login (\S+)$/m ? $1 : undef;
244 my $last_logout = /^schmorplog_last_logout (\S+)$/m ? $1 : undef;
245 my $client = /^schmorplog_client (.*)$/m ? $1 : "?";
246 my $map = /^map (.*)$/m ? $1 : "?";
247
248 return delete $RECENT{$login} unless $last_login;
249
250 $last_logout = $last_save if $last_save > $last_logout && $last_login > $last_logout && $last_save < $NOW - 10 * 60;
251 $last_logout = undef if $last_logout < $last_login;
252
253 return delete $RECENT{$login} unless $last_login > $NOW - 86400 * ($login_count * 7 + 10);
254
255# next if $count < 3 && $login < $NOW - 86400*2;
256 $RECENT{$login} = [$login, $birthdate, $last_login, $login_count, $last_logout, $client, $death_count, $map];
257 }
258}
259
260sub _update {
261 cf::async_ext {
262 $Coro::current->nice (1);
263 $Coro::current->{desc} = "recentlog updater";
264
265 my $lock = cf::lock_acquire "export_recentlog";
266
267 my @logins = keys %need_update; %need_update = ();
268 undef $update_w;
269
270 Coro::AnyEvent::sleep $EXPORT_RECENTLOG_GRACE; # grace time to allow file-saves
271
272 my $t0 = EV::now;
273
274 _update_login $_
275 for @logins;
276
277 cf::get_slot 0.1, 0, "recentlog serialise";
278
279 my $NOW = $cf::NOW;
280
281 cf::replace_file $EXPORT_RECENTLOG, cf::encode_json {
282 version => 1,
283 date => $NOW,
284 data => [
285 sort { ($b->[4] || $NOW) <=> ($a->[4] || $NOW) }
286 values %RECENT
287 ],
288 } or warn "$EXPORT_RECENTLOG: $!";
289
290 cf::trace "recentlog updated (", EV::now - $t0, "s).\n";
291 };
292}
293
294sub update {
295 return unless defined $EXPORT_RECENTLOG;
296
297 $update_w ||= AE::timer $EXPORT_RECENTLOG_INTERVAL - $EXPORT_RECENTLOG_GRACE, 0, \&_update;
298}
299
300sub reload {
301 return unless defined $EXPORT_RECENTLOG;
302
303 my $lock = cf::lock_acquire "export_recentlog";
304
305 cf::async_ext {
306 $lock;
307
308 $Coro::current->{desc} = "recentlog reloader";
309
310 undef $need_update{$_}
311 for @{ +cf::player::list_logins };
312
313 _update;
314 };
315}
316
317cf::player->attach (
318 on_login => sub { undef $need_update{$_[0]->ob->name}; update },
319 on_logout => sub { undef $need_update{$_[0]->ob->name}; update },
320 on_birth => sub { undef $need_update{$_[0]->ob->name}; update },
321 on_death => sub { undef $need_update{$_[0]->ob->name}; update },
322# on_load => sub { undef $need_update{$_[0]->ob->name}; update },
323 on_save => sub { undef $need_update{$_[0]->ob->name}; update },
324);
325
326cf::post_init {
327 reload;
328};
329

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines