using System; using System.Collections.Generic; using AI; namespace CevoAILib { enum Terrain { Unknown = 0x00, DeadLands = 0x01, Ocean = 0x02, Shore = 0x03, Grassland = 0x04, Desert = 0x05, Prairie = 0x06, Tundra = 0x07, Arctic = 0x08, Swamp = 0x09, Plains = 0x0A, Forest = 0x0B, Hills = 0x0C, Mountains = 0x0D, Cobalt = 0x11, Fish = 0x13, Oasis = 0x15, Wheat = 0x16, Gold = 0x17, Ivory = 0x18, Peat = 0x19, Game = 0x1B, Wine = 0x1C, Iron = 0x1D, Uranium = 0x21, Manganese = 0x23, Oil = 0x25, Bauxite = 0x26, Gas = 0x27, MineralWater = 0x2B, Coal = 0x2C, Diamonds = 0x2D, Mercury = 0x31 } enum TerrainImprovement { None = 0x0, Irrigation = 0x1, Farm = 0x2, Mine = 0x3, Fortress = 0x4, Base = 0x5 } unsafe sealed class Map { readonly AEmpire theEmpire; public readonly int Size; public readonly int SizeX; public readonly int SizeY; public readonly int LandMass; public Map(AEmpire empire, int sizeX, int sizeY, int landMass) { this.theEmpire = empire; Size = sizeX * sizeY; this.SizeX = sizeX; this.SizeY = sizeY >> 1; this.LandMass = landMass; Ground = (int*)empire.address[1]; ObservedLast = (short*)empire.address[2]; Territory = (sbyte*)empire.address[3]; } /// /// during own turn, trigger refresh of display of all values set using Location.SetDebugDisplay /// (not necessary in the end of the turn because refresh happens automatically then) /// public void RefreshDebugDisplay() { theEmpire.Play(Protocol.sRefreshDebugMap); } #region template internal stuff /// /// INTERNAL - only access from CevoAILib classes! /// public readonly int* Ground; /// /// INTERNAL - only access from CevoAILib classes! /// public readonly short* ObservedLast; /// /// INTERNAL - only access from CevoAILib classes! /// public readonly sbyte* Territory; /// /// INTERNAL - only call from CevoAILib classes! /// special return values: -0x1000 southpole, -0xFFF..-1 northpole /// /// id of base location /// return value array, must have length of 8 public void GetNeighborIDs(int locationID, int[] neighborIDs) { int wrap = SizeX; int y0 = locationID / wrap; int x0 = locationID - y0 * wrap; neighborIDs[1] = locationID + wrap * 2; neighborIDs[3] = locationID - 1; neighborIDs[5] = locationID - wrap * 2; neighborIDs[7] = locationID + 1; locationID += y0 & 1; neighborIDs[0] = locationID + wrap; neighborIDs[2] = locationID + wrap - 1; neighborIDs[4] = locationID - wrap - 1; neighborIDs[6] = locationID - wrap; // world is round! if (x0 < wrap - 1) { if (x0 == 0) { neighborIDs[3] += wrap; if ((y0 & 1) == 0) { neighborIDs[2] += wrap; neighborIDs[4] += wrap; } } } else { neighborIDs[7] -= wrap; if ((y0 & 1) == 1) { neighborIDs[0] -= wrap; neighborIDs[6] -= wrap; } } // check south pole switch ((SizeY << 1) - y0) { case 1: { neighborIDs[0] = -0x1000; neighborIDs[1] = -0x1000; neighborIDs[2] = -0x1000; break; } case 2: { neighborIDs[1] = -0x1000; break; } } } /// /// INTERNAL - only call from CevoAILib classes! /// special return values: -0x2000 gap (V21 invalid), -0x1000 southpole, -0xFFF..-1 northpole /// /// id of base location /// return value array, must have length of 28 public void GetDistance5IDs(int locationID, int[] distance5IDs) { int wrap = SizeX; int y0 = locationID / wrap; int xComponent0 = locationID - y0 * wrap - 1; int xComponentSwitch = xComponent0 - 1 + (y0 & 1); if (xComponent0 < 0) xComponent0 += wrap; if (xComponentSwitch < 0) xComponentSwitch += wrap; xComponentSwitch = xComponentSwitch ^ xComponent0; // allows easy switching between 2 values int yComponent = wrap * (y0 - 3); // may start negative int V21 = 0; int bit = 1; for (int dy = 0; dy < 7; dy++) { if (yComponent < Size) { xComponent0 = xComponent0 ^ xComponentSwitch; // switch int xComponent = xComponent0; for (int dx = 0; dx < 4; dx++) { if ((bit & 0x67F7F76) != 0) distance5IDs[V21] = xComponent + yComponent; else distance5IDs[V21] = -0x2000; xComponent++; if (xComponent >= wrap) xComponent -= wrap; V21++; bit <<= 1; } yComponent += wrap; } else { for (int dx = 0; dx < 4; dx++) { if ((bit & 0x67F7F76) != 0) distance5IDs[V21] = -0x1000; else distance5IDs[V21] = -0x2000; V21++; bit <<= 1; } } } } #endregion } struct RC // relative coordinates { public readonly int a; public readonly int b; public RC(int a, int b) { this.a = a; this.b = b; } public override string ToString() { return string.Format("({0},{1})",a, b); } public static bool operator ==(RC RC1, RC RC2) { return RC1.a == RC2.a && RC1.b == RC2.b; } public static bool operator !=(RC RC1, RC RC2) { return RC1.a != RC2.a || RC1.b != RC2.b; } public override bool Equals(object obj) { return a == ((RC)obj).a && b == ((RC)obj).b; } public override int GetHashCode() { return a + (b << 16); } public static RC operator +(RC RC1, RC RC2) { return new RC(RC1.a + RC2.a, RC1.b + RC2.b); } public static RC operator -(RC RC1, RC RC2) { return new RC(RC1.a - RC2.a, RC1.b - RC2.b); } /// /// Absolute distance regardless of direction. /// One tile counts 2 if straight, 3 if diagonal. /// public int Distance { get { int adx = Math.Abs(a-b); int ady = Math.Abs(a+b); return adx + ady + (Math.Abs(adx - ady) >> 1); } } } struct OtherLocation { public readonly Location Location; public readonly RC RC; public OtherLocation(Location location, RC RC) // location is the other location, RC the coordinate relative to an origin tile not stored { this.Location = location; this.RC = RC; } } struct JobProgress { public readonly int Required; public readonly int Done; public readonly int NextTurnPlus; public JobProgress(int Required, int Done, int NextTurnPlus) { this.Required = Required; this.Done = Done; this.NextTurnPlus = NextTurnPlus; } } unsafe struct Location { readonly AEmpire theEmpire; public readonly int ID; readonly int* address; static readonly int[] V8_a = { 1, 1, 0, -1, -1, -1, 0, 1 }; static readonly int[] V8_b = { 0, 1, 1, 1, 0, -1, -1, -1 }; // for internal use, if used check < Map.Size, because not checked internally public Location(AEmpire empire, int ID) { this.theEmpire = empire; this.ID = ID; this.address = theEmpire.Map.Ground + ID; } public override string ToString() { return string.Format("{0}", ID); } public static bool operator ==(Location location1, Location location2) { return location1.ID == location2.ID; } public static bool operator !=(Location location1, Location location2) { return location1.ID != location2.ID; } public override bool Equals(object obj) { return ID == ((Location)obj).ID; } public override int GetHashCode() { return ID; } public static RC operator -(Location location1, Location location2) { int wrap = location2.theEmpire.Map.SizeX; int y1 = location2.ID / wrap; int x1 = location2.ID - y1 * wrap; int dy = location1.ID / wrap; int dx = location1.ID - dy * wrap; dx = ((dx * 2 + (dy & 1)) - (x1 * 2 + (y1 & 1)) + 3 * wrap) % (2 * wrap) - wrap; dy -= y1; return new RC((dx + dy) >> 1, (dy - dx) >> 1); } public static Location operator +(Location location, RC RC) { int wrap = location.theEmpire.Map.SizeX; int y0 = location.ID / wrap; int otherLocationID = (location.ID + ((RC.a - RC.b + (y0 & 1) + wrap * 2) >> 1)) % wrap + wrap * (y0 + RC.a + RC.b); if (otherLocationID >= location.theEmpire.Map.Size) otherLocationID = -0x1000; return new Location(location.theEmpire, otherLocationID); } public static Location operator -(Location location, RC RC) { return location + new RC(-RC.a, -RC.b); } /// /// true if location is on the map, false if beyond upper or lower edge of the map /// public bool IsValid { get { return ID >= 0; } } /// /// set number shown on debug map /// /// number, 0 for nothing public void SetDebugDisplay(int value) { if (ID >= 0) theEmpire.debugMapAddress[ID] = value; } /// /// Set of all adjacent locations. /// All locations returned are on the map. /// Usually the array has 8 elements, but it's less if the location is close to the upper or lower edge of the map. /// Take the result as a set with no specific order. Don't rely on the array indices to always have the same meaning. /// public OtherLocation[] Neighbors { get { int[] neighborIDs = new int[8]; theEmpire.Map.GetNeighborIDs(ID, neighborIDs); int count = 0; for (int V8 = 0; V8 < 8; V8++) { if (neighborIDs[V8] >= 0) count++; } OtherLocation[] neighbors = new OtherLocation[count]; count = 0; for (int V8 = 0; V8 < 8; V8++) { if (neighborIDs[V8] >= 0) { neighbors[count] = new OtherLocation(new Location(theEmpire, neighborIDs[V8]), new RC(V8_a[V8], V8_b[V8])); count++; } } return neighbors; } } /// /// Set of all locations with a distance of 5 or less, including the location itself. /// This is the city radius, and also it's the extended visibility radius of units. /// All locations returned are on the map. /// Usually the array has 21 elements, but it's less if the location is close to the upper or lower edge of the map. /// Take the result as a set with no specific order. Don't rely on the array indices to always have the same meaning. /// public OtherLocation[] Distance5Area { get { int[] distance5IDs = new int[28]; theEmpire.Map.GetDistance5IDs(ID, distance5IDs); int count = 0; for (int V21 = 1; V21 < 27; V21++) { if (distance5IDs[V21] >= 0) count++; } OtherLocation[] distance5Area = new OtherLocation[count]; count = 0; for (int V21 = 1; V21 < 27; V21++) { if (distance5IDs[V21] >= 0) { int dy = (V21 >> 2) - 3; int dx = ((V21 & 3) << 1) - 3 + ((dy + 3) & 1); distance5Area[count] = new OtherLocation(new Location(theEmpire, distance5IDs[V21]), new RC((dx + dy) >> 1, (dy - dx) >> 1)); count++; } } return distance5Area; } } /// /// whether this location is adjacent to another one /// /// the other location /// true if adjacent, false if not adjacent, also false if identical public bool IsNeighborOf(Location otherLocation) { int[] neighborIDs = new int[8]; theEmpire.Map.GetNeighborIDs(ID, neighborIDs); return Array.IndexOf(neighborIDs, otherLocation.ID) >= 0; } #region basic info /// /// Simulation of latitude, returns value between -90 and 90. /// (May be used for strategic consideration and climate estimation.) /// public int Latitude { get { return 90 - (ID / theEmpire.Map.SizeX) * 180 / ((theEmpire.Map.SizeY << 1) - 1); } } /// /// whether the tile at this location was visible to an own unit or city at any point in the game /// public bool IsDiscovered { get { return (*address & 0x1F) != 0x1F; } } /// /// whether the tile is visible to an own unit or city in this turn /// public bool IsObserved { get { return (*address & 0x100000) != 0; } } /// /// whether the tile is visible to an own special commando or spy plane in this turn /// public bool IsSpiedOut { get { return (*address & 0x20000) != 0; } } /// /// turn in which the tile was visible to an own unit or city last /// public int TurnObservedLast { get { return theEmpire.Map.ObservedLast[ID]; } } /// /// whether an own city at this location would be protected by the great wall /// public bool IsGreatWallProtected { get { return (*address & 0x10000) != 0; } } /// /// Whether tile can not be moved to because it's in the territory of a nation that we are in peace with but not allied. /// public bool IsDisallowedTerritory { get { return (*address & 0x40000000) != 0; } } /// /// whether units located here have 2 tiles observation range (distance 5) instead of adjacent locations only /// public bool ProvidesExtendedObservationRange { get { return BaseTerrain == Terrain.Mountains || Improvement == TerrainImprovement.Fortress || Improvement == TerrainImprovement.Base; } } #endregion #region terrain /// /// Exact terrain type including special resources. /// public Terrain Terrain { get { int raw = *address; if ((raw & 0x1F) == 0x1F) return Terrain.Unknown; else if ((raw & 0x1000000) != 0) return Terrain.DeadLands + ((raw >> 21) & 0x30); else if ((raw & 0x7F) == 0x22) return Terrain.Plains; else return (Terrain)((raw & 0xF) + ((raw >> 1) & 0x30) + 2); } } /// /// Base terrain type not including special resources. /// public Terrain BaseTerrain { get { return (Terrain)((int)Terrain & 0xF); } } /// /// Whether it's a water tile (terrain Ocean or Shore). /// public bool IsWater { get { return (*address & 0x1E) == 0x00; } } /// /// damage dealt to a unit which is not resistant to hostile terrain if that unit stays at this location for a full turn /// public int OneTurnHostileDamage { get { if ((*address & 0x800480) != 0 || // city, river or canal (*address & 0xF000) == 0x5000) // base return 0; else if ((*address & 0x1F) == 0x03 && (*address & 0x60) != 0x20) // desert but not an oasis return Cevo.DamagePerTurnInDesert; else if ((*address & 0x1F) == 0x06) // arctic return Cevo.DamagePerTurnInArctic; else return 0; } } public bool HasRiver { get { return (*address & 0x80) != 0; } } public bool HasRoad { get { return (*address & 0x100) != 0; } } public bool HasRailRoad { get { return (*address & 0x200) != 0; } } public bool HasCanal { get { return (*address & 0x400) != 0; } } public bool IsPolluted { get { return (*address & 0x800) != 0; } } /// /// Terrain improvement built on this tile. /// public TerrainImprovement Improvement { get { return (TerrainImprovement)((*address >> 12) & 0xF); } } #endregion /// /// Query progress of a specific settler job at this location /// /// the job /// the progress /// result of operation public PlayResult GetJobProgress__Turn(Job job, out JobProgress progress) { fixed (int* jobProgressData = new int[Protocol.nJob * 3]) { PlayResult result = theEmpire.Play(Protocol.sGetJobProgress, ID, jobProgressData); progress = new JobProgress(jobProgressData[(int)job * 3], jobProgressData[(int)job * 3 + 1], jobProgressData[(int)job * 3 + 2]); return result; } } /// /// Nation to who's territory this location belongs. Nation.None if none. /// public Nation TerritoryNation { get { sbyte raw = theEmpire.Map.Territory[ID]; if (raw < 0) return Nation.None; else return new Nation(theEmpire, raw); } } /// /// Whether a non-civil unit will cause unrest in it's home city if placed at this location. /// public bool MayCauseUnrest { get { switch (theEmpire.Government) { case Government.Republic: case Government.FutureSociety: { sbyte raw = theEmpire.Map.Territory[ID]; return raw >= 0 && theEmpire.RelationTo(new Nation(theEmpire, raw)) < Relation.Alliance; } case Government.Democracy: { sbyte raw = theEmpire.Map.Territory[ID]; return raw < 0 || theEmpire.RelationTo(new Nation(theEmpire, raw)) < Relation.Alliance; } default: return false; } } } #region unit info public bool HasOwnUnit { get { return (*address & 0x600000) == 0x600000; } } public bool HasOwnZoCUnit { get { return (*address & 0x10000000) != 0; } } public bool HasForeignUnit { get { return (*address & 0x600000) == 0x400000; } } public bool HasAnyUnit { get { return (*address & 0x400000) != 0; } } public bool HasForeignSubmarine { get { return (*address & 0x80000) != 0; } } public bool HasForeignStealthUnit { get { return (*address & 0x40000) != 0; } } public bool IsInForeignZoC { get { return (*address & 0x20000000) != 0; } } /// /// Own unit that would defend an enemy attack to this location. null if no own unit present. /// public Unit OwnDefender { get { if (!HasOwnUnit) return null; else { fixed (int* data = new int[1]) { if (!theEmpire.Play(Protocol.sGetDefender, ID, data).OK) return null; else return theEmpire.UnitLookup[data[0]]; } } } } /// /// Foreign unit that would defend an attack to this location. null if no foreign unit present. /// public IUnitInfo ForeignDefender { get { if (!HasForeignUnit) return null; else return theEmpire.ForeignUnits.UnitByLocation(this); } } /// /// Unit that would defend an attack to this location. null if no unit present. /// public IUnitInfo Defender { get { if (HasOwnUnit) return OwnDefender; else if (HasForeignUnit) return ForeignDefender; else return null; } } #endregion #region city info public bool HasOwnCity { get { return (*address & 0xA00000) == 0xA00000; } } public bool HasForeignCity { get { return (*address & 0xA00000) == 0x800000; } } public bool HasAnyCity { get { return (*address & 0x800000) != 0; } } /// /// Own city at this location. null if no own city present. /// public City OwnCity { get { if (!HasOwnCity) return null; else { foreach (City city in theEmpire.Cities) { if (city.Location == this) return city; } return null; } } } /// /// Foreign city at this location. null if no foreign city present. /// public ForeignCity ForeignCity { get { if (!HasForeignCity) return null; { foreach (ForeignCity city in theEmpire.ForeignCities) { if (city.Location == this) return city; } return null; } } } /// /// City at this location. null if no city present. /// public ICity City { get { if (HasOwnCity) return OwnCity; else if (HasForeignCity) return ForeignCity; else return null; } } /// /// Own city that is exploiting this tile. null if not exploited or exploited by foreign city. /// /// public City GetExploitingCity__Turn() { if (!IsValid) return null; City city = null; fixed (int* tileInfo = new int[4]) { theEmpire.Play(Protocol.sGetCityTileInfo, ID, tileInfo); if (tileInfo[3] >= 0) city = theEmpire.CityLookup[tileInfo[3]]; if (city != null && city.Location != this) city = null; } return city; } #endregion } }