using System.Collections.Generic; using Server.Collections; using Server.Regions; using Server.Spells; namespace Server.Multis { public enum HousePlacementResult { Valid, BadRegion, BadLand, BadStatic, BadItem, NoSurface, BadRegionHidden, BadRegionTemp, InvalidCastleKeep, BadRegionRaffle } public static class HousePlacement { private const int YardSize = 5; // Any land tile which matches one of these ID numbers is considered a road and cannot be placed over. private static readonly int[] m_RoadIDs = { 0x0071, 0x0078, 0x00E8, 0x00EB, 0x07AE, 0x07B1, 0x3FF4, 0x3FF4, 0x3FF8, 0x3FFB, 0x0442, 0x0479, // Sand stones 0x0501, 0x0510, // Sand stones 0x0009, 0x0015, // Furrows 0x0150, 0x015C // Furrows }; public static HousePlacementResult Check(Mobile from, int multiID, Point3D center, out List toMove) { // If this spot is considered valid, every item and mobile in this list will be moved under the house sign toMove = new List(); var map = from.Map; if (map == null || map == Map.Internal) { return HousePlacementResult.BadLand; // A house cannot go here } if (from.AccessLevel >= AccessLevel.GameMaster) { return HousePlacementResult.Valid; // Staff can place anywhere } if (map == Map.Ilshenar || SpellHelper.IsFeluccaT2A(map, center)) { return HousePlacementResult.BadRegion; // No houses in Ilshenar/T2A } if (map == Map.Malas && multiID is 0x007C or 0x007E) { return HousePlacementResult.InvalidCastleKeep; } if (Region.Find(center, map).IsPartOf()) { return HousePlacementResult.BadRegion; } // This holds data describing the internal structure of the house var mcl = MultiData.GetComponents(multiID); if (multiID >= 0x13EC && multiID < 0x1D00) { HouseFoundation.AddStairsTo(ref mcl); // this is a AOS house, add the stairs } // Location of the nortwest-most corner of the house var start = new Point3D(center.X + mcl.Min.X, center.Y + mcl.Min.Y, center.Z); // These are storage lists. They hold items and mobiles found in the map for further processing using var items = PooledRefList.Create(); var mobiles = new List(); // These are also storage lists. They hold location values indicating the yard and border locations. List yard = new(), borders = new(); /* RULES: * * 1) All tiles which are around the -outside- of the foundation must not have anything impassable. * 2) No impassable object or land tile may come in direct contact with any part of the house. * 3) Five tiles from the front and back of the house must be completely clear of all house tiles. * 4) The foundation must rest flatly on a surface. Any bumps around the foundation are not allowed. * 5) No foundation tile may reside over terrain which is viewed as a road. */ for (var x = 0; x < mcl.Width; ++x) { for (var y = 0; y < mcl.Height; ++y) { var tileX = start.X + x; var tileY = start.Y + y; var addTiles = mcl.Tiles[x][y]; if (addTiles.Length == 0) { continue; // There are no tiles here, continue checking somewhere else } var testPoint = new Point3D(tileX, tileY, center.Z); var reg = Region.Find(testPoint, map); if (!reg.AllowHousing(from, testPoint)) // Cannot place houses in dungeons, towns, treasure map areas etc { if (reg.IsPartOf()) { return HousePlacementResult.BadRegionTemp; } if (reg.IsPartOf() || reg.IsPartOf()) { return HousePlacementResult.BadRegionHidden; } if (reg.IsPartOf()) { return HousePlacementResult.BadRegionRaffle; } return HousePlacementResult.BadRegion; } var landTile = map.Tiles.GetLandTile(tileX, tileY); var landID = landTile.ID & TileData.MaxLandValue; items.Clear(); foreach (var item in map.GetItemsAt(tileX, tileY)) { if (item.Visible && item.X == tileX && item.Y == tileY) { items.Add(item); } } mobiles.Clear(); foreach (var m in map.GetMobilesAt(tileX, tileY)) { if (m.X == tileX && m.Y == tileY) { mobiles.Add(m); } } map.GetAverageZ(tileX, tileY, out var landStartZ, out var landAvgZ, out _); var hasFoundation = false; for (var i = 0; i < addTiles.Length; ++i) { var addTile = addTiles[i]; if (addTile.ID == 0x1) // Nodraw { continue; } var addTileData = TileData.ItemTable[addTile.ID & TileData.MaxItemValue]; var isFoundation = addTile.Z == 0 && addTileData.Wall; var hasSurface = false; if (isFoundation) { hasFoundation = true; } var addTileZ = center.Z + addTile.Z; var addTileTop = addTileZ + addTile.Height; if (addTileData.Surface) { addTileTop += 16; } if (addTileTop > landStartZ && landAvgZ > addTileZ) { return HousePlacementResult.BadLand; // Broke rule #2 } if (isFoundation && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Impassable) == 0 && landAvgZ == center.Z) { hasSurface = true; } foreach (var tile in map.Tiles.GetStaticAndMultiTiles(tileX, tileY)) { var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; if ((id.Impassable || id.Surface && !id.Background) && addTileTop > tile.Z && tile.Z + id.CalcHeight > addTileZ) { return HousePlacementResult.BadStatic; // Broke rule #2 } /*else if (isFoundation && !hasSurface && (id.Flags & TileFlag.Surface) != 0 && (oldTile.Z + id.CalcHeight) == center.Z) hasSurface = true;*/ } foreach (var item in items) { var id = item.ItemData; if (addTileTop > item.Z && item.Z + id.CalcHeight > addTileZ) { if (item.Movable) { toMove.Add(item); } else if (id.Impassable || id.Surface && !id.Background) { return HousePlacementResult.BadItem; // Broke rule #2 } } } if (isFoundation && !hasSurface) { return HousePlacementResult.NoSurface; // Broke rule #4 } for (var j = 0; j < mobiles.Count; ++j) { var m = mobiles[j]; if (addTileTop > m.Z && m.Z + 16 > addTileZ) { toMove.Add(m); } } } for (var i = 0; i < m_RoadIDs.Length; i += 2) { if (landID >= m_RoadIDs[i] && landID <= m_RoadIDs[i + 1]) { return HousePlacementResult.BadLand; // Broke rule #5 } } if (hasFoundation) { for (var xOffset = -1; xOffset <= 1; ++xOffset) { for (var yOffset = -YardSize; yOffset <= YardSize; ++yOffset) { var yardPoint = new Point2D(tileX + xOffset, tileY + yOffset); if (!yard.Contains(yardPoint)) { yard.Add(yardPoint); } } } for (var xOffset = -1; xOffset <= 1; ++xOffset) { for (var yOffset = -1; yOffset <= 1; ++yOffset) { if (xOffset == 0 && yOffset == 0) { continue; } // To ease this rule, we will not add to the border list if the tile here is under a base floor (z<=8) var vx = x + xOffset; var vy = y + yOffset; if (vx >= 0 && vx < mcl.Width && vy >= 0 && vy < mcl.Height) { var breakTiles = mcl.Tiles[vx][vy]; var shouldBreak = false; for (var i = 0; !shouldBreak && i < breakTiles.Length; ++i) { var breakTile = breakTiles[i]; if (breakTile.Height == 0 && breakTile.Z <= 8 && TileData.ItemTable[breakTile.ID & TileData.MaxItemValue].Surface) { shouldBreak = true; } } if (shouldBreak) { continue; } } var borderPoint = new Point2D(tileX + xOffset, tileY + yOffset); if (!borders.Contains(borderPoint)) { borders.Add(borderPoint); } } } } } } for (var i = 0; i < borders.Count; ++i) { var borderPoint = borders[i]; var landTile = map.Tiles.GetLandTile(borderPoint.X, borderPoint.Y); var landID = landTile.ID & TileData.MaxLandValue; if ((TileData.LandTable[landID].Flags & TileFlag.Impassable) != 0) { return HousePlacementResult.BadLand; } for (var j = 0; j < m_RoadIDs.Length; j += 2) { if (landID >= m_RoadIDs[j] && landID <= m_RoadIDs[j + 1]) { return HousePlacementResult.BadLand; // Broke rule #5 } } foreach (var tile in map.Tiles.GetStaticAndMultiTiles(borderPoint.X, borderPoint.Y)) { var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; if (id.Impassable || id.Surface && !id.Background && tile.Z + id.CalcHeight > center.Z + 2) { return HousePlacementResult.BadStatic; // Broke rule #1 } } foreach (var item in map.GetItemsAt(borderPoint)) { if (item.Movable) { continue; } var id = item.ItemData; if (id.Impassable || id.Surface && !id.Background && item.Z + id.CalcHeight > center.Z + 2) { return HousePlacementResult.BadItem; // Broke rule #1 } } } for (var i = 0; i < yard.Count; i++) { var yardPoint = yard[i]; foreach (var house in map.GetMultisInSector(yardPoint)) { if (house.Contains(yard[i])) { return HousePlacementResult.BadStatic; // Broke rule #3 } } } // TODO: Should we check for MultiTilesAt each yard point? // for (var i = 0; i < yard.Count; i++) // { // var yardPoint = yard[i]; // // foreach (var tiles in map.GetMultiTilesAt(yardPoint)) // { // for (int j = 0; j < tiles.Length; ++j) // { // if ((TileData.ItemTable[tiles[j].ID & TileData.MaxItemValue].Flags & (TileFlag.Impassable | TileFlag.Surface)) != 0) // { // return HousePlacementResult.BadStatic; // Broke rule #3 // } // } // } // } return HousePlacementResult.Valid; } } }