/* Thor Server 2.0 Project - Codenamed "Stockholm" Copyright 2008 - 2009 Joe Hegarty This file is part of The Thor Server 2.0 Project - Codenamed "Stockholm". The Thor Server 2.0 Project - Codenamed "Stockholm" is free software: you can redistribute it and/or modify it under the terms of the Microsoft Public License The Thor Server Project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Microsoft Public License for more details. You should have received a copy of the Microsoft Public License along with The Thor Server Project. If not, see http://www.opensource.org/licenses/ms-pl.html */ using System; using System.Collections.Generic; using StockholmLibrary.Game.Flats; using StockholmLibrary.Game.Sessions; using StockholmLibrary.Game.Packets.Client; using StockholmLibrary.Game.Packets.Server; using StockholmLibrary.Net; using StockholmLibrary.Util; using StockholmLibrary.Data; namespace StockholmLibrary.Game.Entities.Components { //------------------------------------ public class ECFlatPhysics : EntityComponent { //------------------------------------ public class TileProperties { public IsometricPoint Point = new IsometricPoint(); public byte TileWeight = 1; public bool ValidTile = false; public bool IsPathable; private List _entities = new List(); private object _entityLock = new object(); //********************************************** public TileProperties(IsometricPoint point, bool validTile) { Point.Copy(point); ValidTile = validTile; ResetRules(); } //********************************************** public void AddEntity(Entity entity) { lock (_entityLock) { if (!_entities.Contains(entity)) { _entities.Add(entity); } } ResetRules(); } //********************************************** public void RemoveEntity(Entity entity) { lock (_entityLock) { if (_entities.Contains(entity)) { _entities.Remove(entity); ResetRules(); } } } //********************************************** public bool HasEntity(Entity entity) { lock (_entityLock) { return _entities.Contains(entity); } } //********************************************** private void ResetRules() { IsPathable = true; lock (_entityLock) { foreach (var entity in _entities) { ECPhysics ecPhysics = null; if (entity.GetEC(ref ecPhysics)) { if (ecPhysics.PhysicsObject != null) { PhysicsObject physics = ecPhysics.PhysicsObject; if (!physics.IsPathable) { // This entity stops the tile being pathable IsPathable = false; } } } } } } } //------------------------------------ public enum EPathFinderNodeType { Start = 1, End = 2, Open = 4, Close = 8, Current = 16, Path = 32 } //------------------------------------ public struct PathFinderNode { public int F; public int G; public int H; public int X; public int Y; public int PX; public int PY; } //------------------------------------ internal class ComparePFNode : IComparer { //********************************************** public int Compare(PathFinderNode x, PathFinderNode y) { if (x.F > y.F) { return 1; } else if (x.F < y.F) { return -1; } return 0; } } private object _tileLock = new object(); private TileProperties[,] _tiles; private float _maxAscend = 0; private float _maxDescend = 0; private const int MAX_PATHING_TRIES = 25000; //********************************************** public void CalculateRoomMap() { lock (_tileLock) { // The first thing we need to do is get the heightmap // so we can calculate the default tile properties ECHeightmap ecHeightmap = null; if (GetParentEntity().GetEC(ref ecHeightmap)) { string defaultHeightmap = ecHeightmap.DefaultHeightmap; string[] heightmapLines = defaultHeightmap.Split(Convert.ToChar(13)); int roomY = heightmapLines.Length; int roomX = heightmapLines[0].Length; // Ok, we need to create our tile properties array now _tiles = new TileProperties[roomX, roomY]; // It's time to create an internal representation of // the default heightmap ProcessDefaultMap(roomX, roomY, heightmapLines); } else { throw new Exception("An entity has ECFlatPhysics but does not have ECHeightmap. This is not valid!"); } } } //********************************************** private void ProcessDefaultMap(int roomX, int roomY, string[] heightmapLines) { lock (_tileLock) { for (int line = 0; line < roomY; line++) { for (int chr = 0; chr < roomX; chr++) { char tileValue = heightmapLines[line][chr]; float tileZ = 0; bool validTile = true; if (!float.TryParse(tileValue.ToString(), out tileZ)) { validTile = false; tileZ = 0; } IsometricPoint location = new IsometricPoint(chr, line, tileZ); _tiles[chr, line] = new TileProperties(location, validTile); } } } } //********************************************** public void AddEntityToTile(Entity entity, IsometricPoint tilePosition) { if (entity != null && tilePosition != null) { lock (_tileLock) { _tiles[tilePosition.X, tilePosition.Y].AddEntity(entity); } } } //********************************************** public void RemoveEntityFromTile(Entity entity, IsometricPoint tilePosition) { if (entity != null && tilePosition != null) { lock (_tileLock) { _tiles[tilePosition.X, tilePosition.Y].RemoveEntity(entity); } } } //********************************************** public void RemoveEntity(Entity entity, IsometricPoint tilePosition) { if (entity != null) { lock (_tileLock) { foreach (var tile in _tiles) { tile.RemoveEntity(entity); } } } } //********************************************** public TileProperties GetTile(IsometricPoint tilePosition) { return _tiles[tilePosition.X, tilePosition.Y]; } //********************************************** public Queue CalculatePath(Entity entity, IsometricPoint target) { return CalculatePath(entity.GetPosition(), target); } //********************************************** public Queue CalculatePath(IsometricPoint start, IsometricPoint end) { PriorityQueue openList = new PriorityQueue(new ComparePFNode()); List closeList = new List(); int estimate = 2; // We need to lock the pathfinder if we're going to start calculation // for a route lock (_tileLock) { PathFinderNode parentNode; bool found = false; int roomX = _tiles.GetUpperBound(0); int roomY = _tiles.GetUpperBound(1); // Clear the lists openList.Clear(); closeList.Clear(); // Supported directions sbyte[,] direction = new sbyte[4, 2] { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 } }; // Set up the initial parent node parentNode.G = 0; parentNode.H = estimate; parentNode.F = parentNode.G + parentNode.H; parentNode.X = start.X; parentNode.Y = start.Y; parentNode.PX = parentNode.X; parentNode.PY = parentNode.Y; openList.Push(parentNode); // Start pathfinding while (openList.Count > 0) { // Get the next parent node from the queue parentNode = openList.Pop(); if (parentNode.X == end.X && parentNode.Y == end.Y) { // We're done! closeList.Add(parentNode); found = true; break; } if (closeList.Count > MAX_PATHING_TRIES) { // Too expensive, we should give up return null; } // We need to try all 4 possible tiles. for (int i = 0; i < 4; i++) { PathFinderNode newNode; newNode.X = parentNode.X + direction[i, 0]; newNode.Y = parentNode.Y + direction[i, 1]; TileProperties parentTile = _tiles[parentNode.X, parentNode.Y]; if (newNode.X < 0 || newNode.Y < 0 || newNode.X > roomX || newNode.Y > roomY) { // This tile is outside of the map continue; } // Get the tile we're trying TileProperties newTile = _tiles[newNode.X, newNode.Y]; if (!newTile.ValidTile || !newTile.IsPathable) { // This is not a valid tile or is not pathable continue; } if (parentTile.Point.Z - _maxDescend > newTile.Point.Z) { // This tile is too low continue; } if (parentTile.Point.Z + _maxAscend < newTile.Point.Z) { // This tile is too high continue; } int newG = parentNode.G + newTile.TileWeight; if (newG == parentNode.G) { // This tile is unbrekeable continue; } int foundInOpenIndex = -1; for (int j = 0; j < openList.Count; j++) { if (openList[j].X == newNode.X && openList[j].Y == newNode.Y) { foundInOpenIndex = j; break; } } if (foundInOpenIndex != -1 && openList[foundInOpenIndex].G <= newG) { continue; } int foundInCloseIndex = -1; for (int j = 0; j < closeList.Count; j++) { if (closeList[j].X == newNode.X && closeList[j].Y == newNode.Y) { foundInCloseIndex = j; break; } } if (foundInCloseIndex != -1 && closeList[foundInCloseIndex].G <= newG) { continue; } newNode.PX = parentNode.X; newNode.PY = parentNode.Y; newNode.G = newG; // Apply our heuristic which is the MaxDXDY heuristic commonly used in A* newNode.H = estimate * (Math.Max(Math.Abs(newNode.X - end.X), Math.Abs(newNode.Y - end.Y))); // Tie breaking int dx1 = parentNode.X - end.X; int dy1 = parentNode.Y - end.Y; int dx2 = start.X - end.X; int dy2 = start.Y - end.Y; int cross = Math.Abs(dx1 * dy2 - dx2 * dy1); newNode.H = (int)(newNode.H + cross * 0.001); newNode.F = newNode.G + newNode.H; // Tile is open openList.Push(newNode); } closeList.Add(parentNode); } if (found) { // We found our route, just need to tidy up PathFinderNode fNode = closeList[closeList.Count - 1]; for (int i = closeList.Count - 1; i >= 0; i--) { if (fNode.PX == closeList[i].X && fNode.PY == closeList[i].Y || i == closeList.Count - 1) { fNode = closeList[i]; } else { closeList.RemoveAt(i); } } // We actually want the target in twice closeList.Add(closeList[closeList.Count - 1]); // Done, return our route Queue resultList = new Queue(); foreach (PathFinderNode node in closeList) { TileProperties tile = _tiles[node.X, node.Y]; IsometricPoint newPoint = new IsometricPoint(); newPoint.Copy(tile.Point); resultList.Enqueue(newPoint); } return resultList; } // Failed return null; } } } }