source: branches/AlphaChannel/AI Template/Project/Lib/Map.cs

Last change on this file was 191, checked in by chronos, 5 years ago
  • Added: AI Template directory with manual for development of custom AI from original game.
File size: 20.4 KB
Line 
1using System;
2using System.Collections.Generic;
3using AI;
4
5namespace CevoAILib
6{
7 enum Terrain
8 {
9 Unknown = 0x00, DeadLands = 0x01, Ocean = 0x02, Shore = 0x03, Grassland = 0x04, Desert = 0x05, Prairie = 0x06,
10 Tundra = 0x07, Arctic = 0x08, Swamp = 0x09, Plains = 0x0A, Forest = 0x0B, Hills = 0x0C, Mountains = 0x0D,
11 Cobalt = 0x11, Fish = 0x13, Oasis = 0x15, Wheat = 0x16, Gold = 0x17, Ivory = 0x18, Peat = 0x19, Game = 0x1B, Wine = 0x1C, Iron = 0x1D,
12 Uranium = 0x21, Manganese = 0x23, Oil = 0x25, Bauxite = 0x26, Gas = 0x27, MineralWater = 0x2B, Coal = 0x2C, Diamonds = 0x2D, Mercury = 0x31
13 }
14
15 enum TerrainImprovement { None = 0x0, Irrigation = 0x1, Farm = 0x2, Mine = 0x3, Fortress = 0x4, Base = 0x5 }
16
17 unsafe sealed class Map
18 {
19 readonly AEmpire theEmpire;
20 public readonly int Size;
21 public readonly int SizeX;
22 public readonly int SizeY;
23 public readonly int LandMass;
24
25 public Map(AEmpire empire, int sizeX, int sizeY, int landMass)
26 {
27 this.theEmpire = empire;
28 Size = sizeX * sizeY;
29 this.SizeX = sizeX;
30 this.SizeY = sizeY >> 1;
31 this.LandMass = landMass;
32 Ground = (int*)empire.address[1];
33 ObservedLast = (short*)empire.address[2];
34 Territory = (sbyte*)empire.address[3];
35 }
36
37 /// <summary>
38 /// during own turn, trigger refresh of display of all values set using Location.SetDebugDisplay
39 /// (not necessary in the end of the turn because refresh happens automatically then)
40 /// </summary>
41 public void RefreshDebugDisplay()
42 {
43 theEmpire.Play(Protocol.sRefreshDebugMap);
44 }
45
46 #region template internal stuff
47 /// <summary>
48 /// INTERNAL - only access from CevoAILib classes!
49 /// </summary>
50 public readonly int* Ground;
51
52 /// <summary>
53 /// INTERNAL - only access from CevoAILib classes!
54 /// </summary>
55 public readonly short* ObservedLast;
56
57 /// <summary>
58 /// INTERNAL - only access from CevoAILib classes!
59 /// </summary>
60 public readonly sbyte* Territory;
61
62 /// <summary>
63 /// INTERNAL - only call from CevoAILib classes!
64 /// special return values: -0x1000 southpole, -0xFFF..-1 northpole
65 /// </summary>
66 /// <param name="locationID">id of base location</param>
67 /// <param name="neighborIDs">return value array, must have length of 8</param>
68 public void GetNeighborIDs(int locationID, int[] neighborIDs)
69 {
70 int wrap = SizeX;
71 int y0 = locationID / wrap;
72 int x0 = locationID - y0 * wrap;
73
74 neighborIDs[1] = locationID + wrap * 2;
75 neighborIDs[3] = locationID - 1;
76 neighborIDs[5] = locationID - wrap * 2;
77 neighborIDs[7] = locationID + 1;
78 locationID += y0 & 1;
79 neighborIDs[0] = locationID + wrap;
80 neighborIDs[2] = locationID + wrap - 1;
81 neighborIDs[4] = locationID - wrap - 1;
82 neighborIDs[6] = locationID - wrap;
83
84 // world is round!
85 if (x0 < wrap - 1)
86 {
87 if (x0 == 0)
88 {
89 neighborIDs[3] += wrap;
90 if ((y0 & 1) == 0)
91 {
92 neighborIDs[2] += wrap;
93 neighborIDs[4] += wrap;
94 }
95 }
96 }
97 else
98 {
99 neighborIDs[7] -= wrap;
100 if ((y0 & 1) == 1)
101 {
102 neighborIDs[0] -= wrap;
103 neighborIDs[6] -= wrap;
104 }
105 }
106
107 // check south pole
108 switch ((SizeY << 1) - y0)
109 {
110 case 1:
111 {
112 neighborIDs[0] = -0x1000;
113 neighborIDs[1] = -0x1000;
114 neighborIDs[2] = -0x1000;
115 break;
116 }
117 case 2:
118 {
119 neighborIDs[1] = -0x1000;
120 break;
121 }
122 }
123 }
124
125 /// <summary>
126 /// INTERNAL - only call from CevoAILib classes!
127 /// special return values: -0x2000 gap (V21 invalid), -0x1000 southpole, -0xFFF..-1 northpole
128 /// </summary>
129 /// <param name="locationID">id of base location</param>
130 /// <param name="distance5IDs">return value array, must have length of 28</param>
131 public void GetDistance5IDs(int locationID, int[] distance5IDs)
132 {
133 int wrap = SizeX;
134 int y0 = locationID / wrap;
135 int xComponent0 = locationID - y0 * wrap - 1;
136 int xComponentSwitch = xComponent0 - 1 + (y0 & 1);
137 if (xComponent0 < 0)
138 xComponent0 += wrap;
139 if (xComponentSwitch < 0)
140 xComponentSwitch += wrap;
141 xComponentSwitch = xComponentSwitch ^ xComponent0; // allows easy switching between 2 values
142 int yComponent = wrap * (y0 - 3); // may start negative
143 int V21 = 0;
144 int bit = 1;
145 for (int dy = 0; dy < 7; dy++)
146 {
147 if (yComponent < Size)
148 {
149 xComponent0 = xComponent0 ^ xComponentSwitch; // switch
150 int xComponent = xComponent0;
151 for (int dx = 0; dx < 4; dx++)
152 {
153 if ((bit & 0x67F7F76) != 0)
154 distance5IDs[V21] = xComponent + yComponent;
155 else
156 distance5IDs[V21] = -0x2000;
157 xComponent++;
158 if (xComponent >= wrap)
159 xComponent -= wrap;
160 V21++;
161 bit <<= 1;
162 }
163 yComponent += wrap;
164 }
165 else
166 {
167 for (int dx = 0; dx < 4; dx++)
168 {
169 if ((bit & 0x67F7F76) != 0)
170 distance5IDs[V21] = -0x1000;
171 else
172 distance5IDs[V21] = -0x2000;
173 V21++;
174 bit <<= 1;
175 }
176 }
177 }
178 }
179 #endregion
180 }
181
182 struct RC // relative coordinates
183 {
184 public readonly int a;
185 public readonly int b;
186
187 public RC(int a, int b)
188 {
189 this.a = a;
190 this.b = b;
191 }
192
193 public override string ToString()
194 {
195 return string.Format("({0},{1})",a, b);
196 }
197
198 public static bool operator ==(RC RC1, RC RC2) { return RC1.a == RC2.a && RC1.b == RC2.b; }
199 public static bool operator !=(RC RC1, RC RC2) { return RC1.a != RC2.a || RC1.b != RC2.b; }
200 public override bool Equals(object obj) { return a == ((RC)obj).a && b == ((RC)obj).b; }
201 public override int GetHashCode() { return a + (b << 16); }
202
203 public static RC operator +(RC RC1, RC RC2) { return new RC(RC1.a + RC2.a, RC1.b + RC2.b); }
204 public static RC operator -(RC RC1, RC RC2) { return new RC(RC1.a - RC2.a, RC1.b - RC2.b); }
205
206 /// <summary>
207 /// Absolute distance regardless of direction.
208 /// One tile counts 2 if straight, 3 if diagonal.
209 /// </summary>
210 public int Distance
211 {
212 get
213 {
214 int adx = Math.Abs(a-b);
215 int ady = Math.Abs(a+b);
216 return adx + ady + (Math.Abs(adx - ady) >> 1);
217 }
218 }
219 }
220
221 struct OtherLocation
222 {
223 public readonly Location Location;
224 public readonly RC RC;
225
226 public OtherLocation(Location location, RC RC) // location is the other location, RC the coordinate relative to an origin tile not stored
227 {
228 this.Location = location;
229 this.RC = RC;
230 }
231 }
232
233 struct JobProgress
234 {
235 public readonly int Required;
236 public readonly int Done;
237 public readonly int NextTurnPlus;
238
239 public JobProgress(int Required, int Done, int NextTurnPlus)
240 {
241 this.Required = Required;
242 this.Done = Done;
243 this.NextTurnPlus = NextTurnPlus;
244 }
245 }
246
247 unsafe struct Location
248 {
249 readonly AEmpire theEmpire;
250 public readonly int ID;
251 readonly int* address;
252
253 static readonly int[] V8_a = { 1, 1, 0, -1, -1, -1, 0, 1 };
254 static readonly int[] V8_b = { 0, 1, 1, 1, 0, -1, -1, -1 };
255
256 // for internal use, if used check < Map.Size, because not checked internally
257 public Location(AEmpire empire, int ID)
258 {
259 this.theEmpire = empire;
260 this.ID = ID;
261 this.address = theEmpire.Map.Ground + ID;
262 }
263
264 public override string ToString()
265 {
266 return string.Format("{0}", ID);
267 }
268
269 public static bool operator ==(Location location1, Location location2) { return location1.ID == location2.ID; }
270 public static bool operator !=(Location location1, Location location2) { return location1.ID != location2.ID; }
271 public override bool Equals(object obj) { return ID == ((Location)obj).ID; }
272 public override int GetHashCode() { return ID; }
273
274 public static RC operator -(Location location1, Location location2)
275 {
276 int wrap = location2.theEmpire.Map.SizeX;
277 int y1 = location2.ID / wrap;
278 int x1 = location2.ID - y1 * wrap;
279 int dy = location1.ID / wrap;
280 int dx = location1.ID - dy * wrap;
281 dx = ((dx * 2 + (dy & 1)) - (x1 * 2 + (y1 & 1)) + 3 * wrap) % (2 * wrap) - wrap;
282 dy -= y1;
283 return new RC((dx + dy) >> 1, (dy - dx) >> 1);
284 }
285
286 public static Location operator +(Location location, RC RC)
287 {
288 int wrap = location.theEmpire.Map.SizeX;
289 int y0 = location.ID / wrap;
290 int otherLocationID = (location.ID + ((RC.a - RC.b + (y0 & 1) + wrap * 2) >> 1)) % wrap + wrap * (y0 + RC.a + RC.b);
291 if (otherLocationID >= location.theEmpire.Map.Size)
292 otherLocationID = -0x1000;
293 return new Location(location.theEmpire, otherLocationID);
294 }
295
296 public static Location operator -(Location location, RC RC) { return location + new RC(-RC.a, -RC.b); }
297
298 /// <summary>
299 /// true if location is on the map, false if beyond upper or lower edge of the map
300 /// </summary>
301 public bool IsValid { get { return ID >= 0; } }
302
303 /// <summary>
304 /// set number shown on debug map
305 /// </summary>
306 /// <param name="value">number, 0 for nothing</param>
307 public void SetDebugDisplay(int value)
308 {
309 if (ID >= 0)
310 theEmpire.debugMapAddress[ID] = value;
311 }
312
313 /// <summary>
314 /// Set of all adjacent locations.
315 /// All locations returned are on the map.
316 /// Usually the array has 8 elements, but it's less if the location is close to the upper or lower edge of the map.
317 /// Take the result as a set with no specific order. Don't rely on the array indices to always have the same meaning.
318 /// </summary>
319 public OtherLocation[] Neighbors
320 {
321 get
322 {
323 int[] neighborIDs = new int[8];
324 theEmpire.Map.GetNeighborIDs(ID, neighborIDs);
325 int count = 0;
326 for (int V8 = 0; V8 < 8; V8++)
327 {
328 if (neighborIDs[V8] >= 0)
329 count++;
330 }
331 OtherLocation[] neighbors = new OtherLocation[count];
332 count = 0;
333 for (int V8 = 0; V8 < 8; V8++)
334 {
335 if (neighborIDs[V8] >= 0)
336 {
337 neighbors[count] = new OtherLocation(new Location(theEmpire, neighborIDs[V8]), new RC(V8_a[V8], V8_b[V8]));
338 count++;
339 }
340 }
341 return neighbors;
342 }
343 }
344
345 /// <summary>
346 /// Set of all locations with a distance of 5 or less, including the location itself.
347 /// This is the city radius, and also it's the extended visibility radius of units.
348 /// All locations returned are on the map.
349 /// Usually the array has 21 elements, but it's less if the location is close to the upper or lower edge of the map.
350 /// Take the result as a set with no specific order. Don't rely on the array indices to always have the same meaning.
351 /// </summary>
352 public OtherLocation[] Distance5Area
353 {
354 get
355 {
356 int[] distance5IDs = new int[28];
357 theEmpire.Map.GetDistance5IDs(ID, distance5IDs);
358 int count = 0;
359 for (int V21 = 1; V21 < 27; V21++)
360 {
361 if (distance5IDs[V21] >= 0)
362 count++;
363 }
364 OtherLocation[] distance5Area = new OtherLocation[count];
365 count = 0;
366 for (int V21 = 1; V21 < 27; V21++)
367 {
368 if (distance5IDs[V21] >= 0)
369 {
370 int dy = (V21 >> 2) - 3;
371 int dx = ((V21 & 3) << 1) - 3 + ((dy + 3) & 1);
372 distance5Area[count] = new OtherLocation(new Location(theEmpire, distance5IDs[V21]), new RC((dx + dy) >> 1, (dy - dx) >> 1));
373 count++;
374 }
375 }
376 return distance5Area;
377 }
378 }
379
380 /// <summary>
381 /// whether this location is adjacent to another one
382 /// </summary>
383 /// <param name="otherLocation">the other location</param>
384 /// <returns>true if adjacent, false if not adjacent, also false if identical</returns>
385 public bool IsNeighborOf(Location otherLocation)
386 {
387 int[] neighborIDs = new int[8];
388 theEmpire.Map.GetNeighborIDs(ID, neighborIDs);
389 return Array.IndexOf<int>(neighborIDs, otherLocation.ID) >= 0;
390 }
391
392 #region basic info
393 /// <summary>
394 /// Simulation of latitude, returns value between -90 and 90.
395 /// (May be used for strategic consideration and climate estimation.)
396 /// </summary>
397 public int Latitude { get { return 90 - (ID / theEmpire.Map.SizeX) * 180 / ((theEmpire.Map.SizeY << 1) - 1); } }
398
399 /// <summary>
400 /// whether the tile at this location was visible to an own unit or city at any point in the game
401 /// </summary>
402 public bool IsDiscovered { get { return (*address & 0x1F) != 0x1F; } }
403
404 /// <summary>
405 /// whether the tile is visible to an own unit or city in this turn
406 /// </summary>
407 public bool IsObserved { get { return (*address & 0x100000) != 0; } }
408
409 /// <summary>
410 /// whether the tile is visible to an own special commando or spy plane in this turn
411 /// </summary>
412 public bool IsSpiedOut { get { return (*address & 0x20000) != 0; } }
413
414 /// <summary>
415 /// turn in which the tile was visible to an own unit or city last
416 /// </summary>
417 public int TurnObservedLast { get { return theEmpire.Map.ObservedLast[ID]; } }
418
419 /// <summary>
420 /// whether an own city at this location would be protected by the great wall
421 /// </summary>
422 public bool IsGreatWallProtected { get { return (*address & 0x10000) != 0; } }
423
424 /// <summary>
425 /// 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.
426 /// </summary>
427 public bool IsDisallowedTerritory { get { return (*address & 0x40000000) != 0; } }
428
429 /// <summary>
430 /// whether units located here have 2 tiles observation range (distance 5) instead of adjacent locations only
431 /// </summary>
432 public bool ProvidesExtendedObservationRange
433 {
434 get
435 {
436 return BaseTerrain == Terrain.Mountains ||
437 Improvement == TerrainImprovement.Fortress ||
438 Improvement == TerrainImprovement.Base;
439 }
440 }
441 #endregion
442
443 #region terrain
444 /// <summary>
445 /// Exact terrain type including special resources.
446 /// </summary>
447 public Terrain Terrain
448 {
449 get
450 {
451 int raw = *address;
452 if ((raw & 0x1F) == 0x1F)
453 return Terrain.Unknown;
454 else if ((raw & 0x1000000) != 0)
455 return Terrain.DeadLands + ((raw >> 21) & 0x30);
456 else if ((raw & 0x7F) == 0x22)
457 return Terrain.Plains;
458 else
459 return (Terrain)((raw & 0xF) + ((raw >> 1) & 0x30) + 2);
460 }
461 }
462
463 /// <summary>
464 /// Base terrain type not including special resources.
465 /// </summary>
466 public Terrain BaseTerrain { get { return (Terrain)((int)Terrain & 0xF); } }
467
468 /// <summary>
469 /// Whether it's a water tile (terrain Ocean or Shore).
470 /// </summary>
471 public bool IsWater { get { return (*address & 0x1E) == 0x00; } }
472
473 /// <summary>
474 /// damage dealt to a unit which is not resistant to hostile terrain if that unit stays at this location for a full turn
475 /// </summary>
476 public int OneTurnHostileDamage
477 {
478 get
479 {
480 if ((*address & 0x800480) != 0 || // city, river or canal
481 (*address & 0xF000) == 0x5000) // base
482 return 0;
483 else if ((*address & 0x1F) == 0x03 && (*address & 0x60) != 0x20) // desert but not an oasis
484 return Cevo.DamagePerTurnInDesert;
485 else if ((*address & 0x1F) == 0x06) // arctic
486 return Cevo.DamagePerTurnInArctic;
487 else
488 return 0;
489 }
490 }
491
492 public bool HasRiver { get { return (*address & 0x80) != 0; } }
493 public bool HasRoad { get { return (*address & 0x100) != 0; } }
494 public bool HasRailRoad { get { return (*address & 0x200) != 0; } }
495 public bool HasCanal { get { return (*address & 0x400) != 0; } }
496 public bool IsPolluted { get { return (*address & 0x800) != 0; } }
497
498 /// <summary>
499 /// Terrain improvement built on this tile.
500 /// </summary>
501 public TerrainImprovement Improvement { get { return (TerrainImprovement)((*address >> 12) & 0xF); } }
502 #endregion
503
504 /// <summary>
505 /// Query progress of a specific settler job at this location
506 /// </summary>
507 /// <param name="job">the job</param>
508 /// <param name="progress">the progress</param>
509 /// <returns>result of operation</returns>
510 public PlayResult GetJobProgress__Turn(Job job, out JobProgress progress)
511 {
512 fixed (int* jobProgressData = new int[Protocol.nJob * 3])
513 {
514 PlayResult result = theEmpire.Play(Protocol.sGetJobProgress, ID, jobProgressData);
515 progress = new JobProgress(jobProgressData[(int)job * 3], jobProgressData[(int)job * 3 + 1], jobProgressData[(int)job * 3 + 2]);
516 return result;
517 }
518 }
519
520 /// <summary>
521 /// Nation to who's territory this location belongs. Nation.None if none.
522 /// </summary>
523 public Nation TerritoryNation
524 {
525 get
526 {
527 sbyte raw = theEmpire.Map.Territory[ID];
528 if (raw < 0)
529 return Nation.None;
530 else
531 return new Nation(theEmpire, raw);
532 }
533 }
534
535 /// <summary>
536 /// Whether a non-civil unit will cause unrest in it's home city if placed at this location.
537 /// </summary>
538 public bool MayCauseUnrest
539 {
540 get
541 {
542 switch (theEmpire.Government)
543 {
544 case Government.Republic:
545 case Government.FutureSociety:
546 {
547 sbyte raw = theEmpire.Map.Territory[ID];
548 return raw >= 0 && theEmpire.RelationTo(new Nation(theEmpire, raw)) < Relation.Alliance;
549 }
550 case Government.Democracy:
551 {
552 sbyte raw = theEmpire.Map.Territory[ID];
553 return raw < 0 || theEmpire.RelationTo(new Nation(theEmpire, raw)) < Relation.Alliance;
554 }
555 default:
556 return false;
557 }
558 }
559 }
560
561 #region unit info
562 public bool HasOwnUnit { get { return (*address & 0x600000) == 0x600000; } }
563 public bool HasOwnZoCUnit { get { return (*address & 0x10000000) != 0; } }
564 public bool HasForeignUnit { get { return (*address & 0x600000) == 0x400000; } }
565 public bool HasAnyUnit { get { return (*address & 0x400000) != 0; } }
566 public bool HasForeignSubmarine { get { return (*address & 0x80000) != 0; } }
567 public bool HasForeignStealthUnit { get { return (*address & 0x40000) != 0; } }
568 public bool IsInForeignZoC { get { return (*address & 0x20000000) != 0; } }
569
570 /// <summary>
571 /// Own unit that would defend an enemy attack to this location. null if no own unit present.
572 /// </summary>
573 public Unit OwnDefender
574 {
575 get
576 {
577 if (!HasOwnUnit)
578 return null;
579 else
580 {
581 fixed (int* data = new int[1])
582 {
583 if (!theEmpire.Play(Protocol.sGetDefender, ID, data).OK)
584 return null;
585 else
586 return theEmpire.UnitLookup[data[0]];
587 }
588 }
589 }
590 }
591
592 /// <summary>
593 /// Foreign unit that would defend an attack to this location. null if no foreign unit present.
594 /// </summary>
595 public IUnitInfo ForeignDefender
596 {
597 get
598 {
599 if (!HasForeignUnit)
600 return null;
601 else
602 return theEmpire.ForeignUnits.UnitByLocation(this);
603 }
604 }
605
606 /// <summary>
607 /// Unit that would defend an attack to this location. null if no unit present.
608 /// </summary>
609 public IUnitInfo Defender
610 {
611 get
612 {
613 if (HasOwnUnit)
614 return OwnDefender;
615 else if (HasForeignUnit)
616 return ForeignDefender;
617 else
618 return null;
619 }
620 }
621 #endregion
622
623 #region city info
624 public bool HasOwnCity { get { return (*address & 0xA00000) == 0xA00000; } }
625 public bool HasForeignCity { get { return (*address & 0xA00000) == 0x800000; } }
626 public bool HasAnyCity { get { return (*address & 0x800000) != 0; } }
627
628 /// <summary>
629 /// Own city at this location. null if no own city present.
630 /// </summary>
631 public City OwnCity
632 {
633 get
634 {
635 if (!HasOwnCity)
636 return null;
637 else
638 {
639 foreach (City city in theEmpire.Cities)
640 {
641 if (city.Location == this)
642 return city;
643 }
644 return null;
645 }
646 }
647 }
648
649 /// <summary>
650 /// Foreign city at this location. null if no foreign city present.
651 /// </summary>
652 public ForeignCity ForeignCity
653 {
654 get
655 {
656 if (!HasForeignCity)
657 return null;
658 {
659 foreach (ForeignCity city in theEmpire.ForeignCities)
660 {
661 if (city.Location == this)
662 return city;
663 }
664 return null;
665 }
666 }
667 }
668
669 /// <summary>
670 /// City at this location. null if no city present.
671 /// </summary>
672 public ICity City
673 {
674 get
675 {
676 if (HasOwnCity)
677 return OwnCity;
678 else if (HasForeignCity)
679 return ForeignCity;
680 else
681 return null;
682 }
683 }
684
685 /// <summary>
686 /// Own city that is exploiting this tile. null if not exploited or exploited by foreign city.
687 /// </summary>
688 /// <returns></returns>
689 public City GetExploitingCity__Turn()
690 {
691 if (!IsValid)
692 return null;
693 City city = null;
694 fixed (int* tileInfo = new int[4])
695 {
696 theEmpire.Play(Protocol.sGetCityTileInfo, ID, tileInfo);
697 if (tileInfo[3] >= 0)
698 city = theEmpire.CityLookup[tileInfo[3]];
699 if (city != null && city.Location != this)
700 city = null;
701 }
702 return city;
703 }
704 #endregion
705 }
706}
Note: See TracBrowser for help on using the repository browser.