· 9 years ago · Dec 05, 2016, 08:30 PM
1#!/usr/bin/perl
2# HLstatsX Community Edition - Real-time player and clan rankings and statistics
3# Copyleft (L) 2008-20XX Nicholas Hastings (nshastings@gmail.com)
4# http://www.hlxcommunity.com
5#
6# HLstatsX Community Edition is a continuation of
7# ELstatsNEO - Real-time player and clan rankings and statistics
8# Copyleft (L) 2008-20XX Malte Bayer (steam@neo-soft.org)
9# http://ovrsized.neo-soft.org/
10#
11# ELstatsNEO is an very improved & enhanced - so called Ultra-Humongus Edition of HLstatsX
12# HLstatsX - Real-time player and clan rankings and statistics for Half-Life 2
13# http://www.hlstatsx.com/
14# Copyright (C) 2005-2007 Tobias Oetzel (Tobi@hlstatsx.com)
15#
16# HLstatsX is an enhanced version of HLstats made by Simon Garner
17# HLstats - Real-time player and clan rankings and statistics for Half-Life
18# http://sourceforge.net/projects/hlstats/
19# Copyright (C) 2001 Simon Garner
20#
21# This program is free software; you can redistribute it and/or
22# modify it under the terms of the GNU General Public License
23# as published by the Free Software Foundation; either version 2
24# of the License, or (at your option) any later version.
25#
26# This program is distributed in the hope that it will be useful,
27# but WITHOUT ANY WARRANTY; without even the implied warranty of
28# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29# GNU General Public License for more details.
30#
31# You should have received a copy of the GNU General Public License
32# along with this program; if not, write to the Free Software
33# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
34#
35# For support and installation notes visit http://www.hlxcommunity.com
36
37use strict;
38no strict 'vars';
39
40$SIG{HUP} = 'HUP_handler';
41$SIG{INT} = 'INT_handler'; # unix
42$SIG{INT2} = 'INT_handler'; # windows
43
44##
45## Settings
46##
47
48# $opt_configfile - Absolute path and filename of configuration file.
49$opt_configfile = "./hlstats.conf";
50
51# $opt_libdir - Directory to look in for local required files
52# (our *.plib, *.pm files).
53$opt_libdir = "./";
54
55
56##
57##
58################################################################################
59## No need to edit below this line
60##
61
62use Getopt::Long;
63use Time::Local;
64use IO::Socket;
65use IO::Select;
66use DBI;
67use Digest::MD5;
68use Encode;
69use bytes;
70
71require "$opt_libdir/ConfigReaderSimple.pm";
72require "$opt_libdir/TRcon.pm";
73require "$opt_libdir/BASTARDrcon.pm";
74require "$opt_libdir/HLstats_Server.pm";
75require "$opt_libdir/HLstats_Player.pm";
76require "$opt_libdir/HLstats_Game.pm";
77do "$opt_libdir/HLstats_GameConstants.plib";
78do "$opt_libdir/HLstats.plib";
79do "$opt_libdir/HLstats_EventHandlers.plib";
80
81$|=1;
82Getopt::Long::Configure ("bundling");
83
84$last_trend_timestamp = 0;
85
86binmode STDIN, ":utf8";
87binmode STDOUT, ":utf8";
88
89##
90## Functions
91##
92
93sub lookupPlayer
94{
95 my ($saddr, $id, $uniqueid) = @_;
96 if (defined($g_servers{$saddr}->{"srv_players"}->{"$id/$uniqueid"}))
97 {
98 return $g_servers{$saddr}->{"srv_players"}->{"$id/$uniqueid"};
99 }
100 return undef;
101}
102
103sub removePlayer
104{
105 my ($saddr, $id, $uniqueid, $dontUpdateCount) = @_;
106 my $deleteplayer = 0;
107 if(defined($g_servers{$saddr}->{"srv_players"}->{"$id/$uniqueid"}))
108 {
109 $deleteplayer = 1;
110 }
111 else
112 {
113 &::printEvent("400", "Bad attempted delete ($saddr) ($id/$uniqueid)");
114 }
115
116 if ($deleteplayer == 1) {
117 $g_servers{$saddr}->{"srv_players"}->{"$id/$uniqueid"}->playerCleanup();
118 delete($g_servers{$saddr}->{"srv_players"}->{"$id/$uniqueid"});
119 if (!$dontUpdateCount) # double negative, i know...
120 {
121 $g_servers{$saddr}->updatePlayerCount();
122 }
123 }
124}
125
126sub checkBonusRound
127{
128 if ($g_servers{$s_addr}->{bonusroundtime} > 0 && ($::ev_remotetime > ($g_servers{$s_addr}->{bonusroundtime_ts} + $g_servers{$s_addr}->{bonusroundtime}))) {
129 if ($g_servers{$s_addr}->{bonusroundtime_state} == 1) {
130 &printEvent("SERVER", "Bonus Round Expired",1);
131 }
132 $g_servers{$s_addr}->set("bonusroundtime_state",0);
133 }
134
135 if($g_servers{$s_addr}->{bonusroundignore} == 1 && $g_servers{$s_addr}->{bonusroundtime_state} == 1) {
136 return 1;
137 }
138 return 0;
139}
140
141sub is_number ($) { ( $_[0] ^ $_[0] ) eq '0' }
142
143
144#
145# void printNotice (string notice)
146#
147# Prins a debugging notice to stdout.
148#
149
150sub printNotice
151{
152 my ($notice) = @_;
153
154 if ($g_debug > 1) {
155 print ">> $notice\n";
156 }
157}
158
159sub track_hlstats_trend
160{
161 if ($last_trend_timestamp > 0) {
162 if ($last_trend_timestamp+299 < $ev_daemontime) {
163 my $query = "
164 SELECT
165 COUNT(*),
166 a.game
167 FROM
168 hlstats_Players a
169 INNER JOIN
170 (
171 SELECT
172 game
173 FROM
174 hlstats_Servers
175 GROUP BY
176 game
177 ) AS b
178 ON
179 a.game = b.game
180 GROUP BY
181 a.game
182 ";
183 my $result = &execCached("get_total_player_counts", $query);
184 my $insvalues = "";
185 while ( my($total_players, $game) = $result->fetchrow_array) {
186 my $query = "
187 SELECT
188 SUM(kills),
189 SUM(headshots),
190 COUNT(serverId),
191 SUM(act_players),
192 SUM(max_players)
193 FROM
194 hlstats_Servers
195 WHERE
196 game=?
197 ";
198 my $data = &execCached("get_game_stat_counts", $query, "eSQL($game));
199 my ($total_kills, $total_headshots, $total_servers, $act_slots, $max_slots) = $data->fetchrow_array;
200 if ($max_slots > 0) {
201 if ($act_slots > $max_slots) {
202 $act_slots = $max_slots;
203 }
204 }
205 if ($insvalues ne "") {
206 $insvalues .= ",";
207 }
208 $insvalues .= "
209 (
210 $ev_daemontime,
211 '"."eSQL($game)."',
212 $total_players,
213 $total_kills,
214 $total_headshots,
215 $total_servers,
216 $act_slots,
217 $max_slots
218 )
219 ";
220 }
221 if ($insvalues ne "") {
222 &execNonQuery("
223 INSERT INTO
224 hlstats_Trend
225 (
226 timestamp,
227 game,
228 players,
229 kills,
230 headshots,
231 servers,
232 act_slots,
233 max_slots
234 )
235 VALUES $insvalues
236 ");
237 }
238 $last_trend_timestamp = $ev_daemontime;
239 &::printEvent("HLSTATSX", "Insert new server trend timestamp", 1);
240 }
241 } else {
242 $last_trend_timestamp = $ev_daemontime;
243 }
244}
245
246sub send_global_chat
247{
248 my ($message) = @_;
249 while( my($server) = each(%g_servers))
250 {
251 if ($server ne $s_addr && $g_servers{$server}->{"srv_players"})
252 {
253 my @userlist;
254 my %players_temp=%{$g_servers{$server}->{"srv_players"}};
255 my $pcount = scalar keys %players_temp;
256
257 if ($pcount > 0) {
258 while ( my($pl, $b_player) = each(%players_temp) ) {
259 my $b_userid = $b_player->{userid};
260 if ($g_global_chat == 2) {
261 my $b_steamid = $b_player->{uniqueid};
262 if ($g_servers{$server}->is_admin($b_steamid) == 1) {
263 if (($b_player->{display_events} == 1) && ($b_player->{display_chat} == 1)) {
264 push(@userlist, $b_player->{userid});
265 }
266 }
267 } else {
268 if (($b_player->{display_events} == 1) && ($b_player->{display_chat} == 1)) {
269 push(@userlist, $b_player->{userid});
270 }
271 }
272 }
273 $g_servers{$server}->messageMany($message, 0, @userlist);
274 }
275 }
276 }
277}
278
279#
280# void buildEventInsertData ()
281#
282# Ran at startup to init event table queues, build initial queries, and set allowed-null columns
283#
284
285my %g_eventtable_data = ();
286
287sub buildEventInsertData
288{
289 my $insertType = "";
290 $insertType = " DELAYED" if ($db_lowpriority);
291 while ( my ($table, $colsref) = each(%g_eventTables) )
292 {
293 $g_eventtable_data{$table}{queue} = [];
294 $g_eventtable_data{$table}{nullallowed} = 0;
295 $g_eventtable_data{$table}{lastflush} = $ev_daemontime;
296 $g_eventtable_data{$table}{query} = "
297 INSERT$insertType INTO
298 hlstats_Events_$table
299 (
300 eventTime,
301 serverId,
302 map"
303 ;
304 my $j = 0;
305 foreach $i (@{$colsref})
306 {
307 $g_eventtable_data{$table}{query} .= ",\n$i";
308 if (substr($i, 0, 4) eq 'pos_') {
309 $g_eventtable_data{$table}{nullallowed} |= (1 << $j);
310 }
311 $j++;
312 }
313 $g_eventtable_data{$table}{query} .= ") VALUES\n";
314 }
315}
316
317#
318# void recordEvent (string table, array cols, bool getid, [mixed eventData ...])
319#
320# Queues an event for addition to an Events table, flushing when hitting table queue limit.
321#
322
323sub recordEvent
324{
325 my $table = shift;
326 my $unused = shift;
327 my @coldata = @_;
328
329 my $value = "(FROM_UNIXTIME($::ev_unixtime),".$g_servers{$s_addr}->{'id'}.",'".quoteSQL($g_servers{$s_addr}->get_map())."'";
330 $j = 0;
331 for $i (@coldata) {
332 if ($g_eventtable_data{$table}{nullallowed} & (1 << $j) && (!defined($i) || $i eq "")) {
333 $value .= ",NULL";
334 } elsif (!defined($i)) {
335 $value .= ",''";
336 } else {
337 $value .= ",'".quoteSQL($i)."'";
338 }
339 $j++;
340 }
341 $value .= ")";
342
343 push(@{$g_eventtable_data{$table}{queue}}, $value);
344
345 if (scalar(@{$g_eventtable_data{$table}{queue}}) > $g_event_queue_size)
346 {
347 flushEventTable($table);
348 }
349}
350
351sub flushEventTable
352{
353 my ($table) = @_;
354
355 if (scalar(@{$g_eventtable_data{$table}{queue}}) == 0)
356 {
357 return;
358 }
359
360 my $query = $g_eventtable_data{$table}{query};
361 foreach (@{$g_eventtable_data{$table}{queue}})
362 {
363 $query .= $_.",";
364 }
365 $query =~ s/,$//;
366 execNonQuery($query);
367 $g_eventtable_data{$table}{lastflush} = $ev_daemontime;
368 $g_eventtable_data{$table}{queue} = [];
369}
370
371
372#
373# array calcSkill (int skill_mode, int killerSkill, int killerKills, int victimSkill, int victimKills, string weapon)
374#
375# Returns an array, where the first index contains the killer's new skill, and
376# the second index contains the victim's new skill.
377#
378
379sub calcSkill
380{
381 my ($skill_mode, $killerSkill, $killerKills, $victimSkill, $victimKills, $weapon, $killerTeam) = @_;
382 my @newSkill;
383
384 # ignored bots never do a "comeback"
385 return ($g_skill_minchange, $victimSkill) if ($killerSkill < 1);
386 return ($killerSkill + $g_skill_minchange, $victimSkill) if ($victimSkill < 1);
387
388 if ($g_debug > 2) {
389 &printNotice("Begin calcSkill: killerSkill=$killerSkill");
390 &printNotice("Begin calcSkill: victimSkill=$victimSkill");
391 }
392
393 my $modifier = 1.00;
394 # Look up the weapon's skill modifier
395 if (defined($g_games{$g_servers{$s_addr}->{game}}{weapons}{$weapon})) {
396 $modifier = $g_games{$g_servers{$s_addr}->{game}}{weapons}{$weapon}{modifier};
397 }
398
399 # Calculate the new skills
400
401 my $killerSkillChange = 0;
402 if ($g_skill_ratio_cap > 0) {
403 # SkillRatioCap, from *XYZ*SaYnt
404 #
405 # dgh...we want to cap the ratio between the victimkill and killerskill. For example, if the number 1 player
406 # kills a newbie, he gets 1000/5000 * 5 * 1 = 1 points. If gets killed by the newbie, he gets 5000/1000 * 5 *1
407 # = -25 points. Not exactly fair. To fix this, I'm going to cap the ratio to 1/2 and 2/1.
408 # these numbers are designed such that an excellent player will have to get about a 2:1 ratio against noobs to
409 # hold steady in points.
410 my $lowratio = 0.7;
411 my $highratio = 1.0 / $lowratio;
412 my $ratio = ($victimSkill / $killerSkill);
413 if ($ratio < $lowratio) { $ratio = $lowratio; }
414 if ($ratio > $highratio) { $ratio = $highratio; }
415 $killerSkillChange = $ratio * 5 * $modifier;
416 } else {
417 $killerSkillChange = ($victimSkill / $killerSkill) * 5 * $modifier;
418 }
419
420 if ($killerSkillChange > $g_skill_maxchange) {
421 &printNotice("Capping killer skill change of $killerSkillChange to $g_skill_maxchange") if ($g_debug > 2);
422 $killerSkillChange = $g_skill_maxchange;
423 }
424
425 my $victimSkillChange = $killerSkillChange;
426
427 if ($skill_mode == 1)
428 {
429 $victimSkillChange = $killerSkillChange * 0.75;
430 }
431 elsif ($skill_mode == 2)
432 {
433 $victimSkillChange = $killerSkillChange * 0.5;
434 }
435 elsif ($skill_mode == 3)
436 {
437 $victimSkillChange = $killerSkillChange * 0.25;
438 }
439 elsif ($skill_mode == 4)
440 {
441 $victimSkillChange = 0;
442 }
443 elsif ($skill_mode == 5)
444 {
445 #Zombie Panic: Source only
446 #Method suggested by heimer. Survivor's lose half of killer's gain when dying, but Zombie's only lose a quarter.
447 if ($killerTeam eq "Undead")
448 {
449 $victimSkillChange = $killerSkillChange * 0.5;
450 }
451 elsif ($killerTeam eq "Survivor")
452 {
453 $victimSkillChange = $killerSkillChange * 0.25;
454 }
455 }
456
457 if ($victimSkillChange > $g_skill_maxchange) {
458 &printNotice("Capping victim skill change of $victimSkillChange to $g_skill_maxchange") if ($g_debug > 2);
459 $victimSkillChange = $g_skill_maxchange;
460 }
461
462 if ($g_skill_maxchange >= $g_skill_minchange) {
463 if ($killerSkillChange < $g_skill_minchange) {
464 &printNotice("Capping killer skill change of $killerSkillChange to $g_skill_minchange") if ($g_debug > 2);
465 $killerSkillChange = $g_skill_minchange;
466 }
467
468 if (($victimSkillChange < $g_skill_minchange) && ($skill_mode != 4)) {
469 &printNotice("Capping victim skill change of $victimSkillChange to $g_skill_minchange") if ($g_debug > 2);
470 $victimSkillChange = $g_skill_minchange;
471 }
472 }
473 if (($killerKills < $g_player_minkills ) || ($victimKills < $g_player_minkills )) {
474 $killerSkillChange = $g_skill_minchange;
475 if ($skill_mode != 4) {
476 $victimSkillChange = $g_skill_minchange;
477 } else {
478 $victimSkillChange = 0;
479 }
480 }
481
482 $killerSkill += $killerSkillChange;
483 $victimSkill -= $victimSkillChange;
484
485 # we want int not float
486 $killerSkill = sprintf("%d", $killerSkill + 0.5);
487 $victimSkill = sprintf("%d", $victimSkill + 0.5);
488
489 if ($g_debug > 2) {
490 &printNotice("End calcSkill: killerSkill=$killerSkill");
491 &printNotice("End calcSkill: victimSkill=$victimSkill");
492 }
493
494 return ($killerSkill, $victimSkill);
495}
496
497sub calcL4DSkill
498{
499 my ($killerSkill, $weapon, $difficulty) = @_;
500
501 # ignored bots never do a "comeback"
502 #return ($killerSkill, $victimSkill) if ($killerSkill < 1);
503 #return ($killerSkill, $victimSkill) if ($victimSkill < 1);
504
505 if ($g_debug > 2) {
506 &printNotice("Begin calcSkill: killerSkill=$killerSkill");
507 &printNotice("Begin calcSkill: victimSkill=$victimSkill");
508 }
509
510 my $modifier = 1.00;
511 # Look up the weapon's skill modifier
512 if (defined($g_games{$g_servers{$s_addr}->{game}}{weapons}{$weapon})) {
513 $modifier = $g_games{$g_servers{$s_addr}->{game}}{weapons}{$weapon}{modifier};
514 }
515
516 # Calculate the new skills
517
518 $diffweight=0.5;
519 if ($difficulty > 0) {
520 $diffweight = $difficulty / 2;
521 }
522
523 my $killerSkillChange = $pointvalue * $diffweight;
524
525 if ($killerSkillChange > $g_skill_maxchange) {
526 &printNotice("Capping killer skill change of $killerSkillChange to $g_skill_maxchange") if ($g_debug > 2);
527 $killerSkillChange = $g_skill_maxchange;
528 }
529
530 if ($g_skill_maxchange >= $g_skill_minchange) {
531 if ($killerSkillChange < $g_skill_minchange) {
532 &printNotice("Capping killer skill change of $killerSkillChange to $g_skill_minchange") if ($g_debug > 2);
533 $killerSkillChange = $g_skill_minchange;
534 }
535 }
536
537 $killerSkill += $killerSkillChange;
538 # we want int not float
539 $killerSkill = sprintf("%d", $killerSkill + 0.5);
540
541 if ($g_debug > 2) {
542 &printNotice("End calcSkill: killerSkill=$killerSkill");
543 }
544
545 return $killerSkill;
546}
547
548
549# Gives members of 'team' an extra 'reward' skill points. Members of the team
550# who have been inactive (no events) for more than 2 minutes are not rewarded.
551#
552
553sub rewardTeam
554{
555 my ($team, $reward, $actionid, $actionname, $actioncode) = @_;
556 $rcmd = $g_servers{$s_addr}->{broadcasting_command};
557
558 my $player;
559
560 &printNotice("Rewarding team \"$team\" with \"$reward\" skill for action \"$actionid\" ...");
561 my @userlist;
562 foreach $player (values(%g_players)) {
563 my $player_team = $player->{team};
564 my $player_timestamp = $player->{timestamp};
565 if (($g_servers{$s_addr}->{ignore_bots} == 1) && (($player->{is_bot} == 1) || ($player->{userid} <= 0))) {
566 $desc = "(IGNORED) BOT: ";
567 } else {
568 if ($player_team eq $team) {
569 if ($g_debug > 2) {
570 &printNotice("Rewarding " . $player->getInfoString() . " with \"$reward\" skill for action \"$actionid\"");
571 }
572
573 &recordEvent(
574 "TeamBonuses", 0,
575 $player->{playerid},
576 $actionid,
577 $reward
578 );
579 $player->increment("skill", $reward, 1);
580 $player->increment("session_skill", $reward, 1);
581 $player->updateDB();
582 }
583 if ($player->{is_bot} == 0 && $player->{userid} > 0 && $player->{display_events} == 1) {
584 push(@userlist, $player->{userid});
585 }
586 }
587 }
588 if (($g_servers{$s_addr}->{broadcasting_events} == 1) && ($g_servers{$s_addr}->{broadcasting_player_actions} == 1)) {
589 my $coloraction = $g_servers{$s_addr}->{format_action};
590 my $verb = "got";
591 if ($reward < 0) {
592 $verb = "lost";
593 }
594 my $msg = sprintf("%s %s %s points for %s%s", $team, $verb, abs($reward), $coloraction, $actionname);
595 $g_servers{$s_addr}->messageMany($msg, 0, @userlist);
596 }
597}
598
599
600#
601# int getPlayerId (uniqueId)
602#
603# Looks up a player's ID number, from their unique (WON) ID. Returns their PID.
604#
605
606sub getPlayerId
607{
608 my ($uniqueId) = @_;
609
610 my $query = "
611 SELECT
612 playerId
613 FROM
614 hlstats_PlayerUniqueIds
615 WHERE
616 uniqueId='" . &::quoteSQL($uniqueId) . "' AND
617 game='" . $g_servers{$s_addr}->{game} . "'
618 ";
619 my $result = &doQuery($query);
620
621 if ($result->rows > 0) {
622 my ($playerId) = $result->fetchrow_array;
623 $result->finish;
624 return $playerId;
625 } else {
626 $result->finish;
627 return 0;
628 }
629}
630
631
632#
633# int updatePlayerProfile (object player, string field, string value)
634#
635# Updates a player's profile information in the database.
636#
637
638sub updatePlayerProfile
639{
640 my ($player, $field, $value) = @_;
641 $rcmd = $g_servers{$s_addr}->{player_command};
642
643 unless ($player) {
644 &printNotice("updatePlayerInfo: Bad player");
645 return 0;
646 }
647
648 $value = "eSQL($value);
649 if ($value eq "none" || $value eq " ") {
650 $value = "";
651 }
652
653 my $playerName = &abbreviate($player->{name});
654 my $playerId = $player->{playerid};
655
656 &execNonQuery("
657 UPDATE
658 hlstats_Players
659 SET
660 $field='$value'
661 WHERE
662 playerId=$playerId
663 ");
664
665 if ($g_servers{$s_addr}->{player_events} == 1) {
666 my $p_userid = $g_servers{$s_addr}->format_userid($player->{userid});
667 my $p_is_bot = $player->{is_bot};
668 $cmd_str = $rcmd." $p_userid ".$g_servers{$s_addr}->quoteparam("SET command successful for '$playerName'.");
669 $g_servers{$s_addr}->dorcon($cmd_str);
670 }
671 return 1;
672}
673
674
675#
676# mixed getClanId (string name)
677#
678# Looks up a player's clan ID from their name. Compares the player's name to tag
679# patterns in hlstats_ClanTags. Patterns look like: [AXXXXX] (matches 1 to 6
680# letters inside square braces, e.g. [ZOOM]Player) or =\*AAXX\*= (matches
681# 2 to 4 letters between an equals sign and an asterisk, e.g. =*RAGE*=Player).
682#
683# Special characters in the pattern:
684# A matches one character (i.e. a character is required)
685# X matches zero or one characters (i.e. a character is optional)
686# a matches literal A or a
687# x matches literal X or x
688#
689# If no clan exists for the tag, it will be created. Returns the clan's ID, or
690# 0 if the player is not in a clan.
691#
692
693sub getClanId
694{
695 my ($name) = @_;
696 my $clanTag = "";
697 my $clanName = "";
698 my $clanId = 0;
699 my $result = &doQuery("
700 SELECT
701 pattern,
702 position,
703 LENGTH(pattern) AS pattern_length
704 FROM
705 hlstats_ClanTags
706 ORDER BY
707 pattern_length DESC,
708 id
709 ");
710
711 while ( my($pattern, $position) = $result->fetchrow_array) {
712 my $regpattern = quotemeta($pattern);
713 $regpattern =~ s/([A-Za-z0-9]+[A-Za-z0-9_-]*)/\($1\)/; # to find clan name from tag
714 $regpattern =~ s/A/./g;
715 $regpattern =~ s/X/.?/g;
716
717 if ($g_debug > 2) {
718 &printNotice("regpattern=$regpattern");
719 }
720
721 if ((($position eq "START" || $position eq "EITHER") && $name =~ /^($regpattern).+/i) ||
722 (($position eq "END" || $position eq "EITHER") && $name =~ /.+($regpattern)$/i)) {
723
724 if ($g_debug > 2) {
725 &printNotice("pattern \"$regpattern\" matches \"$name\"! 1=\"$1\" 2=\"$2\"");
726 }
727
728 $clanTag = $1;
729 $clanName = $2;
730 last;
731 }
732 }
733
734 unless ($clanTag) {
735 return 0;
736 }
737
738 my $query = "
739 SELECT
740 clanId
741 FROM
742 hlstats_Clans
743 WHERE
744 tag='" . "eSQL($clanTag) . "' AND
745 game='$g_servers{$s_addr}->{game}'
746 ";
747 $result = &doQuery($query);
748
749 if ($result->rows) {
750 ($clanId) = $result->fetchrow_array;
751 $result->finish;
752 return $clanId;
753 } else {
754 # The clan doesn't exist yet, so we create it.
755 $query = "
756 REPLACE INTO
757 hlstats_Clans
758 (
759 tag,
760 name,
761 game
762 )
763 VALUES
764 (
765 '" . "eSQL($clanTag) . "',
766 '" . "eSQL($clanName) . "',
767 '"."eSQL($g_servers{$s_addr}->{game})."'
768 )
769 ";
770 &execNonQuery($query);
771
772 $clanId = $db_conn->{'mysql_insertid'};
773
774 &printNotice("Created clan \"$clanName\" <C:$clanId> with tag "
775 . "\"$clanTag\" for player \"$name\"");
776 return $clanId;
777 }
778}
779
780
781#
782# object getServer (string address, int port)
783#
784# Looks up a server's ID number in the Servers table, by searching for a
785# matching IP address and port. NOTE you must specify IP addresses in the
786# Servers table, NOT hostnames.
787#
788# Returns a new "Server object".
789#
790
791sub getServer
792{
793 my ($address, $port) = @_;
794
795 my $query = "
796 SELECT
797 a.serverId,
798 a.game,
799 a.name,
800 a.rcon_password,
801 a.publicaddress,
802 IFNULL(b.`value`,3) AS game_engine,
803 IFNULL(c.`realgame`, 'hl2mp') AS realgame,
804 IFNULL(a.max_players, 0) AS maxplayers
805
806 FROM
807 hlstats_Servers a LEFT JOIN hlstats_Servers_Config b on a.serverId = b.serverId AND b.`parameter` = 'GameEngine' LEFT JOIN `hlstats_Games` c ON a.game = c.code
808 WHERE
809 address=? AND
810 port=? LIMIT 1
811 ";
812 my @vals = (
813 $address,
814 $port
815 );
816 my $result = &execCached("get_server_information", $query, @vals);
817
818 if ($result->rows) {
819 my ($serverId, $game, $name, $rcon_pass, $publicaddress, $gameengine, $realgame, $maxplayers) = $result->fetchrow_array;
820 $result->finish;
821 if (!defined($g_games{$game})) {
822 $g_games{$game} = new HLstats_Game($game);
823 }
824 # l4d code should be reused for l4d2
825 # trying first using l4d as "realgame" code for l4d2 in db. if default server config settings won't work, will leave as own "realgame" code in db but uncomment line.
826 #$realgame = "l4d" if $realgame eq "l4d2";
827
828 return new HLstats_Server($serverId, $address, $port, $name, $rcon_pass, $game, $publicaddress, $gameengine, $realgame, $maxplayers);
829 } else {
830 $result->finish;
831 return 0;
832 }
833}
834
835#
836#
837#
838#
839#
840
841sub queryServer
842{
843 my ($iaddr, $iport, @query) = @_;
844 my $game = "";
845 my $timeout=2;
846 my $message = IO::Socket::INET->new(Proto=>"udp",Timeout=>$timeout,PeerPort=>$iport,PeerAddr=>$iaddr) or die "Can't make UDP socket: $@";
847 $message->send("\xFF\xFF\xFF\xFFTSource Engine Query\x00");
848 my ($datagram,$flags);
849 my $end = time + $timeout;
850 my $rin = '';
851 vec($rin, fileno($message), 1) = 1;
852
853 my %hash = ();
854
855 while (1) {
856 my $timeleft = $end - time;
857 last if ($timeleft <= 0);
858 my ($nfound, $t) = select(my $rout = $rin, undef, undef, $timeleft);
859 last if ($nfound == 0); # either timeout or end of file
860 $message->recv($datagram,1024,$flags);
861 @hash{qw/key type netver hostname mapname gamedir gamename id numplayers maxplayers numbots dedicated os passreq secure gamever edf port/} = unpack("LCCZ*Z*Z*Z*vCCCCCCCZ*Cv",$datagram);
862 }
863
864 return @hash{@query};
865}
866
867
868sub getServerMod
869{
870 my ($address, $port) = @_;
871 my ($playgame);
872
873 &printEvent ("DETECT", "Querying $address".":$port for gametype");
874
875 my @query = (
876 'gamename',
877 'gamedir',
878 'hostname',
879 'numplayers',
880 'maxplayers',
881 'mapname'
882 );
883
884 my ($gamename, $gamedir, $hostname, $numplayers, $maxplayers, $mapname) = &queryServer($address, $port, @query);
885
886 if ($gamename =~ /^Counter-Strike$/i) {
887 $playgame = "cstrike";
888 } elsif ($gamename =~ /^Counter-Strike/i) {
889 $playgame = "css";
890 } elsif ($gamename =~ /^Team Fortress C/i) {
891 $playgame = "tfc";
892 } elsif ($gamename =~ /^Team Fortress/i) {
893 $playgame = "tf";
894 } elsif ($gamename =~ /^Day of Defeat$/i) {
895 $playgame = "dod";
896 } elsif ($gamename =~ /^Day of Defeat/i) {
897 $playgame = "dods";
898 } elsif ($gamename =~ /^Insurgency/i) {
899 $playgame = "insmod";
900 } elsif ($gamename =~ /^Neotokyo/i) {
901 $playgame = "nts";
902 } elsif ($gamename =~ /^Fortress Forever/i) {
903 $playgame = "ff";
904 } elsif ($gamename =~ /^Age of Chivalry/i) {
905 $playgame = "aoc";
906 } elsif ($gamename =~ /^Dystopia/i) {
907 $playgame = "dystopia";
908 } elsif ($gamename =~ /^Stargate/i) {
909 $playgame = "sgtls";
910 } elsif ($gamename =~ /^Battle Grounds/i) {
911 $playgame = "bg2";
912 } elsif ($gamename =~ /^Hidden/i) {
913 $playgame = "hidden";
914 } elsif ($gamename =~ /^L4D /i) {
915 $playgame = "l4d";
916 } elsif ($gamename =~ /^Left 4 Dead 2/i) {
917 $playgame = "l4d2";
918 } elsif ($gamename =~ /^ZPS /i) {
919 $playgame = "zps";
920 } elsif ($gamename =~ /^NS /i) {
921 $playgame = "ns";
922 } elsif ($gamename =~ /^pvkii/i) {
923 $playgame = "pvkii";
924 } elsif ($gamename =~ /^CSPromod/i) {
925 $playgame = "csp";
926 } elsif ($gamename eq "Half-Life") {
927 $playgame = "valve";
928 } elsif ($gamename eq "Nuclear Dawn") {
929 $playgame = "nucleardawn";
930
931 # We didn't found our mod, trying secondary way. This is required for some games such as FOF and GES and is a fallback for others
932 } elsif ($gamedir =~ /^ges/i) {
933 $playgame = "ges";
934 } elsif ($gamedir =~ /^fistful_of_frags/i || $gamedir =~ /^fof/i) {
935 $playgame = "fof";
936 } elsif ($gamedir =~ /^hl2mp/i) {
937 $playgame = "hl2mp";
938 } elsif ($gamedir =~ /^tfc/i) {
939 $playgame = "tfc";
940 } elsif ($gamedir =~ /^tf/i) {
941 $playgame = "tf";
942 } elsif ($gamedir =~ /^ins/i) {
943 $playgame = "insmod";
944 } elsif ($gamedir =~ /^neotokyo/i) {
945 $playgame = "nts";
946 } elsif ($gamedir =~ /^fortressforever/i) {
947 $playgame = "ff";
948 } elsif ($gamedir =~ /^ageofchivalry/i) {
949 $playgame = "aoc";
950 } elsif ($gamedir =~ /^dystopia/i) {
951 $playgame = "dystopia";
952 } elsif ($gamedir =~ /^sgtls/i) {
953 $playgame = "sgtls";
954 } elsif ($gamedir =~ /^hidden/i) {
955 $playgame = "hidden";
956 } elsif ($gamedir =~ /^left4dead/i) {
957 $playgame = "l4d";
958 } elsif ($gamedir =~ /^left4dead2/i) {
959 $playgame = "l4d2";
960 } elsif ($gamedir =~ /^zps/i) {
961 $playgame = "zps";
962 } elsif ($gamedir =~ /^ns/i) {
963 $playgame = "ns";
964 } elsif ($gamedir =~ /^bg/i) {
965 $playgame = "bg2";
966 } elsif ($gamedir =~ /^pvkii/i) {
967 $playgame = "pvkii";
968 } elsif ($gamedir =~ /^cspromod/i) {
969 $playgame = "csp";
970 } elsif ($gamedir =~ /^valve$/i) {
971 $playgame = "valve";
972 } elsif ($gamedir =~ /^nucleardawn$/i) {
973 $playgame = "nucleardawn";
974 } elsif ($gamedir =~ /^dinodday$/i) {
975 $playgame = "dinodday";
976 } else {
977 # We didn't found our mod, giving up.
978 &printEvent("DETECT", "Failed to get Server Mod");
979 return 0;
980 }
981 &printEvent("DETECT", "Saving server " . $address . ":" . $port . " with gametype " . $playgame);
982 &addServerToDB($address, $port, $hostname, $playgame, $numplayers, $maxplayers, $mapname);
983 return $playgame;
984}
985
986sub addServerToDB
987{
988 my ($address, $port, $name, $game, $act_players, $max_players, $act_map) = @_;
989 my $sql = "INSERT INTO hlstats_Servers (address, port, name, game, act_players, max_players, act_map) VALUES ('$address', $port, '"."eSQL($name)."', '"."eSQL($game)."', $act_players, $max_players, '"."eSQL($act_map)."')";
990 &execNonQuery($sql);
991
992 my $last_id = $db_conn->{'mysql_insertid'};
993 &execNonQuery("DELETE FROM `hlstats_Servers_Config` WHERE serverId = $last_id");
994 &execNonQuery("INSERT INTO `hlstats_Servers_Config` (`serverId`, `parameter`, `value`)
995 SELECT $last_id, `parameter`, `value`
996 FROM `hlstats_Mods_Defaults` WHERE `code` = '';");
997 &execNonQuery("INSERT INTO `hlstats_Servers_Config` (`serverId`, `parameter`, `value`) VALUES
998 ($last_id, 'Mod', '');");
999 &execNonQuery("INSERT INTO `hlstats_Servers_Config` (`serverId`, `parameter`, `value`)
1000 SELECT $last_id, `parameter`, `value`
1001 FROM `hlstats_Games_Defaults` WHERE `code` = '"."eSQL($game)."'
1002 ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
1003 &readDatabaseConfig();
1004
1005 return 1;
1006}
1007
1008#
1009# boolean sameTeam (string team1, string team2)
1010#
1011# This should be expanded later to allow for team alliances (e.g. TFC-hunted).
1012#
1013
1014sub sameTeam
1015{
1016 my ($team1, $team2) = @_;
1017
1018 if (($team1 eq $team2) && (($team1 ne "Unassigned") || ($team2 ne "Unassigned"))) {
1019 return 1;
1020 } else {
1021 return 0;
1022 }
1023}
1024
1025
1026#
1027# string getPlayerInfoString (object player, string ident)
1028#
1029
1030sub getPlayerInfoString
1031{
1032 my ($player) = shift;
1033 my @ident = @_;
1034
1035 if ($player) {
1036 return $player->getInfoString();
1037 } else {
1038 return "(" . join(",", @ident) . ")";
1039 }
1040}
1041
1042
1043
1044#
1045# array getPlayerInfo (string player, string $ipAddr)
1046#
1047# Get a player's name, uid, wonid and team from "Name<uid><wonid><team>".
1048#
1049
1050sub getPlayerInfo
1051{
1052 my ($player, $create_player, $ipAddr) = @_;
1053
1054 if ($player =~ /^(.*?)<(\d+)><([^<>]*)><([^<>]*)>(?:<([^<>]*)>)?.*$/) {
1055 my $name = $1;
1056 my $userid = $2;
1057 my $uniqueid = $3;
1058 my $team = $4;
1059 my $role = $5;
1060 my $bot = 0;
1061 my $haveplayer = 0;
1062
1063 $plainuniqueid = $uniqueid;
1064 $uniqueid =~ s/^STEAM_[0-9]+?\://;
1065
1066 if (($uniqueid eq "Console") && ($team eq "Console")) {
1067 return 0;
1068 }
1069 if ($g_servers{$s_addr}->{play_game} == L4D()) {
1070 #for l4d, create meta player object for each role
1071 if ($uniqueid eq "") {
1072 #infected & witch have blank steamid
1073 if ($name eq "infected") {
1074 $uniqueid = "BOT-Horde";
1075 $team = "Infected";
1076 $userid = -9;
1077 } elsif ($name eq "witch") {
1078 $uniqueid = "BOT-Witch";
1079 $team = "Infected";
1080 $userid = -10;
1081 } else {
1082 return 0;
1083 }
1084 } elsif ($uniqueid eq "BOT") {
1085 #all other bots have BOT for steamid
1086 if ($team eq "Survivor") {
1087 if ($name eq "Nick") {
1088 $userid = -11;
1089 } elsif ($name eq "Ellis") {
1090 $userid = -13;
1091 } elsif ($name eq "Rochelle") {
1092 $userid = -14;
1093 } elsif ($name eq "Coach") {
1094 $userid = -12;
1095 } elsif ($name eq "Louis") {
1096 $userid = -4;
1097 } elsif ($name eq "Zoey") {
1098 $userid = -1;
1099 } elsif ($name eq "Francis") {
1100 $userid = -2;
1101 } elsif ($name eq "Bill") {
1102 $userid = -3;
1103 } else {
1104 &printEvent("ERROR", "No survivor match for $name",0,1);
1105 $userid = -4;
1106 }
1107 } else {
1108 if ($name eq "Smoker") {
1109 $userid = -5;
1110 } elsif ($name eq "Boomer") {
1111 $userid = -6;
1112 } elsif ($name eq "Hunter") {
1113 $userid = -7;
1114 } elsif ($name eq "Spitter") {
1115 $userid = -15;
1116 } elsif ($name eq "Jockey") {
1117 $userid = -16;
1118 } elsif ($name eq "Charger") {
1119 $userid = -17;
1120 } elsif ($name eq "Tank") {
1121 $userid = -8;
1122 } else {
1123 &printEvent("DEBUG", "No infected match for $name",0,1);
1124 $userid = -8;
1125 }
1126 }
1127 $uniqueid = "BOT-".$name;
1128 $name = "BOT-".$name;
1129 }
1130 }
1131
1132 if ($ipAddr eq "none") {
1133 $ipAddr = "";
1134 }
1135
1136 $bot = botidcheck($uniqueid);
1137
1138 if ($g_mode eq "NameTrack") {
1139 $uniqueid = $name;
1140 } else {
1141 if ($g_mode eq "LAN" && !$bot && $userid > 0) {
1142 if ($ipAddr ne "") {
1143 $g_lan_noplayerinfo->{"$s_addr/$userid/$name"} = {
1144 ipaddress => $ipAddr,
1145 userid => $userid,
1146 name => $name,
1147 server => $s_addr
1148 };
1149 $uniqueid = $ipAddr;
1150 } else {
1151 while ( my($index, $player) = each(%g_players) ) {
1152 if (($player->{userid} eq $userid) &&
1153 ($player->{name} eq $name)) {
1154
1155 $uniqueid = $player->{uniqueid};
1156 $haveplayer = 1;
1157 last;
1158 }
1159 }
1160 if (!$haveplayer) {
1161 while ( my($index, $player) = each(%g_lan_noplayerinfo) ) {
1162 if (($player->{server} eq $s_addr) &&
1163 ($player->{userid} eq $userid) &&
1164 ($player->{name} eq $name)) {
1165
1166 $uniqueid = $player->{ipaddress};
1167 $haveplayer = 1;
1168 }
1169 }
1170 }
1171 if (!$haveplayer) {
1172 $uniqueid = "UNKNOWN";
1173 }
1174 }
1175 } else {
1176 # Normal (steamid) mode player and bot, as well as lan mode bots
1177 if ($bot) {
1178 $md5 = Digest::MD5->new;
1179 $md5->add($name);
1180 $md5->add($s_addr);
1181 $uniqueid = "BOT:" . $md5->hexdigest;
1182 $unique_id = $uniqueid if ($g_mode eq "LAN");
1183 }
1184
1185 if ($uniqueid eq "UNKNOWN"
1186 || $uniqueid eq "STEAM_ID_PENDING" || $uniqueid eq "STEAM_ID_LAN"
1187 || $uniqueid eq "VALVE_ID_PENDING" || $uniqueid eq "VALVE_ID_LAN"
1188 ) {
1189 return {
1190 name => $name,
1191 userid => $userid,
1192 uniqueid => $uniqueid,
1193 team => $team
1194 };
1195 }
1196 }
1197 }
1198
1199 if (!$haveplayer)
1200 {
1201 while ( my ($index, $player) = each(%g_players) ) {
1202 # Cannot exit loop early as more than one player can exist with same uniqueid
1203 # (bug? or just bad logging)
1204 # Either way, we disconnect any that don't match the current line
1205 if ($player->{uniqueid} eq $uniqueid) {
1206 $haveplayer = 1;
1207 # Catch players reconnecting without first disconnecting
1208 if ($player->{userid} != $userid) {
1209
1210 &doEvent_Disconnect(
1211 $player->{"userid"},
1212 $uniqueid,
1213 ""
1214 );
1215 $haveplayer = 0;
1216 }
1217 }
1218 }
1219 }
1220
1221 if ($haveplayer) {
1222 my $player = lookupPlayer($s_addr, $userid, $uniqueid);
1223 if ($player) {
1224 # The only time team should go /back/ to unassigned ("") is on mapchange
1225 # (which is already handled in the ChangeMap handler)
1226 # So ignore when team is blank (<>) from lazy log lines
1227 if ($team ne "" && $player->{team} ne $team) {
1228 &doEvent_TeamSelection(
1229 $userid,
1230 $uniqueid,
1231 $team
1232 );
1233 }
1234 if ($role ne "" && $role ne $player->{role}) {
1235 &doEvent_RoleSelection(
1236 $player->{"userid"},
1237 $player->{"uniqueid"},
1238 $role
1239 );
1240 }
1241
1242 $player->updateTimestamp();
1243 }
1244 } else {
1245 if ($userid != 0) {
1246 if ($create_player > 0) {
1247 my $preIpAddr = "";
1248 if ($g_preconnect->{"$s_addr/$userid/$name"}) {
1249 $preIpAddr = $g_preconnect->{"$s_addr/$userid/$name"}->{"ipaddress"};
1250 }
1251 # Add the player to our hash of player objects
1252 $g_servers{$s_addr}->{"srv_players"}->{"$userid/$uniqueid"} = new HLstats_Player(
1253 server => $s_addr,
1254 server_id => $g_servers{$s_addr}->{id},
1255 userid => $userid,
1256 uniqueid => $uniqueid,
1257 plain_uniqueid => $plainuniqueid,
1258 game => $g_servers{$s_addr}->{game},
1259 name => $name,
1260 team => $team,
1261 role => $role,
1262 is_bot => $bot,
1263 display_events => $g_servers{$s_addr}->{default_display_events},
1264 address => (($preIpAddr ne "") ? $preIpAddr : $ipAddr)
1265 );
1266
1267 if ($preIpAddr ne "") {
1268 &printEvent("SERVER", "LATE CONNECT [$name/$userid] - steam userid validated");
1269 &doEvent_Connect($userid, $uniqueid, $preIpAddr);
1270 delete($g_preconnect->{"$s_addr/$userid/$name"});
1271 }
1272 # Increment number of players on server
1273 $g_servers{$s_addr}->updatePlayerCount();
1274 }
1275 } elsif (($g_mode eq "LAN") && (defined($g_lan_noplayerinfo{"$s_addr/$userid/$name"}))) {
1276 if ((!$haveplayer) && ($uniqueid ne "UNKNOWN") && ($create_player > 0)) {
1277 $g_servers{$s_addr}->{srv_players}->{"$userid/$uniqueid"} = new HLstats_Player(
1278 server => $s_addr,
1279 server_id => $g_servers{$s_addr}->{id},
1280 userid => $userid,
1281 uniqueid => $uniqueid,
1282 plain_uniqueid => $plainuniqueid,
1283 game => $g_servers{$s_addr}->{game},
1284 name => $name,
1285 team => $team,
1286 role => $role,
1287 is_bot => $bot
1288 );
1289 delete($g_lan_noplayerinfo{"$s_addr/$userid/$name"});
1290 # Increment number of players on server
1291
1292 $g_servers{$s_addr}->updatePlayerCount();
1293 }
1294 } else {
1295 &printNotice("No player object available for player \"$name\" <U:$userid>");
1296 }
1297 }
1298
1299 return {
1300 name => $name,
1301 userid => $userid,
1302 uniqueid => $uniqueid,
1303 team => $team,
1304 is_bot => $bot
1305 };
1306 } elsif ($player =~ /^(.+)<([^<>]+)>$/) {
1307 my $name = $1;
1308 my $uniqueid = $2;
1309 my $bot = 0;
1310
1311 if (&botidcheck($uniqueid)) {
1312 $md5 = Digest::MD5->new;
1313 $md5->add($ev_daemontime);
1314 $md5->add($s_addr);
1315 $uniqueid = "BOT:" . $md5->hexdigest;
1316 $bot = 1;
1317 }
1318 return {
1319 name => $name,
1320 uniqueid => $uniqueid,
1321 is_bot => $bot
1322 };
1323 } elsif ($player =~ /^<><([^<>]+)><>$/) {
1324 my $uniqueid = $1;
1325 my $bot = 0;
1326 if (&botidcheck($uniqueid)) {
1327 $md5 = Digest::MD5->new;
1328 $md5->add($ev_daemontime);
1329 $md5->add($s_addr);
1330 $uniqueid = "BOT:" . $md5->hexdigest;
1331 $bot = 1;
1332 }
1333 return {
1334 uniqueid => $uniqueid,
1335 is_bot => $bot
1336 };
1337 } else {
1338 return 0;
1339 }
1340}
1341
1342
1343#
1344# hash getProperties (string propstring)
1345#
1346# Parse (key "value") properties into a hash.
1347#
1348
1349sub getProperties
1350{
1351 my ($propstring) = @_;
1352 my %properties;
1353 my $dods_flag = 0;
1354
1355 while ($propstring =~ s/^\s*\((\S+)(?:(?: "(.+?)")|(?: ([^\)]+)))?\)//) {
1356 my $key = $1;
1357 if (defined($2)) {
1358 if ($key eq "player") {
1359 if ($dods_flag == 1) {
1360 $key = "player_a";
1361 $dods_flag++;
1362 } elsif ($dods_flag == 2) {
1363 $key = "player_b";
1364 }
1365 }
1366 $properties{$key} = $2;
1367 } elsif (defined($3)) {
1368 $properties{$key} = $3;
1369 } else {
1370 $properties{$key} = 1; # boolean property
1371 }
1372 if ($key eq "flagindex") {
1373 $dods_flag++;
1374 }
1375 }
1376
1377 return %properties;
1378}
1379
1380
1381#
1382# boolean like (string subject, string compare)
1383#
1384# Returns true if 'subject' equals 'compare' with optional whitespace.
1385#
1386
1387sub like
1388{
1389 my ($subject, $compare) = @_;
1390
1391 if ($subject =~ /^\s*\Q$compare\E\s*$/) {
1392 return 1;
1393 } else {
1394 return 0;
1395 }
1396}
1397
1398
1399#
1400# boolean botidcheck (string uniqueid)
1401#
1402# Returns true if 'uniqueid' is that of a bot.
1403#
1404
1405sub botidcheck
1406{
1407 # needs cleaned up
1408 # added /^00000000\:\d+\:0$/ check for "whichbot"
1409 my ($uniqueid) = @_;
1410 if ($uniqueid eq "BOT" || $uniqueid eq "0" || $uniqueid =~ /^00000000\:\d+\:0$/) {
1411 return 1
1412 }
1413 return 0;
1414}
1415
1416sub isTrackableTeam
1417{
1418 my ($team) = @_;
1419 #if ($team =~ /spectator/i || $team =~ /unassigned/i || $team eq "") {
1420 if ($team =~ /spectator/i || $team eq "") {
1421 return 0;
1422 }
1423 return 1;
1424}
1425
1426sub reloadConfiguration
1427{
1428 &flushAll;
1429 &readDatabaseConfig;
1430}
1431
1432
1433sub flushAll
1434{
1435 # we only need to flush events if we're about to shut down. they are unaffected by server/player deletion
1436 my ($flushevents) = @_;
1437 if ($flushevents)
1438 {
1439 while ( my ($table, $colsref) = each(%g_eventTables) )
1440 {
1441 flushEventTable($table);
1442 }
1443 }
1444
1445 while( my($se, $server) = each(%g_servers))
1446 {
1447 while ( my($pl, $player) = each(%{$server->{"srv_players"}}) )
1448 {
1449 if ($player)
1450 {
1451 $player->playerCleanup();
1452 }
1453 }
1454 $server->flushDB();
1455 }
1456}
1457
1458
1459##
1460## MAIN
1461##
1462
1463# Options
1464
1465$opt_help = 0;
1466$opt_version = 0;
1467
1468$db_host = "localhost";
1469$db_user = "";
1470$db_pass = "";
1471$db_name = "hlstats";
1472$db_lowpriority = 1;
1473
1474$s_ip = "";
1475$s_port = "27500";
1476
1477$g_mailto = "";
1478$g_mailpath = "/bin/mail";
1479$g_mode = "Normal";
1480$g_deletedays = 5;
1481$g_requiremap = 0;
1482$g_debug = 1;
1483$g_nodebug = 0;
1484$g_rcon = 1;
1485$g_rcon_ignoreself = 0;
1486$g_rcon_record = 1;
1487$g_stdin = 0;
1488$g_server_ip = "";
1489$g_server_port = 27015;
1490$g_timestamp = 0;
1491$g_cpanelhack = 0;
1492$g_event_queue_size = 10;
1493$g_dns_resolveip = 1;
1494$g_dns_timeout = 5;
1495$g_skill_maxchange = 100;
1496$g_skill_minchange = 2;
1497$g_skill_ratio_cap = 0;
1498$g_geoip_binary = 0;
1499$g_player_minkills = 50;
1500$g_onlyconfig_servers = 1;
1501$g_track_stats_trend = 0;
1502%g_lan_noplayerinfo = ();
1503%g_preconnect = ();
1504$g_global_banning = 0;
1505$g_log_chat = 0;
1506$g_log_chat_admins = 0;
1507$g_global_chat = 0;
1508$g_ranktype = "skill";
1509$g_gi = undef;
1510
1511my %dysweaponcodes = (
1512 "1" => "Light Katana",
1513 "2" => "Medium Katana",
1514 "3" => "Fatman Fist",
1515 "4" => "Machine Pistol",
1516 "5" => "Shotgun",
1517 "6" => "Laser Rifle",
1518 "7" => "BoltGun",
1519 "8" => "SmartLock Pistols",
1520 "9" => "Assault Rifle",
1521 "10" => "Grenade Launcher",
1522 "11" => "MK-808 Rifle",
1523 "12" => "Tesla Rifle",
1524 "13" => "Rocket Launcher",
1525 "14" => "Minigun",
1526 "15" => "Ion Cannon",
1527 "16" => "Basilisk",
1528 "17" => "Frag Grenade",
1529 "18" => "EMP Grenade",
1530 "19" => "Spider Grenade",
1531 "22" => "Cortex Bomb"
1532);
1533
1534# Usage message
1535
1536$usage = <<EOT
1537Usage: hlstats.pl [OPTION]...
1538Collect statistics from one or more Half-Life2 servers for insertion into
1539a MySQL database.
1540
1541 -h, --help display this help and exit
1542 -v, --version output version information and exit
1543 -d, --debug enable debugging output (-dd for more)
1544 -n, --nodebug disables above; reduces debug level
1545 -m, --mode=MODE player tracking mode (Normal, LAN or NameTrack) [$g_mode]
1546 --db-host=HOST database ip or ip:port [$db_host]
1547 --db-name=DATABASE database name [$db_name]
1548 --db-password=PASSWORD database password (WARNING: specifying the
1549 password on the command line is insecure.
1550 Use the configuration file instead.)
1551 --db-username=USERNAME database username
1552 --dns-resolveip resolve player IP addresses to hostnames
1553 (requires working DNS)
1554 -c,--configfile Specific configfile to use, settings in this file can now
1555 be overidden with commandline settings.
1556 --nodns-resolveip disables above
1557 --dns-timeout=SEC timeout DNS queries after SEC seconds [$g_dns_timeout]
1558 -i, --ip=IP set IP address to listen on for UDP log data
1559 -p, --port=PORT set port to listen on for UDP log data [$s_port]
1560 -r, --rcon enables rcon command exec support (the default)
1561 --norcon disables rcon command exec support
1562 -s, --stdin read log data from standard input, instead of
1563 from UDP socket. Must specify --server-ip
1564 and --server-port to indicate the generator
1565 of the inputted log data (implies --norcon)
1566 --nostdin disables above
1567 --server-ip specify data source IP address for --stdin
1568 --server-port specify data source port for --stdin [$g_server_port]
1569 -t, --timestamp tells HLstatsX:CE to use the timestamp in the log
1570 data, instead of the current time on the
1571 database server, when recording events
1572 --notimestamp disables above
1573 --event-queue-size=SIZE manually set event queue size to control flushing
1574 (recommend 100+ for STDIN)
1575
1576Long options can be abbreviated, where such abbreviation is not ambiguous.
1577Default values for options are indicated in square brackets [...].
1578
1579Most options can be specified in the configuration file:
1580 $opt_configfile
1581Note: Options set on the command line take precedence over options set in the
1582configuration file. The configuration file name is set at the top of hlstats.pl.
1583
1584HLstatsX: Community Edition http://www.hlxcommunity.com
1585EOT
1586;
1587
1588%g_config_servers = ();
1589
1590
1591sub readDatabaseConfig()
1592{
1593 &printEvent("CONFIG", "Reading database config...", 1);
1594 %g_config_servers = ();
1595 %g_servers = ();
1596 %g_games = ();
1597
1598 # elstatsneo: read the servers portion from the mysql database
1599 my $srv_id = &doQuery("SELECT serverId,CONCAT(address,':',port) AS addr FROM hlstats_Servers");
1600 while ( my($serverId,$addr) = $srv_id->fetchrow_array) {
1601 $g_config_servers{$addr} = ();
1602 my $serverConfig = &doQuery("SELECT parameter,value FROM hlstats_Servers_Config WHERE serverId=$serverId");
1603 while ( my($p,$v) = $serverConfig->fetchrow_array) {
1604 $g_config_servers{$addr}{$p} = $v;
1605 }
1606 }
1607 $srv_id->finish;
1608 # hlxce: read the global settings from the database!
1609 my $gsettings = &doQuery("SELECT keyname,value FROM hlstats_Options WHERE opttype <= 1");
1610 while ( my($p,$v) = $gsettings->fetchrow_array) {
1611 if ($g_debug > 1) {
1612 print "Config parameter '$p' = '$v'\n";
1613 }
1614 $tmp = "\$".$directives_mysql{$p}." = '$v'";
1615 #print " -> setting ".$tmp."\n";
1616 eval $tmp;
1617 }
1618 $gsettings->finish;
1619 # setting defaults
1620
1621 &printEvent("DAEMON", "Proxy_Key DISABLED", 1) if ($proxy_key eq "");
1622 while (my($addr, $server) = each(%g_config_servers)) {
1623
1624 if (!defined($g_config_servers{$addr}{"MinPlayers"})) {
1625 $g_config_servers{$addr}{"MinPlayers"} = 6;
1626 }
1627 if (!defined($g_config_servers{$addr}{"DisplayResultsInBrowser"})) {
1628 $g_config_servers{$addr}{"DisplayResultsInBrowser"} = 0;
1629 }
1630 if (!defined($g_config_servers{$addr}{"BroadCastEvents"})) {
1631 $g_config_servers{$addr}{"BroadCastEvents"} = 0;
1632 }
1633 if (!defined($g_config_servers{$addr}{"BroadCastPlayerActions"})) {
1634 $g_config_servers{$addr}{"BroadCastPlayerActions"} = 0;
1635 }
1636 if (!defined($g_config_servers{$addr}{"BroadCastEventsCommand"})) {
1637 $g_config_servers{$addr}{"BroadCastEventsCommand"} = "say";
1638 }
1639 if (!defined($g_config_servers{$addr}{"BroadCastEventsCommandAnnounce"})) {
1640 $g_config_servers{$addr}{"BroadCastEventsCommandAnnounce"} = "say";
1641 }
1642 if (!defined($g_config_servers{$addr}{"PlayerEvents"})) {
1643 $g_config_servers{$addr}{"PlayerEvents"} = 1;
1644 }
1645 if (!defined($g_config_servers{$addr}{"PlayerEventsCommand"})) {
1646 $g_config_servers{$addr}{"PlayerEventsCommand"} = "say";
1647 }
1648 if (!defined($g_config_servers{$addr}{"PlayerEventsCommandOSD"})) {
1649 $g_config_servers{$addr}{"PlayerEventsCommandOSD"} = "";
1650 }
1651 if (!defined($g_config_servers{$addr}{"PlayerEventsCommandHint"})) {
1652 $g_config_servers{$addr}{"PlayerEventsCommandHint"} = "";
1653 }
1654 if (!defined($g_config_servers{$addr}{"PlayerEventsAdminCommand"})) {
1655 $g_config_servers{$addr}{"PlayerEventsAdminCommand"} = "";
1656 }
1657 if (!defined($g_config_servers{$addr}{"ShowStats"})) {
1658 $g_config_servers{$addr}{"ShowStats"} = 1;
1659 }
1660 if (!defined($g_config_servers{$addr}{"AutoTeamBalance"})) {
1661 $g_config_servers{$addr}{"AutoTeamBalance"} = 0;
1662 }
1663 if (!defined($g_config_servers{$addr}{"AutoBanRetry"})) {
1664 $g_config_servers{$addr}{"AutoBanRetry"} = 0;
1665 }
1666 if (!defined($g_config_servers{$addr}{"TrackServerLoad"})) {
1667 $g_config_servers{$addr}{"TrackServerLoad"} = 0;
1668 }
1669 if (!defined($g_config_servers{$addr}{"MinimumPlayersRank"})) {
1670 $g_config_servers{$addr}{"MinimumPlayersRank"} = 0;
1671 }
1672 if (!defined($g_config_servers{$addr}{"Admins"})) {
1673 $g_config_servers{$addr}{"Admins"} = "";
1674 }
1675 if (!defined($g_config_servers{$addr}{"SwitchAdmins"})) {
1676 $g_config_servers{$addr}{"SwitchAdmins"} = 0;
1677 }
1678 if (!defined($g_config_servers{$addr}{"IgnoreBots"})) {
1679 $g_config_servers{$addr}{"IgnoreBots"} = 1;
1680 }
1681 if (!defined($g_config_servers{$addr}{"SkillMode"})) {
1682 $g_config_servers{$addr}{"SkillMode"} = 0;
1683 }
1684 if (!defined($g_config_servers{$addr}{"GameType"})) {
1685 $g_config_servers{$addr}{"GameType"} = 0;
1686 }
1687 if (!defined($g_config_servers{$addr}{"BonusRoundTime"})) {
1688 $g_config_servers{$addr}{"BonusRoundTime"} = 0;
1689 }
1690 if (!defined($g_config_servers{$addr}{"BonusRoundIgnore"})) {
1691 $g_config_servers{$addr}{"BonusRoundIgnore"} = 0;
1692 }
1693 if (!defined($g_config_servers{$addr}{"Mod"})) {
1694 $g_config_servers{$addr}{"Mod"} = "";
1695 }
1696 if (!defined($g_config_servers{$addr}{"EnablePublicCommands"})) {
1697 $g_config_servers{$addr}{"EnablePublicCommands"} = 1;
1698 }
1699 if (!defined($g_config_servers{$addr}{"ConnectAnnounce"})) {
1700 $g_config_servers{$addr}{"ConnectAnnounce"} = 1;
1701 }
1702 if (!defined($g_config_servers{$addr}{"UpdateHostname"})) {
1703 $g_config_servers{$addr}{"UpdateHostname"} = 0;
1704 }
1705 if (!defined($g_config_servers{$addr}{"DefaultDisplayEvents"})) {
1706 $g_config_servers{$addr}{"DefaultDisplayEvents"} = 1;
1707 }
1708 }
1709
1710 &printEvent("CONFIG", "I have found the following server configs in database:", 1);
1711 while (my($addr, $server) = each(%g_config_servers)) {
1712 &printEvent("S_CONFIG", $addr, 1);
1713 }
1714
1715 my $geotell = ((!defined($g_gi)) ? -1 : tell $g_gi{fh});
1716
1717 if ($g_geoip_binary > 0 && $geotell == -1) {
1718 my $geoipfile = "$opt_libdir/GeoLiteCity/GeoLiteCity.dat";
1719 if (-r $geoipfile) {
1720 eval "use Geo::IP::PurePerl"; my $hasGeoIP = $@ ? 0 : 1;
1721 if ($hasGeoIP) {
1722 $g_gi = Geo::IP::PurePerl->open($geoipfile, "GEOIP_STANDARD");
1723 } else {
1724 &printEvent("ERROR", "GeoIP method set to binary file lookup but Geo::IP::PurePerl module NOT FOUND", 1);
1725 $g_gi = undef;
1726 }
1727 } else {
1728 &printEvent("ERROR", "GeoIP method set to binary file lookup but $geoipfile NOT FOUND", 1);
1729 $g_gi = undef;
1730 }
1731 } elsif ($g_geoip_binary == 0 && $geotell > -1) {
1732 close($g_gi{fh});
1733 $g_gi = undef;
1734 }
1735}
1736
1737# Read Config File
1738
1739if ($opt_configfile && -r $opt_configfile) {
1740 $conf = ConfigReaderSimple->new($opt_configfile);
1741 $conf->parse();
1742 %directives = (
1743 "DBHost", "db_host",
1744 "DBUsername", "db_user",
1745 "DBPassword", "db_pass",
1746 "DBName", "db_name",
1747 "DBLowPriority", "db_lowpriority",
1748 "BindIP", "s_ip",
1749 "Port", "s_port",
1750 "DebugLevel", "g_debug",
1751 "CpanelHack", "g_cpanelhack",
1752 "EventQueueSize", "g_event_queue_size"
1753 );
1754
1755 %directives_mysql = (
1756 "version", "g_version",
1757 "MailTo", "g_mailto",
1758 "MailPath", "g_mailpath",
1759 "Mode", "g_mode",
1760 "DeleteDays", "g_deletedays",
1761 "UseTimestamp", "g_timestamp",
1762 "DNSResolveIP", "g_dns_resolveip",
1763 "DNSTimeout", "g_dns_timeout",
1764 "RconIgnoreSelf", "g_rcon_ignoreself",
1765 "Rcon", "g_rcon",
1766 "RconRecord", "g_rcon_record",
1767 "MinPlayers", "g_minplayers",
1768 "SkillMaxChange", "g_skill_maxchange",
1769 "SkillMinChange", "g_skill_minchange",
1770 "PlayerMinKills", "g_player_minkills",
1771 "AllowOnlyConfigServers", "g_onlyconfig_servers",
1772 "TrackStatsTrend", "g_track_stats_trend",
1773 "GlobalBanning", "g_global_banning",
1774 "LogChat", "g_log_chat",
1775 "LogChatAdmins", "g_log_chat_admins",
1776 "GlobalChat", "g_global_chat",
1777 "SkillRatioCap", "g_skill_ratio_cap",
1778 "rankingtype", "g_ranktype",
1779 "UseGeoIPBinary", "g_geoip_binary",
1780 "Proxy_Key", "proxy_key"
1781 );
1782
1783# "Servers", "g_config_servers"
1784 &doConf($conf, %directives);
1785
1786} else {
1787 print "-- Warning: unable to open configuration file '$opt_configfile'\n";
1788}
1789
1790# Read Command Line Arguments
1791
1792%copts = ();
1793
1794GetOptions(
1795 "help|h" => \$copts{opt_help},
1796 "version|v" => \$copts{opt_version},
1797 "debug|d+" => \$copts{g_debug},
1798 "nodebug|n+" => \$copts{g_nodebug},
1799 "mode|m=s" => \$copts{g_mode},
1800 "configfile|c=s" => \$copts{configfile},
1801 "db-host=s" => \$copts{db_host},
1802 "db-name=s" => \$copts{db_name},
1803 "db-password=s" => \$copts{db_pass},
1804 "db-username=s" => \$copts{db_user},
1805 "dns-resolveip!" => \$copts{g_dns_resolveip},
1806 "dns-timeout=i" => \$copts{g_dns_timeout},
1807 "ip|i=s" => \$copts{s_ip},
1808 "port|p=i" => \$copts{s_port},
1809 "rcon!" => \$copts{g_rcon},
1810 "r" => \$copts{g_rcon},
1811 "stdin!" => \$copts{g_stdin},
1812 "s" => \$copts{g_stdin},
1813 "server-ip=s" => \$copts{g_server_ip},
1814 "server-port=i" => \$copts{g_server_port},
1815 "timestamp!" => \$copts{g_timestamp},
1816 "t" => \$copts{g_timestamp},
1817 "event-queue-size" => \$copts{g_event_queue_size}
1818) or die($usage);
1819
1820
1821if ($configfile && -r $configfile) {
1822 $conf = '';
1823 $conf = ConfigReaderSimple->new($configfile);
1824 $conf->parse();
1825 &doConf($conf, %directives);
1826}
1827
1828# these are set above, we then reload them to override values in the actual config
1829setOptionsConf(%copts);
1830
1831if ($g_cpanelhack) {
1832 my $home_dir = $ENV{ HOME };
1833 my $base_module_dir = (-d "$home_dir/perl" ? "$home_dir/perl" : ( getpwuid($>) )[7] . '/perl/');
1834 unshift @INC, map { $base_module_dir . $_ } @INC;
1835}
1836
1837eval {
1838 require Geo::IP::PurePerl;
1839};
1840import Geo::IP::PurePerl;
1841
1842if ($opt_help) {
1843 print $usage;
1844 exit(0);
1845}
1846
1847if ($opt_version) {
1848 &doConnect;
1849 my $result = &doQuery("
1850 SELECT
1851 value
1852 FROM
1853 hlstats_Options
1854 WHERE
1855 keyname='version'
1856 ");
1857
1858 if ($result->rows > 0) {
1859 $g_version = $result->fetchrow_array;
1860 }
1861 $result->finish;
1862 print "\nhlstats.pl (HLstatsX Community Edition) Version $g_version\n"
1863 . "Real-time player and clan rankings and statistics for Half-Life 2\n"
1864 . "Modified (C) 2008-20XX Nicholas Hastings (nshastings@gmail.com)\n"
1865 . "Copyleft (L) 2007-2008 Malte Bayer\n"
1866 . "Modified (C) 2005-2007 Tobias Oetzel (Tobi@hlstatsx.com)\n"
1867 . "Original (C) 2001 by Simon Garner \n\n";
1868
1869 print "Using ConfigReaderSimple module version $ConfigReaderSimple::VERSION\n";
1870 if ($g_rcon) {
1871 print "Using rcon module\n";
1872 }
1873
1874 print "\nThis is free software; see the source for copying conditions. There is NO\n"
1875 . "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n";
1876 exit(0);
1877}
1878
1879# Connect to the database
1880
1881&doConnect;
1882&readDatabaseConfig;
1883&buildEventInsertData;
1884
1885if ($g_mode ne "Normal" && $g_mode ne "LAN" && $g_mode ne "NameTrack") {
1886 $g_mode = "Normal";
1887}
1888
1889$g_debug -= $g_nodebug;
1890$g_debug = 0 if ($g_debug < 0);
1891
1892
1893# Init Timestamp
1894my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
1895$ev_timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
1896$ev_unixtime = time();
1897$ev_daemontime = $ev_unixtime;
1898
1899# Startup
1900
1901&printEvent("HLSTATSX", "HLstatsX:CE $g_version starting...", 1);
1902
1903# Create the UDP & TCP socket
1904
1905if ($g_stdin) {
1906 $g_rcon = 0;
1907 &printEvent("UDP", "UDP listen socket disabled, reading log data from STDIN.", 1);
1908 if (!$g_server_ip || !$g_server_port) {
1909 &printEvent("UDP", "ERROR: You must specify source of STDIN data using --server-ip and --server-port", 1);
1910 &printEvent("UDP", "Example: ./hlstats.pl --stdin --server-ip 12.34.56.78 --server-port 27015", 1);
1911 exit(255);
1912 } else {
1913 &printEvent("UDP", "All data from STDIN will be allocated to server '$g_server_ip:$g_server_port'.", 1);
1914 $s_peerhost = $g_server_ip;
1915 $s_peerport = $g_server_port;
1916 $s_addr = "$s_peerhost:$s_peerport";
1917 }
1918} else {
1919 if ($s_ip) { $ip = $s_ip . ":"; } else { $ip = "port "; }
1920 $s_socket = IO::Socket::INET->new(
1921 Proto=>"udp",
1922 LocalAddr=>"$s_ip",
1923 LocalPort=>"$s_port"
1924 ) or die ("\nCan't setup UDP socket on $ip$s_port: $!\n");
1925
1926 &printEvent("UDP", "Opening UDP listen socket on $ip$s_port ... ok", 1);
1927}
1928
1929if ($g_track_stats_trend > 0) {
1930 &printEvent("HLSTATSX", "Tracking Trend of the stats are enabled", 1);
1931}
1932
1933if ($g_global_banning > 0) {
1934 &printEvent("HLSTATSX", "Global Banning on all servers is enabled", 1);
1935}
1936
1937&printEvent("HLSTATSX", "Maximum Skill Change on all servers are ".$g_skill_maxchange." points", 1);
1938&printEvent("HLSTATSX", "Minimum Skill Change on all servers are ".$g_skill_minchange." points", 1);
1939&printEvent("HLSTATSX", "Minimum Players Kills on all servers are ".$g_player_minkills." kills", 1);
1940
1941if ($g_log_chat > 0) {
1942 &printEvent("HLSTATSX", "Players chat logging is enabled", 1);
1943 if ($g_log_chat_admins > 0) {
1944 &printEvent("HLSTATSX", "Admins chat logging is enabled", 1);
1945 }
1946}
1947
1948if ($g_global_chat == 1) {
1949 &printEvent("HLSTATSX", "Broadcasting public chat to all players is enabled", 1);
1950} elsif ($g_gloabl_chat == 2) {
1951 &printEvent("HLSTATSX", "Broadcasting public chat to admins is enabled", 1);
1952} else {
1953 &printEvent("HLSTATSX", "Broadcasting public chat is disabled", 1);
1954}
1955
1956&printEvent("HLSTATSX", "Event queue size is set to ".$g_event_queue_size, 1);
1957
1958
1959%g_servers = ();
1960
1961&printEvent("HLSTATSX", "HLstatsX:CE is now running ($g_mode mode, debug level $g_debug)", 1);
1962
1963$start_time = time();
1964if ($g_stdin) {
1965 $g_timestamp = 1;
1966 $start_parse_time = time();
1967 $import_logs_count = 0;
1968 &printEvent("IMPORT", "Start importing logs. Every dot signs 500 parsed lines", 1, 1);
1969}
1970
1971# Main data loop
1972$c = 0;
1973
1974sub getLine
1975{
1976 if ($g_stdin) {
1977 return <STDIN>;
1978 } else {
1979 return 1;
1980 }
1981}
1982
1983
1984&execNonQuery("TRUNCATE TABLE hlstats_Livestats");
1985$timeout = 0;
1986$s_output = "";
1987my ($proxy_s_peerhost, $proxy_s_peerport);
1988while ($loop = &getLine()) {
1989
1990 my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time());
1991 $ev_timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
1992 $ev_unixtime = time();
1993 $ev_daemontime = $ev_unixtime; #time()
1994
1995 if ($g_stdin) {
1996 $s_output = $loop;
1997 if (($import_logs_count > 0) && ($import_logs_count % 500 == 0)) {
1998 $parse_time = $ev_unixtime - $start_parse_time;
1999 if ($parse_time == 0) {
2000 $parse_time++;
2001 }
2002 print ". [".($parse_time)." sec (".sprintf("%.3f", (500 / $parse_time)).")]\n";
2003 $start_parse_time = $ev_unixtime;
2004 }
2005 } else {
2006 if(IO::Select->new($s_socket)->can_read(2)) { # 2 second timeout
2007 $s_socket->recv($s_output, 1024);
2008 $s_output = decode( 'utf8', $s_output );
2009 $timeout = 0;
2010 } else {
2011 $timeout++;
2012 if ($timeout % 60 == 0) {
2013 &printEvent("HLSTATSX", "No data since 120 seconds");
2014 }
2015 }
2016
2017 if (($s_output =~ /^.*PROXY Key=(.+) (.*)PROXY.+/) && $proxy_key ne "") {
2018 $rproxy_key = $1;
2019 $s_addr = $2;
2020
2021 if ($s_addr ne "") {
2022 ($s_peerhost, $s_peerport) = split(/:/, $s_addr);
2023 }
2024
2025 $proxy_s_peerhost = $s_socket->peerhost;
2026 $proxy_s_peerport = $s_socket->peerport;
2027 &printEvent("PROXY", "Detected proxy call from $proxy_s_peerhost:$proxy_s_peerport") if ($d_debug > 2);
2028
2029
2030 if ($proxy_key eq $rproxy_key) {
2031 $s_output =~ s/PROXY.*PROXY //;
2032 if ($s_output =~ /^C;HEARTBEAT;/) {
2033 &printEvent("PROXY, Heartbeat request from $proxy_s_peerhost:$proxy_s_peerport");
2034 } elsif ($s_output =~ /^C;RELOAD;/) {
2035 &printEvent("PROXY, Reload request from $proxy_s_peerhost:$proxy_s_peerport");
2036 } elsif ($s_output =~ /^C;KILL;/) {
2037 &printEvent("PROXY, Kill request from $proxy_s_peerhost:$proxy_s_peerport");
2038 } else {
2039 &printEvent("PROXY", $s_output);
2040 }
2041 } else {
2042 &printEvent("PROXY", "proxy_key mismatch, dropping package");
2043 &printEvent("PROXY", $s_output) if ($g_debug > 2);
2044 $s_output = "";
2045 next;
2046 }
2047 } else {
2048 # Reset the proxy stuff and use it as "normal"
2049 $rproxy_key = "";
2050 $proxy_s_peerhost = "";
2051 $proxy_s_peerport = "";
2052
2053 $s_peerhost = $s_socket->peerhost;
2054 $s_peerport = $s_socket->peerport;
2055
2056 $s_addr = "$s_peerhost:$s_peerport";
2057 }
2058 }
2059
2060 if ($timeout == 0) {
2061 my ($address, $port);
2062 my @data = split ";", $s_output;
2063 $cmd = $data[0];
2064 if ($cmd eq "C" && ($s_peerhost eq "127.0.0.1" || (($proxy_key eq $rproxy_key) && $proxy_key ne ""))) {
2065 &printEvent("CONTROL", "Command received: ".$data[1], 1);
2066 if ($proxy_s_peerhost ne "" && $proxy_s_peerport ne "") {
2067 $address = $proxy_s_peerhost;
2068 $port = $proxy_s_peerport;
2069 } else {
2070 $address = $s_peerhost;
2071 $port = $s_peerport;
2072 }
2073
2074 $s_addr = "$address:$port";
2075
2076 my $dest = sockaddr_in($port, inet_aton($address));
2077 if ($data[1] eq "HEARTBEAT") {
2078 my $msg = "Heartbeat OK";
2079 $bytes = send($::s_socket, $msg, 0, $dest);
2080 &printEvent("CONTROL", "Send heartbeat status to frontend at '$address:$port'", 1);
2081 } else {
2082 my $msg = "OK, EXECUTING COMMAND: ".$data[1];
2083 $bytes = send($::s_socket, $msg, 0, $dest);
2084 &printEvent("CONTROL", "Sent $bytes bytes to frontend at '$address:$port'", 1);
2085 }
2086
2087 if ($data[1] eq "RELOAD") {
2088 &printEvent("CONTROL", "Re-Reading Configuration by request from Frontend...", 1);
2089 &reloadConfiguration;
2090 }
2091
2092 if ($data[1] eq "KILL") {
2093 &printEvent("CONTROL", "SHUTTING DOWN SCRIPT", 1);
2094 &flushAll;
2095 die "Exit script by request";
2096 }
2097
2098 next;
2099 }
2100 $s_output =~ s/[\r\n\0]//g; # remove naughty characters
2101 $s_output =~ s/\[No.C-D\]//g; # remove [No C-D] tag
2102 $s_output =~ s/\[OLD.C-D\]//g; # remove [OLD C-D] tag
2103 $s_output =~ s/\[NOCL\]//g; # remove [NOCL] tag
2104
2105 # Get the server info, if we know the server, otherwise ignore the data
2106 if (!defined($g_servers{$s_addr})) {
2107 if (($g_onlyconfig_servers == 1) && (!defined($g_config_servers{$s_addr}))) {
2108 # HELLRAISER disabled this for testing
2109 &printEvent(997, "NOT ALLOWED SERVER: " . $s_output);
2110 next;
2111 } elsif (!defined($g_config_servers{$s_addr})) { # create std cfg.
2112 my %std_cfg;
2113 $std_cfg{"MinPlayers"} = 6;
2114 $std_cfg{"HLStatsURL"} = "";
2115 $std_cfg{"DisplayResultsInBrowser"} = 0;
2116 $std_cfg{"BroadCastEvents"} = 0;
2117 $std_cfg{"BroadCastPlayerActions"} = 0;
2118 $std_cfg{"BroadCastEventsCommand"} = "say";
2119 $std_cfg{"BroadCastEventsCommandAnnounce"} = "say",
2120 $std_cfg{"PlayerEvents"} = 1;
2121 $std_cfg{"PlayerEventsCommand"} = "say";
2122 $std_cfg{"PlayerEventsCommandOSD"} = "",
2123 $std_cfg{"PlayerEventsCommandHint"} = "",
2124 $std_cfg{"PlayerEventsAdminCommand"} = "";
2125 $std_cfg{"ShowStats"} = 1;
2126 $std_cfg{"TKPenalty"} = 50;
2127 $std_cfg{"SuicidePenalty"} = 5;
2128 $std_cfg{"AutoTeamBalance"} = 0;
2129 $std_cfg{"AutobanRetry"} = 0;
2130 $std_cfg{"TrackServerLoad"} = 0;
2131 $std_cfg{"MinimumPlayersRank"} = 0;
2132 $std_cfg{"EnablePublicCommands"} = 1;
2133 $std_cfg{"Admins"} = "";
2134 $std_cfg{"SwitchAdmins"} = 0;
2135 $std_cfg{"IgnoreBots"} = 1;
2136 $std_cfg{"SkillMode"} = 0;
2137 $std_cfg{"GameType"} = 0;
2138 $std_cfg{"Mod"} = "";
2139 $std_cfg{"BonusRoundIgnore"} = 0;
2140 $std_cfg{"BonusRoundTime"} = 20;
2141 $std_cfg{"UpdateHostname"} = 0;
2142 $std_cfg{"ConnectAnnounce"} = 1;
2143 $std_cfg{"DefaultDisplayEvents"} = 1;
2144 %{$g_config_servers{$s_addr}} = %std_cfg;
2145 &printEvent("CFG", "Created default config for unknown server [$s_addr]");
2146 &printEvent("DETECT", "New server with game: " . &getServerMod($s_peerhost, $s_peerport));
2147 }
2148
2149 if ($g_config_servers{$s_addr}) {
2150 my $tempsrv = &getServer($s_peerhost, $s_peerport);
2151 next if ($tempsrv == 0);
2152 $g_servers{$s_addr} = $tempsrv;
2153 my %s_cfg = %{$g_config_servers{$s_addr}};
2154 $g_servers{$s_addr}->set("minplayers", $s_cfg{"MinPlayers"});
2155 $g_servers{$s_addr}->set("hlstats_url", $s_cfg{"HLStatsURL"});
2156 if ($s_cfg{"DisplayResultsInBrowser"} > 0) {
2157 $g_servers{$s_addr}->set("use_browser", 1);
2158 &printEvent("SERVER", "Query results will displayed in valve browser", 1);
2159 } else {
2160 $g_servers{$s_addr}->set("use_browser", 0);
2161 &printEvent("SERVER", "Query results will not displayed in valve browser", 1);
2162 }
2163 if ($s_cfg{"ShowStats"} == 1) {
2164 $g_servers{$s_addr}->set("show_stats", 1);
2165 &printEvent("SERVER", "Showing stats is enabled", 1);
2166 } else {
2167 $g_servers{$s_addr}->set("show_stats", 0);
2168 &printEvent("SERVER", "Showing stats is disabled", 1);
2169 }
2170 if ($s_cfg{"BroadCastEvents"} == 1) {
2171 $g_servers{$s_addr}->set("broadcasting_events", 1);
2172 $g_servers{$s_addr}->set("broadcasting_player_actions", $s_cfg{"BroadCastPlayerActions"});
2173 $g_servers{$s_addr}->set("broadcasting_command", $s_cfg{"BroadCastEventsCommand"});
2174 if ($s_cfg{"BroadCastEventsCommandAnnounce"} eq "ma_hlx_csay") {
2175 $s_cfg{"BroadCastEventsCommandAnnounce"} = $s_cfg{"BroadCastEventsCommandAnnounce"}." #all";
2176 }
2177 $g_servers{$s_addr}->set("broadcasting_command_announce", $s_cfg{"BroadCastEventsCommandAnnounce"});
2178
2179 &printEvent("SERVER", "Broadcasting Live-Events with \"".$s_cfg{"BroadCastEventsCommand"}."\" is enabled", 1);
2180 if ($s_cfg{"BroadCastEventsCommandAnnounce"} ne "") {
2181 &printEvent("SERVER", "Broadcasting Announcements with \"".$s_cfg{"BroadCastEventsCommandAnnounce"}."\" is enabled", 1);
2182 }
2183 } else {
2184 $g_servers{$s_addr}->set("broadcasting_events", 0);
2185 &printEvent("SERVER", "Broadcasting Live-Events is disabled", 1);
2186 }
2187 if ($s_cfg{"PlayerEvents"} == 1) {
2188 $g_servers{$s_addr}->set("player_events", 1);
2189 $g_servers{$s_addr}->set("player_command", $s_cfg{"PlayerEventsCommand"});
2190 $g_servers{$s_addr}->set("player_command_osd", $s_cfg{"PlayerEventsCommandOSD"});
2191 $g_servers{$s_addr}->set("player_command_hint", $s_cfg{"PlayerEventsCommandHint"});
2192 $g_servers{$s_addr}->set("player_admin_command", $s_cfg{"PlayerEventsAdminCommand"});
2193 &printEvent("SERVER", "Player Event-Handler with \"".$s_cfg{"PlayerEventsCommand"}."\" is enabled", 1);
2194 if ($s_cfg{"PlayerEventsCommandOSD"} ne "") {
2195 &printEvent("SERVER", "Displaying amx style menu with \"".$s_cfg{"PlayerEventsCommandOSD"}."\" is enabled", 1);
2196 }
2197 } else {
2198 $g_servers{$s_addr}->set("player_events", 0);
2199 &printEvent("SERVER", "Player Event-Handler is disabled", 1);
2200 }
2201 if ($s_cfg{"DefaultDisplayEvents"} > 0) {
2202 $g_servers{$s_addr}->set("default_display_events", "1");
2203 &printEvent("SERVER", "New Players defaulting to show event messages", 1);
2204 } else {
2205 $g_servers{$s_addr}->set("default_display_events", "0");
2206 &printEvent("SERVER", "New Players defaulting to NOT show event messages", 1);
2207 }
2208 if ($s_cfg{"TrackServerLoad"} > 0) {
2209 $g_servers{$s_addr}->set("track_server_load", "1");
2210 &printEvent("SERVER", "Tracking server load is enabled", 1);
2211 } else {
2212 $g_servers{$s_addr}->set("track_server_load", "0");
2213 &printEvent("SERVER", "Tracking server load is disabled", 1);
2214 }
2215
2216 if ($s_cfg{"TKPenalty"} > 0) {
2217 $g_servers{$s_addr}->set("tk_penalty", $s_cfg{"TKPenalty"});
2218 &printEvent("SERVER", "Penalty team kills with ".$s_cfg{"TKPenalty"}." points", 1);
2219 }
2220 if ($s_cfg{"SuicidePenalty"} > 0) {
2221 $g_servers{$s_addr}->set("suicide_penalty", $s_cfg{"SuicidePenalty"});
2222 &printEvent("SERVER", "Penalty suicides with ".$s_cfg{"SuicidePenalty"}." points", 1);
2223 }
2224 if ($s_cfg{"BonusRoundTime"} > 0) {
2225 $g_servers{$s_addr}->set("bonusroundtime", $s_cfg{"BonusRoundTime"});
2226 &printEvent("SERVER", "Bonus Round time set to: ".$s_cfg{"BonusRoundTime"}, 1);
2227 }
2228 if ($s_cfg{"BonusRoundIgnore"} > 0) {
2229 $g_servers{$s_addr}->set("bonusroundignore", $s_cfg{"BonusRoundIgnore"});
2230 &printEvent("SERVER", "Bonus Round is being ignored. Length: (".$s_cfg{"BonusRoundTime"}.")", 1);
2231 }
2232 if ($s_cfg{"AutoTeamBalance"} > 0) {
2233 $g_servers{$s_addr}->set("ba_enabled", "1");
2234 &printEvent("TEAMS", "Auto-Team-Balancing is enabled", 1);
2235 } else {
2236 $g_servers{$s_addr}->set("ba_enabled", "0");
2237 &printEvent("TEAMS", "Auto-Team-Balancing is disabled", 1);
2238 }
2239 if ($s_cfg{"AutoBanRetry"} > 0) {
2240 $g_servers{$s_addr}->set("auto_ban", "1");
2241 &printEvent("TEAMS", "Auto-Retry-Banning is enabled", 1);
2242 } else {
2243 $g_servers{$s_addr}->set("auto_ban", "0");
2244 &printEvent("TEAMS", "Auto-Retry-Banning is disabled", 1);
2245 }
2246
2247 if ($s_cfg{"MinimumPlayersRank"} > 0) {
2248 $g_servers{$s_addr}->set("min_players_rank", $s_cfg{"MinimumPlayersRank"});
2249 &printEvent("SERVER", "Requires minimum players rank is enabled [MinPos:".$s_cfg{"MinimumPlayersRank"}."]", 1);
2250 } else {
2251 $g_servers{$s_addr}->set("min_players_rank", "0");
2252 &printEvent("SERVER", "Requires minimum players rank is disabled", 1);
2253 }
2254
2255 if ($s_cfg{"EnablePublicCommands"} > 0) {
2256 $g_servers{$s_addr}->set("public_commands", $s_cfg{"EnablePublicCommands"});
2257 &printEvent("SERVER", "Public chat commands are enabled", 1);
2258 } else {
2259 $g_servers{$s_addr}->set("public_commands", "0");
2260 &printEvent("SERVER", "Public chat commands are disabled", 1);
2261 }
2262
2263 if ($s_cfg{"Admins"} ne "") {
2264 @{$g_servers{$s_addr}->{admins}} = split(/,/, $s_cfg{"Admins"});
2265 foreach(@{$g_servers{$s_addr}->{admins}})
2266 {
2267 $_ =~ s/^STEAM_[0-9]+?\://i;
2268 }
2269 &printEvent("SERVER", "Admins: ".$s_cfg{"Admins"}, 1);
2270 }
2271
2272 if ($s_cfg{"SwitchAdmins"} > 0) {
2273 $g_servers{$s_addr}->set("switch_admins", "1");
2274 &printEvent("TEAMS", "Switching Admins on Auto-Team-Balance is enabled", 1);
2275 } else {
2276 $g_servers{$s_addr}->set("switch_admins", "0");
2277 &printEvent("TEAMS", "Switching Admins on Auto-Team-Balance is disabled", 1);
2278 }
2279
2280 if ($s_cfg{"IgnoreBots"} > 0) {
2281 $g_servers{$s_addr}->set("ignore_bots", "1");
2282 &printEvent("SERVER", "Ignoring bots is enabled", 1);
2283 } else {
2284 $g_servers{$s_addr}->set("ignore_bots", "0");
2285 &printEvent("SERVER", "Ignoring bots is disabled", 1);
2286 }
2287 $g_servers{$s_addr}->set("skill_mode", $s_cfg{"SkillMode"});
2288 &printEvent("SERVER", "Using skill mode ".$s_cfg{"SkillMode"}, 1);
2289
2290 if ($s_cfg{"GameType"} == 1) {
2291 $g_servers{$s_addr}->set("game_type", $s_cfg{"GameType"});
2292 &printEvent("SERVER", "Game type: Counter-Strike: Source - Deathmatch", 1);
2293 } else {
2294 $g_servers{$s_addr}->set("game_type", "0");
2295 &printEvent("SERVER", "Game type: Normal", 1);
2296 }
2297
2298 $g_servers{$s_addr}->set("mod", $s_cfg{"Mod"});
2299
2300 if ($s_cfg{"Mod"} ne "") {
2301 &printEvent("SERVER", "Using plugin ".$s_cfg{"Mod"}." for internal functions!", 1);
2302 }
2303 if ($s_cfg{"ConnectAnnounce"} == 1) {
2304 $g_servers{$s_addr}->set("connect_announce", $s_cfg{"ConnectAnnounce"});
2305 &printEvent("SERVER", "Connect Announce is enabled", 1);
2306 } else {
2307 $g_servers{$s_addr}->set("connect_announce", "0");
2308 &printEvent("SERVER", "Connect Announce is disabled", 1);
2309 }
2310 if ($s_cfg{"UpdateHostname"} == 1) {
2311 $g_servers{$s_addr}->set("update_hostname", $s_cfg{"UpdateHostname"});
2312 &printEvent("SERVER", "Auto-updating hostname is enabled", 1);
2313 } else {
2314 $g_servers{$s_addr}->set("update_hostname", "0");
2315 &printEvent("SERVER", "Auto-updating hostname is disabled", 1);
2316 }
2317 $g_servers{$s_addr}->get_game_mod_opts();
2318 }
2319 }
2320
2321 if (!$g_servers{$s_addr}->{"srv_players"})
2322 {
2323 $g_servers{$s_addr}->{"srv_players"} = ();
2324 %g_players=();
2325 }
2326 else
2327 {
2328 %g_players=%{$g_servers{$s_addr}->{"srv_players"}};
2329 }
2330
2331 # Get the datestamp (or complain)
2332 #if ($s_output =~ s/^.*L (\d\d)\/(\d\d)\/(\d{4}) - (\d\d):(\d\d):(\d\d):\s*//)
2333
2334 #$is_streamed = 0;
2335 #$test_for_date = 0;
2336 #$is_streamed = ($s_output !~ m/^L\s*/);
2337
2338 #if ( !$is_streamed ) {
2339 # $test_for_date = ($s_output =~ s/^L (\d\d)\/(\d\d)\/(\d{4}) - (\d\d):(\d\d):(\d\d):\s*//);
2340 #} else {
2341 # $test_for_date = ($s_output =~ s/^\S*L (\d\d)\/(\d\d)\/(\d{4}) - (\d\d):(\d\d):(\d\d):\s*//);
2342 #}
2343
2344 #if ($test_for_date)
2345
2346 # EXPLOIT FIX
2347
2348 if ($s_output =~ s/^(?:.*?)?L (\d\d)\/(\d\d)\/(\d{4}) - (\d\d):(\d\d):(\d\d):\s*//) {
2349 $ev_month = $1;
2350 $ev_day = $2;
2351 $ev_year = $3;
2352 $ev_hour = $4;
2353 $ev_min = $5;
2354 $ev_sec = $6;
2355 $ev_time = "$ev_hour:$ev_min:$ev_sec";
2356 $ev_remotetime = timelocal($ev_sec,$ev_min,$ev_hour,$ev_day,$ev_month-1,$ev_year);
2357
2358 if ($g_timestamp) {
2359 $ev_timestamp = "$ev_year-$ev_month-$ev_day $ev_time";
2360 $ev_unixtime = $ev_remotetime;
2361 if ($g_stdin)
2362 {
2363 $ev_daemontime = $ev_unixtime;
2364 }
2365 }
2366 } else {
2367 &printEvent(998, "MALFORMED DATA: " . $s_output);
2368 next;
2369 }
2370
2371 if ($g_debug >= 4) {
2372 print $s_addr.": \"".$s_output."\"\n";
2373 }
2374
2375 if (($g_stdin == 0) && ($g_servers{$s_addr}->{last_event} > 0) && ( ($ev_unixtime - $g_servers{$s_addr}->{last_event}) > 299) ) {
2376 $g_servers{$s_addr}->set("map", "");
2377 $g_servers{$s_addr}->get_map();
2378 }
2379
2380 $g_servers{$s_addr}->set("last_event", $ev_unixtime);
2381
2382 # Now we parse the events.
2383
2384 my $ev_type = 0;
2385 my $ev_status = "";
2386 my $ev_team = "";
2387 my $ev_player = 0;
2388 my $ev_verb = "";
2389 my $ev_obj_a = "";
2390 my $ev_obj_b = "";
2391 my $ev_obj_c = "";
2392 my $ev_properties = "";
2393 my %ev_properties = ();
2394 my %ev_player = ();
2395
2396 # pvkii parrot log lines also fit the death line parsing
2397 if ($g_servers{$s_addr}->{play_game} == PVKII()
2398 && $s_output =~ /^
2399 "(.+?(?:<[^>]*>){3})" # player string
2400 \s[a-z]{6}\s # 'killed'
2401 "npc_parrot<.+?>" # parrot string
2402 \s[a-z]{5}\s[a-z]{2}\s # 'owned by'
2403 "(.+?(?:<[^>]*>){3})" # owner string
2404 \s[a-z]{4}\s # 'with'
2405 "([^"]*)" #weapon
2406 (.*) #properties
2407 /x)
2408 {
2409 $ev_player = $1; # player
2410 $ev_obj_b = $2; # victim
2411 $ev_obj_c = $3; # weapon
2412 $ev_properties = $4;
2413 %ev_properties_hash = &getProperties($ev_properties);
2414
2415 my $playerinfo = &getPlayerInfo($ev_player, 1);
2416 my $victiminfo = &getPlayerInfo($ev_obj_b, 1);
2417 $ev_type = 10;
2418
2419 if ($playerinfo) {
2420 if ($victiminfo) {
2421 $ev_status = &doEvent_PlayerPlayerAction(
2422 $playerinfo->{"userid"},
2423 $playerinfo->{"uniqueid"},
2424 $victiminfo->{"userid"},
2425 $victiminfo->{"uniqueid"},
2426 "killed_parrot",
2427 undef,
2428 undef,
2429 undef,
2430 undef,
2431 undef,
2432 undef,
2433 &getProperties($ev_properties)
2434 );
2435 }
2436
2437 $ev_type = 11;
2438
2439 $ev_status = &doEvent_PlayerAction(
2440 $playerinfo->{"userid"},
2441 $playerinfo->{"uniqueid"},
2442 "killed_parrot",
2443 undef,
2444 undef,
2445 undef,
2446 &getProperties($ev_properties)
2447 );
2448 }
2449 } elsif ($s_output =~ /^
2450 (?:\(DEATH\))? # l4d prefix, such as (DEATH) or (INCAP)
2451 "(.+?(?:<.+?>)*?
2452 (?:<setpos_exact\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d);[^"]*)?
2453 )" # player string with or without l4d-style location coords
2454 (?:\s\[(-?\d+)\s(-?\d+)\s(-?\d+)\])?
2455 \skilled\s # verb (ex. attacked, killed, triggered)
2456 "(.+?(?:<.+?>)*?
2457 (?:<setpos_exact\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d);[^"]*)?
2458 )" # player string as above or action name
2459 (?:\s\[(-?\d+)\s(-?\d+)\s(-?\d+)\])?
2460 \swith\s # (ex. with, against)
2461 "([^"]*)"
2462 (.*) #properties
2463 /x)
2464 {
2465
2466 # Prototype: "player" verb "obj_a" ?... "obj_b"[properties]
2467 # Matches:
2468 # 8. Kills
2469
2470 $ev_player = $1;
2471 $ev_Xcoord = $2; # attacker/player coords (L4D)
2472 $ev_Ycoord = $3;
2473 $ev_Zcoord = $4;
2474 if( !defined($ev_Xcoord) ) {
2475 # if we didn't get L4D style, overwrite with CSGO style (which we may still not have)
2476 $ev_Xcoord = $5;
2477 $ev_Ycoord = $6;
2478 $ev_Zcoord = $7;
2479 }
2480 $ev_obj_a = $8; # victim
2481 $ev_XcoordKV = $9; # kill victim coords (L4D)
2482 $ev_YcoordKV = $10;
2483 $ev_ZcoordKV = $11;
2484 if( !defined($ev_XcoordKV) ) {
2485 $ev_XcoordKV = $12; # kill victim coords (CSGO)
2486 $ev_YcoordKV = $13;
2487 $ev_ZcoordKV = $14;
2488 }
2489 $ev_obj_b = $15; # weapon
2490 $ev_properties = $16;
2491 %ev_properties_hash = &getProperties($ev_properties);
2492
2493 my $killerinfo = &getPlayerInfo($ev_player, 1);
2494 my $victiminfo = &getPlayerInfo($ev_obj_a, 1);
2495 $ev_type = 8;
2496
2497 $headshot = 0;
2498 if ($ev_properties =~ m/headshot/) {
2499 $headshot = 1;
2500 }
2501
2502 if ($killerinfo && $victiminfo) {
2503 my $killerId = $killerinfo->{"userid"};
2504 my $killerUniqueId = $killerinfo->{"uniqueid"};
2505 my $killer = lookupPlayer($s_addr, $killerId, $killerUniqueId);
2506
2507 # octo
2508 if($killer->{role} eq "scout") {
2509 $ev_status = &doEvent_PlayerAction(
2510 $killerinfo->{"userid"},
2511 $killerinfo->{"uniqueid"},
2512 "kill_as_scout",
2513 "kill_as_scout"
2514 );
2515 }
2516 if($killer->{role} eq "spy") {
2517 $ev_status = &doEvent_PlayerAction(
2518 $killerinfo->{"userid"},
2519 $killerinfo->{"uniqueid"},
2520 "kill_as_spy",
2521 "kill_as_spy"
2522 );
2523 }
2524
2525 my $victimId = $victiminfo->{"userid"};
2526 my $victimUniqueId = $victiminfo->{"uniqueid"};
2527 my $victim = lookupPlayer($s_addr, $victimId, $victimUniqueId);
2528
2529 $ev_status = &doEvent_Frag(
2530 $killerinfo->{"userid"},
2531 $killerinfo->{"uniqueid"},
2532 $victiminfo->{"userid"},
2533 $victiminfo->{"uniqueid"},
2534 $ev_obj_b,
2535 $headshot,
2536 $ev_Xcoord,
2537 $ev_Ycoord,
2538 $ev_Zcoord,
2539 $ev_XcoordKV,
2540 $ev_YcoordKV,
2541 $ev_ZcoordKV,
2542 %ev_properties_hash
2543 );
2544 }
2545 } elsif ($g_servers{$s_addr}->{play_game} == L4D() && $s_output =~ /^
2546 \(INCAP\) # l4d prefix, such as (DEATH) or (INCAP)
2547 "(.+?(?:<.+?>)*?
2548 <setpos_exact\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d);[^"]*
2549 )" # player string with or without l4d-style location coords
2550 \swas\sincapped\sby\s # verb (ex. attacked, killed, triggered)
2551 "(.+?(?:<.+?>)*?
2552 <setpos_exact\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d)\s(-?\d+?\.\d\d);[^"]*
2553 )" # player string as above or action name
2554 \swith\s # (ex. with, against)
2555 "([^"]*)" # weapon name
2556 (.*) #properties
2557 /x)
2558 {
2559 # 800. L4D Incapacitation
2560
2561 $ev_player = $1;
2562 $ev_l4dXcoord = $2; # attacker/player coords (L4D)
2563 $ev_l4dYcoord = $3;
2564 $ev_l4dZcoord = $4;
2565 $ev_obj_a = $5; # victim
2566 $ev_l4dXcoordKV = $6; # kill victim coords (L4D)
2567 $ev_l4dYcoordKV = $7;
2568 $ev_l4dZcoordKV = $8;
2569 $ev_obj_b = $9; # weapon
2570 $ev_properties = $10;
2571 %ev_properties_hash = &getProperties($ev_properties);
2572
2573 # reverse killer/victim (x was incapped by y = y killed x)
2574 my $killerinfo = &getPlayerInfo($ev_obj_a, 1);
2575 my $victiminfo = &getPlayerInfo($ev_player, 1);
2576
2577 if ($victiminfo->{team} eq "Infected") {
2578 $victiminfo = undef;
2579 }
2580 $ev_type = 800;
2581
2582 $headshot = 0;
2583 if ($ev_properties =~ m/headshot/) {
2584 $headshot = 1;
2585 }
2586 if ($killerinfo && $victiminfo) {
2587 my $killerId = $killerinfo->{"userid"};
2588 my $killerUniqueId = $killerinfo->{"uniqueid"};
2589 my $killer = lookupPlayer($s_addr, $killerId, $killerUniqueId);
2590
2591 my $victimId = $victiminfo->{"userid"};
2592 my $victimUniqueId = $victiminfo->{"uniqueid"};
2593 my $victim = lookupPlayer($s_addr, $victimId, $victimUniqueId);
2594
2595 $ev_status = &doEvent_Frag(
2596 $killerinfo->{"userid"},
2597 $killerinfo->{"uniqueid"},
2598 $victiminfo->{"userid"},
2599 $victiminfo->{"uniqueid"},
2600 $ev_obj_b,
2601 $headshot,
2602 $ev_l4dXcoord,
2603 $ev_l4dYcoord,
2604 $ev_l4dZcoord,
2605 $ev_l4dXcoordKV,
2606 $ev_l4dYcoordKV,
2607 $ev_l4dZcoordKV,
2608 &getProperties($ev_properties)
2609 );
2610 }
2611 } elsif ($g_servers{$s_addr}->{play_game} == L4D() && $s_output =~ /^\(TONGUE\)\sTongue\sgrab\sstarting\.\s+Smoker:"(.+?(?:<.+?>)*?(?:|<setpos_exact ((?:|-)\d+?\.\d\d) ((?:|-)\d+?\.\d\d) ((?:|-)\d+?\.\d\d);.*?))"\.\s+Victim:"(.+?(?:<.+?>)*?(?:|<setpos_exact ((?:|-)\d+?\.\d\d) ((?:|-)\d+?\.\d\d) ((?:|-)\d+?\.\d\d);.*?))".*/) {
2612 # Prototype: (TONGUE) Tongue grab starting. Smoker:"player". Victim:"victim".
2613 # Matches:
2614 # 11. Player Action
2615
2616 $ev_player = $1;
2617 $ev_l4dXcoord = $2;
2618 $ev_l4dYcoord = $3;
2619 $ev_l4dZcoord = $4;
2620 $ev_victim = $5;
2621 $ev_l4dXcoordV = $6;
2622 $ev_l4dYcoordV = $7;
2623 $ev_l4dZcoordV = $8;
2624
2625 $playerinfo = &getPlayerInfo($ev_player, 1);
2626 $victiminfo = &getPlayerInfo($ev_victim, 1);
2627
2628 $ev_type = 11;
2629
2630 if ($playerinfo) {
2631 $ev_status = &doEvent_PlayerAction(
2632 $playerinfo->{"userid"},
2633 $playerinfo->{"uniqueid"},
2634 "tongue_grab"
2635 );
2636 }
2637 if ($playerinfo && $victiminfo) {
2638 $ev_status = &doEvent_PlayerPlayerAction(
2639 $playerinfo->{"userid"},
2640 $playerinfo->{"uniqueid"},
2641 $victiminfo->{"userid"},
2642 $victiminfo->{"uniqueid"},
2643 "tongue_grab",
2644 $ev_l4dXcoord,
2645 $ev_l4dYcoord,
2646 $ev_l4dZcoord,
2647 $ev_l4dXcoordV,
2648 $ev_l4dYcoordV,
2649 $ev_l4dZcoordV
2650 );
2651 }
2652 } elsif ($s_output =~ /^
2653 "(.+?(?:<.+?>)*?
2654 )" # player string
2655 \s(triggered(?:\sa)?)\s # verb (ex. attacked, killed, triggered)
2656 "(.+?(?:<.+?>)*?
2657 )" # player string as above or action name
2658 \s[a-zA-Z]+\s # (ex. with, against)
2659 "(.+?(?:<.+?>)*?
2660 )" # player string as above or weapon name
2661 (?:\s[a-zA-Z]+\s"(.+?)")? # weapon name on plyrplyr actions
2662 (.*) #properties
2663 /x)
2664 {
2665
2666 # 10. Player-Player Actions
2667
2668 # no l4d/2 actions are logged with the locations (in fact, very few are logged period) so the l4d/2 location parsing can be skipped
2669
2670 $ev_player = $1;
2671 $ev_verb = $2; # triggered or triggered a
2672 $ev_obj_a = $3; # action
2673 $ev_obj_b = $4; # victim
2674 $ev_obj_c = $5; # weapon (optional)
2675 $ev_properties = $6;
2676 %ev_properties_hash = &getProperties($ev_properties);
2677
2678
2679 if ($ev_verb eq "triggered") { # it's either 'triggered' or 'triggered a'
2680
2681 my $playerinfo = &getPlayerInfo($ev_player, 1);
2682 my $victiminfo = &getPlayerInfo($ev_obj_b, 1);
2683 $ev_type = 10;
2684
2685 if ($playerinfo) {
2686 if ($victiminfo) {
2687 $ev_status = &doEvent_PlayerPlayerAction(
2688 $playerinfo->{"userid"},
2689 $playerinfo->{"uniqueid"},
2690 $victiminfo->{"userid"},
2691 $victiminfo->{"uniqueid"},
2692 $ev_obj_a,
2693 undef,
2694 undef,
2695 undef,
2696 undef,
2697 undef,
2698 undef,
2699 &getProperties($ev_properties)
2700 );
2701 }
2702
2703 $ev_type = 11;
2704
2705 $ev_status = &doEvent_PlayerAction(
2706 $playerinfo->{"userid"},
2707 $playerinfo->{"uniqueid"},
2708 $ev_obj_a,
2709 undef,
2710 undef,
2711 undef,
2712 &getProperties($ev_properties)
2713 );
2714 }
2715 } else {
2716 my $playerinfo = &getPlayerInfo($ev_player, 1);
2717 $ev_type = 11;
2718
2719 if ($playerinfo) {
2720 $ev_status = &doEvent_PlayerAction(
2721 $playerinfo->{"userid"},
2722 $playerinfo->{"uniqueid"},
2723 $ev_obj_a,
2724 undef,
2725 undef,
2726 undef,
2727 &getProperties($ev_properties)
2728 );
2729 }
2730 }
2731 } elsif ( $s_output =~ /^(?:\[STATSME\] )?"(.+?(?:<.+?>)*)" triggered "(weaponstats\d{0,1})"(.*)$/ ) {
2732 # Prototype: [STATSME] "player" triggered "weaponstats?"[properties]
2733 # Matches:
2734 # 501. Statsme weaponstats
2735 # 502. Statsme weaponstats2
2736
2737 $ev_player = $1;
2738 $ev_verb = $2; # weaponstats; weaponstats2
2739 $ev_properties = $3;
2740 %ev_properties = &getProperties($ev_properties);
2741
2742 if (like($ev_verb, "weaponstats")) {
2743 $ev_type = 501;
2744 my $playerinfo = &getPlayerInfo($ev_player, 0);
2745
2746 if ($playerinfo) {
2747 my $playerId = $playerinfo->{"userid"};
2748 my $playerUniqueId = $playerinfo->{"uniqueid"};
2749 my $ingame = 0;
2750
2751 $ingame = 1 if (lookupPlayer($s_addr, $playerId, $playerUniqueId));
2752
2753 if (!$ingame) {
2754 &getPlayerInfo($ev_player, 1);
2755 }
2756
2757 $ev_status = &doEvent_Statsme(
2758 $playerId,
2759 $playerUniqueId,
2760 $ev_properties{"weapon"},
2761 $ev_properties{"shots"},
2762 $ev_properties{"hits"},
2763 $ev_properties{"headshots"},
2764 $ev_properties{"damage"},
2765 $ev_properties{"kills"},
2766 $ev_properties{"deaths"}
2767 );
2768
2769 if (!$ingame) {
2770 &doEvent_Disconnect(
2771 $playerId,
2772 $playerUniqueId,
2773 ""
2774 );
2775 }
2776 }
2777 } elsif (like($ev_verb, "weaponstats2")) {
2778 $ev_type = 502;
2779 my $playerinfo = &getPlayerInfo($ev_player, 0);
2780
2781 if ($playerinfo) {
2782 my $playerId = $playerinfo->{"userid"};
2783 my $playerUniqueId = $playerinfo->{"uniqueid"};
2784 my $ingame = 0;
2785
2786 $ingame = 1 if (lookupPlayer($s_addr, $playerId, $playerUniqueId));
2787
2788 if (!$ingame) {
2789 &getPlayerInfo($ev_player, 1);
2790 }
2791
2792 $ev_status = &doEvent_Statsme2(
2793 $playerId,
2794 $playerUniqueId,
2795 $ev_properties{"weapon"},
2796 $ev_properties{"head"},
2797 $ev_properties{"chest"},
2798 $ev_properties{"stomach"},
2799 $ev_properties{"leftarm"},
2800 $ev_properties{"rightarm"},
2801 $ev_properties{"leftleg"},
2802 $ev_properties{"rightleg"}
2803 );
2804
2805 if (!$ingame) {
2806 &doEvent_Disconnect(
2807 $playerId,
2808 $playerUniqueId,
2809 ""
2810 );
2811 }
2812 }
2813 }
2814 } elsif ( $s_output =~ /^(?:\[STATSME\] )?"(.+?(?:<.+?>)*)" triggered "(latency|time)"(.*)$/ ) {
2815 # Prototype: [STATSME] "player" triggered "latency|time"[properties]
2816 # Matches:
2817 # 503. Statsme latency
2818 # 504. Statsme time
2819
2820 $ev_player = $1;
2821 $ev_verb = $2; # latency; time
2822 $ev_properties = $3;
2823 %ev_properties = &getProperties($ev_properties);
2824
2825 if ($ev_verb eq "time") {
2826 $ev_type = 504;
2827 my $playerinfo = &getPlayerInfo($ev_player, 0);
2828
2829 if ($playerinfo) {
2830 my ($min, $sec) = split(/:/, $ev_properties{"time"});
2831 my $hour = sprintf("%d", $min / 60);
2832
2833 if ($hour) {
2834 $min = $min % 60;
2835 }
2836
2837 $ev_status = &doEvent_Statsme_Time(
2838 $playerinfo->{"userid"},
2839 $playerinfo->{"uniqueid"},
2840 "$hour:$min:$sec"
2841 );
2842 }
2843 } else { # latency
2844 $ev_type = 503;
2845 my $playerinfo = &getPlayerInfo($ev_player, 0);
2846
2847 if ($playerinfo) {
2848 $ev_status = &doEvent_Statsme_Latency(
2849 $playerinfo->{"userid"},
2850 $playerinfo->{"uniqueid"},
2851 $ev_properties{"ping"}
2852 );
2853 }
2854 }
2855 } elsif ($s_output =~ /^"(.+?(?:<.+?>)*?)" ([a-zA-Z,_\s]+) "(.+?)"(.*)$/) {
2856 # Prototype: "player" verb "obj_a"[properties]
2857 # Matches:
2858 # 1. Connection
2859 # 4. Suicides
2860 # 5. Team Selection
2861 # 6. Role Selection
2862 # 7. Change Name
2863 # 11. Player Objectives/Actions
2864 # 14. a) Chat; b) Team Chat
2865
2866 $ev_player = $1;
2867 $ev_verb = $2;
2868 $ev_obj_a = $3;
2869 $ev_properties = $4;
2870 %ev_properties = &getProperties($ev_properties);
2871
2872 if ($ev_verb eq "connected, address") {
2873 my $ipAddr = $ev_obj_a;
2874 my $playerinfo;
2875
2876 if ($ipAddr =~ /([\d\.]+):(\d+)/) {
2877 $ipAddr = $1;
2878 }
2879
2880 $playerinfo = &getPlayerInfo($ev_player, 1, $ipAddr);
2881
2882 $ev_type = 1;
2883
2884 if ($playerinfo) {
2885 if (($playerinfo->{"uniqueid"} =~ /UNKNOWN/) || ($playerinfo->{"uniqueid"} =~ /PENDING/) || ($playerinfo->{"uniqueid"} =~ /VALVE_ID_LAN/)) {
2886 $ev_status = "(DELAYING CONNECTION): $s_output";
2887
2888 if ($g_mode ne "LAN") {
2889 my $p_name = $playerinfo->{"name"};
2890 my $p_userid = $playerinfo->{"userid"};
2891 &printEvent("SERVER", "LATE CONNECT [$p_name/$p_userid] - STEAM_ID_PENDING");
2892 $g_preconnect->{"$s_addr/$p_userid/$p_name"} = {
2893 ipaddress => $ipAddr,
2894 name => $p_name,
2895 server => $s_addr,
2896 timestamp => $ev_daemontime
2897 };
2898 }
2899 } else {
2900 $ev_status = &doEvent_Connect(
2901 $playerinfo->{"userid"},
2902 $playerinfo->{"uniqueid"},
2903 $ipAddr
2904 );
2905 }
2906 }
2907 } elsif ($ev_verb eq "committed suicide with") {
2908 my $playerinfo = &getPlayerInfo($ev_player, 1);
2909
2910 $ev_type = 4;
2911
2912 if ($playerinfo) {
2913 $ev_status = &doEvent_Suicide(
2914 $playerinfo->{"userid"},
2915 $playerinfo->{"uniqueid"},
2916 $ev_obj_a,
2917 %ev_properties
2918 );
2919 }
2920 } elsif ($ev_verb eq "joined team") {
2921 my $playerinfo = &getPlayerInfo($ev_player, 1);
2922
2923 $ev_type = 5;
2924
2925 if ($playerinfo) {
2926 $ev_status = &doEvent_TeamSelection(
2927 $playerinfo->{"userid"},
2928 $playerinfo->{"uniqueid"},
2929 $ev_obj_a
2930 );
2931 }
2932 } elsif ($ev_verb eq "changed role to") {
2933 my $playerinfo = &getPlayerInfo($ev_player, 1);
2934
2935 $ev_type = 6;
2936
2937 if ($playerinfo) {
2938 $ev_status = &doEvent_RoleSelection(
2939 $playerinfo->{"userid"},
2940 $playerinfo->{"uniqueid"},
2941 $ev_obj_a
2942 );
2943 }
2944 } elsif ($ev_verb eq "changed name to") {
2945 my $playerinfo = &getPlayerInfo($ev_player, 1);
2946
2947 $ev_type = 7;
2948
2949 if ($playerinfo) {
2950 $ev_status = &doEvent_ChangeName(
2951 $playerinfo->{"userid"},
2952 $playerinfo->{"uniqueid"},
2953 $ev_obj_a
2954 );
2955 }
2956 } elsif ($ev_verb eq "triggered") {
2957
2958 # in cs:s players dropp the bomb if they are the only ts
2959 # and disconnect...the dropp the bomb after they disconnected :/
2960 if ($ev_obj_a eq "Dropped_The_Bomb") {
2961 $playerinfo = &getPlayerInfo($ev_player, 0);
2962 } else {
2963 $playerinfo = &getPlayerInfo($ev_player, 1);
2964 }
2965 if ($playerinfo) {
2966 if ($ev_obj_a eq "player_changeclass" && defined($ev_properties{newclass})) {
2967
2968 $ev_type = 6;
2969
2970 $ev_status = &doEvent_RoleSelection(
2971 $playerinfo->{"userid"},
2972 $playerinfo->{"uniqueid"},
2973 $ev_properties{newclass}
2974 );
2975 } else {
2976 if ($g_servers{$s_addr}->{play_game} == TFC())
2977 {
2978 if ($ev_obj_a eq "Sentry_Destroyed")
2979 {
2980 $ev_obj_a = "Sentry_Dismantle";
2981 }
2982 elsif ($ev_obj_a eq "Dispenser_Destroyed")
2983 {
2984 $ev_obj_a = "Dispenser_Dismantle";
2985 }
2986 elsif ($ev_obj_a eq "Teleporter_Entrance_Destroyed")
2987 {
2988 $ev_obj_a = "Teleporter_Entrance_Dismantle"
2989 }
2990 elsif ($ev_obj_a eq "Teleporter_Exit_Destroyed")
2991 {
2992 $ev_obj_a = "Teleporter_Exit_Dismantle"
2993 }
2994 }
2995
2996 $ev_type = 11;
2997
2998 $ev_status = &doEvent_PlayerAction(
2999 $playerinfo->{"userid"},
3000 $playerinfo->{"uniqueid"},
3001 $ev_obj_a,
3002 undef,
3003 undef,
3004 undef,
3005 %ev_properties
3006 );
3007 }
3008 }
3009 } elsif ($ev_verb eq "triggered a") {
3010 my $playerinfo = &getPlayerInfo($ev_player, 1);
3011
3012 $ev_type = 11;
3013
3014 if ($playerinfo)
3015 {
3016 $ev_status = &doEvent_PlayerAction(
3017 $playerinfo->{"userid"},
3018 $playerinfo->{"uniqueid"},
3019 $ev_obj_a,
3020 undef,
3021 undef,
3022 undef,
3023 %ev_properties
3024 );
3025 }
3026 } elsif ($ev_verb eq "say" || $ev_verb eq "say_team" || $ev_verb eq "say_squad") {
3027 my $playerinfo = &getPlayerInfo($ev_player, 1);
3028
3029 $ev_type = 14;
3030
3031 if ($playerinfo) {
3032 $ev_status = &doEvent_Chat(
3033 $ev_verb,
3034 $playerinfo->{"userid"},
3035 $playerinfo->{"uniqueid"},
3036 $ev_obj_a
3037 );
3038 }
3039 }
3040 } elsif ($s_output =~ /^(?:Kick: )?"(.+?(?:<.+?>)*)" ([^\(]+)(.*)$/) {
3041 # Prototype: "player" verb[properties]
3042 # Matches:
3043 # 2. Enter Game
3044 # 3. Disconnection
3045
3046 $ev_player = $1;
3047 $ev_verb = $2;
3048 $ev_properties = $3;
3049 %ev_properties = &getProperties($ev_properties);
3050
3051 if (like($ev_verb, "entered the game")) {
3052 my $playerinfo = &getPlayerInfo($ev_player, 1);
3053
3054 if ($playerinfo) {
3055 $ev_type = 2;
3056 $ev_status = &doEvent_EnterGame($playerinfo->{"userid"}, $playerinfo->{"uniqueid"}, $ev_obj_a);
3057 }
3058 } elsif (like($ev_verb, "disconnected") || like($ev_verb, "was kicked")) {
3059 my $playerinfo = &getPlayerInfo($ev_player, 0);
3060
3061 if ($playerinfo) {
3062 $ev_type = 3;
3063
3064 $userid = $playerinfo->{userid};
3065 $uniqueid = $playerinfo->{uniqueid};
3066
3067 $ev_status = &doEvent_Disconnect(
3068 $playerinfo->{userid},
3069 $playerinfo->{uniqueid},
3070 $ev_properties
3071 );
3072 }
3073 }
3074 elsif (like($ev_verb, "STEAM USERID validated") || like($ev_verb, "VALVE USERID validated")) {
3075 my $playerinfo = &getPlayerInfo($ev_player, 0);
3076
3077 if ($playerinfo) {
3078 $ev_type = 1;
3079
3080 }
3081 }
3082 } elsif ($s_output =~ /^Team "(.+?)" ([^"\(]+) "([^"]+)"(.*)$/) {
3083 # Prototype: Team "team" verb "obj_a"[properties]
3084 # Matches:
3085 # 12. Team Objectives/Actions
3086 # 1200. Team Objective With Players involved
3087 # 15. Team Alliances
3088
3089 $ev_team = $1;
3090 $ev_verb = $2;
3091 $ev_obj_a = $3;
3092 $ev_properties = $4;
3093 %ev_properties_hash = &getProperties($ev_properties);
3094
3095 if ($ev_obj_a eq "pointcaptured") {
3096 $numcappers = $ev_properties_hash{numcappers};
3097 if ($g_debug > 1) {
3098 print "NumCappers = ".$numcappers."\n";
3099 }
3100 foreach ($i = 1; $i <= $numcappers; $i++) {
3101 # reward each player involved in capturing
3102 $player = $ev_properties_hash{"player".$i};
3103 if ($g_debug > 1) {
3104 print $i." -> ".$player."\n";
3105 }
3106 #$position = $ev_properties_hash{"position".$i};
3107 my $playerinfo = &getPlayerInfo($player, 1);
3108 if ($playerinfo) {
3109 $ev_status = &doEvent_PlayerAction(
3110 $playerinfo->{"userid"},
3111 $playerinfo->{"uniqueid"},
3112 $ev_obj_a,
3113 "",
3114 "",
3115 "",
3116 &getProperties($ev_properties)
3117 );
3118 }
3119 }
3120 }
3121 if ($ev_obj_a eq "captured_loc") {
3122 # $flag_name = $ev_properties_hash{flagname};
3123 $player_a = $ev_properties_hash{player_a};
3124 $player_b = $ev_properties_hash{player_b};
3125
3126 my $playerinfo_a = &getPlayerInfo($player_a, 1);
3127 if ($playerinfo_a) {
3128 $ev_status = &doEvent_PlayerAction(
3129 $playerinfo_a->{"userid"},
3130 $playerinfo_a->{"uniqueid"},
3131 $ev_obj_a,
3132 "",
3133 "",
3134 "",
3135 &getProperties($ev_properties)
3136 );
3137 }
3138
3139 my $playerinfo_b = &getPlayerInfo($player_b, 1);
3140 if ($playerinfo_b) {
3141 $ev_status = &doEvent_PlayerAction(
3142 $playerinfo_b->{"userid"},
3143 $playerinfo_b->{"uniqueid"},
3144 $ev_obj_a,
3145 "",
3146 "",
3147 "",
3148 &getProperties($ev_properties)
3149 );
3150 }
3151 }
3152
3153 if (like($ev_verb, "triggered")) {
3154 if ($ev_obj_a ne "captured_loc") {
3155 $ev_type = 12;
3156 $ev_status = &doEvent_TeamAction(
3157 $ev_team,
3158 $ev_obj_a,
3159 &getProperties($ev_properties)
3160 );
3161 }
3162 } elsif (like($ev_verb, "triggered a")) {
3163 $ev_type = 12;
3164 $ev_status = &doEvent_TeamAction(
3165 $ev_team,
3166 $ev_obj_a
3167 );
3168 }
3169 } elsif ($s_output =~ /^(Rcon|Bad Rcon): "rcon [^"]+"([^"]+)"\s+(.+)" from "([0-9\.]+?):(\d+?)"(.*)$/) {
3170 # Prototype: verb: "rcon ?..."obj_a" obj_b" from "obj_c"[properties]
3171 # Matches:
3172 # 20. HL1 a) Rcon; b) Bad Rcon
3173
3174 $ev_verb = $1;
3175 $ev_obj_a = $2; # password
3176 $ev_obj_b = $3; # command
3177 $ev_obj_c = $4; # ip
3178 $ev_obj_d = $5; # port
3179 $ev_properties = $6;
3180 %ev_properties = &getProperties($ev_properties);
3181 if ($g_rcon_ignoreself == 0 || $ev_obj_c ne $s_ip) {
3182 $ev_obj_b = substr($ev_obj_b, 0, 255);
3183 if (like($ev_verb, "Rcon")) {
3184 $ev_type = 20;
3185 $ev_status = &doEvent_Rcon(
3186 "OK",
3187 $ev_obj_b,
3188 "",
3189 $ev_obj_c
3190 );
3191 } elsif (like($ev_verb, "Bad Rcon")) {
3192 $ev_type = 20;
3193 $ev_status = &doEvent_Rcon(
3194 "BAD",
3195 $ev_obj_b,
3196 $ev_obj_a,
3197 $ev_obj_c
3198 );
3199 }
3200 } else {
3201 $ev_status = "(IGNORED) Rcon from \"$ev_obj_a:$ev_obj_b\": \"$ev_obj_c\"";
3202 }
3203 } elsif ($s_output =~ /^rcon from "(.+?):(.+?)": (?:command "(.*)".*|(Bad) Password)$/) {
3204 # Prototype: verb: "rcon ?..."obj_a" obj_b" from "obj_c"[properties]
3205 # Matches:
3206 # 20. a) Rcon;
3207
3208 $ev_obj_a = $1; # ip
3209 $ev_obj_b = $2; # port
3210 $ev_obj_c = $3; # command
3211 $ev_isbad = $4; # if bad, "Bad"
3212 if ($g_rcon_ignoreself == 0 || $ev_obj_a ne $s_ip) {
3213 if ($ev_isbad ne "Bad") {
3214 $ev_type = 20;
3215 @cmds = split(/;/,$ev_obj_c);
3216 foreach(@cmds)
3217 {
3218 $ev_status = &doEvent_Rcon(
3219 "OK",
3220 substr($_, 0, 255),
3221 "",
3222 $ev_obj_a
3223 );
3224 }
3225 } else {
3226 $ev_type = 20;
3227 $ev_status = &doEvent_Rcon(
3228 "BAD",
3229 "",
3230 "",
3231 $ev_obj_a
3232 );
3233 }
3234 } else {
3235 $ev_status = "(IGNORED) Rcon from \"$ev_obj_a:$ev_obj_b\": \"$ev_obj_c\"";
3236 }
3237 } elsif ($s_output =~ /^\[(.+)\.(smx|amxx)\]\s*(.+)$/i) {
3238 # Prototype: Cmd:[SM] obj_a
3239 # Matches:
3240 # Admin Mod messages
3241
3242 my $ev_plugin = $1;
3243 my $ev_adminmod = $2;
3244 $ev_obj_a = $3;
3245 $ev_type = 500;
3246 $ev_status = &doEvent_Admin(
3247 (($ev_adminmod eq "smx")?"Sourcemod":"AMXX")." ($ev_plugin)",
3248 substr($ev_obj_a, 0, 255)
3249 );
3250 } elsif ($s_output =~ /^([^"\(]+) "([^"]+)"(.*)$/) {
3251 # Prototype: verb "obj_a"[properties]
3252 # Matches:
3253 # 13. World Objectives/Actions
3254 # 19. a) Loading map; b) Started map
3255 # 21. Server Name
3256
3257 $ev_verb = $1;
3258 $ev_obj_a = $2;
3259 $ev_properties = $3;
3260 %ev_properties = &getProperties($ev_properties);
3261
3262 if (like($ev_verb, "World triggered")) {
3263 $ev_type = 13;
3264 if ($ev_obj_a eq "killlocation") {
3265 $ev_status = &doEvent_Kill_Loc(
3266 %ev_properties
3267 );
3268 } else {
3269 $ev_status = &doEvent_WorldAction(
3270 $ev_obj_a
3271 );
3272 if ($ev_obj_a eq "Round_Win" || $ev_obj_a eq "Mini_Round_Win") {
3273 $ev_team = $ev_properties{"winner"};
3274 $ev_status = &doEvent_TeamAction(
3275 $ev_team,
3276 $ev_obj_a
3277 );
3278 }
3279 }
3280 } elsif (like($ev_verb, "Loading map")) {
3281 $ev_type = 19;
3282 $ev_status = &doEvent_ChangeMap(
3283 "loading",
3284 $ev_obj_a
3285 );
3286 } elsif (like($ev_verb, "Started map")) {
3287 $ev_type = 19;
3288 $ev_status = &doEvent_ChangeMap(
3289 "started",
3290 $ev_obj_a
3291 );
3292 }
3293 } elsif ($s_output =~ /^\[MANI_ADMIN_PLUGIN\]\s*(.+)$/) {
3294 # Prototype: [MANI_ADMIN_PLUGIN] obj_a
3295 # Matches:
3296 # Mani-Admin-Plugin messages
3297
3298 $ev_obj_a = $1;
3299 $ev_type = 500;
3300 $ev_status = &doEvent_Admin(
3301 "Mani Admin Plugin",
3302 substr($ev_obj_a, 0, 255)
3303 );
3304 } elsif ($s_output =~ /^\[BeetlesMod\]\s*(.+)$/) {
3305 # Prototype: Cmd:[BeetlesMod] obj_a
3306 # Matches:
3307 # Beetles Mod messages
3308
3309 $ev_obj_a = $1;
3310 $ev_type = 500;
3311 $ev_status = &doEvent_Admin(
3312 "Beetles Mod",
3313 substr($ev_obj_a, 0, 255)
3314 );
3315 } elsif ($s_output =~ /^\[ADMIN:(.+)\] ADMIN Command: \1 used command (.+)$/) {
3316 # Prototype: [ADMIN] obj_a
3317 # Matches:
3318 # Admin Mod messages
3319
3320 $ev_obj_a = $1;
3321 $ev_obj_b = $2;
3322 $ev_type = 500;
3323 $ev_status = &doEvent_Admin(
3324 "Admin Mod",
3325 substr($ev_obj_b, 0, 255),
3326 $ev_obj_a
3327 );
3328 } elsif ($g_servers{$s_addr}->{play_game} == DYSTOPIA()) {
3329 if ($s_output =~ /^weapon { steam_id: 'STEAM_\d+:(.+?)', weapon_id: (\d+), class: \d+, team: \d+, shots: \((\d+),(\d+)\), hits: \((\d+),(\d+)\), damage: \((\d+),(\d+)\), headshots: \((\d+),(\d+)\), kills: \(\d+,\d+\) }$/ && $g_mode eq "Normal") {
3330
3331 # Prototype: weapon { steam_id: 'STEAMID', weapon_id: X, class: X, team: X, shots: (X,X), hits: (X,X), damage: (X,X), headshots: (X,X), kills: (X,X) }
3332 # Matches:
3333 # 501. Statsme weaponstats (Dystopia)
3334
3335 my $steamid = $1;
3336 my $weapon = $2;
3337 my $shots = $3 + $4;
3338 my $hits = $5 + $6;
3339 my $damage = $7 + $8;
3340 my $headshots = $9 + $10;
3341 my $kills = $11 + $12;
3342
3343 $ev_type = 501;
3344
3345 my $weapcode = $dysweaponcodes{$weapon};
3346
3347 foreach $player (values(%g_players)) {
3348 if ($player->{uniqueid} eq $steamid) {
3349 $ev_status = &doEvent_Statsme(
3350 $player->{"userid"},
3351 $steamid,
3352 $weapcode,
3353 $shots,
3354 $hits,
3355 $headshots,
3356 $damage,
3357 $kills,
3358 0
3359 );
3360 last;
3361 }
3362 }
3363 } elsif ($s_output =~ /^(?:join|change)_class { steam_id: 'STEAM_\d+:(.+?)', .* (?:new_|)class: (\d+), .* }$/ && $g_mode eq "Normal") {
3364 # Prototype: join_class { steam_id: 'STEAMID', team: X, class: Y, time: ZZZZZZZZZ }
3365 # Matches:
3366 # 6. Role Selection (Dystopia)
3367
3368 my $steamid = $1;
3369 my $role = $2;
3370 $ev_type = 6;
3371
3372 foreach $player (values(%g_players)) {
3373 if ($player->{uniqueid} eq $steamid) {
3374 $ev_status = &doEvent_RoleSelection(
3375 $player->{"userid"},
3376 $steamid,
3377 $role
3378 );
3379 last;
3380 }
3381 }
3382 } elsif ($s_output =~ /^objective { steam_id: 'STEAM_\d+:(.+?)', class: \d+, team: \d+, objective: '(.+?)', time: \d+ }$/ && $g_mode eq "Normal") {
3383 # Prototype: objective { steam_id: 'STEAMID', class: X, team: X, objective: 'TEXT', time: X }
3384 # Matches:
3385 # 11. Player Action (Dystopia Objectives)
3386
3387 my $steamid = $1;
3388 my $action = $2;
3389 foreach $player (values(%g_players)) {
3390 if ($player->{uniqueid} eq $steamid) {
3391 $ev_status = &doEvent_PlayerAction(
3392 $player->{"userid"},
3393 $steamid,
3394 $action
3395 );
3396 last;
3397 }
3398 }
3399 }
3400 }
3401
3402 if ($ev_type) {
3403 if ($g_debug > 2) {
3404 print <<EOT
3405 type = "$ev_type"
3406 team = "$ev_team"
3407 player = "$ev_player"
3408 verb = "$ev_verb"
3409 obj_a = "$ev_obj_a"
3410 obj_b = "$ev_obj_b"
3411 obj_c = "$ev_obj_c"
3412 properties = "$ev_properties"
3413EOT
3414;
3415 while (my($key, $value) = each(%ev_properties)) {
3416 print "property: \"$key\" = \"$value\"\n";
3417 }
3418
3419 while (my($key, $value) = each(%ev_player)) {
3420 print "player $key = \"$value\"\n";
3421 }
3422 }
3423
3424 if ($ev_status ne "") {
3425 &printEvent($ev_type, $ev_status);
3426 } else {
3427 &printEvent($ev_type, "BAD DATA: $s_output");
3428 }
3429 } elsif (($s_output =~ /^Banid: "(.+?(?:<.+?>)*)" was (?:kicked and )?banned "for ([0-9]+).00 minutes" by "Console"$/) ||
3430 ($s_output =~ /^Banid: "(.+?(?:<.+?>)*)" was (?:kicked and )?banned "(permanently)" by "Console"$/)) {
3431
3432 # Prototype: "player" verb[properties]
3433 # Banid: huaaa<1804><STEAM_0:1:10769><>" was kicked and banned "permanently" by "Console"
3434
3435 $ev_player = $1;
3436 $ev_bantime = $2;
3437 my $playerinfo = &getPlayerInfo($ev_player, 1);
3438
3439 if ($ev_bantime eq "5") {
3440 &printEvent("BAN", "Auto Ban - ignored");
3441 } elsif ($playerinfo) {
3442 if (($g_global_banning > 0) && ($g_servers{$s_addr}->{ignore_nextban}->{$playerinfo->{"uniqueid"}} == 1)) {
3443 delete($g_servers{$s_addr}->{ignore_nextban}->{$playerinfo->{"uniqueid"}});
3444 &printEvent("BAN", "Global Ban - ignored");
3445 } elsif (!$g_servers{$s_addr}->{ignore_nextban}->{$playerinfo->{"uniqueid"}}) {
3446 my $p_steamid = $playerinfo->{"uniqueid"};
3447 my $player_obj = lookupPlayer($s_addr, $playerId, $p_steamid);
3448 &printEvent("BAN", "Steamid: ".$p_steamid);
3449
3450 if ($player_obj) {
3451 $player_obj->{"is_banned"} = 1;
3452 }
3453 if (($p_steamid ne "") && ($playerinfo->{"is_bot"} == 0) && ($playerinfo->{"userid"} > 0)) {
3454 if ($g_global_banning > 0) {
3455 if ($ev_bantime eq "permanently") {
3456 &printEvent("BAN", "Hide player!");
3457 &execNonQuery("UPDATE hlstats_Players SET hideranking=2 WHERE playerId IN (SELECT playerId FROM hlstats_PlayerUniqueIds WHERE uniqueId='"."eSQL($p_steamid)."')");
3458 $ev_bantime = 0;
3459 }
3460 my $pl_steamid = $playerinfo->{"plain_uniqueid"};
3461 while (my($addr, $server) = each(%g_servers)) {
3462 if ($addr ne $s_addr) {
3463 &printEvent("BAN", "Global banning on ".$addr);
3464 $server->{ignore_nextban}->{$p_steamid} = 1;
3465 $server->dorcon("banid ".$ev_bantime." $pl_steamid");
3466 $server->dorcon("writeid");
3467 }
3468 }
3469 }
3470 }
3471 }
3472 } else {
3473 &printEvent("BAN", "No playerinfo");
3474 }
3475 &printEvent("BAN", $s_output);
3476 } else {
3477 # Unrecognized event
3478 # HELLRAISER
3479 if ($g_debug > 1) {
3480 &printEvent(999, "UNRECOGNIZED: " . $s_output);
3481 }
3482 }
3483
3484 if (!$g_stdin && defined($g_servers{$s_addr}) && $ev_daemontime > $g_servers{$s_addr}->{next_plyr_flush}) {
3485 &printEvent("MYSQL", "Flushing player updates to database...",1);
3486 if ($g_servers{$s_addr}->{"srv_players"}) {
3487 while ( my($pl, $player) = each(%{$g_servers{$s_addr}->{"srv_players"}}) ) {
3488 if ($player->{needsupdate}) {
3489 $player->flushDB();
3490 }
3491 }
3492 }
3493 &printEvent("MYSQL", "Flushing player updates to database is complete.",1);
3494
3495 $g_servers{$s_addr}->{next_plyr_flush} = $ev_daemontime + 15+int(rand(15));
3496 }
3497
3498 if (($g_stdin == 0) && defined($g_servers{$s_addr})) {
3499 $s_lines = $g_servers{$s_addr}->{lines};
3500 # get ping from players
3501 if ($s_lines % 1000 == 0) {
3502 $g_servers{$s_addr}->update_players_pings();
3503 }
3504
3505 if ($g_servers{$s_addr}->{show_stats} == 1) {
3506 # show stats
3507 if ($s_lines % 2500 == 40) {
3508 $g_servers{$s_addr}->dostats();
3509 }
3510 }
3511
3512 if ($s_lines > 500000) {
3513 $g_servers{$s_addr}->set("lines", 0);
3514 } else {
3515 $g_servers{$s_addr}->increment("lines");
3516 }
3517 }
3518 } else {
3519 $s_addr = "";
3520 }
3521
3522 while( my($server) = each(%g_servers))
3523 {
3524 if($g_servers{$server}->{next_timeout}<$ev_daemontime)
3525 {
3526 #print "checking $ev_unixtime\n";
3527 # Clean up
3528 # look
3529 if($g_servers{$server}->{"srv_players"})
3530 {
3531 my %players_temp=%{$g_servers{$server}->{"srv_players"}};
3532 while ( my($pl, $player) = each(%players_temp) ) {
3533 my $timeout = 250; # 250;
3534 if ($g_mode eq "LAN") {
3535 $timeout = $timeout * 2;
3536 }
3537 #if (($player->{is_bot} == 0) && (($ev_unixtime - $player->get("timestamp")) > ($timeout - 20))) {
3538 # &printEvent(400, $player->getInfoString()." is ".($ev_unixtime - $player->get("timestamp"))."s idle");
3539 #}
3540 my $userid = $player->{userid};
3541 my $uniqueid = $player->{uniqueid};
3542 if ( ($ev_daemontime - $player->{timestamp}) > $timeout ) {
3543 #printf("%s - %s %s\n",$server, $player->{userid}, $player->{uniqueid});
3544 # we delete any player who is inactive for over $timeout sec
3545 # - they probably disconnected silently somehow.
3546 if (($player->{is_bot} == 0) || ($g_stdin)) {
3547 &printEvent(400, "Auto-disconnecting " . $player->getInfoString() ." for idling (" . ($ev_daemontime - $player->{timestamp}) . " sec) on server (".$server.")");
3548 removePlayer($server, $userid, $uniqueid);
3549 }
3550 }
3551 }
3552 }
3553 $g_servers{$server}->{next_timeout}=$ev_daemontime+30+rand(30);
3554 }
3555
3556 if ($ev_daemontime > $g_servers{$server}->{next_flush}
3557 && $g_servers{$server}->{needsupdate}
3558 )
3559 {
3560 $g_servers{$server}->flushDB();
3561 $g_servers{$server}->{next_flush} = $ev_daemontime + 20;
3562 }
3563 }
3564
3565 while ( my($pl, $player) = each(%g_preconnect) ) {
3566 my $timeout = 600;
3567 if ( ($ev_unixtime - $player->{"timestamp"}) > $timeout ) {
3568 &printEvent(401, "Clearing pre-connect entry with key ".$pl);
3569 delete($g_preconnect{$pl});
3570 }
3571 }
3572
3573 if ($g_stdin == 0) {
3574 # Track the Trend
3575 if ($g_track_stats_trend > 0) {
3576 track_hlstats_trend();
3577 }
3578 while (my($addr, $server) = each(%g_servers)) {
3579 if (defined($server)) {
3580 $server->track_server_load();
3581 }
3582 }
3583
3584 while( my($table) = each(%g_eventtable_data))
3585 {
3586 if ($g_eventtable_data{$table}{lastflush} + 30 < $ev_daemontime)
3587 {
3588 flushEventTable($table);
3589 }
3590 }
3591
3592 if (defined($g_servers{$s_addr})) {
3593 if (($g_servers{$s_addr}->{map} eq "") && (($timeout > 0) && ($timeout % 60 == 0))) {
3594 $g_servers{$s_addr}->get_map();
3595 }
3596 }
3597 }
3598
3599 $c++;
3600 $c = 1 if ($c > 500000);
3601 $import_logs_count++ if ($g_stdin);
3602}
3603
3604$end_time = time();
3605if ($g_stdin) {
3606 if ($import_logs_count > 0) {
3607 print "\n";
3608 }
3609
3610 &flushAll(1);
3611 &execNonQuery("UPDATE hlstats_Players SET last_event=UNIX_TIMESTAMP();");
3612 &printEvent("IMPORT", "Import of log file complete. Scanned ".$import_logs_count." lines in ".($end_time-$start_time)." seconds", 1, 1);
3613}
3614
3615sub INT_handler
3616{
3617 print "SIGINT received. Flushing data and shutting down...\n";
3618 flushAll(1);
3619 exit(0);
3620}
3621
3622sub HUP_handler
3623{
3624 print "SIGHUP received. Flushing data and reloading configuration...\n";
3625 &reloadConfiguration;
3626}