using System; using System.Collections.Generic; using ModernUO.CodeGeneratedEvents; using ModernUO.Serialization; using Server.Accounting; using Server.Collections; using Server.ContextMenus; using Server.Engines.Spawners; using Server.Ethics; using Server.Guilds; using Server.Gumps; using Server.Items; using Server.Misc; using Server.Mobiles; using Server.Multis.Deeds; using Server.Regions; using Server.Targeting; namespace Server.Multis { public abstract partial class BaseHouse : BaseMulti { public static bool DecayEnabled { get; set; } public const int MaxCoOwners = 15; public const int MaximumBarkeepCount = 2; private static readonly Dictionary> m_Table = new(); private DecayLevel m_CurrentStage; private DecayLevel m_LastDecayLevel; private Mobile m_Owner; private bool m_Public; private HouseRegion m_Region; private Point3D m_RelativeBanLocation; private TrashBarrel m_Trash; public BaseHouse(int multiID, Mobile owner, int maxLockDown, int maxSecure) : base(multiID) { AllHouses.Add(this); LastRefreshed = Core.Now; BuiltOn = Core.Now; LastTraded = DateTime.MinValue; Doors = new List(); LockDowns = new List(); Secures = new List(); Addons = new List(); CoOwners = new List(); Friends = new List(); Bans = new List(); Access = new List(); VendorRentalContracts = new List(); InternalizedVendors = new List(); m_Owner = owner; MaxLockDowns = maxLockDown; MaxSecures = maxSecure; m_RelativeBanLocation = BaseBanLocation; // UpdateRegion(); if (owner != null) { if (!m_Table.TryGetValue(owner, out var list)) { m_Table[owner] = list = new List(); } list.Add(this); } Movable = false; } public BaseHouse(Serial serial) : base(serial) { AllHouses.Add(this); } // Is new player vendor system enabled? public static bool NewVendorSystem => Core.AOS; public static int MaxFriends => !Core.AOS ? 50 : 140; public static int MaxBans => !Core.AOS ? 50 : 140; [CommandProperty(AccessLevel.GameMaster)] public DateTime LastRefreshed { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool RestrictDecay { get; set; } public virtual Direction HouseDirection => Direction.South; public virtual TimeSpan DecayPeriod => TimeSpan.FromDays(5.0); public virtual DecayType DecayType { get { if (RestrictDecay || !DecayEnabled || DecayPeriod == TimeSpan.Zero) { return DecayType.Ageless; } if (m_Owner == null) { return Core.AOS ? DecayType.Condemned : DecayType.ManualRefresh; } if (m_Owner.Account is not Account acct) { return Core.AOS ? DecayType.Condemned : DecayType.ManualRefresh; } if (acct.AccessLevel >= AccessLevel.GameMaster) { return DecayType.Ageless; } for (var i = 0; i < acct.Length; ++i) { var mob = acct[i]; if (mob?.AccessLevel >= AccessLevel.GameMaster) { return DecayType.Ageless; } } if (!Core.AOS) { return DecayType.ManualRefresh; } if (acct.Inactive) { return DecayType.Condemned; } var allHouses = new List(); for (var i = 0; i < acct.Length; ++i) { var mob = acct[i]; if (mob != null) { allHouses.AddRange(GetHouses(mob)); } } BaseHouse newest = null; for (var i = 0; i < allHouses.Count; ++i) { var check = allHouses[i]; if (newest == null || IsNewer(check, newest)) { newest = check; } } if (this == newest) { return DecayType.AutoRefresh; } return DecayType.ManualRefresh; } } public virtual bool CanDecay { get { var type = DecayType; return type is DecayType.Condemned or DecayType.ManualRefresh; } } [CommandProperty(AccessLevel.GameMaster)] public virtual DecayLevel DecayLevel { get { DecayLevel result; if (!CanDecay) { if (DynamicDecay.Enabled) { ResetDynamicDecay(); } LastRefreshed = Core.Now; result = DecayLevel.Ageless; } else if (DynamicDecay.Enabled) { var stage = m_CurrentStage; if (stage == DecayLevel.Ageless || DynamicDecay.Decays(stage) && NextDecayStage <= Core.Now) { SetDynamicDecay(++stage); } if (stage == DecayLevel.Collapsed && (HasRentedVendors || VendorInventories.Count > 0)) { result = DecayLevel.DemolitionPending; } else { result = stage; } } else { result = GetOldDecayLevel(); } if (result != m_LastDecayLevel) { m_LastDecayLevel = result; if (Sign?.GettingProperties == false) { Sign.InvalidateProperties(); } } return result; } } public virtual TimeSpan RestrictedPlacingTime => TimeSpan.FromHours(1.0); [CommandProperty(AccessLevel.GameMaster)] public virtual double BonusStorageScalar => Core.ML ? 1.2 : 1.0; public virtual bool IsAosRules => Core.AOS; public virtual bool IsActive => true; public bool HasPersonalVendors { get { foreach (var vendor in PlayerVendors) { if (vendor is not RentedVendor) { return true; } } return false; } } public bool HasRentedVendors { get { foreach (var vendor in PlayerVendors) { if (vendor is RentedVendor) { return true; } } return false; } } public bool HasAddonContainers { get { foreach (var item in Addons) { if (item is BaseAddonContainer) { return true; } } return false; } } public static List AllHouses { get; } = new(); public abstract Rectangle2D[] Area { get; } public abstract Point3D BaseBanLocation { get; } public override bool Decays => false; [CommandProperty(AccessLevel.GameMaster)] public Mobile Owner { get => m_Owner; set { if (m_Owner == value) { return; } if (m_Owner != null) { if (m_Table.TryGetValue(m_Owner, out var list) && list.Remove(this) && list.Count == 0) { m_Table.Remove(m_Owner); } m_Owner.Delta(MobileDelta.Noto); } m_Owner = value; if (m_Owner != null) { if (!m_Table.TryGetValue(m_Owner, out var list)) { m_Table[m_Owner] = list = new List(); } list.Add(this); m_Owner.Delta(MobileDelta.Noto); } Sign?.InvalidateProperties(); } } [CommandProperty(AccessLevel.GameMaster)] public int Visits { get; set; } [CommandProperty(AccessLevel.GameMaster)] public bool Public { get => m_Public; set { if (m_Public != value) { m_Public = value; if (!m_Public) // Privatizing the house, change to brass sign { ChangeSignType(0xBD2); } Sign?.InvalidateProperties(); } } } [CommandProperty(AccessLevel.GameMaster)] public int MaxSecures { get; set; } [CommandProperty(AccessLevel.GameMaster)] public Point3D BanLocation { get { if (m_Region != null) { return m_Region.GoLocation; } var rel = m_RelativeBanLocation; return new Point3D(X + rel.X, Y + rel.Y, Z + rel.Z); } set => RelativeBanLocation = new Point3D(value.X - X, value.Y - Y, value.Z - Z); } [CommandProperty(AccessLevel.GameMaster)] public Point3D RelativeBanLocation { get => m_RelativeBanLocation; set { m_RelativeBanLocation = value; if (m_Region != null) { m_Region.GoLocation = new Point3D(X + value.X, Y + value.Y, Z + value.Z); } } } [CommandProperty(AccessLevel.GameMaster)] public int MaxLockDowns { get; set; } public Region Region => m_Region; public List CoOwners { get; set; } public List Friends { get; set; } public List Access { get; set; } public List Bans { get; set; } public List Doors { get; set; } public int LockDownCount { get { var count = 0; count += GetLockdowns(); if (Secures != null) { for (var i = 0; i < Secures.Count; ++i) { var info = Secures[i]; if (info.Item.Deleted) { continue; } if (info.Item is StrongBox) { count += 1; } else { count += 125; } } } return count; } } public int SecureCount { get { var count = 0; if (Secures != null) { for (var i = 0; i < Secures.Count; i++) { var info = Secures[i]; if (info.Item.Deleted) { continue; } if (info.Item is not StrongBox) { count += 1; } } } return count; } } public List Addons { get; set; } public List LockDowns { get; private set; } public List Secures { get; private set; } public HouseSign Sign { get; set; } public List PlayerVendors { get; } = new(); public List PlayerBarkeepers { get; } = new(); public List VendorRentalContracts { get; private set; } public List VendorInventories { get; } = new(); public List RelocatedEntities { get; } = new(); public MovingCrate MovingCrate { get; set; } public List InternalizedVendors { get; private set; } public DateTime BuiltOn { get; set; } public DateTime LastTraded { get; set; } public virtual HousePlacementEntry ConvertEntry => null; public virtual int ConvertOffsetX => 0; public virtual int ConvertOffsetY => 0; public virtual int ConvertOffsetZ => 0; public virtual int DefaultPrice => 0; [CommandProperty(AccessLevel.GameMaster)] public int Price { get; set; } [CommandProperty(AccessLevel.GameMaster)] public DateTime NextDecayStage { get; set; } public static void Decay_OnTick() { for (var i = 0; i < AllHouses.Count; ++i) { AllHouses[i].CheckDecay(); } } public bool IsNewer(BaseHouse check, BaseHouse house) { var checkTime = check.LastTraded > check.BuiltOn ? check.LastTraded : check.BuiltOn; var houseTime = house.LastTraded > house.BuiltOn ? house.LastTraded : house.BuiltOn; return checkTime > houseTime; } public DecayLevel GetOldDecayLevel() { var timeAfterRefresh = Core.Now - LastRefreshed; var percent = (int)(timeAfterRefresh.Ticks * 1000 / DecayPeriod.Ticks); if (percent >= 1000) // 100.0% { return HasRentedVendors || VendorInventories.Count > 0 ? DecayLevel.DemolitionPending : DecayLevel.Collapsed; } if (percent >= 950) // 95.0% - 99.9% { return DecayLevel.IDOC; } if (percent >= 750) // 75.0% - 94.9% { return DecayLevel.Greatly; } if (percent >= 500) // 50.0% - 74.9% { return DecayLevel.Fairly; } if (percent >= 250) // 25.0% - 49.9% { return DecayLevel.Somewhat; } if (percent >= 005) // 00.5% - 24.9% { return DecayLevel.Slightly; } return DecayLevel.LikeNew; } public virtual bool RefreshDecay() { if (DecayType == DecayType.Condemned) { return false; } var oldLevel = DecayLevel; LastRefreshed = Core.Now; if (DynamicDecay.Enabled) { ResetDynamicDecay(); } Sign?.InvalidateProperties(); return oldLevel > DecayLevel.LikeNew; } public virtual bool CheckDecay() { if (!Deleted && DecayLevel == DecayLevel.Collapsed) { Timer.StartTimer(Decay_Sandbox); return true; } return false; } public virtual void KillVendors() { using var list = PooledRefList.Create(); list.AddRange(PlayerVendors); foreach (var vendor in list) { ((PlayerVendor)vendor).Destroy(true); } list.Clear(); list.AddRange(PlayerBarkeepers); foreach (var barkeeper in list) { barkeeper.Delete(); } } public virtual void Decay_Sandbox() { if (Deleted) { return; } if (Core.ML) { new TempNoHousingRegion(this, null); } KillVendors(); Delete(); } public virtual HousePlacementEntry GetAosEntry() => HousePlacementEntry.Find(this); public virtual int GetAosMaxSecures() { var hpe = GetAosEntry(); if (hpe == null) { return 0; } return (int)(hpe.Storage * BonusStorageScalar); } public virtual int GetAosMaxLockdowns() { var hpe = GetAosEntry(); if (hpe == null) { return 0; } return (int)(hpe.Lockdowns * BonusStorageScalar); } public virtual int GetAosCurSecures( out int fromSecures, out int fromVendors, out int fromLockdowns, out int fromMovingCrate ) { fromSecures = 0; fromVendors = 0; fromLockdowns = 0; fromMovingCrate = 0; var list = Secures; if (list != null) { for (var i = 0; i < list.Count; ++i) { var si = list[i]; fromSecures += si.Item.TotalItems; } fromLockdowns += list.Count; } fromLockdowns += GetLockdowns(); if (!NewVendorSystem) { foreach (var vendor in PlayerVendors) { if (vendor.Backpack != null) { fromVendors += vendor.Backpack.TotalItems; } } } if (MovingCrate != null) { fromMovingCrate += MovingCrate.TotalItems; foreach (var item in MovingCrate.Items) { if (item is PackingBox) { fromMovingCrate--; } } } return fromSecures + fromVendors + fromLockdowns + fromMovingCrate; } public override bool InRange(Point3D from, int range) => InRange(new Point2D(from), range); public override bool InRange(Point2D from, int range) { if (Region == null) { return false; } foreach (var rect in Region.Area) { if (from.X >= rect.Start.X - range && from.Y >= rect.Start.Y - range && from.X < rect.End.X + range && from.Y < rect.End.Y + range) { return true; } } return false; } public virtual int GetNewVendorSystemMaxVendors() { var hpe = GetAosEntry(); if (hpe == null) { return 0; } return (int)(hpe.Vendors * BonusStorageScalar); } public virtual bool CanPlaceNewVendor() => !IsAosRules || (!NewVendorSystem ? CheckAosLockdowns(10) : PlayerVendors.Count + VendorRentalContracts.Count < GetNewVendorSystemMaxVendors()); public virtual bool CanPlaceNewBarkeep() => PlayerBarkeepers.Count < MaximumBarkeepCount; public static void IsThereVendor(Point3D location, Map map, out bool vendor, out bool rentalContract) { vendor = false; rentalContract = false; foreach (var m in map.GetMobilesAt(location)) { if ((location.Z - m.Z).Abs() <= 16 && m is PlayerVendor or PlayerBarkeeper) { vendor = true; return; } } foreach (var item in map.GetItemsAt(location)) { if ((location.Z - item.Z).Abs() <= 16) { if (item is PlayerVendorPlaceholder) { vendor = true; return; } if (item is VendorRentalContract) { rentalContract = true; return; } } } } public List AvailableVendorsFor(Mobile m) { var list = new List(); foreach (var vendor in PlayerVendors) { if (vendor.CanInteractWith(m, false)) { list.Add(vendor); } } return list; } public bool AreThereAvailableVendorsFor(Mobile m) { foreach (var vendor in PlayerVendors) { if (vendor.CanInteractWith(m, false)) { return true; } } return false; } public void MoveAllToCrate() { RelocatedEntities.Clear(); MovingCrate?.Hide(); if (m_Trash != null) { m_Trash.Delete(); m_Trash = null; } foreach (var item in LockDowns) { if (!item.Deleted) { item.IsLockedDown = false; item.IsSecure = false; item.Movable = true; if (item.Parent == null) { DropToMovingCrate(item); } } } LockDowns.Clear(); foreach (Item item in VendorRentalContracts) { if (!item.Deleted) { item.IsLockedDown = false; item.IsSecure = false; item.Movable = true; if (item.Parent == null) { DropToMovingCrate(item); } } } VendorRentalContracts.Clear(); foreach (var info in Secures) { Item item = info.Item; if (!item.Deleted) { if (item is StrongBox box) { item = box.ConvertToStandardContainer(); } item.IsLockedDown = false; item.IsSecure = false; item.Movable = true; if (item.Parent == null) { DropToMovingCrate(item); } } } Secures.Clear(); foreach (var addon in Addons) { if (!addon.Deleted) { Item deed = null; var retainDeedHue = false; // if the items aren't hued but the deed itself is var hue = 0; var ba = addon as BaseAddon; if (addon is IAddon baseAddon) { deed = baseAddon.Deed; // There are things that are IAddon which aren't BaseAddon if (ba?.RetainDeedHue == true) { retainDeedHue = true; for (var i = 0; hue == 0 && i < ba.Components.Count; ++i) { var c = ba.Components[i]; if (c.Hue != 0) { hue = c.Hue; } } } } if (deed != null) { if (deed is BaseAddonContainerDeed containerDeed && addon is BaseAddonContainer c) { c.DropItemsToGround(); containerDeed.Resource = c.Resource; } else if (deed is BaseAddonDeed addonDeed && ba != null) { addonDeed.Resource = ba.Resource; } addon.Delete(); if (retainDeedHue) { deed.Hue = hue; } DropToMovingCrate(deed); } else { DropToMovingCrate(addon); } } } Addons.Clear(); foreach (var mobile in PlayerVendors) { mobile.Return(); mobile.Internalize(); InternalizedVendors.Add(mobile); } foreach (var mobile in PlayerBarkeepers) { mobile.Internalize(); InternalizedVendors.Add(mobile); } } public PooledRefList GetHouseEntities() { var list = PooledRefList.Create(256); MovingCrate?.Hide(); if (m_Trash != null && m_Trash.Map != Map.Internal) { list.Add(m_Trash); } for (var i = 0; i < LockDowns.Count; i++) { var item = LockDowns[i]; if (item.Parent == null && item.Map != Map.Internal) { list.Add(item); } } for (var i = 0; i < VendorRentalContracts.Count; i++) { var item = VendorRentalContracts[i]; if (item.Parent == null && item.Map != Map.Internal) { list.Add(item); } } for (var i = 0; i < Secures.Count; i++) { var item = Secures[i].Item; if (item.Parent == null && item.Map != Map.Internal) { list.Add(item); } } for (var i = 0; i < Addons.Count; i++) { var item = Addons[i]; if (item.Parent == null && item.Map != Map.Internal) { list.Add(item); } } for (var i = 0; i < PlayerVendors.Count; i++) { var mobile = PlayerVendors[i]; mobile.Return(); if (mobile.Map != Map.Internal) { list.Add(mobile); } } for (var i = 0; i < PlayerBarkeepers.Count; i++) { var mobile = PlayerBarkeepers[i]; if (mobile.Map != Map.Internal) { list.Add(mobile); } } return list; } public void RelocateEntities() { foreach (var entity in GetHouseEntities()) { var relLoc = new Point3D(entity.X - X, entity.Y - Y, entity.Z - Z); var relocEntity = new RelocatedEntity(entity, relLoc); RelocatedEntities.Add(relocEntity); if (entity is Item item) { item.Internalize(); } else if (entity is Mobile mobile) { mobile.Internalize(); } } } public void RestoreRelocatedEntities() { foreach (var relocEntity in RelocatedEntities) { var relLoc = relocEntity.RelativeLocation; var location = new Point3D(relLoc.X + X, relLoc.Y + Y, relLoc.Z + Z); var entity = relocEntity.Entity; if (entity is Item item) { if (!item.Deleted) { var addon = item as IAddon; if (addon != null) { if (addon.CouldFit(location, Map)) { item.MoveToWorld(location, Map); continue; } } else { int height; bool requireSurface; if (item is VendorRentalContract) { height = 16; requireSurface = true; } else { height = item.ItemData.Height; requireSurface = false; } if (Map.CanFit(location.X, location.Y, location.Z, height, false, false, requireSurface)) { item.MoveToWorld(location, Map); continue; } } // The item can't fit if (item is TrashBarrel) { item.Delete(); // Trash barrels don't go to the moving crate } else { SetLockdown(item, false); item.IsSecure = false; item.Movable = true; var relocateItem = item; if (item is StrongBox box) { relocateItem = box.ConvertToStandardContainer(); } if (addon != null) { var deed = addon.Deed; var retainDeedHue = false; // if the items aren't hued but the deed itself is var hue = 0; // There are things that are IAddon which aren't BaseAddon if (item is BaseAddon ba && ba.RetainDeedHue) { retainDeedHue = true; for (var i = 0; hue == 0 && i < ba.Components.Count; ++i) { var c = ba.Components[i]; if (c.Hue != 0) { hue = c.Hue; } } } if (deed != null) { if (deed is BaseAddonContainerDeed containerDeed && item is BaseAddonContainer c) { c.DropItemsToGround(); containerDeed.Resource = c.Resource; } else if (deed is BaseAddonDeed addonDeed && item is BaseAddon baseAddon) { addonDeed.Resource = baseAddon.Resource; } if (retainDeedHue) { deed.Hue = hue; } } relocateItem = deed; item.Delete(); } if (relocateItem != null) { DropToMovingCrate(relocateItem); } } } if (m_Trash == item) { m_Trash = null; } LockDowns.Remove(item); if (item is VendorRentalContract contract) { VendorRentalContracts.Remove(contract); } Addons.Remove(item); for (var i = Secures.Count - 1; i >= 0; i--) { if (Secures[i].Item == item) { Secures.RemoveAt(i); } } } else if (entity is Mobile mobile && !mobile.Deleted) { if (Map.CanFit(location, 16, false, false)) { mobile.MoveToWorld(location, Map); } else { InternalizedVendors.Add(mobile); } } } RelocatedEntities.Clear(); } public void DropToMovingCrate(Item item) { MovingCrate ??= new MovingCrate(this); MovingCrate.DropItem(item); } // TODO: Convert to a ref struct enumerator public List GetItems() { if (Map == null || Map == Map.Internal) { return new List(); } var start = new Point2D(X + Components.Min.X, Y + Components.Min.Y); var end = new Point2D(X + Components.Max.X + 1, Y + Components.Max.Y + 1); var rect = new Rectangle2D(start, end); var list = new List(); foreach (var item in Map.GetItemsInBounds(rect)) { if (item.Movable && IsInside(item)) { list.Add(item); } } return list; } public PooledRefList GetMobilesPooled() { if (Map == null || Map == Map.Internal) { return PooledRefList.Create(0); } var mobileList = Region.GetMobilesPooled(); for (var i = mobileList.Count - 1; i >= 0; i--) { var mobile = mobileList[i]; if (!IsInside(mobile)) { mobileList.Remove(mobile); } } return mobileList; } public virtual bool CheckAosLockdowns(int need) => GetAosCurLockdowns() + need <= GetAosMaxLockdowns(); public virtual bool CheckAosStorage(int need) => GetAosCurSecures( out var fromSecures, out var fromVendors, out var fromLockdowns, out var fromMovingCrate ) + need <= GetAosMaxSecures(); public static void Configure() { DecayEnabled = ServerConfiguration.GetOrUpdateSetting("houseDecay.enable", true); LockedDownFlag = 1; SecureFlag = 2; } public static void Initialize() { Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), Decay_OnTick); } public virtual int GetAosCurLockdowns() { var v = 0; v += GetLockdowns(); if (Secures != null) { v += Secures.Count; } if (!NewVendorSystem) { v += PlayerVendors.Count * 10; } return v; } public static bool CheckLockedDown(Item item) => FindHouseAt(item)?.HasLockedDownItem(item) == true; public static bool CheckSecured(Item item) => FindHouseAt(item)?.HasSecureItem(item) == true; public static bool CheckLockedDownOrSecured(Item item) { var house = FindHouseAt(item); return house != null && (house.HasSecureItem(item) || house.HasLockedDownItem(item)); } public static List GetHouses(Mobile m) { var list = new List(); if (m != null) { if (m_Table.TryGetValue(m, out var exists)) { for (var i = 0; i < exists.Count; ++i) { var house = exists[i]; if (house?.Deleted == false && house.Owner == m) { list.Add(house); } } } } return list; } public static bool CheckHold( Mobile m, Container cont, Item item, bool message, bool checkItems, int plusItems, int plusWeight ) { var house = FindHouseAt(cont); if (house?.IsAosRules != true) { return true; } if (house.HasSecureItem(cont) && !house.CheckAosStorage(1 + item.TotalItems + plusItems)) { if (message) { m.SendLocalizedMessage(1061839); // This action would exceed the secure storage limit of the house. } return false; } return true; } public static bool CheckAccessible(Mobile m, Item item) { if (m.AccessLevel >= AccessLevel.GameMaster) { return true; // Staff can access anything } var house = FindHouseAt(item); if (house == null) { return true; } var res = house.CheckSecureAccess(m, item); switch (res) { case SecureAccessResult.Insecure: break; case SecureAccessResult.Accessible: return true; case SecureAccessResult.Inaccessible: return false; } if (house.HasLockedDownItem(item)) { return house.IsCoOwner(m) && item is Container; } return true; } public static BaseHouse FindHouseAt(Mobile m) { if (m?.Deleted != false) { return null; } return FindHouseAt(m.Location, m.Map, 16); } public static BaseHouse FindHouseAt(Item item) => item?.Deleted != false ? null : FindHouseAt(item.GetWorldLocation(), item.Map, item.ItemData.Height); public static BaseHouse FindHouseAt(Point3D loc, Map map, int height) { if (map == null || map == Map.Internal) { return null; } foreach (var house in map.GetMultisInSector(loc)) { if (house.IsInside(loc, height)) { return house; } } return null; } public bool IsInside(Mobile m) => m?.Deleted == false && m.Map == Map && IsInside(m.Location, 16); public bool IsInside(Item item) => item?.Deleted == false && item.Map == Map && IsInside(item.Location, item.ItemData.Height); public bool CheckAccessibility(Item item, Mobile from) { var res = CheckSecureAccess(from, item); switch (res) { case SecureAccessResult.Insecure: break; case SecureAccessResult.Accessible: return true; case SecureAccessResult.Inaccessible: return false; } if (!HasLockedDownItem(item)) { return true; } if (from.AccessLevel >= AccessLevel.GameMaster) { return true; } if (item is Runebook) { return true; } if (item is ISecurable securable) { return HasSecureAccess(from, securable.Level); } if (item is Container) { return IsCoOwner(from); } if (item.Stackable) { return true; } if (item is BaseLight) { return IsFriend(from); } if (item is PotionKeg) { return IsFriend(from); } return item is Dices or RecallRune or TreasureMap or Clock or BaseInstrument or Dyes or VendorRentalContract or RewardBrazier; } public virtual bool IsInside(Point3D p, int height) { if (Deleted) { return false; } var mcl = Components; var x = p.X - X - mcl.Min.X; var y = p.Y - Y - mcl.Min.Y; if (x < 0 || x >= mcl.Width || y < 0 || y >= mcl.Height) { return false; } if (this is HouseFoundation && y < mcl.Height - 1 && p.Z >= Z) { return true; } // TODO: Use ref struct var tiles = mcl.Tiles[x][y]; for (var j = 0; j < tiles.Length; ++j) { var tile = tiles[j]; var id = tile.ID & TileData.MaxItemValue; var data = TileData.ItemTable[id]; // Slanted roofs do not count; they overhang blocking south and east sides of the multi if (data.Roof) { continue; } // Signs and signposts are not considered part of the multi if (id >= 0xB95 && id <= 0xC0E || id >= 0xC43 && id <= 0xC44) { continue; } var tileZ = tile.Z + Z; if (p.Z == tileZ || p.Z + height > tileZ) { return true; } } return false; } public SecureAccessResult CheckSecureAccess(Mobile m, Item item) { if (Secures == null || item is not Container) { return SecureAccessResult.Insecure; } for (var i = 0; i < Secures.Count; ++i) { var info = Secures[i]; if (info.Item == item) { return HasSecureAccess(m, info.Level) ? SecureAccessResult.Accessible : SecureAccessResult.Inaccessible; } } return SecureAccessResult.Insecure; } public override void OnMapChange() { base.OnMapChange(); if (LockDowns == null) { return; } UpdateRegion(); // Invalidate spawn position cache for affected sectors if (Map != null && Map != Map.Internal) { var mcl = Components; var bounds = new Rectangle2D( X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height ); SectorSpawnCacheManager.InvalidateSectors(Map, bounds); } if (Sign?.Deleted == false) { Sign.Map = Map; } if (Doors != null) { foreach (var item in Doors) { item.Map = Map; } } foreach (var entity in GetHouseEntities()) { if (entity is Item item) { item.Map = Map; } else if (entity is Mobile mobile) { mobile.Map = Map; } } } public virtual void ChangeSignType(int itemID) { if (Sign != null) { Sign.ItemID = itemID; } } public virtual void UpdateRegion() { m_Region?.Unregister(); if (Map != null) { m_Region = new HouseRegion(this); m_Region.Register(); } else { m_Region = null; } } public override void OnLocationChange(Point3D oldLocation) { base.OnLocationChange(oldLocation); if (LockDowns == null) { return; } var x = Location.X - oldLocation.X; var y = Location.Y - oldLocation.Y; var z = Location.Z - oldLocation.Z; if (Sign?.Deleted == false) { Sign.Location = new Point3D(Sign.X + x, Sign.Y + y, Sign.Z + z); } UpdateRegion(); if (Doors != null) { foreach (var item in Doors) { if (!item.Deleted) { item.Location = new Point3D(item.X + x, item.Y + y, item.Z + z); } } } foreach (var entity in GetHouseEntities()) { var newLocation = new Point3D(entity.X + x, entity.Y + y, entity.Z + z); if (entity is Item item) { item.Location = newLocation; } else if (entity is Mobile mobile) { mobile.Location = newLocation; } } } public BaseDoor AddEastDoor(int x, int y, int z) => AddEastDoor(true, x, y, z); public BaseDoor AddEastDoor(bool wood, int x, int y, int z) { var door = MakeDoor(wood, DoorFacing.SouthCW); AddDoor(door, x, y, z); return door; } public BaseDoor AddSouthDoor(int x, int y, int z) => AddSouthDoor(true, x, y, z); public BaseDoor AddSouthDoor(bool wood, int x, int y, int z) { var door = MakeDoor(wood, DoorFacing.WestCW); AddDoor(door, x, y, z); return door; } public BaseDoor AddEastDoor(int x, int y, int z, uint k) => AddEastDoor(true, x, y, z, k); public BaseDoor AddEastDoor(bool wood, int x, int y, int z, uint k) { var door = MakeDoor(wood, DoorFacing.SouthCW); door.Locked = true; door.KeyValue = k; AddDoor(door, x, y, z); return door; } public BaseDoor AddSouthDoor(int x, int y, int z, uint k) => AddSouthDoor(true, x, y, z, k); public BaseDoor AddSouthDoor(bool wood, int x, int y, int z, uint k) { var door = MakeDoor(wood, DoorFacing.WestCW); door.Locked = true; door.KeyValue = k; AddDoor(door, x, y, z); return door; } public BaseDoor[] AddSouthDoors(int x, int y, int z, uint k) => AddSouthDoors(true, x, y, z, k); public BaseDoor[] AddSouthDoors(bool wood, int x, int y, int z, uint k) { var westDoor = MakeDoor(wood, DoorFacing.WestCW); var eastDoor = MakeDoor(wood, DoorFacing.EastCCW); westDoor.Locked = true; eastDoor.Locked = true; westDoor.KeyValue = k; eastDoor.KeyValue = k; westDoor.Link = eastDoor; eastDoor.Link = westDoor; AddDoor(westDoor, x, y, z); AddDoor(eastDoor, x + 1, y, z); return new[] { westDoor, eastDoor }; } protected BaseDoor AddDoor(int itemID, int xOffset, int yOffset, int zOffset) => AddDoor(null, itemID, xOffset, yOffset, zOffset); protected BaseDoor AddDoor(Mobile from, int itemID, int xOffset, int yOffset, int zOffset) { BaseDoor door = null; if (itemID >= 0x675 && itemID < 0x6F5) { var type = (itemID - 0x675) / 16; var facing = (DoorFacing)((itemID - 0x675) / 2 % 8); door = type switch { 0 => new GenericHouseDoor(facing, 0x675, 0xEC, 0xF3), 1 => new GenericHouseDoor(facing, 0x685, 0xEC, 0xF3), 2 => new GenericHouseDoor(facing, 0x695, 0xEB, 0xF2), 3 => new GenericHouseDoor(facing, 0x6A5, 0xEA, 0xF1), 4 => new GenericHouseDoor(facing, 0x6B5, 0xEA, 0xF1), 5 => new GenericHouseDoor(facing, 0x6C5, 0xEC, 0xF3), 6 => new GenericHouseDoor(facing, 0x6D5, 0xEA, 0xF1), _ => new GenericHouseDoor(facing, 0x6E5, 0xEA, 0xF1) // 7 }; } else if (itemID >= 0x314 && itemID < 0x364) { var type = (itemID - 0x314) / 16; var facing = (DoorFacing)((itemID - 0x314) / 2 % 8); door = new GenericHouseDoor(facing, 0x314 + type * 16, 0xED, 0xF4); } else if (itemID >= 0x824 && itemID < 0x834) { var facing = (DoorFacing)((itemID - 0x824) / 2 % 8); door = new GenericHouseDoor(facing, 0x824, 0xEC, 0xF3); } else if (itemID >= 0x839 && itemID < 0x849) { var facing = (DoorFacing)((itemID - 0x839) / 2 % 8); door = new GenericHouseDoor(facing, 0x839, 0xEB, 0xF2); } else if (itemID >= 0x84C && itemID < 0x85C) { var facing = (DoorFacing)((itemID - 0x84C) / 2 % 8); door = new GenericHouseDoor(facing, 0x84C, 0xEC, 0xF3); } else if (itemID >= 0x866 && itemID < 0x876) { var facing = (DoorFacing)((itemID - 0x866) / 2 % 8); door = new GenericHouseDoor(facing, 0x866, 0xEB, 0xF2); } else if (itemID >= 0xE8 && itemID < 0xF8) { var facing = (DoorFacing)((itemID - 0xE8) / 2 % 8); door = new GenericHouseDoor(facing, 0xE8, 0xED, 0xF4); } else if (itemID >= 0x1FED && itemID < 0x1FFD) { var facing = (DoorFacing)((itemID - 0x1FED) / 2 % 8); door = new GenericHouseDoor(facing, 0x1FED, 0xEC, 0xF3); } else if (itemID >= 0x241F && itemID < 0x2421) { // DoorFacing facing = (DoorFacing)(((itemID - 0x241F) / 2) % 8); door = new GenericHouseDoor(DoorFacing.NorthCCW, 0x2415, -1, -1); } else if (itemID >= 0x2423 && itemID < 0x2425) { // DoorFacing facing = (DoorFacing)(((itemID - 0x241F) / 2) % 8); // This one and the above one are 'special' cases, ie: OSI had the ItemID pattern discombobulated for these door = new GenericHouseDoor(DoorFacing.WestCW, 0x2423, -1, -1); } else if (itemID >= 0x2A05 && itemID < 0x2A1D) { var facing = (DoorFacing)((itemID - 0x2A05) / 2 % 4 + 8); var sound = itemID >= 0x2A0D && itemID < 0x2a15 ? 0x539 : -1; door = new GenericHouseDoor(facing, 0x29F5 + 8 * ((itemID - 0x2A05) / 8), sound, sound); } else if (itemID == 0x2D46) { door = new GenericHouseDoor(DoorFacing.NorthCW, 0x2D46, 0xEA, 0xF1, false); } else if (itemID is 0x2D48 or 0x2FE2) { door = new GenericHouseDoor(DoorFacing.SouthCCW, itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x2D63 && itemID < 0x2D70) { var mod = (itemID - 0x2D63) / 2 % 2; var facing = mod == 0 ? DoorFacing.SouthCCW : DoorFacing.WestCCW; var type = (itemID - 0x2D63) / 4; door = new GenericHouseDoor(facing, 0x2D63 + 4 * type + mod * 2, 0xEA, 0xF1, false); } else if (itemID is 0x2FE4 or 0x31AE) { door = new GenericHouseDoor(DoorFacing.WestCCW, itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x319C && itemID < 0x31AE) { // special case for 0x31aa <-> 0x31a8 (a9) var mod = (itemID - 0x319C) / 2 % 2; var facing = itemID switch { 0x31AA => mod == 0 ? DoorFacing.NorthCW : DoorFacing.EastCW, 0x31A8 => mod == 0 ? DoorFacing.NorthCW : DoorFacing.EastCW, _ => mod == 0 ? DoorFacing.EastCW : DoorFacing.NorthCW }; var type = (itemID - 0x319C) / 4; door = new GenericHouseDoor(facing, 0x319C + 4 * type + mod * 2, 0xEA, 0xF1, false); } else if (itemID >= 0x367B && itemID < 0x369B) { var type = (itemID - 0x367B) / 16; var facing = (DoorFacing)((itemID - 0x367B) / 2 % 8); door = type switch { 0 => new GenericHouseDoor(facing, 0x367B, 0xED, 0xF4), _ => new GenericHouseDoor(facing, 0x368B, 0xEC, 0x3E7) // 1 }; } else if (itemID >= 0x409B && itemID < 0x40A3) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x409B), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x410C && itemID < 0x4114) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x410C), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x41C2 && itemID < 0x41CA) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x41C2), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x41CF && itemID < 0x41D7) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x41CF), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x436E && itemID < 0x437E) { /* These ones had to be different... * Offset 0 2 4 6 8 10 12 14 * DoorFacing 2 3 2 3 6 7 6 7 */ var offset = itemID - 0x436E; var facing = (DoorFacing)((offset / 2 + 2 * ((1 + offset / 4) % 2)) % 8); door = new GenericHouseDoor(facing, itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x46DD && itemID < 0x46E5) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x46DD), itemID, 0xEB, 0xF2, false); } else if (itemID >= 0x4D22 && itemID < 0x4D2A) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x4D22), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x50C8 && itemID < 0x50D0) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x50C8), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x50D0 && itemID < 0x50D8) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x50D0), itemID, 0xEA, 0xF1, false); } else if (itemID >= 0x5142 && itemID < 0x514A) { door = new GenericHouseDoor(GetSADoorFacing(itemID - 0x5142), itemID, 0xF0, 0xEF, false); } // TODO: Fix this because the heuristic is broken, or remove the type calculation else if (itemID >= 0x9AD7 && itemID <= 0x9AE6) { var type = (itemID - 0x9AD7) / 16; var facing = (DoorFacing)((itemID - 0x9AD7) / 2 % 8); door = new GenericHouseDoor(facing, 0x9AD7 + type * 16, 0xED, 0xF4); } // TODO: Fix this because the heuristic is broken, or remove the type calculation else if (itemID >= 0x9B3C && itemID <= 0x9B4B) { var type = (itemID - 0x9B3C) / 16; var facing = (DoorFacing)((itemID - 0x9B3C) / 2 % 8); door = new GenericHouseDoor(facing, 0x9B3C + type * 16, 0xED, 0xF4); } if (door != null) { if (from != null) { door.KeyValue = CreateKeys(from); } AddDoor(door, xOffset, yOffset, zOffset); } else { Console.WriteLine("BaseHouse: Door ItemID {0} not supported.", itemID); } return door; } /* Offset 0 2 4 6 * DoorFacing 2 3 6 7 */ private static DoorFacing GetSADoorFacing(int offset) => (DoorFacing)((offset / 2 + 2 * (1 + offset / 4)) % 8); public uint CreateKeys(Mobile m) { var value = Key.RandomValue(); if (!IsAosRules) { var packKey = new Key(KeyType.Gold); var bankKey = new Key(KeyType.Gold); packKey.KeyValue = value; bankKey.KeyValue = value; packKey.LootType = LootType.Newbied; bankKey.LootType = LootType.Newbied; var box = m.BankBox; if (!box.TryDropItem(m, bankKey, false)) { bankKey.Delete(); } m.AddToBackpack(packKey); } return value; } public BaseDoor[] AddSouthDoors(int x, int y, int z) => AddSouthDoors(true, x, y, z, false); public BaseDoor[] AddSouthDoors(bool wood, int x, int y, int z, bool inv) { var westDoor = MakeDoor(wood, inv ? DoorFacing.WestCCW : DoorFacing.WestCW); var eastDoor = MakeDoor(wood, inv ? DoorFacing.EastCW : DoorFacing.EastCCW); westDoor.Link = eastDoor; eastDoor.Link = westDoor; AddDoor(westDoor, x, y, z); AddDoor(eastDoor, x + 1, y, z); return new[] { westDoor, eastDoor }; } public BaseDoor MakeDoor(bool wood, DoorFacing facing) { if (wood) { return new DarkWoodHouseDoor(facing); } return new MetalHouseDoor(facing); } public void AddDoor(BaseDoor door, int xoff, int yoff, int zoff) { door.MoveToWorld(new Point3D(xoff + X, yoff + Y, zoff + Z), Map); Doors.Add(door); } public void AddTrashBarrel(Mobile from) { if (!IsActive) { return; } for (var i = 0; i < Doors?.Count; ++i) { var door = Doors[i]; var p = door.Location; if (door.Open) { p = new Point3D(p.X - door.Offset.X, p.Y - door.Offset.Y, p.Z - door.Offset.Z); } if (from.Z + 16 >= p.Z && p.Z + 16 >= from.Z) { if (from.InRange(p, 1)) { from.SendLocalizedMessage(502120); // You cannot place a trash barrel near a door or near steps. return; } } } if (m_Trash?.Deleted != false) { m_Trash = new TrashBarrel { Movable = false }; m_Trash.MoveToWorld(from.Location, from.Map); /* You have a new trash barrel. * Three minutes after you put something in the barrel, the trash will be emptied. * Be forewarned, this is permanent! */ from.SendLocalizedMessage(502121); } else { from.SendLocalizedMessage(502117); // You already have a trash barrel! } } public void SetSign(int xoff, int yoff, int zoff) { Sign = new HouseSign(this); Sign.MoveToWorld(new Point3D(X + xoff, Y + yoff, Z + zoff), Map); } private void SetLockdown(Item i, bool locked, bool checkContains = false) { if (LockDowns == null) { return; } if (i is BaseAddonContainer) { i.Movable = false; } else { i.Movable = !locked; } i.IsLockedDown = locked; if (locked) { if (i is VendorRentalContract contract) { if (!VendorRentalContracts.Contains(contract)) { VendorRentalContracts.Add(contract); } } else { if (!checkContains || !LockDowns.Contains(i)) { LockDowns.Add(i); } } } else { if (i is VendorRentalContract contract) { VendorRentalContracts.Remove(contract); } LockDowns.Remove(i); } if (!locked) { i.SetLastMoved(); } if (i is Container && (!locked || !(i is BaseBoard or Aquarium or FishBowl))) { foreach (var c in i.Items) { SetLockdown(c, locked, checkContains); } } } public bool LockDown(Mobile m, Item item) => LockDown(m, item, true); public bool LockDown(Mobile m, Item item, bool checkIsInside) { if (!IsCoOwner(m) || !IsActive) { return false; } if (item is BaseAddonContainer || item.Movable && !HasSecureItem(item)) { var amt = 1 + item.TotalItems; var rootItem = item.RootParent as Item; var parentItem = item.Parent as Item; if (checkIsInside && item.RootParent is Mobile) { m.SendLocalizedMessage(1005525); // That is not in your house } else if (checkIsInside && !IsInside(item.GetWorldLocation(), item.ItemData.Height)) { m.SendLocalizedMessage(1005525); // That is not in your house } else if (Ethic.IsImbued(item)) { m.SendLocalizedMessage(1005377); // You cannot lock that down } else if (HasSecureItem(rootItem)) { m.SendLocalizedMessage(501737); // You need not lock down items in a secure container. } else if (parentItem != null && !HasLockedDownItem(parentItem)) { m.SendLocalizedMessage(501736); // You must lockdown the container first! } else if (item is not VendorRentalContract && (IsAosRules ? !CheckAosLockdowns(amt) || !CheckAosStorage(amt) : LockDownCount + amt > MaxLockDowns)) { m.SendLocalizedMessage(1005379); // That would exceed the maximum lock down limit for this house } else { SetLockdown(item, true); return true; } } else if (LockDowns.IndexOf(item) != -1) { m.LocalOverheadMessage(MessageType.Regular, 0x3E9, 1005526); // That is already locked down return true; } else if (item is HouseSign or Static) { m.LocalOverheadMessage(MessageType.Regular, 0x3E9, 1005526); // This is already locked down. } else { m.SendLocalizedMessage(1005377); // You cannot lock that down } return false; } public bool CheckTransferPosition(Mobile from, Mobile to) { var isValid = true; Item sign = Sign; var p = sign?.GetWorldLocation() ?? Point3D.Zero; if (from.Map != Map || to.Map != Map) { isValid = false; } else if (sign == null) { isValid = false; } else if (from.Map != sign.Map || to.Map != sign.Map) { isValid = false; } else if (IsInside(from)) { isValid = false; } else if (IsInside(to)) { isValid = false; } else if (!from.InRange(p, 2)) { isValid = false; } else if (!to.InRange(p, 2)) { isValid = false; } if (!isValid) { // In order to transfer the house, you and the recipient must both be outside the building and within two paces of the house sign. from.SendLocalizedMessage(1062067); } return isValid; } public void BeginConfirmTransfer(Mobile from, Mobile to) { if (Deleted || !from.CheckAlive() || !IsOwner(from)) { return; } if (NewVendorSystem && HasPersonalVendors) { // You cannot trade this house while you still have personal vendors inside. from.SendLocalizedMessage(1062467); } else if (DecayLevel == DecayLevel.DemolitionPending) { // This house has been marked for demolition, and it cannot be transferred. from.SendLocalizedMessage(1005321); } else if (from == to) { from.SendLocalizedMessage(1005330); // You cannot transfer a house to yourself, silly. } else if (HasAccountHouse(to)) { from.SendLocalizedMessage(501388); // You cannot transfer ownership to another house owner or co-owner! } else if (CheckTransferPosition(from, to)) { from.SendLocalizedMessage(1005326); // Please wait while the other player verifies the transfer. if (HasRentedVendors) { to.SendGump( new TradeHouseWarningGump(okay => ConfirmTransfer_Callback(to, okay, from)) ); } else { to.SendGump(new AcceptHouseTransferGump(from, this)); } } } private class TradeHouseWarningGump : StaticWarningGump { /* * You are about to be traded a home that has active vendor contracts. * While there are active vendor contracts in this house, you * cannot demolish OR customize the home. * When you accept this house, you also accept landlordship for every contract vendor in the house. */ public override int StaticLocalizedContent => 1062487; public override int Width => 420; public override int Height => 280; public TradeHouseWarningGump(Action callback) : base(callback) { } } private void ConfirmTransfer_Callback(Mobile transferee, bool ok, Mobile transferor) { if (!ok || Deleted || !transferor.CheckAlive() || !IsOwner(transferor)) { return; } if (CheckTransferPosition(transferor, transferee)) { transferee.SendGump(new AcceptHouseTransferGump(transferor, this)); } } public void EndConfirmTransfer(Mobile transferor, Mobile transferee) { if (Deleted || !transferor.CheckAlive() || !IsOwner(transferor)) { return; } if (NewVendorSystem && HasPersonalVendors) { // You cannot trade this house while you still have personal vendors inside. transferor.SendLocalizedMessage(1062467); } else if (DecayLevel == DecayLevel.DemolitionPending) { // This house has been marked for demolition, and it cannot be transferred. transferor.SendLocalizedMessage(1005321); } else if (transferor == transferee) { transferor.SendLocalizedMessage(1005330); // You cannot transfer a house to yourself, silly. } else if (HasAccountHouse(transferee)) { transferor.SendLocalizedMessage(501388); // You cannot transfer ownership to another house owner or co-owner! } else if (CheckTransferPosition(transferor, transferee)) { var fromState = transferor.NetState; var toState = transferee.NetState; if (fromState != null && toState != null) { if (transferor.HasTrade) { // You cannot trade a house while you have other trades pending. transferor.SendLocalizedMessage(1062071); } else if (transferee.HasTrade) { // You cannot trade a house while you have other trades pending. transferee.SendLocalizedMessage(1062071); } else if (!transferee.Alive) { // TODO: Check if the message is correct. transferor.SendLocalizedMessage(1062069); // You cannot transfer this house to that person. } else { fromState.AddTrade(toState).DropItem(new TransferItem(this)); } } } } public void Release(Mobile m, Item item) { if (!IsCoOwner(m) || !IsActive) { return; } if (HasLockedDownItem(item)) { item.PublicOverheadMessage(MessageType.Label, 0x3B2, 501657); // [no longer locked down] SetLockdown(item, false); // TidyItemList( m_LockDowns ); (item as RewardBrazier)?.TurnOff(); } else if (HasSecureItem(item)) { ReleaseSecure(m, item); } else { m.LocalOverheadMessage(MessageType.Regular, 0x3E9, 1010416); // This is not locked down or secured. } } public void AddSecure(Mobile m, Item item) { if (Secures == null || !IsOwner(m) || !IsActive) { return; } if (!IsInside(item)) { m.SendLocalizedMessage(1005525); // That is not in your house } else if (HasLockedDownItem(item)) { m.SendLocalizedMessage(1010550); // This is already locked down and cannot be secured. } else if (item is not Container) { LockDown(m, item); } else { SecureInfo info = null; for (var i = 0; info == null && i < Secures.Count; ++i) { if (Secures[i].Item == item) { info = Secures[i]; } } if (info != null) { m.SendGump(new SetSecureLevelGump(info, this)); } else if (item.Parent != null) { m.SendLocalizedMessage(1010423); // You cannot secure this, place it on the ground first. } // Mondain's Legacy mod else if (item is not BaseAddonContainer && !item.Movable) { m.SendLocalizedMessage(1010424); // You cannot secure this. } else if (!IsAosRules && SecureCount >= MaxSecures) { // The maximum number of secure items has been reached : m.SendLocalizedMessage(1008142, true, MaxSecures.ToString()); } else if (IsAosRules ? !CheckAosLockdowns(1) : LockDownCount + 125 >= MaxLockDowns) { m.SendLocalizedMessage(1005379); // That would exceed the maximum lock down limit for this house } else if (IsAosRules && !CheckAosStorage(item.TotalItems)) { m.SendLocalizedMessage(1061839); // This action would exceed the secure storage limit of the house. } else { info = new SecureInfo((Container)item, SecureLevel.Owner); item.IsLockedDown = false; item.IsSecure = true; Secures.Add(info); LockDowns.Remove(item); item.Movable = false; m.SendGump(new SetSecureLevelGump(info, this)); } } } public virtual bool IsCombatRestricted(Mobile m) { if (m?.Player != true || m.AccessLevel >= AccessLevel.GameMaster || !IsAosRules || m_Owner?.AccessLevel >= AccessLevel.GameMaster) { return false; } for (var i = 0; i < m.Aggressed.Count; ++i) { var info = m.Aggressed[i]; if (info.Defender.Player && info.Defender.Alive && Core.Now - info.LastCombatTime < HouseRegion.CombatHeatDelay && (m.Guild is not Guild attackerGuild || info.Defender.Guild is not Guild defenderGuild || defenderGuild != attackerGuild && !defenderGuild.IsEnemy(attackerGuild))) { return true; } } return false; } public bool HasSecureAccess(Mobile m, SecureLevel level) { if (m.AccessLevel >= AccessLevel.GameMaster) { return true; } if (IsCombatRestricted(m)) { return false; } return level switch { SecureLevel.Owner => IsOwner(m), SecureLevel.CoOwners => IsCoOwner(m), SecureLevel.Friends => IsFriend(m), SecureLevel.Anyone => true, SecureLevel.Guild => IsGuildMember(m), _ => false }; } public void ReleaseSecure(Mobile m, Item item) { if (Secures == null || !IsOwner(m) || item is StrongBox || !IsActive) { return; } for (var i = 0; i < Secures.Count; ++i) { var info = Secures[i]; if (info.Item == item && HasSecureAccess(m, info.Level)) { item.IsLockedDown = false; item.IsSecure = false; if (item is BaseAddonContainer) { item.Movable = false; } else { item.Movable = true; } item.SetLastMoved(); item.PublicOverheadMessage(MessageType.Label, 0x3B2, 501656); // [no longer secure] Secures.RemoveAt(i); return; } } m.SendLocalizedMessage(501717); // This isn't secure... } public void AddStrongBox(Mobile from) { if (!IsCoOwner(from) || !IsActive) { return; } if (from == Owner) { from.SendLocalizedMessage(502109); // Owners don't get a strong box return; } if (IsAosRules ? !CheckAosLockdowns(1) : LockDownCount + 1 > MaxLockDowns) { from.SendLocalizedMessage(1005379); // That would exceed the maximum lock down limit for this house return; } foreach (var info in Secures) { var c = info.Item; if (!c.Deleted && c is StrongBox box && box.Owner == from) { from.SendLocalizedMessage(502112); // You already have a strong box return; } } for (var i = 0; i < Doors?.Count; ++i) { var door = Doors[i]; var p = door.Location; if (door.Open) { p = new Point3D(p.X - door.Offset.X, p.Y - door.Offset.Y, p.Z - door.Offset.Z); } if (from.Z + 16 >= p.Z && p.Z + 16 >= from.Z) { if (from.InRange(p, 1)) { from.SendLocalizedMessage(502113); // You cannot place a strongbox near a door or near steps. return; } } } var sb = new StrongBox(from, this) { Movable = false, IsLockedDown = false, IsSecure = true }; Secures.Add(new SecureInfo(sb, SecureLevel.CoOwners)); sb.MoveToWorld(from.Location, from.Map); } public void Kick(Mobile from, Mobile targ) { if (!IsFriend(from) || Friends == null) { return; } if (targ.AccessLevel > AccessLevel.Player && from.AccessLevel <= targ.AccessLevel) { from.SendLocalizedMessage(501346); // Uh oh...a bigger boot may be required! } else if (IsFriend(targ) && !Core.ML) { from.SendLocalizedMessage(501348); // You cannot eject a friend of the house! } else if (targ is PlayerVendor) { from.SendLocalizedMessage(501351); // You cannot eject a vendor. } else if (!IsInside(targ)) { from.SendLocalizedMessage(501352); // You may not eject someone who is not in your house! } else if (targ is BaseCreature creature && creature.NoHouseRestrictions) { from.SendLocalizedMessage(501347); // You cannot eject that from the house! } else { targ.MoveToWorld(BanLocation, Map); from.SendLocalizedMessage(1042840, targ.Name); // ~1_PLAYER NAME~ has been ejected from this house. /* You have been ejected from this house. * If you persist in entering, you may be banned from the house. */ targ.SendLocalizedMessage(501341); } } public void RemoveAccess(Mobile from, Mobile targ) { if (!IsFriend(from) || Access == null) { return; } if (Access.Remove(targ)) { if (!HasAccess(targ) && IsInside(targ)) { targ.Location = BanLocation; targ.SendLocalizedMessage(1060734); // Your access to this house has been revoked. } from.SendLocalizedMessage(1050051); // The invitation has been revoked. } } public void RemoveBan(Mobile from, Mobile targ) { if (!IsCoOwner(from) || Bans == null) { return; } if (Bans.Contains(targ)) { Bans.Remove(targ); from.SendLocalizedMessage(501297); // The ban is lifted. } } public void Ban(Mobile from, Mobile targ) { if (!IsFriend(from) || Bans == null) { return; } if (targ.AccessLevel > AccessLevel.Player && from.AccessLevel <= targ.AccessLevel) { from.SendLocalizedMessage(501354); // Uh oh...a bigger boot may be required. } else if (IsFriend(targ)) { from.SendLocalizedMessage(501348); // You cannot eject a friend of the house! } else if (targ is PlayerVendor) { from.SendLocalizedMessage(501351); // You cannot eject a vendor. } else if (Bans.Count >= MaxBans) { from.SendLocalizedMessage(501355); // The ban limit for this house has been reached! } else if (IsBanned(targ)) { from.SendLocalizedMessage(501356); // This person is already banned! } else if (!IsInside(targ)) { from.SendLocalizedMessage(501352); // You may not eject someone who is not in your house! } else if (!Public && IsAosRules) { // You cannot ban someone from a private house. Revoke their access instead. from.SendLocalizedMessage(1062521); } else if (targ is BaseCreature bc && bc.NoHouseRestrictions) { from.SendLocalizedMessage(1062040); // You cannot ban that. } else { Bans.Add(targ); from.SendLocalizedMessage(1042839, targ.Name); // ~1_PLAYER_NAME~ has been banned from this house. targ.SendLocalizedMessage(501340); // You have been banned from this house. targ.MoveToWorld(BanLocation, Map); } } public void GrantAccess(Mobile from, Mobile targ) { if (!IsFriend(from) || Access == null) { return; } if (HasAccess(targ)) { from.SendLocalizedMessage(1060729); // That person already has access to this house. } else if (!targ.Player) { from.SendLocalizedMessage(1060712); // That is not a player. } else if (IsBanned(targ)) { from.SendLocalizedMessage(501367); // This person is banned! Unban them first. } else { Access.Add(targ); targ.SendLocalizedMessage(1060735); // You have been granted access to this house. } } public void AddCoOwner(Mobile from, Mobile targ) { if (!IsOwner(from) || CoOwners == null || Friends == null) { return; } if (IsOwner(targ)) { from.SendLocalizedMessage(501360); // This person is already the house owner! } else if (Friends.Contains(targ)) { from.SendLocalizedMessage(501361); // This person is a friend of the house. Remove them first. } else if (!targ.Player) { from.SendLocalizedMessage(501362); // That can't be a co-owner of the house. } else if (!Core.AOS && HasAccountHouse(targ)) { from.SendLocalizedMessage(501364); // That person is already a house owner. } else if (IsBanned(targ)) { from.SendLocalizedMessage(501367); // This person is banned! Unban them first. } else if (CoOwners.Count >= MaxCoOwners) { from.SendLocalizedMessage(501368); // Your co-owner list is full! } else if (CoOwners.Contains(targ)) { from.SendLocalizedMessage(501369); // This person is already on your co-owner list! } else { CoOwners.Add(targ); targ.Delta(MobileDelta.Noto); targ.SendLocalizedMessage(501343); // You have been made a co-owner of this house. } } public void RemoveCoOwner(Mobile from, Mobile targ) { if (!IsOwner(from) || CoOwners == null) { return; } if (CoOwners.Remove(targ)) { targ.Delta(MobileDelta.Noto); from.SendLocalizedMessage(501299); // Co-owner removed from list. targ.SendLocalizedMessage(501300); // You have been removed as a house co-owner. foreach (var info in Secures) { var c = info.Item; if (c is StrongBox box && box.Owner == targ) { box.IsLockedDown = false; box.IsSecure = false; Secures.Remove(info); box.Destroy(); break; } } } } public void AddFriend(Mobile from, Mobile targ) { if (!IsCoOwner(from) || Friends == null || CoOwners == null) { return; } if (IsOwner(targ)) { from.SendLocalizedMessage(501370); // This person is already an owner of the house! } else if (CoOwners.Contains(targ)) { from.SendLocalizedMessage(501369); // This person is already on your co-owner list! } else if (!targ.Player) { from.SendLocalizedMessage(501371); // That can't be a friend of the house. } else if (IsBanned(targ)) { from.SendLocalizedMessage(501374); // This person is banned! Unban them first. } else if (Friends.Count >= MaxFriends) { from.SendLocalizedMessage(501375); // Your friends list is full! } else if (Friends.Contains(targ)) { from.SendLocalizedMessage(501376); // This person is already on your friends list! } else { Friends.Add(targ); targ.Delta(MobileDelta.Noto); targ.SendLocalizedMessage(501337); // You have been made a friend of this house. } } public void RemoveFriend(Mobile from, Mobile targ) { if (!IsCoOwner(from) || Friends == null) { return; } if (Friends.Remove(targ)) { targ.Delta(MobileDelta.Noto); from.SendLocalizedMessage(501298); // Friend removed from list. targ.SendLocalizedMessage(1060751); // You are no longer a friend of this house. } } public override void Serialize(IGenericWriter writer) { base.Serialize(writer); writer.Write(15); // version if (!DynamicDecay.Enabled) { writer.Write(-1); } else { writer.Write((int)m_CurrentStage); writer.Write(NextDecayStage); } writer.Write(m_RelativeBanLocation); VendorRentalContracts.Tidy(); writer.Write(VendorRentalContracts); InternalizedVendors.Tidy(); writer.Write(InternalizedVendors); writer.WriteEncodedInt(RelocatedEntities.Count); foreach (var relEntity in RelocatedEntities) { writer.Write(relEntity.RelativeLocation); if (relEntity.Entity.Deleted) { writer.Write(Serial.MinusOne); } else { writer.Write(relEntity.Entity.Serial); } } writer.WriteEncodedInt(VendorInventories.Count); for (var i = 0; i < VendorInventories.Count; i++) { var inventory = VendorInventories[i]; inventory.Serialize(writer); } writer.Write(LastRefreshed); writer.Write(RestrictDecay); writer.Write(Visits); writer.Write(Price); writer.Write(Access); writer.Write(BuiltOn); writer.Write(LastTraded); Addons.Tidy(); writer.Write(Addons); writer.Write(Secures.Count); for (var i = 0; i < Secures.Count; ++i) { Secures[i].Serialize(writer); } writer.Write(m_Public); // writer.Write( BanLocation ); writer.Write(m_Owner); // Version 5 no longer serializes region coords CoOwners.Tidy(); writer.Write(CoOwners); Friends.Tidy(); writer.Write(Friends); Bans.Tidy(); writer.Write(Bans); writer.Write(Sign); writer.Write(m_Trash); Doors.Tidy(); writer.Write(Doors); LockDowns.Tidy(); writer.Write(LockDowns); // writer.WriteItemList( m_Secures, true ); writer.Write(MaxLockDowns); writer.Write(MaxSecures); // Items in locked down containers that aren't locked down themselves must decay! for (var i = 0; i < LockDowns.Count; ++i) { var item = LockDowns[i]; if (item is Container cont && !(cont is BaseBoard or Aquarium or FishBowl)) { var children = cont.Items; for (var j = 0; j < children.Count; ++j) { var child = children[j]; if (child.Decays && !child.IsLockedDown && !child.IsSecure && child.LastMoved + child.DecayTime <= Core.Now) { Timer.StartTimer(child.Delete); } } } } } public override void Deserialize(IGenericReader reader) { base.Deserialize(reader); var version = reader.ReadInt(); int count; var loadedDynamicDecay = false; switch (version) { case 15: { var stage = reader.ReadInt(); if (stage != -1) { m_CurrentStage = (DecayLevel)stage; NextDecayStage = reader.ReadDateTime(); loadedDynamicDecay = true; } goto case 14; } case 14: { m_RelativeBanLocation = reader.ReadPoint3D(); goto case 13; } case 13: // removed ban location serialization case 12: { VendorRentalContracts = reader.ReadEntityList(); InternalizedVendors = reader.ReadEntityList(); var relocatedCount = reader.ReadEncodedInt(); for (var i = 0; i < relocatedCount; i++) { var relLocation = reader.ReadPoint3D(); var entity = reader.ReadEntity(); if (entity != null) { RelocatedEntities.Add(new RelocatedEntity(entity, relLocation)); } } var inventoryCount = reader.ReadEncodedInt(); for (var i = 0; i < inventoryCount; i++) { var inventory = new VendorInventory(this, reader); VendorInventories.Add(inventory); } goto case 11; } case 11: { LastRefreshed = reader.ReadDateTime(); RestrictDecay = reader.ReadBool(); goto case 10; } case 10: // just a signal for updates case 9: { Visits = reader.ReadInt(); goto case 8; } case 8: { Price = reader.ReadInt(); goto case 7; } case 7: { Access = reader.ReadEntityList(); goto case 6; } case 6: { BuiltOn = reader.ReadDateTime(); LastTraded = reader.ReadDateTime(); goto case 5; } case 5: // just removed fields case 4: { Addons = reader.ReadEntityList(); goto case 3; } case 3: { count = reader.ReadInt(); Secures = new List(count); for (var i = 0; i < count; ++i) { var info = new SecureInfo(reader); if (info.Item != null) { info.Item.IsSecure = true; Secures.Add(info); } } goto case 2; } case 2: { m_Public = reader.ReadBool(); goto case 1; } case 1: { if (version < 13) { reader.ReadPoint3D(); // house ban location } goto case 0; } case 0: { if (version < 14) { m_RelativeBanLocation = BaseBanLocation; } if (version < 12) { VendorRentalContracts = new List(); InternalizedVendors = new List(); } if (version < 4) { Addons = new List(); } if (version < 7) { Access = new List(); } if (version < 8) { Price = DefaultPrice; } m_Owner = reader.ReadEntity(); if (version < 5) { count = reader.ReadInt(); for (var i = 0; i < count; i++) { reader.ReadRect2D(); } } UpdateRegion(); CoOwners = reader.ReadEntityList(); Friends = reader.ReadEntityList(); Bans = reader.ReadEntityList(); Sign = reader.ReadEntity(); m_Trash = reader.ReadEntity(); Doors = reader.ReadEntityList(); LockDowns = reader.ReadEntityList(); for (var i = 0; i < LockDowns.Count; ++i) { LockDowns[i].IsLockedDown = true; } for (var i = 0; i < VendorRentalContracts.Count; ++i) { VendorRentalContracts[i].IsLockedDown = true; } if (version < 3) { var items = reader.ReadEntityList(); Secures = new List(items.Count); for (var i = 0; i < items.Count; ++i) { if (items[i] is Container c) { c.IsSecure = true; Secures.Add(new SecureInfo(c, SecureLevel.CoOwners)); } } } MaxLockDowns = reader.ReadInt(); MaxSecures = reader.ReadInt(); if ((Map == null || Map == Map.Internal) && Location == Point3D.Zero) { Delete(); } if (m_Owner != null) { if (!m_Table.TryGetValue(m_Owner, out var list)) { m_Table[m_Owner] = list = new List(); } list.Add(this); } break; } } if (version <= 1) { ChangeSignType(0xBD2); // private house, plain brass sign } if (version < 10) { Timer.StartTimer(FixLockdowns_Sandbox); } if (version < 11) { LastRefreshed = Core.Now + TimeSpan.FromHours(24 * Utility.RandomDouble()); } if (DynamicDecay.Enabled && !loadedDynamicDecay) { var old = GetOldDecayLevel(); if (old == DecayLevel.DemolitionPending) { old = DecayLevel.Collapsed; } SetDynamicDecay(old); } if (!CheckDecay()) { if (RelocatedEntities.Count > 0) { Timer.StartTimer(RestoreRelocatedEntities); } if (m_Owner == null && Friends.Count == 0 && CoOwners.Count == 0) { Timer.StartTimer(TimeSpan.FromSeconds(10.0), Delete); } } } private void FixLockdowns_Sandbox() { if (LockDowns?.Count > 0) { using var queue = PooledRefQueue.Create(); foreach (var item in LockDowns) { if (item is Container) { queue.Enqueue(item); } } while (queue.Count > 0) { SetLockdown(queue.Dequeue(), true, true); } } } [OnEvent(nameof(PlayerMobile.PlayerDeletedEvent))] public static void HandleDeletion(Mobile mob) { var houses = GetHouses(mob); if (houses.Count == 0) { return; } var acct = mob.Account as Account; Mobile trans = null; if (acct != null) { for (var i = 0; i < acct.Length; ++i) { if (acct[i] != null && acct[i] != mob) { trans = acct[i]; } } } for (var i = 0; i < houses.Count; ++i) { var house = houses[i]; if (trans == null && house.CoOwners.Count == 0) { Timer.StartTimer(house.Delete); } else { house.Owner = trans; } } } public int GetLockdowns() { var count = 0; if (LockDowns != null) { for (var i = 0; i < LockDowns.Count; ++i) { if (LockDowns[i] != null) { var item = LockDowns[i]; if (item is not Container) { count += item.TotalItems; } } count++; } } return count; } public override void OnDelete() { RestoreRelocatedEntities(); // Invalidate spawn position cache for affected sectors before deletion if (Map != null && Map != Map.Internal) { var mcl = Components; var bounds = new Rectangle2D( X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height ); SectorSpawnCacheManager.InvalidateSectors(Map, bounds); } new FixColumnTimer(this).Start(); base.OnDelete(); } public override void OnAfterDelete() { base.OnAfterDelete(); if (m_Owner != null && m_Table.TryGetValue(m_Owner, out var list) && list.Remove(this) && list.Count == 0) { m_Table.Remove(m_Owner); } if (m_Region != null) { m_Region.Unregister(); m_Region = null; } Sign?.Delete(); m_Trash?.Delete(); if (Doors != null) { for (var i = 0; i < Doors.Count; ++i) { Doors[i]?.Delete(); } Doors.Clear(); } if (LockDowns != null) { for (var i = 0; i < LockDowns.Count; ++i) { var item = LockDowns[i]; if (item != null) { item.IsLockedDown = false; item.IsSecure = false; item.Movable = true; item.SetLastMoved(); } } LockDowns.Clear(); } if (VendorRentalContracts != null) { for (var i = 0; i < VendorRentalContracts.Count; ++i) { Item item = VendorRentalContracts[i]; if (item != null) { item.IsLockedDown = false; item.IsSecure = false; item.Movable = true; item.SetLastMoved(); } } VendorRentalContracts.Clear(); } if (Secures != null) { for (var i = 0; i < Secures.Count; ++i) { var info = Secures[i]; if (info.Item is StrongBox) { info.Item.Destroy(); } else { info.Item.IsLockedDown = false; info.Item.IsSecure = false; info.Item.Movable = true; info.Item.SetLastMoved(); } } Secures.Clear(); } if (Addons != null) { for (var i = 0; i < Addons.Count; ++i) { var item = Addons[i]; if (item != null) { if (!item.Deleted && item is IAddon addon) { var deed = addon.Deed; var retainDeedHue = false; // if the items aren't hued but the deed itself is var hue = 0; // There are things that are IAddon which aren't BaseAddon if (addon is BaseAddon ba && ba.RetainDeedHue) { retainDeedHue = true; for (var j = 0; hue == 0 && j < ba.Components.Count; ++j) { var c = ba.Components[j]; if (c.Hue != 0) { hue = c.Hue; } } } if (deed != null) { if (retainDeedHue) { deed.Hue = hue; } deed.MoveToWorld(item.Location, item.Map); } } item.Delete(); } } Addons.Clear(); } if (VendorInventories.Count > 0) { using var inventories = PooledRefList.Create(VendorInventories.Count); inventories.AddRange(VendorInventories); foreach (var inventory in inventories) { inventory.Delete(); } } MovingCrate?.Delete(); KillVendors(); AllHouses.Remove(this); } public static bool HasHouse(Mobile m) { if (m == null || !m_Table.TryGetValue(m, out var list)) { return false; } foreach (var h in list) { if (!h.Deleted) { return true; } } return false; } public static bool HasAccountHouse(Mobile m) { if (m.Account is not Account a) { return false; } for (var i = 0; i < a.Length; ++i) { if (a[i] != null && HasHouse(a[i])) { return true; } } return false; } public bool IsOwner(Mobile m) => m != null && (m == m_Owner || m.AccessLevel >= AccessLevel.GameMaster || IsAosRules && AccountHandler.CheckAccount(m, m_Owner)); public bool IsCoOwner(Mobile m) => m != null && CoOwners != null && (IsOwner(m) || CoOwners.Contains(m) || !IsAosRules && AccountHandler.CheckAccount(m, m_Owner)); public bool IsGuildMember(Mobile m) => m != null && Owner?.Guild != null && m.Guild == Owner.Guild; public void RemoveKeys(Mobile m) { if (Doors != null) { uint keyValue = 0; for (var i = 0; keyValue == 0 && i < Doors.Count; ++i) { keyValue = Doors[i].KeyValue; } Key.RemoveKeys(m, keyValue); } } public void ChangeLocks(Mobile m) { var keyValue = CreateKeys(m); if (Doors != null) { for (var i = 0; i < Doors.Count; ++i) { Doors[i].KeyValue = keyValue; } } } public void RemoveLocks() { if (Doors != null) { for (var i = 0; i < Doors.Count; ++i) { var door = Doors[i]; door.KeyValue = 0; door.Locked = false; } } } public virtual HouseDeed GetDeed() => null; public bool IsFriend(Mobile m) => m != null && Friends != null && (IsCoOwner(m) || Friends.Contains(m)); public bool IsBanned(Mobile m) { if (m == null || m == Owner || m.AccessLevel > AccessLevel.Player || Bans == null) { return false; } var theirAccount = m.Account as Account; for (var i = 0; i < Bans.Count; ++i) { var c = Bans[i]; if (c == m) { return true; } if (c.Account is Account bannedAccount && bannedAccount == theirAccount) { return true; } } return false; } public bool HasAccess(Mobile m) { if (m == null) { return false; } if (m.AccessLevel > AccessLevel.Player || IsFriend(m) || Access?.Contains(m) == true) { return true; } if (m is not BaseCreature bc) { return false; } if (bc.NoHouseRestrictions) { return true; } if (!(bc.Controlled || bc.Summoned)) { return false; } m = bc.ControlMaster ?? bc.SummonMaster; return m != null && (m.AccessLevel > AccessLevel.Player || IsFriend(m) || Access?.Contains(m) == true); } public bool HasLockedDownItem(Item check) => LockDowns?.Contains(check) == true || check is VendorRentalContract contract && VendorRentalContracts.Contains(contract); public bool HasSecureItem(Item item) { if (item == null) { return false; } for (var i = 0; i < Secures?.Count; ++i) { if (Secures[i].Item == item) { return true; } } return false; } public virtual Guildstone FindGuildstone() { var map = Map; if (map == null) { return null; } var mcl = Components; var bounds = new Rectangle2D(X + mcl.Min.X, Y + mcl.Min.Y, mcl.Width, mcl.Height); foreach (var gs in map.GetItemsInBounds(bounds)) { return gs; } return null; } public void ResetDynamicDecay() { m_CurrentStage = DecayLevel.Ageless; NextDecayStage = DateTime.MinValue; } public void SetDynamicDecay(DecayLevel level) { m_CurrentStage = level; if (DynamicDecay.Decays(level)) { NextDecayStage = Core.Now + DynamicDecay.GetRandomDuration(level); } else { NextDecayStage = DateTime.MinValue; } } [SerializationGenerator(0)] private partial class TransferItem : Item { private readonly BaseHouse _house; public TransferItem(BaseHouse house) : base(0x14F0) { _house = house; Hue = 0x480; Movable = false; } public override bool SkipSerialization => true; public override string DefaultName => "a house transfer contract"; public override void GetProperties(IPropertyList list) { base.GetProperties(list); var houseName = _house == null ? "an unnamed house" : _house.Sign.GetName(); var owner = _house?.Owner?.Name ?? "nobody"; int xLong = 0, yLat = 0, xMins = 0, yMins = 0; bool xEast = false, ySouth = false; var valid = _house != null && Sextant.Format( _house.Location, _house.Map, ref xLong, ref yLat, ref xMins, ref yMins, ref xEast, ref ySouth ); list.Add(1061112, houseName.FixHtml()); // House Name: ~1_val~ list.Add(1061113, owner); // Owner: ~1_val~ if (valid) { // Location: ~1_val~ list.Add(1061114, $"{yLat}° {yMins}'{(ySouth ? "S" : "N")}, {xLong}° {xMins}'{(xEast ? "E" : "W")}"); } else { list.Add(1061114, "unknown"); // Location: ~1_val~ } } public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) { if (!base.AllowSecureTrade(from, to, newOwner, accepted)) { return false; } if (!accepted) { return true; } if (Deleted || _house?.Deleted != false || !_house.IsOwner(from) || !from.CheckAlive() || !to.CheckAlive()) { return false; } if (HasAccountHouse(to)) { from.SendLocalizedMessage(501388); // You cannot transfer ownership to another house owner or co-owner! return false; } return _house.CheckTransferPosition(from, to); } public override void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) { if (Deleted) { return; } Delete(); if (_house?.Deleted != false || !_house.IsOwner(from) || !from.CheckAlive() || !to.CheckAlive()) { return; } if (!accepted) { return; } from.SendLocalizedMessage(501338); // You have transferred ownership of the house. /* You are now the owner of this house. * The house's co-owner, friend, ban, and access lists have been cleared. * You should double-check the security settings on any doors and teleporters in the house. */ to.SendLocalizedMessage(501339); _house.RemoveKeys(from); _house.Owner = to; _house.Bans.Clear(); _house.Friends.Clear(); _house.CoOwners.Clear(); _house.ChangeLocks(to); _house.LastTraded = Core.Now; } } private class FixColumnTimer : Timer { private readonly int m_EndX; private readonly int m_EndY; private readonly Map m_Map; private readonly int m_StartX; private readonly int m_StartY; public FixColumnTimer(BaseMulti multi) : base(TimeSpan.Zero) { m_Map = multi.Map; var mcl = multi.Components; m_StartX = multi.X + mcl.Min.X; m_StartY = multi.Y + mcl.Min.Y; m_EndX = multi.X + mcl.Max.X; m_EndY = multi.Y + mcl.Max.Y; } protected override void OnTick() { if (m_Map == null) { return; } for (var x = m_StartX; x <= m_EndX; ++x) { for (var y = m_StartY; y <= m_EndY; ++y) { m_Map.FixColumn(x, y); } } } } } public enum DecayType { Ageless, AutoRefresh, ManualRefresh, Condemned } public enum DecayLevel { Ageless, LikeNew, Slightly, Somewhat, Fairly, Greatly, IDOC, Collapsed, DemolitionPending } public enum SecureAccessResult { Insecure, Accessible, Inaccessible } public enum SecureLevel { Owner, CoOwners, Friends, Anyone, Guild } public class SecureInfo : ISecurable { public SecureInfo(Container item, SecureLevel level) { Item = item; Level = level; } public SecureInfo(IGenericReader reader) { Item = reader.ReadEntity(); Level = (SecureLevel)reader.ReadByte(); } public Container Item { get; } public SecureLevel Level { get; set; } public void Serialize(IGenericWriter writer) { writer.Write(Item); writer.Write((byte)Level); } } public class RelocatedEntity { public RelocatedEntity(IEntity entity, Point3D relativeLocation) { Entity = entity; RelativeLocation = relativeLocation; } public IEntity Entity { get; } public Point3D RelativeLocation { get; } } public class LockdownTarget : Target { private readonly BaseHouse m_House; private readonly bool m_Release; public LockdownTarget(bool release, BaseHouse house) : base(12, false, TargetFlags.None) { CheckLOS = false; m_Release = release; m_House = house; } protected override void OnTargetNotAccessible(Mobile from, object targeted) { OnTarget(from, targeted); } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsCoOwner(from)) { return; } if (targeted is Item item) { if (m_Release) { if (item is AddonContainerComponent component) { if (component.Addon != null) { m_House.Release(from, component.Addon); } } else { m_House.Release(from, item); } } else { if (item is VendorRentalContract) { // You must double click the contract in your pack to lock it down. from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1062392); from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 501732); // I cannot lock this down! } else if (item is AddonComponent) { from.LocalOverheadMessage(MessageType.Regular, 0x3E9, 501727); // You cannot lock that down! from.LocalOverheadMessage(MessageType.Regular, 0x3E9, 501732); // I cannot lock this down! } else { if (item is AddonContainerComponent component) { if (component.Addon != null) { m_House.LockDown(from, component.Addon); } } else { m_House.LockDown(from, item); } } } } else if (targeted is StaticTarget) { } else { from.SendLocalizedMessage(1005377); // You cannot lock that down } } } public class SecureTarget : Target { private readonly BaseHouse m_House; private readonly bool m_Release; public SecureTarget(bool release, BaseHouse house) : base(12, false, TargetFlags.None) { CheckLOS = false; m_Release = release; m_House = house; } protected override void OnTargetNotAccessible(Mobile from, object targeted) { OnTarget(from, targeted); } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsCoOwner(from)) { return; } if (targeted is Item item) { if (m_Release) { if (item is AddonContainerComponent component) { if (component.Addon != null) { m_House.ReleaseSecure(from, component.Addon); } } else { m_House.ReleaseSecure(from, item); } } else { if (item is VendorRentalContract) { // You must double click the contract in your pack to lock it down. from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1062392); from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 501732); // I cannot lock this down! } else { if (item is AddonContainerComponent component) { if (component.Addon != null) { m_House.AddSecure(from, component.Addon); } } else { m_House.AddSecure(from, item); } } } } else { from.SendLocalizedMessage(1010424); // You cannot secure this } } } public class HouseKickTarget : Target { private readonly BaseHouse m_House; public HouseKickTarget(BaseHouse house) : base(-1, false, TargetFlags.None) { CheckLOS = false; m_House = house; } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsFriend(from)) { return; } if (targeted is Mobile mobile) { m_House.Kick(from, mobile); } else { from.SendLocalizedMessage(501347); // You cannot eject that from the house! } } } public class HouseBanTarget : Target { private readonly bool m_Banning; private readonly BaseHouse m_House; public HouseBanTarget(bool ban, BaseHouse house) : base(-1, false, TargetFlags.None) { CheckLOS = false; m_House = house; m_Banning = ban; } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsFriend(from)) { return; } if (targeted is Mobile mobile) { if (m_Banning) { m_House.Ban(from, mobile); } else { m_House.RemoveBan(from, mobile); } } else { from.SendLocalizedMessage(501347); // You cannot eject that from the house! } } } public class HouseAccessTarget : Target { private readonly BaseHouse m_House; public HouseAccessTarget(BaseHouse house) : base(-1, false, TargetFlags.None) { CheckLOS = false; m_House = house; } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsFriend(from)) { return; } if (targeted is Mobile mobile) { m_House.GrantAccess(from, mobile); } else { from.SendLocalizedMessage(1060712); // That is not a player. } } } public class CoOwnerTarget : Target { private readonly bool m_Add; private readonly BaseHouse m_House; public CoOwnerTarget(bool add, BaseHouse house) : base(12, false, TargetFlags.None) { CheckLOS = false; m_House = house; m_Add = add; } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsOwner(from)) { return; } if (targeted is Mobile mobile) { if (m_Add) { m_House.AddCoOwner(from, mobile); } else { m_House.RemoveCoOwner(from, mobile); } } else { from.SendLocalizedMessage(501362); // That can't be a coowner } } } public class HouseFriendTarget : Target { private readonly bool m_Add; private readonly BaseHouse m_House; public HouseFriendTarget(bool add, BaseHouse house) : base(12, false, TargetFlags.None) { CheckLOS = false; m_House = house; m_Add = add; } protected override void OnTarget(Mobile from, object targeted) { if (!from.Alive || m_House.Deleted || !m_House.IsCoOwner(from)) { return; } if (targeted is Mobile mobile) { if (m_Add) { m_House.AddFriend(from, mobile); } else { m_House.RemoveFriend(from, mobile); } } else { from.SendLocalizedMessage(501371); // That can't be a friend } } } public class HouseOwnerTarget : Target { private readonly BaseHouse m_House; public HouseOwnerTarget(BaseHouse house) : base(12, false, TargetFlags.None) { CheckLOS = false; m_House = house; } protected override void OnTarget(Mobile from, object targeted) { if (targeted is Mobile { Player: true } mobile) { m_House.BeginConfirmTransfer(from, mobile); } else { from.SendLocalizedMessage(501384); // Only a player can own a house! } } } public class SetSecureLevelEntry : ContextMenuEntry { public SetSecureLevelEntry() : base(6203, 6) { } public static ISecurable GetSecurable(Mobile from, Item item) { var house = BaseHouse.FindHouseAt(item); if (house?.IsOwner(from) != true || !house.IsAosRules) { return null; } if (item is ISecurable securable) { var isOwned = item is BaseDoor door && house.Doors.Contains(door); if (!isOwned) { isOwned = house is HouseFoundation foundation && foundation.IsFixture(item); } if (!isOwned) { isOwned = house.HasLockedDownItem(item); } if (isOwned) { return securable; } } else { var list = house.Secures; for (var i = 0; i < list?.Count; ++i) { var si = list[i]; if (si.Item == item) { return si; } } } return null; } public static void AddTo(Mobile from, Item item, ref PooledRefList list) { var sec = GetSecurable(from, item); if (sec != null) { list.Add(new SetSecureLevelEntry()); } } public override void OnClick(Mobile from, IEntity target) { if (target is not Item item) { return; } var sec = GetSecurable(from, item); if (sec != null) { from.SendGump(new SetSecureLevelGump(sec, BaseHouse.FindHouseAt(item))); } } } public class TempNoHousingRegion : BaseRegion { private readonly Mobile m_RegionOwner; public TempNoHousingRegion(BaseHouse house, Mobile regionowner) : base(null, house.Map, DefaultPriority, house.Region.Area) { Register(); m_RegionOwner = regionowner; Timer.StartTimer(house.RestrictedPlacingTime, Unregister); } public override bool AllowHousing(Mobile from, Point3D p) => from == m_RegionOwner || AccountHandler.CheckAccount(from, m_RegionOwner); } }