using System; using System.Collections.Generic; using System.Linq; using System.Text; using Mango.Rooms.Mapping; using Mango.Items; using Mango.Communication.Packets.Outgoing.Room.Engine; using log4net; namespace Mango.Rooms.Instance { sealed class MappingComponent { private RoomInstance Instance = null; /// /// Redirects certain maps like beds. /// private Vector2D[,] _redirectGrid = null; /// /// Tile States which present Open, Blocked or Door tiles. /// private RoomTileState[,] _tileStates = null; private RoomTileEffect[,] _tileEffects = null; private double[,] _stackHeight = null; private double[,] _stackTopItemHeight = null; private List[,] _userGrid = null; private UserMovementNode[,] _userMovementNodes = null; private string RelativeHeightmap = null; public int SizeX { get; set; } public int SizeY { get; set; } public int DoorX { get; set; } public int DoorY { get; set; } /// /// Initialize new instance of the MappingComponent. /// /// public MappingComponent(RoomInstance instance) { if (instance == null) throw new NullReferenceException("RoomInstance cannot be null"); this.Instance = instance; this._redirectGrid = new Vector2D[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this._tileStates = new RoomTileState[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this._tileEffects = new RoomTileEffect[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this._stackHeight = new double[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this._stackTopItemHeight = new double[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this._userGrid = new List[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this._userMovementNodes = new UserMovementNode[instance.Model.Heightmap.SizeX, instance.Model.Heightmap.SizeY]; this.RelativeHeightmap = string.Empty; this.SizeX = instance.Model.Heightmap.SizeX; this.SizeY = instance.Model.Heightmap.SizeY; this.DoorX = instance.Model.DoorX; this.DoorY = instance.Model.DoorY; } public RoomTileEffect GetTileEffect(Vector2D Position) { return (this._tileEffects[Position.X, Position.Y] != null ? this._tileEffects[Position.X, Position.Y] : null); } public Vector2D GetRedirectedTarget(Vector2D Target) { return (this._redirectGrid[Target.X, Target.Y] != null ? this._redirectGrid[Target.X, Target.Y] : Target); } public bool IsValidPosition(Vector2D Position) { return (Position.X >= 0 && Position.Y >= 0 && Position.X < this.Instance.Model.Heightmap.SizeX && Position.Y < this.Instance.Model.Heightmap.SizeY); } public bool IsValidItemRotation(int Rotation) { return (Rotation == 0 || Rotation == 2 || Rotation == 4 || Rotation == 6); } /// /// Get the users step height for the position. /// /// /// public double GetUserStepHeight(Vector2D Position) { RoomTileEffect Effect = this._tileEffects[Position.X, Position.Y]; double Height = this._stackHeight[Position.X, Position.Y]; if (Effect.InteractHeight >= 0) { Height -= this._stackTopItemHeight[Position.X, Position.Y]; } return Height; } /// /// Checks if the position is available for the Avatar to move to. /// /// The position to perform the check on. /// True if it can or false if it can't. public bool CanInitiateMoveToPosition(Vector2D Position) { return (IsValidPosition(Position) && this._tileStates[Position.X, Position.Y] != RoomTileState.Blocked && this._userGrid[Position.X, Position.Y] == null && this._userMovementNodes[Position.X, Position.Y] != UserMovementNode.Blocked); } /// /// Is the target/position blocked? /// /// /// public bool IsTargetBlocked(Vector2D Target) { if (_userMovementNodes[Target.X, Target.Y] == UserMovementNode.Blocked) { return true; } return false; } /// /// Checks if the next step is a valid move. /// /// The position the Avatar is currently on. /// The position it wants to move to. /// Is this the very last step? /// The grid used to map out where users are (can be null) /// True or false if the step is valid. public bool IsValidStep(Vector2D From, Vector2D To, bool EndOfPath, List[,] AvatarBlockGrid = null) { if (AvatarBlockGrid == null) { AvatarBlockGrid = this._userGrid; } if (!IsValidPosition(To)) { return false; } if (_tileStates[To.X, To.Y] == RoomTileState.Blocked || ((!Instance.DisableRoomBlocking || EndOfPath) && AvatarBlockGrid[To.X, To.Y] != null) || this._userMovementNodes[To.X, To.Y] == UserMovementNode.Blocked || (this._userMovementNodes[To.X, To.Y] == UserMovementNode.Walkable_End_Of_Route && !EndOfPath)) { return false; } if ((To.X == this.DoorX && To.Y == DoorY) && !EndOfPath) { return false; } double HeightDiff = GetUserStepHeight(To) - GetUserStepHeight(From); if (HeightDiff > 1.5 || (this.Instance.Type == RoomType.PUBLIC && HeightDiff < -1.5)) { return false; } return true; } /// /// Finds the Avatars on the current position in the room. /// /// Position X. /// Position Y. /// The list of avatars on the square. public List GetAvatarsOnPosition(Vector2D Position) { return this._userGrid[Position.X, Position.Y] == null ? new List() : this._userGrid[Position.X, Position.Y]; } /// /// Updates the Avatar grid to the new grid provided for the room. /// /// The new grid to replace the old. public void UpdateUserGrid(List[,] NewGrid) { this._userGrid = NewGrid; } /// /// Finds the correct placement height for the item. /// /// The item to get the placement height for. /// The tiles that are affected. /// Is the move a rotate only? /// The placement height as a double. public double GetItemPlacementHeight(Item Item, List AffectedTiles, bool RotateOnly) { double RootFloorHeight = -1; double HighestStack = 0; double[,] TempStackHeights = new double[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; foreach (Vector2D AffectedTile in AffectedTiles) { if (!IsValidPosition(AffectedTile) || this._tileStates[AffectedTile.X, AffectedTile.Y] == RoomTileState.Door) { return -1; } if (RootFloorHeight == -1) { RootFloorHeight = this.Instance.Model.Heightmap.FloorHeight[AffectedTile.X, AffectedTile.Y]; } if (RootFloorHeight != this.Instance.Model.Heightmap.FloorHeight[AffectedTile.X, AffectedTile.Y]) { return -1; } TempStackHeights[AffectedTile.X, AffectedTile.Y] = RootFloorHeight; HighestStack = RootFloorHeight; if (Item.Data.StackingBehaviour != ItemStackingBehaviour.Ignore) { bool CanRotateIntoAvatars = (Item.Data.Behaviour == ItemBehaviour.SEAT || Item.Data.Behaviour == ItemBehaviour.BED || Item.Data.Behaviour == ItemBehaviour.LOVE_SHUFFLER || Item.Data.Behaviour == ItemBehaviour.TELEPORTER || Item.Data.Behaviour == ItemBehaviour.ROLLER); if ((!RotateOnly || !CanRotateIntoAvatars) && this.Instance.GetMapping().GetAvatarsOnPosition(AffectedTile).Count > 0) { return -1; } } } if (Item.Data.StackingBehaviour == ItemStackingBehaviour.Ignore) { return RootFloorHeight; } ItemStackingBehaviour[,] TempStackBehaviors = new ItemStackingBehaviour[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; int[,] InitiatorLimitations = new int[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; foreach (Item StackItem in this.Instance.GetItems().GetWallAndFloor) { if (StackItem.Id == Item.Id || StackItem.Data.StackingBehaviour == ItemStackingBehaviour.Ignore) { continue; } Vector2D MatchedTile = null; List ItemTiles = CalculateAffectedTiles(StackItem, StackItem.Position.ToVector2D(), StackItem.RoomRot); foreach (Vector2D AffectedTile in AffectedTiles) { foreach (Vector2D ItemTile in ItemTiles) { if (ItemTile.X == AffectedTile.X && ItemTile.Y == AffectedTile.Y) { MatchedTile = AffectedTile; if (StackItem.Data.Behaviour == ItemBehaviour.ROLLER && (Item.Data.SizeX != 1 || Item.Data.SizeY != 1)) { return -1; } double ItemTotalHeight = StackItem.Position.Z + StackItem.Data.Height; if (ItemTotalHeight >= TempStackHeights[MatchedTile.X, MatchedTile.Y]) { TempStackHeights[MatchedTile.X, MatchedTile.Y] = ItemTotalHeight; TempStackBehaviors[MatchedTile.X, MatchedTile.Y] = StackItem.Data.StackingBehaviour; } if (StackItem.Data.StackingBehaviour != ItemStackingBehaviour.Ignore) { InitiatorLimitations[MatchedTile.X, MatchedTile.Y]++; } if (ItemTotalHeight >= HighestStack) { HighestStack = ItemTotalHeight; } } } } } if ((HighestStack + Item.Data.Height) >= 12) // to-do: stack height limit { return -1; } foreach (Vector2D AffectedTile in AffectedTiles) { if (((Item.Data.StackingBehaviour == ItemStackingBehaviour.Initiator || Item.Data.StackingBehaviour == ItemStackingBehaviour.Initiate_And_Terminate) && InitiatorLimitations[AffectedTile.X, AffectedTile.Y] > 0) || (TempStackBehaviors[AffectedTile.X, AffectedTile.Y] == ItemStackingBehaviour.Terminator || TempStackBehaviors[AffectedTile.X, AffectedTile.Y] == ItemStackingBehaviour.Initiate_And_Terminate)) { return -1; } } return HighestStack; } /// /// Finds the affected tiles for a item/position/rotation. /// /// /// /// /// /// public List CalculateAffectedTiles(Item Item, Vector2D Position, int Rot) { List Tiles = new List(); Tiles.Add(Position); if (Item.Data.SizeY > 0) { if (Rot == 0 || Rot == 4) { for (int posY = 1; posY < Item.Data.SizeY; posY++) { Tiles.Add(new Vector2D(Position.X, Position.Y + posY)); for (int posX = 1; posX < Item.Data.SizeX; posX++) { Tiles.Add(new Vector2D(Position.X + posX, Position.Y + posY)); } } } else if (Rot == 2 || Rot == 6) { for (int posX = 1; posX < Item.Data.SizeY; posX++) { Tiles.Add(new Vector2D(Position.X + posX, Position.Y)); for (int posY = 1; posY < Item.Data.SizeX; posY++) { Tiles.Add(new Vector2D(Position.X + posX, Position.Y + posY)); } } } } if (Item.Data.SizeX > 0) { if (Rot == 0 || Rot == 4) { for (int posX = 1; posX < Item.Data.SizeX; posX++) { Vector2D Vector = new Vector2D(Position.X + posX, Position.Y); if (!Tiles.Contains(Vector)) { Tiles.Add(Vector); } for (int posY = 1; posY < Item.Data.SizeY; posY++) { Vector2D _Vector = new Vector2D(Position.X + posX, Position.Y + posY); if (!Tiles.Contains(Vector)) { Tiles.Add(_Vector); } } } } else if (Rot == 2 || Rot == 6) { for (int posY = 1; posY < Item.Data.SizeX; posY++) { Vector2D Vector = new Vector2D(Position.X, Position.Y + posY); if (!Tiles.Contains(Vector)) { Tiles.Add(Vector); } for (int posX = 1; posX < Item.Data.SizeY; posX++) { Vector2D _Vector = new Vector2D(Position.X + posX, Position.Y + posY); if (!Tiles.Contains(Vector)) { Tiles.Add(Vector); } } } } } return Tiles; } /// /// Returns the relative heightmap as a string. /// /// Heightmap. public string GetRelativeHeightmap() { return this.RelativeHeightmap; } /// /// Regenerates the relative heightmap for a room, it must be recalculated each item placement or change. /// /// Should the map be broadcasted to all avatars? (resend the packet). public void RegenerateRelativeHeightmap(bool Broadcast = false) { Instance.GetItems().GuestsCanPlaceStickies = false; /*this.StackHeight = new double[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; this.StackTopItemHeight = new double[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; this.UserMovementNodes = new UserMovementNode[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; this.TileEffects = new RoomTileEffect[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; this.RedirectGrid = new Vector2D[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; this.TileStates = new RoomTileState[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY];*/ for (int y = 0; y < this.Instance.Model.Heightmap.SizeY; y++) { for (int x = 0; x < this.Instance.Model.Heightmap.SizeX; x++) { this._stackHeight[x, y] = this.Instance.Model.Heightmap.FloorHeight[x, y]; this._stackTopItemHeight[x, y] = 0; this._userMovementNodes[x, y] = UserMovementNode.Walkable; this._tileEffects[x, y] = new RoomTileEffect(); this._redirectGrid[x, y] = null; this._tileStates[x, y] = RoomTileState.Open; } } bool[,] AnyItemInStack = new bool[this.Instance.Model.Heightmap.SizeX, this.Instance.Model.Heightmap.SizeY]; foreach (Item Item in this.Instance.GetItems().GetWallAndFloor) { if (Item.Data.StackingBehaviour == ItemStackingBehaviour.Ignore) { continue; } double TotalItemStackHeight = Item.Position.Z + Math.Round(Item.Data.Height, 1); List AffectedTiles = CalculateAffectedTiles(Item, Item.Position.ToVector2D(), Item.RoomRot); RoomTileEffect Effect = new RoomTileEffect(); UserMovementNode MovementNode = UserMovementNode.Blocked; switch (Item.Data.Behaviour) { case ItemBehaviour.SEAT: case ItemBehaviour.LOVE_SHUFFLER: Effect = new RoomTileEffect(RoomTileEffectType.Sit, Item.Position.X, Item.Position.Y, Item.RoomRot, Item.Data.Height, 0, Item.Data.Id); MovementNode = UserMovementNode.Walkable_End_Of_Route; break; case ItemBehaviour.BED: Effect = new RoomTileEffect(RoomTileEffectType.Lay, Item.Position.X, Item.Position.Y, Item.RoomRot, Item.Data.Height, 0, Item.Data.Id); MovementNode = UserMovementNode.Walkable_End_Of_Route; break; case ItemBehaviour.GATE: MovementNode = Item.Flags == "1" ? UserMovementNode.Walkable : UserMovementNode.Blocked; break; case ItemBehaviour.AVATAR_EFFECT_GENERATOR: Effect = new RoomTileEffect(RoomTileEffectType.Effect, Item.Position.X, Item.Position.Y, 0, 0, Item.Data.BehaviourData, 0); break; case ItemBehaviour.STICKY_POLE: Instance.GetItems().GuestsCanPlaceStickies = true; break; } foreach (Vector2D Tile in AffectedTiles) { if (TotalItemStackHeight >= this._stackHeight[Tile.X, Tile.Y]) { if (Item.Data.WalkableMode == ItemWalkableMode.Always) { MovementNode = UserMovementNode.Walkable; } else if (Item.Data.WalkableMode == ItemWalkableMode.Limited) { MovementNode = (AnyItemInStack[Tile.X, Tile.Y] && this._userMovementNodes[Tile.X, Tile.Y] != UserMovementNode.Walkable) ? UserMovementNode.Blocked : UserMovementNode.Walkable; } _stackHeight[Tile.X, Tile.Y] = TotalItemStackHeight; _stackTopItemHeight[Tile.X, Tile.Y] = Item.Data.Height; _userMovementNodes[Tile.X, Tile.Y] = MovementNode; _tileEffects[Tile.X, Tile.Y] = Effect; if (Item.Data.Behaviour == ItemBehaviour.BED) { if (Item.RoomRot == 2 || Item.RoomRot == 6) { _redirectGrid[Tile.X, Tile.Y] = new Vector2D(Item.Position.X, Tile.Y); } if (Item.RoomRot == 0 || Item.RoomRot == 4) { _redirectGrid[Tile.X, Tile.Y] = new Vector2D(Tile.X, Item.Position.Y); } } else { _redirectGrid[Tile.X, Tile.Y] = null; } AnyItemInStack[Tile.X, Tile.Y] = true; } } } foreach (ItemStaticPublics Object in Instance.GetItems().GetStaticItems) { _stackHeight[Object.X, Object.Y] = Instance.Model.Heightmap.FloorHeight[Object.X, Object.Y] + 1.0; _stackTopItemHeight[Object.X, Object.Y] = 1.0; _userMovementNodes[Object.X, Object.Y] = (Object.IsSeat ? UserMovementNode.Walkable_End_Of_Route : UserMovementNode.Blocked); if (Object.IsSeat) { _tileEffects[Object.X, Object.Y] = new RoomTileEffect(RoomTileEffectType.Sit, Object.X, Object.Y, Object.Rotation, 1.0); } else if (Object.IsBed) { _tileEffects[Object.X, Object.Y] = new RoomTileEffect(RoomTileEffectType.Lay, Object.X, Object.Y, Object.Rotation, 1.0); } } StringBuilder RelativeHeightmap = new StringBuilder(); for (int y = 0; y < this.Instance.Model.Heightmap.SizeY; y++) { for (int x = 0; x < this.Instance.Model.Heightmap.SizeX; x++) { if (this.Instance.Type == RoomType.FLAT && this.Instance.Model.DoorX == x && this.Instance.Model.DoorY == y) { this._tileStates[x, y] = RoomTileState.Door; RelativeHeightmap.Append(Instance.Model.DoorZ); continue; } if (Instance.Model.Heightmap.TileStates[x, y] == RoomTileState.Blocked) { this._tileStates[x, y] = RoomTileState.Blocked; RelativeHeightmap.Append('x'); continue; } this._tileStates[x, y] = RoomTileState.Open; RelativeHeightmap.Append(Instance.Model.Heightmap.FloorHeight[x, y]); } RelativeHeightmap.Append(Convert.ToChar(13)); } string NewRelativeMap = RelativeHeightmap.ToString(); if (!NewRelativeMap.Equals(this.RelativeHeightmap)) { this.RelativeHeightmap = RelativeHeightmap.ToString(); if (Broadcast) { this.Instance.GetAvatars().BroadcastPacket(new FloorHeightMapComposer(NewRelativeMap)); } } } public void Cleanup() { this.Instance = null; this._redirectGrid = null; this._tileStates = null; this._tileEffects = null; this._stackHeight = null; this._stackTopItemHeight = null; this._userGrid = null; this._userMovementNodes = null; this.RelativeHeightmap = null; } } }