using System.Collections.Generic; using System.Linq; using CompanionServer; using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Oxide.Core; namespace Oxide.Plugins { [Info("Team Fix", "Khan", "1.0.6")] [Description("Fixes FacePunche Team Logic when incorrect shutdown/crashes occurs")] public class TeamFix : RustPlugin { #region Fields private bool _teamsReduced; private Dictionary _teams = new Dictionary(); private Configuration _config; private StoredData _storedData; #endregion #region Config protected override void LoadDefaultConfig() => _config = new Configuration(); protected override void LoadConfig() { base.LoadConfig(); try { _config = Config.ReadObject(); if (_config == null) { throw new JsonException(); } if (MaybeUpdateConfig(_config)) { PrintWarning("Configuration appears to be outdated; updating and saving"); SaveConfig(); } } catch { PrintWarning($"Configuration file {Name}.json is invalid; using defaults"); LoadDefaultConfig(); } } protected override void SaveConfig() { PrintWarning($"Configuration changes saved to {Name}.json"); Config.WriteObject(_config, true); } private class Configuration : SerializableConfiguration { public Options Option = new Options(); } public class Options { public int MemberLimit = RelationshipManager.maxTeamSize; } #endregion #region Oxide private void Init() { _storedData = StoredData.LoadData(); int counter = 0; List cleanData = new List(); foreach (var team in _storedData.Teams) { if (team.Value.Members.Contains(team.Value.LeaderID)) continue; counter++; cleanData.Add(team.Key); } if (counter > 0) { Puts($"Fixed {counter} team data entries"); foreach (var team in cleanData) { _storedData.Teams.Remove(team); //_storedData.SaveData(); } cleanData.Clear(); } int limit = _config.Option.MemberLimit; int members = 0; int teams = 0; if (limit != 8) { foreach (var team in _storedData.Teams) { if (team.Value.Members.Count > limit) { teams++; while (team.Value.Members.Count > limit) { var remove = team.Value.Members.Last(); if (!remove.Equals(team.Value.LeaderID)) { team.Value.Members.Remove(remove); members++; } } } } } if (members > 0 || teams > 0) Puts($"TeamFix has updated {teams} teams & removed {members} members that had been the last ones added"); } private void OnServerInitialized() { if (_config.Option.MemberLimit > 0) RelationshipManager.maxTeamSize = _config.Option.MemberLimit; if (RelationshipManager.maxTeamSize < 8 && RelationshipManager.maxTeamSize != 0) { Puts($"Teams have been reduced, team size is now {RelationshipManager.maxTeamSize}"); _teamsReduced = true; } if (_config.Option.MemberLimit <= 0) { timer.Once(5, Force); return; } if (_storedData.Teams.IsEmpty()) { foreach (var playerTeam in RelationshipManager.ServerInstance.teams.Values) { if (playerTeam.teamID == 0UL || !playerTeam.teamLeader.IsSteamId() || playerTeam.members.IsNullOrEmpty()) continue; TeamData teamData = new TeamData(); teamData.SetLeader(playerTeam.teamLeader); teamData.Members = playerTeam.members; _teams[playerTeam.teamID] = teamData; } _storedData.SaveData(); } else { if (_storedData.Teams.Count <= 0) return; Unsubscribe("OnTeamCreated"); foreach (TeamData teamData in _storedData.Teams.Values.ToArray()) { if (RelationshipManager.ServerInstance.teams.Values.Contains(teamData.Team)) { RelationshipManager.PlayerTeam clearteam = RelationshipManager.ServerInstance.FindTeam(teamData.Team.teamID); if (clearteam != null) { clearteam.members.Remove(clearteam.teamLeader); RelationshipManager.ServerInstance.playerToTeam.Remove(clearteam.teamLeader); BasePlayer byId = RelationshipManager.FindByID(clearteam.teamLeader); if (byId != null) { byId.ClearTeam(); byId.BroadcastAppTeamRemoval(); } clearteam.Disband(); clearteam.MarkDirty(); } } RelationshipManager.PlayerTeam playerTeam = teamData.Restore(); _teams[playerTeam.teamID] = teamData; } _storedData.Teams = _teams; _storedData.SaveData(); Subscribe("OnTeamCreated"); } } private void Unload() { if (_config.Option.MemberLimit <= 0) { Puts("Teams are now disabled until next restart, TeamFix will now unload."); return; } _storedData.Teams = _teams; _storedData.SaveData(); foreach (var player in BasePlayer.activePlayerList) { if (player != null && player.currentTeam != 0UL && player.Team != null) { player.Team.members?.Remove(player.userID); RelationshipManager.ServerInstance.playerToTeam?.Remove(player.userID); player.currentTeam = 0UL; player.ClientRPCPlayerAndSpectators( null, player, "CLIENT_ClearTeam"); player.SendNetworkUpdate(); player.BroadcastAppTeamRemoval(); player.Team?.Disband(); player.Team?.MarkDirty(); } } foreach (var player in BasePlayer.sleepingPlayerList) { if (player != null && player.userID.IsSteamId() && player.currentTeam != 0UL && player.Team != null) { player.Team.members?.Remove(player.userID); RelationshipManager.ServerInstance.playerToTeam?.Remove(player.userID); player.currentTeam = 0UL; player.BroadcastAppTeamRemoval(); } } RelationshipManager.ServerInstance.playerToTeam?.Clear(); RelationshipManager.ServerInstance.teams?.Clear(); RelationshipManager.ServerInstance.lastTeamIndex = 1; } private void OnServerSave() { _storedData.Teams = _teams; _storedData.SaveData(); } private void OnNewSave(string filename) { _storedData.Teams.Clear(); _storedData.SaveData(); } private void OnTeamCreated(BasePlayer player, RelationshipManager.PlayerTeam playerTeam) { TeamData teamData = FindTeamByID(playerTeam.teamID); teamData.SetLeader(player.userID); teamData.AddMember(player.userID); _storedData.SaveData(); } private void OnTeamAcceptInvite(RelationshipManager.PlayerTeam team, BasePlayer player) { FindTeamByID(team.teamID)?.AddMember(player.userID); } private void OnTeamDisbanded(RelationshipManager.PlayerTeam playerTeam) { _teams.Remove(playerTeam.teamID); } private void OnTeamPromote(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) { FindTeamByID(playerTeam.teamID)?.SetLeader(player.userID); } private void OnTeamKick(RelationshipManager.PlayerTeam playerTeam, BasePlayer player, ulong userID) { FindTeamByID(playerTeam.teamID)?.DelMember(userID); FindByID(userID)?.ClearTeam(); _storedData.SaveData(); } private void OnTeamLeave(RelationshipManager.PlayerTeam playerTeam, BasePlayer player) { if (player == null || _teams.Values.IsNullOrEmpty()) return; _teams[playerTeam.teamID].Members.Remove(player.userID); if (_teams[playerTeam.teamID].LeaderID == player.userID && _teams[playerTeam.teamID].Members.Count > 1) _teams[playerTeam.teamID].LeaderID = _teams[playerTeam.teamID].Members.FirstOrDefault(); _storedData.SaveData(); } private void OnPlayerConnected(BasePlayer player) { if (player == null || RelationshipManager.maxTeamSize <= 0 || _teams.Values.IsNullOrEmpty()) return; if (player.currentTeam == 0UL) { var teamData = _teams.Values.FirstOrDefault(x => x != null && x.Members.Contains(player.userID)); if (teamData != null) { teamData.Team.AddPlayer(player); foreach (var member in teamData.Team.members) { if (player.Team.members.Contains(member)) continue; player.Team.members.Add(member); } player.SendNetworkUpdate(); } } } private void OnPlayerDisconnected(BasePlayer player, string reason) { if (player == null) return; _storedData.SaveData(); } private void OnPlayerKicked(BasePlayer player, string reason) { if (player == null) return; _storedData.SaveData(); } #endregion #region Storage private class StoredData { public Dictionary Teams = new Dictionary(); public static StoredData LoadData() => Interface.Oxide.DataFileSystem.ReadObject("TeamFix"); public void SaveData() => Interface.Oxide.DataFileSystem.WriteObject("TeamFix", this); } private class TeamData { public ulong LeaderID; public List Members = new List(); [JsonIgnore] public RelationshipManager.PlayerTeam Team; public void SetLeader(ulong userID) => LeaderID = userID; public void AddMember(ulong userID) { if (Members.Contains(userID)) return; Members.Add(userID); } public void DelMember(ulong userID) => Members.Remove(userID); public RelationshipManager.PlayerTeam Restore() { RelationshipManager.PlayerTeam playerTeam = Team ?? RelationshipManager.ServerInstance.CreateTeam(); playerTeam.teamLeader = LeaderID; foreach (ulong member in Members) { BasePlayer player = BasePlayer.FindAwakeOrSleeping(member.ToString()); if (player == null || playerTeam.members.Contains(player.userID)) continue; playerTeam.AddPlayer(player); } Team = playerTeam; return playerTeam; } } #endregion #region UpdateChecker internal class SerializableConfiguration { public string ToJson() => JsonConvert.SerializeObject(this); public Dictionary ToDictionary() => JsonHelper.Deserialize(ToJson()) as Dictionary; } private static class JsonHelper { public static object Deserialize(string json) => ToObject(JToken.Parse(json)); private static object ToObject(JToken token) { switch (token.Type) { case JTokenType.Object: return token.Children().ToDictionary(prop => prop.Name, prop => ToObject(prop.Value)); case JTokenType.Array: return token.Select(ToObject).ToList(); default: return ((JValue)token).Value; } } } private bool MaybeUpdateConfig(SerializableConfiguration config) { var currentWithDefaults = config.ToDictionary(); var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value); return MaybeUpdateConfigDict(currentWithDefaults, currentRaw); } private bool MaybeUpdateConfigDict(Dictionary currentWithDefaults, Dictionary currentRaw) { bool changed = false; foreach (var key in currentWithDefaults.Keys) { object currentRawValue; if (currentRaw.TryGetValue(key, out currentRawValue)) { var defaultDictValue = currentWithDefaults[key] as Dictionary; var currentDictValue = currentRawValue as Dictionary; if (defaultDictValue != null) { if (currentDictValue == null) { currentRaw[key] = currentWithDefaults[key]; changed = true; } else if (MaybeUpdateConfigDict(defaultDictValue, currentDictValue)) changed = true; } } else { currentRaw[key] = currentWithDefaults[key]; changed = true; } } return changed; } #endregion #region Helpers TeamData FindTeamByID(ulong teamID) { TeamData teamData; if (!_teams.TryGetValue(teamID, out teamData)) _teams[teamID] = teamData = new TeamData(); return teamData; } private static BasePlayer FindByID(ulong userID) { using (TimeWarning.New("BasePlayer.FindByID")) { foreach (BasePlayer activePlayer in BasePlayer.activePlayerList) { if ((long) activePlayer.userID == (long) userID) return activePlayer; } foreach (BasePlayer sleepingPlayer in BasePlayer.sleepingPlayerList) { if ((long) sleepingPlayer.userID == (long) userID) return sleepingPlayer; } return null; } } private void Force() { Server.Command("o.unload TeamFix"); } #endregion } }