1544 lines
52 KiB
C#
Raw Normal View History

2024-02-20 18:39:12 +08:00
using HPJ.Simulation.Enums;
using HPJ.Simulation.Pathing;
using HPJ.Simulation.Utilities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace HPJ.Simulation.Map
{
/// <summary>
/// The Map that represents a area with square tiles
/// </summary>
public class Map
{
protected string _name;
/// <summary>
/// The Name of the Map
/// </summary>
public string Name { get { return _name; } }
protected byte[,] _tiles;
/// <summary>
/// A 2D Array of the tiles of the Map in bytes
/// </summary>
public byte[,] Tiles { get { return _tiles; } }
public MapSettings _settings;
/// <summary>
/// The Map Settings
/// </summary>
public MapSettings Settings { get { return _settings; } }
/// <summary>
/// If the Map has been Initialized
/// </summary>
public bool Initialized { get; protected set; }
/// <summary>
/// If the Map logic is currently running
/// </summary>
public bool Running { get; protected set; }
/// <summary>
/// If the Map uses agent avoidence
/// </summary>
public bool AgentAvoidence { get; protected set; }
/// <summary>
/// If the map is currently completing navigation job requests
/// </summary>
public bool CompletingNavigationJobs { get; protected set; }
/// <summary>
/// If the map is currently completing navigation map change requests
/// </summary>
public bool CompletingMapChanges { get; protected set; }
/// <summary>
/// If the map is currently completing tile ownership claims
/// </summary>
public bool CompletingClaims { get; protected set; }
/// <summary>
/// If the map is currently completing tile disownment claims
/// </summary>
public bool CompletingDisownment { get; protected set; }
protected Dictionary<string, BytePointMap> _pointMaps;
/// <summary>
/// The Byte point maps used by the JPS pathfinding for each valid tile types
/// </summary>
public Dictionary<string, BytePointMap> PointMaps { get { return _pointMaps; } }
public List<MapBridge> _bridges;
/// <summary>
/// A List of all the bridges connected to this Map
/// </summary>
public List<MapBridge> Bridges { get { return _bridges; } }
public List<MapSeam> _seams;
/// <summary>
/// A List of all the seams connected to this Map
/// </summary>
public List<MapSeam> Seams { get { return _seams; } }
/// <summary>
/// The map copy that keeps record of tile ownership by agent
/// </summary>
public AgentMap AgentOwnershipMap { get; protected set; }
/// <summary>
/// The Parrallel options used by the map for the multithreading of each Unit that wants pathfinding
/// </summary>
protected ParallelOptions _parallelOptions;
public Map(MapSettings mapSettings, string MapName, int DegreeOfParallelism, bool CreateAgentMap)
{
_name = MapName;
_settings = mapSettings;
_bridges = new List<MapBridge>();
_requestedJobs = new List<NavigationJob>();
_requestedMapChanges = new List<ChangeMapJob>();
_agentClaimRequests = new List<TileOwnershipClaim>();
_agentDisownmentClaimRequests = new List<DisownTileOwnership>();
_savedPaths = new Dictionary<PathingInfo, NavigationPath>();
_needToBeUpdatedPaths = new List<NavigationPath>();
_parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = DegreeOfParallelism };
AgentAvoidence = CreateAgentMap;
if (CreateAgentMap)
{
AgentOwnershipMap = new AgentMap(this);
}
}
/// <summary>
/// The Debug callback to Unity
/// </summary>
public Action<string, SimulationDebugLog> DebugLogCallback;
/// <summary>
/// Initialized the Map
/// </summary>
/// <param name="debugLogCallback"></param>
public void Initialize(Action<string, SimulationDebugLog> debugLogCallback)
{
DebugLogCallback = debugLogCallback;
Running = true;
try
{
_pointMaps = new Dictionary<string, BytePointMap>();
_settings.MapInfo.MakeSureSizeIsRight();
_tiles = new byte[_settings.MapWidth, _settings.MapHeight];
// Create Tiles
for (int x = 0; x < _settings.MapWidth; x++)
{
for (int y = 0; y < _settings.MapHeight; y++)
{
_tiles[x, y] = _settings.MapInfo.SavedTiles[x, y];
}
}
Initialized = true;
_pointMaps.Add(_settings.DefaultTraversaleTiles.TilesToString(), new BytePointMap(_tiles, _settings.DefaultTraversaleTiles, Settings.MapWidth, Settings.MapHeight, Settings.CornerCutting));
DebugLogCallback?.Invoke($"{_name} Map Initialized", SimulationDebugLog.Log);
}
catch (Exception ex)
{
Log($"({_settings.MapWidth},{_settings.MapHeight}) vs ({_settings.MapInfo.Width},{_settings.MapInfo.Height}) vs ({_settings.MapInfo.SavedTiles.GetLength(0)},{_settings.MapInfo.SavedTiles.GetLength(1)}) -> {_settings.MapInfo.SavedTiles.Length}, P{_settings.MapInfo.SavedTiles.LongLength}, {_settings.MapInfo.SavedTiles.Rank}");
DebugLogCallback?.Invoke($"{ex.Message}: {ex.StackTrace}", SimulationDebugLog.Error);
}
}
public void OnDestroy()
{
Running = false;
}
/// <summary>
/// Debug.Log
/// </summary>
/// <param name="Log"></param>
public void Log(string Log)
{
DebugLogCallback?.Invoke(Log, SimulationDebugLog.Log);
}
/// <summary>
/// Debug.Warning
/// </summary>
/// <param name="Log"></param>
public void LogWarning(string Log)
{
DebugLogCallback?.Invoke(Log, SimulationDebugLog.Warning);
}
/// <summary>
/// Debug.Error
/// </summary>
/// <param name="Log"></param>
public void LogError(string Log)
{
DebugLogCallback?.Invoke(Log, SimulationDebugLog.Error);
}
/// <summary>
/// Gets or Generates a Byte point map based on a set of traversable tiles types
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal BytePointMap GetBytePointMap(NavigationPath path)
{
lock (_pointMaps)
{
if (!_pointMaps.ContainsKey(path.Info.TraversableTilesKey))
{
_pointMaps.Add(path.Info.TraversableTilesKey, new BytePointMap(_tiles, path.Info.TraversableTilesKey.StringToTilesArray(), Settings.MapWidth, Settings.MapHeight, Settings.CornerCutting));
}
}
return _pointMaps[path.Info.TraversableTilesKey];
}
/// <summary>
/// Gets the movement speed for a specific tile type on this Map
/// </summary>
/// <param name="TileType"></param>
/// <returns></returns>
internal byte GetTileSpeedData(TileTypes TileType)
{
for (int i = 0; i < Settings.TileData.Count; i++)
{
TileData Data = Settings.TileData[i];
if (Data.Type == TileType)
{
return Data.Speed;
}
}
return 0;
}
/// <summary>
/// A List of the Requested Navigation Jobs
/// </summary>
protected List<NavigationJob> _requestedJobs;
/// <summary>
/// A List of the Requested Map Changes
/// </summary>
protected List<ChangeMapJob> _requestedMapChanges;
/// <summary>
/// A List of the Requested Tile Ownership claims
/// </summary>
protected List<TileOwnershipClaim> _agentClaimRequests;
/// <summary>
/// A List of the Requested Tile Disownment claims
/// </summary>
protected List<DisownTileOwnership> _agentDisownmentClaimRequests;
/// <summary>
/// A List of all the saved paths that need to be updated because they were invalidated or are new
/// </summary>
protected List<NavigationPath> _needToBeUpdatedPaths;
/// <summary>
/// A Dictionary of all the saved paths on this Map
/// </summary>
protected Dictionary<PathingInfo, NavigationPath> _savedPaths;
/// <summary>
/// The Update function of the map each update tick
/// </summary>
public void Update()
{
try
{
if (!Initialized)
{
return;
}
// Update Claims
lock (_agentDisownmentClaimRequests)
{
if (_agentDisownmentClaimRequests.Count > 0)
{
CompletingDisownment = true;
for (int i = 0; i < _agentDisownmentClaimRequests.Count; i++)
{
Disown(_agentDisownmentClaimRequests[i]);
}
CompletingDisownment = false;
_agentDisownmentClaimRequests.Clear();
}
}
lock (_agentClaimRequests)
{
if (_agentClaimRequests.Count > 0)
{
CompletingClaims = true;
for (int i = 0; i < _agentClaimRequests.Count; i++)
{
Claim(_agentClaimRequests[i]);
}
CompletingClaims = false;
_agentClaimRequests.Clear();
}
}
// Update Map
lock (_requestedMapChanges)
{
if (_requestedMapChanges.Count > 0)
{
CompletingMapChanges = true;
for (int i = 0; i < _requestedMapChanges.Count; i++)
{
ChangeMap(_requestedMapChanges[i]);
}
CompletingMapChanges = false;
_requestedMapChanges.Clear();
}
}
// Complete Navigation Jobs
lock (_requestedJobs)
{
if (_requestedJobs.Count > 0)
{
CompletingNavigationJobs = true;
lock (_savedPaths)
{
if (AgentAvoidence)
{
// Find out the paths that need to be recalculated
for (int i = 0; i < _requestedJobs.Count; i++)
{
NavigationJob Job = _requestedJobs[i];
NavigationPath Path;
Path = new NavigationPath(Job.Info, this);
_needToBeUpdatedPaths.Add(Path);
Path.AgentID = Job.AgentID;
Job.SetNewCurrentPath(Path);
}
}
else
{
// Find out the paths that need to be recalculated
for (int i = 0; i < _requestedJobs.Count; i++)
{
NavigationJob Job = _requestedJobs[i];
NavigationPath Path;
if (_savedPaths.ContainsKey(Job.Info))
{
Path = _savedPaths[Job.Info];
if (!Path.ValidPath || Job.RepathSavedPath)
{
Path.Path.Clear();
// In Case the Path is already being pathed
if (!_needToBeUpdatedPaths.Contains(Path))
{
_needToBeUpdatedPaths.Add(Path);
}
}
}
else
{
Path = new NavigationPath(Job.Info, this);
_savedPaths.Add(Job.Info, Path);
_needToBeUpdatedPaths.Add(Path);
}
Job.SetNewCurrentPath(Path);
}
}
}
// Recalculate Path
Parallel.ForEach(_needToBeUpdatedPaths, _parallelOptions, CompletePathfind);
// Complete the pathfinding
//Parallel.ForEach(_requestedJobs, _parallelOptions, FinishPathfinding);
for(int i = _requestedJobs.Count - 1; i >= 0; i--)
{
_requestedJobs[i].PathfindingComplete();
}
_needToBeUpdatedPaths.Clear();
_requestedJobs.Clear();
CompletingNavigationJobs = false;
}
}
}
catch (Exception ex)
{
LogError($"Map Error {ex.Message}: {ex.StackTrace}");
}
}
/// <summary>
/// A Check to see if the navigation job can be run on this map
/// </summary>
/// <param name="job"></param>
/// <returns></returns>
internal bool JobCompatible(NavigationJob job)
{
if (!job.TraverableTileTypes.Contains(GetTileType(job.Info.PointA)) || !job.TraverableTileTypes.Contains(GetTileType(job.Info.PointB)))
{
return false;
}
return PointInMap(job.Info.PointA) && PointInMap(job.Info.PointB);
}
/// <summary>
/// Checks to see if this point is valid on this map
/// </summary>
/// <param name="WorldPosition"></param>
/// <returns></returns>
public bool PointInMap(IntVector3 WorldPosition)
{
return GetTileType(WorldPosition) != TileTypes.OutOfBounds;
}
/// <summary>
/// Checks to see if this point is valid on this map
/// </summary>
/// <param name="WorldPosition"></param>
/// <returns></returns>
public bool PointInMap(IntVector2 TilePosition)
{
return GetTileType(TilePosition) != TileTypes.OutOfBounds;
}
/// <summary>
/// Adds a Map change Request for this map
/// </summary>
/// <param name="MapChange"></param>
public void AddMapChange(ChangeMapJob MapChange)
{
lock (_requestedMapChanges)
{
_requestedMapChanges.Add(MapChange);
}
}
/// <summary>
/// Adds a agent tile request claim for this map
/// </summary>
/// <param name="Claim"></param>
public void AddClaim(TileOwnershipClaim Claim)
{
lock (_agentClaimRequests)
{
_agentClaimRequests.Add(Claim);
}
}
/// <summary>
/// Adds a agent tile disownment claim for this map
/// </summary>
/// <param name="Claim"></param>
public void AddDisownmentClaim(DisownTileOwnership Claim)
{
lock (_agentDisownmentClaimRequests)
{
_agentDisownmentClaimRequests.Add(Claim);
}
}
/// <summary>
/// Changes the map tiles based on the map change requests
/// </summary>
/// <param name="MapChange"></param>
protected void ChangeMap(ChangeMapJob MapChange)
{
for (int i = 0; i < MapChange.TilesToBeChanged.Count; i++)
{
(IntVector2, TileTypes) TileChange = MapChange.TilesToBeChanged[i];
SetTile(TileChange.Item1, TileChange.Item2);
foreach(BytePointMap ByteMap in _pointMaps.Values)
{
ByteMap.ChangeTile(TileChange.Item1, TileChange.Item2);
}
}
}
/// <summary>
/// Agent claims a tile
/// </summary>
/// <param name="Claim"></param>
protected void Claim(TileOwnershipClaim Claim)
{
AgentOwnershipMap.ClaimTile(Claim.RequestTile, Claim.OwnerID);
}
/// <summary>
/// Agent disowns a claims to a tile
/// </summary>
/// <param name="Claim"></param>
protected void Disown(DisownTileOwnership Claim)
{
AgentOwnershipMap.DisownTile(Claim.RequestTile, Claim.OwnerID);
}
/// <summary>
/// Sets the tile to the new type
/// </summary>
/// <param name="TilePosition"></param>
/// <param name="TypeChange"></param>
protected void SetTile(IntVector2 TilePosition, TileTypes TypeChange)
{
_tiles[TilePosition.x, TilePosition.y] = (byte)TypeChange;
}
/// <summary>
/// Gets the current tile type of this tile index
/// </summary>
/// <param name="WorldPosition"></param>
/// <returns></returns>
public TileTypes GetTileType(IntVector3 WorldPosition)
{
// Offset the Position
WorldPosition.x -= Settings.Offset.x;
WorldPosition.z -= Settings.Offset.y;
WorldPosition.x = (int)Math.Floor((float)WorldPosition.x / Settings.TileSize);
WorldPosition.z = (int)Math.Floor((float)WorldPosition.z / Settings.TileSize);
if (WorldPosition.x < 0 || WorldPosition.z < 0 || WorldPosition.x >= Settings.MapWidth || WorldPosition.z >= Settings.MapHeight)
{
return TileTypes.OutOfBounds;
}
return (TileTypes)Tiles[WorldPosition.x, WorldPosition.z];
}
/// <summary>
/// Gets the current tile type of this tile index
/// </summary>
/// <param name="TilePosition"></param>
/// <returns></returns>
public TileTypes GetTileType(IntVector2 TilePosition)
{
if (TilePosition.x < 0 || TilePosition.y < 0 || TilePosition.x >= Settings.MapWidth || TilePosition.y >= Settings.MapHeight)
{
return TileTypes.OutOfBounds;
}
return (TileTypes)Tiles[TilePosition.x, TilePosition.y];
}
/// <summary>
/// Gets the current tile type of this tile index
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public TileTypes GetTileType(int x, int y)
{
if (x < 0 || y < 0 || x >= Settings.MapWidth || y >= Settings.MapHeight)
{
return TileTypes.OutOfBounds;
}
return (TileTypes)Tiles[x, y];
}
/// <summary>
/// Gets the current tile index at this world position
/// </summary>
/// <param name="WorldTileIndexPosition"></param>
/// <returns></returns>
public IntVector2 GetTileIndexPosition(IntVector3 WorldTileIndexPosition)
{
// Offset the Position
IntVector2 TileIndex = new IntVector2();
TileIndex.x = (int)((WorldTileIndexPosition.x - _settings.Offset.x) / (float)_settings.TileSize);
TileIndex.y = (int)((WorldTileIndexPosition.z - _settings.Offset.z) / (float)_settings.TileSize);
return TileIndex;
}
/// <summary>
/// Adds a navigation Request to the Map
/// </summary>
/// <param name="Job"></param>
public void AddNavigationJob(NavigationJob Job)
{
lock (_requestedJobs)
{
Job.CurrentStep = PathfindingCalculationStep.Pathfinding;
_requestedJobs.Add(Job);
}
}
/// <summary>
/// Calculates the Requested Path on the Map
/// </summary>
/// <param name="Path"></param>
protected void CompletePathfind(NavigationPath Path)
{
if (Path.Info.NavigationType == NavigationTypes.JumpPointSearch)
{
JumpPointSearch.Instance.CalculatePath(Path, Path.HostMap, out string Status, out bool Succeeded);
Path.PreviousStatus = Status;
Path.ValidPath = Succeeded;
}
else if (Path.Info.NavigationType == NavigationTypes.AStar)
{
AStar.Instance.CalculatePath(Path, Path.HostMap, out string Status, out bool Succeeded);
Path.PreviousStatus = Status;
Path.ValidPath = Succeeded;
}
else if (Path.Info.NavigationType == NavigationTypes.Free)
{
Path.Path.Clear();
Path.Path.Add(Path.Info.PointA);
Path.Path.Add(Path.Info.PointB);
Path.PreviousStatus = "Free Pathed";
Path.ValidPath = true;
}
}
/// <summary>
/// Comples the Navigation Job Request
/// </summary>
/// <param name="Job"></param>
protected void FinishPathfinding(NavigationJob Job)
{
Job.PathfindingComplete();
}
}
/// <summary>
/// The Map settings used to shape the Map
/// </summary>
[Serializable]
public class MapSettings
{
/// <summary>
/// The Size of the tile in centimeters not meters
/// </summary>
public int TileSize = 100; // In cm
/// <summary>
/// The Number of tiles in the x direction
/// </summary>
public int MapWidth = 100;
/// <summary>
/// The Number of tiles in the z direction
/// </summary>
public int MapHeight = 100;
/// <summary>
/// The Physical offset of the map in centimeters
/// </summary>
public IntVector3 Offset = new IntVector3(); // in cm
/// <summary>
/// If corner cutting is enabled in the pathfinding on this Map
/// </summary>
public bool CornerCutting = false;
/// <summary>
/// The Data for each tile on this Map
/// </summary>
public List<TileData> TileData = new List<TileData>();
/// The Data used to keep track of the map width and height
/// </summary>
public MapData MapInfo = new MapData();
/// <summary>
/// The Default traversable tiles for this Map
/// </summary>
public TileTypes[] DefaultTraversaleTiles = new TileTypes[2] { TileTypes.Standard, TileTypes.Free };
public MapSettings()
{
}
/// <summary>
/// Adjusts the Map Size in a specific direction
/// </summary>
/// <param name="TilesChanged"></param>
/// <param name="ExpansionSide"></param>
/// <param name="NewOffset"></param>
/// <param name="Translation"></param>
public void AdjustMap(int TilesChanged, MapExpansionSide ExpansionSide, int NewOffset, int Translation = 0)
{
if (TilesChanged == 0)
{
return;
}
if (ExpansionSide == MapExpansionSide.Left)
{
if (TilesChanged <= 0)
{
return;
}
if (TilesChanged >= 100000)
{
return;
}
Translation = MapWidth - TilesChanged;
MapWidth = TilesChanged;
Offset.x = NewOffset;
}
else if (ExpansionSide == MapExpansionSide.Right)
{
if (TilesChanged <= 0)
{
return;
}
if (TilesChanged >= 100000)
{
return;
}
MapWidth = TilesChanged;
Offset.x = NewOffset;
}
else if (ExpansionSide == MapExpansionSide.Top)
{
if (TilesChanged <= 0)
{
return;
}
if (TilesChanged >= 100000)
{
return;
}
MapHeight = TilesChanged;
Offset.z = NewOffset;
}
else if (ExpansionSide == MapExpansionSide.Bottom)
{
if (TilesChanged <= 0)
{
return;
}
if (TilesChanged >= 100000)
{
return;
}
Translation = MapHeight - TilesChanged;
MapHeight = TilesChanged;
Offset.z = NewOffset;
}
UpdateMapSize();
if (ExpansionSide == MapExpansionSide.Left)
{
MapInfo.TransalateValues(Translation, 0);
}
else if (ExpansionSide == MapExpansionSide.Right)
{
MapInfo.TransalateValues(Translation, 0);
}
else if (ExpansionSide == MapExpansionSide.Bottom)
{
MapInfo.TransalateValues(0, Translation);
}
else if (ExpansionSide == MapExpansionSide.Top)
{
MapInfo.TransalateValues(0, Translation);
}
}
/// <summary>
/// Updates the Map Size
/// </summary>
public void UpdateMapSize()
{
MapInfo.UpdateToMapSize(MapWidth, MapHeight);
}
}
/// <summary>
/// A Set of points that the navigation can use to transfer a Agent from one map to another
/// </summary>
[Serializable]
public class MapBridge
{
/// <summary>
/// Map on Side A of the Bridge
/// </summary>
public MapSet MapA; /*{ get { return _mapA; } set { _mapA = value; } }*/
//protected MapSet _mapA;
/// <summary>
/// Map on Side B of the Bridge
/// </summary>
public MapSet MapB; /*{ get { return _mapB; } set { _mapB = value; } }*/
//protected MapSet _mapB;
/// <summary>
/// Which ways the bridge can be used
/// </summary>
public BridgeDirections WalkableDirections;
/// <summary>
/// All the points from Map A to Map B
/// </summary>
public List<IntVector3> MidPoints;
/// <summary>
/// If this map is a valid and can be used
/// </summary>
public bool ValidBridge = false;
public MapBridge(MapSet A, MapSet B, List<IntVector3> BridgePoints)
{
WalkableDirections = BridgeDirections.Both;
MapA = A;
MapB = B;
MidPoints = BridgePoints;
}
/// <summary>
/// Checks to see this bridge connects two maps
/// </summary>
/// <param name="startingMap"></param>
/// <param name="endingMap"></param>
/// <returns></returns>
public bool DirectConnects(MapSet startingMap, MapSet endingMap)
{
if (startingMap.InstanceID == MapA.InstanceID)
{
if (endingMap.InstanceID == MapB.InstanceID)
{
return true;
}
}
else if (startingMap.InstanceID == MapB.InstanceID)
{
if (endingMap.InstanceID == MapA.InstanceID)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks to see if you can walk on the bridge in this direction. Starting Map to Ending Map
/// </summary>
/// <param name="startingMap"></param>
/// <param name="endingMap"></param>
/// <returns></returns>
public bool CanWalkInDirection(MapSet startingMap, MapSet endingMap)
{
// Make sure these maps are even connected
if (!DirectConnects(startingMap, endingMap))
{
return false;
}
if (WalkableDirections == BridgeDirections.Both)
{
return true;
}
if (startingMap.InstanceID == MapA.InstanceID)
{
if (endingMap.InstanceID == MapB.InstanceID)
{
return WalkableDirections == BridgeDirections.A_To_B;
}
}
else if (startingMap.InstanceID == MapB.InstanceID)
{
if (endingMap.InstanceID == MapA.InstanceID)
{
return WalkableDirections == BridgeDirections.B_To_A;
}
}
return false;
}
/// <summary>
/// Gets the starting point for a Map if that map connects to this Bridge
/// </summary>
/// <param name="startingMap"></param>
/// <returns></returns>
public IntVector3 GetPoint(MapSet startingMap)
{
if (startingMap.InstanceID == MapA.InstanceID)
{
return MidPoints[0];
}
else if (startingMap.InstanceID == MapB.InstanceID)
{
return MidPoints[MidPoints.Count - 1];
}
return new IntVector3();
}
/// <summary>
/// Gets the ending point for a Map if that map connects to this Bridge
/// </summary>
/// <param name="startingMap"></param>
/// <returns></returns>
public IntVector3 GetOpposingPoint(MapSet startingMap)
{
if (startingMap.InstanceID == MapA.InstanceID)
{
return MidPoints[MidPoints.Count - 1];
}
else if (startingMap.InstanceID == MapB.InstanceID)
{
return MidPoints[0];
}
return new IntVector3();
}
/// <summary>
/// Checks to see if the given maps is on one side of the Bridge
/// </summary>
/// <param name="Map"></param>
/// <returns></returns>
public bool Contains(MapSet Map)
{
if (Map.InstanceID == MapA.InstanceID)
{
return true;
}
else if (Map.InstanceID == MapB.InstanceID)
{
return true;
}
return false;
}
/// <summary>
/// Gets the other Map if you know one side the bridge connects too
/// </summary>
/// <param name="Map"></param>
/// <returns></returns>
public MapSet Other(MapSet Map)
{
if (MapA.InstanceID == Map.InstanceID)
{
return MapB;
}
else if (MapB.InstanceID == Map.InstanceID)
{
return MapA;
}
return null;
}
public void RefreshWithNew(MapSet Map)
{
if (MapA.InstanceID == Map.InstanceID)
{
MapA = Map;
}
else if (MapB.InstanceID == Map.InstanceID)
{
MapB = Map;
}
}
public void Verify()
{
ValidBridge = MapA != null && MapB != null;
}
}
/// <summary>
/// A Set of points that the navigation can use to transfer a Agent from one map to another
/// </summary>
[Serializable]
public class MapSeam
{
/// <summary>
/// Map on Side A of the Bridge
/// </summary>
public MapSet MapA;
/// <summary>
/// The connection side for Map A
/// </summary>
public SeamConnectionSide MapA_ConectionSide;
/// <summary>
/// The Map A Starting Tile Index
/// </summary>
public IntVector2 MapA_StartingTileIndex;
/// <summary>
/// The Map A Ending Tile Index
/// </summary>
public IntVector2 MapA_EndingTileIndex;
/// <summary>
/// Map on Side B of the Bridge
/// </summary>
public MapSet MapB;
/// <summary>
/// The connection side for Map A
/// </summary>
public SeamConnectionSide MapB_ConectionSide;
/// <summary>
/// The Map B Starting Tile Index
/// </summary>
public IntVector2 MapB_StartingTileIndex;
/// <summary>
/// The Map B Ending Tile Index
/// </summary>
public IntVector2 MapB_EndingTileIndex;
/// <summary>
/// If this map is a valid and can be used
/// </summary>
public bool ValidSeam = false;
public MapSeam(MapSet A, MapSet B, SeamConnectionSide mapA_ConectionSide, SeamConnectionSide mapB_ConectionSide)
{
MapA = A;
MapB = B;
MapA_ConectionSide = mapA_ConectionSide;
MapB_ConectionSide = mapB_ConectionSide;
if (mapA_ConectionSide == SeamConnectionSide.South)
{
MapA_StartingTileIndex = IntVector2.Zero;
MapA_EndingTileIndex = new IntVector2(A.SetSettings.MapSettings.MapWidth - 1, 0);
}
else if (mapA_ConectionSide == SeamConnectionSide.North)
{
MapA_StartingTileIndex = new IntVector2(0, A.SetSettings.MapSettings.MapHeight - 1);
MapA_EndingTileIndex = new IntVector2(A.SetSettings.MapSettings.MapWidth - 1, A.SetSettings.MapSettings.MapHeight - 1);
}
else if (mapA_ConectionSide == SeamConnectionSide.West)
{
MapA_StartingTileIndex = IntVector2.Zero;
MapA_EndingTileIndex = new IntVector2(0, A.SetSettings.MapSettings.MapHeight - 1);
}
else if (mapA_ConectionSide == SeamConnectionSide.East)
{
MapA_StartingTileIndex = new IntVector2(A.SetSettings.MapSettings.MapWidth - 1, 0);
MapA_EndingTileIndex = new IntVector2(A.SetSettings.MapSettings.MapWidth - 1, A.SetSettings.MapSettings.MapHeight - 1);
}
if (mapB_ConectionSide == SeamConnectionSide.South)
{
MapB_StartingTileIndex = IntVector2.Zero;
MapB_EndingTileIndex = new IntVector2(B.SetSettings.MapSettings.MapWidth - 1, 0);
}
else if (mapB_ConectionSide == SeamConnectionSide.North)
{
MapB_StartingTileIndex = new IntVector2(0, B.SetSettings.MapSettings.MapHeight - 1);
MapB_EndingTileIndex = new IntVector2(B.SetSettings.MapSettings.MapWidth - 1, B.SetSettings.MapSettings.MapHeight - 1);
}
else if (mapB_ConectionSide == SeamConnectionSide.West)
{
MapB_StartingTileIndex = IntVector2.Zero;
MapB_EndingTileIndex = new IntVector2(0, B.SetSettings.MapSettings.MapHeight - 1);
}
else if (mapB_ConectionSide == SeamConnectionSide.East)
{
MapB_StartingTileIndex = new IntVector2(B.SetSettings.MapSettings.MapWidth - 1, 0);
MapB_EndingTileIndex = new IntVector2(B.SetSettings.MapSettings.MapWidth - 1, B.SetSettings.MapSettings.MapHeight - 1);
}
}
/// <summary>
/// Checks to see this bridge connects two maps
/// </summary>
/// <param name="startingMap"></param>
/// <param name="endingMap"></param>
/// <returns></returns>
public bool DirectConnects(MapSet startingMap, MapSet endingMap)
{
if (startingMap.InstanceID == MapA.InstanceID)
{
if (endingMap.InstanceID == MapB.InstanceID)
{
return true;
}
}
else if (startingMap.InstanceID == MapB.InstanceID)
{
if (endingMap.InstanceID == MapA.InstanceID)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks to see if the given maps is on one side of the Bridge
/// </summary>
/// <param name="Map"></param>
/// <returns></returns>
public bool Contains(MapSet Map)
{
if (Map.InstanceID == MapA.InstanceID)
{
return true;
}
else if (Map.InstanceID == MapB.InstanceID)
{
return true;
}
return false;
}
/// <summary>
/// Gets the other Map if you know one side the bridge connects too
/// </summary>
/// <param name="Map"></param>
/// <returns></returns>
public MapSet Other(MapSet Map)
{
if (MapA.InstanceID == Map.InstanceID)
{
return MapB;
}
else if (MapB.InstanceID == Map.InstanceID)
{
return MapA;
}
return null;
}
public void RefreshWithNew(MapSet Map)
{
if (MapA.InstanceID == Map.InstanceID)
{
MapA = Map;
}
else if (MapB.InstanceID == Map.InstanceID)
{
MapB = Map;
}
}
public void Verify()
{
ValidSeam = MapA != null && MapB != null;
}
/// <summary>
/// Gets the closest valid point for the starting map to the seam
/// </summary>
/// <param name="startingMap"></param>
/// <param name="start"></param>
/// <returns></returns>
public IntVector3 GetPoint(MapSet startingMap, IntVector3 start)
{
IntVector2 StartIndex = startingMap.GetClosestMapTileIndex(start);
if (startingMap == MapA)
{
if (MapA_ConectionSide == SeamConnectionSide.North)
{
StartIndex.y = startingMap.SetSettings.MapSettings.MapHeight - 1;
}
else if (MapA_ConectionSide == SeamConnectionSide.South)
{
StartIndex.y = 0;
}
else if (MapA_ConectionSide == SeamConnectionSide.West)
{
StartIndex.x = startingMap.SetSettings.MapSettings.MapWidth - 1;
}
else if (MapA_ConectionSide == SeamConnectionSide.East)
{
StartIndex.x = 0;
}
}
else
{
if (MapB_ConectionSide == SeamConnectionSide.North)
{
StartIndex.y = startingMap.SetSettings.MapSettings.MapHeight - 1;
}
else if (MapB_ConectionSide == SeamConnectionSide.South)
{
StartIndex.y = 0;
}
else if (MapB_ConectionSide == SeamConnectionSide.West)
{
StartIndex.x = startingMap.SetSettings.MapSettings.MapWidth - 1;
}
else if (MapB_ConectionSide == SeamConnectionSide.East)
{
StartIndex.x = 0;
}
}
return startingMap.GetWorldTileIndex(StartIndex);
}
public IntVector3 GetOppositePoint(MapSet startingMap, IntVector3 start)
{
IntVector3 StartingPoint = GetPoint(startingMap, start);
// Iterative Method - Would like to remove when calculative method is fixed
MapSet EndingMap = Other(startingMap);
if (EndingMap == MapA)
{
int ClosestScore = int.MaxValue;
IntVector2 ClosestEndTileIndex = new IntVector2();
for (int x = 0; x <= MapA_StartingTileIndex.x; x++ )
{
for (int y = 0; y <= MapA_EndingTileIndex.y; y++)
{
int NewScore = IntVector3.Displacement(StartingPoint, EndingMap.GetWorldTileIndex(x, y));
if (NewScore < ClosestScore)
{
ClosestScore = NewScore;
ClosestEndTileIndex = new IntVector2(x, y);
}
}
}
return EndingMap.GetWorldTileIndex(ClosestEndTileIndex);
}
else if (EndingMap == MapB)
{
int ClosestScore = int.MaxValue;
IntVector2 ClosestEndTileIndex = new IntVector2();
for (int x = 0; x <= MapB_StartingTileIndex.x; x++)
{
for (int y = 0; y <= MapB_EndingTileIndex.y; y++)
{
int NewScore = IntVector3.Displacement(StartingPoint, EndingMap.GetWorldTileIndex(x, y));
if (NewScore < ClosestScore)
{
ClosestScore = NewScore;
ClosestEndTileIndex = new IntVector2(x, y);
}
}
}
return EndingMap.GetWorldTileIndex(ClosestEndTileIndex);
}
// Caluclation Method - Needs Work somehwere
IntVector2 StartIndex = startingMap.GetClosestMapTileIndex(StartingPoint);
int startingMapLength;
int endingMapLength;
double startingMapRatio;
if (startingMap == MapA)
{
if (MapA_ConectionSide == SeamConnectionSide.South || MapA_ConectionSide == SeamConnectionSide.North)
{
startingMapLength = MapA.SetSettings.MapSettings.MapWidth;
startingMapRatio = (double)StartIndex.x / startingMapLength;
}
else
{
startingMapLength = MapA.SetSettings.MapSettings.MapHeight;
startingMapRatio = (double)StartIndex.y / startingMapLength;
}
if (MapB_ConectionSide == SeamConnectionSide.South || MapB_ConectionSide == SeamConnectionSide.North)
{
endingMapLength = MapB.SetSettings.MapSettings.MapWidth;
}
else
{
endingMapLength = MapB.SetSettings.MapSettings.MapHeight;
}
}
else
{
if (MapB_ConectionSide == SeamConnectionSide.South || MapB_ConectionSide == SeamConnectionSide.North)
{
startingMapLength = MapB.SetSettings.MapSettings.MapWidth;
startingMapRatio = (double)StartIndex.x / startingMapLength;
}
else
{
startingMapLength = MapB.SetSettings.MapSettings.MapHeight;
startingMapRatio = (double)StartIndex.y / startingMapLength;
}
if (MapA_ConectionSide == SeamConnectionSide.South || MapA_ConectionSide == SeamConnectionSide.North)
{
endingMapLength = MapA.SetSettings.MapSettings.MapWidth;
}
else
{
endingMapLength = MapA.SetSettings.MapSettings.MapHeight;
}
}
double mapLengthRatio = (double)endingMapLength / startingMapLength;
int mapEndFinalPoint = (int)System.Math.Round(mapLengthRatio * startingMapRatio * endingMapLength);
IntVector2 EndingPoint;
MapSet endingMap;
if (startingMap == MapA)
{
endingMap = MapB;
if (MapB_ConectionSide == SeamConnectionSide.South)
{
EndingPoint = new IntVector2(mapEndFinalPoint, 0);
}
else if (MapB_ConectionSide == SeamConnectionSide.North)
{
EndingPoint = new IntVector2(mapEndFinalPoint, MapB.SetSettings.MapSettings.MapHeight - 1);
}
else if (MapB_ConectionSide == SeamConnectionSide.West)
{
EndingPoint = new IntVector2(MapB.SetSettings.MapSettings.MapWidth - 1, mapEndFinalPoint);
}
else
{
EndingPoint = new IntVector2(0, mapEndFinalPoint);
}
}
else
{
endingMap = MapA;
if (MapA_ConectionSide == SeamConnectionSide.South)
{
EndingPoint = new IntVector2(mapEndFinalPoint, 0);
}
else if (MapA_ConectionSide == SeamConnectionSide.North)
{
EndingPoint = new IntVector2(mapEndFinalPoint, MapA.SetSettings.MapSettings.MapHeight - 1);
}
else if (MapA_ConectionSide == SeamConnectionSide.West)
{
EndingPoint = new IntVector2(MapA.SetSettings.MapSettings.MapWidth - 1, mapEndFinalPoint);
}
else
{
EndingPoint = new IntVector2(0, mapEndFinalPoint);
}
}
return endingMap.GetWorldTileIndex(EndingPoint);
}
}
/// <summary>
/// Tile Size Data for the Map, used to save default tiles to the Map
/// </summary>
[System.Serializable]
public class MapData
{
public byte[,] SavedTiles = new byte[100, 100];
public int Width = 100;
public int Height = 100;
public MapData()
{
}
public bool MakeSureSizeIsRight()
{
if (Width != SavedTiles.GetLength(0) || Height != SavedTiles.GetLength(1))
{
try
{
byte[,] PreviousTiles = SavedTiles;
int PreviousWidth = SavedTiles.GetLength(0);
int PreviousHeight = SavedTiles.GetLength(1);
SavedTiles = new byte[Width, Height];
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
if (x < PreviousWidth && y < PreviousHeight)
{
SavedTiles[x, y] = PreviousTiles[x, y];
}
else
{
SavedTiles[x, y] = (byte)TileTypes.Standard;
}
}
}
}
catch (Exception e)
{
throw new Exception($"{e.Message} \n {e.StackTrace} \n ----------------------------------------- \n {Width} vs {SavedTiles.GetLength(0)} & {Height} vs {SavedTiles.GetLength(1)}");
}
return true;
}
return false;
}
/// <summary>
/// Updates the size of the Data
/// </summary>
/// <param name="mapWidth"></param>
/// <param name="mapHeight"></param>
public void UpdateToMapSize(int mapWidth, int mapHeight)
{
if (mapWidth == Width && mapHeight == Height)
{
return;
}
try
{
byte[,] PreviousTiles = SavedTiles;
SavedTiles = new byte[mapWidth, mapHeight];
int PreviousWidth = Width;
int PreviousHeight = Height;
Width = mapWidth;
Height = mapHeight;
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
if (x < PreviousWidth && y < PreviousHeight)
{
SavedTiles[x, y] = PreviousTiles[x, y];
}
else
{
SavedTiles[x, y] = (byte)TileTypes.Standard;
}
}
}
}
catch
{
}
}
/// <summary>
/// Moves the values of the tiles
/// </summary>
/// <param name="HorizontalTranslation"></param>
/// <param name="VerticalTranslation"></param>
public void TransalateValues(int HorizontalTranslation, int VerticalTranslation)
{
if (HorizontalTranslation == 0 && VerticalTranslation == 0)
{
return;
}
byte[,] NewTiles = new byte[Width, Height];
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
int TranslatedX = x + HorizontalTranslation;
int TranslatedY = y + VerticalTranslation;
if (TranslatedX < 0 || TranslatedY < 0 || TranslatedX >= Width || TranslatedY >= Height)
{
NewTiles[x, y] = (byte)TileTypes.Standard;
}
else
{
NewTiles[x, y] = SavedTiles[TranslatedX, TranslatedY];
}
}
}
SavedTiles = NewTiles;
}
/// <summary>
/// Clears the map of all tile types and turns them into standard tiles
/// </summary>
public void CleanMap()
{
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
SavedTiles[x, y] = (byte)TileTypes.Standard;
}
}
}
}
/// <summary>
/// The Data that tells the speed multiplier for a given tile type
/// </summary>
[System.Serializable]
public class TileData
{
public TileTypes Type;
public byte Speed;
public TileData()
{
Type = TileTypes.Standard;
Speed = 100; // 100 = 100% Speed
}
public TileData(TileTypes tileType, byte speedMult)
{
Type = tileType;
Speed = speedMult;
}
}
/// <summary>
/// Used to keep track of a obstacles tiles and its state in the obstacle
/// </summary>
[Serializable]
public struct ObstacleData
{
public IntVector3 TileIndexKey;
public ObstacleValidationStates ValidationState;
public ObstacleData(IntVector3 Index)
{
TileIndexKey = Index;
ValidationState = ObstacleValidationStates.Added;
}
/// <summary>
/// Means this tile is no longer valid and will need to be checked if it still belongs on this obstacle
/// </summary>
public void UnValidate()
{
ValidationState = ObstacleValidationStates.Invalidated;
}
/// <summary>
/// Validates the tile, it belongs on this obstacle
/// </summary>
public void Validate()
{
ValidationState = ObstacleValidationStates.Validated;
}
}
}