#!/usr/bin/perl -w use strict; use File::Copy; use POSIX qw(strftime); my $sds_config = "..\\..\\data\\Multiplayer\\COMBAT-BOX.sds"; my $text_logs = "..\\..\\data\\logs\\text"; my $il2_stats_base = "C:/il2_stats"; # TODO: # Do we really want this script to start with a forcible server kill? Use a --option to skip that? Or a --server-already-running to jump into the middle of the control loop? # Consider deciding on maps automatically so we don't have to edit this when file name or map version changes? # (use a glob for that?) my @possible_maps = ( "Dogfight\\Alonzo\\Operation_Paravane\\Operation-Paravane-Sep-1944a", "Dogfight\\Alonzo\\Battle_for_Kalinin\\Battle-for-Kalinin-Aug-1944a", "Dogfight\\Alonzo\\Operation_Eisenhammer\\Operation-Eisenhammer-Feb-1945a", "Dogfight\\Alonzo\\Crimean_Offensive\\Crimean-Offensive-Apr-1944a", #"Dogfight\\Alonzo\\D_Day\\D-Day-June-1944a", "Dogfight\\Alonzo\\Operation_Frantic\\Operation-Frantic-June-1944a" ); sub get_different_map { my $current_map = $_[0]; my $next_map = $current_map; $next_map = $possible_maps[rand @possible_maps] while ($next_map eq $current_map); # maybe add -ALT to the map name if(rand(100) > 60) { $next_map = "$next_map-ALT"; } print " Picked next map: $next_map\n"; return $next_map; } sub write_map_for_il2_stats { my $full_map = $_[0]; my $map = $full_map; if($full_map =~ /.*\\(.+)$/) { $map = $1; } $map =~ s/(194\d)[a-z]/$1/; $map =~ s/\-ALT//; print " Recording map for IL2 Stats front page...\n"; my $timestamp = strftime("%Y%m%dT%H:%M:%S", localtime(time())); open(OUT, ">$il2_stats_base/current_map.txt") or die "Couldn't open output: $!"; print OUT "$map;$timestamp"; close OUT; my $from = "$il2_stats_base/static/$map.json"; my $to = "$il2_stats_base/static/current-map.json"; print " Updating current map JSON...\n"; unlink $to; print " Copying $from to $to...\n"; copy($from, $to); } sub dserver_is_running { my @lines = `tasklist /FI "IMAGENAME eq dserver.exe" 2>NUL | find /I /N "dserver"`; return (scalar @lines) > 0; } sub kill_any_running_dserver { while(dserver_is_running()) { print "Killing dserver...\n"; system("taskkill /IM \"dserver.exe\" /F"); sleep 1; } } sub get_ctime_for_newest_zero_log { opendir (LOGS, $text_logs) or die "Couldn't open $text_logs: $!\n"; my $newest_ctime = 0; while(my $file = readdir(LOGS)) { if($file =~ /missionReport.+\[0\].*\.txt/) { #print "Found a zero log file: $file\n"; my $ctime = (stat "$text_logs/$file")[9]; if($ctime > $newest_ctime) { $newest_ctime = $ctime; } } } closedir(LOGS); #print "Newest zero log ctime is $newest_ctime\n"; return $newest_ctime; } my $newest_zero_log_ctime = get_ctime_for_newest_zero_log(); sub wait_for_new_zero_log { print "Waiting for a new zero log file"; my $iter = 0; while($iter < 60) { print "."; my $newest = get_ctime_for_newest_zero_log(); if($newest > $newest_zero_log_ctime) { $newest_zero_log_ctime = $newest; print "\n"; return $newest; } $iter++; sleep 1; } print "\n"; print "*** WARNING: No log file found -- dserver failed to start!\n"; return -1; } sub cleanup_latest_zero_log { opendir (LOGS, $text_logs) or die "Couldn't open $text_logs: $!\n"; my @zero_logs = (); while(my $file = readdir(LOGS)) { if($file =~ /missionReport.+\[0\].*\.txt/) { push @zero_logs, $file; } } closedir(LOGS); @zero_logs = sort @zero_logs; my $latest_zero_log = pop @zero_logs; print "Cleaning up $latest_zero_log\n"; unlink "$text_logs/$latest_zero_log" or print "WARNING: Could not unlink file: $!\n"; } # First forcibly kill any dserver # TODO: Really? kill_any_running_dserver(); # Turn on STDOUT auto flushing $| = 1; print "======== Alonzo's dserver control script ========\n"; print "Version 0.2\n"; print "=================================================\n"; # Main loop while(1) { my @output = (); my $round_time = 0; print "=== Starting game control loop\n"; print " Reading $sds_config...\n"; open(IN, $sds_config) or die "Couldn't open $sds_config for input: $!\n"; my $map_updated = 0; my $next_map = ""; while(my $line = ) { # load dserver config, copying to temp file, making note of maximum map time if($line =~ /tdmRoundTime = (\d+)/) { $round_time = $1; print " Found round time: $round_time seconds\n"; push @output, $line; } elsif($line =~ /file = \"(Dogfight.+)\"/) { # Skip if we already updated the map next if $map_updated; # once map is found, pick a new random map that is not the one we just had my $previous_map = $1; if($previous_map =~ /(.+)\-ALT/) { $previous_map = $1; } print " Previous map was: $previous_map\n"; $next_map = get_different_map($previous_map); push @output, "file = \"$next_map\"\n"; $map_updated = 1; } else { push @output, $line; } } close IN; die "Could not determine map rotation time from SDS. Is it set correctly?\n" if not $round_time; die "Map rotation time is set to infinity.\n" if $round_time < 0; # save new SDS configuration open(OUT, ">$sds_config") or die "Couldn't open $sds_config for output: $!\n"; print " Writing updated SDS..."; foreach(@output) { print OUT; } close OUT; print "done.\n"; write_map_for_il2_stats($next_map); # spawn dserver my $cmd = "start \"dserver\" DServer.exe $sds_config"; print "Running system command: $cmd\n"; system $cmd; # wait for first [0] text log file to appear my $current_map_zero_log = wait_for_new_zero_log(); if($current_map_zero_log < 0) { # Server failed to start print "Server failed to start. Skipping this map and restarting controller loop.\n"; next; } my $scheduled_end_time = time() + $round_time; print "Scheduled finish for this map: $scheduled_end_time\n"; # This means server has started print "Server is up, waiting for map roll."; my $need_to_clean_up_latest_zero_log = 0; my $iter = 0; while(1) { # check every second until one of: # dserver not running any more if(!dserver_is_running()) { print "\nIt looks like dserver exited or crashed.\n"; last; } # maximum map time is exceeded plus 2 minutes if (time() > $scheduled_end_time + 2 * 60) { print "\nMap time exceeded by more than two minutes.\n"; last; } # a new [0] text log appears if (get_ctime_for_newest_zero_log() > $current_map_zero_log) { print "\nNew zero log detected, map has rolled.\n"; $need_to_clean_up_latest_zero_log = 1; last; } # TODO: detect if dserver has not written a new text log for 90 seconds print "." if ($iter % 10 == 0); sleep 1; $iter++; } # forcibly kill any dserver processes kill_any_running_dserver(); # if a new [0] log has been written, delete it if($need_to_clean_up_latest_zero_log) { cleanup_latest_zero_log(); } print "=== Game loop done, returning to start of control loop.\n"; }