using System;
using System.Collections.Generic;
using AI;
namespace CevoAILib
{
enum Job
{
None = 0, BuildRoad = 1, BuildRailRoad = 2, ClearOrDrain = 3, Irrigate = 4, BuildFarmland = 5, Afforest = 6, BuildMine = 7,
BuildCanal = 8, Transform = 9, BuildFortress = 10, CleanUp = 11, BuildBase = 12, Pillage = 13, BuildCity = 14
}
enum SpyMission { SabotageProduction = 1, StealMaps = 2, CollectThirdNationKnowledge = 3, PrepareDossier = 4, PrepareMilitaryReport = 5 }
struct BattleOutcome
{
public readonly int EndHealthOfAttacker;
public readonly int EndHealthOfDefender;
public BattleOutcome(int endHealthOfAttacker, int endHealthOfDefender)
{
this.EndHealthOfAttacker = endHealthOfAttacker;
this.EndHealthOfDefender = endHealthOfDefender;
}
public override string ToString()
{
return string.Format("A{0} D{1}", EndHealthOfAttacker, EndHealthOfDefender);
}
}
///
/// basic unit information as available for both own and foreign units
///
interface IUnitInfo
{
Nation Nation { get; }
ModelBase Model { get; }
Location Location { get; }
bool AreOtherUnitsPresent { get; }
bool IsLoaded { get; }
int Speed { get; } // usually same as Model.Speed
bool IsTerrainResistant { get; }
int Experience { get; }
int ExperienceLevel { get; }
int Health { get; }
bool IsFortified { get; }
int Load { get; }
int Fuel { get; }
Job Job { get; }
}
///
/// own unit, abstract base class
///
unsafe abstract class AUnit : IUnitInfo
{
///
/// movement points an own or foreign unit has per turn, considering damage and wonders
///
/// the unit
/// movement points
public static int UnitSpeed(IUnitInfo unit)
{
if (unit.Model.Domain == ModelDomain.Sea)
{
int speed = unit.Model.Speed;
if (unit.Nation.HasWonder(Building.MagellansExpedition))
speed += 200;
if (unit.Health < 100)
speed = ((speed - 250) * unit.Health / 5000) * 50 + 250;
return speed;
}
else
return unit.Model.Speed;
}
protected readonly Empire theEmpire;
public readonly int ID;
protected readonly Model model;
public AUnit(Empire empire, int indexInSharedMemory)
{
this.theEmpire = empire;
IndexInSharedMemory = indexInSharedMemory;
ID = address[3] & 0xFFFF; // save to be able to find unit back
model = theEmpire.Models[(address[3] >> 16) & 0xFFFF];
}
public override string ToString()
{
return string.Format("{0}@{1}", model, address[0]);
}
#region IUnitInfo members
public Nation Nation { get { return theEmpire.Us; } }
public ModelBase Model { get { return model; } }
public Location Location { get { return new Location(theEmpire, address[0]); } }
///
/// whether other units are present at the same location
///
public bool AreOtherUnitsPresent { get { return (address[7] & Protocol.unMulti) != 0; } }
///
/// whether unit is loaded to a ship or plane
///
public bool IsLoaded { get { return ((uint)address[4] & 0x80000000) == 0; } } // sign bit
///
/// movement points this unit has per turn, considering damage and wonders
///
public int Speed { get { return UnitSpeed(this); } }
///
/// whether this unit passes hostile terrain without damage
///
public bool IsTerrainResistant { get { return model.IsTerrainResistant || theEmpire.Us.HasWonder(Building.HangingGardens); } }
///
/// Experience points collected.
///
public int Experience { get { return (address[6] >> 8) & 0xFF; } }
///
/// Experience level as applied in combat. 0 = Green ... 4 = Elite.
///
public int ExperienceLevel { get { return Experience / Cevo.ExperienceLevelCost; } }
public int Health { get { return (sbyte)((address[5] >> 16) & 0xFF); } }
public bool IsFortified { get { return (address[7] & Protocol.unFortified) != 0; } }
///
/// total number of units loaded to this unit
///
public int Load { get { return TroopLoad + AirLoad; } }
///
/// Fuel remaining, not necessarily the same as Model.Fuel.
///
public int Fuel { get { return (sbyte)((address[5] >> 24) & 0xFF); } }
///
/// settler job this unit is currently doing
///
public Job Job { get { return (Job)(address[6] & 0xFF); } }
#endregion
public bool Exists { get { return indexInSharedMemory >= 0; } }
public bool IsConscripts { get { return (address[7] & Protocol.unConscripts) != 0; } }
public int MovementLeft { get { return (short)(address[5] & 0xFFFF); } }
public bool MustPauseForMountains { get { return (address[7] & Protocol.unMountainDelay) != 0; } }
public bool WasWithdrawn { get { return (address[7] & Protocol.unWithdrawn) != 0; } }
public bool CausesUnrest { get { return Location.MayCauseUnrest && !model.IsCivil; } }
public int TroopLoad { get { return (address[6] >> 16) & 0xFF; } }
public int AirLoad { get { return (address[6] >> 24) & 0xFF; } }
public bool AreBombsLoaded { get { return (address[7] & Protocol.unBombsLoaded) != 0; } }
///
/// home city, null if none
///
public City Home
{
get
{
int homeCityIndexInSharedMemory = (short)(address[4] & 0xFFFF);
if (homeCityIndexInSharedMemory >= 0)
return theEmpire.CityLookup[homeCityIndexInSharedMemory];
else
return null;
}
}
///
/// ship or aircraft by which this unit is currently transported, null if not transported
///
public Unit Transport
{
get
{
int transportIndexInSharedMemory = (short)((address[4] >> 16) & 0xFFFF);
if (transportIndexInSharedMemory >= 0)
return theEmpire.UnitLookup[transportIndexInSharedMemory];
else
return null;
}
}
///
/// persistent custom value
///
public int Status
{
get { return address[1]; }
set { address[1] = value; }
}
#region effective methods
///
/// Move unit to a certain location.
/// Moves along shortest possible path considering all known information.
/// Only does the part of the move that is possible within this turn.
/// If move has to be continued next turn the return value has the Error property Incomplete.
/// Operation breaks even if it could be continued within the turn if a new foreign unit or city is spotted,
/// in this case the result has the NewUnitOrCitySpotted property set.
/// Hostile terrain is considered to find a compromise between damage and reaching the target fast.
///
/// target location
/// result of operation
public PlayResult MoveTo__Turn(Location target)
{
if (target == Location)
return PlayResult.NoChange;
else
return MoveTo(target, false);
}
///
/// Move unit adjacent to a certain location.
/// Moves along shortest possible path considering all known information.
/// Only does the part of the move that is possible within this turn.
/// If move has to be continued next turn the return value has the Error property Incomplete.
/// Operation breaks even if it could be continued within the turn if a new foreign unit or city is spotted,
/// in this case the result has the NewUnitOrCitySpotted property set.
/// Hostile terrain is considered to find a compromise between damage and reaching the target fast.
///
/// location to move adjacent to
/// result of operation
public PlayResult MoveToNeighborOf__Turn(Location target)
{
if (target.IsNeighborOf(Location))
return PlayResult.NoChange;
else
return MoveTo(target, true);
}
// internal
PlayResult MoveTo(Location target, bool approach)
{
if (!target.IsValid)
return new PlayResult(PlayError.InvalidLocation);
if (MustPauseForMountains)
return new PlayResult(PlayError.Incomplete);
// pathfinding necessary
TravelSprawl sprawl = null;
if (approach)
sprawl = new TravelSprawl(theEmpire, this, target);
else
sprawl = new TravelSprawl(theEmpire, this);
foreach (Location reachedLocation in sprawl)
{
if (reachedLocation == target)
break;
}
if (!sprawl.WasIterated(target))
return new PlayResult(PlayError.NoWay);
Location[] path = sprawl.Path(target);
foreach (Location step in path)
{
if (sprawl.Distance(step).NewTurn)
return new PlayResult(PlayError.Incomplete); // has to be continued next turn
if (!IsTerrainResistant && Location.OneTurnHostileDamage == 0 && step.OneTurnHostileDamage > 0)
{ // recover before passing hostile terrain?
int damageToNextNonHostileLocation = sprawl.DamageToNextNonHostileLocation(Location, target);
if (damageToNextNonHostileLocation >= 100)
return new PlayResult(PlayError.NoWay);
else if (Location.OneTurnHostileDamage == 0 && Health <= damageToNextNonHostileLocation)
return new PlayResult(PlayError.RecoverFirst);
}
PlayResult result = Step__Turn(step);
if (!result.OK || result.UnitRemoved || result.NewUnitOrCitySpotted)
return result;
}
return PlayResult.Success;
}
///
/// Move unit to neighbor location.
/// Causes loading to transport if:
/// (1) unit is ground unit and target location is water and has transport present
/// (2) unit is aircraft and target location has carrier present
///
/// location to move to, must be neighbor of current location
/// result of operation
public PlayResult Step__Turn(Location target)
{
if (!target.IsValid)
return new PlayResult(PlayError.InvalidLocation);
RC targetRC = target - Location;
if (targetRC.Distance > 3)
return new PlayResult(PlayError.RulesViolation);
OtherLocation[] newObservations = null;
if (model.HasExtendedObservationRange || target.ProvidesExtendedObservationRange)
newObservations = target.Distance5Area;
else
newObservations = target.Neighbors;
for (int i = 0; i < newObservations.Length; i++)
{
if (newObservations[i].Location.IsObserved)
newObservations[i] = new OtherLocation(new Location(theEmpire, -1), new RC(0, 0)); // not a new observation, make invalid
}
bool foreignCityChangePossible = false;
foreach (OtherLocation observation in newObservations)
{
if (observation.Location.IsValid)
foreignCityChangePossible |= observation.Location.HasForeignCity;
}
int moveCommand = Protocol.sMoveUnit + (((targetRC.a - targetRC.b) & 7) << 4) + (((targetRC.a + targetRC.b) & 7) << 7);
List citiesChanged = null;
if (Load > 0)
{
if ((!target.HasForeignUnit && target.MayCauseUnrest != Location.MayCauseUnrest) || // crossing border changing unrest
theEmpire.TestPlay(moveCommand, indexInSharedMemory).UnitRemoved) // transport will die
{ // reports of all home cities of transported units will become invalid
citiesChanged = new List();
foreach (Unit unit in theEmpire.Units)
{
if (unit.Transport == this && unit.Home != null && !citiesChanged.Contains(unit.Home))
citiesChanged.Add(unit.Home);
}
}
}
bool targetHadForeignCityBefore = target.HasForeignCity;
bool causedUnrestBefore = CausesUnrest;
PlayResult result = theEmpire.Play(moveCommand, indexInSharedMemory);
if (result.Effective)
{
AEmpire.UpdateArea updateArea = AEmpire.UpdateArea.Basic;
foreach (OtherLocation observation in newObservations)
{
if (observation.Location.IsValid)
foreignCityChangePossible |= observation.Location.HasForeignCity;
}
if (foreignCityChangePossible)
updateArea |= AEmpire.UpdateArea.ForeignCities;
if (result.UnitRemoved)
updateArea |= AEmpire.UpdateArea.Units;
if (targetHadForeignCityBefore && !target.HasForeignCity)
{
updateArea |= AEmpire.UpdateArea.ForeignCities; // foreign city destroyed or captured
if (target.HasOwnCity)
updateArea |= AEmpire.UpdateArea.Cities; // captured, new own city
}
if (updateArea != AEmpire.UpdateArea.Basic)
theEmpire.UpdateLists(updateArea);
if (Home != null && (!Exists || CausesUnrest != causedUnrestBefore))
Home.InvalidateReport();
if (citiesChanged != null)
{
foreach (City city in citiesChanged)
city.InvalidateReport();
}
if (theEmpire.HadEvent__Turn((EmpireEvent)Protocol.phStealTech)) // capture with temple of zeus
theEmpire.StealAdvance();
}
return result;
}
///
/// Attack a unit. Moves along shortest possible path considering all known information.
/// Only does the part of the move that is possible within this turn.
/// If move has to be continued next turn the return value has the Error property Incomplete.
/// Hostile terrain is considered to find a compromise between damage and reaching the target fast.
///
/// unit to attack
/// result of operation
public PlayResult Attack__Turn(Location target)
{
if (!target.IsValid)
return new PlayResult(PlayError.InvalidLocation);
PlayResult moved = MoveToNeighborOf__Turn(target);
if (!moved.OK || moved.UnitRemoved || moved.NewUnitOrCitySpotted)
return moved;
else
return Step__Turn(target);
}
///
/// Attack a unit. Moves along shortest possible path considering all known information.
/// Only does the part of the move that is possible within this turn.
/// If move has to be continued next turn the return value has the Error property Incomplete.
/// Operation breaks even if it could be continued within the turn if a new foreign unit or city is spotted,
/// in this case the result has the NewUnitOrCitySpotted property set.
/// Hostile terrain is considered to find a compromise between damage and reaching the target fast.
///
/// unit to attack
/// result of operation
public PlayResult Attack__Turn(IUnitInfo unit)
{
return Attack__Turn(unit.Location);
}
///
/// Attack a city. If city is defended, attack defender. If city is undefended, capture (Ground) or bombard (Sea, Air) it.
/// Moves along shortest possible path considering all known information.
/// Only does the part of the move that is possible within this turn.
/// If move has to be continued next turn the return value has the Error property Incomplete.
/// Operation breaks even if it could be continued within the turn if a new foreign unit or city is spotted,
/// in this case the result has the NewUnitOrCitySpotted property set.
/// Hostile terrain is considered to find a compromise between damage and reaching the target fast.
///
/// city to attack
/// result of operation
public PlayResult Attack__Turn(ICity city)
{
return Attack__Turn(city.Location);
}
public PlayResult DoSpyMission__Turn(SpyMission mission, Location target)
{
if (!target.IsValid)
return new PlayResult(PlayError.InvalidLocation);
PlayResult result = theEmpire.Play(Protocol.sSetSpyMission + ((int)mission << 4));
if (!result.OK)
return result;
else
{
result = MoveToNeighborOf__Turn(target);
if (!result.OK || result.UnitRemoved || result.NewUnitOrCitySpotted)
return result;
else
return Step__Turn(target);
}
}
public PlayResult DoSpyMission__Turn(SpyMission mission, ICity city)
{
return DoSpyMission__Turn(mission, city.Location);
}
//bool MoveForecast__Turn(ToLoc; var RemainingMovement: integer)
//{
// return true; // todo !!!
//}
//bool AttackForecast__Turn(ToLoc,AttackMovement; var RemainingHealth: integer)
//{
// return true; // todo !!!
//}
//bool DefenseForecast__Turn(euix,ToLoc: integer; var RemainingHealth: integer)
//{
// return true; // todo !!!
//}
///
/// Disband unit. If located in city producing a unit, utilize material.
///
/// result of operation
public PlayResult Disband__Turn()
{
City city = Location.OwnCity;
List citiesChanged = null;
if (Load > 0)
{
citiesChanged = new List();
foreach (Unit unit in theEmpire.Units)
{
if (unit.Transport == this && unit.Home != null && !citiesChanged.Contains(unit.Home))
citiesChanged.Add(unit.Home);
}
}
PlayResult result = theEmpire.Play(Protocol.sRemoveUnit, indexInSharedMemory);
if (result.OK)
{
theEmpire.UpdateLists(AEmpire.UpdateArea.Units);
if (Home != null)
Home.InvalidateReport();
if (city != null)
city.InvalidateReport(); // in case unit was utilized
if (citiesChanged != null)
{
foreach (City city1 in citiesChanged)
city1.InvalidateReport();
}
}
return result;
}
///
/// start settler job
///
/// the job to start
/// result of operation
public PlayResult StartJob__Turn(Job job)
{
return theEmpire.Play(Protocol.sStartJob + ((int)job << 4), indexInSharedMemory);
}
///
/// set home of unit in city it's located in
///
/// result of operation
public PlayResult SetHomeHere__Turn()
{
City oldHome = Home;
PlayResult result = theEmpire.Play(Protocol.sSetUnitHome, indexInSharedMemory);
if (result.OK)
{
if (oldHome != null)
oldHome.InvalidateReport();
if (Home != null)
Home.InvalidateReport();
}
return result;
}
///
/// load unit to transport at same location
///
/// result of operation
public PlayResult LoadToTransport__Turn()
{
return theEmpire.Play(Protocol.sLoadUnit, indexInSharedMemory);
}
///
/// unload unit from transport
///
/// result of operation
public PlayResult UnloadFromTransport__Turn()
{
return theEmpire.Play(Protocol.sUnloadUnit, indexInSharedMemory);
}
///
/// if this unit is a transport, select it as target for subsequent loading of units
///
///
public PlayResult SelectAsTransport__Turn()
{
return theEmpire.Play(Protocol.sSelectTransport, indexInSharedMemory);
}
///
/// add unit to the city it's located in
///
/// result of operation
public PlayResult AddToCity__Turn()
{
City city = Location.OwnCity;
PlayResult result = theEmpire.Play(Protocol.sAddToCity, indexInSharedMemory);
if (result.OK)
{
if (Home != null)
Home.InvalidateReport();
if (city != null)
city.InvalidateReport();
}
return result;
}
#endregion
#region template internal stuff
int indexInSharedMemory = -1;
int* address;
///
/// INTERNAL - only access from CevoAILib classes!
///
public int IndexInSharedMemory
{
get { return indexInSharedMemory; }
set
{
if (value != indexInSharedMemory)
{
indexInSharedMemory = value;
if (indexInSharedMemory >= 0)
address = (int*)theEmpire.address[4] + ROReadPoint.SizeOfUn * indexInSharedMemory;
}
}
}
#endregion
}
unsafe struct MovingUnit : IUnitInfo
{
readonly AEmpire theEmpire;
int[] showMoveData;
public MovingUnit(AEmpire empire, int* data)
{
this.theEmpire = empire;
showMoveData = new int[13];
for (int i = 0; i < 13; i++)
showMoveData[i] = data[i];
}
public override string ToString()
{
return Model.ToString();
}
#region IUnitInfo members
public Nation Nation { get { return new Nation(theEmpire, showMoveData[0]); } }
public ModelBase Model { get { return theEmpire.ForeignModels[showMoveData[3]]; } }
public Location Location { get { return new Location(theEmpire, showMoveData[5]); } }
public bool AreOtherUnitsPresent { get { return (showMoveData[4] & Protocol.unMulti) != 0; } }
public bool IsLoaded { get { return false; } }
public int Speed { get { return AUnit.UnitSpeed(this); } }
public bool IsTerrainResistant { get { return Model.IsTerrainResistant || Nation.HasWonder(Building.HangingGardens); } }
public int Experience { get { return showMoveData[11]; } }
public int ExperienceLevel { get { return Experience / Cevo.ExperienceLevelCost; } }
public int Health { get { return showMoveData[1]; } }
public bool IsFortified { get { return (showMoveData[4] & Protocol.unFortified) != 0; } }
public int Load { get { return showMoveData[12]; } }
public int Fuel { get { return showMoveData[10]; } }
public Job Job { get { return Job.None; } }
#endregion
}
///
/// foreign unit, abstract base class
///
struct ForeignUnit : IUnitInfo
{
readonly AEmpire theEmpire;
readonly Location location;
readonly ModelBase model;
readonly int data2;
readonly int data3;
public ForeignUnit(AEmpire empire, Location location, ModelBase model, int data2, int data3)
{
this.theEmpire = empire;
this.location = location;
this.model = model;
this.data2 = data2;
this.data3 = data3;
}
public override string ToString()
{
return string.Format("{0}@{1}", model, location.ID);
}
#region IUnitInfo members
public Nation Nation { get { return new Nation(theEmpire, data2 & 0xFF); } }
public ModelBase Model { get { return model; } }
public Location Location { get { return location; } }
///
/// whether other units are present at the same location
///
public bool AreOtherUnitsPresent { get { return (data3 & (Protocol.unMulti << 16)) != 0; } }
///
/// alwas false, loaded foreign units are not in list
///
public bool IsLoaded { get { return false; } }
///
/// movement points this unit has per turn, considering damage and wonders
///
public int Speed { get { return AUnit.UnitSpeed(this); } }
///
/// whether this unit passes hostile terrain without damage
///
public bool IsTerrainResistant { get { return model.IsTerrainResistant || Nation.HasWonder(Building.HangingGardens); } }
///
/// Experience points collected.
///
public int Experience { get { return data3 & 0xFF; } }
///
/// Experience level as applied in combat. 0 = Green ... 4 = Elite.
///
public int ExperienceLevel { get { return Experience / Cevo.ExperienceLevelCost; } }
public int Health { get { return (sbyte)((data2 >> 8) & 0xFF); } }
public bool IsFortified { get { return (data3 & (Protocol.unFortified << 16)) != 0; } }
///
/// total number of units loaded to this unit
///
public int Load { get { return (sbyte)((data3 >> 8) & 0xFF); } }
///
/// Fuel remaining, not necessarily the same as Model.Fuel.
///
public int Fuel { get { return (sbyte)((data2 >> 16) & 0xFF); } }
///
/// settler job this unit is currently doing
///
public Job Job { get { return (Job)((data2 >> 24) & 0xFF); } }
#endregion
}
unsafe sealed class ForeignUnitList : IEnumerable
{
readonly AEmpire theEmpire;
readonly int* address;
public ForeignUnitList(AEmpire empire)
{
theEmpire = empire;
address = (int*)empire.address[7];
}
IUnitInfo this[int index]
{
get
{
return new ForeignUnit(
theEmpire,
new Location(theEmpire, address[ROReadPoint.SizeOfUnitInfo * index]),
theEmpire.ForeignModels[(address[ROReadPoint.SizeOfUnitInfo * index + 1] >> 16) & 0xFFFF],
address[ROReadPoint.SizeOfUnitInfo * index + 2],
address[ROReadPoint.SizeOfUnitInfo * index + 3]);
}
}
///
/// INTERNAL - only call from CevoAILib classes!
///
public IUnitInfo UnitByLocation(Location location)
{
int index = -1;
bool inRange = true;
do
{
index++;
inRange = (index < theEmpire.address[ROReadPoint.TestFlags + 10]);
}
while (inRange && address[ROReadPoint.SizeOfUnitInfo * index] != location.ID);
if (inRange)
return this[index];
else
return null;
}
#region IEnumerable members
class Enumerator : IEnumerator
{
ForeignUnitList list;
int index;
public Enumerator(ForeignUnitList list) { this.list = list; index = -1; }
public void Reset() { index = -1; }
public IUnitInfo Current { get { return list[index]; } }
object System.Collections.IEnumerator.Current { get { return list[index]; } }
public void Dispose() { }
public bool MoveNext()
{
bool inRange = true;
do
{
index++;
inRange = (index < list.theEmpire.address[ROReadPoint.TestFlags + 10]);
}
while (inRange && list.address[ROReadPoint.SizeOfUnitInfo * index] < 0); // LID < 0 indicates gap in list
return inRange;
}
}
public IEnumerator GetEnumerator() { return new Enumerator(this); }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new Enumerator(this); }
#endregion
}
}