diff --git a/CivOne-SelfContained.7z b/CivOne-SelfContained.7z
new file mode 100644
index 00000000..d6a89d73
Binary files /dev/null and b/CivOne-SelfContained.7z differ
diff --git a/CivOne_NeedRuntime.zip b/CivOne_NeedRuntime.zip
new file mode 100644
index 00000000..6898a25c
Binary files /dev/null and b/CivOne_NeedRuntime.zip differ
diff --git a/SavedGamesForTest.7z b/SavedGamesForTest.7z
new file mode 100644
index 00000000..ef4c084a
Binary files /dev/null and b/SavedGamesForTest.7z differ
diff --git a/src/AStar.cs b/src/AStar.cs
new file mode 100644
index 00000000..b08fd603
--- /dev/null
+++ b/src/AStar.cs
@@ -0,0 +1,671 @@
+// CivOne
+//
+// To the extent possible under law, the person who associated CC0 with
+// CivOne has waived all copyright and related or neighboring rights
+// to CivOne.
+
+//
+// You should have received a copy of the CC0 legalcode along with this
+// work. If not, see .
+
+using System.Collections.Generic;
+using System;
+using CivOne;
+using CivOne.Units;
+using System.Collections.ObjectModel;
+using CivOne.Enums;
+using CivOne.Tiles;
+using CivOne.Wonders;
+using System.Linq;
+
+// An algorithm using AStar for finding best route from start to finish when using "goto" command in CivOne.
+// "Stolen" from https://stackoverflow.com/questions/38387646/concise-universal-a-search-in-c-sharp by kaalus
+// and modified to my taste
+
+
+public class AStar
+{
+ protected sPosition _GoalPosition;
+ protected IUnit _unit;
+ protected static Map Map => Map.Instance;
+ public struct sPosition { public int iX; public int iY; };
+ protected static Node[,] _nodes = new Node [ Map.WIDTH, Map.HEIGHT ];
+ public class Node
+ {
+ public sPosition Position;
+ public sPosition PreviousPosition;
+ public float F, G, H;
+ public bool IsClosed;
+ public int Steps; // number of steps from StartPosition
+ }
+
+ protected int m_nodesCacheIndex;
+ protected List m_nodesCache = new List();
+ protected List m_openHeap = new List();
+ protected List m_neighbors = new List();
+ protected ICollection path = new Collection();
+
+ // User must override Neighbors, Cost and Heuristic functions to define search domain. Well.... I did replaced them
+ // It is optional to override StorageClear, StorageGet and StorageAdd functions.
+ // Default implementation of these storage functions uses a Dictionary, this works for all possible search domains. Don't like removed
+ // A domain-specific storage algorihm may be significantly faster.
+ // For example if searching on a finite 2D or 3D grid, storage using fixed array with each element representing one world point benchmarks an order of magnitude faster.
+
+
+ /* ******************************************************************************************************** */
+ /// Clear A* storage.
+ /// This will be called every time before a search starts and before any other user functions are called.
+ /// Optional override when using domain-optimized storage.
+ ///
+ protected virtual void StorageClear()
+ {
+ Array.Clear(_nodes, 0, _nodes.Length);
+ }
+
+ /* ******************************************************************************************************** */
+ private void BuildPathFromEndNode(ICollection path, Node startNode, Node endNode)
+ {
+ for (Node node = endNode; node != startNode; node = (Node)StorageGet(node.PreviousPosition))
+ {
+ path.Add(node.Position);
+ }
+ }
+
+ /* ******************************************************************************************************** */
+ private void HeapEnqueue(Node node)
+ {
+ m_openHeap.Add(node);
+ HeapifyFromPosToStart(m_openHeap.Count - 1);
+ }
+
+ /* ******************************************************************************************************** */
+
+ // Return node at pos 0 in open heap
+ private Node HeapDequeue()
+ {
+ Node result = m_openHeap[0];
+ if (m_openHeap.Count <= 1)
+ {
+ m_openHeap.Clear();
+ }
+ else
+ {
+ m_openHeap[0] = m_openHeap[m_openHeap.Count - 1];
+ m_openHeap.RemoveAt(m_openHeap.Count - 1);
+ HeapifyFromPosToEnd(0);
+ }
+ return result;
+ }
+
+ /* ******************************************************************************************************** */
+
+ private void HeapUpdate(Node node)
+ {
+ int pos = -1;
+ for (int i = 0; i < m_openHeap.Count; ++i)
+ {
+ if (m_openHeap[i] == node)
+ {
+ pos = i;
+ break;
+ }
+ }
+ HeapifyFromPosToStart(pos);
+ }
+
+ /* ******************************************************************************************************** */
+ // Locate the the open node with the lowest F ( heuristics + cost ) by moving it to position 0
+ private void HeapifyFromPosToEnd(int pos)
+ {
+ while (true)
+ {
+ int smallest = pos;
+ int left = 2 * pos + 1;
+ int right = 2 * pos + 2;
+ if (left < m_openHeap.Count && m_openHeap[left].F < m_openHeap[smallest].F)
+ smallest = left;
+ if (right < m_openHeap.Count && m_openHeap[right].F < m_openHeap[smallest].F)
+ smallest = right;
+ if (smallest != pos)
+ {
+ Node tmp = m_openHeap[smallest];
+ m_openHeap[smallest] = m_openHeap[pos];
+ m_openHeap[pos] = tmp;
+ pos = smallest;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ /* ******************************************************************************************************** */
+
+ private void HeapifyFromPosToStart(int pos)
+ {
+ int childPos = pos;
+ while (childPos > 0)
+ {
+ int parentPos = (childPos - 1) / 2;
+ Node parentNode = m_openHeap[parentPos];
+ Node childNode = m_openHeap[childPos];
+ if (parentNode.F > childNode.F)
+ {
+ m_openHeap[parentPos] = childNode;
+ m_openHeap[childPos] = parentNode;
+ childPos = parentPos;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ /* ******************************************************************************************************** */
+ private Node NewNode(sPosition position, sPosition previousPosition, float g, float h)
+ {
+ while (m_nodesCacheIndex >= m_nodesCache.Count)
+ {
+ m_nodesCache.Add(new Node());
+ }
+ Node node = m_nodesCache[m_nodesCacheIndex++];
+ node.Position = position;
+ node.PreviousPosition = previousPosition;
+ node.F = g + h;
+ node.G = g;
+ node.H = h;
+ node.IsClosed = false;
+ return node;
+ }
+
+ /* ******************************************************************************************************** */
+ /// Retrieve data from storage at given point.
+ /// Optional override when using domain-optimized storage.
+ /// Point to retrieve data at
+ /// Data stored for point p or null if nothing stored
+
+ protected Node StorageGet( sPosition position )
+ {
+ return _nodes[position.iX, position.iY];
+ }
+
+/* protected virtual object StorageGet(sPosition position)
+{
+ object data;
+ m_defaultStorage.TryGetValue(position, out data);
+ return data;
+}
+*/
+
+ /* ******************************************************************************************************** */
+ ///
+ /// Add data to storage at given point.
+ /// There will never be any data already stored at that point.
+ /// Optional override when using domain-optimized storage.
+ ///
+ /// Point to add data at
+ /// Data to add
+ protected virtual void StorageAdd(sPosition position, object data)
+ {
+ _nodes[position.iX, position.iY] = (Node)data;
+
+ // m_defaultStorage.Add(position, data);
+ }
+
+
+ /* ******************************************************************************************************** */
+ ///
+ /// Find best path from start to nearest goal.
+ /// Goal is any point for which Heuristic override returns 0.
+ /// If maxPositionsToCheck limit is reached, best path found so far is returned.
+ /// If there is no path to goal, path to a point nearest to goal is returned instead.
+ ///
+ /// Path will contain steps to reach goal from start in reverse order (first step at the end of collection)
+ ///
+ /// Maximum number of positions to check
+ /// True when path to goal was found, false if partial path only
+ protected bool FindPath(ICollection path, int maxPositionsToCheck = int.MaxValue)
+ {
+ // Check arguments
+ if (path == null)
+ {
+ throw new ArgumentNullException(nameof(path));
+ }
+
+ // Reset cache and storage
+ path.Clear();
+ m_nodesCacheIndex = 0;
+ m_openHeap.Clear();
+ StorageClear();
+
+ // Put start node
+ sPosition StartPosition;
+ StartPosition.iX = _unit.X;
+ StartPosition.iY = _unit.Y;
+ Node startNode = NewNode(StartPosition, StartPosition, 0, 0); // Start node point to itself
+ StorageAdd(StartPosition, startNode);
+ HeapEnqueue(startNode);
+
+ // Astar loop
+ Node bestNode = null;
+ int checkedPositions = 0;
+ while (true)
+ {
+ // Get next node from heap
+ Node currentNode = m_openHeap.Count > 0 ? HeapDequeue() : null;
+
+ // Check end conditions
+ if (currentNode == null || checkedPositions >= maxPositionsToCheck)
+ {
+ // No more nodes or limit reached, path not found, return path to best node if possible
+ if (bestNode != null)
+ {
+ BuildPathFromEndNode(path, startNode, bestNode);
+ }
+ return false;
+ }
+
+ else if (Heuristic(currentNode.Position) <= 0)
+ {
+ // Node is goal, return path
+ BuildPathFromEndNode(path, startNode, currentNode);
+ return true;
+ }
+
+ // Remember node with best heuristic; ignore start node
+ if (currentNode != startNode && (bestNode == null || currentNode.H < bestNode.H))
+ {
+ bestNode = currentNode;
+ }
+
+ // Move current node from open to closed in the storage
+ currentNode.IsClosed = true;
+ ++checkedPositions;
+
+ // Try all neighbors
+ m_neighbors.Clear();
+ Neighbors(currentNode, m_neighbors);
+ for (int i = 0; i < m_neighbors.Count; ++i)
+ {
+ // Get a neighbour
+ Node NeighborNode = m_neighbors[i];
+
+ // Check if this node is already in list(Closed)
+ Node NodeInList = (Node)StorageGet(NeighborNode.Position);
+
+ // If position was already analyzed, ignore step
+ if (NodeInList != null && NodeInList.IsClosed == true) // if alredy in "closed" list
+ {
+ continue;
+ }
+
+ float cost = Cost( currentNode.Position, NeighborNode.Position, currentNode.Steps );
+
+ // If position is not passable, ignore step
+ if( cost == float.PositiveInfinity)
+ {
+ continue;
+ }
+
+ // Calculate A* values
+ float g = currentNode.G + cost;
+ float h = Heuristic(currentNode.Position);
+ // Update or create new node at position
+ if (NodeInList != null)
+ {
+ // Update existing node if better
+ if (g < NodeInList.G)
+ {
+ NodeInList.G = g;
+ NodeInList.F = g + NodeInList.H;
+ NodeInList.PreviousPosition = currentNode.Position;
+ NodeInList.Steps = currentNode.Steps + 1;
+ HeapUpdate(NodeInList);
+ }
+ }
+ else
+ {
+ // Create new open node if not yet exists
+ Node node = NewNode(NeighborNode.Position, currentNode.Position, g, h);
+ node.Steps = currentNode.Steps + 1;
+ StorageAdd(node.Position, node);
+ HeapEnqueue(node);
+ }
+ }
+ }
+ }
+
+ /* ******************************************************************************************************** */
+ ///
+ /// Return all neighbors of the given point.
+ ///
+ /// Point to return neighbors for
+ /// Empty collection to fill with neighbors
+
+ protected static uint ii = 0;
+ protected void Neighbors(Node CurrNode, List neighbors)
+ {
+ int[,] aiRelPos = new int[,] { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } };
+
+ // ii = ++ii; // just to make som variation in sea and air routing
+ for ( uint i = 0; i < 8; i++)
+ {
+ sPosition CurPosition = CurrNode.Position;
+ sPosition NewPosition;
+ uint j = ( i + ii ) % 8;
+ NewPosition.iX = CurPosition.iX + aiRelPos[ j, 0 ];
+ NewPosition.iX = ( NewPosition.iX + Map.WIDTH ) % Map.WIDTH;
+ NewPosition.iY = CurPosition.iY + aiRelPos[ j, 1 ];
+ if( NewPosition.iY < 0 || NewPosition.iY >= Map.HEIGHT ) continue;
+ Node NewNode = new Node();
+ NewNode.Position = NewPosition;
+ NewNode.IsClosed = false;
+ NewNode.PreviousPosition = CurPosition;
+ neighbors.Add(NewNode);
+ }
+ }
+
+ /* ******************************************************************************************************** */
+ protected float GetMoveCost( int x, int y )
+ {
+
+ float fCost = 3; // plain etc...
+
+ if ( x < 0 || x >= Map.WIDTH ) return float.PositiveInfinity;
+ if ( y < 0 || y >= Map.HEIGHT ) return float.PositiveInfinity;
+
+ bool road = Map[ x, y ].Road;
+ bool railRoad = Map[ x, y ].RailRoad;
+ if ( road || railRoad ) return 1f;
+
+ switch ( Map[ x, y ].Type )
+ {
+ case Terrain.Forest: fCost = 6; break;
+ case Terrain.Swamp: fCost = 6; break;
+ case Terrain.Jungle: fCost = 6; break;
+ case Terrain.Hills: fCost = 6; break;
+ case Terrain.Mountains: fCost = 9; break;
+ case Terrain.Arctic: fCost = 6; break;
+ case Terrain.Ocean: fCost = float.PositiveInfinity; break;
+ }
+ return fCost;
+ }
+
+ /* ******************************************************************************************************** */
+ ///
+ /// Return cost of making a step from Positition to NextPosition (which are neighbors).
+ /// Cost equal to float.PositiveInfinity indicates that passage from Positition to NextPosition is impossible.
+ ///
+ protected static Game Game => Game.Instance;
+
+ protected float Cost(sPosition Positition, sPosition NextPosition, int iDistance )
+ {
+ float _cost = 1f;
+
+ ITile _tile = Map[NextPosition.iX, NextPosition.iY];
+
+ // Try Avoide nmys for the first steps by doing a detour
+ if( iDistance <= 9 && ( _unit.Class == UnitClass.Land || _unit.Class == UnitClass.Water))
+ {
+ if ( ChecknNighbours( NextPosition ) ) _cost = 5.0f; // increase cost if close to nmy Land/sea unit
+ }
+
+ if (_unit.Class == UnitClass.Land)
+ {
+ float fNextCost = GetMoveCost(NextPosition.iX, NextPosition.iY);
+ float fCost = GetMoveCost(Positition.iX, Positition.iY);
+ if (fNextCost == 1f && fCost == 1f) return _cost; // if going along a road/railroad
+ else if (fNextCost == 1f) return 3f * _cost; // if moving from terrain to road/railroad ( dont know if this is correct )
+ else return fNextCost * _cost;
+ }
+
+ else if (_unit.Class == UnitClass.Water)
+ {
+ bool IsMyCity = _tile.City != null && _tile.City.Owner == _unit.Owner;
+
+ if (_tile.Type != Terrain.Ocean && !IsMyCity ) return float.PositiveInfinity;
+
+ if (_unit.Type == UnitType.Trireme)
+ {
+
+ int iMoves = 3;
+ Player player = Game.GetPlayer(_unit.Owner);
+ if (player.HasWonder() || (!Game.WonderObsolete() && player.HasWonder())) iMoves = 4;
+
+ iDistance = (iDistance % iMoves) + 1;
+ byte _MovesLeft = _unit.MovesLeft;
+ int iDistanceToLand = DistanceToLand(NextPosition.iX, NextPosition.iY, iMoves);
+
+ // Code to make sure Trireme dont go too far from land and still able to cross gaps
+ // and that the Trireme is at land at end of turn
+ if (iMoves >= 4 && iDistanceToLand > 3) return float.PositiveInfinity;
+ if (iMoves == 3 && iDistanceToLand > 2) return float.PositiveInfinity;
+ if (_MovesLeft == iDistance && iDistanceToLand > 1) return float.PositiveInfinity;
+ if (_tile.City != null) return _cost * 2; // avoid citys
+ if (iDistanceToLand >= 2) return _cost - 0.1f; // Go offshore if safe
+ return 1.0f;
+
+ }
+ else if (_tile.City != null) return _cost * 2; // avoid citys
+
+ return _cost;
+ }
+ else if( _unit.Type == UnitType.Fighter || _unit.Type == UnitType.Bomber )
+ {
+ if (_tile.Units != null && _tile.Units.Any(u => u.Owner != _unit.Owner))
+ return float.PositiveInfinity; // don't attack
+
+ if (Math.Abs(Positition.iX - NextPosition.iX) + Math.Abs(Positition.iY - NextPosition.iY) == 1 )
+ _cost += 1; // Just to make a "nice" path
+
+ if( _tile.City != null )
+ _cost += 3; // avoide citys
+ }
+ return _cost; // nuke only
+ }
+
+ /* ******************************************************************************************************** */
+
+ protected int DistanceToLand( int iX, int iY, int iMoves )
+ {
+ for ( int iYY = -1; iYY <= 1; iYY++ )
+ for ( int iXX = -1; iXX <= 1; iXX++ )
+ {
+ int iXXX = ( iXX + iX + Map.WIDTH ) % Map.WIDTH;
+ if ( Map[ iXXX, iY + iYY ].Type != Terrain.Ocean )
+ return 1;
+ }
+
+ for(int iYY = -2; iYY <= 2; iYY++)
+ for(int iXX = -2; iXX <= 2; iXX++)
+ {
+ int iXXX = ( iXX + iX + Map.WIDTH ) % Map.WIDTH;
+ if (Map[ iXXX, iY + iYY ].Type != Terrain.Ocean )
+ return 2;
+ }
+ if ( iMoves == 4 ) // dont bother if "standard" Trireme
+ {
+ for ( int iYY = -3; iYY <= 3; iYY++ )
+ for ( int iXX = -3; iXX <= 3; iXX++ )
+ {
+ int iXXX = ( iXX + iX + Map.WIDTH ) % Map.WIDTH;
+ if ( Map[ iXXX, iY + iYY ].Type != Terrain.Ocean )
+ return 3;
+ }
+ }
+ return 10;
+ }
+
+ /* ******************************************************************************************************** */
+ /// Return an estimate of cost of moving from Positition to goal.
+ /// Return 0 when Positition is goal.
+ /// This is an estimate of sum of all costs along the path between Positition and the goal.
+ protected float Heuristic( sPosition Positition )
+ {
+ float fGoalF = 3.0f;
+
+ return Distance( Positition, _GoalPosition ) * fGoalF;
+ }
+
+ /* ******************************************************************************************************** */
+ private int Distance( sPosition P1, sPosition P2 )
+ {
+ return Common.Distance( P1.iX, P1.iY, P2.iX, P2.iY );
+ }
+
+ /* ******************************************************************************************************** */
+ private int Distance( City C, sPosition P2 )
+ {
+ return Common.Distance( C.X, C.Y, P2.iX, P2.iY );
+ }
+
+ /* ******************************************************************************************************** */
+ private int Distance( IUnit U, sPosition P2 )
+ {
+ return Common.Distance( U.X, U.Y, P2.iX, P2.iY );
+ }
+
+ /* ******************************************************************************************************** */
+ private sPosition Position( IUnit U )
+ {
+ sPosition position;
+ position.iX = U.X;
+ position.iY = U.Y;
+ return position;
+ }
+
+ /* ******************************************************************************************************** */
+ private sPosition Position( City C )
+ {
+ sPosition position;
+ position.iX = C.X;
+ position.iY = C.Y;
+ return position;
+ }
+
+ /* ******************************************************************************************************** */
+ private bool ChecknNighbours( sPosition position )
+ {
+ int iX, iY;
+ byte _owner = _unit.Owner;
+
+ iX = position.iX;
+ iY = position.iY;
+
+ for ( int iYY = -1; iYY <= 1; iYY++ )
+ for ( int iXX = -1; iXX <= 1; iXX++ )
+ {
+ int iXXX = ( iXX + iX + Map.WIDTH ) % Map.WIDTH;
+
+ ITile Nighbour = Map[ iXXX, iYY + iY ];
+ if ( Nighbour == null ) continue; // Ever happens ??
+ if ( Nighbour.Units.Any( u => u.Owner != _owner ))
+ return true; // enemy close
+ }
+ return false;
+ }
+
+ /* ******************************************************************************************************** */
+
+ /// Return the next position for a unit on its way to "goto"-Goal
+ ///
+ ///
+ ///
+
+ public sPosition FindPath(sPosition GoalPosition, IUnit unit)
+ {
+ sPosition NoPath = new sPosition();
+ NoPath.iX = -1;
+ int iCount;
+ AStar.sPosition[] Positions = new sPosition[200]; // For full path
+ City[] _OwnCities;
+
+ _unit = unit;
+ _GoalPosition = GoalPosition;
+ sPosition _position;
+
+ if( _unit.Type == UnitType.Fighter || _unit.Type == UnitType.Bomber )
+ {
+ // this is to ease movement of figthers and bombers by checking for refuling stations enroute to targets
+
+ IUnit _RefuelCarrier = null;
+ City _RefuelCity = null;
+ int _fuelLeft = ( (BaseUnitAir)_unit ).FuelLeft;
+ _position.iX = _unit.X;
+ _position.iY = _unit.Y;
+ int _DistanceToGoal = Distance( _GoalPosition, _position );
+
+ // Fuel left at goal
+ int _fuelLeftAtGoal = _fuelLeft - _DistanceToGoal;
+
+ int _CarrierDistance = 1000; // "inpossible" distance
+ int _CityDistance = 1000; // "inpossible" distance
+
+ // Check for carriers
+ IUnit[] _OwnCarriers = Game.GetUnits().Where( u => u.Owner == unit.Owner && u.Type == UnitType.Carrier ).ToArray();
+ if( _OwnCarriers.Length > 0 )
+ {
+ IUnit _nearestCarrierToGoal = _OwnCarriers.OrderBy( c => Distance( c, GoalPosition ) ).First();
+ _CarrierDistance = Distance( _nearestCarrierToGoal, GoalPosition );
+ }
+
+ _OwnCities = Game.GetCities().Where( c => _unit.Owner == c.Owner && c.Size > 0 ).ToArray();
+ // Just in case
+ if( _OwnCities.GetLength( 0 ) == 0 )
+ return NoPath;
+
+ // Get a fuel station close to goal
+ City _NearestCityToGoal = _OwnCities.OrderBy( c => Distance( c, GoalPosition )).First();
+ int _NearestFuelAtGoal = Math.Min( Distance( _NearestCityToGoal, _GoalPosition ), _CarrierDistance );
+
+ // Enought fuel at goal ?
+ if( _fuelLeftAtGoal < _NearestFuelAtGoal )
+ {
+ // NO ! Refueling is needed enroute. Find the city/carrier within fuel range nearest goal
+ // Check city for refuling
+ City[] _RefuelCitys = _OwnCities.Where( c => Distance( c, _position ) <= _fuelLeft ).ToArray();
+ if( _RefuelCitys.Length > 0 )
+ {
+ _RefuelCity = _RefuelCitys.OrderBy( c => Distance( c, _GoalPosition ) ).First();
+ _CityDistance = Distance( _RefuelCity, _GoalPosition );
+ }
+
+ // Check carrier for refuling
+ if( _CarrierDistance < 100 ) { // If we have a carrier
+ _CarrierDistance = 100;
+ IUnit[] _RefuelCarriers = _OwnCarriers.Where( c => Distance( c, _position ) <= _fuelLeft ).ToArray();
+ if( _RefuelCarriers.Length > 0 )
+ {
+ _RefuelCarrier = _RefuelCarriers.OrderBy( c => Distance( c, _GoalPosition ) ).First();
+ _CarrierDistance = Distance( _RefuelCarrier, _GoalPosition );
+ }
+ }
+ if( _CityDistance < _CarrierDistance )
+ {
+ // Reroute for refuling to city
+ _GoalPosition = Position( _RefuelCity );
+ }
+ else
+ {
+ // Reroute for refuling to carrier
+ if( _RefuelCarrier == null )
+ return NoPath; // something very wrong
+ _GoalPosition = Position( _RefuelCarrier );
+ }
+ }
+ }
+ if( !FindPath(path, 3000)) // Find path using AStar algorithm
+ {
+ return NoPath; // unable to find path
+ }
+ iCount = path.Count;
+ if( iCount == 0 )
+ return NoPath;
+ path.CopyTo(Positions, 0);
+ return Positions[iCount - 1]; // Get next "goto" position
+ }
+
+}
diff --git a/src/Common.cs b/src/Common.cs
index 1cf75621..cc134c6c 100644
--- a/src/Common.cs
+++ b/src/Common.cs
@@ -210,8 +210,23 @@ internal static int CitizenGroup(Citizen citizen)
public static bool InCityRange(int x1, int y1, int x2, int y2) => new Rectangle(x2 - 2, y2 - 2, 5, 5).IntersectsWith(new Rectangle(x1, y1, 1, 1));
public static int DistanceToTile(int x1, int y1, int x2, int y2) => Math.Max(Math.Min(Math.Abs(x2 - x1), Math.Abs(Map.WIDTH - (x2 - x1))), Math.Abs(y2 - y1));
-
- public static byte BinaryReadByte(BinaryReader reader, int position)
+
+ // The above function do not work properly at "dateline" ( methink is is just a "Math.Abs" that is missing ? ) I use the one below. JR
+ /* ******************************************************************************************************** */
+ public static int Distance( int X1, int Y1, int X2, int Y2 )
+ {
+ int X = Math.Abs( X1 - X2 );
+ int Y = Math.Abs( Y1 - Y2 );
+
+ if( X > Map.WIDTH / 2 )
+ {
+ X = Map.WIDTH - X;
+ }
+ if( X > Y ) return X;
+ return Y;
+ }
+
+ public static byte BinaryReadByte(BinaryReader reader, int position)
{
if (reader.BaseStream.Position != position)
reader.BaseStream.Seek(position, SeekOrigin.Begin);
@@ -358,4 +373,4 @@ public static bool AllowSaveGame
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Game.LoadSave.cs b/src/Game.LoadSave.cs
index 8c823286..4607029b 100644
--- a/src/Game.LoadSave.cs
+++ b/src/Game.LoadSave.cs
@@ -192,10 +192,17 @@ private Game(IGameData gameData)
foreach (byte fortifiedUnit in cityData.FortifiedUnits)
{
IUnit unit = CreateUnit((UnitType)fortifiedUnit, city.X, city.Y);
- unit.Status = (byte)(1 << 3);
- unit.Owner = city.Owner;
- unit.SetHome(city);
- _units.Add(unit);
+ if ( unit != null )
+ {
+ unit.Status = ( byte )( 1 << 3 );
+ unit.Owner = city.Owner;
+ unit.SetHome( city );
+ _units.Add( unit );
+ }
+ else
+ {
+ Log( "Unknown fortified unit found: {0}", fortifiedUnit );
+ }
}
cityList.Add(cityData.Id, city);
diff --git a/src/Game.cs b/src/Game.cs
index 6cb506d6..3ae936ab 100644
--- a/src/Game.cs
+++ b/src/Game.cs
@@ -11,6 +11,8 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using CivOne.Advances;
using CivOne.Buildings;
using CivOne.Civilizations;
@@ -203,22 +205,53 @@ public void Update()
{
if (unit != null && !unit.Goto.IsEmpty)
{
- int distance = unit.Tile.DistanceTo(unit.Goto);
- ITile[] tiles = (unit as BaseUnit).MoveTargets.OrderBy(x => x.DistanceTo(unit.Goto)).ThenBy(x => x.Movement).ToArray();
- if (tiles.Length == 0 || tiles[0].DistanceTo(unit.Goto) > distance)
- {
- // No valid tile to move to, cancel goto
- unit.Goto = Point.Empty;
- return;
+
+ ITile[] tiles = (unit as BaseUnit).MoveTargets.OrderBy(x => x.DistanceTo(unit.Goto)).ThenBy(x => x.Movement).ToArray();
+
+ if( Settings.Instance.PathFinding )
+ {
+ /* Use AStar */
+ AStar.sPosition Destination, Pos;
+ Destination.iX = unit.Goto.X;
+ Destination.iY = unit.Goto.Y;
+ Pos.iX = unit.X;
+ Pos.iY = unit.Y;
+
+ if( Destination.iX == Pos.iX && Destination.iY == Pos.iY )
+ {
+ unit.Goto = Point.Empty; // eh... never mind
+ return;
+ }
+ AStar AStar = new AStar();
+ AStar.sPosition NextPosition = AStar.FindPath( Destination, unit );
+ if (NextPosition.iX < 0)
+ { // if no path found
+ unit.Goto = Point.Empty;
+ return;
+ }
+ unit.MoveTo(NextPosition.iX - Pos.iX, NextPosition.iY - Pos.iY);
+ return;
+
}
- else if (tiles[0].DistanceTo(unit.Goto) == distance)
+ else
{
- // Distance is unchanged, 50% chance to cancel goto
- if (Common.Random.Next(0, 100) < 50)
+
+ int distance = unit.Tile.DistanceTo(unit.Goto);
+ if (tiles.Length == 0 || tiles[0].DistanceTo(unit.Goto) > distance)
{
+ // No valid tile to move to, cancel goto
unit.Goto = Point.Empty;
return;
}
+ else if (tiles[0].DistanceTo(unit.Goto) == distance)
+ {
+ // Distance is unchanged, 50% chance to cancel goto
+ if (Common.Random.Next(0, 100) < 50)
+ {
+ unit.Goto = Point.Empty;
+ return;
+ }
+ }
}
unit.MoveTo(tiles[0].X - unit.X, tiles[0].Y - unit.Y);
@@ -442,7 +475,8 @@ public IUnit ActiveUnit
_activeUnit = 0;
// Does the current unit still have moves left?
- if (_units[_activeUnit].Owner == _currentPlayer && (_units[_activeUnit].MovesLeft > 0 || _units[_activeUnit].PartMoves > 0) && !_units[_activeUnit].Sentry && !_units[_activeUnit].Fortify)
+ if (_units[_activeUnit].Owner == _currentPlayer && (_units[_activeUnit].MovesLeft > 0 || _units[_activeUnit].PartMoves > 0)
+ && !_units[_activeUnit].Sentry && !_units[_activeUnit].Fortify)
return _units[_activeUnit];
// Task busy, don't change the active unit
@@ -460,7 +494,8 @@ public IUnit ActiveUnit
}
// Loop through units
- while (_units[_activeUnit].Owner != _currentPlayer || (_units[_activeUnit].MovesLeft == 0 && _units[_activeUnit].PartMoves == 0) || (_units[_activeUnit].Sentry || _units[_activeUnit].Fortify))
+ while (_units[_activeUnit].Owner != _currentPlayer || (_units[_activeUnit].MovesLeft == 0 && _units[_activeUnit].PartMoves == 0)
+ || (_units[_activeUnit].Sentry || _units[_activeUnit].Fortify))
{
_activeUnit++;
if (_activeUnit >= _units.Count)
diff --git a/src/IO/RLE.cs b/src/IO/RLE.cs
index f43de7a2..c3d1a2b9 100644
--- a/src/IO/RLE.cs
+++ b/src/IO/RLE.cs
@@ -59,12 +59,12 @@ public static byte[] Encode(byte[] input)
if (repeat == 1) continue;
ms.WriteByte(RLE_REPEAT);
ms.WriteByte(repeat);
- if (repeat == RLE_REPEAT) ms.WriteByte(RLE_ESCAPE);
- i += (repeat - 1);
+// if (repeat == RLE_REPEAT) ms.WriteByte(RLE_ESCAPE); Never RLE_ESCAPE after repeat byte
+ i += (repeat - 1);
}
return ms.ToArray();
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Map.Generate.cs b/src/Map.Generate.cs
index b472781a..2bd05ba2 100644
--- a/src/Map.Generate.cs
+++ b/src/Map.Generate.cs
@@ -16,450 +16,443 @@
using CivOne.Screens;
using CivOne.Tasks;
using CivOne.Tiles;
+using System.Collections;
namespace CivOne
{
- public partial class Map
- {
- private bool[,] GenerateLandChunk()
- {
- bool[,] stencil = new bool[WIDTH, HEIGHT];
-
- int x = Common.Random.Next(4, WIDTH - 4);
- int y = Common.Random.Next(8, HEIGHT - 8);
- int pathLength = Common.Random.Next(1, 64);
-
- for (int i = 0; i < pathLength; i++)
- {
- stencil[x, y] = true;
- stencil[x + 1, y] = true;
- stencil[x, y + 1] = true;
- switch (Common.Random.Next(4))
- {
- case 0: y--; break;
- case 1: x++; break;
- case 2: y++; break;
- default: x--; break;
- }
+ public partial class Map
+ {
+ private bool[,] GenerateLandChunk()
+ {
+ bool[,] stencil = new bool[ WIDTH, HEIGHT ];
- if (x < 3 || y < 3 || x > (WIDTH - 4) || y > (HEIGHT - 5)) break;
- }
+ int x = Common.Random.Next( 4, WIDTH - 4 );
+ int y = Common.Random.Next( 8, HEIGHT - 8 );
+ int pathLength = Common.Random.Next( 1, 64 );
- return stencil;
- }
+ for( int i = 0; i < pathLength; i++ )
+ {
+ stencil[ x, y ] = true;
+ stencil[ x + 1, y ] = true;
+ stencil[ x, y + 1 ] = true;
+ switch( Common.Random.Next( 4 ) )
+ {
+ case 0: y--; break;
+ case 1: x++; break;
+ case 2: y++; break;
+ default: x--; break;
+ }
- private bool TileHasHut(int x, int y)
- {
- if (y < 2 || y > (HEIGHT - 3)) return false;
- return ModGrid(x, y) == ((x / 4) * 13 + (y / 4) * 11 + _terrainMasterWord + 8) % 32;
- }
+ if( x < 3 || y < 3 || x > ( WIDTH - 4 ) || y > ( HEIGHT - 5 ) ) break;
+ }
- private int[,] GenerateLandMass()
- {
- Log("Map: Stage 1 - Generate land mass");
-
- int[,] elevation = new int[WIDTH, HEIGHT];
- int landMassSize = (int)((WIDTH * HEIGHT) / 12.5) * (_landMass + 2);
-
- // Generate the landmass
- while ((from int tile in elevation where tile > 0 select 1).Sum() < landMassSize)
- {
- bool[,] chunk = GenerateLandChunk();
- for (int y = 0; y < HEIGHT; y++)
- for (int x = 0; x < WIDTH; x++)
- {
- if (chunk[x, y]) elevation[x, y]++;
- }
- }
-
- // remove narrow passages
- for (int y = 0; y < (HEIGHT - 1); y++)
- for (int x = 0; x < (WIDTH - 1); x++)
- {
- if ((elevation[x, y] > 0 && elevation[x + 1, y + 1] > 0) && (elevation[x + 1, y] == 0 && elevation[x, y + 1] == 0))
- {
- elevation[x + 1, y]++;
- elevation[x, y + 1]++;
- }
- else if ((elevation[x, y] == 0 && elevation[x + 1, y + 1] == 0) && (elevation[x + 1, y] > 0 && elevation[x, y + 1] > 0))
- {
- elevation[x + 1, y + 1]++;
- }
- }
-
- return elevation;
- }
-
- private int[,] TemperatureAdjustments()
- {
- Log("Map: Stage 2 - Temperature adjustments");
-
- int[,] latitude = new int[WIDTH, HEIGHT];
-
- for (int y = 0; y < HEIGHT; y++)
- for (int x = 0; x < WIDTH; x++)
- {
- int l = (int)(((float)y / HEIGHT) * 50) - 29;
- l += Common.Random.Next(7);
- if (l < 0) l = -l;
- l += 1 - _temperature;
-
- l = (l / 6) + 1;
-
- switch (l)
- {
- case 0:
- case 1: latitude[x, y] = 0; break;
- case 2:
- case 3: latitude[x, y] = 1; break;
- case 4:
- case 5: latitude[x, y] = 2; break;
- case 6:
- default: latitude[x, y] = 3; break;
- }
- }
-
- return latitude;
- }
-
- private void MergeElevationAndLatitude(int[,] elevation, int[,] latitude)
- {
- Log("Map: Stage 3 - Merge elevation and latitude into the map");
-
- // merge elevation and latitude into the map
- for (int y = 0; y < HEIGHT; y++)
- for (int x = 0; x < WIDTH; x++)
- {
- bool special = TileIsSpecial(x, y);
- switch (elevation[x, y])
- {
- case 0: _tiles[x, y] = new Ocean(x, y, special); break;
- case 1:
- {
- switch (latitude[x, y])
- {
- case 0: _tiles[x, y] = new Desert(x, y, special); break;
- case 1: _tiles[x, y] = new Plains(x, y, special); break;
- case 2: _tiles[x, y] = new Tundra(x, y, special); break;
- case 3: _tiles[x, y] = new Arctic(x, y, special); break;
- }
- }
- break;
- case 2: _tiles[x, y] = new Hills(x, y, special); break;
- default: _tiles[x, y] = new Mountains(x, y, special); break;
- }
- }
- }
-
- private void ClimateAdjustments()
- {
- Log("Map: Stage 4 - Climate adjustments");
-
- int wetness, latitude;
-
- for (int y = 0; y < HEIGHT; y++)
- {
- int yy = (int)(((float)y / HEIGHT) * 50);
-
- wetness = 0;
- latitude = Math.Abs(25 - yy);
-
- for (int x = 0; x < WIDTH; x++)
- {
- if (_tiles[x, y].Type == Terrain.Ocean)
- {
- // wetness yield
- int wy = latitude - 12;
- if (wy < 0) wy = -wy;
- wy += (_climate * 4);
-
- if (wy > wetness) wetness++;
- }
- else if (wetness > 0)
- {
- bool special = TileIsSpecial(x, y);
- int rainfall = Common.Random.Next(7 - (_climate * 2));
- wetness -= rainfall;
-
- switch (_tiles[x, y].Type)
- {
- case Terrain.Plains: _tiles[x, y] = new Grassland(x, y); break;
- case Terrain.Tundra: _tiles[x, y] = new Arctic(x, y, special); break;
- case Terrain.Hills: _tiles[x, y] = new Forest(x, y, special); break;
- case Terrain.Desert: _tiles[x, y] = new Plains(x, y, special); break;
- case Terrain.Mountains: wetness -= 3; break;
- }
- }
- }
-
- wetness = 0;
- latitude = Math.Abs(25 - yy);
-
- // reset row wetness to 0
- for (int x = WIDTH - 1; x >= 0; x--)
- {
- if (_tiles[x, y].Type == Terrain.Ocean)
- {
- // wetness yield
- int wy = (latitude / 2) + _climate;
- if (wy > wetness) wetness++;
- }
- else if (wetness > 0)
- {
- bool special = TileIsSpecial(x, y);
- int rainfall = Common.Random.Next(7 - (_climate * 2));
- wetness -= rainfall;
-
- switch (_tiles[x, y].Type)
- {
- case Terrain.Swamp: _tiles[x, y] = new Forest(x, y, special); break;
- case Terrain.Plains: new Grassland(x, y); break;
- case Terrain.Grassland1:
- case Terrain.Grassland2: _tiles[x, y] = new Jungle(x, y, special); break;
- case Terrain.Hills: _tiles[x, y] = new Forest(x, y, special); break;
- case Terrain.Mountains: _tiles[x, y] = new Forest(x, y, special); wetness -= 3; break;
- case Terrain.Desert: _tiles[x, y] = new Plains(x, y, special); break;
- }
- }
- }
- }
- }
-
- private void AgeAdjustments()
- {
- Log("Map: Stage 5 - Age adjustments");
-
- int x = 0;
- int y = 0;
- int ageRepeat = (int)(((float)800 * (1 + _age) / (80 * 50)) * (WIDTH * HEIGHT));
- for (int i = 0; i < ageRepeat; i++)
- {
- if (i % 2 == 0)
- {
- x = Common.Random.Next(WIDTH);
- y = Common.Random.Next(HEIGHT);
- }
- else
- {
- switch (Common.Random.Next(8))
- {
- case 0: { x--; y--; break; }
- case 1: { y--; break; }
- case 2: { x++; y--; break; }
- case 3: { x--; break; }
- case 4: { x++; break; }
- case 5: { x--; y++; break; }
- case 6: { y++; break; }
- default: { x++; y++; break; }
- }
- if (x < 0) x = 1;
- if (y < 0) y = 1;
- if (x >= WIDTH) x = WIDTH - 2;
- if (y >= HEIGHT) y = HEIGHT - 2;
- }
-
- bool special = TileIsSpecial(x, y);
- switch (_tiles[x, y].Type)
- {
- case Terrain.Forest: _tiles[x, y] = new Jungle(x, y, special); break;
- case Terrain.Swamp: _tiles[x, y] = new Grassland(x, y); break;
- case Terrain.Plains: _tiles[x, y] = new Hills(x, y, special); break;
- case Terrain.Tundra: _tiles[x, y] = new Hills(x, y, special); break;
- case Terrain.River: _tiles[x, y] = new Forest(x, y, special); break;
- case Terrain.Grassland1:
- case Terrain.Grassland2: _tiles[x, y] = new Forest(x, y, special); break;
- case Terrain.Jungle: _tiles[x, y] = new Swamp(x, y, special); break;
- case Terrain.Hills: _tiles[x, y] = new Mountains(x, y, special); break;
- case Terrain.Mountains:
- if ((x == 0 || _tiles[x - 1, y - 1].Type != Terrain.Ocean) &&
- (y == 0 || _tiles[x + 1, y - 1].Type != Terrain.Ocean) &&
- (x == (WIDTH - 1) || _tiles[x + 1, y + 1].Type != Terrain.Ocean) &&
- (y == (HEIGHT - 1) || _tiles[x - 1, y + 1].Type != Terrain.Ocean))
- _tiles[x, y] = new Ocean(x, y, special);
- break;
- case Terrain.Desert: _tiles[x, y] = new Plains(x, y, special); break;
- case Terrain.Arctic: _tiles[x, y] = new Mountains(x, y, special); break;
- }
- }
- }
-
- private void CreateRivers()
- {
- Log("Map: Stage 6 - Create rivers");
-
- int rivers = 0;
- for (int i = 0; i < 256 && rivers < ((_climate + _landMass) * 2) + 6; i++)
- {
- ITile[,] tilesBackup = (ITile[,])_tiles.Clone();
-
- int riverLength = 0;
- int varA = Common.Random.Next(4) * 2;
- bool nearOcean = false;
-
- ITile tile = null;
- while (tile == null)
- {
- int x = Common.Random.Next(WIDTH);
- int y = Common.Random.Next(HEIGHT);
- if (_tiles[x, y].Type == Terrain.Hills) tile = _tiles[x, y];
- }
- do
- {
- _tiles[tile.X, tile.Y] = new River(tile.X, tile.Y);
- int varB = varA;
- int varC = Common.Random.Next(2);
- varA = (((varC - riverLength % 2) * 2 + varA) & 0x07);
- varB = 7 - varB;
-
- riverLength++;
-
- nearOcean = NearOcean(tile.X, tile.Y);
- switch (varA)
- {
- case 0:
- case 1: tile = _tiles[tile.X, tile.Y - 1]; break;
- case 2:
- case 3: tile = _tiles[tile.X + 1, tile.Y]; break;
- case 4:
- case 5: tile = _tiles[tile.X, tile.Y + 1]; break;
- case 6:
- case 7: tile = _tiles[tile.X - 1, tile.Y]; break;
- }
- }
- while (!nearOcean && (tile.GetType() != typeof(Ocean) && tile.GetType() != typeof(River) && tile.GetType() != typeof(Mountains)));
-
- if ((nearOcean || tile.Type == Terrain.River) && riverLength > 5)
- {
- rivers++;
- ITile[,] mapPart = this[tile.X - 3, tile.Y - 3, 7, 7];
- for (int x = 0; x < 7; x++)
- for (int y = 0; y < 7; y++)
- {
- if (mapPart[x, y] == null) continue;
- int xx = mapPart[x, y].X, yy = mapPart[x, y].Y;
- if (_tiles[xx, yy].Type == Terrain.Forest)
- _tiles[xx, yy] = new Jungle(xx, yy, TileIsSpecial(x, y));
- }
- }
- else
- {
- _tiles = (ITile[,])tilesBackup.Clone(); ;
- }
- }
- }
-
- private void CalculateContinentSize()
- {
- // TODO: This function needs to be been checked against the original function. It does not yet work as intended.
- Log("Map: Calculate continent and ocean sizes");
-
- // Initial continents
- byte continentId = 0;
- for (int y = 0; y < HEIGHT; y++)
- for (int x = 0; x < WIDTH; x++)
- {
- ITile tile = this[x, y], north = this[x, y - 1], west = this[x - 1, y];
-
- if (north != null && (north.IsOcean == tile.IsOcean) && north.ContinentId > 0)
- {
- tile.ContinentId = north.ContinentId;
- }
- else if (west != null && (west.IsOcean == tile.IsOcean) && west.ContinentId > 0)
- {
- tile.ContinentId = west.ContinentId;
- }
- else
- {
- tile.ContinentId = ++continentId;
- }
-
- if (north == null || west == null) continue;
- if (north.IsOcean != west.IsOcean) continue;
-
- // Merge continents
- if (north.ContinentId != west.ContinentId && north.ContinentId > 0 && west.ContinentId > 0)
- {
- int northCount = AllTiles().Count(t => t.ContinentId == north.ContinentId);
- int westCount = AllTiles().Count(t => t.ContinentId == west.ContinentId);
- if (northCount > westCount)
- {
- foreach (ITile westTile in AllTiles().Where(t => t.ContinentId == west.ContinentId))
- {
- westTile.ContinentId = north.ContinentId;
- }
- continue;
- }
- foreach (ITile northTile in AllTiles().Where(t => t.ContinentId == north.ContinentId))
- {
- northTile.ContinentId = west.ContinentId;
- }
- }
- }
-
- for (int x = 0; x < WIDTH; x++)
- for (int y = 0; y < HEIGHT; y++)
- {
- ITile tile = this[x, y], north = this[x, y - 1], west = this[x - 1, y];
-
- if (north == null || west == null) continue;
- if (north.IsOcean != west.IsOcean) continue;
-
- // Merge continents
- if (north.ContinentId != west.ContinentId && north.ContinentId > 0 && west.ContinentId > 0)
- {
- int northCount = AllTiles().Count(t => t.ContinentId == north.ContinentId);
- int westCount = AllTiles().Count(t => t.ContinentId == west.ContinentId);
- if (northCount > westCount)
- {
- foreach (ITile westTile in AllTiles().Where(t => t.ContinentId == west.ContinentId))
- {
- westTile.ContinentId = north.ContinentId;
- }
- continue;
- }
- foreach (ITile northTile in AllTiles().Where(t => t.ContinentId == north.ContinentId))
- {
- northTile.ContinentId = west.ContinentId;
- }
- }
- }
-
- List continents = new List();
- for (int i = 0; i <= 255; i++)
- {
- if (!AllTiles().Any(x => x.ContinentId == i)) continue;
- continents.Add(AllTiles().Where(x => x.ContinentId == i).ToArray());
- }
- for (byte i = 1; i < 15; i++)
- {
- ITile[] continent = continents.OrderByDescending(x => x.Length).FirstOrDefault();
- if (continent == null) break;
-
- continents.Remove(continent);
- foreach (ITile tile in continent)
- {
- tile.ContinentId = i;
- }
- }
- foreach (ITile[] continent in continents)
- foreach (ITile tile in continent)
- {
- tile.ContinentId = 15;
- }
- }
-
- private void CreatePoles()
+ return stencil;
+ }
+
+ private bool TileHasHut( int x, int y )
+ {
+ if( y < 2 || y > ( HEIGHT - 3 ) ) return false;
+ return ModGrid( x, y ) == ( ( x / 4 ) * 13 + ( y / 4 ) * 11 + _terrainMasterWord + 8 ) % 32;
+ }
+
+ private int[,] GenerateLandMass()
+ {
+ Log( "Map: Stage 1 - Generate land mass" );
+
+ int[,] elevation = new int[ WIDTH, HEIGHT ];
+ int landMassSize = (int)( ( WIDTH * HEIGHT ) / 12.5 ) * ( _landMass + 2 );
+
+ // Generate the landmass
+ while( ( from int tile in elevation where tile > 0 select 1 ).Sum() < landMassSize )
+ {
+ bool[,] chunk = GenerateLandChunk();
+ for( int y = 0; y < HEIGHT; y++ )
+ for( int x = 0; x < WIDTH; x++ )
+ {
+ if( chunk[ x, y ] ) elevation[ x, y ]++;
+ }
+ }
+
+ // remove narrow passages
+ for( int y = 0; y < ( HEIGHT - 1 ); y++ )
+ for( int x = 0; x < ( WIDTH - 1 ); x++ )
+ {
+ if( ( elevation[ x, y ] > 0 && elevation[ x + 1, y + 1 ] > 0 ) && ( elevation[ x + 1, y ] == 0 && elevation[ x, y + 1 ] == 0 ) )
+ {
+ elevation[ x + 1, y ]++;
+ elevation[ x, y + 1 ]++;
+ }
+ else if( ( elevation[ x, y ] == 0 && elevation[ x + 1, y + 1 ] == 0 ) && ( elevation[ x + 1, y ] > 0 && elevation[ x, y + 1 ] > 0 ) )
+ {
+ elevation[ x + 1, y + 1 ]++;
+ }
+ }
+
+ return elevation;
+ }
+
+ private int[,] TemperatureAdjustments()
+ {
+ Log( "Map: Stage 2 - Temperature adjustments" );
+
+ int[,] latitude = new int[ WIDTH, HEIGHT ];
+
+ for( int y = 0; y < HEIGHT; y++ )
+ for( int x = 0; x < WIDTH; x++ )
+ {
+ int l = (int)( ( (float)y / HEIGHT ) * 50 ) - 29;
+ l += Common.Random.Next( 7 );
+ if( l < 0 ) l = -l;
+ l += 1 - _temperature;
+
+ l = ( l / 6 ) + 1;
+
+ switch( l )
+ {
+ case 0:
+ case 1: latitude[ x, y ] = 0; break;
+ case 2:
+ case 3: latitude[ x, y ] = 1; break;
+ case 4:
+ case 5: latitude[ x, y ] = 2; break;
+ case 6:
+ default: latitude[ x, y ] = 3; break;
+ }
+ }
+
+ return latitude;
+ }
+
+ private void MergeElevationAndLatitude( int[,] elevation, int[,] latitude )
+ {
+ Log( "Map: Stage 3 - Merge elevation and latitude into the map" );
+
+ // merge elevation and latitude into the map
+ for( int y = 0; y < HEIGHT; y++ )
+ for( int x = 0; x < WIDTH; x++ )
+ {
+ bool special = TileIsSpecial( x, y );
+ switch( elevation[ x, y ] )
+ {
+ case 0: _tiles[ x, y ] = new Ocean( x, y, special ); break;
+ case 1:
+ {
+ switch( latitude[ x, y ] )
+ {
+ case 0: _tiles[ x, y ] = new Desert( x, y, special ); break;
+ case 1: _tiles[ x, y ] = new Plains( x, y, special ); break;
+ case 2: _tiles[ x, y ] = new Tundra( x, y, special ); break;
+ case 3: _tiles[ x, y ] = new Arctic( x, y, special ); break;
+ }
+ }
+ break;
+ case 2: _tiles[ x, y ] = new Hills( x, y, special ); break;
+ default: _tiles[ x, y ] = new Mountains( x, y, special ); break;
+ }
+ }
+ }
+
+ private void ClimateAdjustments()
+ {
+ Log( "Map: Stage 4 - Climate adjustments" );
+
+ int wetness, latitude;
+
+ for( int y = 0; y < HEIGHT; y++ )
+ {
+ int yy = (int)( ( (float)y / HEIGHT ) * 50 );
+
+ wetness = 0;
+ latitude = Math.Abs( 25 - yy );
+
+ for( int x = 0; x < WIDTH; x++ )
+ {
+ if( _tiles[ x, y ].Type == Terrain.Ocean )
+ {
+ // wetness yield
+ int wy = latitude - 12;
+ if( wy < 0 ) wy = -wy;
+ wy += ( _climate * 4 );
+
+ if( wy > wetness ) wetness++;
+ }
+ else if( wetness > 0 )
+ {
+ bool special = TileIsSpecial( x, y );
+ int rainfall = Common.Random.Next( 7 - ( _climate * 2 ) );
+ wetness -= rainfall;
+
+ switch( _tiles[ x, y ].Type )
+ {
+ case Terrain.Plains: _tiles[ x, y ] = new Grassland( x, y ); break;
+ case Terrain.Tundra: _tiles[ x, y ] = new Arctic( x, y, special ); break;
+ case Terrain.Hills: _tiles[ x, y ] = new Forest( x, y, special ); break;
+ case Terrain.Desert: _tiles[ x, y ] = new Plains( x, y, special ); break;
+ case Terrain.Mountains: wetness -= 3; break;
+ }
+ }
+ }
+
+ wetness = 0;
+ latitude = Math.Abs( 25 - yy );
+
+ // reset row wetness to 0
+ for( int x = WIDTH - 1; x >= 0; x-- )
+ {
+ if( _tiles[ x, y ].Type == Terrain.Ocean )
+ {
+ // wetness yield
+ int wy = ( latitude / 2 ) + _climate;
+ if( wy > wetness ) wetness++;
+ }
+ else if( wetness > 0 )
+ {
+ bool special = TileIsSpecial( x, y );
+ int rainfall = Common.Random.Next( 7 - ( _climate * 2 ) );
+ wetness -= rainfall;
+
+ switch( _tiles[ x, y ].Type )
+ {
+ case Terrain.Swamp: _tiles[ x, y ] = new Forest( x, y, special ); break;
+ case Terrain.Plains: new Grassland( x, y ); break;
+ case Terrain.Grassland1:
+ case Terrain.Grassland2: _tiles[ x, y ] = new Jungle( x, y, special ); break;
+ case Terrain.Hills: _tiles[ x, y ] = new Forest( x, y, special ); break;
+ case Terrain.Mountains: _tiles[ x, y ] = new Forest( x, y, special ); wetness -= 3; break;
+ case Terrain.Desert: _tiles[ x, y ] = new Plains( x, y, special ); break;
+ }
+ }
+ }
+ }
+ }
+
+ private void AgeAdjustments()
+ {
+ Log( "Map: Stage 5 - Age adjustments" );
+
+ int x = 0;
+ int y = 0;
+ int ageRepeat = (int)( ( (float)800 * ( 1 + _age ) / ( 80 * 50 ) ) * ( WIDTH * HEIGHT ) );
+ for( int i = 0; i < ageRepeat; i++ )
+ {
+ if( i % 2 == 0 )
+ {
+ x = Common.Random.Next( WIDTH );
+ y = Common.Random.Next( HEIGHT );
+ }
+ else
+ {
+ switch( Common.Random.Next( 8 ) )
+ {
+ case 0: { x--; y--; break; }
+ case 1: { y--; break; }
+ case 2: { x++; y--; break; }
+ case 3: { x--; break; }
+ case 4: { x++; break; }
+ case 5: { x--; y++; break; }
+ case 6: { y++; break; }
+ default: { x++; y++; break; }
+ }
+ if( x < 0 ) x = 1;
+ if( y < 0 ) y = 1;
+ if( x >= WIDTH ) x = WIDTH - 2;
+ if( y >= HEIGHT ) y = HEIGHT - 2;
+ }
+
+ bool special = TileIsSpecial( x, y );
+ switch( _tiles[ x, y ].Type )
+ {
+ case Terrain.Forest: _tiles[ x, y ] = new Jungle( x, y, special ); break;
+ case Terrain.Swamp: _tiles[ x, y ] = new Grassland( x, y ); break;
+ case Terrain.Plains: _tiles[ x, y ] = new Hills( x, y, special ); break;
+ case Terrain.Tundra: _tiles[ x, y ] = new Hills( x, y, special ); break;
+ case Terrain.River: _tiles[ x, y ] = new Forest( x, y, special ); break;
+ case Terrain.Grassland1:
+ case Terrain.Grassland2: _tiles[ x, y ] = new Forest( x, y, special ); break;
+ case Terrain.Jungle: _tiles[ x, y ] = new Swamp( x, y, special ); break;
+ case Terrain.Hills: _tiles[ x, y ] = new Mountains( x, y, special ); break;
+ case Terrain.Mountains:
+ if( ( x == 0 || _tiles[ x - 1, y - 1 ].Type != Terrain.Ocean ) &&
+ ( y == 0 || _tiles[ x + 1, y - 1 ].Type != Terrain.Ocean ) &&
+ ( x == ( WIDTH - 1 ) || _tiles[ x + 1, y + 1 ].Type != Terrain.Ocean ) &&
+ ( y == ( HEIGHT - 1 ) || _tiles[ x - 1, y + 1 ].Type != Terrain.Ocean ) )
+ _tiles[ x, y ] = new Ocean( x, y, special );
+ break;
+ case Terrain.Desert: _tiles[ x, y ] = new Plains( x, y, special ); break;
+ case Terrain.Arctic: _tiles[ x, y ] = new Mountains( x, y, special ); break;
+ }
+ }
+ }
+
+ private void CreateRivers()
+ {
+ Log( "Map: Stage 6 - Create rivers" );
+
+ int rivers = 0;
+ for( int i = 0; i < 256 && rivers < ( ( _climate + _landMass ) * 2 ) + 6; i++ )
+ {
+ ITile[,] tilesBackup = (ITile[,])_tiles.Clone();
+
+ int riverLength = 0;
+ int varA = Common.Random.Next( 4 ) * 2;
+ bool nearOcean = false;
+
+ ITile tile = null;
+ while( tile == null )
+ {
+ int x = Common.Random.Next( WIDTH );
+ int y = Common.Random.Next( HEIGHT );
+ if( _tiles[ x, y ].Type == Terrain.Hills ) tile = _tiles[ x, y ];
+ }
+ do
+ {
+ _tiles[ tile.X, tile.Y ] = new River( tile.X, tile.Y );
+ int varB = varA;
+ int varC = Common.Random.Next( 2 );
+ varA = ( ( ( varC - riverLength % 2 ) * 2 + varA ) & 0x07 );
+ varB = 7 - varB;
+
+ riverLength++;
+
+ nearOcean = NearOcean( tile.X, tile.Y );
+ switch( varA )
+ {
+ case 0:
+ case 1: tile = _tiles[ tile.X, tile.Y - 1 ]; break;
+ case 2:
+ case 3: tile = _tiles[ tile.X + 1, tile.Y ]; break;
+ case 4:
+ case 5: tile = _tiles[ tile.X, tile.Y + 1 ]; break;
+ case 6:
+ case 7: tile = _tiles[ tile.X - 1, tile.Y ]; break;
+ }
+ }
+ while( !nearOcean && ( tile.GetType() != typeof( Ocean ) && tile.GetType() != typeof( River ) && tile.GetType() != typeof( Mountains ) ) );
+
+ if( ( nearOcean || tile.Type == Terrain.River ) && riverLength > 5 )
+ {
+ rivers++;
+ ITile[,] mapPart = this[ tile.X - 3, tile.Y - 3, 7, 7 ];
+ for( int x = 0; x < 7; x++ )
+ for( int y = 0; y < 7; y++ )
+ {
+ if( mapPart[ x, y ] == null ) continue;
+ int xx = mapPart[ x, y ].X, yy = mapPart[ x, y ].Y;
+ if( _tiles[ xx, yy ].Type == Terrain.Forest )
+ _tiles[ xx, yy ] = new Jungle( xx, yy, TileIsSpecial( x, y ) );
+ }
+ }
+ else
+ {
+ _tiles = (ITile[,])tilesBackup.Clone(); ;
+ }
+ }
+ }
+
+ // This is a recursive function used to mark all tiles in a continent with a continent/ocean number. That
+ // number will then be corrected so continents/ocans are numberd in size order
+
+ readonly int[,] aiRelPos = { { -1, 0 }, { 0, -1 }, { 0, 1 }, { 1, 0 } }; // Check "Manhattan" conections only
+
+ private byte ContinentId;
+ private ulong ContinetSize;
+
+ private void CountContinent( int x, int y, bool oOcean )
+ {
+ for( int i = 0; i < 4; i++ )
+ {
+ int XX = x + aiRelPos[ i, 0 ];
+ int YY = y + aiRelPos[ i, 1 ];
+ if( XX < 0 || XX >= WIDTH ) continue;
+ if( YY < 0 || YY >= HEIGHT ) continue;
+
+ if( this[ XX, YY ].IsOcean != oOcean ) continue;
+ if( this[ XX, YY ].ContinentId > 0 ) continue; // Already counted
+ this[ XX, YY ].ContinentId = ContinentId;
+ ContinetSize++;
+ CountContinent( XX, YY, oOcean );
+ }
+ }
+
+ /* ***********************************************************************************************************/
+
+
+ private struct Continent
+ {
+ public byte ContinentId;
+ public ulong ContinetSize;
+ };
+
+ private readonly List Continents = new List();
+ private List ContinentsSorted = new List();
+
+ private void CalculateContinentSize()
+ {
+ Log( "Map: Calculate continent/ocean sizes and give continents a number in size order" );
+
+ for( int y = 0; y < HEIGHT; y++ ) // todo remove JR
+ for( int x = 0; x < WIDTH; x++ )
+ this[ x, y ].ContinentId = 0;
+
+ int nTiles = 0;
+ bool oOcean = false;
+ for( int j = 0; j < 2; j++ )
+ {
+ Continents.Clear();
+ ContinentId = 0;
+ for( int y = 0; y < HEIGHT; y++ )
+ for( int x = 0; x < WIDTH; x++ )
+ if( this[ x, y ].ContinentId == 0 && ( this[ x, y ].IsOcean == oOcean ))
+ { // Found a "new" continent/ocean
+ ContinetSize = 1;
+ ContinentId++;
+ this[ x, y ].ContinentId = ContinentId;
+ CountContinent( x, y, oOcean ); // Here is where the counting is done
+ Continent continent;
+ continent.ContinentId = ContinentId;
+ continent.ContinetSize = ContinetSize;
+ Continents.Add( continent );
+
+ }
+ ContinentsSorted = Continents.OrderByDescending( x => x.ContinetSize ).ToList();
+ int[] _iConvTbl = new int[ ContinentsSorted.LongCount() + 1 ];
+
+ for ( int i = 0; i < ContinentsSorted.LongCount(); i++ )
+ {
+ if( oOcean ) Log( "Map: ocean Nr = {0}, Size {1}", ContinentsSorted[ i ].ContinentId, ContinentsSorted[ i ].ContinetSize );
+ else Log( "Map: Continent Nr = {0}, Size {1}", ContinentsSorted[ i ].ContinentId, ContinentsSorted[ i ].ContinetSize );
+ _iConvTbl[ ContinentsSorted[ i ].ContinentId ] = i + 1;
+ }
+
+ // Give all ITiles there correct continent/ocean number
+ for ( int y = 0; y < HEIGHT; y++ )
+ for( int x = 0; x < WIDTH; x++ )
+ {
+ if( this[ x, y ].IsOcean != oOcean ) continue;
+ {
+ this[ x, y ].ContinentId = (byte)( Math.Min( _iConvTbl[ this[ x, y ].ContinentId ], 15 ) );
+ nTiles++; // Just a check
+ }
+
+ }
+ oOcean = true;
+ }
+ Log( "Map: ´Total number of tiles = {0}", nTiles );
+ }
+
+ /* ***********************************************************************************************/
+ private void CreatePoles()
{
Log("Map: Creating poles");
for (int x = 0; x < WIDTH; x++)
- foreach (int y in new int[] { 0, (HEIGHT - 1) })
+ foreach (int y in new[] { 0, (HEIGHT - 1) })
{
_tiles[x, y] = new Arctic(x, y, false);
}
for (int i = 0; i < (WIDTH / 4); i++)
- foreach (int y in new int[] { 0, 1, (HEIGHT - 2), (HEIGHT - 1) })
+ foreach (int y in new[] { 0, 1, (HEIGHT - 2), (HEIGHT - 1) })
{
int x = Common.Random.Next(WIDTH);
_tiles[x, y] = new Tundra(x, y, false);
@@ -620,4 +613,4 @@ public void Generate(int landMass = 1, int temperature = 1, int climate = 1, int
Task.Run(() => GenerateThread());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Map.cs b/src/Map.cs
index 5274b715..cdc6bec8 100644
--- a/src/Map.cs
+++ b/src/Map.cs
@@ -13,6 +13,7 @@
using CivOne.Enums;
using CivOne.Graphics;
using CivOne.Tiles;
+using CivOne.Units;
namespace CivOne
{
@@ -169,4 +170,4 @@ private Map()
Log("Map instance created");
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Screens/Setup.cs b/src/Screens/Setup.cs
index 4c2e3fe6..942a50b4 100644
--- a/src/Screens/Setup.cs
+++ b/src/Screens/Setup.cs
@@ -182,6 +182,7 @@ private void PatchesMenu(int activeItem = 0) => CreateMenu("Patches", activeItem
MenuItem.Create($"Enable Deity difficulty: {Settings.DeityEnabled.YesNo()}").OnSelect(GotoMenu(DeityEnabledMenu)),
MenuItem.Create($"Enable (no keypad) arrow helper: {Settings.ArrowHelper.YesNo()}").OnSelect(GotoMenu(ArrowHelperMenu)),
MenuItem.Create($"Custom map sizes (experimental): {Settings.CustomMapSize.YesNo()}").OnSelect(GotoMenu(CustomMapSizeMenu)),
+ MenuItem.Create($"Use smart PathFinding for \"goto\": {Settings.PathFinding.YesNo()}").OnSelect(GotoMenu(PathFindingeMenu)),
MenuItem.Create("Back").OnSelect(GotoMenu(MainMenu, 1))
);
@@ -234,6 +235,13 @@ private void CustomMapSizeMenu() => CreateMenu("Custom map sizes (experimental)"
MenuItem.Create("Back")
);
+
+ private void PathFindingeMenu() => CreateMenu("Use smart PathFinding for \"goto\"", GotoMenu(PatchesMenu, 8),
+ MenuItem.Create($"{false.YesNo()} (default)").OnSelect((s, a) => Settings.PathFinding = false).SetActive(() => !Settings.PathFinding),
+ MenuItem.Create(true.YesNo()).OnSelect((s, a) => Settings.PathFinding = true).SetActive(() => Settings.PathFinding),
+ MenuItem.Create("Back")
+ );
+
private void PluginsMenu(int activeItem = 0) => CreateMenu("Plugins", activeItem,
new MenuItem[0]
.Concat(
diff --git a/src/Settings.cs b/src/Settings.cs
index 8b749e69..1a1a50ed 100644
--- a/src/Settings.cs
+++ b/src/Settings.cs
@@ -33,6 +33,7 @@ public class Settings
private bool _deityEnabled = false;
private bool _arrowHelper = false;
private bool _customMapSize = false;
+ private bool _pathFinding = false;
private CursorType _cursorType = CursorType.Default;
private DestroyAnimation _destroyAnimation = DestroyAnimation.Sprites;
private GameOption _instantAdvice, _autoSave, _endOfTurn, _animations, _sound, _enemyMoves, _civilopediaText, _palace;
@@ -197,7 +198,19 @@ internal bool CustomMapSize
Common.ReloadSettings = true;
}
}
-
+
+ internal bool PathFinding
+ {
+ get => _pathFinding;
+ set
+ {
+ _pathFinding = value;
+ SetSetting("PathFindingAlgorithm", _pathFinding ? "1" : "0");
+ Common.ReloadSettings = true;
+ }
+ }
+
+
public CursorType CursorType
{
get
@@ -411,6 +424,7 @@ private Settings()
GetSetting("DeityEnabled", ref _deityEnabled);
GetSetting("ArrowHelper", ref _arrowHelper);
GetSetting("CustomMapSize", ref _customMapSize);
+ GetSetting("PathFindingAlgorithm", ref _pathFinding);
GetSetting("CursorType", ref _cursorType);
GetSetting("DestroyAnimation", ref _destroyAnimation);
GetSetting("GameInstantAdvice", ref _instantAdvice);
diff --git a/src/Units/BaseUnit.cs b/src/Units/BaseUnit.cs
index 1430626e..ff61f160 100644
--- a/src/Units/BaseUnit.cs
+++ b/src/Units/BaseUnit.cs
@@ -1,842 +1,857 @@
-// CivOne
-//
-// To the extent possible under law, the person who associated CC0 with
-// CivOne has waived all copyright and related or neighboring rights
-// to CivOne.
-//
-// You should have received a copy of the CC0 legalcode along with this
-// work. If not, see .
-
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.Linq;
-using CivOne.Advances;
-using CivOne.Buildings;
-using CivOne.Enums;
-using CivOne.Graphics;
-using CivOne.IO;
-using CivOne.Screens;
-using CivOne.Tasks;
-using CivOne.Tiles;
-using CivOne.UserInterface;
-using CivOne.Wonders;
-
-namespace CivOne.Units
-{
- internal abstract class BaseUnit : BaseInstance, IUnit
- {
- protected int _x, _y;
-
- public virtual bool Busy
- {
- get
- {
- return (Sentry || Fortify);
- }
- set
- {
- Sentry = false;
- Fortify = false;
- FortifyActive = false;
- }
- }
- public bool Veteran { get; set; }
- public bool FortifyActive { get; private set; }
- private bool _fortify = false;
- public bool Fortify
- {
- get
- {
- return (_fortify || FortifyActive);
- }
- set
- {
- if (Class != UnitClass.Land) return;
- if (this is Settlers) return;
- if (!value)
- _fortify = false;
- else if (Fortify)
- return;
- else
- FortifyActive = true;
- }
- }
-
- private bool _sentry;
- public bool Sentry
- {
- get
- {
- return _sentry;
- }
- set
- {
- if (_sentry == value) return;
- if (!(_sentry = value) || !Game.Started) return;
- MovesLeft = 0;
- PartMoves = 0;
- MovementDone(Map[X, Y]);
- }
- }
-
- public bool Moving => (Movement != null);
- public MoveUnit Movement { get; protected set; }
-
- private int AttackStrength(IUnit defendUnit)
- {
- // Step 1: Determine the nominal attack value of the attacking unit and multiply it by 8.
- int attackStrength = ((int)Attack * 8);
-
- if (Owner == 0)
- {
- // Step 2: If the attacking unit is a Barbarian unit and the defending unit is player-controlled, multiply the attack strength by the Difficulty Modifier, then divide it by 4.
- if (Human == defendUnit.Owner)
- {
- attackStrength *= (Game.Difficulty + 1);
- attackStrength /= 4;
- }
-
- // Step 3: If the attacking unit is a Barbarian unit and the defensing unit is AI-controlled, divide the attack strength by 2.
- if (Human != defendUnit.Owner)
- {
- attackStrength /= 2;
- }
-
- // Step 4: If the attacking unit is a Barbarian unit and the defending unit is inside a city and the defending civilization does not control any other cities, set the attack strength to zero.
- // This actually makes the defending unit invincible in this special case. Might well save you from being obliterated by that unlucky hut at 3600BC.
- if (defendUnit.Tile.City != null && Game.GetPlayer(defendUnit.Owner).Cities.Length == 1)
- {
- attackStrength = 0;
- }
-
- // Step 5: If the attacking unit is a Barbarian unit and the defending unit is inside a city with a Palace, divide the attack strength by 2.
- if (defendUnit.Tile.City != null && defendUnit.Tile.City.HasBuilding())
- {
- attackStrength /= 2;
- }
- }
-
- // Step 6: If the attacking unit is a veteran unit, increase the attack strength by 50%.
- if (Veteran)
- {
- attackStrength += (attackStrength / 2);
- }
-
- // Step 7: If the attacking unit has only 0.2 movement points left, multiply the attack strength by 2, then divide it by 3. If the attacking unit has only 0.1 movement points left, then just divide by 3 instead.
- if (MovesLeft == 0)
- {
- attackStrength *= PartMoves;
- attackStrength /= 3;
- }
-
- // Step 8: If the attacking unit is a Barbarian unit and the defending unit is player-controlled, check the difficulty level. On Chieftain and Warlord levels, divide the attack strength by 2.
- if (Owner == 0 && Human == defendUnit.Owner)
- {
- if (Game.Difficulty < 2)
- {
- attackStrength /= 2;
- }
- }
-
- // Step 9: If the attacking unit is player-controlled, check the difficulty level. On Chieftain level, multiply the attack strength by 2.
- // So on Chieftain difficulty, it is often better to attack than be attacked, even with a defensive unit.
- if (Human == Owner && Game.Difficulty == 0)
- {
- attackStrength *= 2;
- }
-
- return attackStrength;
- }
-
- private int DefendStrength(IUnit defendUnit, IUnit attackUnit)
- {
- // Check City Walls for step 5
- bool cityWalls = (defendUnit.Tile.City != null && defendUnit.Tile.City.HasBuilding());
-
- // Step 1: Determine the nominal defense value of defending unit.
- int defendStrength = (int)defendUnit.Defense;
-
- if (defendUnit.Class == UnitClass.Land || (defendUnit.Class == UnitClass.Water && cityWalls && attackUnit.Attack != 12))
- {
- int fortificationModifier = 4;
- if (defendUnit.Tile.Fortress)
- fortificationModifier = 8;
- else if (defendUnit.Fortify || defendUnit.FortifyActive)
- fortificationModifier = 6;
-
- // Step 2: If the defending unit is a ground unit, multiply the defense strength by the Terrain Modifier.
- // This modifier effectively includes a factor of 2.
- defendStrength *= defendUnit.Tile.Defense;
-
- if (!cityWalls || attackUnit.Attack == 12)
- {
- // Step 3: If the defending unit is a ground unit, multiply the defense strength by the Fortification Modifier.
- // This modifier effectively includes a factor of 4, resulting in a combined factor of 8.
- defendStrength *= fortificationModifier;
- }
- }
-
- // Step 4: If the defending unit is a sea or air unit, multiply the defense strength by 8.
- // This effectively treats the Terrain Modifier as 2, regardless of the actual terrain type. It also means that these units will never benefit from the Fortification Modifier.
- if (defendUnit.Class != UnitClass.Land && (!cityWalls || attackUnit.Attack == 12))
- {
- defendStrength *= 8;
- }
-
- // Step 5: If the defending unit is inside a city with City Walls and the nominal attack value of the attacking unit is NOT equal to 12, check the domain of the defending unit. If the domain is NOT air, re-calculate steps 1 and 2 (ignore steps 3 and 4) and multiply the result by 12.
- // When determining if the attacking unit ignores City Walls, the game just checks for attack value, not unit type. So if you change any unit's attack rating to 12, the game will have it ignore City Walls as well.
- if (cityWalls && attackUnit.Attack != 12)
- {
- defendStrength *= 12;
- }
-
- // Step 6: If the defending unit is a veteran unit, increase the defense strength by 50%.
- if (defendUnit.Veteran)
- {
- defendStrength += (defendStrength / 2);
- }
-
- return defendStrength;
- }
-
- private bool AttackOutcome(IUnit attackUnit, ITile defendTile)
- {
- IUnit defendUnit = defendTile.Units.OrderByDescending(x => x.Attack * (x.Veteran ? 1.5 : 1)).ThenBy(x => (int)x.Type).First();
-
- int attackStrength = AttackStrength(defendUnit);
- int defenseStrength = DefendStrength(defendUnit, attackUnit);
- int randomAttack = Common.Random.Next(attackStrength);
- int randomDefense = Common.Random.Next(defenseStrength);
- bool win = (randomAttack > randomDefense);
- if (win && attackUnit.Owner == 0 && defendUnit.Tile.City != null)
- {
- // If the attacking unit is a Barbarian unit and the defending unit is inside a city, then, if the attacking unit won, the procedure will be repeated once
- // This time, the attacking unit wins on a tie.
- randomAttack = Common.Random.Next(attackStrength);
- randomDefense = Common.Random.Next(defenseStrength);
- win = (randomAttack >= randomDefense);
- }
-
- // 50% chance to award veteran status to the winner
- if (Common.Random.Next(100) < 50)
- {
- if (win && !attackUnit.Veteran) attackUnit.Veteran = true;
- if (!win && !defendUnit.Veteran) defendUnit.Veteran = true;
- }
-
- return win;
- }
-
- protected virtual bool Confront(int relX, int relY)
- {
- if (Class == UnitClass.Land && (this is Diplomat || this is Caravan))
- {
- // TODO: Perform other unit action (confront)
- return false;
- }
-
- Movement = new MoveUnit(relX, relY);
-
- ITile moveTarget = Map[X, Y][relX, relY];
- if (moveTarget == null) return false;
- if (!moveTarget.Units.Any(u => u.Owner != Owner) && moveTarget.City != null && moveTarget.City.Owner != Owner)
- {
- if (Class != UnitClass.Land)
- {
- GameTask.Enqueue(Message.Error("-- Civilization Note --", TextFile.Instance.GetGameText($"ERROR/OCCUPY")));
- Movement = null;
- return false;
- }
-
- City capturedCity = moveTarget.City;
- Movement.Done += (s, a) =>
- {
- Action changeOwner = delegate()
- {
- Player previousOwner = Game.GetPlayer(capturedCity.Owner);
-
- if (capturedCity.HasBuilding())
- capturedCity.RemoveBuilding();
- capturedCity.Food = 0;
- capturedCity.Shields = 0;
- while (capturedCity.Units.Length > 0)
- Game.DisbandUnit(capturedCity.Units[0]);
- capturedCity.Owner = Owner;
-
- if (!capturedCity.HasBuilding())
- {
- capturedCity.Size--;
- }
-
- previousOwner.IsDestroyed();
- };
-
- IList advancesToSteal = GetAdvancesToSteal(capturedCity.Player);
-
- if (Human == capturedCity.Owner || Human == Owner)
- {
- Show captureCity = Show.CaptureCity(capturedCity);
- captureCity.Done += (s1, a1) =>
- {
- changeOwner();
-
- if (capturedCity.Size == 0 || Human != Owner) return;
- GameTask.Insert(Show.CityManager(capturedCity));
- };
- GameTask.Insert(captureCity);
-
- if (advancesToSteal.Any())
- GameTask.Enqueue(Tasks.Show.SelectAdvanceAfterCityCapture(Player, advancesToSteal));
- }
- else
- {
- changeOwner();
- if (advancesToSteal.Any())
- Player.AddAdvance(advancesToSteal.First());
- }
- MoveEnd(s, a);
- };
- }
- else if (this is Nuclear)
- {
- int xx = (X - Common.GamePlay.X + relX) * 16;
- int yy = (Y - Common.GamePlay.Y + relY) * 16;
- Show nuke = Show.Nuke(xx, yy);
-
- if (Map[X, Y][relX, relY].City != null)
- PlaySound("airnuke");
- else
- PlaySound("s_nuke");
-
- nuke.Done += (s, a) =>
- {
- foreach (ITile tile in Map.QueryMapPart(X + relX - 1, Y + relY - 1, 3, 3))
- {
- while (tile.Units.Length > 0)
- {
- Game.DisbandUnit(tile.Units[0]);
- }
- }
- };
- GameTask.Enqueue(nuke);
- }
- else if (AttackOutcome(this, Map[X, Y][relX, relY]))
- {
- Movement.Done += (s, a) =>
- {
- if (this is Cannon)
- {
- PlaySound("cannon");
- }
- else if (this is Musketeers || this is Riflemen || this is Armor || this is Artillery || this is MechInf)
- {
- PlaySound("s_land");
- }
- else
- {
- PlaySound("they_die");
- }
-
- IUnit unit = Map[X, Y][relX, relY].Units.FirstOrDefault();
- if (unit != null)
- {
- GameTask.Insert(Show.DestroyUnit(unit, true));
- }
-
- if (MovesLeft == 0)
- {
- PartMoves = 0;
- }
- else if (MovesLeft > 0)
- {
- if (this is Bomber)
- {
- SkipTurn();
- }
- else
- {
- MovesLeft--;
- }
- }
- Movement = null;
- if (Map[X, Y][relX, relY].City != null)
- {
- if (!Map[X, Y][relX, relY].City.HasBuilding())
- {
- Map[X, Y][relX, relY].City.Size--;
- }
- }
- };
- }
- else
- {
- Movement.Done += (s, a) =>
- {
- if (this is Cannon)
- {
- PlaySound("cannon");
- }
- else if (this is Musketeers || this is Riflemen || this is Armor || this is Artillery || this is MechInf)
- {
- PlaySound("s_land");
- }
- else
- {
- PlaySound("we_die");
- }
- GameTask.Insert(Show.DestroyUnit(this, false));
- Movement = null;
- };
- }
- GameTask.Insert(Movement);
- return false;
- }
-
- private IList GetAdvancesToSteal(Player victim)
- {
- return victim.Advances
- .Where(p => !Player.Advances.Any(p2 => p2.Id == p.Id))
- .OrderBy(a => Common.Random.Next(0, 1000))
- .Take(3)
- .ToList();
- }
-
- public virtual bool MoveTo(int relX, int relY)
- {
- if (Movement != null) return false;
-
- ITile moveTarget = Map[X, Y][relX, relY];
- if (moveTarget == null) return false;
- if (moveTarget.Units.Any(u => u.Owner != Owner))
- {
- if (Class == UnitClass.Land && Tile.IsOcean)
- {
- if (Human == Owner) GameTask.Enqueue(Message.Error("-- Civilization Note --", TextFile.Instance.GetGameText($"ERROR/AMPHIB")));
- return false;
- }
- return Confront(relX, relY);
- }
- if (Class == UnitClass.Land && !(this is Diplomat || this is Caravan) && !new ITile[] { Map[X, Y], moveTarget }.Any(t => t.IsOcean || t.City != null) && moveTarget.GetBorderTiles().SelectMany(t => t.Units).Any(u => u.Owner != Owner))
- {
- if (!moveTarget.Units.Any(x => x.Owner == Owner))
- {
- IUnit[] targetUnits = moveTarget.GetBorderTiles().SelectMany(t => t.Units).Where(u => u.Owner != Owner).ToArray();
- IUnit[] borderUnits = Map[X, Y].GetBorderTiles().SelectMany(t => t.Units).Where(u => u.Owner != Owner).ToArray();
-
- if (borderUnits.Any(u => targetUnits.Any(t => t.X == u.X && t.Y == u.Y)))
- {
- if (Human == Owner)
- GameTask.Enqueue(Message.Error("-- Civilization Note --", TextFile.Instance.GetGameText($"ERROR/ZOC")));
- return false;
- }
- }
- }
- if (moveTarget.City != null && moveTarget.City.Owner != Owner)
- {
- return Confront(relX, relY);
- }
-
- if (!MoveTargets.Any(t => t.X == moveTarget.X && t.Y == moveTarget.Y))
- {
- // Target tile is invalid
- // TODO: For some tiles, display a message detailing why the move is illegal
- return false;
- }
-
- // TODO: This implementation was done by observation, may need a revision
- if ((moveTarget.Road || moveTarget.RailRoad) && (Tile.Road || Tile.RailRoad))
- {
- // Handle movement in MovementDone
- }
- else if (MovesLeft == 0 && !moveTarget.Road && moveTarget.Movement > 1)
- {
- bool success;
- if (PartMoves >= 2)
- {
- // 2/3 moves left? 50% chance of success
- success = (Common.Random.Next(0, 2) == 0);
- }
- else
- {
- // 2/3 moves left? 33% chance of success
- success = (Common.Random.Next(0, 3) == 0);
- }
-
- if (!success)
- {
- PartMoves = 0;
- return false;
- }
- }
-
- MovementTo(relX, relY);
- return true;
- }
-
- private void MoveEnd(object sender, EventArgs args)
- {
- ITile previousTile = Map[_x, _y];
- X += Movement.RelX;
- Y += Movement.RelY;
- if (X == Goto.X && Y == Goto.Y)
- {
- Goto = Point.Empty;
- }
- Movement = null;
-
- Explore();
- MovementDone(previousTile);
- }
-
- protected void MovementTo(int relX, int relY)
- {
- MovementStart(Tile);
- Movement = new MoveUnit(relX, relY);
- Movement.Done += MoveEnd;
- GameTask.Insert(Movement);
- }
-
- protected virtual void MovementStart(ITile previousTile)
- {
- }
-
- protected virtual void MovementDone(ITile previousTile)
- {
- if (MovesLeft > 0) MovesLeft--;
-
- Tile.Visit(Owner);
-
- if (Tile.Hut)
- {
- Tile.Hut = false;
- }
- }
-
- private static IBitmap[] _iconCache = new IBitmap[28];
- public virtual IBitmap Icon { get; private set; }
- private string _name;
- public string Name
- {
- get => Modifications.LastOrDefault(x => x.Name.HasValue)?.Name.Value ?? _name;
- protected set => _name = value;
- }
- public byte PageCount => 2;
- public Picture DrawPage(byte pageNumber)
- {
- string[] text = new string[0];
- switch (pageNumber)
- {
- case 1:
- text = Resources.GetCivilopediaText("BLURB2/" + _name.ToUpper());
- break;
- case 2:
- text = Resources.GetCivilopediaText("BLURB2/" + _name.ToUpper() + "2");
- break;
- default:
- Log("Invalid page number: {0}", pageNumber);
- break;
- }
-
- Picture output = new Picture(320, 200);
-
- output.AddLayer(this.ToBitmap(1), 215, 47);
-
- int yy = 76;
- foreach (string line in text)
- {
- Log(line);
- output.DrawText(line, 6, 1, 12, yy);
- yy += 9;
- }
-
- if (pageNumber == 2)
- {
- yy += 8;
- string requiredTech = "";
- if (RequiredTech != null) requiredTech = RequiredTech.Name;
- output.DrawText(string.Format("Requires {0}", requiredTech), 6, 9, 100, yy); yy += 8;
- output.DrawText(string.Format("Cost: {0}0 resources.", Price), 6, 9, 100, yy); yy += 8;
- output.DrawText(string.Format("Attack Strength: {0}", Attack), 6, 12, 100, yy); yy += 8;
- output.DrawText(string.Format("Defense Strength: {0}", Defense), 6, 12, 100, yy); yy += 8;
- output.DrawText(string.Format("Movement Rate: {0}", Move), 6, 5, 100, yy);
- }
-
- return output;
- }
-
- private IAdvance _requiredTech;
- public IAdvance RequiredTech
- {
- get => Modifications.LastOrDefault(x => x.Requires.HasValue)?.Requires.Value.ToInstance() ?? _requiredTech;
- protected set => _requiredTech = value;
- }
-
- public IWonder RequiredWonder { get; protected set; }
-
- private IAdvance _obsoleteTech;
- public IAdvance ObsoleteTech
- {
- get => Modifications.LastOrDefault(x => x.Obsolete.HasValue)?.Obsolete.Value.ToInstance() ?? _obsoleteTech;
- protected set => _obsoleteTech = value;
- }
-
- public UnitClass Class { get; protected set; }
- public UnitType Type { get; protected set; }
- public City Home { get; protected set; }
- public short _buyPrice;
- public short BuyPrice
- {
- get => Modifications.LastOrDefault(x => x.BuyPrice.HasValue)?.BuyPrice.Value ?? _buyPrice;
- private set => _buyPrice = value;
- }
- public byte ProductionId => (byte)Type;
- private byte _price;
- public byte Price
- {
- get => Modifications.LastOrDefault(x => x.Price.HasValue)?.Price.Value ?? _price;
- protected set => _price = value;
- }
- public virtual UnitRole Role
- {
- get
- {
- UnitRole output = UnitRole.LandAttack;
- if (this is Settlers) output = UnitRole.Settler;
- else if (this is Caravan || this is Diplomat) output = UnitRole.Civilian;
- else if (this is BaseUnitSea)
- {
- if (this is IBoardable) output = UnitRole.Transport;
- else output = UnitRole.SeaAttack;
- }
- else if (this is Fighter) output = UnitRole.AirAttack;
- else if (this.Defense >= this.Attack) output = UnitRole.Defense;
- return output;
- }
- }
-
- private byte _attack;
- public byte Attack
- {
- get => Modifications.LastOrDefault(x => x.Attack.HasValue)?.Attack.Value ?? _attack;
- protected set => _attack = value;
- }
-
- private byte _defense;
- public byte Defense
- {
- get => Modifications.LastOrDefault(x => x.Defense.HasValue)?.Defense.Value ?? _defense;
- protected set => _defense = value;
- }
-
- private byte _move;
- public byte Move
- {
- get => Modifications.LastOrDefault(x => x.Moves.HasValue)?.Moves.Value ?? _move;
- protected set => _move = value;
- }
-
- public int X
- {
- get
- {
- return _x;
- }
- set
- {
- int val = value;
- while (val < 0) val += Map.WIDTH;
- while (val >= Map.WIDTH) val -= Map.WIDTH;
- if (_x == -1 && _y != -1) Explore();
- _x = val;
- }
- }
- public int Y
- {
- get
- {
- return _y;
- }
- set
- {
- if (value < 0 || value >= Map.HEIGHT) return;
- if (_y == -1 && _x != -1 && value != -1) Explore();
- _y = value;
- }
- }
-
- public Point Goto { get; set; }
-
- public ITile Tile => Map[_x, _y];
-
- private byte _owner;
- public byte Owner
- {
- get => _owner;
- set
- {
- _owner = value;
- if (Game.Started) Tile.Visit(_owner);
- }
- }
-
- public Player Player => Game.GetPlayer(Owner);
-
- public byte Status
- {
- get
- {
- return 0;
- }
- set
- {
- bool[] bits = new bool[8];
- for (int i = 0; i < 8; i++)
- bits[i] = (((value >> i) & 1) > 0);
- if (bits[0]) Sentry = true;
- else if (bits[2]) FortifyActive = true;
- else if (bits[3]) _fortify = true;
-
- if (this is Settlers)
- {
- (this as Settlers).SetStatus(bits);
- }
-
- Veteran = bits[5];
- }
- }
- public byte MovesLeft { get; set; }
- public byte PartMoves { get; set; }
-
- public virtual void NewTurn()
- {
- if (FortifyActive)
- {
- FortifyActive = false;
- _fortify = true;
- }
- MovesLeft = Move;
- Explore();
- }
-
- public void SetHome()
- {
- if (Map[X, Y].City == null) return;
- Home = Map[X, Y].City;
- }
-
- public void SetHome(City city) => Home = city;
-
- public void Pillage()
- {
- if (!(Tile.Irrigation || Tile.Mine || Tile.Road || Tile.RailRoad))
- return;
-
- if (Tile.Irrigation)
- Tile.Irrigation = false;
- else if (Tile.Mine)
- Tile.Mine = false;
- else if (Tile.Road)
- Tile.Road = false;
- else if (Tile.RailRoad)
- {
- Tile.RailRoad = false;
- Tile.Road = true;
- }
-
- MovesLeft = 0;
- PartMoves = 0;
- }
-
- public virtual void SkipTurn()
- {
- MovesLeft = 0;
- PartMoves = 0;
- }
-
- protected void SetIcon(char page, int col, int row)
- {
- if (_iconCache[(int)Type] == null)
- {
- _iconCache[(int)Type] = Resources[$"ICONPG{page}"][col * 160, row * 62, 160, 60]
- .ColourReplace((byte)(GFX256 ? 253 : 15), 0);
- }
- Icon = _iconCache[(int)Type];
- }
-
- protected MenuItem MenuNoOrders() => MenuItem.Create("No Orders").SetShortcut("space").OnSelect((s, a) => SkipTurn());
-
- protected MenuItem MenuFortify() => MenuItem.Create("Fortify").SetShortcut("f").OnSelect((s, a) => Fortify = true);
-
- protected MenuItem MenuWait() => MenuItem.Create("Wait").SetShortcut("w").OnSelect((s, a) => Game.UnitWait());
-
- protected MenuItem MenuSentry() => MenuItem.Create("Sentry").SetShortcut("s").OnSelect((s, a) => Sentry = true);
-
- protected MenuItem MenuGoTo() => MenuItem.Create("GoTo").OnSelect((s, a) => GameTask.Enqueue(Show.Goto));
-
- protected MenuItem MenuPillage() => MenuItem.Create("Pillage").SetShortcut("P").OnSelect((s, a) => Pillage());
-
- protected MenuItem MenuHomeCity() => MenuItem.Create("Home City").SetShortcut("h").OnSelect((s, a) => SetHome());
-
- protected MenuItem MenuDisbandUnit() => MenuItem.Create("Disband Unit").SetShortcut("D").OnSelect((s, a) => Game.DisbandUnit(this));
-
- public abstract IEnumerable