ace/Source/ACE.Server/WorldObjects/Player_House.cs
Ty Conner e5fbb6ce31
Minimal changes to support metrics mod (#4346)
These are changes from PR #4199 which support outputting certain metrics that do not currently exist in ACEmulator.

These changes allow for the original PR to change to a simple Mod found at ACEmulator/ACE.Mods.Metrics
2025-08-11 22:38:37 -04:00

1893 lines
78 KiB
C#

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<ObjectGuid, bool> Guests => House?.Guests;
/// <summary>
/// Called when player clicks the 'Buy house' button,
/// after adding the items required
/// </summary>
public void HandleActionBuyHouse(uint slumlord_id, List<uint> 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);
}
/// <summary>
/// Removes the house deed from the player's inventory when they abandon the house
/// </summary>
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<uint> 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<WorldObject>();
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}");
}
/// <summary>
/// Returns the WorldObjects for item_ids that are in the player's inventory
/// </summary>
public List<WorldObject> GetInventoryItems(List<uint> item_ids)
{
var inventoryItems = new List<WorldObject>();
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;
}
/// <summary>
/// Returns the amount of items to consume for a house purchase / maintenance payment
/// </summary>
public List<WorldObjectInfo<int>> GetConsumeItems(List<HousePayment> houseItems, List<WorldObject> playerItems)
{
var consumeItems = new List<WorldObjectInfo<int>>();
foreach (var houseItem in houseItems)
{
consumeItems.AddRange(houseItem.GetConsumeItems(playerItems));
}
return consumeItems;
}
/// <summary>
/// Moves or splits an item from player inventory => slumlord inventory
/// </summary>
public bool TryConsumeItemForRent(SlumLord slumlord, WorldObjectInfo<int> 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;
}
/// <summary>
/// Moves an item from player inventory => slumlord inventory
/// </summary>
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;
}
/// <summary>
/// Splits an item from player inventory => slumlord inventory
/// </summary>
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);
}
/// <summary>
/// Sets this player as the owner of a house
/// </summary>
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();
}
/// <summary>
/// Removes verified items from inventory for house purchase
/// </summary>
public bool TryConsumePurchaseItems(List<WorldObjectInfo<int>> 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;
}
/// <summary>
/// Verifies the player inventory has required items to purchase house
/// </summary>
public bool VerifyPurchase(SlumLord slumlord, List<uint> 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<WorldObject>();
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);
}
/// <summary>
/// Returns TRUE if player inventory contains the required items to purchase house
/// </summary>
/// <param name="items">The items required to purchase a house</param>
public bool HasItems(List<WorldObject> sentItems, List<WorldObject> 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;
}
/// <summary>
/// Returns TRUE if player inventory contains an item required to purchase house
/// </summary>
/// <param name="item">An item to search for, using stack size as the minimum amount</param>
public bool HasItem(List<WorldObject> 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;
}
/// <summary>
/// Determines if a player has at least some amount of currency in their inventory
/// </summary>
/// <param name="amount">The minimum amount of currency required</param>
/// <param name="useTradeNotes">if TRUE, trade note will also be evaluated</param>
public bool HasCurrency(List<WorldObject> 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;
}
/// <summary>
/// Returns the total amount of currency in the player's inventory
/// </summary>
/// <param name="useTradeNotes">if TRUE, uses pyreals + trade notes. if FALSE, uses pyreals only</param>
public uint GetTotalCurrency(List<WorldObject> 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<WorldObject> 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<WorldObject> 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));
}
}
/// <summary>
/// Called when player is exiting portal space
/// </summary>
/// <returns>TRUE if player was booted from house property they don't have access to, upon exiting portal space</returns>
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;
}
/// <summary>
/// Handles the @hslist [housetype] command
/// </summary>
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<House> GetMultiHouses()
{
if (PropertyManager.GetBool("house_per_char").Item)
return HouseManager.GetCharacterHouses(Guid.Full);
else
return HouseManager.GetAccountHouses(Account.AccountId);
}
public void ShowMultiHouseWarning(List<House> 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));
}
/// <summary>
/// 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
/// </summary>
private int FifteenDaysBeforeAccountCreation => (int)Time.GetUnixTime(Account.CreateTime.AddDays(-15));
/// <summary>
/// Munges the HousePurchaseTimestamp for new accounts for correct display on House panel
/// </summary>
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;
}
}
}
}