/// <summary>
        /// Starts spring game
        /// </summary>
        /// <param name="client">tasclient to get current battle from</param>
        /// <param name="priority">spring process priority</param>
        /// <param name="affinity">spring process cpu affinity</param>
        /// <param name="scriptOverride">if set, overrides generated script with supplied one</param>
        /// <param name="userName">lobby user name - used to submit score</param>
        /// <param name="passwordHash">lobby password hash - used to submit score</param>
        /// <returns>generates script</returns>
        public string StartGame(TasClient client, ProcessPriorityClass?priority, int?affinity, string scriptOverride, bool useSafeMode = false, bool useMultithreaded = false, BattleContext contextOverride = null, Battle battleOverride = null)
        {
            if (!File.Exists(paths.Executable) && !File.Exists(paths.DedicatedServer))
            {
                throw new ApplicationException(string.Format("Spring or dedicated server executable not found: {0}, {1}", paths.Executable, paths.DedicatedServer));
            }

            this.client = client;
            wasKilled   = false;

            if (!IsRunning)
            {
                gameEndedOk   = false;
                IsBattleOver  = false;
                lobbyUserName = client.UserName;
                lobbyPassword = client.UserPassword;
                battleResult  = new BattleResult();

                talker              = new Talker();
                talker.SpringEvent += talker_SpringEvent;
                var battle = battleOverride ?? client.MyBattle;
                isHosting = client != null && battle != null && battle.Founder.Name == client.MyUser.Name;

                if (isHosting)
                {
                    scriptPath = Utils.MakePath(paths.WritableDirectory, "script_" + battle.Founder + ".txt").Replace('\\', '/');
                }
                else
                {
                    scriptPath = Utils.MakePath(paths.WritableDirectory, "script.txt").Replace('\\', '/');
                }

                statsPlayers.Clear();
                statsData.Clear();
                StartContext = null;

                string script;
                if (!string.IsNullOrEmpty(scriptOverride))
                {
                    battleResult.IsMission = true;
                    isHosting = false;
                    script    = scriptOverride;
                }
                else
                {
                    List <UserBattleStatus> players;
                    battleGuid = Guid.NewGuid();
                    var service = GlobalConst.GetSpringieService();
                    SpringBattleStartSetup startSetup = null;
                    if (isHosting && GlobalConst.IsZkMod(battle.ModName))
                    {
                        try {
                            StartContext = contextOverride ?? battle.GetContext();
                            startSetup   = service.GetSpringBattleStartSetup(StartContext);
                            if (startSetup.BalanceTeamsResult != null)
                            {
                                StartContext.Players = startSetup.BalanceTeamsResult.Players;
                                StartContext.Bots    = startSetup.BalanceTeamsResult.Bots;
                            }
                            connectedPlayers.Clear();
                            foreach (var p in StartContext.Players)
                            {
                                p.IsIngame = true;
                            }
                        } catch (Exception ex) {
                            Trace.TraceError("Error getting start setup: {0}", ex);
                        }
                    }

                    script = battle.GenerateScript(out players, client.MyUser, talker.LoopbackPort, battleGuid.ToString(), startSetup);
                    battleResult.IsMission     = battle.IsMission;
                    battleResult.IsBots        = battle.Bots.Any();
                    battleResult.Title         = battle.Title;
                    battleResult.Mod           = battle.ModName;
                    battleResult.Map           = battle.MapName;
                    battleResult.EngineVersion = paths.SpringVersion;
                    talker.SetPlayers(players);
                    statsPlayers = players.ToDictionary(x => x.Name,
                                                        x => new BattlePlayerResult
                    {
                        LobbyID       = x.LobbyUser.AccountID,
                        AllyNumber    = x.AllyNumber,
                        CommanderType = null,
                        // todo commandertype
                        IsSpectator   = x.IsSpectator,
                        IsVictoryTeam = false,
                    });
                }
                if (isHosting)
                {
                    timer.Start();
                }

                File.WriteAllText(scriptPath, script);

                LogLines = new StringBuilder();

                var optirun = Environment.GetEnvironmentVariable("OPTIRUN");

                process = new Process();
                process.StartInfo.CreateNoWindow = true;
                List <string> arg = new List <string>();


                if (string.IsNullOrEmpty(optirun))
                {
                    if (UseDedicatedServer)
                    {
                        process.StartInfo.FileName         = paths.DedicatedServer;
                        process.StartInfo.WorkingDirectory = Path.GetDirectoryName(paths.DedicatedServer);
                    }
                    else
                    {
                        process.StartInfo.FileName         = useMultithreaded ? paths.MtExecutable : paths.Executable;
                        process.StartInfo.WorkingDirectory = Path.GetDirectoryName(paths.Executable);
                    }
                }
                else
                {
                    Trace.TraceInformation("Using optirun {0} to start the game (OPTIRUN env var defined)", optirun);
                    process.StartInfo.FileName = optirun;
                    arg.Add(string.Format("\"{0}\"", (useMultithreaded ? paths.MtExecutable : paths.Executable)));
                }



                arg.Add(string.Format("--config \"{0}\"", paths.GetSpringConfigPath()));
                if (useSafeMode)
                {
                    arg.Add("--safemode");
                }
                arg.Add(string.Format("\"{0}\"", scriptPath));
                //Trace.TraceInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);

                process.StartInfo.Arguments              = string.Join(" ", arg);
                process.StartInfo.UseShellExecute        = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError  = true;
                process.Exited             += springProcess_Exited;
                process.ErrorDataReceived  += process_ErrorDataReceived;
                process.OutputDataReceived += process_OutputDataReceived;
                process.EnableRaisingEvents = true;

                gamePrivateMessages    = new Dictionary <string, int>();
                battleResult.StartTime = DateTime.UtcNow;
                process.Start();
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                if (IsRunning && SpringStarted != null)
                {
                    SpringStarted(this, EventArgs.Empty);
                }

                Utils.StartAsync(() =>
                {
                    Thread.Sleep(1000);
                    try {
                        if (priority != null)
                        {
                            process.PriorityClass = priority.Value;
                        }
                        if (affinity != null)
                        {
                            process.ProcessorAffinity = (IntPtr)affinity.Value;
                        }
                    } catch (Exception ex) {
                        Trace.TraceWarning("Error setting spring process affinity: {0}", ex);
                    }
                });

                return(script);
            }
            else
            {
                Trace.TraceError("Spring already running");
            }
            return(null);
        }