PageRenderTime 78ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/scripts/hlstats.pl

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