Skip to content

Commit 4fa421e

Browse files
committed
Implement most of the difficulty factors
- AI players now get cheaper production on higher difficulties - AI players get extra units on turn 1 - AI players get extra unit support I also fixed a bug where the combat AI wouldn't pursue empty barbarian camps (no enemy players, no enemy cities). This was causing the extra units to just sit in the starting city.
1 parent a4bc0c6 commit 4fa421e

File tree

13 files changed

+94
-20
lines changed

13 files changed

+94
-20
lines changed

C7/UIElements/NewGame/PlayerSetup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ private void DoWorldGenerationAndstartGame(SaveGame save, GlobalSingleton Global
272272
isHuman: false);
273273
}
274274

275+
save.GameDifficulty = difficulty;
276+
275277
log.Information("saving generated map");
276278
save.Save(Global.DefaultGeneratedGamePath);
277279
Global.LoadGamePath = Global.DefaultGeneratedGamePath;

C7Engine/AI/PlayerAI.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ public static UnitAI GetAIForUnit(MapUnit unit, Player player) {
156156
} else if (unit.location.cityAtTile != null && unit.CanDefendOnLand() && unit.location.unitsOnTile.Count(u => u.CanDefendOnLand() && u != unit) == 0) {
157157
return new DefenderAI(DefenderAI.MakeAiDataForDefendInPlace(unit, player));
158158
} else if (GetCombatAIIfUnitCanAttackNearbyBarbCamp(unit, player) is UnitAI unitAI && unitAI != null) {
159-
log.Information("Set unit " + unit + " to take out barb camp");
160159
return unitAI;
161160
} else if (unit.unitType.name == "Catapult") {
162161
//For now tell catapults to sit tight. It's getting really annoying watching them pointlessly bombard barb camps forever
@@ -262,6 +261,7 @@ private static UnitAI GetCombatAIIfUnitCanAttackNearbyBarbCamp(MapUnit unit, Pla
262261

263262
PathingAlgorithm algorithm = PathingAlgorithmChooser.GetAlgorithm(unit);
264263
caid.path = algorithm.PathFrom(unit.location, closestBarbCamp);
264+
log.Information($"Set unit {unit} to take out barb camp at {closestBarbCamp}");
265265
return new CombatAI(caid);
266266
}
267267
return null;

C7Engine/AI/UnitAI/CombatAI.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ public C7GameData.UnitAI.Result PlayTurnImpl(Player player, MapUnit unit) {
5151
&& !dest.unitsOnTile[0].owner.IsAtPeaceWith(unit.owner);
5252
bool destinationHasEnemyCity = dest.cityAtTile != null
5353
&& !data.destination.cityAtTile.owner.IsAtPeaceWith(unit.owner);
54+
bool destinationHasBarbCamp = dest.hasBarbarianCamp;
5455

55-
if (!destinationHasEnemyCity && !destinationHasEnemyUnits) {
56+
if (!destinationHasEnemyCity && !destinationHasEnemyUnits && !destinationHasBarbCamp) {
5657
return C7GameData.UnitAI.Result.Done;
5758
}
5859

@@ -63,8 +64,9 @@ public C7GameData.UnitAI.Result PlayTurnImpl(Player player, MapUnit unit) {
6364
foreach (Tile t in unit.location.neighbors.Values) {
6465
bool nextToEnemyCity = t.cityAtTile != null && !t.cityAtTile.owner.IsAtPeaceWith(unit.owner);
6566
bool nextToEnemyUnit = t.unitsOnTile.Count > 0 && !t.unitsOnTile[0].owner.IsAtPeaceWith(unit.owner);
67+
bool nextToBarbCamp = t.hasBarbarianCamp;
6668

67-
if (!nextToEnemyCity && !nextToEnemyUnit) {
69+
if (!nextToEnemyCity && !nextToEnemyUnit && !nextToBarbCamp) {
6870
continue;
6971
}
7072

C7Engine/C7GameData/Building.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,13 @@ public bool CanProduce(City city, HashSet<Resource> accessibleResources) {
228228
return true;
229229
}
230230

231-
public int ShieldCost(HashSet<Civilization.Trait> civTraits) {
231+
public int ShieldCost(HashSet<Civilization.Trait> civTraits, float costFactor) {
232232
foreach (Civilization.Trait trait in dataSource.traits) {
233233
if (civTraits.Contains(trait)) {
234-
return (int)(shieldCost * EngineStorage.gameData.rules.BuildingDiscountForCivTraits);
234+
return (int)(shieldCost * EngineStorage.gameData.rules.BuildingDiscountForCivTraits * costFactor);
235235
}
236236
}
237-
return shieldCost;
237+
return (int)(shieldCost * costFactor);
238238
}
239239

240240
public bool isGreatWonderObsolete(Player owner) {

C7Engine/C7GameData/IProducible.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface IProducible {
1111
Tech requiredTech { get; set; }
1212
HashSet<Resource> requiredResources { get; set; }
1313

14-
int ShieldCost(HashSet<Civilization.Trait> civTraits);
14+
int ShieldCost(HashSet<Civilization.Trait> civTraits, float costFactor);
1515

1616
bool CanProduce(City city, HashSet<Resource> accessibleResources);
1717
}

C7Engine/C7GameData/ImportCiv3.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,9 +1312,15 @@ private void ImportRules() {
13121312
save.Rules.CitizenValueInShields = rule.CitizenValueInShields;
13131313
save.Rules.TurnPenaltyForEachHurrySacrifice = rule.TurnPenaltyForEachHurrySacrifice;
13141314
save.GameDifficulty = save.Difficulties[rule.DefaultDifficultyLevel];
1315-
save.Rules.StartUnitType1 = theBiq.Prto[rule.StartUnitType1].Name;
1316-
save.Rules.StartUnitType2 = theBiq.Prto[rule.StartUnitType2].Name;
1317-
save.Rules.ScoutUnitType = theBiq.Prto[rule.Scout].Name;
1315+
if (rule.StartUnitType1 >= 0) {
1316+
save.Rules.StartUnitType1 = theBiq.Prto[rule.StartUnitType1].Name;
1317+
}
1318+
if (rule.StartUnitType2 >= 0) {
1319+
save.Rules.StartUnitType2 = theBiq.Prto[rule.StartUnitType2].Name;
1320+
}
1321+
if (rule.Scout >= 0) {
1322+
save.Rules.ScoutUnitType = theBiq.Prto[rule.Scout].Name;
1323+
}
13181324
}
13191325

13201326
private static void SetWorldWrap(SavData civ3Save, SaveGame save) {

C7Engine/C7GameData/Player.cs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,9 @@ public int GetAdjustedOptimalCityNumber(GameData gameData) {
459459
// civilizations, the formula is: 1 (2 for Conquests) + random number
460460
// between 1-4 + number between 0-3 depending on size of your empire.
461461
public int GetTurnsOfAnarchyForTransition(GameData gameData) {
462-
// TODO: if religious, return 2.
462+
if (civilization.traits.Contains(Civilization.Trait.Religious)) {
463+
return 2;
464+
}
463465

464466
// We add Next(3)+Next(3) to roughly approximate a normal
465467
// distribution. With the base of 2, this gets us a random value
@@ -617,12 +619,19 @@ private bool CanAdvanceToNextEra(GameData gameData) {
617619
public (int, int, int) TotalUnitsAllowedUnitsAndSupportCost() {
618620
int freeUnits = 0;
619621

622+
Difficulty difficulty = EngineStorage.gameData.gameDifficulty;
623+
if (!isHuman) {
624+
freeUnits += difficulty.AdditionalFreeUnitSupport;
625+
}
626+
620627
foreach (City city in cities) {
621-
// TODO: Import these sizes from Rule.cs in the biq. Maybe have
622-
// them live in the city class?
623-
if (city.residents.Count <= 6) {
628+
if (!isHuman) {
629+
freeUnits += difficulty.UnitSupportBonusForEachSettlement;
630+
}
631+
632+
if (city.residents.Count <= rules.MaximumLevel1CitySize) {
624633
freeUnits += government.freeUnitsPerTown;
625-
} else if (city.residents.Count <= 12) {
634+
} else if (city.residents.Count <= rules.MaximumLevel2CitySize) {
626635
freeUnits += government.freeUnitsPerCity;
627636
} else {
628637
freeUnits += government.freeUnitsPerMetropolis;
@@ -745,6 +754,43 @@ public List<Tuple<City, CityBuilding>> GetActiveWonders() {
745754
return result;
746755
}
747756

757+
public void MaybeSpawnBonusUnits(GameData gD) {
758+
// Bonus units only spawn on the first turn, if we have a city and
759+
// are a non-barbarian AI player.
760+
if (gD.turn != 1 || cities.Count != 1 || isHuman || isBarbarians) {
761+
return;
762+
}
763+
764+
for (int i = 0; i < gD.gameDifficulty.ExtraStartUnit1; ++i) {
765+
cities[0].AddUnit(gD.unitPrototypes.Find(x => x.name == gD.rules.StartUnitType1), gD);
766+
}
767+
for (int i = 0; i < gD.gameDifficulty.ExtraStartUnit2; ++i) {
768+
cities[0].AddUnit(gD.unitPrototypes.Find(x => x.name == gD.rules.StartUnitType2), gD);
769+
}
770+
for (int i = 0; i < gD.gameDifficulty.NumberOfAIDefensiveStartingUnits; ++i) {
771+
UnitPrototype unit = (UnitPrototype)cities[0].ListProductionOptions(gD).MaxBy(
772+
x => {
773+
if (x is UnitPrototype u) {
774+
return u.defense;
775+
}
776+
return -1;
777+
}
778+
);
779+
cities[0].AddUnit(unit, gD);
780+
}
781+
for (int i = 0; i < gD.gameDifficulty.NumberOfAIOffensiveStartingUnits; ++i) {
782+
UnitPrototype unit = (UnitPrototype)cities[0].ListProductionOptions(gD).MaxBy(
783+
x => {
784+
if (x is UnitPrototype u) {
785+
return u.attack;
786+
}
787+
return -1;
788+
}
789+
);
790+
cities[0].AddUnit(unit, gD);
791+
}
792+
}
793+
748794
public void UpdateResourcesInBorders(IEnumerable<Tile> ownedTiles) {
749795
resourcesInBorders = ownedTiles
750796
.Where(t => t.Resource != Resource.NONE)
@@ -758,7 +804,11 @@ public bool HasRequiredTechnology(IProducible producible) {
758804
}
759805

760806
public int ShieldCost(IProducible producible) {
761-
return producible.ShieldCost(civilization.traits);
807+
// At higher difficulties, AI players get a cost discount.
808+
Difficulty difficulty = EngineStorage.gameData.gameDifficulty;
809+
float costFactor = isHuman ? 1.0f : difficulty.AiCostFactor / (float)(difficulty.HumanCostFactor);
810+
811+
return producible.ShieldCost(civilization.traits, costFactor);
762812
}
763813
}
764814

C7Engine/C7GameData/Terraform.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ public Terraform(SaveTerraform saveTerraform, GameData gameData) {
5555
SetRules();
5656
}
5757

58+
public string ToString() {
59+
return Name;
60+
}
61+
5862
private void SetRules() {
5963
if (TerraformRules.ActionEffects.TryGetValue(Action, out var onComplete)) {
6064
OnComplete = onComplete;

C7Engine/C7GameData/Tile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public bool IsWater() {
220220
}
221221

222222
public bool IsAllowCities() {
223-
return overlayTerrainType.allowCities;
223+
return overlayTerrainType.allowCities && !hasBarbarianCamp;
224224
}
225225

226226
public bool IsVolcano() {

C7Engine/C7GameData/UnitPrototype.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ public bool CanProduce(City city, HashSet<Resource> accessibleResources) {
104104
return MeetsProductionRequirements(city, accessibleResources) && !IsUnitObsolete(city, accessibleResources);
105105
}
106106

107-
public int ShieldCost(HashSet<Civilization.Trait> civTraits) {
108-
return shieldCost;
107+
public int ShieldCost(HashSet<Civilization.Trait> civTraits, float costFactor) {
108+
return (int)(shieldCost * costFactor);
109109
}
110110

111111
// TODO: Consider golden ages when determining whether a unit is obsolete.

0 commit comments

Comments
 (0)