using System; using System.Collections.Generic; using System.Linq; using ACE.Common; using ACE.Entity; using ACE.Entity.Enum; using ACE.Entity.Enum.Properties; using ACE.Entity.Models; using ACE.Server.Entity; using ACE.Server.Entity.Actions; using ACE.Server.Factories; using ACE.Server.Managers; using ACE.Server.Network.GameEvent.Events; using ACE.Server.Network.GameMessages.Messages; using ACE.Server.Network.Structure; namespace ACE.Server.WorldObjects { partial class Player { public House House; public Dictionary Guests => House?.Guests; /// /// Called when player clicks the 'Buy house' button, /// after adding the items required /// public void HandleActionBuyHouse(uint slumlord_id, List item_ids) { //Console.WriteLine($"\n{Name}.HandleActionBuyHouse()"); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse()"); // verify player doesn't already own a house var houseInstance = GetHouseInstance(); if (houseInstance != null) { //Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.HouseAlreadyOwned)); Session.Network.EnqueueSend(new GameMessageSystemChat("You already own a house!", ChatMessageType.Broadcast)); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Failed pre-purchase requirement - Already owns another house"); return; } var slumlord = (SlumLord)CurrentLandblock.GetObject(slumlord_id); if (slumlord == null) { log.Error($"[HOUSE] {Name}.HandleActionBuyHouse: Couldn't find slumlord 0x{slumlord_id:X8}!"); return; } if (slumlord.MinLevel != null) { var playerLevel = Level ?? 1; if (playerLevel < slumlord.MinLevel) { Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouMustBeAboveLevel_ToBuyHouse, slumlord.MinLevel.ToString())); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Failed pre-purchase requirement - MinLevel"); return; } } if (slumlord.HouseRequiresMonarch) { if (Allegiance == null || Allegiance.MonarchId != Guid.Full) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustBeMonarchToPurchaseDwelling)); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Failed pre-purchase requirement - HouseRequiresMonarch"); return; } } if (slumlord.AllegianceMinLevel != null) { var allegianceMinLevel = PropertyManager.GetLong("mansion_min_rank", -1).Item; if (allegianceMinLevel == -1) allegianceMinLevel = slumlord.AllegianceMinLevel.Value; if (allegianceMinLevel > 0 && (Allegiance == null || AllegianceNode.Rank < allegianceMinLevel)) { Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(Session, WeenieErrorWithString.YouMustBeAboveAllegianceRank_ToBuyHouse, allegianceMinLevel.ToString())); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Failed pre-purchase requirement - AllegianceMinLevel"); return; } } if (slumlord.House.HouseType != HouseType.Apartment) { if (PropertyManager.GetBool("house_15day_account").Item && !Account15Days) { var accountTimeSpan = DateTime.UtcNow - Account.CreateTime; if (accountTimeSpan.TotalDays < 15) { var msg = "Your account must be at least 15 days old to purchase this dwelling. This applies to all housing except apartments."; Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, msg), new GameMessageSystemChat(msg, ChatMessageType.Broadcast)); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Failed pre-purchase requirement - house_15day_account"); return; } } if (PropertyManager.GetBool("house_30day_cooldown").Item) { // fix gap if (!Account15Days) ManageAccount15Days_HousePurchaseTimestamp(); var lastPurchaseTime = Time.GetDateTimeFromTimestamp(HousePurchaseTimestamp ?? 0); var lastPurchaseTimePlus30 = lastPurchaseTime.AddDays(30); if (lastPurchaseTimePlus30 > DateTime.UtcNow) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustWaitToPurchaseHouse)); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Failed pre-purchase requirement - house_30day_cooldown"); return; } } } var verified = VerifyPurchase(slumlord, item_ids); if (!verified) { log.Warn($"[HOUSE] {Name} tried to purchase house {slumlord.Guid} without the required items!"); return; } //Console.WriteLine("\nInventory check passed!"); log.Info($"[HOUSE] {Name}.HandleActionBuyHouse(): Inventory check passed!"); // get the list of items / amounts to consume for purchase var houseProfile = slumlord.GetHouseProfile(); var items = GetInventoryItems(item_ids); var consumeItems = GetConsumeItems(houseProfile.Buy, items); if (!TryConsumePurchaseItems(consumeItems)) { var item_id_list = string.Join(", ", item_ids.Select(i => i.ToString("X8"))); var consumeItemsList = string.Join(", ", consumeItems.Select(i => $"{i.Name} ({i.Guid}) x{i.Value}")); log.ErrorFormat("[HOUSE] {0}.HandleActionBuyHouse({1:X8}, {2}) - TryConsumePurchaseItems failed with {3}", Name, slumlord_id, item_id_list, consumeItemsList); return; } SetHouseOwner(slumlord); GiveDeed(slumlord); } public void GiveDeed(SlumLord slumLord) { var deed = WorldObjectFactory.CreateNewWorldObject("deed"); var title = CharacterTitleId != null ? GetTitle((CharacterTitle)CharacterTitleId) : null; var titleStr = title != null ? $", {title}" : ""; var derethDateTime = DerethDateTime.UtcNowToLoreTime; var date = derethDateTime.DateToString(); var time = derethDateTime.TimeToString(); var location = slumLord.Location.GetMapCoordStr(); if (location == null) { if (!HouseManager.ApartmentBlocks.TryGetValue(slumLord.Location.Landblock, out location)) log.ErrorFormat("{0}.GiveDeed() - couldn't find location {1}", Name, slumLord.Location.ToLOCString()); } deed.LongDesc = $"Bought by {Name}{titleStr} on {date} at {time}\n\nPurchased at {location}"; TryCreateInInventoryWithNetworking(deed); } /// /// Removes the house deed from the player's inventory when they abandon the house /// public void RemoveDeed() { var deeds = GetInventoryItemsOfWCID(9549); if (deeds == null) { log.Warn($"[HOUSE] {Name}.RemoveDeed(): couldn't find inventory deed"); return; } foreach (var deed in deeds) TryConsumeFromInventoryWithNetworking(deed); } public void HandleActionRentHouse(uint slumlord_id, List item_ids) { //Console.WriteLine($"{Name}.HandleActionRentHouse({slumlord_id:X8}, {string.Join(", ", item_ids.Select(i => i.ToString("X8")))})"); log.Info($"[HOUSE] {Name}.HandleActionRentHouse({slumlord_id:X8}, {string.Join(", ", item_ids.Select(i => i.ToString("X8")))})"); var slumlord = FindObject(slumlord_id, SearchLocations.Landblock) as SlumLord; if (slumlord == null) { log.Warn($"[HOUSE] {Name}.HandleActionRentHouse({slumlord_id:X8}): Could not find SlumLord in world."); return; } if (slumlord.IsRentPaid()) { //Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.HouseRentFailed)); // WeenieError.HouseRentFailed == blank message Session.Network.EnqueueSend(new GameMessageSystemChat("The maintenance has already been paid for this period.\nYou may not prepay next period's maintenance.", ChatMessageType.Broadcast)); log.Info($"[HOUSE] {Name}.HandleActionRentHouse({slumlord_id:X8}): The maintenance has already been paid for this period."); return; } var owner = PlayerManager.FindByGuid(slumlord.HouseOwner ?? 0); if (owner != null) { var characterHouses = HouseManager.GetCharacterHouses(owner.Guid.Full); var accountHouses = HouseManager.GetAccountHouses(owner.Account.AccountId); var ownerHouses = PropertyManager.GetBool("house_per_char").Item ? characterHouses : accountHouses; if (ownerHouses.Count() > 1) { Session.Network.EnqueueSend(new GameMessageSystemChat("The owner of this house currently owns multiple houses. Maintenance cannot be paid until they only own 1 house.", ChatMessageType.Broadcast)); log.Info($"[HOUSE] {Name}.HandleActionRentHouse({slumlord_id:X8}): The owner of this house currently owns multiple houses. Maintenance cannot be paid until they only own 1 house."); return; } } else log.ErrorFormat("[HOUSE] {0}.HandleActionRentHouse({1:X8}): couldn't find house owner {2}", Name, slumlord_id, slumlord.HouseOwner); var logLine = $"[HOUSE] HandleActionRentHouse:" + Environment.NewLine; logLine += $"{slumlord.Name} ({slumlord.Guid})" + Environment.NewLine; var rentItems = slumlord.GetRentItems(); //Console.WriteLine("Required items:"); logLine += "Required items:"; foreach (var buyItem in rentItems) { var stackStr = buyItem.StackSize != null && buyItem.StackSize > 1 ? buyItem.StackSize.ToString() + " " : ""; //Console.WriteLine($"{stackStr}{buyItem.Name}"); logLine += $"{stackStr}{buyItem.Name}" + Environment.NewLine; } //Console.WriteLine("\nSent items:"); logLine += Environment.NewLine + "Sent items:" + Environment.NewLine; var sentItems = new List(); foreach (var item_id in item_ids) { var item = GetInventoryItem(new ObjectGuid(item_id)); if (item == null) { //Console.WriteLine($"Couldn't find inventory item {item_id:X8}"); logLine += $"Couldn't find inventory item {item_id:X8}" + Environment.NewLine; continue; } var stackStr = item.StackSize != null && item.StackSize > 1 ? item.StackSize.ToString() + " " : ""; //Console.WriteLine($"{stackStr}{item.Name} ({item.Guid})"); logLine += $"{stackStr}{item.Name} ({item.Guid})" + Environment.NewLine; if (IsTrading && item.IsBeingTradedOrContainsItemBeingTraded(ItemsInTradeWindow)) { //Console.WriteLine($"{stackStr}{item.Name} ({item.Guid}) is currently being traded, skipping."); logLine += $"{stackStr}{item.Name} ({item.Guid}) is currently being traded, skipping." + Environment.NewLine; continue; } sentItems.Add(item); } //Console.WriteLine(); logLine += Environment.NewLine; log.Info(logLine); // filter to items found in player's inventory var items = GetInventoryItems(item_ids); // get the list of items / amounts to consume for rent remaining var houseProfile = slumlord.GetHouseProfile(); var consumeItems = GetConsumeItems(houseProfile.Rent, items); if (IsTrading) { foreach (var item in consumeItems.ToList()) { if (ItemsInTradeWindow.Contains(item.Guid)) consumeItems.Remove(item); } } if (consumeItems.Count == 0) { log.Warn($"[HOUSE] {Name}.HandleActionRentHouse({slumlord_id:X8}): Nothing sent could be transferred to slumlord for rent."); return; } foreach (var consumeItem in consumeItems) TryConsumeItemForRent(slumlord, consumeItem); slumlord.MergeAllStackables(); // force save to database slumlord.SaveBiotaToDatabase(); foreach (var item in slumlord.Inventory.Values) item.SaveBiotaToDatabase(); slumlord.ActOnUse(this); HandleActionQueryHouse(); var maintenanceStatus = $"Maintenance {(slumlord.IsRentPaid() ? "" : "partially ")}paid."; Session.Network.EnqueueSend(new GameMessageSystemChat(maintenanceStatus, ChatMessageType.Broadcast)); log.Info($"[HOUSE] {Name}.HandleActionRentHouse({slumlord_id:X8}): {maintenanceStatus}"); } /// /// Returns the WorldObjects for item_ids that are in the player's inventory /// public List GetInventoryItems(List item_ids) { var inventoryItems = new List(); foreach (var item_id in item_ids) { var item = FindObject(item_id, SearchLocations.MyInventory); if (item != null) inventoryItems.Add(item); else log.ErrorFormat("{0}.GetInventoryItems() - couldn't find {1:X8}", Name, item_id); } return inventoryItems; } /// /// Returns the amount of items to consume for a house purchase / maintenance payment /// public List> GetConsumeItems(List houseItems, List playerItems) { var consumeItems = new List>(); foreach (var houseItem in houseItems) { consumeItems.AddRange(houseItem.GetConsumeItems(playerItems)); } return consumeItems; } /// /// Moves or splits an item from player inventory => slumlord inventory /// public bool TryConsumeItemForRent(SlumLord slumlord, WorldObjectInfo itemInfo) { var item = itemInfo.TryGetWorldObject(); if (item == null) { log.Error($"[HOUSE] {Name}.TryConsumeItemForRent({itemInfo.Guid:X8}) - couldn't get item"); return false; } var amount = itemInfo.Value; var stackSize = item.StackSize ?? 1; if (amount > stackSize) { log.Error($"[HOUSE] {Name}.TryConsumeItemForRent({item.Name} ({item.Guid}) - amount {amount} > stacksize {stackSize}"); return false; } var success = false; if (amount == stackSize) success = TryMoveItemForRent(slumlord, item); else success = TrySplitItemForRent(slumlord, item, amount); return success; } /// /// Moves an item from player inventory => slumlord inventory /// public bool TryMoveItemForRent(SlumLord slumlord, WorldObject item) { // verify slumlord can add item to inventory if (!slumlord.CanAddToInventory(item)) { log.Error($"[HOUSE] {Name}.TryMoveItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}) ) - CanAddToInventory failed!"); return false; } // remove entire item from player's inventory if (!TryRemoveFromInventoryWithNetworking(item.Guid, out _, RemoveFromInventoryAction.SpendItem)) { log.Error($"[HOUSE] {Name}.TryMoveItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}) ) - TryRemoveFromInventoryWithNetworking failed!"); return false; } // add to slumlord inventory if (!slumlord.TryAddToInventory(item)) { log.Error($"[HOUSE] {Name}.TryMoveItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}) ) - TryAddToInventory failed!"); return false; } log.InfoFormat("[HOUSE] {0}.TryMoveItemForRent({1} ({2}), {3}{4} ({5})) - Successfully moved to Slumlord.", Name, slumlord.Name, slumlord.Guid, ((item.StackSize ?? 1) > 1 ? $"{item.StackSize}x " : ""), item.Name, item.Guid); return true; } /// /// Splits an item from player inventory => slumlord inventory /// public bool TrySplitItemForRent(SlumLord slumlord, WorldObject item, int amount) { // create a new item w/ stacksize = amount for the slumlord's inventory var newItem = WorldObjectFactory.CreateNewWorldObject(item.WeenieClassId); newItem.SetStackSize(amount); // verify it can be added to slumlord's inventory if (!slumlord.CanAddToInventory(newItem)) { log.Error($"[HOUSE] {Name}.TrySplitItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}), {amount}) - CanAddToInventory failed for split item {newItem.Name} ({newItem.Guid})!"); newItem.Destroy(); return false; } // fetch container for AdjustStack if (GetInventoryItem(item.Guid, out var container) == null) { log.Error($"[HOUSE] {Name}.TrySplitItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}), {amount}) - GetInventoryItem failed!"); newItem.Destroy(); return false; } // subtract amount from player's item stacksize if (!AdjustStack(item, -amount, container, this)) { log.Error($"[HOUSE] {Name}.TrySplitItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}), {amount}) - failed to adjust stack!"); newItem.Destroy(); return false; } // force save of new player stack item.SaveBiotaToDatabase(); // send network updates Session.Network.EnqueueSend(new GameMessageSetStackSize(item)); Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.EncumbranceVal, EncumbranceVal ?? 0)); if (item.WeenieType == WeenieType.Coin) UpdateCoinValue(); if (!slumlord.TryAddToInventory(newItem)) { log.Error($"[HOUSE] {Name}.TrySplitItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}), {amount}) - TryAddToInventory failed for split item {newItem.Name} ({newItem.Guid})!"); newItem.Destroy(); return false; } log.Info($"[HOUSE] {Name}.TrySplitItemForRent({slumlord.Name} ({slumlord.Guid}), {item.Name} ({item.Guid}), {amount}) - Created new item {((newItem.StackSize ?? 1) > 1 ? $"{newItem.StackSize}x " : "")}{newItem.Name} ({newItem.Guid}) and moved to Slumlord."); // force save of new slumlord stack newItem.SaveBiotaToDatabase(); return true; } public void HandleActionAbandonHouse() { //Console.WriteLine($"\n{Name}.HandleActionAbandonHouse()"); log.Info($"[HOUSE] {Name}.HandleActionAbandonHouse()"); var houseInstance = GetHouseInstance(); if (houseInstance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } // only the character who directly owns the house can use /house abandon if (HouseInstance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OnlyHouseOwnerCanUseCommand)); return; } var house = GetHouse(); if (house != null) { house.HouseOwner = null; house.MonarchId = null; house.HouseOwnerName = null; house.ClearPermissions(); house.SaveBiotaToDatabase(); // relink house.UpdateLinks(); if (house.HasDungeon) { var dungeonHouse = house.GetDungeonHouse(); if (dungeonHouse != null) dungeonHouse.UpdateLinks(); } // player slumlord 'off' animation var slumlord = house.SlumLord; slumlord.ClearInventory(); slumlord.Off(); // reset slumlord name slumlord.SetAndBroadcastName(); slumlord.SaveBiotaToDatabase(); HouseList.AddToAvailable(slumlord, house); } HouseId = null; HouseInstance = null; //HousePurchaseTimestamp = null; HouseRentTimestamp = null; House = null; houseRentWarnTimestamp = 0; SaveBiotaToDatabase(); // send text message Session.Network.EnqueueSend(new GameMessageSystemChat("You abandon your house!", ChatMessageType.Broadcast)); HouseManager.RemoveRentQueue(house.Guid.Full); HouseManager.DecrementTotalOwnedHousingByType(house.HouseType); house.ClearRestrictions(); RemoveDeed(); HandleActionQueryHouse(); } public void HandleHouseOnLogin() { var evicted = GetProperty(PropertyBool.HouseEvicted) ?? false; if (evicted) { var evictChain = new ActionChain(); evictChain.AddDelaySeconds(5.0f); // todo: need inventory callback evictChain.AddAction(this, HandleEviction); evictChain.EnqueueChain(); return; } var houseInstance = GetHouseInstance(); if (House == null) LoadHouse(houseInstance); if (House == null || House.SlumLord == null) return; var houseOwner = GetHouseOwner(); // var purchaseTime = (uint)(houseOwner.HousePurchaseTimestamp ?? 0); if (HousePurchaseTimestamp != houseOwner.HousePurchaseTimestamp) { HousePurchaseTimestamp = houseOwner.HousePurchaseTimestamp; Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.HousePurchaseTimestamp, HousePurchaseTimestamp ?? 0), new GameMessageSystemChat("Updating housing information...", ChatMessageType.Broadcast), new GameEventHouseStatus(Session, WeenieError.HouseEvicted)); } // var rentTime = (uint)(houseOwner.HouseRentTimestamp ?? 0); if (HouseRentTimestamp != houseOwner.HouseRentTimestamp) HouseRentTimestamp = houseOwner.HouseRentTimestamp; if (!House.SlumLord.InventoryLoaded) { HouseManager.RegisterCallback(House, (house) => HandleHouseOnLogin_Inner()); } else HandleHouseOnLogin_Inner(); } public void HandleHouseOnLogin_Inner() { var actionChain = new ActionChain(); actionChain.AddDelaySeconds(5.0f); actionChain.AddAction(this, () => { if (House == null || House.SlumLord == null) return; if (House.HouseStatus == HouseStatus.Active && !House.SlumLord.IsRentPaid() && PropertyManager.GetBool("house_rent_enabled", true).Item) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Warning! You have not paid your maintenance costs for the last {(House.IsApartment ? "90" : "30")} day maintenance period. Please pay these costs by this deadline or you will lose your house, and all your items within it.", ChatMessageType.System)); } if (House.HouseOwner == Guid.Full && !House.SlumLord.HasRequirements(this) && PropertyManager.GetBool("house_purchase_requirements").Item) { var rankStr = AllegianceNode != null ? $"{AllegianceNode.Rank}" : ""; Session.Network.EnqueueSend(new GameMessageSystemChat($"Warning! Your allegiance rank {rankStr} is now below the requirements for owning a mansion. Please raise your allegiance rank to {House.SlumLord.GetAllegianceMinLevel()} before the end of the maintenance period or you will lose your mansion, and all your items within it.", ChatMessageType.System)); } // TODO: for account houses, run this even if char doesn't own house IsMultiHouseOwner(); }); actionChain.EnqueueChain(); } public void HandleEviction() { Session.Network.EnqueueSend(new GameMessageSystemChat("Your house has reverted due to non-payment of the maintenance costs. All items stored in the house have been lost.", ChatMessageType.Broadcast)); RemoveDeed(); RemoveProperty(PropertyBool.HouseEvicted); } /// /// Sets this player as the owner of a house /// public void SetHouseOwner(SlumLord slumlord) { var house = slumlord.House; //Console.WriteLine($"Setting {Name} as owner of {house.Name}"); log.Info($"[HOUSE] Setting {Name} (0x{Guid}) as owner of {house.Name} (0x{house.Guid:X8})"); // set player properties HouseId = house.HouseId; HouseInstance = house.Guid.Full; var housePurchaseTimestamp = Time.GetUnixTime(); if (house.HouseType != HouseType.Apartment) HousePurchaseTimestamp = (int)housePurchaseTimestamp; HouseRentTimestamp = (int)house.GetRentDue((uint)housePurchaseTimestamp); houseRentWarnTimestamp = 0; // set house properties house.HouseOwner = Guid.Full; house.HouseOwnerName = Name; house.OpenToEveryone = false; house.SaveBiotaToDatabase(); // relink house.UpdateLinks(); if (house.HasDungeon) { var dungeonHouse = house.GetDungeonHouse(); if (dungeonHouse != null) dungeonHouse.UpdateLinks(); } // notify client w/ HouseID Session.Network.EnqueueSend(new GameMessageSystemChat("Congratulations! You now own this dwelling.", ChatMessageType.Broadcast)); // player slumlord 'on' animation slumlord.On(); // set house name slumlord.SetAndBroadcastName(Name); slumlord.ClearInventory(); slumlord.SaveBiotaToDatabase(); HouseList.RemoveFromAvailable(slumlord, house); SaveBiotaToDatabase(); if (house.HouseType != HouseType.Apartment) Session.Network.EnqueueSend(new GameMessagePrivateUpdatePropertyInt(this, PropertyInt.HousePurchaseTimestamp, HousePurchaseTimestamp ?? 0)); // set house data // why has this changed? use callback? var actionChain = new ActionChain(); actionChain.AddDelaySeconds(3.0f); actionChain.AddAction(this, () => { HandleActionQueryHouse(); house.UpdateRestrictionDB(); // boot anyone who may have been wandering around inside... HandleActionBootAll(false); HouseManager.AddRentQueue(this, house.Guid.Full); slumlord.ActOnUse(this); }); actionChain.EnqueueChain(); } /// /// Removes verified items from inventory for house purchase /// public bool TryConsumePurchaseItems(List> purchaseItems) { foreach (var purchaseItem in purchaseItems) { var item = FindObject(purchaseItem.Guid.Full, SearchLocations.MyInventory); if (item == null) { // this should never happen, due to previous verifications log.Error($"[HOUSE] {Name}.ConsumeItemsForHousePurchase(): couldn't find {purchaseItem.Guid}!"); return false; } var amount = purchaseItem.Value; var stackSize = item.StackSize ?? 1; if (amount > stackSize) { // this should also never happen, due to previous checks log.Error($"[HOUSE] {Name}.ConsumeItemsForHousePurchase(): {item.Name} ({item.Guid}) amount({amount}) > stackSize({stackSize})!"); return false; } if (!TryConsumeFromInventoryWithNetworking(item, amount)) { // all of these things should never happen, just being absolutely certain... log.Error($"[HOUSE] {Name}.ConsumeItemsForHousePurchase(): TryConsumeFromInventoryWithNetworking({item.Name} ({item.Guid}), {amount}) failed!"); return false; } // force save partial stack reductions item.SaveBiotaToDatabase(); } return true; } /// /// Verifies the player inventory has required items to purchase house /// public bool VerifyPurchase(SlumLord slumlord, List item_ids) { // verify house is not already owned if (slumlord.HouseOwner != null) return false; //Console.WriteLine($"{slumlord.Name} ({slumlord.Guid})"); var logLine = $"[HOUSE] VerifyPurchase:" + Environment.NewLine; logLine += $"{slumlord.Name} ({slumlord.Guid})" + Environment.NewLine; var buyItems = slumlord.GetBuyItems(); //Console.WriteLine("Required items:"); logLine += "Required items:"; foreach (var buyItem in buyItems) { var stackStr = buyItem.StackSize != null && buyItem.StackSize > 1 ? buyItem.StackSize.ToString() + " " : ""; //Console.WriteLine($"{stackStr}{buyItem.Name}"); logLine += $"{stackStr}{buyItem.Name}" + Environment.NewLine; } //Console.WriteLine("\nSent items:"); logLine += Environment.NewLine + "Sent items:" + Environment.NewLine; var sentItems = new List(); foreach (var item_id in item_ids) { var item = GetInventoryItem(new ObjectGuid(item_id)); if (item == null) { //Console.WriteLine($"Couldn't find inventory item {item_id:X8}"); logLine += $"Couldn't find inventory item {item_id:X8}" + Environment.NewLine; continue; } var stackStr = item.StackSize != null && item.StackSize > 1 ? item.StackSize.ToString() + " " : ""; //Console.WriteLine($"{stackStr}{item.Name} ({item.Guid})"); logLine += $"{stackStr}{item.Name} ({item.Guid})" + Environment.NewLine; if (IsTrading && item.IsBeingTradedOrContainsItemBeingTraded(ItemsInTradeWindow)) { //Console.WriteLine($"{stackStr}{item.Name} ({item.Guid}) is currently being traded, skipping."); logLine += $"{stackStr}{item.Name} ({item.Guid}) is currently being traded, skipping." + Environment.NewLine; continue; } sentItems.Add(item); } //Console.WriteLine(); logLine += Environment.NewLine; log.Info(logLine); // compare list of input items // to required items for purchase return HasItems(sentItems, buyItems); } /// /// Returns TRUE if player inventory contains the required items to purchase house /// /// The items required to purchase a house public bool HasItems(List sentItems, List buyItems) { // requires: no duplicate individual items in list, // ie. items have already been stacked foreach (var buyItem in buyItems) { // special handling for currency if (buyItem.Name.Equals("Pyreal")) { if (!HasCurrency(sentItems, (uint)(buyItem.StackSize ?? 1))) return false; } else if (!HasItem(sentItems, buyItem)) return false; } return true; } /// /// Returns TRUE if player inventory contains an item required to purchase house /// /// An item to search for, using stack size as the minimum amount public bool HasItem(List sentItems, WorldObject buyItem) { var stackStr = buyItem.StackSize != null && buyItem.StackSize > 1 ? buyItem.StackSize.ToString() + " " : ""; //Console.WriteLine($"Checking for item: {stackStr}{buyItem.Name}"); log.Info($"[HOUSE] Checking for item: {stackStr}{buyItem.Name}"); // get all items of this wcid from inventory var itemMatches = sentItems.Where(i => i.WeenieClassId == buyItem.WeenieClassId).ToList(); var totalStack = itemMatches.Select(i => (int)(i.StackSize ?? 1)).Sum(); if (itemMatches.Count == 0) { //Console.WriteLine("No matching items found."); log.Info($"[HOUSE] No matching items found."); return false; } var required = buyItem.StackSize ?? 1; if (totalStack < required) { //Console.WriteLine($"Found {totalStack} items, requires {required}."); log.Info($"[HOUSE] Found {totalStack} items, requires {required}."); return false; } return true; } /// /// Determines if a player has at least some amount of currency in their inventory /// /// The minimum amount of currency required /// if TRUE, trade note will also be evaluated public bool HasCurrency(List sentItems, uint amount, bool useTradeNotes = true) { //Console.WriteLine($"Checking for currency: {amount}"); log.Info($"[HOUSE] Checking for currency: {amount}"); var totalCurrency = GetTotalCurrency(sentItems, useTradeNotes); return totalCurrency >= amount; } /// /// Returns the total amount of currency in the player's inventory /// /// if TRUE, uses pyreals + trade notes. if FALSE, uses pyreals only public uint GetTotalCurrency(List sentItems, bool useTradeNotes = true) { var totalPyreals = GetTotalPyreals(sentItems); //Console.WriteLine($"Total pyreals: {totalPyreals}"); log.Info($"[HOUSE] Total pyreals: {totalPyreals}"); if (!useTradeNotes) return totalPyreals; var totalTradeNotes = GetTotalTradeNotes(sentItems); //Console.WriteLine($"Total trade notes: {totalTradeNotes}"); log.Info($"[HOUSE] Total trade notes: {totalTradeNotes}"); return totalPyreals + totalTradeNotes; } public uint GetTotalPyreals(List sentItems) { var coinStacks = sentItems.Where(i => i.WeenieClassId == 273); // pyreals uint totalPyreals = 0; foreach (var coinStack in coinStacks) totalPyreals += (uint)(coinStack.Value ?? 0); return totalPyreals; } public uint GetTotalTradeNotes(List sentItems) { var tradeNotes = sentItems.Where(i => i.WeenieClassName.StartsWith("tradenote")).ToList(); uint totalValue = 0; foreach (var tradeNote in tradeNotes) totalValue += (uint)(tradeNote.Value ?? 0); return totalValue; } public IPlayer GetHouseOwner() { // if this character owns a house, always use that if (HouseInstance != null) return this; // if server is running house_per_char mode (non-default), // only use the HouseInstance for the current character if (PropertyManager.GetBool("house_per_char").Item) return this; // else return the account house owner var accountHouseOwner = GetAccountHouseOwner(); return accountHouseOwner; } public uint? GetHouseInstance() { return GetHouseOwner()?.HouseInstance; } public void HandleActionQueryHouse() { var houseOwner = GetHouseOwner(); var houseInstance = houseOwner?.HouseInstance; // no house owned - send 0x226 HouseStatus? if (houseInstance == null) { Session.Network.EnqueueSend(new GameEventHouseStatus(Session)); return; } // house owned - send 0x225 HouseData? if (House == null) LoadHouse(houseInstance); HouseManager.GetHouse(houseInstance.Value, (house) => { var houseData = house.GetHouseData(houseOwner); Session.Network.EnqueueSend(new GameEventHouseData(Session, houseData)); }); } public House LoadHouse(uint? houseInstance, bool forceLoad = false) { if (House != null && !forceLoad) return House; if (houseInstance == null) return House; House = House.Load(houseInstance.Value); return House; } public House GetHouse() { var houseInstance = GetHouseInstance(); return GetHouse(houseInstance); } public House GetHouse(uint? houseInstance) { if (houseInstance == null) return null; var houseGuid = houseInstance.Value; var landblock = (ushort)((houseGuid >> 12) & 0xFFFF); var landblockId = new LandblockId((uint)(landblock << 16 | 0xFFFF)); var isLoaded = LandblockManager.IsLoaded(landblockId); if (!isLoaded) return House = House.Load(houseGuid); var loaded = LandblockManager.GetLandblock(landblockId, false); return House = loaded.GetObject(new ObjectGuid(houseGuid)) as House; } public void HandleActionAddGuest(string guestName) { //Console.WriteLine($"{Name}.HandleActionAddGuest({guestName})"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); var guest = PlayerManager.FindByName(guestName, out bool isOnline); if (guest == null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{guestName} not found", ChatMessageType.Broadcast)); return; } if (guest.Equals(this)) { Session.Network.EnqueueSend(new GameMessageSystemChat("You already have access to your house.", ChatMessageType.Broadcast)); return; } var accountPlayers = PlayerManager.GetAccountPlayers(Account.AccountId); if (Guests.ContainsKey(guest.Guid) || accountPlayers.ContainsKey(guest.Guid.Full)) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{guest.Name} is already on your guest list.", ChatMessageType.Broadcast)); return; } if (Guests.Count == House.MaxGuests) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your guest list has already reached the maximum limit ({House.MaxGuests})", ChatMessageType.Broadcast)); return; } house.AddGuest(guest, false); Session.Network.EnqueueSend(new GameMessageSystemChat($"{guest.Name} has been added to your guest list.", ChatMessageType.Broadcast)); // notify online guest for addition if (isOnline) { var onlineGuest = PlayerManager.GetOnlinePlayer(guest.Guid); onlineGuest.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} has added you to their house guest list.", ChatMessageType.Broadcast)); } } public void HandleActionRemoveGuest(string guestName, bool sendMsgToOwner = true) { //Console.WriteLine($"{Name}.HandleActionRemoveGuest({guestName})"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); var guest = PlayerManager.FindByName(guestName, out bool isOnline); if (guest == null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{guestName} not found", ChatMessageType.Broadcast)); return; } if (guest.Equals(this)) { Session.Network.EnqueueSend(new GameMessageSystemChat("You have permanent access to your house.", ChatMessageType.Broadcast)); return; } if (!Guests.ContainsKey(guest.Guid)) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{guest.Name} is not on your guest list.", ChatMessageType.Broadcast)); return; } house.RemoveGuest(guest); if (sendMsgToOwner) Session.Network.EnqueueSend(new GameMessageSystemChat($"{guest.Name} removed from your guest list.", ChatMessageType.Broadcast)); // notify online guest removed if (isOnline) { var onlineGuest = PlayerManager.GetOnlinePlayer(guest.Guid); onlineGuest.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} has removed you from their house guest list.", ChatMessageType.Broadcast)); // if guest access is removed while player is in house, // they will be stuck in restriction space if (house.OnProperty(onlineGuest)) HandleActionBoot(onlineGuest.Name); } } public void HandleActionRemoveAllGuests() { //Console.WriteLine($"{Name}.HandleActionRemoveAllGuests()"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); if (Guests.Count == 0) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your house guest list is empty.", ChatMessageType.Broadcast)); return; } foreach (var guid in Guests.Keys.ToList()) { var guest = PlayerManager.FindByGuid(guid); HandleActionRemoveGuest(guest.Name, false); } Session.Network.EnqueueSend(new GameMessageSystemChat($"You have removed all the guests from your house.", ChatMessageType.Broadcast)); } public void HandleActionGuestList() { //Console.WriteLine($"{Name}.HandleActionGuestList()"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); if (house == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } Session.Network.EnqueueSend(new GameEventUpdateHAR(Session, house)); return; } public void HandleActionSetOpenStatus(bool openStatus) { //Console.WriteLine($"{Name}.HandleActionSetOpenStatus({openStatus})"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); if (openStatus == house.OpenStatus) { if (openStatus) Session.Network.EnqueueSend(new GameMessageSystemChat("You already have an open house.", ChatMessageType.Broadcast)); else Session.Network.EnqueueSend(new GameMessageSystemChat("Your house is already closed to the public.", ChatMessageType.Broadcast)); return; } house.OpenStatus = openStatus; house.Biota.SetProperty(PropertyBool.Open, house.OpenStatus, house.BiotaDatabaseLock, out _); house.ChangesDetected = true; house.UpdateRestrictionDB(); if (openStatus) Session.Network.EnqueueSend(new GameMessageSystemChat("Your house is now open to the public.", ChatMessageType.Broadcast)); else { Session.Network.EnqueueSend(new GameMessageSystemChat("Your house is now closed to the public.", ChatMessageType.Broadcast)); // boot anyone not on the guest list, // else they will be stuck in restricted space HandleActionBootAll(false); } if (house.CurrentLandblock == null) house.SaveBiotaToDatabase(); } public void HandleActionSetHooksVisible(bool visible) { //Console.WriteLine($"{Name}.HandleActionSetHooksVisible({visible})"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var visibleStr = visible ? "visible" : "invisible"; Session.Network.EnqueueSend(new GameMessageSystemChat($"Your hooks are set to {visibleStr}.", ChatMessageType.Broadcast)); var house = GetHouse(); if (visible == (house.HouseHooksVisible ?? true)) return; house.HouseHooksVisible = visible; foreach (var hook in house.Hooks.Where(i => i.Inventory.Count == 0)) { hook.UpdateHookVisibility(); } // if house has dungeon, repeat this process if (house.HasDungeon) { var dungeonHouse = house.GetDungeonHouse(); if (dungeonHouse == null) return; dungeonHouse.HouseHooksVisible = visible; foreach (var hook in dungeonHouse.Hooks.Where(i => i.Inventory.Count == 0)) { hook.UpdateHookVisibility(); } if (dungeonHouse.CurrentLandblock == null) dungeonHouse.SaveBiotaToDatabase(); } if (house.CurrentLandblock == null) house.SaveBiotaToDatabase(); } public void HandleActionModifyStorage(string guestName, bool hasPermission, bool sendMsgToOwner = true) { //Console.WriteLine($"{Name}.HandleActionModifyStorage({guestName}, {hasPermission})"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); var storage = PlayerManager.FindByName(guestName, out bool isOnline); if (storage == null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{guestName} not found", ChatMessageType.Broadcast)); return; } if (storage.Equals(this)) { Session.Network.EnqueueSend(new GameMessageSystemChat("You have permanent access to your house storage.", ChatMessageType.Broadcast)); return; } var existing = Guests.TryGetValue(storage.Guid, out var storageAccess); if (hasPermission) { if (!existing) { if (Guests.Count == House.MaxGuests) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your guest list has already reached the maximum limit ({House.MaxGuests})", ChatMessageType.Broadcast)); return; } house.AddGuest(storage, true); } else { if (storageAccess) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{storage.Name} already has access to your home's storage.", ChatMessageType.Broadcast)); return; } house.ModifyGuest(storage, true); } var andStr = !existing ? "and " : ""; if (sendMsgToOwner) Session.Network.EnqueueSend(new GameMessageSystemChat($"You have granted {storage.Name} access to your home's storage. This is denoted by the asterisk next to their name in the guest list.", ChatMessageType.Broadcast)); // spacing from PCAP // notify online storage guest added if (isOnline) { var onlineGuest = PlayerManager.GetOnlinePlayer(storage.Guid); onlineGuest.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} has granted you access to their house {andStr}storage.", ChatMessageType.Broadcast)); } } else { if (!existing || !storageAccess) { var storageStr = existing ? " storage" : ""; Session.Network.EnqueueSend(new GameMessageSystemChat($"{storage.Name} doesn't have access to your house{storageStr}.", ChatMessageType.Broadcast)); return; } house.ModifyGuest(storage, false); Session.Network.EnqueueSend(new GameMessageSystemChat($"{storage.Name} no longer has access to your house storage.", ChatMessageType.Broadcast)); // notify online storage guest added if (isOnline) { var onlineGuest = PlayerManager.GetOnlinePlayer(storage.Guid); onlineGuest.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} has revoked access to their house storage.", ChatMessageType.Broadcast)); // if they are in house, and have storage opened? } } } public void HandleActionAllStorage() { //Console.WriteLine($"{Name}.HandleActionAllStorage()"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); if (Guests.Count == 0) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your house guest list is empty.", ChatMessageType.Broadcast)); return; } var noStorage = Guests.Where(g => !g.Value).ToList(); if (noStorage.Count == 0) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your house guests already have access to your storage.", ChatMessageType.Broadcast)); return; } foreach (var guid in noStorage) { var guest = PlayerManager.FindByGuid(guid.Key); HandleActionModifyStorage(guest.Name, true, false); } Session.Network.EnqueueSend(new GameMessageSystemChat($"You grant item storage permission to all your guests", ChatMessageType.Broadcast)); } public void HandleActionRemoveAllStorage() { //Console.WriteLine($"{Name}.HandleActionRemoveAllStorage()"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = GetHouse(); if (Guests.Count == 0) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your house guest list is empty.", ChatMessageType.Broadcast)); return; } var storage = Guests.Where(g => g.Value).ToList(); if (storage.Count == 0) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your house guests don't have storage access.", ChatMessageType.Broadcast)); return; } foreach (var guid in storage) { var guest = PlayerManager.FindByGuid(guid.Key); HandleActionModifyStorage(guest.Name, false, false); } Session.Network.EnqueueSend(new GameMessageSystemChat($"You remove item storage permission from all your guests", ChatMessageType.Broadcast)); } public void HandleActionBoot(string playerName, bool allegianceHouse = false) { //Console.WriteLine($"{Name}.HandleActionBoot({playerName})"); if (House == null && !allegianceHouse) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } var house = allegianceHouse ? Allegiance.GetHouse() : GetHouse(); var player = PlayerManager.GetOnlinePlayer(playerName); if (player == null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{playerName} is not online.", ChatMessageType.Broadcast)); return; } // is this player in the house landcell? var owner = allegianceHouse ? "allegiance" : "your"; if (!house.OnProperty(player)) { Session.Network.EnqueueSend(new GameMessageSystemChat($"{player.Name} is not on {owner} property.", ChatMessageType.Broadcast)); return; } // play script? player.Teleport(house.BootSpot.Location); owner = allegianceHouse ? "the allegiance" : "your"; Session.Network.EnqueueSend(new GameMessageSystemChat($"Booted {player.Name} from {owner} house.", ChatMessageType.Broadcast)); owner = allegianceHouse ? "the allegiance" : "their"; player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} has booted you from {owner} house.", ChatMessageType.Broadcast)); } public void HandleActionBootAll(bool guests = true) { //Console.WriteLine($"{Name}.HandleActionBootAll()"); if (House == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } // since it can be an open house, the guest list wouldn't be enough here? var house = GetHouse(); var booted = house.BootAll(this, guests); if (guests && booted == 0) { var elseStr = house.OnProperty(this) ? "else " : ""; Session.Network.EnqueueSend(new GameMessageSystemChat($"There is no one {elseStr}on your property.", ChatMessageType.Broadcast)); } } /// /// Called when player is exiting portal space /// /// TRUE if player was booted from house property they don't have access to, upon exiting portal space public bool CheckHouse() { if (CurrentLandblock == null || IgnoreHouseBarriers) return false; foreach (var house in CurrentLandblock.Houses) { var rootHouse = house.RootHouse; if (!rootHouse.OnProperty(this)) continue; if (rootHouse.HouseOwner != null && !rootHouse.HasPermission(this, false)) { if (!rootHouse.IsOpen || (rootHouse.HouseType != HouseType.Apartment && CurrentLandblock.HasDungeon)) { Teleport(rootHouse.BootSpot.Location); return true; } } //if (rootHouse.HouseOwner == null && rootHouse.HouseType != HouseType.Apartment && CurrentLandblock.HasDungeon) //{ // Teleport(rootHouse.BootSpot.Location); // return true; //} } return false; } /// /// Handles the @hslist [housetype] command /// public void HandleActionListAvailable(HouseType houseType) { //Console.WriteLine($"{Name}.HandleActionListAvailable({houseType})"); var locations = HouseList.GetAvailableLocations(houseType); var unique = locations.Distinct().ToList(); Session.Network.EnqueueSend(new GameEventHouseAvailableHouses(Session, houseType, unique, locations.Count)); } //========================= // allegiance permissions //========================= public void HandleActionModifyAllegianceGuestPermission(bool add) { //Console.WriteLine($"{Name}.HandleActionModifyAllegianceGuestPermission({add})"); var houseInstance = GetHouseInstance(); if (houseInstance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } // only the character who directly owns the house can use /house guest add_allegiance / remove_allegiance if (HouseInstance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OnlyHouseOwnerCanUseCommand)); return; } // check if player is in an allegiance if (Allegiance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance)); return; } var house = GetHouse(); if (add) { if (house.MonarchId != null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy already has access to your dwelling.", ChatMessageType.Broadcast)); return; } if (Guests.Count == House.MaxGuests) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your guest list has already reached the maximum limit ({House.MaxGuests})", ChatMessageType.Broadcast)); return; } house.MonarchId = Allegiance.MonarchId; if (!Guests.ContainsKey(Allegiance.Monarch.PlayerGuid)) house.AddGuest(Allegiance.Monarch.Player, false); else house.ModifyGuest(Allegiance.Monarch.Player, false); // handle case: the monarch already has guest/storage access already, now adding allegiance Session.Network.EnqueueSend(new GameMessageSystemChat($"You have granted your monarchy access to your dwelling.", ChatMessageType.Broadcast)); } else { if (house.MonarchId == null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy did not have access to your dwelling.", ChatMessageType.Broadcast)); return; } house.MonarchId = null; house.RemoveGuest(Allegiance.Monarch.Player); HandleActionBootAll(false); // boot anyone who doesn't have guest access Session.Network.EnqueueSend(new GameMessageSystemChat($"You have revoked access to your dwelling to your monarchy.", ChatMessageType.Broadcast)); } } public void HandleActionModifyAllegianceStoragePermission(bool add) { //Console.WriteLine($"{Name}.HandleActionModifyAllegianceStoragePermission({add})"); var houseInstance = GetHouseInstance(); if (houseInstance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouMustOwnHouseToUseCommand)); return; } // only the character who directly owns the house can use /house storage add_allegiance / remove_allegiance if (HouseInstance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.OnlyHouseOwnerCanUseCommand)); return; } // check if player is in an allegiance if (Allegiance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance)); return; } var house = GetHouse(); if (add) { if (house.MonarchId != null && Guests.TryGetValue(new ObjectGuid(house.MonarchId.Value), out bool storage) && storage) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy already has storage access in your dwelling.", ChatMessageType.Broadcast)); return; } if (house.MonarchId == null && Guests.Count == House.MaxGuests) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your guest list has already reached the maximum limit ({House.MaxGuests})", ChatMessageType.Broadcast)); return; } house.MonarchId = Allegiance.MonarchId; if (!Guests.ContainsKey(Allegiance.Monarch.PlayerGuid)) house.AddGuest(Allegiance.Monarch.Player, true); else house.ModifyGuest(Allegiance.Monarch.Player, true); // handle case: the monarch already has guest/storage access already, now adding allegiance Session.Network.EnqueueSend(new GameMessageSystemChat($"You have granted your monarchy access to your storage.", ChatMessageType.Broadcast)); } else { if (house.MonarchId == null || Guests.TryGetValue(new ObjectGuid(house.MonarchId.Value), out bool storage) && !storage) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy did not have storage access to your dwelling.", ChatMessageType.Broadcast)); return; } // downgrade to guest access house.ModifyGuest(Allegiance.Monarch.Player, false); Session.Network.EnqueueSend(new GameMessageSystemChat($"You have revoked storage access to your monarchy.", ChatMessageType.Broadcast)); } } //============================= // /allegiance house commands //============================= public void HandleActionDoAllegianceHouseAction(AllegianceHouseAction action) { //Console.WriteLine($"{Name}.DoAllegianceHouseAction({action})"); if (Allegiance == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouAreNotInAllegiance)); return; } if (AllegiancePermissionLevel < AllegiancePermissionLevel.Castellan) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YouDoNotHaveAuthorityInAllegiance)); return; } var allegianceHouse = Allegiance.GetHouse(); if (allegianceHouse == null) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchDoesNotOwnAMansionOrVilla)); return; } if (allegianceHouse.HouseType != HouseType.Villa && allegianceHouse.HouseType != HouseType.Mansion) { Session.Network.EnqueueSend(new GameEventWeenieError(Session, WeenieError.YourMonarchsHouseIsNotAMansionOrVilla)); return; } if (action == AllegianceHouseAction.Help) { var help = "Note: You may substitute a forward slash(/) for the at symbol(@).\n" + "@allegiance house guest open - Adds your allegiance to the allegiance house guest list.\n" + "@allegiance house guest close - Removes your allegiance from the allegiance house guest list.\n" + "@allegiance house storage open - Adds your allegiance to the allegiance house storage list.\n" + "@allegiance house storage close - Removes your allegiance from the allegiance house storage list."; var status = ""; if (allegianceHouse.MonarchId == null) status = "\nYour monarchy currently does not have guest or storage access to allegiance housing."; else { allegianceHouse.Guests.TryGetValue(Allegiance.Monarch.PlayerGuid, out bool storage); if (!storage) status = "\nYour monarchy currently has guest access to allegiance housing."; else status = "\nYour monarchy currently has guest and storage access to allegiance housing."; } Session.Network.EnqueueSend(new GameMessageSystemChat(help + status, ChatMessageType.Broadcast)); return; } switch (action) { case AllegianceHouseAction.GuestOpen: HandleActionDoAllegianceHouseAction_GuestOpen(allegianceHouse); break; case AllegianceHouseAction.GuestClose: HandleActionDoAllegianceHouseAction_GuestClose(allegianceHouse); break; case AllegianceHouseAction.StorageOpen: HandleActionDoAllegianceHouseAction_StorageOpen(allegianceHouse); break; case AllegianceHouseAction.StorageClose: HandleActionDoAllegianceHouseAction_StorageClose(allegianceHouse); break; } if (allegianceHouse.CurrentLandblock == null) allegianceHouse.SaveBiotaToDatabase(); } public void HandleActionDoAllegianceHouseAction_GuestOpen(House allegianceHouse) { if (allegianceHouse.MonarchId != null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy already has access to the allegiance dwelling.", ChatMessageType.Broadcast)); return; } if (allegianceHouse.Guests.Count == House.MaxGuests) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The allegiance house guest list has already reached the maximum limit ({House.MaxGuests})", ChatMessageType.Broadcast)); return; } allegianceHouse.MonarchId = Allegiance.MonarchId; // AddHouseGuest allegianceHouse.AddGuest(Allegiance.Monarch.Player, false); Session.Network.EnqueueSend(new GameMessageSystemChat($"You have granted your monarchy access to the allegiance dwelling.", ChatMessageType.Broadcast)); } public void HandleActionDoAllegianceHouseAction_GuestClose(House allegianceHouse) { if (allegianceHouse.MonarchId == null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy already does not have access to the allegiance dwelling.", ChatMessageType.Broadcast)); return; } allegianceHouse.MonarchId = null; // RemoveHouseGuest allegianceHouse.RemoveGuest(Allegiance.Monarch.Player); var booted = allegianceHouse.BootAll(this, false, true); Session.Network.EnqueueSend(new GameMessageSystemChat($"You have revoked allegiance access to the allegiance dwelling.", ChatMessageType.Broadcast)); } public void HandleActionDoAllegianceHouseAction_StorageOpen(House allegianceHouse) { if (allegianceHouse.MonarchId != null && allegianceHouse.Guests.TryGetValue(new ObjectGuid(allegianceHouse.MonarchId.Value), out bool storage) && storage) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy already has storage access in the allegiance dwelling.", ChatMessageType.Broadcast)); return; } if (allegianceHouse.MonarchId == null && allegianceHouse.Guests.Count == House.MaxGuests) { Session.Network.EnqueueSend(new GameMessageSystemChat($"Your allegiance house guest list has already reached the maximum limit ({House.MaxGuests})", ChatMessageType.Broadcast)); return; } allegianceHouse.MonarchId = Allegiance.MonarchId; // AddHouseGuest if (!allegianceHouse.Guests.ContainsKey(Allegiance.Monarch.PlayerGuid)) { allegianceHouse.AddGuest(Allegiance.Monarch.Player, true); } else { // handle guest -> storage access upgrade allegianceHouse.ModifyGuest(Allegiance.Monarch.Player, true); } Session.Network.EnqueueSend(new GameMessageSystemChat($"You have granted your monarchy access to allegiance storage.", ChatMessageType.Broadcast)); } public void HandleActionDoAllegianceHouseAction_StorageClose(House allegianceHouse) { if (allegianceHouse.MonarchId == null || allegianceHouse.Guests.TryGetValue(new ObjectGuid(allegianceHouse.MonarchId.Value), out bool storage) && !storage) { Session.Network.EnqueueSend(new GameMessageSystemChat($"The monarchy already does not have storage access to the allegiance dwelling.", ChatMessageType.Broadcast)); return; } // ModifyHouseGuest - downgrade to guest access allegianceHouse.ModifyGuest(Allegiance.Monarch.Player, false); Session.Network.EnqueueSend(new GameMessageSystemChat($"You have revoked your monarchy's access to the allegiance housing storage.", ChatMessageType.Broadcast)); } public IPlayer GetAccountHouseOwner() { var accountPlayers = PlayerManager.GetAccountPlayers(Account.AccountId); var accountHouseOwners = accountPlayers.Values.Where(i => i.HouseInstance != null); return accountHouseOwners.OrderBy(i => i.HousePurchaseTimestamp).FirstOrDefault(); } public House GetAccountHouse() { if (HouseInstance != null) return GetHouse(HouseInstance); var accountHouseOwner = GetAccountHouseOwner(); if (accountHouseOwner != null) return null; //Console.WriteLine($"Account House Owner: {accountHouseOwner.Name}"); return GetHouse(accountHouseOwner); } public House GetHouse(IPlayer player) { if (player?.HouseInstance == null) return null; // is landblock loaded? var houseGuid = 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(houseGuid); } public bool IsMultiHouseOwner(bool showMsg = true) { var characterHouses = HouseManager.GetCharacterHouses(Guid.Full); var accountHouses = HouseManager.GetAccountHouses(Account.AccountId); //if (showMsg) //Session.Network.EnqueueSend(new GameMessageSystemChat($"AccountHouses: {accountHouses.Count}, CharacterHouses: {characterHouses.Count}", ChatMessageType.Broadcast)); if (PropertyManager.GetBool("house_per_char").Item) { // 1 house per character if (characterHouses.Count > 1 && showMsg) ShowMultiHouseWarning(characterHouses, "character"); return characterHouses.Count > 1; } else { // 1 house per account (retail default) if (accountHouses.Count > 1 && showMsg) ShowMultiHouseWarning(accountHouses, "account"); return accountHouses.Count > 1; } } public List GetMultiHouses() { if (PropertyManager.GetBool("house_per_char").Item) return HouseManager.GetCharacterHouses(Guid.Full); else return HouseManager.GetAccountHouses(Account.AccountId); } public void ShowMultiHouseWarning(List houses, string type) { // this is a dangerous situation, and we want to clean it up asap Session.Network.EnqueueSend(new GameMessageSystemChat($"Warning! You currently own {houses.Count} different houses on this {type}.", ChatMessageType.Broadcast)); Session.Network.EnqueueSend(new GameMessageSystemChat($"Each {type} is only allowed to own 1 house, so you will have to choose which house you want to keep.", ChatMessageType.Broadcast)); Session.Network.EnqueueSend(new GameMessageSystemChat($"You currently own houses at:", ChatMessageType.Broadcast)); for (var i = 0; i < houses.Count; i++) { var house = houses[i]; var slumlord = house.SlumLord; if (slumlord == null) { log.Error($"{Name}.IsMultiHouseOwner(): {house.Guid} slumlord is null!"); continue; } var coords = HouseManager.GetCoords(slumlord.Location); Session.Network.EnqueueSend(new GameMessageSystemChat($"{i + 1}. {coords}", ChatMessageType.Broadcast)); } Session.Network.EnqueueSend(new GameMessageSystemChat($"Please choose the house you want to keep with /house-select # , where # is 1-{houses.Count}", ChatMessageType.Broadcast)); } /// /// When house_15day_account is true (retail server default), /// new characters logging in on accounts less than 15 days old /// have their HousePurchaseTimestamp set to 15 days before account creation. /// /// This is for the House panel to show the correct date the character may purchase a house, /// which the client automatically calculates as 30 days after HousePurchaseTimestamp /// private int FifteenDaysBeforeAccountCreation => (int)Time.GetUnixTime(Account.CreateTime.AddDays(-15)); /// /// Munges the HousePurchaseTimestamp for new accounts for correct display on House panel /// private void ManageAccount15Days_HousePurchaseTimestamp() { // http://acpedia.org/wiki/Housing_FAQ#Purchase_timer if (HouseRentTimestamp != null) return; if (PropertyManager.GetBool("house_15day_account").Item && !Account15Days) { // this is set so the next purchase time displays properly on house tab HousePurchaseTimestamp = FifteenDaysBeforeAccountCreation; } else if (HousePurchaseTimestamp == FifteenDaysBeforeAccountCreation) { // account is now 15+ days old and still has not purchased a house, remove unneeded HousePurchaseTimestamp // also if server admin sets house_15day_account to false, this corrects the next purchase time on the House panel HousePurchaseTimestamp = null; } } } }