using System; using System.Collections.Generic; using System.Linq; using ACE.Entity; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Entity.Models; using ACE.Server.Entity; using ACE.Server.Managers; using ACE.Server.Network.GameEvent.Events; using ACE.Server.Network.GameMessages.Messages; namespace ACE.Server.WorldObjects { public class Allegiance: WorldObject { /// /// The top of the AllegianceNode tree /// public AllegianceNode Monarch; /// /// The total # of players in the Allegiance /// public int TotalMembers => Members.Count; /// /// A lookup table of Players => AllegianceNodes /// public Dictionary Members; public Dictionary Officers; /// /// Approved vassals for adding to locked allegiances /// // TODO now that the new biota model uses a dictionary for this, see if we can remove this duplicate dictionary public Dictionary ApprovedVassals => Biota.PropertiesAllegiance.GetApprovedVassals(BiotaDatabaseLock); /// /// Handles booting players from allegiance chat /// public Dictionary ChatFilters { get; set; } /// /// A list of players who are banned from joining. /// //public HashSet BanList { get; set; } // TODO now that the new biota model uses a dictionary for this, see if we can remove this duplicate dictionary public Dictionary BanList => Biota.PropertiesAllegiance.GetBanList(BiotaDatabaseLock); /// /// Returns the list of allegiance members who are currently online /// public List OnlinePlayers { get { var onlinePlayers = new List(); foreach (var member in Members) { var player = PlayerManager.GetOnlinePlayer(member.Key); if (player != null) onlinePlayers.Add(player); } return onlinePlayers; } } /// /// A new biota be created taking all of its values from weenie. /// public Allegiance(Weenie weenie, ObjectGuid guid) : base(weenie, guid) { //Console.WriteLine($"Allegiance({weenie.ClassId}, {guid}): weenie constructor"); InitializePropertyDictionaries(); } /// /// Restore a WorldObject from the database. /// public Allegiance(Biota biota) : base(biota) { //Console.WriteLine($"Allegiance({biota.Id:X8}): biota constructor"); if (MonarchId == null) { Console.WriteLine($"Allegiance({biota.Id:X8}): constructor called with no monarch"); return; } InitializePropertyDictionaries(); Init(new ObjectGuid(MonarchId.Value)); } public Allegiance(ObjectGuid monarch) { //Console.WriteLine($"Allegiance({monarch}): monarch constructor"); Init(monarch); } private void InitializePropertyDictionaries() { if (Biota.PropertiesAllegiance == null) Biota.PropertiesAllegiance = new Dictionary(); } /// /// Constructs a new Allegiance from a Monarch /// public void Init(ObjectGuid monarch) { Monarch = new AllegianceNode(monarch, this); // find all players with this monarch var members = AllegianceManager.FindAllPlayers(monarch); var patronVassals = BuildPatronVassals(members); Monarch.BuildChain(this, members, patronVassals); BuildMembers(Monarch); //Console.WriteLine("TotalMembers: " + TotalMembers); BuildOfficers(); ChatFilters = new Dictionary(); } /// /// Build a mapping of patron guids => vassal guids /// public Dictionary> BuildPatronVassals(List members) { var patronVassals = new Dictionary>(); foreach (var member in members) { var patronId = member.PatronId; if (patronId == null) continue; if (!patronVassals.TryGetValue(patronId.Value, out var vassals)) { vassals = new List(); patronVassals.Add(patronId.Value, vassals); } vassals.Add(member); } return patronVassals; } /// /// Builds the lookup table of Players => AllegianceNodes /// public void BuildMembers(AllegianceNode node) { if (Monarch.PlayerGuid.Equals(node.PlayerGuid)) Members = new Dictionary(); Members.Add(node.PlayerGuid, node); foreach (var vassal in node.Vassals.Values) BuildMembers(vassal); } public void BuildOfficers() { Officers = Members.Where(i => i.Value.Player.AllegianceOfficerRank != null).ToDictionary(i => i.Key, i => i.Value); } /// /// An allegiance is defined by its monarch /// public override bool Equals(object obj) { if (obj is Allegiance allegiance) return Monarch.PlayerGuid.Full == allegiance.Monarch.PlayerGuid.Full; return false; } public override int GetHashCode() { return Monarch.PlayerGuid.Full.GetHashCode(); } public string AllegianceName { get => GetProperty(PropertyString.AllegianceName); set { if (value == null) RemoveProperty(PropertyString.AllegianceName); else SetProperty(PropertyString.AllegianceName, value); } } public string AllegianceMotd { get => GetProperty(PropertyString.AllegianceMotd); set { if (value == null) RemoveProperty(PropertyString.AllegianceMotd); else SetProperty(PropertyString.AllegianceMotd, value); } } public string AllegianceMotdSetBy { get => GetProperty(PropertyString.AllegianceMotdSetBy); set { if (value == null) RemoveProperty(PropertyString.AllegianceMotdSetBy); else SetProperty(PropertyString.AllegianceMotdSetBy, value); } } public string AllegianceSpeakerTitle { get => GetProperty(PropertyString.AllegianceSpeakerTitle); set { if (value == null) RemoveProperty(PropertyString.AllegianceSpeakerTitle); else SetProperty(PropertyString.AllegianceSpeakerTitle, value); } } public string AllegianceSeneschalTitle { get => GetProperty(PropertyString.AllegianceSeneschalTitle); set { if (value == null) RemoveProperty(PropertyString.AllegianceSeneschalTitle); else SetProperty(PropertyString.AllegianceSeneschalTitle, value); } } public string AllegianceCastellanTitle { get => GetProperty(PropertyString.AllegianceCastellanTitle); set { if (value == null) RemoveProperty(PropertyString.AllegianceCastellanTitle); else SetProperty(PropertyString.AllegianceCastellanTitle, value); } } /// /// Returns TRUE if playerGuid is a member /// public bool IsMember(ObjectGuid playerGuid) { return Members.ContainsKey(playerGuid); } /// /// Returns TRUE if playerGuid is an officer /// public bool IsOfficer(ObjectGuid playerGuid) { return Officers.ContainsKey(playerGuid); } /// /// Returns TRUE if playerGuid is an officer of minimum rank /// public bool IsOfficerRank(ObjectGuid playerGuid, int officerRank) { Officers.TryGetValue(playerGuid, out var node); return node != null && node.Player.AllegianceOfficerRank >= officerRank; } public bool IsSpeaker(ObjectGuid playerGuid) { return IsOfficerRank(playerGuid, 1); } public bool IsSeneschal(ObjectGuid playerGuid) { return IsOfficerRank(playerGuid, 2); } public bool IsCastellan(ObjectGuid playerGuid) { return IsOfficerRank(playerGuid, 3); } public string GetOfficerTitle(AllegianceOfficerLevel officerRank) { switch (officerRank) { case AllegianceOfficerLevel.Speaker: return string.IsNullOrEmpty(AllegianceSpeakerTitle) ? "Speaker" : AllegianceSpeakerTitle; case AllegianceOfficerLevel.Seneschal: return string.IsNullOrEmpty(AllegianceSeneschalTitle) ? "Seneschal" : AllegianceSeneschalTitle; case AllegianceOfficerLevel.Castellan: return string.IsNullOrEmpty(AllegianceCastellanTitle) ? "Castellan" : AllegianceCastellanTitle; default: return ""; } } public bool HasCustomTitles => AllegianceSpeakerTitle != null || AllegianceSeneschalTitle != null || AllegianceCastellanTitle != null; public House GetHouse() { if (Monarch.Player.HouseInstance == null) return null; // is landblock loaded? var houseGuid = Monarch.Player.HouseInstance.Value; var landblock = (ushort)((houseGuid >> 12) & 0xFFFF); var landblockId = new LandblockId((uint)(landblock << 16 | 0xFFFF)); var isLoaded = LandblockManager.IsLoaded(landblockId); if (isLoaded) { var loaded = LandblockManager.GetLandblock(landblockId, false); return loaded.GetObject(new ObjectGuid(houseGuid)) as House; } // load an offline copy return House.Load(Monarch.Player.HouseInstance.Value); } /// /// Returns TRUE if input player guid has an active chat filter /// public bool IsFiltered(ObjectGuid playerGuid) { if (!ChatFilters.TryGetValue(playerGuid, out var filter)) return false; if (filter > DateTime.UtcNow) return true; // filter has expired ChatFilters.Remove(playerGuid); return false; } /// /// Updates any dynamic properties if they have changed for allegiance members /// public void UpdateProperties() { foreach (var member in Members) { var player = PlayerManager.FindByGuid(member.Key); var onlinePlayer = PlayerManager.GetOnlinePlayer(member.Key); if (player == null) continue; var updated = false; // if changed, update monarch id if ((player.MonarchId ?? 0) != member.Value.Allegiance.MonarchId) { player.UpdateProperty(PropertyInstanceId.Monarch, member.Value.Allegiance.MonarchId, true); updated = true; } // if changed, update rank if ((player.AllegianceRank ?? 0) != member.Value.Rank) { player.AllegianceRank = (int)member.Value.Rank; if (onlinePlayer != null) onlinePlayer.Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(onlinePlayer, PropertyInt.AllegianceRank, player.AllegianceRank.Value)); updated = true; } if (updated) player.SaveBiotaToDatabase(); if (onlinePlayer != null) onlinePlayer.Session.Network.EnqueueSend(new GameEventAllegianceUpdate(onlinePlayer.Session, this, member.Value), new GameEventAllegianceAllegianceUpdateDone(onlinePlayer.Session)); } } public void ShowMembers() { Console.WriteLine($"Total members: {Members.Count}"); foreach (var member in Members) { var player = PlayerManager.FindByGuid(member.Key, out bool isOnline); var prefix = isOnline ? "* " : ""; Console.WriteLine($"{prefix}{player.Name}"); } } public void ShowInfo() { Monarch.ShowInfo(); } public void AddBan(uint playerGuid) { var entity = Biota.PropertiesAllegiance.GetFirstOrDefaultByCharacterId(playerGuid, BiotaDatabaseLock); if (entity == null) Biota.PropertiesAllegiance.AddOrUpdateAllegiance(playerGuid, true, false, BiotaDatabaseLock); else Biota.PropertiesAllegiance.AddOrUpdateAllegiance(playerGuid, true, entity.ApprovedVassal, BiotaDatabaseLock); // ChangesDetected = true doesn't work here, // since the Allegiance WO is not associated with a landblock SaveBiotaToDatabase(); } public bool RemoveBan(uint playerGuid) { var entity = Biota.PropertiesAllegiance.GetFirstOrDefaultByCharacterId(playerGuid, BiotaDatabaseLock); if (entity == null) return false; if (entity.ApprovedVassal) { Biota.PropertiesAllegiance.AddOrUpdateAllegiance(playerGuid, false, true, BiotaDatabaseLock); SaveBiotaToDatabase(); return true; } var removed = Biota.PropertiesAllegiance.TryRemoveAllegiance(playerGuid, BiotaDatabaseLock); if (removed) SaveBiotaToDatabase(); return removed; } public bool IsBanned(uint playerGuid) { return BanList.ContainsKey(playerGuid); } public void AddApprovedVassal(uint playerGuid) { var entity = Biota.PropertiesAllegiance.GetFirstOrDefaultByCharacterId(playerGuid, BiotaDatabaseLock); if (entity == null) Biota.PropertiesAllegiance.AddOrUpdateAllegiance(playerGuid, false, true, BiotaDatabaseLock); else Biota.PropertiesAllegiance.AddOrUpdateAllegiance(playerGuid, entity.Banned, true, BiotaDatabaseLock); // ChangesDetected = true doesn't work here, // since the Allegiance WO is not associated with a landblock SaveBiotaToDatabase(); } public bool RemoveApprovedVassal(uint playerGuid) { var entity = Biota.PropertiesAllegiance.GetFirstOrDefaultByCharacterId(playerGuid, BiotaDatabaseLock); if (entity == null) return false; if (entity.Banned) { Biota.PropertiesAllegiance.AddOrUpdateAllegiance(playerGuid, true, false, BiotaDatabaseLock); SaveBiotaToDatabase(); return true; } var removed = Biota.PropertiesAllegiance.TryRemoveAllegiance(playerGuid, BiotaDatabaseLock); if (removed) SaveBiotaToDatabase(); return removed; } public bool HasApprovedVassal(uint playerGuid) { return ApprovedVassals.ContainsKey(playerGuid); } } }