public static string SubmitSpringBattleResult(Spring.SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server) { try { if (!result.GameEndedOk) return "Game didn't end properly"; if (result.IsCheating) return "Cheats were enabled during this game"; var db = new ZkDataContext(); var text = new StringBuilder(); var sb = SaveSpringBattle(result, db); ProcessExtras(result.OutputExtras, sb, db); if (result.LobbyStartContext.Mode == AutohostMode.Planetwars) ProcessPlanetWars(result, server, sb, db, text); ProcessElos(result, server, db, sb); text.AppendLine(string.Format("BATTLE DETAILS AND REPLAY ----> {1}/Battles/Detail/{0} <-----", sb.SpringBattleID, GlobalConst.BaseSiteUrl)); return text.ToString(); } catch (Exception ex) { var data = JsonConvert.SerializeObject(result); Trace.TraceError($"{ex}\nData:\n{data}"); return $"{ex}\nData:\n{data}"; } }
public void StartMission(string missionName) { var down = Program.Downloader.GetResource(DownloadType.MISSION, missionName); if (down == null) { //okay Mission exist, but lets check for dependency! down = Program.Downloader.GetDependenciesOnly(missionName); } var engine = Program.Downloader.GetResource(DownloadType.ENGINE, Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride); var metaWait = new EventWaitHandle(false, EventResetMode.ManualReset); Mod modInfo = null; Program.MetaData.GetModAsync(missionName, mod => { if (!mod.IsMission) { Program.MainWindow.InvokeFunc(() => { WarningBar.DisplayWarning(string.Format("{0} is not a valid mission", missionName)); }); } else modInfo = mod; metaWait.Set(); }, error => { Program.MainWindow.InvokeFunc(() => { WarningBar.DisplayWarning(string.Format("Download of metadata failed: {0}", error.Message)); //container.btnStop.Enabled = true; }); metaWait.Set(); }); var downloads = new List<Download>() { down, engine }.Where(x => x != null).ToList(); if (downloads.Count > 0) { var dd = new WaitDownloadDialog(downloads); if (dd.ShowDialog(Program.MainWindow) == DialogResult.Cancel) { Program.MainWindow.InvokeFunc(() => Program.NotifySection.RemoveBar(this)); return; } } metaWait.WaitOne(); var spring = new Spring(Program.SpringPaths); spring.RunLocalScriptGame(modInfo.MissionScript, Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride); var cs = GlobalConst.GetContentService(); cs.NotifyMissionRun(Program.Conf.LobbyPlayerName, missionName); spring.SpringExited += (o, args) => RecordMissionResult(spring, modInfo); Program.MainWindow.InvokeFunc(() => Program.NotifySection.RemoveBar(this)); }
/// <summary> /// GEnerates script for connecting to game /// </summary> /// <returns></returns> public static string GenerateConnectScript(Spring.SpringBattleContext context) { var sb = new StringBuilder(); sb.AppendLine("[GAME]"); sb.AppendLine("{"); sb.AppendFormat("HostIP={0};\n", context.IpAddress); sb.AppendFormat("HostPort={0};\n", context.Port); sb.AppendLine("IsHost=0;"); sb.AppendFormat("MyPlayerName={0};\n", context.MyUserName); sb.AppendFormat("MyPasswd={0};\n", context.MyPassword ?? context.MyPassword); sb.AppendLine("}"); return sb.ToString(); }
public static void StartDownloadedMission(ScriptMissionData profile, string modInternalName) { var spring = new Spring(Program.SpringPaths); var name = Program.Conf.LobbyPlayerName; if (string.IsNullOrEmpty(name)) name = "Player"; if (Utils.VerifySpringInstalled()) { spring.RunLocalScriptGame(profile.StartScript.Replace("%MOD%", modInternalName).Replace("%MAP%", profile.MapName).Replace("%NAME%", name)); var serv = GlobalConst.GetContentService(); serv.NotifyMissionRun(Program.Conf.LobbyPlayerName, profile.Name); } }
public ChatToSpeech(Spring spring) { try { speechSynthesizer = new SpeechSynthesizer(); voices = speechSynthesizer.GetInstalledVoices(); spring.LogLineAdded += spring_LogLineAdded; } catch (Exception ex) { Trace.TraceError("voice synthetizer failed: {0}", ex); } }
private static void ProcessElos(Spring.SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, ZkDataContext db, SpringBattle sb) { bool noElo = result.OutputExtras.Any(x => x?.StartsWith("noElo", true, System.Globalization.CultureInfo.CurrentCulture) == true); Dictionary<int, int> orgLevels = sb.SpringBattlePlayers.Select(x => x.Account).ToDictionary(x => x.AccountID, x => x.Level); sb.CalculateAllElo(noElo); foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator)) u.Account.CheckLevelUp(); db.SaveChanges(); try { foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account)) server.PublishAccountUpdate(a); } catch (Exception ex) { Trace.TraceError("error updating extension data: {0}", ex); } foreach (Account account in sb.SpringBattlePlayers.Select(x => x.Account)) { if (account.Level > orgLevels[account.AccountID]) { try { string message = string.Format("Congratulations {0}! You just leveled up to level {1}. {3}/Users/Detail/{2}", account.Name, account.Level, account.AccountID, GlobalConst.BaseSiteUrl); //text.AppendLine(message); server.GhostPm(account.Name, message); } catch (Exception ex) { Trace.TraceError("Error sending level up lobby message: {0}", ex); } } } }
/// <summary> /// Generates script for hosting a game /// </summary> public static string GenerateHostScript(Spring.SpringBattleContext context, int loopbackListenPort) { var previousCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; var script = new StringBuilder(); script.AppendLine("[GAME]"); script.AppendLine("{"); script.AppendFormat(" ZkSearchTag={0};\n", Guid.NewGuid()); script.AppendFormat(" Mapname={0};\n", context.LobbyStartContext.Map); script.AppendFormat(" StartPosType={0};\n", context.LobbyStartContext.IsMission ? 3 : 2); script.AppendFormat(" GameType={0};\n", context.LobbyStartContext.Mod); script.AppendFormat(" ModHash=1;\n"); script.AppendFormat(" MapHash=1;\n"); script.AppendFormat(" AutohostPort={0};\n", loopbackListenPort); script.AppendLine(); script.AppendFormat(" HostIP={0};\n", context.IpAddress); script.AppendFormat(" HostPort={0};\n", context.Port); //script.AppendFormat(" SourcePort={0};\n", 8300); script.AppendFormat(" IsHost=1;\n"); script.AppendLine(); if (!string.IsNullOrEmpty(context.MyUserName)) script.AppendFormat(" MyPlayerName={0};\n", context.MyUserName); if (!string.IsNullOrEmpty(context.MyPassword) || !string.IsNullOrEmpty(context.MyUserName)) script.AppendFormat(" MyPasswd={0};\n", context.MyPassword??context.MyUserName); GeneratePlayerSection(script, context); return script.ToString(); } finally { Thread.CurrentThread.CurrentCulture = previousCulture; } }
private void MissionBar_Load(object sender, EventArgs e) { label1.Text = string.Format("Starting mission {0} - please wait", missionName); var down = Program.Downloader.GetResource(DownloadType.MOD, missionName); if (down==null) { //okay Mission exist, but lets check for dependency! down = Program.Downloader.GetDependenciesOnly(missionName); } var engine = Program.Downloader.GetAndSwitchEngine(Program.SpringPaths.SpringVersion); ZkData.Utils.StartAsync(() => { var metaWait = new EventWaitHandle(false, EventResetMode.ManualReset); Mod modInfo = null; Program.SpringScanner.MetaData.GetModAsync(missionName, mod => { if (!mod.IsMission) { Program.MainWindow.InvokeFunc(() => { label1.Text = string.Format("{0} is not a valid mission", missionName); container.btnStop.Enabled = true; }); } else modInfo = mod; metaWait.Set(); }, error => { Program.MainWindow.InvokeFunc(() => { label1.Text = string.Format("Download of metadata failed: {0}", error.Message); container.btnStop.Enabled = true; }); metaWait.Set(); }); //if (down != null) WaitHandle.WaitAll(new WaitHandle[] { down.WaitHandle, metaWait }); //else metaWait.WaitOne(); var waitHandles = new List<EventWaitHandle>(); waitHandles.Add(metaWait); if (down != null) waitHandles.Add(down.WaitHandle); if (engine != null) waitHandles.Add(engine.WaitHandle); if (waitHandles.Any()) WaitHandle.WaitAll(waitHandles.ToArray()); if ((down != null && down.IsComplete == false) || (engine != null && engine.IsComplete == false) || modInfo==null) { Program.MainWindow.InvokeFunc(() => { label1.Text = string.Format("Download of {0} failed", missionName); container.btnStop.Enabled = true; }); } if (modInfo != null && (down == null || down.IsComplete == true) && (engine == null || engine.IsComplete == true)) { if (Utils.VerifySpringInstalled()) { var spring = new Spring(Program.SpringPaths); spring.StartGame(Program.TasClient, null, null, modInfo.MissionScript, Program.Conf.UseSafeMode); var cs = GlobalConst.GetContentService(); cs.NotifyMissionRun(Program.Conf.LobbyPlayerName, missionName); } Program.MainWindow.InvokeFunc(() => Program.NotifySection.RemoveBar(this)); } }); }
/// <summary> /// singleton, dont use, internal for designer /// </summary> internal BattleBar() { InitializeComponent(); Program.ToolTip.SetText(btnLeave, "Leave this battle"); picoChat.ChatBackgroundColor = TextColor.background; //same color as Program.Conf.BgColor picoChat.IRCForeColor = 14; //mirc grey. Unknown use picoChat.DefaultTooltip = "Last lines from room chat, click to enter full screen chat"; gameBox.BackColor = Color.Transparent; btnStart.Image = Buttons.fight.GetResizedWithCache(38, 38); btnStart.ImageAlign = ContentAlignment.MiddleCenter; btnStart.TextImageRelation = TextImageRelation.ImageAboveText; btnStart.Text = "Play"; btnLeave.Image = Buttons.exit.GetResizedWithCache(38, 38); btnLeave.ImageAlign = ContentAlignment.MiddleCenter; btnLeave.TextImageRelation = TextImageRelation.ImageAboveText; btnLeave.Text = "Leave"; Program.ToolTip?.SetText(btnStart, "Start battle"); Program.ToolTip?.SetText(btnLeave, "Quit battle"); picoChat.Visible = false; btnStart.Click += btnStart_Click; btnLeave.Click += BtnLeaveClick; client = Program.TasClient; spring = new Spring(Program.SpringPaths); try { // silly way to create speech and voice engines on runtime - needed due to mono crash speech = Activator.CreateInstance(Type.GetType("ZeroKLobby.ChatToSpeech"), spring); } catch (Exception ex) { Trace.TraceWarning("Failed to init VoiceCommands:{0}", ex.Message); } spring.SpringExited += (s, e) => { client.ChangeMyUserStatus(isInGame: false); if (e.IsCrash) { Program.MainWindow.InvokeFunc(() => { var defaultButton = MessageBoxDefaultButton.Button2; var icon = MessageBoxIcon.None; if ( MessageBox.Show(this, "Do you want me to set Low details?\n(will effect: lups.cfg and springsettings.cfg)\n\nIf you wish to file a bug report, please include a copy of infolog.txt in your game data folder (accessible through Settings).\nUpload it to a text sharing site such as pastebin.com.", "Spring engine has crashed, update your video and audio drivers please!", MessageBoxButtons.YesNo, icon, defaultButton) == DialogResult.Yes) { Program.Conf.UseSafeMode = true; Program.EngineConfigurator.Configure(true, 0); } }); } }; spring.SpringStarted += (s, e) => { Program.MainWindow.SwitchMusicOnOff(false); client.ChangeMyUserStatus(isInGame: true); }; client.Rang += (s, e) => { if (e.Place == SayPlace.Channel) MainWindow.Instance.NotifyUser($"chat/{e.Target}", $"Attention needed in {e.Target} channel", true, true); else { MainWindow.Instance.NotifyUser("chat/battle", "Someone demands your attention in battle room!", true, true); AutoRespond(); } }; client.BattleJoined += (s, e) => { if (!isVisible) ManualBattleStarted(); if (IsHostGameRunning()) btnStart.Text = "Rejoin"; else btnStart.Text = "Start"; //client.ChangeMyUserStatus(false, false); var battle = client.MyBattle; lastBattleFounder = battle.FounderName; //Title = string.Format("Joined battle room hosted by {0}", battle.Founder.Name); //TitleTooltip = "Use button on the left side to start a game"; radioPlay.Visible = true; radioSpec.Visible = true; btnStart.Visible = true; Program.Downloader.GetResource(DownloadType.MAP, battle.MapName); Program.Downloader.GetResource(DownloadType.MOD, battle.ModName); Program.Downloader.GetResource(DownloadType.ENGINE, battle.EngineVersion); if (gameBox.Image != null) gameBox.Image.Dispose(); CreateBattleIcon(Program.BattleIconManager.GetBattleIcon(battle.BattleID)); RefreshTooltip(); if (Program.TasClient.MyBattle != null) NavigationControl.Instance.Path = "chat/battle"; client.ChangeMyBattleStatus(desiredSpectatorState, HasAllResources() ? SyncStatuses.Synced : SyncStatuses.Unsynced, 0); }; client.MyBattleMapChanged += (s, e) => { if (client.MyBattle != null && !Program.SpringScanner.HasResource(client.MyBattle.MapName)) { client.ChangeMyBattleStatus(syncStatus: SyncStatuses.Unsynced); Program.Downloader.GetResource(DownloadType.MAP, client.MyBattle.MapName); } RefreshTooltip(); }; client.MyBattleHostExited += (s, e) => { btnStart.Text = "Start"; }; client.ConnectSpringReceived += (s, e) => { try { btnStart.Text = "Rejoin"; List<Download> downloads = new List<Download>(); downloads.Add(Program.Downloader.GetResource(DownloadType.ENGINE, e.Engine)); downloads.Add(Program.Downloader.GetResource(DownloadType.MOD, e.Game)); downloads.Add(Program.Downloader.GetResource(DownloadType.MAP, e.Map)); downloads = downloads.Where(x => x != null).ToList(); if (downloads.Count > 0) { var dd = new WaitDownloadDialog(downloads); if (dd.ShowDialog(Program.MainWindow) == DialogResult.Cancel) return; } if (spring.IsRunning) spring.ExitGame(); spring.ConnectGame(e.Ip, e.Port, client.UserName, e.ScriptPassword, e.Engine); } catch (Exception ex) { MessageBox.Show(this, "Error starting spring: " + ex.Message); } RefreshTooltip(); }; client.BattleMyUserStatusChanged += (s, e) => { if (client.MyBattleStatus != null) { //btnStart.Enabled = client.MyBattleStatus.SyncStatus == SyncStatuses.Synced; if (client.MyBattleStatus.IsSpectator && radioPlay.Checked) ChangeGuiSpectatorWithoutEvent(false); // i was spectated if (!client.MyBattleStatus.IsSpectator && radioSpec.Checked) ChangeGuiSpectatorWithoutEvent(true); //i was unspectated } }; client.BattleClosed += (s, e) => { btnStart.Text = "Start"; if (gameBox.Image != null) gameBox.Image.Dispose(); gameBox.Image = null; RefreshTooltip(); Stop(); }; client.MyBattleRemoved += (s, e) => { var t = new Timer(); var tryCount = 0; t.Interval = 1000; t.Tick += (s2, e2) => { tryCount++; if (tryCount > 15) { t.Stop(); t.Dispose(); } else if (client.IsLoggedIn && client.MyBattle == null) { var bat = client.ExistingBattles.Values.FirstOrDefault(x => x.FounderName == lastBattleFounder && !x.IsPassworded); if (bat != null) { ActionHandler.JoinBattle(bat.BattleID, null); t.Stop(); t.Dispose(); } } }; t.Start(); }; client.ConnectionLost += (s, e) => { if (gameBox.Image != null) gameBox.Image.Dispose(); gameBox.Image = null; RefreshTooltip(); Stop(); }; timer.Tick += (s, e) => { if (client.IsLoggedIn) { if (WindowsApi.IdleTime.TotalMinutes > Program.Conf.IdleTime) { if (!client.MyUser.IsAway) client.ChangeMyUserStatus(isAway: true); } else { if (client.MyUser.IsAway) client.ChangeMyUserStatus(isAway: false); } CheckMyBattle(); } }; Program.MainWindow.navigationControl.PageChanged += s => { if (s != "chat/battle") { picoChat.Visible = true; } else picoChat.Visible = false; }; timer.Interval = 1000; timer.Start(); Program.BattleIconManager.BattleChanged += BattleIconManager_BattleChanged; //picoChat.Font = new Font(Program.Conf.ChatFont.FontFamily, Program.Conf.ChatFont.Size*0.8f); picoChat.ShowHistory = false; picoChat.ShowJoinLeave = false; //picoChat.HideScroll = true; BattleChatControl.BattleLine += (s, e) => picoChat.AddLine(e.Data); picoChat.MouseClick += (s, e) => NavigationControl.Instance.Path = "chat/battle"; }
internal static int FilterUsers(string[] words, TasClient tas, Spring spring, out string[] vals, out int[] indexes) { var b = tas.MyBattle; var i = 0; var temp = b.Users.Values.Select(u => u.Name).ToList(); if (spring.IsRunning) foreach (var u in spring.StartContext.Players) { if (!temp.Contains(u.Name)) temp.Add(u.Name); } return Filter(temp.ToArray(), words, out vals, out indexes); }
public VoteSplitPlayers(TasClient tas, Spring spring, AutoHost ah): base(tas, spring, ah) {}
public VoteResign(TasClient tas, Spring spring, AutoHost ah): base(tas, spring, ah) {}
public static void SayBattle(TasClient tas, Spring spring, string text, bool ingame) { tas.Say(SayPlace.Battle, "", text, true); if (spring.IsRunning && ingame) spring.SayGame(text); }
public VoteMove(TasClient tas, Spring spring, AutoHost ah): base(tas, spring, ah) {}
private static void ProcessPlanetWars(Spring.SpringBattleContext result, ZkLobbyServer.ZkLobbyServer server, SpringBattle sb, ZkDataContext db, StringBuilder text) { if (result.LobbyStartContext.Mode != AutohostMode.Planetwars || sb.PlayerCount < 2 || sb.Duration >= GlobalConst.MinDurationForPlanetwars) return; List<int> winnerTeams = sb.SpringBattlePlayers.Where(x => x.IsInVictoryTeam && !x.IsSpectator).Select(x => x.AllyNumber).Distinct().ToList(); int? winNum = null; if (winnerTeams.Count == 1) { winNum = winnerTeams[0]; if (winNum > 1) winNum = null; } PlanetWarsTurnHandler.EndTurn(result.LobbyStartContext.Map, result.OutputExtras, db, winNum, sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account).ToList(), text, sb, sb.SpringBattlePlayers.Where(x => !x.IsSpectator && x.AllyNumber == 0).Select(x => x.Account).ToList(), server.PlanetWarsEventCreator); // TODO HACK Global.PlanetWarsMatchMaker.RemoveFromRunningBattles(context.AutohostName); }
public static void JoinBattleSpec(int battleId) { Battle bat; if (!Program.TasClient.ExistingBattles.TryGetValue(battleId, out bat)) return; Program.TasClient.Say(SayPlace.User, bat.Founder.Name, string.Format("!adduser {0}", Program.TasClient.UserName),false); var de = Program.Downloader.GetAndSwitchEngine(bat.EngineVersion); var dm = Program.Downloader.GetResource(DownloadType.MAP, bat.MapName); var dg = Program.Downloader.GetResource(DownloadType.MOD, bat.ModName); ZkData.Utils.StartAsync(() => { if (de != null) { de.WaitHandle.WaitOne(); if (de.IsComplete == false) return; } if (dm != null) { dm.WaitHandle.WaitOne(); if (dm.IsComplete == false) return; } if (dg != null) { dg.WaitHandle.WaitOne(); if (dg.IsComplete == false) return; } var spring = new Spring(Program.SpringPaths); Thread.Sleep(200);// give host time to adduser spring.StartGame(Program.TasClient, null, null, null, Program.Conf.UseSafeMode, battleOverride: bat); }); }
public VoteSetOptions(TasClient tas, Spring spring, AutoHost ah): base(tas, spring, ah) {}
public void StartScriptMission(string missionName) { var serv = GlobalConst.GetContentService(); var profile = serv.GetScriptMissionData(missionName); var downloads = new List<Download>(); downloads.Add(Program.Downloader.GetResource(DownloadType.RAPID, profile.ModName)); downloads.Add(Program.Downloader.GetResource(DownloadType.MAP, profile.MapName)); downloads.Add(Program.Downloader.GetResource(DownloadType.ENGINE, Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride)); if (profile.ManualDependencies != null) foreach (var entry in profile.ManualDependencies) if (!string.IsNullOrEmpty(entry)) downloads.Add(Program.Downloader.GetResource(DownloadType.NOTKNOWN, entry)); downloads = downloads.Where(x => x != null).ToList(); if (downloads.Count > 0) { var dd = new WaitDownloadDialog(downloads); if (dd.ShowDialog(Program.MainWindow) == DialogResult.Cancel) return; } var spring = new Spring(Program.SpringPaths); var name = Program.Conf.LobbyPlayerName; if (string.IsNullOrEmpty(name)) name = "Player"; spring.RunLocalScriptGame( profile.StartScript.Replace("%MOD%", profile.ModName).Replace("%MAP%", profile.MapName).Replace("%NAME%", name), Program.TasClient.ServerWelcome.Engine ?? GlobalConst.DefaultEngineOverride); serv.NotifyMissionRun(name, profile.Name); }
//[STAThread] private static void Main(params string[] args) { var startupPath = Path.GetDirectoryName(Path.GetFullPath(Application.ExecutablePath)); var springPaths = new SpringPaths(null, startupPath); Spring runningSpring = null; springPaths.MakeFolders(); TcpTransport connection = null; // speed up spring start springPaths.SpringVersionChanged += (sender, eventArgs) => { Utils.StartAsync( () => { UnitSync unitSync = null; try { unitSync = new UnitSync(springPaths); // initialize unitsync to avoid slowdowns when starting if (unitSync.UnitsyncWritableFolder != springPaths.WritableDirectory) { // unitsync created its cache in different folder than is used to start spring -> move it var fi = ArchiveCache.GetCacheFile(unitSync.UnitsyncWritableFolder); if (fi != null) File.Copy(fi.FullName, Path.Combine(springPaths.WritableDirectory, "cache", fi.Name), true); } } finally { unitSync?.Dispose(); } }); }; Config config = null; try { config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(startupPath + "/config.json")); } catch (Exception) { } CefWrapper.Initialize(startupPath + "/render", args); var springScanner = new SpringScanner(springPaths); springScanner.Start(); EventHandler<ProgressEventArgs> workHandler = (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_work(" + JsonConvert.SerializeObject(e) + ");"); }; springScanner.WorkStarted += workHandler; springScanner.WorkProgressChanged += workHandler; springScanner.WorkStopped += (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_work(null);"); }; springScanner.LocalResourceAdded += (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_add(" + JsonConvert.SerializeObject(e.Item) + ")"); }; springScanner.LocalResourceRemoved += (s, e) => { CefWrapper.ExecuteJavascript("on_spring_scanner_remove(" + JsonConvert.SerializeObject(e.Item) + ")"); }; var downloader = new PlasmaDownloader.PlasmaDownloader(springScanner, springPaths); //rapid downloader.GetAndSwitchEngine(GlobalConst.DefaultEngineOverride); // ZKL's downloader doesn't send events to monitor download progress, so we have to poll it. Timer pollDownloads = new Timer(); pollDownloads.Interval = 250; pollDownloads.Tick += (s, e) => { CefWrapper.ExecuteJavascript("on_downloads_change(" + JsonConvert.SerializeObject(downloader.Downloads) + ")"); }; // Through some WinAPI dark magic it manages to use the message pump in the window that is run by CEF. // Can this be dangerous? pollDownloads.Start(); CefWrapper.RegisterApiFunction( "getEngines", () => { return new List<string> { "100.0" }; // TODO: stub }); CefWrapper.RegisterApiFunction("getMods", () => { return springScanner.GetAllModResource(); }); CefWrapper.RegisterApiFunction("getMaps", () => { return springScanner.GetAllMapResource(); }); CefWrapper.RegisterApiFunction( "downloadEngine", (string engine) => { // Don't let GetAndSwitchEngine() touch the main SpringPaths. var path = new SpringPaths(springPaths.GetEngineFolderByVersion(engine), springPaths.WritableDirectory); downloader.GetAndSwitchEngine(engine, path); }); CefWrapper.RegisterApiFunction("downloadMod", (string game) => { downloader.GetResource(DownloadType.MOD, game); }); CefWrapper.RegisterApiFunction("downloadMap", (string map) => { downloader.GetResource(DownloadType.MAP, map); }); CefWrapper.RegisterApiFunction( "abortDownload", (string name) => { downloader.Downloads.FirstOrDefault(d => d.Name == name)?.Abort(); }); CefWrapper.RegisterApiFunction( "startSpringScript", (string engineVer, string script) => { if (runningSpring != null) return null; // Ultimately we should get rid of the concept of a "current set engine", but for now let's work around it. var path = new SpringPaths(springPaths.GetEngineFolderByVersion(engineVer), springPaths.WritableDirectory); runningSpring = new Spring(path); runningSpring.SpringExited += (obj, evt) => { CefWrapper.ExecuteJavascript("on_spring_exit(" + (evt.Data ? "true" : "false") + ");"); runningSpring = null; }; try { runningSpring.StartSpring(script); return null; } catch (Exception e) { runningSpring = null; return e.Message; } }); CefWrapper.RegisterApiFunction( "connect", (string host, int port) => { if (connection != null) connection.RequestClose(); connection = new TcpTransport(host, port); connection.ConnectAndRun( async (s) => CefWrapper.ExecuteJavascript( $"on_lobby_message({CefWrapper.mangleUtf8(JsonConvert.SerializeObject(s))})"), async () => { }, async (requested) => CefWrapper.ExecuteJavascript( $"on_connection_closed({CefWrapper.mangleUtf8(JsonConvert.SerializeObject(requested))})") ); }); CefWrapper.RegisterApiFunction("disconnect", () => connection?.RequestClose()); CefWrapper.RegisterApiFunction("sendLobbyMessage", (string msg) => connection?.SendLine(CefWrapper.unmangleUtf8(msg) + '\n')); CefWrapper.RegisterApiFunction( "readConfig", () => { try { return JsonConvert.DeserializeObject(File.ReadAllText(startupPath + "/config.json")); } catch(FileNotFoundException) { return null; } }); CefWrapper.RegisterApiFunction("saveConfig", (object conf) => File.WriteAllText(startupPath + "/config.json", JsonConvert.SerializeObject(conf, Formatting.Indented))); CefWrapper.RegisterApiFunction("setFullscreen", (bool fullscreen) => CefWrapper.SetFullscreen(fullscreen)); var fileUrl = new Uri(startupPath + "/zkwl/index.html"); CefWrapper.StartMessageLoop(fileUrl.AbsoluteUri, "black", !config?.lobbyWindowed ?? true); CefWrapper.Deinitialize(); downloader.Dispose(); springScanner.Dispose(); }
private static void RecordMissionResult(Spring spring, Mod modInfo) { if (spring.Context.GameEndedOk && !spring.Context.IsCheating) { Trace.TraceInformation("Submitting score for mission " + modInfo.Name); try { var service = GlobalConst.GetContentService(); Task.Factory.StartNew(() => { try { service.SubmitMissionScore(Program.Conf.LobbyPlayerName, ZkData.Utils.HashLobbyPassword(Program.Conf.LobbyPlayerPassword), modInfo.Name, spring.Context.MissionScore ?? 0, spring.Context.MissionFrame/30, spring.Context.MissionVars); } catch (Exception ex) { Trace.TraceError("Error sending score: {0}", ex); } }); } catch (Exception ex) { Trace.TraceError($"Error sending mission score: {ex}"); } } }
public AbstractPoll(TasClient tas, Spring spring, AutoHost ah) { this.tas = tas; this.spring = spring; this.ah = ah; }
void spring_SpringExited(object sender, Spring.SpringBattleContext springBattleContext) { if (speechSynthesizer!=null) speechSynthesizer.SpeakAsyncCancelAll(); }
/// <summary> /// singleton, dont use, internal for designer /// </summary> internal BattleBar() { InitializeComponent(); picoChat.ChatBackgroundColor = TextColor.background; //same color as Program.Conf.BgColor picoChat.IRCForeColor = 14; //mirc grey. Unknown use picoChat.DefaultTooltip = "Last lines from room chat, click to enter full screen chat"; client = Program.TasClient; spring = new Spring(Program.SpringPaths); try { // silly way to create speech and voice engines on runtime - needed due to mono crash speech = Activator.CreateInstance(Type.GetType("ZeroKLobby.ChatToSpeech"), spring); } catch (Exception ex) { Trace.TraceWarning("Failed to init VoiceCommands:{0}", ex.Message); } spring.SpringExited += (s, e) => { client.ChangeMyUserStatus(isInGame: false); if (e.Data) { Program.MainWindow.InvokeFunc(() => { var defaultButton = MessageBoxDefaultButton.Button2; var icon = MessageBoxIcon.None; if ( MessageBox.Show("Do you want me to set Low details?\n(will effect: lups.cfg and springsettings.cfg)\n\nIf you wish to file a bug report, please include a copy of infolog.txt in your game data folder (accessible through Settings).\nUpload it to a text sharing site such as pastebin.com.", "Spring engine has crashed, update your video and audio drivers please!", MessageBoxButtons.YesNo, icon, defaultButton) == DialogResult.Yes) { Program.Conf.UseSafeMode = true; Program.EngineConfigurator.Configure(true, 0); } }); } }; spring.SpringStarted += (s, e) => { Program.MainWindow.SwitchMusicOnOff(false); client.ChangeMyUserStatus(isInGame: true); }; client.Rang += (s, e) => { if (e.User == GlobalConst.NightwatchName) //Nightwatch RING is from UserController.cs (website code) MainWindow.Instance.NotifyUser("chat/zkadmin", "New report arrive at zkadmin channel", true, true); else { MainWindow.Instance.NotifyUser("chat/battle", "Someone demands your attention in battle room!", true, true); AutoRespond(); } }; client.BattleJoined += (s, e) => { if (!isVisible) ManualBattleStarted(); if (IsHostGameRunning()) barContainer.btnDetail.Text = "Rejoin"; else barContainer.btnDetail.Text = "Start"; //client.ChangeMyUserStatus(false, false); var battle = client.MyBattle; lastBattleFounder = battle.Founder.Name; if (battle.Founder.Name.StartsWith("PlanetWars") || battle.Founder.Name.StartsWith("Zk")) ChangeDesiredSpectatorState(false); // TODO pw unpsec hack, remove later if (battle.IsQueue) { barContainer.Title = string.Format("Joined {0} Quick Match Queue", battle.QueueName); barContainer.TitleTooltip = "Please await people, game will start automatically"; lbQueue.Visible = true; radioPlay.Visible = false; radioSpec.Visible = false; barContainer.btnDetail.Visible = false; } else { barContainer.Title = string.Format("Joined battle room hosted by {0}", battle.Founder.Name); barContainer.TitleTooltip = "Use button on the left side to start a game"; lbQueue.Visible = false; radioPlay.Visible = true; radioSpec.Visible = true; barContainer.btnDetail.Visible = true; } Program.Downloader.GetResource(DownloadType.MAP, battle.MapName); Program.Downloader.GetResource(DownloadType.MOD, battle.ModName); engineVersionNeeded = battle.EngineVersion; if (engineVersionNeeded != Program.SpringPaths.SpringVersion) Program.Downloader.GetAndSwitchEngine(engineVersionNeeded); else engineVersionNeeded = null; if (gameBox.Image != null) gameBox.Image.Dispose(); DpiMeasurement.DpiXYMeasurement(this); int scaledIconHeight = DpiMeasurement.ScaleValueY(BattleIcon.Height); int scaledIconWidth = DpiMeasurement.ScaleValueX(BattleIcon.Width); gameBox.Image = new Bitmap(scaledIconWidth, scaledIconHeight); using (var g = Graphics.FromImage(gameBox.Image)) { g.FillRectangle(Brushes.White, 0, 0, scaledIconWidth, scaledIconHeight); var bi = Program.BattleIconManager.GetBattleIcon(battle.BattleID); g.DrawImageUnscaled(bi.Image, 0, 0); } gameBox.Invalidate(); RefreshTooltip(); var team = battle.GetFreeTeamID(client.UserName); client.ChangeMyBattleStatus(desiredSpectatorState, HasAllResources() ? SyncStatuses.Synced : SyncStatuses.Unsynced, 0, team); }; client.MyBattleMapChanged += (s, e) => { if (client.MyBattle != null && !Program.SpringScanner.HasResource(client.MyBattle.MapName)) { client.ChangeMyBattleStatus(syncStatus: SyncStatuses.Unsynced); Program.Downloader.GetResource(DownloadType.MAP, client.MyBattle.MapName); } RefreshTooltip(); }; client.MyBattleHostExited += (s, e) => { barContainer.btnDetail.Text = "Start"; }; client.MyBattleStarted += (s, e) => { try { barContainer.btnDetail.Text = "Rejoin"; if (client.MyBattleStatus.SyncStatus == SyncStatuses.Synced) { if (Utils.VerifySpringInstalled()) { if (spring.IsRunning) spring.ExitGame(); lastScript = spring.ConnectGame(client.MyBattle.Ip, client.MyBattle.HostPort, client.UserName, client.MyBattle.Users[client.UserName].ScriptPassword); //use MT tag when in spectator slot } } } catch (Exception ex) { MessageBox.Show("Error starting spring: " + ex.Message); } RefreshTooltip(); }; client.BattleMyUserStatusChanged += (s, e) => { if (client.MyBattleStatus != null) { barContainer.btnDetail.Enabled = client.MyBattleStatus.SyncStatus == SyncStatuses.Synced; if (client.MyBattleStatus.IsSpectator && radioPlay.Checked) ChangeGuiSpectatorWithoutEvent(false); // i was spectated if (!client.MyBattleStatus.IsSpectator && radioSpec.Checked) ChangeGuiSpectatorWithoutEvent(true); //i was unspectated } }; client.BattleClosed += (s, e) => { barContainer.btnDetail.Text = "Start"; if (gameBox.Image != null) gameBox.Image.Dispose(); gameBox.Image = null; RefreshTooltip(); Stop(); }; client.MyBattleRemoved += (s, e) => { var t = new Timer(); var tryCount = 0; t.Interval = 1000; t.Tick += (s2, e2) => { tryCount++; if (tryCount > 15) { t.Stop(); t.Dispose(); } else if (client.IsLoggedIn && client.MyBattle == null) { var bat = client.ExistingBattles.Values.FirstOrDefault(x => x.Founder.Name == lastBattleFounder && !x.IsPassworded); if (bat != null) { ActionHandler.JoinBattle(bat.BattleID, null); t.Stop(); t.Dispose(); } } }; t.Start(); }; client.ConnectionLost += (s, e) => { if (gameBox.Image != null) gameBox.Image.Dispose(); gameBox.Image = null; RefreshTooltip(); Stop(); }; // process special queue message to display in label client.Said += (s, e) => { if (e.Place == SayPlace.Battle && client.MyBattle != null && client.MyBattle.Founder.Name == e.UserName && e.Text.StartsWith("Queue")) { var t = e.Text.Substring(6); queueLabelFormatter = Regex.Replace(t, "([0-9]+)s", m => { var queueSeconds = int.Parse(m.Groups[1].Value); queueTarget = DateTime.Now.AddSeconds(queueSeconds); return "{0}s"; }); lbQueue.Text = string.Format(queueLabelFormatter, Math.Round(queueTarget.Subtract(DateTime.Now).TotalSeconds)); } }; timer.Tick += (s, e) => { if (client.IsLoggedIn) { if (WindowsApi.IdleTime.TotalMinutes > Program.Conf.IdleTime) { if (!client.MyUser.IsAway) client.ChangeMyUserStatus(isAway: true); } else { if (client.MyUser.IsAway) client.ChangeMyUserStatus(isAway: false); } CheckMyBattle(); } if (client.MyBattle != null && client.MyBattle.IsQueue) { lbQueue.Text = string.Format(queueLabelFormatter, Math.Round(queueTarget.Subtract(DateTime.Now).TotalSeconds)); } }; timer.Interval = 1000; timer.Start(); Program.BattleIconManager.BattleChanged += BattleIconManager_BattleChanged; //picoChat.Font = new Font(Program.Conf.ChatFont.FontFamily, Program.Conf.ChatFont.Size*0.8f); picoChat.ShowHistory = false; picoChat.ShowJoinLeave = false; //picoChat.HideScroll = true; BattleChatControl.BattleLine += (s, e) => picoChat.AddLine(e.Data); picoChat.MouseClick += (s, e) => NavigationControl.Instance.Path = "chat/battle"; }
public AutoHost(MetaDataCache cache, AhConfig config, int hostingPort, SpawnConfig spawn) { this.config = config; Commands = new CommandList(config); this.cache = cache; SpawnConfig = spawn; this.hostingPort = hostingPort; string version = config.SpringVersion ?? Program.main.Config.SpringVersion ?? GlobalConst.DefaultEngineOverride; springPaths = new SpringPaths(Program.main.paths.GetEngineFolderByVersion(version), Program.main.Config.DataDir); springPaths.SpringVersionChanged += (s, e) => { if (!String.IsNullOrEmpty(requestedEngineChange) && requestedEngineChange == springPaths.SpringVersion) { config.SpringVersion = requestedEngineChange; springPaths.SetEnginePath(Program.main.paths.GetEngineFolderByVersion(requestedEngineChange)); requestedEngineChange = null; tas.Say(SayPlace.Battle, "", "rehosting to engine version " + springPaths.SpringVersion, true); ComRehost(TasSayEventArgs.Default, new string[] { }); } }; spring = new Spring(springPaths) { UseDedicatedServer = true }; bool isManaged = SpawnConfig == null && config.Mode != AutohostMode.None; tas = new TasClient(MainConfig.SpringieVersion, isManaged ? Login.ClientTypes.SpringieManaged : Login.ClientTypes.Springie, Program.main.Config.IpOverride); pollTimer = new Timer(PollTimeout*1000); pollTimer.Enabled = false; pollTimer.AutoReset = false; pollTimer.Elapsed += pollTimer_Elapsed; spring.SpringExited += spring_SpringExited; spring.GameOver += spring_GameOver; spring.SpringExited += spring_SpringExited; spring.SpringStarted += spring_SpringStarted; spring.PlayerSaid += spring_PlayerSaid; spring.BattleStarted += spring_BattleStarted; tas.BattleUserLeft += tas_BattleUserLeft; tas.UserStatusChanged += tas_UserStatusChanged; tas.BattleUserJoined += tas_BattleUserJoined; tas.MyBattleMapChanged += tas_MyBattleMapChanged; tas.BattleOpened += tas_BattleOpened; tas.UserAdded += (o, u) => { if (u.Name == GetAccountName()) OpenBattleRoom(null, null); }; tas.RegistrationDenied += (s, e) => { Trace.TraceWarning("Registration denied: {0} {1}", e.ResultCode.Description(), e.Reason); CloneNumber++; tas.Login(GetAccountName(), config.Password); }; tas.RegistrationAccepted += (s, e) => tas.Login(GetAccountName(), config.Password); tas.ConnectionLost += tas_ConnectionLost; tas.Connected += tas_Connected; tas.LoginDenied += tas_LoginDenied; tas.LoginAccepted += tas_LoginAccepted; tas.Said += tas_Said; tas.MyBattleStarted += tas_MyStatusChangedToInGame; linkSpringieClient = new ResourceLinkSpringieClient(this); // queue autohost if (config != null && config.MinToJuggle != null && SpawnConfig == null) { queue = new MatchMakerQueue(this); } Program.main.Downloader.PackagesChanged += Downloader_PackagesChanged; timer = new Timer(15000); timer.Elapsed += (s, e) => { try { timer.Stop(); timerTick++; // auto update engine branch if (!String.IsNullOrEmpty(config.AutoUpdateSpringBranch) && timerTick%4 == 0) CheckEngineBranch(); // auto verify pw map if (!spring.IsRunning && config.Mode != AutohostMode.None) if (SpawnConfig == null && config.Mode == AutohostMode.Planetwars) ServerVerifyMap(false); // auto start split vote if (!spring.IsRunning && config.SplitBiggerThan != null && tas.MyBattle != null && config.SplitBiggerThan < tas.MyBattle.NonSpectatorCount) { if (DateTime.Now.Subtract(spring.GameExited).TotalSeconds >= GameExitSplitDelay) ComSplitPlayers(TasSayEventArgs.Default, new string[]{}); /* int cnt = tas.MyBattle.NonSpectatorCount; if (cnt > lastSplitPlayersCountCalled && cnt%2 == 0) { StartVote(new VoteSplitPlayers(tas, spring, this), TasSayEventArgs.Default, new string[] { }); lastSplitPlayersCountCalled = cnt; }*/ } // auto rehost to latest mod version if (!string.IsNullOrEmpty(config.AutoUpdateRapidTag) && SpawnConfig == null) UpdateRapidMod(config.AutoUpdateRapidTag); } catch (Exception ex) { Trace.TraceError(ex.ToString()); } finally { timer.Start(); } }; timer.Start(); }
private void Event_ComboBox_SelectedIndexChanged(object sender, EventArgs e) { if (suppressEvent_SelectedIndexChanged) return; if ((sender as Control).Name == "map_comboBox" && map_comboBox.SelectedItem!= null) { string mapName = (string)map_comboBox.SelectedItem; int selectedView = normalRadioButton.Checked ? 0 : (elevationRadioButton.Checked ? 1 : 2); Set_MapImages(mapName, selectedView); if (infoLabel.Text.StartsWith("Select map")) infoLabel.Text = ""; Set_InfoLabel(); Program.Conf.SkirmisherMap = mapName; } else if ((sender as Control).Name == "game_comboBox" && game_comboBox.SelectedItem != null) { string gameName = (string)game_comboBox.SelectedItem; bool foundLocally=false; for(int i=0; i<modCache_folder.Count;i++) { var mod = modCache_folder[i]; var version = modCache_folder[i].PrimaryModVersion; version = string.IsNullOrWhiteSpace(version) ? "" : " " + version; var modName = mod.Name + version; if (gameName == modName) { modCache_folder[i] = SkirmishControlTool.GetOneSddMod(mod); CallBack_Mod(modCache_folder[i]); foundLocally = true; break; } } if (!foundLocally) { //run GetMod() in new thread, then call "CallBack_Mod()" in current thread when finish(?). //TODO: GetModAsync() is not an offline method, it rely on downloading server generated mod/map information to work. This can cause (minor) error if user downloaded a unique map or mod that server haven't process yet! Program.SpringScanner.MetaData.GetModAsync( gameName, mod => Invoke(new Action(() => { try { CallBack_Mod(mod); } catch (Exception ex) { Trace.TraceError("CallBack_Mod(mod) error: {0}", ex.ToString()); } })), exception => Trace.TraceError("CallBack_Mod(mod) error: {0}", exception.ToString())); //Program.SpringScanner.MetaData.GetModAsync( // (string)game_comboBox.SelectedItem, // mod=>{ // try { CallBack_Mod(mod); } // catch (Exception ex) { Trace.TraceError("CallBack_Mod(mod) error: {0}", ex.ToString()); } // }, // exception => { Trace.TraceError("CallBack_Mod(mod) error: {0}", exception.ToString()); }, // (string)engine_comboBox.SelectedItem); } if (infoLabel.Text.StartsWith("Select game")) infoLabel.Text = ""; Set_InfoLabel(); var defGameVer = Program.Downloader.PackageDownloader.GetByTag(KnownGames.GetDefaultGame().RapidTag); if (defGameVer!=null && gameName == defGameVer.InternalName) Program.Conf.SkirmisherGame = null; //tell Skirmisher to use default in next startup else Program.Conf.SkirmisherGame = gameName; } else if ((sender as Control).Name == "engine_comboBox" && engine_comboBox.SelectedItem != null) { string springVersion = (string)engine_comboBox.SelectedItem; string engineFolder = ZkData.Utils.MakePath(Program.SpringPaths.WritableDirectory, "engine"); if (Environment.OSVersion.Platform != PlatformID.Unix) engineFolder = engineFolder + "\\" + springVersion; else engineFolder = engineFolder + "/" + springVersion; if (Program.SpringPaths.HasEngineVersion(springVersion)) Program.SpringPaths.SetEnginePath (engineFolder); spring = new Spring(Program.SpringPaths); if (infoLabel.Text.StartsWith("Select engine")) infoLabel.Text = ""; Set_InfoLabel(); if ((string)engine_comboBox.SelectedItem == (GlobalConst.DefaultEngineOverride ?? Program.TasClient.ServerSpringVersion)) Program.Conf.SkirmisherEngine = null; //tell Skirmihser to use default in next run else Program.Conf.SkirmisherEngine = (string)engine_comboBox.SelectedItem; if (Program.SpringPaths.HasEngineVersion(springVersion)) springAi = SkirmishControlTool.GetSpringAIs(engineFolder); else springAi.Clear(); } //check if we have entered game, map and engine value so that we can update the Sync icon. bool missingEntry = (game_comboBox.SelectedItem == null || engine_comboBox.SelectedItem == null || map_comboBox.SelectedItem == null); if (missingEntry != wasMissingEntry) Refresh_PlayerBox(); wasMissingEntry = missingEntry; }
public VotePlanet(TasClient tas, Spring spring, AutoHost ah): base(tas, spring, ah) {}
static void GeneratePlayerSection(StringBuilder script, Spring.SpringBattleContext setup) { // ordinary battle stuff var userNum = 0; var teamNum = 0; var aiNum = 0; foreach (var u in setup.LobbyStartContext.Players) { Dictionary<string, string> parameters = new Dictionary<string, string>(); setup?.LobbyStartContext?.UserParameters.TryGetValue(u.Name, out parameters); ScriptAddUser(script, userNum, u, teamNum, parameters); if (!u.IsSpectator) { ScriptAddTeam(script, teamNum, userNum, u.AllyID); teamNum++; } foreach (var b in setup.LobbyStartContext.Bots.Where(x => x.Owner == u.Name)) { ScriptAddBot(script, aiNum, teamNum, userNum, b.BotAI, b.BotName); aiNum++; ScriptAddTeam(script, teamNum, userNum, b.AllyID); teamNum++; } userNum++; } // add unowned bots to last player foreach (var b in setup.LobbyStartContext.Bots.Where(x => !setup.LobbyStartContext.Players.Any(y=>y.Name == x.Owner))) { ScriptAddBot(script, aiNum, teamNum, userNum-1, b.BotAI, b.BotName); aiNum++; ScriptAddTeam(script, teamNum, userNum-1, b.AllyID); teamNum++; } // ALLIANCES AND START BOXES var startboxes = new StringBuilder(); startboxes.Append("return { "); script.AppendLine(); for (var allyNumber = 0; allyNumber < MaxAllies; allyNumber++) { script.AppendFormat("[ALLYTEAM{0}]\n", allyNumber); script.AppendLine("{"); script.AppendLine(" NumAllies=0;"); script.AppendLine("}"); } startboxes.Append("}"); script.AppendLine(); script.AppendLine(" [MODOPTIONS]"); script.AppendLine(" {"); script.AppendFormat(" startboxes={0};\n", startboxes.ToString()); // write final options to script foreach (var kvp in setup?.LobbyStartContext?.ModOptions) script.AppendFormat(" {0}={1};\n", kvp.Key, kvp.Value); script.AppendLine(" }"); script.AppendLine("}"); }
public static void Respond(TasClient tas, Spring spring, TasSayEventArgs e, string text) { var p = SayPlace.User; bool emote = false; if (e.Place == SayPlace.Battle) { p = SayPlace.BattlePrivate; emote = true; } if (e.Place == SayPlace.Game && spring.IsRunning) spring.SayGame(text); else tas.Say(p, e.UserName, text, emote); }
public VoteForceStart(TasClient tas, Spring spring, AutoHost ah): base(tas, spring, ah) {}
private static SpringBattle SaveSpringBattle(Spring.SpringBattleContext result, ZkDataContext db) { var sb = new SpringBattle { HostAccountID = Account.AccountByName(db, result.LobbyStartContext.FounderName)?.AccountID, Mode = result.LobbyStartContext.Mode, Duration = result.Duration, EngineGameID = result.EngineBattleID, MapResourceID = db.Resources.Single(x => x.InternalName == result.LobbyStartContext.Map).ResourceID, ModResourceID = db.Resources.Single(x => x.InternalName == result.LobbyStartContext.Mod).ResourceID, HasBots = result.LobbyStartContext.Bots.Any(), IsMission = result.LobbyStartContext.IsMission, PlayerCount = result.ActualPlayers.Count(x => !x.IsSpectator), StartTime = result.StartTime, Title = result.LobbyStartContext.Title, ReplayFileName = result.ReplayName, EngineVersion = result.LobbyStartContext.EngineVersion, IsMatchMaker = result.LobbyStartContext.IsMatchMakerGame }; db.SpringBattles.InsertOnSubmit(sb); foreach (BattlePlayerResult p in result.ActualPlayers) { var account = Account.AccountByName(db, p.Name); if (account != null) { sb.SpringBattlePlayers.Add(new SpringBattlePlayer { Account = account, AccountID = account.AccountID, AllyNumber = p.AllyNumber, IsInVictoryTeam = p.IsVictoryTeam, IsSpectator = p.IsSpectator, LoseTime = p.LoseTime }); } } db.SaveChanges(); return db.SpringBattles.FirstOrDefault(x => x.SpringBattleID == sb.SpringBattleID); // reselect from db to get proper lazy proxies }