protected override bool PerformInit(TasSayEventArgs e, string[] words, out string question, out int winCount) { winCount = 0; question = null; if (spring.IsRunning) { question = "Exit this game?"; int cnt = 0; context = spring.StartContext; foreach (var p in context.Players.Where(x => !x.IsSpectator)) { if (p.IsIngame || tas.MyBattle.Users.ContainsKey(p.Name)) { //Note: "ExistingUsers" is empty if users disconnected from lobby but still ingame. bool afk = tas.ExistingUsers.ContainsKey(p.Name) && tas.ExistingUsers[p.Name].IsAway; if (!afk) cnt++; } } winCount = cnt / 2 + 1; return true; } else { AutoHost.Respond(tas, spring, e, "game not running"); return false; } }
protected override bool PerformInit(TasSayEventArgs e, string[] words, out string question, out int winCount) { if (spring.IsRunning) { context = spring.StartContext; voteStarter = context.Players.FirstOrDefault(x => x.Name == e.UserName && !x.IsSpectator); if (voteStarter != null) { question = string.Format("Resign team {0}?", voteStarter.AllyID + 1); int cnt = 0, total = 0; foreach (var p in context.Players.Where(x => x.AllyID == voteStarter.AllyID && !x.IsSpectator)) { total++; if (p.IsIngame || tas.MyBattle.Users.ContainsKey(p.Name)) { //Note: "ExistingUsers" is empty if users disconnected from lobby but still ingame. bool afk = tas.ExistingUsers.ContainsKey(p.Name) && tas.ExistingUsers[p.Name].IsAway; if (!afk) cnt++; } } winCount = (cnt * 3 / 5) + 1; if (total > 1 && winCount == 1) winCount = 2; // prevents most pathological cases (like a falsely AFK partner in 2v2) return true; } } AutoHost.Respond(tas, spring, e, "You cannot resign now"); question = null; winCount = 0; return false; }
/// <summary> /// Generates script for hosting a game /// </summary> public static string GenerateHostScript(BattleContext startContext, SpringBattleStartSetup startSetup, int loopbackListenPort, string zkSearchTag, string host, int port, string myname = null, string mypassword = null) { 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", zkSearchTag); script.AppendFormat(" Mapname={0};\n", startContext.Map); script.AppendFormat(" StartPosType={0};\n", startContext.IsMission ? 3 : 2); script.AppendFormat(" GameType={0};\n", startContext.Mod); script.AppendFormat(" ModHash=1;\n"); script.AppendFormat(" MapHash=1;\n"); script.AppendFormat(" AutohostPort={0};\n", loopbackListenPort); script.AppendLine(); script.AppendFormat(" HostIP={0};\n", host); script.AppendFormat(" HostPort={0};\n", port); //script.AppendFormat(" SourcePort={0};\n", 8300); script.AppendFormat(" IsHost=1;\n"); script.AppendLine(); if (!string.IsNullOrEmpty(myname)) script.AppendFormat(" MyPlayerName={0};\n", myname); if (!string.IsNullOrEmpty(mypassword) || !string.IsNullOrEmpty(myname)) script.AppendFormat(" MyPasswd={0};\n", mypassword??myname); GeneratePlayerSection(script, startContext, startSetup); return script.ToString(); } finally { Thread.CurrentThread.CurrentCulture = previousCulture; } }
public static PlayerJoinResult AutohostPlayerJoined(BattleContext context, int accountID) { var res = new PlayerJoinResult(); var db = new ZkDataContext(); AutohostMode mode = context.GetMode(); if (mode == AutohostMode.Planetwars) { Planet planet = db.Galaxies.Single(x => x.IsDefault).Planets.SingleOrDefault(x => x.Resource.InternalName == context.Map); if (planet == null) { res.PublicMessage = "Invalid map"; return res; } Account account = db.Accounts.Find(accountID); // accountID is in fact lobbyID if (account != null) { var config = context.GetConfig(); if (account.Level < config.MinLevel) { res.PrivateMessage = string.Format("Sorry, PlanetWars is competive online campaign for experienced players. You need to be at least level {0} to play here. To increase your level, play more games on other hosts or open multiplayer game and play against computer AI bots. You can still spectate this game, however.", config.MinLevel); res.ForceSpec = true; return res; } string owner = ""; if (planet.Account != null) owner = planet.Account.Name; string 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 + ", "; string 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; } } Account acc = db.Accounts.Find(accountID); // accountID is in fact lobbyID if (acc != null) { AutohostConfig config = context.GetConfig(); if (acc.Level < config.MinLevel) { res.PrivateMessage = string.Format("Sorry, you need to be at least level {0} to play here. To increase your level, play more games on other hosts or open multiplayer game and play against computer AI bots. You can still spectate this game, however.", config.MinLevel); res.ForceSpec = true; return res; } else if (acc.Level > config.MaxLevel) { res.PrivateMessage = string.Format("Sorry, your level must be {0} or lower to play here. Pick on someone your own size! You can still spectate this game, however.", config.MaxLevel); res.ForceSpec = true; return res; } // FIXME: use 1v1 Elo for 1v1 if (acc.EffectiveElo < config.MinElo) { res.PrivateMessage = string.Format("Sorry, you need to have an Elo rating of at least {0} to play here. Win games against human opponents to raise your Elo. You can still spectate this game, however.", config.MinElo); res.ForceSpec = true; return res; } else if (acc.EffectiveElo > config.MaxElo) { res.PrivateMessage = string.Format("Sorry, your Elo rating must be {0} or lower to play here. Pick on someone your own size! You can still spectate this game, however.", config.MaxElo); res.ForceSpec = true; return res; } } return null; }
static void VerifySpecCheaters(BattleContext 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 var mode = context.GetMode(); // kick same ip specs for starred and non chickens /* if (mode != AutohostMode.None && mode != AutohostMode.GameChickens) { foreach (var p in context.Players.Where(x => x.IsSpectator)) { var ip = ipByLobbyID[p.LobbyID]; if (context.Players.Any(x => !x.IsSpectator && ipByLobbyID[x.LobbyID] == ip)) Global.Nightwatch.Tas.AdminKickFromLobby(p.Name, "Spectators from same location as players are not allowed here!"); } }*/ foreach (var grp in context.Players.GroupBy(x => ipByLobbyID[x.LobbyID]).Where(x => x.Count() > 1)) res.Message += string.Format("\nThese people are in same location: {0}", string.Join(", ", grp.Select(x => x.Name))); } } catch (Exception ex) { Trace.TraceError("Error checking speccheaters: {0}", ex); } }
static void AddPwPlayer(BattleContext context, string matchUser, BalanceTeamsResult res, int allyID) { PlayerTeam player = context.Players.FirstOrDefault(x => x.Name == matchUser); if (player == null) { player = new PlayerTeam() { Name = matchUser }; User us; if (Global.Nightwatch.Tas.GetExistingUser(matchUser, out us)) player.LobbyID = us.AccountID; else { var db = new ZkDataContext(); var acc = Account.AccountByName(db, matchUser); if (acc != null) player.LobbyID = acc.AccountID; } } res.Players.Add(new PlayerTeam { AllyID = allyID , IsSpectator = false, Name = player.Name, LobbyID = player.LobbyID, TeamID = player.TeamID }); }
BalanceTeamsResult PlanetwarsBalance(BattleContext 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); var info = Global.PlanetWarsMatchMaker.GetBattleInfo(context.AutohostName); if (info == null) { res.Message = "Start battle using matchmaker"; res.CanStart = false; return res; } foreach (string matchUser in info.Attackers) { AddPwPlayer(context, matchUser, res, 0); } foreach (string matchUser in info.Defenders) { AddPwPlayer(context, matchUser, res, 1); } foreach (var p in context.Players.Where(x => !res.Players.Any(y => y.Name == x.Name))) { p.IsSpectator = true; res.Players.Add(p); } var teamNum = 1; foreach (var p in res.Players) { p.TeamID = teamNum++; // normalize teams } // bots game int 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 = 2, BotAI = b.EffectBots, BotName = "Aliens" + cnt++ }); res.Message += string.Format("This planet is infested by aliens, fight for your survival"); return res; } return res; } }
static BalanceTeamsResult PerformBalance(BattleContext context, bool isGameStart, int? allyCount, bool? clanWise, AutohostConfig config, int playerCount) { var res = new BalanceTeamsResult(); var mode = context.GetMode(); using (var db = new ZkDataContext()) { if (!CheckPlayersMinimumConditions(context, db, config, ref res.Message)) { res.CanStart = false; return res; } switch (mode) { case AutohostMode.None: { if (!isGameStart) res = new Balancer().LegacyBalance(allyCount ?? 2, clanWise == true ? BalanceMode.ClanWise : BalanceMode.Normal, context); } break; case AutohostMode.Generic: { if (allyCount == null && res.Bots != null && res.Bots.Any()) { res.Players = context.Players.ToList(); res.Bots = context.Bots.Where(x => x.Owner != context.AutohostName).ToList(); foreach (var p in res.Players) p.AllyID = 0; foreach (var b in res.Bots) b.AllyID = 1; } else { var map = db.Resources.Single(x => x.InternalName == context.Map); res = new Balancer().LegacyBalance(allyCount ?? map.MapFFAMaxTeams ?? 2, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); res.DeleteBots = mode == AutohostMode.Teams; } return res; } case AutohostMode.Teams: { var map = db.Resources.Single(x => x.InternalName == context.Map); res = new Balancer().LegacyBalance(allyCount ?? map.MapFFAMaxTeams ?? 2, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); res.DeleteBots = mode == AutohostMode.Teams; return res; } case AutohostMode.Game1v1: { res = new Balancer().LegacyBalance(allyCount ?? 2, clanWise == false ? BalanceMode.Normal : BalanceMode.ClanWise, context); res.DeleteBots = true; } break; case AutohostMode.GameChickens: { res.Players = context.Players.ToList(); res.Bots = context.Bots.Where(x => x.Owner != context.AutohostName).ToList(); foreach (var p in res.Players) p.AllyID = 0; foreach (var b in res.Bots) b.AllyID = 1; 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."; res.CanStart = false; /*else { res.Bots.Add(new BotTeam() { AllyID = 1, TeamID = 16, BotName = "default_Chicken", BotAI = "Chicken: Normal", }); res.Message = "Adding a normal chickens bot for you"; }*/ } } break; case AutohostMode.GameFFA: { 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; } }
static void GeneratePlayerSection(StringBuilder script, BattleContext startContext, SpringBattleStartSetup setup) { // ordinary battle stuff var userNum = 0; var teamNum = 0; var aiNum = 0; foreach (var u in startContext.Players) { ScriptAddUser(script, userNum, u, teamNum, setup.UserParameters.FirstOrDefault(x => x.LobbyID == u.LobbyID)); if (!u.IsSpectator) { ScriptAddTeam(script, teamNum, userNum, u.AllyID); teamNum++; } foreach (var b in startContext.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++; } // ALLIANCES AND START BOXES var startboxes = new StringBuilder(); startboxes.Append("return { "); script.AppendLine(); for (var allyNumber = 0; allyNumber < Spring.MaxAllies; allyNumber++) { script.AppendFormat("[ALLYTEAM{0}]\n", allyNumber); script.AppendLine("{"); script.AppendLine(" NumAllies=0;"); BattleRect rect; if (startContext.Rectangles!=null && startContext.Rectangles.TryGetValue(allyNumber, out rect)) { double left = 0, top = 0, right = 1, bottom = 1; rect.ToFractions(out left, out top, out right, out bottom); startboxes.AppendFormat(CultureInfo.InvariantCulture, "[{0}] = ", allyNumber); startboxes.Append("{ "); startboxes.AppendFormat(CultureInfo.InvariantCulture, "{0}, {1}, {2}, {3}", left, top, right, bottom); startboxes.Append(" }, "); } script.AppendLine("}"); } startboxes.Append("}"); script.AppendLine(); script.AppendLine(" [MODOPTIONS]"); script.AppendLine(" {"); script.AppendFormat(" startboxes={0};\n", startboxes.ToString()); var options = new Dictionary<string, string>(startContext.ModOptions); // replace/add custom modoptions from startsetup (if they exist) if (setup != null && setup.ModOptions != null) foreach (var entry in setup.ModOptions) options[entry.Key] = entry.Value; // write final options to script foreach (var kvp in options) script.AppendFormat(" {0}={1};\n", kvp.Key, kvp.Value); script.AppendLine(" }"); script.AppendLine("}"); }
static bool CheckPlayersMinimumConditions(BattleContext battleContext, ZkDataContext dataContext, AutohostConfig config, ref string actionsDescription) { var ok = true; foreach ( var p in battleContext.Players.Where(x => !x.IsSpectator) .Select(x => new { player = x, account = dataContext.Accounts.First(y => y.AccountID == x.LobbyID) })) { if ((config.MinLevel != null && p.account.Level < config.MinLevel) || (config.MaxLevel != null && p.account.Level > config.MaxLevel) || (config.MinElo != null && p.account.EffectiveElo < config.MinElo) || (config.MaxElo != null && p.account.EffectiveElo > config.MaxElo)) { SpecPlayerOnCondition(p.player, p.account, string.Format( "Sorry, you cannot play here because of skill limits. You can spectate/observe this game however.")); actionsDescription += string.Format("{0} cannot play here because of skill limits\n", p.account.Name); ok = false; } } return ok; }
public static void SplitAutohost(BattleContext context, bool forceStart = false) { var tas = Global.Nightwatch.Tas; try { //find first one that isnt running and is using same mode (by name) var splitTo = tas.ExistingBattles.Values.FirstOrDefault( x => !x.Founder.IsInGame && x.NonSpectatorCount == 0 && x.Founder.Name != context.AutohostName && !x.IsPassworded && x.Founder.Name.TrimNumbers() == context.AutohostName.TrimNumbers()); if (splitTo != null) { // set same map tas.Say(SayPlace.User, splitTo.Founder.Name, "!map " + context.Map, false); var db = new ZkDataContext(); var ids = context.Players.Where(y => !y.IsSpectator).Select(x => (int?)x.LobbyID).ToList(); var users = db.Accounts.Where(x => ids.Contains(x.AccountID)).ToList(); var toMove = new List<Account>(); var moveCount = Math.Ceiling(users.Count/2.0); /*if (users.Count%2 == 0 && users.Count%4 != 0) { // in case of say 18 people, move 10 nubs out, keep 8 pros moveCount = users.Count/2 + 1; }*/ // split while keeping clan groups together // note disabled splittinhg by clan - use "x.ClanID ?? x.LobbyID" for clan balance foreach (var clanGrp in users.GroupBy(x => x.ClanID ?? x.AccountID).OrderBy(x => x.Average(y => y.EffectiveElo))) { toMove.AddRange(clanGrp); if (toMove.Count >= moveCount) break; } try { foreach (var m in toMove) tas.ForceJoinBattle(m.Name, splitTo.BattleID); Thread.Sleep(5000); tas.Say(SayPlace.User, context.AutohostName, "!lock 180", false); tas.Say(SayPlace.User, splitTo.Founder.Name, "!lock 180", false); if (context.GetMode() == AutohostMode.Planetwars) { tas.Say(SayPlace.User, context.AutohostName, "!map", false); Thread.Sleep(500); tas.Say(SayPlace.User, splitTo.Founder.Name, "!map", false); } else tas.Say(SayPlace.User, splitTo.Founder.Name, "!map " + context.Map, false); if (forceStart) { tas.Say(SayPlace.User, splitTo.Founder.Name, "!balance", false); tas.Say(SayPlace.User, context.AutohostName, "!balance", false); tas.Say(SayPlace.User, splitTo.Founder.Name, "!forcestart", false); tas.Say(SayPlace.User, context.AutohostName, "!forcestart", false); } tas.Say(SayPlace.User, context.AutohostName, "!endvote", false); tas.Say(SayPlace.User, splitTo.Founder.Name, "!endvote", false); tas.Say(SayPlace.User, context.AutohostName, "!start", false); tas.Say(SayPlace.User, splitTo.Founder.Name, "!start", false); } catch (Exception ex) { Trace.TraceError("Error when splitting: {0}", ex); } } } catch (Exception ex) { Trace.TraceError(ex.ToString()); } }
static bool listOnlyThatLevelsModules = false; // may cause bugs public static SpringBattleStartSetup GetSpringBattleStartSetup(BattleContext context) { try { AutohostMode mode = context.GetMode(); var ret = new SpringBattleStartSetup(); if (mode == AutohostMode.Planetwars) { ret.BalanceTeamsResult = Balancer.BalanceTeams(context, true,null, null); context.Players = ret.BalanceTeamsResult.Players; } var commanderTypes = new LuaTable(); var db = new ZkDataContext(); var accountIDsWithExtraComms = new List<int>(); // calculate to whom to send extra comms if (mode == AutohostMode.Planetwars || mode == AutohostMode.Generic || mode == AutohostMode.GameFFA || mode == AutohostMode.Teams) { IOrderedEnumerable<IGrouping<int, PlayerTeam>> groupedByTeam = context.Players.Where(x => !x.IsSpectator).GroupBy(x => x.AllyID).OrderByDescending(x => x.Count()); IGrouping<int, PlayerTeam> biggest = groupedByTeam.FirstOrDefault(); if (biggest != null) { foreach (var other in groupedByTeam.Skip(1)) { int cnt = biggest.Count() - other.Count(); if (cnt > 0) { foreach (Account a in other.Select(x => db.Accounts.First(y => y.AccountID == x.LobbyID)).OrderByDescending(x => x.Elo*x.EloWeight).Take( cnt)) accountIDsWithExtraComms.Add(a.AccountID); } } } } bool is1v1 = context.Players.Where(x => !x.IsSpectator).ToList().Count == 2 && context.Bots.Count == 0; 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.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "attackingFaction", Value = attacker.Shortcut }); if (defender != null) ret.ModOptions.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "defendingFaction", Value = defender.Shortcut }); ret.ModOptions.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "planet", Value = planet.Name }); } foreach (PlayerTeam p in context.Players) { Account user = db.Accounts.Find(p.LobbyID); if (user != null) { var userParams = new List<SpringBattleStartSetup.ScriptKeyValuePair>(); ret.UserParameters.Add(new SpringBattleStartSetup.UserCustomParameters { LobbyID = p.LobbyID, Parameters = userParams }); bool userBanMuted = user.PunishmentsByAccountID.Any(x => !x.IsExpired && x.BanMute); if (userBanMuted) userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "muted", Value = "1" }); userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "faction", Value = user.Faction != null ? user.Faction.Shortcut : "" }); userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "clan", Value = user.Clan != null ? user.Clan.Shortcut : "" }); userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "level", Value = user.Level.ToString() }); double elo = mode == AutohostMode.Planetwars ? user.EffectivePwElo : (is1v1 ? user.Effective1v1Elo : user.EffectiveElo); userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "elo", Value = Math.Round(elo).ToString() }); // elo for ingame is just ordering for auto /take userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "avatar", Value = user.Avatar }); userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "admin", Value = (user.IsZeroKAdmin ? "1" : "0") }); if (!p.IsSpectator) { if (mode == AutohostMode.Planetwars) { bool 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.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "canAttackPwStructures", Value = "1" }); } } var pu = new LuaTable(); bool userUnlocksBanned = user.PunishmentsByAccountID.Any(x => !x.IsExpired && x.BanUnlocks); bool userCommandersBanned = user.PunishmentsByAccountID.Any(x => !x.IsExpired && x.BanCommanders); if (!userUnlocksBanned) { if (mode != AutohostMode.Planetwars || user.Faction == null) foreach (Unlock unlock in user.AccountUnlocks.Select(x => x.Unlock)) pu.Add(unlock.Code); else { foreach (Unlock unlock in user.AccountUnlocks.Select(x => x.Unlock).Union(user.Faction.GetFactionUnlocks().Select(x => x.Unlock)).Where(x => x.UnlockType == UnlockTypes.Unit)) pu.Add(unlock.Code); } } userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "unlocks", Value = pu.ToBase64String() }); if (accountIDsWithExtraComms.Contains(user.AccountID)) userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "extracomm", Value = "1" }); var pc = new LuaTable(); if (!userCommandersBanned) { foreach (Commander c in user.Commanders.Where(x => x.Unlock != null && x.ProfileNumber <= GlobalConst.CommanderProfileCount)) { try { if (string.IsNullOrEmpty(c.Name) || c.Name.Any(x => x == '"') ) { c.Name = c.CommanderID.ToString(); } LuaTable morphTable = new LuaTable(); pc["[\"" + c.Name + "\"]"] = morphTable; // process decoration icons LuaTable decorations = new LuaTable(); foreach (Unlock d in c.CommanderDecorations.Where(x => x.Unlock != null).OrderBy( x => x.SlotID).Select(x => x.Unlock)) { CommanderDecorationIcon 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); LuaTable entry = new LuaTable(); entry.Add("image", iconName); decorations.Add("icon_" + iconPosition.ToLower(), entry); } } else decorations.Add(d.Code); } string prevKey = null; for (int i = 0; i <= GlobalConst.NumCommanderLevels; i++) { string key = string.Format("c{0}_{1}_{2}", user.AccountID, c.ProfileNumber, i); morphTable.Add(key); // TODO: maybe don't specify morph series in player data, only starting unit var comdef = new LuaTable(); commanderTypes[key] = comdef; comdef["chassis"] = c.Unlock.Code + i; var modules = new LuaTable(); comdef["modules"] = modules; comdef["decorations"] = decorations; comdef["name"] = c.Name.Substring(0, Math.Min(25, c.Name.Length)) + " level " + i; //if (i < GlobalConst.NumCommanderLevels) //{ // comdef["next"] = string.Format("c{0}_{1}_{2}", user.AccountID, c.ProfileNumber, i+1); //} //comdef["owner"] = user.Name; if (i > 0) { comdef["cost"] = c.GetTotalMorphLevelCost(i); if (listOnlyThatLevelsModules) { if (prevKey != null) comdef["prev"] = prevKey; prevKey = key; foreach (Unlock m in c.CommanderModules.Where(x => x.CommanderSlot.MorphLevel == i && x.Unlock != null).OrderBy( x => x.Unlock.UnlockType).ThenBy(x => x.SlotID).Select(x => x.Unlock)) modules.Add(m.Code); } else { foreach (Unlock m in c.CommanderModules.Where(x => x.CommanderSlot.MorphLevel <= i && x.Unlock != null).OrderBy( x => x.Unlock.UnlockType).ThenBy(x => x.SlotID).Select(x => x.Unlock)) modules.Add(m.Code); } } } } catch (Exception ex) { Trace.TraceError(ex.ToString()); throw new ApplicationException( string.Format("Error processing commander: {0} - {1} of player {2} - {3}", c.CommanderID, c.Name, user.AccountID, user.Name), ex); } } } else userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "jokecomm", Value = "1" }); userParams.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "commanders", Value = pc.ToBase64String() }); } } } ret.ModOptions.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "commanderTypes", Value = commanderTypes.ToBase64String() }); if (mode == AutohostMode.Planetwars) { string owner = planet.Faction != null ? planet.Faction.Shortcut : ""; var pwStructures = new LuaTable(); foreach (PlanetStructure s in planet.PlanetStructures.Where(x => x.StructureType!= null && !string.IsNullOrEmpty(x.StructureType.IngameUnitName))) { pwStructures.Add("s" + s.StructureTypeID, new LuaTable { { "unitname", s.StructureType.IngameUnitName }, //{ "isDestroyed", s.IsDestroyed ? true : false }, { "name", string.Format("{0} {1} ({2})", owner, s.StructureType.Name, s.Account!= null ? s.Account.Name:"unowned") }, { "description", s.StructureType.Description } }); } ret.ModOptions.Add(new SpringBattleStartSetup.ScriptKeyValuePair { Key = "planetwarsStructures", Value = pwStructures.ToBase64String() }); } return ret; } catch (Exception ex) { Trace.TraceError(ex.ToString()); throw; } }
public string HostGame(BattleContext context, string host, int port, string myName = null, string myPassword = 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)); wasKilled = false; string script = null; if (!IsRunning) { gameEndedOk = false; IsBattleOver = false; //lobbyUserName = client.UserName; //lobbyPassword = client.UserPassword; battleResult = new BattleResult(); talker = new Talker(); talker.SpringEvent += talker_SpringEvent; isHosting = true; if (isHosting) scriptPath = Utils.MakePath(paths.WritableDirectory, "script_" + myName + ".txt").Replace('\\', '/'); else scriptPath = Utils.MakePath(paths.WritableDirectory, "script.txt").Replace('\\', '/'); statsPlayers.Clear(); statsData.Clear(); battleGuid = Guid.NewGuid(); var service = GlobalConst.GetSpringieService(); SpringBattleStartSetup startSetup = null; if (isHosting && GlobalConst.IsZkMod(context.Mod)) { try { StartContext = context; 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 = ScriptGenerator.GenerateHostScript(StartContext, startSetup, talker.LoopbackPort, battleGuid.ToString(), host,port, myName, myPassword); statsPlayers = StartContext.Players.ToDictionary(x => x.Name, x => new BattlePlayerResult { LobbyID = x.LobbyID, AllyNumber = x.AllyID, CommanderType = null, // todo commandertype IsSpectator = x.IsSpectator, IsVictoryTeam = false, }); } if (isHosting) timer.Start(); StartSpring(script); return script; } else Trace.TraceError("Spring already running"); return null; }
public BattleContext GetContext() { var ret = new BattleContext(); ret.AutohostName = Founder.Name; ret.Map = MapName; ret.Mod = ModName; ret.Title = Title; ret.EngineVersion = EngineVersion; ret.IsMission = IsMission; ret.Rectangles = new Dictionary<int, BattleRect>(Rectangles); ret.Players = Users.Values.Where(x => x.SyncStatus != SyncStatuses.Unknown).Select(x => x.ToPlayerTeam()).ToList(); ret.Bots = Bots.Values.Select(x => x.ToBotTeam()).ToList(); ret.ModOptions = ModOptions; return ret; }
public static string SubmitSpringBattleResult(BattleContext context, string password, BattleResult result, List<BattlePlayerResult> players, List<string> extraData) { try { Account acc = AuthServiceClient.VerifyAccountPlain(context.AutohostName, password); if (acc == null) throw new Exception("Account name or password not valid"); AutohostMode mode = context.GetMode(); var db = new ZkDataContext(); if (extraData == null) extraData = new List<string>(); var sb = new SpringBattle { HostAccountID = acc.AccountID, Duration = result.Duration, EngineGameID = result.EngineBattleID, MapResourceID = db.Resources.Single(x => x.InternalName == result.Map).ResourceID, ModResourceID = db.Resources.Single(x => x.InternalName == result.Mod).ResourceID, HasBots = result.IsBots, IsMission = result.IsMission, PlayerCount = players.Count(x => !x.IsSpectator), StartTime = result.StartTime, Title = result.Title, ReplayFileName = result.ReplayName, EngineVersion = result.EngineVersion, }; db.SpringBattles.InsertOnSubmit(sb); foreach (BattlePlayerResult p in players) { sb.SpringBattlePlayers.Add(new SpringBattlePlayer { AccountID = db.Accounts.First(x => x.AccountID == p.LobbyID).AccountID, AllyNumber = p.AllyNumber, CommanderType = p.CommanderType, IsInVictoryTeam = p.IsVictoryTeam, IsSpectator = p.IsSpectator, LoseTime = p.LoseTime }); } db.SubmitChanges(); // awards foreach (string line in extraData.Where(x => x.StartsWith("award"))) { string[] partsSpace = line.Substring(6).Split(new[] { ' ' }, 3); string name = partsSpace[0]; string awardType = partsSpace[1]; string awardText = partsSpace[2]; SpringBattlePlayer player = sb.SpringBattlePlayers.FirstOrDefault(x => x.Account.Name == name); if (player != null) { db.AccountBattleAwards.InsertOnSubmit(new AccountBattleAward { AccountID = player.AccountID, SpringBattleID = sb.SpringBattleID, AwardKey = awardType, AwardDescription = awardText }); } } var text = new StringBuilder(); bool isPlanetwars = false; if (mode == AutohostMode.Planetwars && sb.SpringBattlePlayers.Count(x => !x.IsSpectator) >= 2 && sb.Duration >= GlobalConst.MinDurationForPlanetwars) { // test that factions are not intermingled (each faction only has one ally number) - if they are it wasnt actually PW balanced if ( sb.SpringBattlePlayers.Where(x => !x.IsSpectator && x.Account.Faction != null) .GroupBy(x => x.Account.Faction) .All(grp => grp.Select(x => x.AllyNumber).Distinct().Count() < 2)) { isPlanetwars = true; 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; text.AppendLine("ERROR: Invalid winner"); } } PlanetWarsTurnHandler.EndTurn(result.Map, extraData, 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()); Global.PlanetWarsMatchMaker.RemoveFromRunningBattles(context.AutohostName); } else { text.AppendLine("Battle wasn't PlanetWars balanced, it counts as a normal team game only"); } } bool noElo = (extraData.FirstOrDefault(x => x.StartsWith("noElo", true, System.Globalization.CultureInfo.CurrentCulture)) != null); try { db.SubmitChanges(); } catch (System.Data.Linq.DuplicateKeyException ex) { Trace.TraceError(ex.ToString()); } Dictionary<int, int> orgLevels = sb.SpringBattlePlayers.Select(x => x.Account).ToDictionary(x => x.AccountID, x => x.Level); sb.CalculateAllElo(noElo, isPlanetwars); foreach (var u in sb.SpringBattlePlayers.Where(x => !x.IsSpectator)) u.Account.CheckLevelUp(); db.SubmitAndMergeChanges(); try { foreach (Account a in sb.SpringBattlePlayers.Where(x => !x.IsSpectator).Select(x => x.Account)) Global.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); Global.Server.GhostPm(account.Name, message); } catch (Exception ex) { Trace.TraceError("Error sending level up lobby message: {0}", ex); } } } text.AppendLine(string.Format("BATTLE DETAILS AND REPLAY ----> {1}/Battles/Detail/{0} <-----", sb.SpringBattleID, GlobalConst.BaseSiteUrl)); /* // create debriefing room, join players there and output message string channelName = "B" + sb.SpringBattleID; var joinplayers = new List<string>(); joinplayers.AddRange(context.Players.Select(x => x.Name)); // add those who were there at start joinplayers.AddRange(sb.SpringBattlePlayers.Select(x => x.Account.Name)); // add those who played Battle bat = Global.Server.Battles.Values.FirstOrDefault(x => x.Founder.Name == context.AutohostName); // add those in lobby atm var conf = context.GetConfig(); if (bat != null && (conf == null || conf.MinToJuggle == null)) // if not qm room do not join those who are in battle { List<string> inbatPlayers = bat.Users.Keys.ToList(); joinplayers.RemoveAll(x => inbatPlayers.Contains(x)); } foreach (string jp in joinplayers.Distinct().Where(x => x != context.AutohostName)) tas.ForceJoinChannel(jp, channelName); tas.JoinChannel(channelName); // join nightwatch and say it tas.Say(SayPlace.Channel, channelName, text.ToString(), true); tas.LeaveChannel(channelName);*/ //text.Append(string.Format("Debriefing in #{0} - zk://chat/channel/{0} ", channelName)); return text.ToString(); } catch (Exception ex) { Trace.TraceError(ex.ToString()); return ex.ToString(); } }
public static RecommendedMapResult GetRecommendedMap(BattleContext context, bool pickNew) { var mode = context.GetMode(); var config = context.GetConfig(); var res = new RecommendedMapResult(); using (var db = new ZkDataContext()) { if (mode == AutohostMode.Planetwars) { var info = Global.PlanetWarsMatchMaker.GetBattleInfo(context.AutohostName); if (info != null) { res.MapName = info.Map; res.Message = String.Format("Welcome to planet {0} {2}/PlanetWars/Planet/{1} attacked", info.Name, info.PlanetID, GlobalConst.BaseSiteUrl); } else res.MapName = context.Map; } else { if (!pickNew) { // autohost is not managed or has valid featured map - check disabled res.MapName = context.Map; return res; } List<Resource> list = null; var players = context.Players.Count(x => !x.IsSpectator); if (config != null && config.SplitBiggerThan != null && players > config.SplitBiggerThan) players = players/2; // expect the split switch (mode) { case AutohostMode.Generic: case AutohostMode.Teams: case AutohostMode.None: var ret = db.Resources.Where(x => x.TypeID == ResourceType.Map && x.FeaturedOrder != null && 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.FeaturedOrder != null && x.MapIs1v1 == true && x.MapIsSpecial != true).ToList(); break; case AutohostMode.GameChickens: ret = db.Resources.Where(x => x.TypeID == ResourceType.Map && x.FeaturedOrder != null && 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.FeaturedOrder != null && x.MapIsFfa == true && x.MapFFAMaxTeams == players).ToList(); if (!list.Any()) list = db.Resources.Where(x => x.TypeID == ResourceType.Map && x.FeaturedOrder != null && x.MapIsFfa == true && (players%x.MapFFAMaxTeams == 0)).ToList(); if (!list.Any()) list = db.Resources.Where(x => x.TypeID == ResourceType.Map && x.FeaturedOrder != null && x.MapIsFfa == true).ToList(); break; } if (list != null) { var r = new Random(); if (list.Count > 0) res.MapName = list[r.Next(list.Count)].InternalName; } } } return res; }
/// <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; }
BalanceTeamsResult LegacyBalance(int teamCount, BalanceMode mode, BattleContext b, params List<Account>[] unmovablePlayers) { var ret = new BalanceTeamsResult(); 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(unmovablePlayers[i].ToArray()) { CanBeMoved = false }); accs.RemoveAll(x => unmovables.Any(y => y.AccountID == x.AccountID)); } } balanceItems = new List<BalanceItem>(); if (mode == BalanceMode.ClanWise) { var clanGroups = accs.GroupBy(x => 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(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(x.ToArray()))); } if (mode == BalanceMode.Normal) { balanceItems.Clear(); balanceItems.AddRange(accs.Select(x => new BalanceItem(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.AvgElo); var maxElo = bestTeams.Max(x => x.AvgElo); 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; } else { 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)); if (allyNum > 0) text += string.Format("={0}%)", Utils.GetWinChancePercent(lastTeamElo - team.AvgElo)); lastTeamElo = team.AvgElo; 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; }
public BattleContext GetContext() { var ret = new BattleContext(); ret.AutohostName = Founder.Name; ret.Map = MapName; ret.Mod = ModName; ret.Players = Users.Where(x=>x.SyncStatus != SyncStatuses.Unknown).Select(x => new PlayerTeam() { AllyID = x.AllyNumber, Name = x.Name, LobbyID = x.LobbyUser.LobbyID, TeamID = x.TeamNumber, IsSpectator = x.IsSpectator }).ToList(); ret.Bots = Bots.Select(x => new BotTeam() { BotName = x.Name, AllyID = x.AllyNumber, TeamID = x.TeamNumber, Owner = x.owner, BotAI = x.aiLib }).ToList(); return ret; }
public static BalanceTeamsResult BalanceTeams(BattleContext context, bool isGameStart, int? allyCount, bool? clanWise) { var config = context.GetConfig(); var playerCount = context.Players.Count(x => !x.IsSpectator); // game is managed, is bigger than split limit and wants to start -> split into two and issue starts if (isGameStart && context.GetMode() != AutohostMode.None && config.SplitBiggerThan != null && config.SplitBiggerThan < playerCount) { new Thread(() => { try { SplitAutohost(context, true); } catch (Exception ex) { Trace.TraceError("Error when splitting game:{0}", ex); } }).Start(); return new BalanceTeamsResult { CanStart = false, Message = string.Format("Game too big - splitting into two - max players is {0} here", config.SplitBiggerThan) }; } if (clanWise == null && (config.AutohostMode == AutohostMode.Generic || config.AutohostMode == AutohostMode.Teams)) clanWise = true; var res = PerformBalance(context, isGameStart, allyCount, clanWise, config, playerCount); if (context.GetMode() != AutohostMode.Planetwars) // planetwars skip other checks { if (isGameStart) { if (playerCount < (config.MinToStart ?? 0)) { res.Message = string.Format("This host needs at least {0} people to start", config.MinToStart); res.CanStart = false; return res; } if (playerCount > (config.MaxToStart ?? 99)) { res.Message = string.Format("This host can only start with at most {0} players", config.MaxToStart); res.CanStart = false; return res; } // dont allow to start alone if (playerCount <= 1) { if (res.DeleteBots) return new BalanceTeamsResult { CanStart = false, Message = "You cannot play alone on this host, wait for players or join another game room and play with bots." }; if (!context.Bots.Any()) return new BalanceTeamsResult { CanStart = false, Message = "Cannot play alone, you can add bots using button on bottom left." }; } } } if (isGameStart) VerifySpecCheaters(context, res); return res; }