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 }); }
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); } }
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); } }
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); } }
//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> /// 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); } }
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); } }
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); }
/// <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); } }