public void SetForHosting(LobbyHostingContext startContext, string ip, int?port, string myUser, string myPassword) { LobbyStartContext = startContext; EngineVersion = startContext.EngineVersion; IsHosting = true; IpAddress = ip ?? "127.0.0.1"; Port = port ?? 8452; MyUserName = myUser; MyPassword = myPassword; ActualPlayers = LobbyStartContext.Players.Select( x => new BattlePlayerResult(x.Name) { AllyNumber = x.AllyID, IsSpectator = x.IsSpectator, IsVictoryTeam = false, IsIngameReady = false, IsIngame = false, }).ToList(); }
public event EventHandler <SpringLogEventArgs> GameOver; // game has ended public string HostGame(LobbyHostingContext startContext, string host, int port) { if (!File.Exists(paths.GetDedicatedServerPath(startContext.EngineVersion))) { throw new ApplicationException($"Dedicated server executable not found: {paths.GetDedicatedServerPath(startContext.EngineVersion)}"); } Context = new SpringBattleContext(); Context.SetForHosting(startContext, host, port, null, null); if (!IsRunning) { talker = new Talker(); talker.SpringEvent += talker_SpringEvent; Context.IsHosting = true; scriptPath = Utils.MakePath(paths.WritableDirectory, "script_" + startContext.FounderName + ".txt").Replace('\\', '/'); var script = ScriptGenerator.GenerateHostScript(Context, talker.LoopbackPort); timer.Start(); StartDedicated(script); return(script); } else { Trace.TraceError("Dedicated server already running"); } return(null); }
public string HostGame(LobbyHostingContext startContext, string host, int port, string myName, string myPassword) { if (!File.Exists(paths.GetSpringExecutablePath(startContext.EngineVersion))) { throw new ApplicationException($"Spring executable not found: {paths.GetSpringExecutablePath(startContext.EngineVersion)}"); } Context = new SpringBattleContext(); Context.SetForHosting(startContext, host, port, myName, myPassword); if (!IsRunning) { Context.IsHosting = true; scriptPath = Utils.MakePath(paths.WritableDirectory, "script_" + myName + ".txt").Replace('\\', '/'); var script = ScriptGenerator.GenerateHostScript(Context, 0); StartSpring(script); return(script); } else { Trace.TraceError("Spring already running"); } return(null); }
public static BalanceTeamsResult BalanceTeams(LobbyHostingContext context, bool isGameStart, int?allyCount, bool?clanWise) { var playerCount = context.Players.Count(x => !x.IsSpectator); if (clanWise == null && (context.Mode == AutohostMode.Teams)) { clanWise = true; } var res = PerformBalance(context, isGameStart, allyCount, clanWise); if (context.Mode != AutohostMode.None && context.Mode != AutohostMode.GameChickens) { if (isGameStart) { // dont allow to start alone if (playerCount <= 1) { return(new BalanceTeamsResult { CanStart = false, Message = "Sorry this room type needs more human players, open cooperative battle to fight against bots." }); } } } if (isGameStart) { VerifySpecCheaters(context, res); } return(res); }
BalanceTeamsResult PlanetwarsBalance(LobbyHostingContext context) { var res = new BalanceTeamsResult(); res.CanStart = true; res.DeleteBots = true; using (var db = new ZkDataContext()) { res.Message = ""; var planet = db.Galaxies.Single(x => x.IsDefault).Planets.First(x => x.Resource.InternalName == context.Map); res.Players = context.Players; // bots game var cnt = 0; if (planet.PlanetStructures.Any(x => !string.IsNullOrEmpty(x.StructureType.EffectBots))) { foreach (var b in planet.PlanetStructures.Select(x => x.StructureType).Where(x => !string.IsNullOrEmpty(x.EffectBots))) { res.Bots.Add(new BotTeam { AllyID = 1, BotAI = b.EffectBots, BotName = "Aliens" + cnt++ }); } res.Message += "This planet is infested by aliens, fight for your survival"; return(res); } return(res); } }
/// <summary> /// Writes join messages and tells <see cref="Springie" /> to force spectate player if needed /// </summary> public static PlayerJoinResult AutohostPlayerJoined(LobbyHostingContext context, int accountID) { var res = new PlayerJoinResult(); var mode = context.Mode; if (mode == AutohostMode.Planetwars) { using (var db = new ZkDataContext()) { var planet = db.Galaxies.Single(x => x.IsDefault).Planets.SingleOrDefault(x => x.Resource.InternalName == context.Map); if (planet == null) { res.PublicMessage = "Invalid map"; return(res); } var account = db.Accounts.Find(accountID); // accountID is in fact lobbyID if (account != null) { var owner = ""; if (planet.Account != null) { owner = planet.Account.Name; } var facRoles = string.Join(",", account.AccountRolesByAccountID.Where(x => !x.RoleType.IsClanOnly).Select(x => x.RoleType.Name).ToList()); if (!string.IsNullOrEmpty(facRoles)) { facRoles += " of " + account.Faction.Name + ", "; } var clanRoles = string.Join(",", account.AccountRolesByAccountID.Where(x => x.RoleType.IsClanOnly).Select(x => x.RoleType.Name).ToList()); if (!string.IsNullOrEmpty(clanRoles)) { clanRoles += " of " + account.Clan.ClanName; } res.PublicMessage = string.Format("Greetings {0} {1}{2}, welcome to {3} planet {4} {6}/PlanetWars/Planet/{5}", account.Name, facRoles, clanRoles, owner, planet.Name, planet.PlanetID, GlobalConst.BaseSiteUrl); return(res); } } } return(null); }
public LobbyHostingContext GetContext() { var ret = new LobbyHostingContext(); ret.FounderName = FounderName; ret.Map = MapName; ret.Mod = ModName; ret.Title = Title; ret.EngineVersion = EngineVersion; ret.IsMission = IsMission; ret.Players = Users.Values.Where(x => x != null).Select(x => x.ToPlayerTeam()).ToList(); ret.Bots = Bots.Values.Where(x => x != null).Select(x => x.ToBotTeam()).ToList(); ret.ModOptions = new Dictionary <string, string>(ModOptions); ret.Mode = Mode; ret.IsMatchMakerGame = IsMatchMakerBattle; ret.BattleID = BattleID; return(ret); }
/// <summary> /// Makes <see cref="Springie" /> print a message if two or more people have the same IP /// </summary> /// <param name="context"></param> /// <param name="res">The <see cref="BalanceTeamsResult" /> to write the message to</param> static void VerifySpecCheaters(LobbyHostingContext context, BalanceTeamsResult res) { try { // find specs with same IP as some player and kick them using (var db = new ZkDataContext()) { var ids = context.Players.Select(y => (int?)y.LobbyID).ToList(); var ipByLobbyID = db.Accounts.Where(x => ids.Contains(x.AccountID)) .ToDictionary(x => x.AccountID, x => x.AccountIPs.OrderByDescending(y => y.LastLogin).Select(y => y.IP).FirstOrDefault()); // lobbyid -> ip mapping foreach (var grp in context.Players.GroupBy(x => ipByLobbyID[x.LobbyID]).Where(x => x.Count() > 1)) { res.Message += $"\nThese people are in same location: {string.Join(", ", grp.Select(x => x.Name))}"; } } } catch (Exception ex) { Trace.TraceError("Error checking speccheaters: {0}", ex); } }
/// <summary> /// Picks a map and writes a message if applicable /// </summary> /// <param name="context">The battle whose map needs selection</param> /// <remarks> /// <para> /// For Planetwars, picks the map given by the Planetwars matchmaker; else picks a featured map with the /// appropriate tags /// </para> /// <para>For team and chickens games, picks a map of appropriate size based on current player count</para> /// <para> /// For FFA, prefer maps that have a number of boxes equal to player count, or at least a number of boxes that is /// a multiple of player count /// </para> /// </remarks> public static Resource GetRecommendedMap(LobbyHostingContext context, MapSupportLevel minimumSupportLevel = MapSupportLevel.Featured, IQueryable <Resource> candidates = null) { var mode = context.Mode; using (var db = new ZkDataContext()) { List <Resource> list = null; var humanPlayers = context.Players.Count(x => !x.IsSpectator); var botPlayers = context.Bots.Count; var allyteams = context.Players.Where(x => !x.IsSpectator).Select(p => p.AllyID).Union(context.Bots.Select(b => b.AllyID)).Distinct().Count(); var level = context.IsMatchMakerGame ? MapSupportLevel.MatchMaker : minimumSupportLevel; switch (mode) { case AutohostMode.GameChickens: if (!context.Bots.Any(b => b.IsChicken)) { mode = AutohostMode.Teams; } break; case AutohostMode.GameFFA: allyteams = humanPlayers; break; case AutohostMode.None: if (allyteams > 2) { mode = AutohostMode.GameFFA; } if (context.Players.Where(x => !x.IsSpectator).Select(p => p.AllyID).Distinct().Count() == 1 && botPlayers > 0 && context.Bots.Any(b => b.IsChicken)) { mode = AutohostMode.GameChickens; } if (humanPlayers == 2 && botPlayers == 0 && allyteams == 2) { mode = AutohostMode.Game1v1; } break; } var ret = candidates ?? db.Resources.AsQueryable(); switch (mode) { case AutohostMode.Teams: case AutohostMode.None: ret = ret.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsTeams != false && x.MapIsSpecial != true); if (humanPlayers > 11) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16); } else if (humanPlayers > 8) { ret = ret.Where( x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16 && x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24); } else if (humanPlayers > 5) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24 || x.MapIs1v1 == true); } else { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 16 * 16 || x.MapIs1v1 == true); } list = ret.ToList(); break; case AutohostMode.Game1v1: list = ret.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIs1v1 == true && x.MapIsSpecial != true).ToList(); break; case AutohostMode.GameChickens: if (!UseNormalMapForChickens) { ret = ret.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsSpecial != true && x.MapIsChickens == true); } if (!ret.Any() || UseNormalMapForChickens) { ret = ret.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsSpecial != true && (x.MapWaterLevel == 1)); if (humanPlayers > 5) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16); } else if (humanPlayers > 4) { ret = ret.Where( x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16 && x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24); } else if (humanPlayers > 2) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24 || x.MapIs1v1 == true); } else { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 16 * 16 || x.MapIs1v1 == true); } } list = ret.ToList(); break; case AutohostMode.GameFFA: list = ret.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsFfa == true && x.MapFFAMaxTeams == allyteams) .ToList(); if (!list.Any()) { list = ret.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsFfa == true && (humanPlayers % x.MapFFAMaxTeams == 0)).ToList(); } if (!list.Any()) { list = ret.Where(x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsFfa == true).ToList(); } break; } if (list != null) { var r = new Random(); if (list.Count > 0) { var resource = list[r.Next(list.Count)]; return(resource); } } } return(null); }
/// <summary> /// Sets up all the things that Springie needs to know for the battle: how to balance, who to get extra commanders, /// what PlanetWars structures to create, etc. /// </summary> public static LobbyHostingContext GetDedicatedServerStartSetup(LobbyHostingContext context) { var ret = context; try { var mode = context.Mode; var commProfiles = new LuaTable(); var db = new ZkDataContext(); // calculate to whom to send extra comms var accountIDsWithExtraComms = new List <int>(); if (mode == AutohostMode.Planetwars || mode == AutohostMode.GameFFA || mode == AutohostMode.Teams) { var groupedByTeam = context.Players.Where(x => !x.IsSpectator).GroupBy(x => x.AllyID).OrderByDescending(x => x.Count()); var biggest = groupedByTeam.FirstOrDefault(); if (biggest != null) { foreach (var other in groupedByTeam.Skip(1)) { var cnt = biggest.Count() - other.Count(); if (cnt > 0) { foreach (var a in other.Select(x => db.Accounts.First(y => y.AccountID == x.LobbyID)) .OrderByDescending(x => x.GetRating(RatingCategory.Casual).Elo) .Take(cnt)) { accountIDsWithExtraComms.Add(a.AccountID); } } } } } // write Planetwars details to modoptions (for widget) Faction attacker = null; Faction defender = null; Planet planet = null; if (mode == AutohostMode.Planetwars) { planet = db.Galaxies.First(x => x.IsDefault).Planets.First(x => x.Resource.InternalName == context.Map); attacker = context.Players.Where(x => x.AllyID == 0 && !x.IsSpectator) .Select(x => db.Accounts.First(y => y.AccountID == x.LobbyID)) .Where(x => x.Faction != null) .Select(x => x.Faction) .First(); defender = planet.Faction; if (attacker == defender) { defender = null; } ret.ModOptions["attackingFaction"] = attacker.Shortcut; ret.ModOptions["attackingFactionName"] = attacker.Name; ret.ModOptions["attackingFactionColor"] = attacker.Color; if (defender != null) { ret.ModOptions["defendingFaction"] = defender.Shortcut; ret.ModOptions["defendingFactionName"] = defender.Name; ret.ModOptions["defendingFactionColor"] = defender.Color; } else { ret.ModOptions["defendingFaction"] = "Mercenary"; ret.ModOptions["defendingFactionName"] = "Local militia"; ret.ModOptions["defendingFactionColor"] = "#CCCCCC"; } ret.ModOptions["planet"] = planet.Name; } // write player custom keys (level, elo, is muted, etc.) foreach (var p in context.Players) { var user = db.Accounts.Where(x => x.AccountID == p.LobbyID).Include(x => x.RelalationsByOwner).FirstOrDefault(); if (user != null) { var userParams = new Dictionary <string, string>(); ret.UserParameters[p.Name] = userParams; userParams["LobbyID"] = user.AccountID.ToString(); userParams["CountryCode"] = user.HideCountry ? "??" : user.Country; var userBanMuted = Punishment.GetActivePunishment(user.AccountID, null, null, x => x.BanMute) != null; if (userBanMuted) { userParams["muted"] = "1"; } userParams["faction"] = user.Faction != null ? user.Faction.Shortcut : ""; userParams["clan"] = user.Clan != null ? user.Clan.Shortcut : ""; userParams["clanfull"] = user.Clan != null ? user.Clan.ClanName : ""; userParams["level"] = user.Level.ToString(); //userParams["mm_elo"] = Math.Round(user.EffectiveMmElo).ToString(); //userParams["casual_elo"] = Math.Round(user.EffectiveElo).ToString(); userParams["elo"] = Math.Round(user.GetBestRating().Elo).ToString(); userParams["icon"] = user.GetIconName(); userParams["avatar"] = user.Avatar; userParams["badges"] = string.Join(",", user.GetBadges()); userParams["admin"] = user.AdminLevel >= AdminLevel.Moderator ? "1" : "0"; userParams["room_boss"] = p.Name == context.FounderName ? "1" : "0"; if (p.PartyID.HasValue) { userParams["PartyID"] = p.PartyID.ToString(); } var userSpecChatBlocked = Punishment.GetActivePunishment(user.AccountID, null, null, x => x.BanSpecChat) != null;; userParams["can_spec_chat"] = userSpecChatBlocked ? "0" : "1"; userParams["ignored"] = string.Join(",", user.RelalationsByOwner.Where(x => x.Relation == Relation.Ignore).Select(x => x.Target.Name)); userParams["friends"] = string.Join(",", user.RelalationsByOwner.Where(x => x.Relation == Relation.Friend).Select(x => x.Target.Name)); if (!p.IsSpectator) { // set valid PW structure attackers if (mode == AutohostMode.Planetwars) { userParams["pwRank"] = (user.AccountRolesByAccountID.Where( x => !x.RoleType.IsClanOnly && (x.RoleType.RestrictFactionID == null || x.RoleType.RestrictFactionID == user.FactionID)).OrderBy(x => x.RoleType.DisplayOrder).Select(x => (int?)x.RoleType.DisplayOrder).FirstOrDefault() ?? 999).ToString(); var allied = user.Faction != null && defender != null && user.Faction != defender && defender.HasTreatyRight(user.Faction, x => x.EffectPreventIngamePwStructureDestruction == true, planet); if (!allied && user.Faction != null && (user.Faction == attacker || user.Faction == defender)) { userParams["canAttackPwStructures"] = "1"; } userParams["pwInstructions"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(GetPwInstructions(planet, user, db, attacker))); } if (accountIDsWithExtraComms.Contains(user.AccountID)) { userParams["extracomm"] = "1"; } var commProfileIDs = new LuaTable(); var userCommandersBanned = Punishment.GetActivePunishment(user.AccountID, null, null, x => x.BanCommanders) != null; if (!userCommandersBanned) { // set up commander data foreach (var c in user.Commanders.Where(x => x.Unlock != null && x.ProfileNumber <= GlobalConst.CommanderProfileCount) ) { try { var commProfile = new LuaTable(); if (string.IsNullOrEmpty(c.Name) || c.Name.Any(x => x == '"')) { c.Name = c.CommanderID.ToString(); } commProfiles.Add("c" + c.CommanderID, commProfile); commProfileIDs.Add("c" + c.CommanderID); // process decoration icons var decorations = new LuaTable(); foreach (var d in c.CommanderDecorations.Where(x => x.Unlock != null).OrderBy(x => x.SlotID).Select(x => x.Unlock)) { var iconData = db.CommanderDecorationIcons.FirstOrDefault(x => x.DecorationUnlockID == d.UnlockID); if (iconData != null) { string iconName = null, iconPosition = null; // FIXME: handle avatars and preset/custom icons if (iconData.IconType == (int)DecorationIconTypes.Faction) { iconName = user.Faction != null ? user.Faction.Shortcut : null; } else if (iconData.IconType == (int)DecorationIconTypes.Clan) { iconName = user.Clan != null ? user.Clan.Shortcut : null; } if (iconName != null) { iconPosition = CommanderDecoration.GetIconPosition(d); var entry = new LuaTable(); entry.Add("image", iconName); decorations.Add("icon_" + iconPosition.ToLower(), entry); } } else { decorations.Add(d.Code); } } commProfile["name"] = c.Name.Substring(0, Math.Min(25, c.Name.Length)); commProfile["chassis"] = c.Unlock.Code; commProfile["decorations"] = decorations; var modules = new LuaTable(); commProfile["modules"] = modules; for (var i = 1; i <= GlobalConst.NumCommanderLevels; i++) { var modulesForLevel = new LuaTable(); modules.Add(modulesForLevel); var modulesOrdered = c.CommanderModules.Where(x => x.CommanderSlot.MorphLevel == i).ToList(); var slots = db.CommanderSlots.ToList().Where(x => x.MorphLevel == i && (x.ChassisID == null || (x.ChassisID == c.ChassisUnlockID))).ToList(); slots.Sort(delegate(CommanderSlot x, CommanderSlot y) { UnlockTypes type1 = x.UnlockType; UnlockTypes type2 = y.UnlockType; if (type1 == UnlockTypes.WeaponManualFire || type1 == UnlockTypes.WeaponBoth) { type1 = UnlockTypes.Weapon; } if (type2 == UnlockTypes.WeaponManualFire || type2 == UnlockTypes.WeaponBoth) { type2 = UnlockTypes.Weapon; } int result = type1.CompareTo(type2); if (result == 0) { return(x.CommanderSlotID.CompareTo(y.CommanderSlotID)); } else { return(result); } }); foreach (var slot in slots) { String value = String.Empty; var module = c.CommanderModules.FirstOrDefault(x => x.SlotID == slot.CommanderSlotID); if (module != null) { value = module.Unlock.Code; } modulesForLevel.Add(value); } } } catch (Exception ex) { Trace.TraceError(ex.ToString()); throw new ApplicationException( $"Error processing commander: {c.CommanderID} - {c.Name} of player {user.AccountID} - {user.Name}", ex); } } } else { userParams["jokecomm"] = "1"; } userParams["commanders"] = commProfileIDs.ToBase64String(); } } } ret.ModOptions["commanderTypes"] = commProfiles.ToBase64String(); // set PW structures if (mode == AutohostMode.Planetwars) { var owner = planet.Faction != null ? planet.Faction.Shortcut : ""; var pwStructures = new LuaTable(); foreach ( var s in planet.PlanetStructures.Where(x => x.StructureType != null && !string.IsNullOrEmpty(x.StructureType.IngameUnitName))) { pwStructures.Add(s.StructureType.IngameUnitName, new LuaTable { { "unitname", s.StructureType.IngameUnitName }, { "owner", s.Account?.Name }, { "canBeEvacuated", s.StructureType.IsIngameEvacuable }, { "canBeDestroyed", s.StructureType.IsIngameDestructible }, //{ "isDestroyed", s.IsDestroyed ? true : false }, { "name", $"{owner} {s.StructureType.Name} ({(s.Account != null ? s.Account.Name : "unowned")})" }, { "description", s.StructureType.Description } }); } ret.ModOptions["planetwarsStructures"] = pwStructures.ToBase64String(); } return(ret); } catch (Exception ex) { Trace.TraceError(ex.ToString()); throw; } }
/// <summary> /// Calls <see cref="LegacyBalance" /> with the appropriate parameters depending on game settings and conditions /// </summary> /// <param name="isGameStart"> /// If true and <see cref="AutohostMode" /> is none, do nothing (i.e. don't autobalance custom /// rooms at start) /// </param> /// <param name="allyCount"></param> /// <param name="clanWise"></param> /// <param name="config"></param> /// <remarks>Also removes bots from team games, and tells people to add bots to a chicken game if absent</remarks> static BalanceTeamsResult PerformBalance( LobbyHostingContext context, bool isGameStart, int?allyCount, bool?clanWise) { var res = new BalanceTeamsResult(); var mode = context.Mode; using (var db = new ZkDataContext()) { switch (mode) { case AutohostMode.None: { if (!isGameStart) { if (allyCount == null || allyCount == 2) { res = PartitionBalance.BalanceInterface(2, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); } else { res = new Balancer().LegacyBalance(allyCount ?? 2, clanWise == true ? BalanceMode.ClanWise : BalanceMode.Normal, context); } } } break; case AutohostMode.Teams: case AutohostMode.Game1v1: { res = PartitionBalance.BalanceInterface(2, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); res.DeleteBots = true; } break; case AutohostMode.GameChickens: { res.Players = context.Players.ToList(); res.Bots = context.Bots.ToList(); foreach (var p in res.Players) { p.AllyID = 0; } foreach (var b in res.Bots) { b.AllyID = 1; } // add chickens via modoptions hackish thingie string chickBot = null; if (context.ModOptions?.TryGetValue("chickenailevel", out chickBot) == true && !string.IsNullOrEmpty(chickBot) && chickBot != "none") { res.Bots.RemoveAll(x => x.BotAI.StartsWith("Chicken:")); res.Bots.Add(new BotTeam() { AllyID = 1, BotName = "default_Chicken", BotAI = chickBot }); } if (!res.Bots.Any() && res.Players.Count > 0) { //res.Message = "Add some bot (computer player) as your enemy. Use button on bottom left. Chicken or CAI is recommended."; var map = db.Resources.FirstOrDefault(x => x.InternalName == context.Map); if (map?.MapIsChickens == true) { res.Bots.Add(new BotTeam() { AllyID = 1, BotName = "default_Chicken", BotAI = "Chicken: Normal", }); } else { for (int i = 1; i <= res.Players.Where(x => !x.IsSpectator).Count(); i++) { res.Bots.Add(new BotTeam() { AllyID = 1, BotName = "cai" + i, BotAI = "CAI", }); } } res.Message = "Adding computer AI player for you"; } } break; case AutohostMode.GameFFA: { res.DeleteBots = true; var map = db.Resources.Single(x => x.InternalName == context.Map); if (map.MapFFAMaxTeams != null) { res = new Balancer().LegacyBalance( allyCount ?? map.MapFFAMaxTeams.Value, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); } else { res = new Balancer().LegacyBalance( allyCount ?? map.MapFFAMaxTeams ?? 8, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); } return(res); } case AutohostMode.Planetwars: return(new Balancer().PlanetwarsBalance(context)); } return(res); } }
/// <summary> /// The function that actually moves players arounds /// </summary> /// <param name="teamCount"></param> /// <param name="mode"></param> /// <param name="b"></param> /// <param name="unmovablePlayers"></param> /// <returns></returns> public BalanceTeamsResult LegacyBalance(int teamCount, BalanceMode mode, LobbyHostingContext b, params List <Account>[] unmovablePlayers) { var ret = new BalanceTeamsResult(); if (b.IsMatchMakerGame) { mode = BalanceMode.Party; // override, for matchmaker mode is always party } try { ret.CanStart = true; ret.Players = b.Players.ToList(); var db = new ZkDataContext(); var nonSpecList = b.Players.Where(y => !y.IsSpectator).Select(y => (int?)y.LobbyID).ToList(); var accs = db.Accounts.Where(x => nonSpecList.Contains(x.AccountID)).ToList(); if (accs.Count < 1) { ret.CanStart = false; return(ret); } if (teamCount < 1) { teamCount = 1; } if (teamCount > accs.Count) { teamCount = accs.Count; } if (teamCount == 1) { foreach (var p in ret.Players) { p.AllyID = 0; } return(ret); } maxTeamSize = (int)Math.Ceiling(accs.Count / (double)teamCount); teams.Clear(); for (var i = 0; i < teamCount; i++) { var team = new BalanceTeam(); teams.Add(team); if (unmovablePlayers != null && unmovablePlayers.Length > i) { var unmovables = unmovablePlayers[i]; team.AddItem(new BalanceItem(b.IsMatchMakerGame, unmovablePlayers[i].ToArray()) { CanBeMoved = false }); accs.RemoveAll(x => unmovables.Any(y => y.AccountID == x.AccountID)); } } balanceItems = new List <BalanceItem>(); if (mode == BalanceMode.Party) { var clanGroups = accs.GroupBy(x => b.Players.First(p => p.Name == x.Name).PartyID ?? x.AccountID).ToList(); if (teamCount > clanGroups.Count() || clanGroups.Any(x => x.Count() > maxTeamSize)) { mode = BalanceMode.Normal; } else { balanceItems.AddRange(clanGroups.Select(x => new BalanceItem(b.IsMatchMakerGame, x.ToArray()))); } } if (mode == BalanceMode.ClanWise) { var clanGroups = accs.GroupBy(x => b.Players.First(p => p.Name == x.Name).PartyID ?? x.ClanID ?? x.AccountID).ToList(); if (teamCount > clanGroups.Count() || clanGroups.Any(x => x.Count() > maxTeamSize)) { mode = BalanceMode.Normal; } else { balanceItems.AddRange(clanGroups.Select(x => new BalanceItem(b.IsMatchMakerGame, x.ToArray()))); } } if (mode == BalanceMode.FactionWise) { balanceItems.Clear(); var factionGroups = accs.GroupBy(x => x.FactionID ?? x.AccountID).ToList(); balanceItems.AddRange(factionGroups.Select(x => new BalanceItem(b.IsMatchMakerGame, x.ToArray()))); } if (mode == BalanceMode.Normal) { balanceItems.Clear(); balanceItems.AddRange(accs.Select(x => new BalanceItem(b.IsMatchMakerGame, x))); } var sw = new Stopwatch(); sw.Start(); RecursiveBalance(0); sw.Stop(); if (bestTeams == null) { var fallback = new Balancer().LegacyBalance(teamCount, BalanceMode.ClanWise, b, null); fallback.Message += "\nWarning: STANDARD TEAM BALANCE USED, PlanetWars not possible with those teams, too many from one faction"; return(fallback); } var minSize = bestTeams.Min(x => x.Count); var maxSize = bestTeams.Max(x => x.Count); var sizesWrong = maxSize / (double)minSize > MaxTeamSizeDifferenceRatio; // cbalance failed, rebalance using normal if (mode == BalanceMode.ClanWise && (bestTeams == null || GetTeamsDifference(bestTeams) > MaxCbalanceDifference || sizesWrong)) { return(new Balancer().LegacyBalance(teamCount, BalanceMode.Normal, b, unmovablePlayers)); } // cbalance failed, rebalance using normal if (sizesWrong && mode == BalanceMode.FactionWise) { var fallback = new Balancer().LegacyBalance(teamCount, BalanceMode.ClanWise, b, null); fallback.Message += "\nWarning: STANDARD TEAM BALANCE USED, PlanetWars not possible with those teams, too many from one faction"; return(fallback); // fallback standard balance if PW balance fails /*ret.CanStart = false; * ret.Message = string.Format("Failed to balance - too many people from same faction"); * return ret;*/ } if (unmovablePlayers != null && unmovablePlayers.Length > 0) { var minElo = bestTeams.Min(x => x.EloAvg); var maxElo = bestTeams.Max(x => x.EloAvg); if (maxElo - minElo > GlobalConst.MaxPwEloDifference) { var fallback = new Balancer().LegacyBalance(teamCount, BalanceMode.ClanWise, b, null); fallback.Message += "\nWarning: STANDARD TEAM BALANCE USED, PlanetWars not possible with those teams, too many from one faction"; return(fallback); // fallback standard balance if PW balance fails /* * ret.CanStart = false; * ret.Message = string.Format("Team difference is too big - win chance {0}% - spectate some or wait for more people", * Utils.GetWinChancePercent(maxElo - minElo)); * return ret;*/ } } if (bestTeams == null) { ret.CanStart = false; ret.Message = string.Format( "Failed to balance {0} - too many people from same clan or faction (in teams game you can try !random and !forcestart)"); return(ret); } if (unmovablePlayers == null || unmovablePlayers.Length == 0) { bestTeams = bestTeams.Shuffle(); // permute when not unmovable players present } var text = "( "; var lastTeamElo = 0.0; var allyNum = 0; foreach (var team in bestTeams) { if (allyNum > 0) { text += " : "; } text += string.Format("{0}", (allyNum + 1)); text += string.Format("={0}%)", (int)Math.Round((1.0 / (1.0 + Math.Pow(10, ((team.EloAvg - bestTeams.Where(x => !x.Equals(team)).Select(x => x.EloAvg).Average())) / 400.0))) * 100.0 * 2 / bestTeams.Count)); lastTeamElo = team.EloAvg; foreach (var u in team.Items.SelectMany(x => x.LobbyId)) { ret.Players.Single(x => x.LobbyID == u).AllyID = allyNum; } allyNum++; } text += ")"; ret.Message = string.Format( "{0} players balanced {2} to {1} teams {3}. {4} combinations checked, spent {5}ms of CPU time", bestTeams.Sum(x => x.Count), teamCount, mode, text, iterationsChecked, sw.ElapsedMilliseconds); } catch (Exception ex) { Trace.TraceError(ex.ToString()); ret.Message = ex.ToString(); ret.CanStart = false; } return(ret); }
//Interface function to comply with LegacyBalance public static BalanceTeamsResult BalanceInterface(int teamCount, BalanceMode mode, LobbyHostingContext b, params List <Account>[] unmovablePlayers) { if (b.Players.Where(y => !y.IsSpectator).Count() > 38) // dont try doing million+ iterations (arbitrarily chosen cap) { Trace.TraceWarning("PartitionBalance called with too many players: " + b.Players.Where(y => !y.IsSpectator).Count()); return(new Balancer().LegacyBalance(teamCount, mode, b, unmovablePlayers)); } if (teamCount != 2) { Trace.TraceWarning("PartitionBalance called with invalid number of teams: " + teamCount); return(new Balancer().LegacyBalance(teamCount, mode, b, unmovablePlayers)); } if (unmovablePlayers.Length > 0) { Trace.TraceWarning("PartitionBalance called with too many unmovable players: " + unmovablePlayers.Length); return(new Balancer().LegacyBalance(teamCount, mode, b, unmovablePlayers)); } if (mode == BalanceMode.FactionWise) { Trace.TraceWarning("PartitionBalance called with FactionWise balance mode, which is unsupported"); return(new Balancer().LegacyBalance(teamCount, mode, b, unmovablePlayers)); } try { if (b.IsMatchMakerGame) { mode = BalanceMode.Party; } BalanceTeamsResult ret = new BalanceTeamsResult(); ret.CanStart = true; ret.Players = b.Players.ToList(); ret.Bots = b.Bots.ToList(); List <PlayerItem> players = new List <PlayerItem>(); using (var db = new ZkDataContext()) { var nonSpecList = b.Players.Where(y => !y.IsSpectator).Select(y => (int?)y.LobbyID).ToList(); var accs = db.Accounts.Where(x => nonSpecList.Contains(x.AccountID)).ToList(); if (accs.Count < 1) { ret.CanStart = false; return(ret); } players = b.Players.Where(y => !y.IsSpectator).Select(x => new PlayerItem(x.LobbyID, accs.First(a => a.AccountID == x.LobbyID).GetRating(b.ApplicableRating).Elo, x.Clan, x.PartyID)).ToList(); } var dualResult = Balance(mode, players); dualResult.Players.ForEach(r => ret.Players.Where(x => x.LobbyID == r.LobbyID).ForEach(x => x.AllyID = r.AllyID)); ret.Message = dualResult.Message; return(ret); } catch (Exception ex) { Trace.TraceWarning("PartitionBalance encountered an error: " + ex); return(new Balancer().LegacyBalance(teamCount, mode, b, unmovablePlayers)); } }
/// <summary> /// Picks a map and writes a message if applicable /// </summary> /// <param name="context">The battle whose map needs selection</param> /// <remarks> /// <para> /// For Planetwars, picks the map given by the Planetwars matchmaker; else picks a featured map with the /// appropriate tags /// </para> /// <para>For team and chickens games, picks a map of appropriate size based on current player count</para> /// <para> /// For FFA, prefer maps that have a number of boxes equal to player count, or at least a number of boxes that is /// a multiple of player count /// </para> /// </remarks> public static Resource GetRecommendedMap(LobbyHostingContext context) { var mode = context.Mode; using (var db = new ZkDataContext()) { List <Resource> list = null; var players = context.Players.Count(x => !x.IsSpectator); var level = context.IsMatchMakerGame ? MapSupportLevel.MatchMaker : MapSupportLevel.Featured; switch (mode) { case AutohostMode.Teams: case AutohostMode.None: var ret = db.Resources.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsTeams != false && x.MapIsSpecial != true); if (players > 11) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16); } else if (players > 8) { ret = ret.Where( x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16 && x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24); } else if (players > 5) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24 || x.MapIs1v1 == true); } else { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 16 * 16 || x.MapIs1v1 == true); } list = ret.ToList(); break; case AutohostMode.Game1v1: list = db.Resources.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIs1v1 == true && x.MapIsSpecial != true).ToList(); break; case AutohostMode.GameChickens: ret = db.Resources.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsSpecial != true && (x.MapIsChickens == true || x.MapWaterLevel == 1)); if (players > 5) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16); } else if (players > 4) { ret = ret.Where( x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth > 16 * 16 && x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24); } else if (players > 2) { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 24 * 24 || x.MapIs1v1 == true); } else { ret = ret.Where(x => x.MapHeight * x.MapHeight + x.MapWidth * x.MapWidth <= 16 * 16 || x.MapIs1v1 == true); } list = ret.ToList(); break; case AutohostMode.GameFFA: list = db.Resources.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsFfa == true && x.MapFFAMaxTeams == players) .ToList(); if (!list.Any()) { list = db.Resources.Where( x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsFfa == true && (players % x.MapFFAMaxTeams == 0)).ToList(); } if (!list.Any()) { list = db.Resources.Where(x => x.TypeID == ResourceType.Map && x.MapSupportLevel >= level && x.MapIsFfa == true).ToList(); } break; } if (list != null) { var r = new Random(); if (list.Count > 0) { var resource = list[r.Next(list.Count)]; return(resource); } } } return(null); }
/// <summary> /// Sets up all the things that Springie needs to know for the battle: how to balance, who to get extra commanders, /// what PlanetWars structures to create, etc. /// </summary> public static LobbyHostingContext GetDedicatedServerStartSetup(LobbyHostingContext context) { var ret = context; try { var mode = context.Mode; var commProfiles = new LuaTable(); var db = new ZkDataContext(); // calculate to whom to send extra comms var accountIDsWithExtraComms = new Dictionary <int, int>(); if (mode == AutohostMode.Planetwars || mode == AutohostMode.GameFFA || mode == AutohostMode.Teams) { var groupedByTeam = context.Players.Where(x => !x.IsSpectator).GroupBy(x => x.AllyID).OrderByDescending(x => x.Count()); var biggest = groupedByTeam.FirstOrDefault(); if (biggest != null) { foreach (var other in groupedByTeam.Skip(1)) { var cnt = biggest.Count() - other.Count(); if (cnt > 0) { // example case: 3 players on this team, 8 players on largest team // 5 bonus comms to dole out to this team // per_player = 1 (integer result of 5/3) // remainder: 2, so now cnt = 2 // iterate over all players in this team // first player: cnt == 2, >0 so we give him a second extra comm // second player: cnt == 1, >0 so same deal // from now on cnt <= 0 so the last player only gets the one extra comm int per_player = cnt / other.Count(); cnt = cnt % other.Count(); foreach (var a in other .Select(x => db.Accounts.First(y => y.AccountID == x.LobbyID)) .OrderByDescending(x => x.GetRating(RatingCategory.Casual).Elo)) { accountIDsWithExtraComms.Add(a.AccountID, per_player + (cnt > 0 ? 1 : 0)); cnt--; } } } } } // write Planetwars details to modoptions (for widget) Faction attacker = null; Faction defender = null; Planet planet = null; if (mode == AutohostMode.Planetwars) { var galaxy = db.Galaxies.First(x => x.IsDefault); planet = galaxy.Planets.First(x => x.Resource.InternalName == context.Map); attacker = context.Players.Where(x => x.AllyID == 0 && !x.IsSpectator) .Select(x => db.Accounts.First(y => y.AccountID == x.LobbyID)) .Where(x => x.Faction != null) .Select(x => x.Faction) .First(); defender = planet.Faction; if (attacker == defender) { defender = null; } ret.ModOptions["attackingFaction"] = attacker.Shortcut; ret.ModOptions["attackingFactionName"] = attacker.Name; ret.ModOptions["attackingFactionColor"] = attacker.Color; if (defender != null) { ret.ModOptions["defendingFaction"] = defender.Shortcut; ret.ModOptions["defendingFactionName"] = defender.Name; ret.ModOptions["defendingFactionColor"] = defender.Color; } else { ret.ModOptions["defendingFaction"] = "Mercenary"; ret.ModOptions["defendingFactionName"] = "Local militia"; ret.ModOptions["defendingFactionColor"] = "#CCCCCC"; } ret.ModOptions["planet"] = planet.Name; ret.ModOptions["pw_galaxyTurn"] = galaxy.Turn.ToString(); ret.ModOptions["pw_baseIP"] = GlobalConst.BaseInfluencePerBattle.ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_dropshipIP"] = planet.GetEffectiveShipIpBonus(attacker).ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_defenseIP"] = planet.GetEffectiveIpDefense().ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_attackerIP"] = (planet.PlanetFactions.FirstOrDefault(x => x.FactionID == attacker.FactionID)?.Influence ?? 0).ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_maxIP"] = GlobalConst.PlanetWarsMaximumIP.ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_neededIP"] = GlobalConst.InfluenceToCapturePlanet.ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_attackerWinLoseCC"] = GlobalConst.PlanetWarsAttackerWinLoseCcMultiplier.ToString(CultureInfo.InvariantCulture); ret.ModOptions["pw_defenderWinKillCC"] = GlobalConst.PlanetWarsDefenderWinKillCcMultiplier.ToString(CultureInfo.InvariantCulture); } // write player custom keys (level, elo, is muted, etc.) foreach (var p in context.Players) { var user = db.Accounts.Where(x => x.AccountID == p.LobbyID).Include(x => x.RelalationsByOwner).FirstOrDefault(); if (user != null) { var userParams = new Dictionary <string, string>(); ret.UserParameters[p.Name] = userParams; userParams["LobbyID"] = user.AccountID.ToString(); userParams["CountryCode"] = user.HideCountry ? "??" : user.Country; var userBanMuted = Punishment.GetActivePunishment(user.AccountID, null, null, null, x => x.BanMute) != null; if (userBanMuted) { userParams["muted"] = "1"; } userParams["faction"] = user.Faction != null ? user.Faction.Shortcut : ""; userParams["clan"] = user.Clan != null ? user.Clan.Shortcut : ""; userParams["clanfull"] = user.Clan != null ? user.Clan.ClanName : ""; userParams["level"] = user.Level.ToString(); //userParams["mm_elo"] = Math.Round(user.EffectiveMmElo).ToString(); //userParams["casual_elo"] = Math.Round(user.EffectiveElo).ToString(); userParams["elo"] = Math.Round(user.GetRating(context.ApplicableRating).Elo).ToString(); userParams["elo_order"] = context.Players.Where(x => !x.IsSpectator) .Select(x => db.Accounts.First(y => y.AccountID == x.LobbyID)) .Where(x => x.GetRating(context.ApplicableRating).Elo > user.GetRating(context.ApplicableRating).Elo) .Count() .ToString(); userParams["icon"] = user.GetIconName(); userParams["avatar"] = user.Avatar; userParams["badges"] = string.Join(",", user.GetBadges()); userParams["admin"] = user.AdminLevel >= AdminLevel.Moderator ? "1" : "0"; userParams["room_boss"] = p.Name == context.FounderName ? "1" : "0"; if (p.PartyID.HasValue) { userParams["PartyID"] = p.PartyID.ToString(); } var userSpecChatBlocked = Punishment.GetActivePunishment(user.AccountID, null, null, null, x => x.BanSpecChat) != null;; userParams["can_spec_chat"] = userSpecChatBlocked ? "0" : "1"; userParams["ignored"] = string.Join(",", user.RelalationsByOwner.Where(x => x.Relation == Relation.Ignore).Select(x => x.Target.Name)); userParams["friends"] = string.Join(",", user.RelalationsByOwner.Where(x => x.Relation == Relation.Friend).Select(x => x.Target.Name)); if (!p.IsSpectator) { // set valid PW structure attackers if (mode == AutohostMode.Planetwars) { userParams["pwRank"] = (user.AccountRolesByAccountID.Where( x => !x.RoleType.IsClanOnly && (x.RoleType.RestrictFactionID == null || x.RoleType.RestrictFactionID == user.FactionID)).OrderBy(x => x.RoleType.DisplayOrder).Select(x => (int?)x.RoleType.DisplayOrder).FirstOrDefault() ?? 999).ToString(); var allied = user.Faction != null && defender != null && user.Faction != defender && defender.HasTreatyRight(user.Faction, x => x.EffectPreventIngamePwStructureDestruction == true, planet); if (!allied && user.Faction != null && (user.Faction == attacker || user.Faction == defender)) { userParams["canAttackPwStructures"] = "1"; } userParams["pwInstructions"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(GetPwInstructions(planet, user, db, attacker))); } if (accountIDsWithExtraComms.ContainsKey(user.AccountID)) { userParams["extracomm"] = accountIDsWithExtraComms[user.AccountID].ToString(); } var commProfileIDs = new LuaTable(); var userCommandersBanned = Punishment.GetActivePunishment(user.AccountID, null, null, null, x => x.BanCommanders) != null; if (!userCommandersBanned) { // set up commander data foreach (var c in user.Commanders.Where(x => x.Unlock != null && x.ProfileNumber <= GlobalConst.CommanderProfileCount) ) { try { var commProfile = new LuaTable(); if (string.IsNullOrEmpty(c.Name) || c.Name.Any(x => x == '"')) { c.Name = c.CommanderID.ToString(); } commProfiles.Add("c" + c.CommanderID, commProfile); commProfileIDs.Add("c" + c.CommanderID); // process decoration icons var decorations = new LuaTable(); foreach (var d in c.CommanderDecorations.Where(x => x.Unlock != null).OrderBy(x => x.SlotID).Select(x => x.Unlock)) { var iconData = db.CommanderDecorationIcons.FirstOrDefault(x => x.DecorationUnlockID == d.UnlockID); if (iconData != null) { string iconName = null, iconPosition = null; // FIXME: handle avatars and preset/custom icons if (iconData.IconType == (int)DecorationIconTypes.Faction) { iconName = user.Faction != null ? user.Faction.Shortcut : null; } else if (iconData.IconType == (int)DecorationIconTypes.Clan) { iconName = user.Clan != null ? user.Clan.Shortcut : null; } if (iconName != null) { iconPosition = CommanderDecoration.GetIconPosition(d); var entry = new LuaTable(); entry.Add("image", iconName); decorations.Add("icon_" + iconPosition.ToLower(), entry); } } else { decorations.Add(d.Code); } } commProfile["name"] = LuaTable.SanitizeString(c.Name.Substring(0, Math.Min(25, c.Name.Length))) ?? "dummy"; commProfile["chassis"] = c.Unlock.Code; commProfile["decorations"] = decorations; var modules = new LuaTable(); commProfile["modules"] = modules; for (var i = 1; i <= GlobalConst.NumCommanderLevels; i++) { var modulesForLevel = new LuaTable(); modules.Add(modulesForLevel); //var modulesOrdered = c.CommanderModules.Where(x => x.CommanderSlot.MorphLevel == i).ToList(); var slots = db.CommanderSlots.ToList().Where(x => x.MorphLevel == i && (x.ChassisID == null || (x.ChassisID == c.ChassisUnlockID))).ToList(); slots.Sort(delegate(CommanderSlot x, CommanderSlot y) { UnlockTypes type1 = x.UnlockType; UnlockTypes type2 = y.UnlockType; if (type1 == UnlockTypes.WeaponManualFire || type1 == UnlockTypes.WeaponBoth) { type1 = UnlockTypes.Weapon; } if (type2 == UnlockTypes.WeaponManualFire || type2 == UnlockTypes.WeaponBoth) { type2 = UnlockTypes.Weapon; } int result = type1.CompareTo(type2); if (result == 0) { return(x.CommanderSlotID.CompareTo(y.CommanderSlotID)); } else { return(result); } }); foreach (var slot in slots) { String value = String.Empty; var module = c.CommanderModules.FirstOrDefault(x => x.SlotID == slot.CommanderSlotID); if (module != null) { value = module.Unlock.Code; } modulesForLevel.Add(value); } } } catch (Exception ex) { Trace.TraceError(ex.ToString()); throw new ApplicationException( $"Error processing commander: {c.CommanderID} - {c.Name} of player {user.AccountID} - {user.Name}", ex); } } } else { userParams["jokecomm"] = "1"; } userParams["commanders"] = commProfileIDs.ToBase64String(); } } } ret.ModOptions["commanderTypes"] = commProfiles.ToBase64String(); /* General-purpose identifier. * Prefer the more specific ones below when possible */ ret.ModOptions["serverType"] = "ZKLS"; /* Access to commands normally accessible only by the host. * Lua calls prepend the / on their own, but not the autohost, * so /say doesn't need it, but the cheat command does */ ret.ModOptions["cheatCommandPrefix"] = "say !hostsay /"; /* The server is listening for SPRINGIE strings (the game can skip those otherwise). * See https://github.com/ZeroK-RTS/Zero-K-Infrastructure/blob/master/Shared/LobbyClient/DedicatedServer.cs#L317 */ ret.ModOptions["sendSpringieData"] = "1"; // set PW structures if (mode == AutohostMode.Planetwars) { var owner = planet.Faction != null ? planet.Faction.Shortcut : ""; var pwStructures = new LuaTable(); foreach ( var s in planet.PlanetStructures.Where(x => x.StructureType != null && !string.IsNullOrEmpty(x.StructureType.IngameUnitName))) { pwStructures.Add(s.StructureType.IngameUnitName, new LuaTable { { "unitname", s.StructureType.IngameUnitName }, { "owner", s.Account?.Name }, { "canBeEvacuated", s.StructureType.IsIngameEvacuable }, { "canBeDestroyed", s.StructureType.IsIngameDestructible }, { "isInactive", !s.IsActive }, { "name", $"{owner} {s.StructureType.Name} ({(s.Account != null ? s.Account.Name : "unowned")})" }, { "description", s.StructureType.Description } }); } ret.ModOptions["planetwarsStructures"] = pwStructures.ToBase64String(); } return(ret); } catch (Exception ex) { Trace.TraceError(ex.ToString()); throw; } }