From f6e39541523d0d51a257a2fe483ec06df538179e Mon Sep 17 00:00:00 2001 From: Bradley Gannon Date: Tue, 4 Feb 2025 20:29:54 -0500 Subject: [PATCH] Add settler tile adjustments to Civilization class - Add `SettlerTileAdjustments` as a child of `Civilization` - Encodes the previously hard-coded values as defaults for public fields - Uses lambda functions to take values at runtime for adjustments that take arguments; this allows future users to supply arbitrary logic - Replace hard-coded bonus values in `SettlerLocationAI.cs` with calls and references to `SettlerTileAdjustments` via the supplied `Player` - Notably, `DistancePenalty` is now a negative value by default, which I think is more clear because now adjustments are always added to the tile score - Make settler tile score a `float` instead of `int`, which gives more precision for multiplicative adjustments - Apply lots of lints from Rider - Identifier case tweaks (this caused most of the changes in other files) - Using ternary operators to make code a little smaller when it's still clear - Some grammar problems (Rider is picky about these for some reason) - Lint ignores in cases where it makes sense (to me) - I'm willing to undo these if they're too aggressive or unwanted --- C7/C7.sln.DotSettings | 4 + C7/Game.cs | 28 ++-- C7/Map/BorderLayer.cs | 2 +- C7/Map/CityLabelScene.cs | 2 +- C7/Map/FogOfWarLayer.cs | 2 +- C7/Map/UnitLayer.cs | 2 +- C7/MapView.cs | 2 +- C7/UIElements/Advisors/ScienceAdvisor.cs | 14 +- C7/UIElements/Popups/CivilizationDestroyed.cs | 2 +- C7/UIElements/RightClickMenu.cs | 12 +- C7Engine/AI/BarbarianAI.cs | 4 +- C7Engine/AI/CityProductionAI.cs | 2 +- C7Engine/AI/PlayerAI.cs | 120 +++++++++--------- C7Engine/AI/StrategicAI/ExpansionPriority.cs | 2 +- .../AI/StrategicAI/UtilityCalculations.cs | 10 +- C7Engine/AI/StrategicAI/WarPriority.cs | 4 +- C7Engine/AI/UnitAI/ExplorerAI.cs | 8 +- C7Engine/AI/UnitAI/SettlerAI.cs | 27 ++-- C7Engine/AI/UnitAI/SettlerLocationAI.cs | 96 +++++++------- C7Engine/EntryPoints/CityInteractions.cs | 6 +- C7Engine/EntryPoints/CreateGame.cs | 6 +- C7Engine/EntryPoints/MessageToEngine.cs | 10 +- C7Engine/EntryPoints/TurnHandling.cs | 30 ++--- C7Engine/EntryPoints/UnitInteractions.cs | 4 +- C7Engine/MapUnitExtensions.cs | 12 +- C7GameData/AIData/SettlerAIData.cs | 14 +- C7GameData/Civilization.cs | 52 ++++++-- C7GameData/GameData.cs | 4 +- C7GameData/ImportCiv3.cs | 28 ++-- C7GameData/Player.cs | 63 +++++---- C7GameData/Save/SaveCity.cs | 8 +- C7GameData/Save/SaveGame.cs | 4 +- C7GameData/Save/SavePlayer.cs | 58 ++++----- C7GameData/Save/SaveUnit.cs | 4 +- C7GameDataTests/SaveTest.cs | 12 +- 35 files changed, 343 insertions(+), 315 deletions(-) create mode 100644 C7/C7.sln.DotSettings diff --git a/C7/C7.sln.DotSettings b/C7/C7.sln.DotSettings new file mode 100644 index 00000000..b8373783 --- /dev/null +++ b/C7/C7.sln.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/C7/Game.cs b/C7/Game.cs index 3035ecef..12132806 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -113,12 +113,12 @@ public override void _Ready() { // Set initial camera location. If the UI controller has any cities, focus on their capital. Otherwise, focus on their // starting settler. - if (controller.cities.Count > 0) { - City capital = controller.cities.Find(c => c.IsCapital()); + if (controller.Cities.Count > 0) { + City capital = controller.Cities.Find(c => c.IsCapital()); if (capital != null) mapView.centerCameraOnTile(capital.location); } else { - MapUnit startingSettler = controller.units.Find(u => u.unitType.actions.Contains(C7Action.UnitBuildCity)); + MapUnit startingSettler = controller.Units.Find(u => u.unitType.actions.Contains(C7Action.UnitBuildCity)); if (startingSettler != null) mapView.centerCameraOnTile(startingSettler.location); } @@ -155,7 +155,7 @@ public void processEngineMessages(GameData gameData) { switch (msg) { case MsgStartUnitAnimation mSUA: MapUnit unit = gameData.GetUnit(mSUA.unitID); - if (unit != null && (controller.tileKnowledge.isTileKnown(unit.location) || controller.tileKnowledge.isTileKnown(unit.previousLocation))) { + if (unit != null && (controller.TileKnowledge.isTileKnown(unit.location) || controller.TileKnowledge.isTileKnown(unit.previousLocation))) { // TODO: This needs to be extended so that the player is shown when AIs found cities, when they move units // (optionally, depending on preferences) and generalized so that modders can specify whether custom // animations should be shown to the player. @@ -173,7 +173,7 @@ public void processEngineMessages(GameData gameData) { int x, y; gameData.map.tileIndexToCoords(mSEA.tileIndex, out x, out y); Tile tile = gameData.map.tileAt(x, y); - if (tile != Tile.NONE && controller.tileKnowledge.isTileKnown(tile)) + if (tile != Tile.NONE && controller.TileKnowledge.isTileKnown(tile)) animTracker.startAnimation(tile, mSEA.effect, mSEA.completionEvent, mSEA.ending); else { if (mSEA.completionEvent != null) @@ -193,9 +193,9 @@ public void processEngineMessages(GameData gameData) { // handling cases like 1 city elimination, regicide, settlers that // are still alive, etc. if (mCD.city.owner.RemainingCities() == 0) { - popupOverlay.ShowPopup(new CivilizationDestroyed(mCD.city.owner.civilization), PopupOverlay.PopupCategory.Advisor); - for (int i = 0; i < mCD.city.owner.units.Count; ++i) { - MapUnitExtensions.disband(mCD.city.owner.units[i]); + popupOverlay.ShowPopup(new CivilizationDestroyed(mCD.city.owner.Civilization), PopupOverlay.PopupCategory.Advisor); + for (int i = 0; i < mCD.city.owner.Units.Count; ++i) { + MapUnitExtensions.disband(mCD.city.owner.Units[i]); } } break; @@ -213,7 +213,7 @@ public void processEngineMessages(GameData gameData) { // F6 is the science advisor. // TODO: Move the F* key strings to a set of constants/enum. EmitSignal(SignalName.ShowSpecificAdvisor, "F6"); - Tech tech = gameData.techs.Find(x => x.id == gameData.GetHumanPlayers()[0].currentlyResearchedTech); + Tech tech = gameData.techs.Find(x => x.id == gameData.GetHumanPlayers()[0].CurrentlyResearchedTech); // TODO: calculate research speed. EmitSignal(SignalName.UpdateTechProgress, tech.Name, -1); @@ -287,7 +287,7 @@ public override void _Process(double delta) { // If "location" is not already near the center of the screen, moves the camera to bring it into view. public void ensureLocationIsInView(Tile location) { - if (controller.tileKnowledge.isTileKnown(location) && location != Tile.NONE) { + if (controller.TileKnowledge.isTileKnown(location) && location != Tile.NONE) { Vector2 relativeScreenLocation = mapView.screenLocationOfTile(location, true) / mapView.getVisibleAreaSize(); if (relativeScreenLocation.DistanceTo(new Vector2((float)0.5, (float)0.5)) > 0.30) mapView.centerCameraOnTile(location); @@ -342,7 +342,7 @@ private void OnPlayerStartTurn() { int turnNumber = TurnHandling.GetTurnNumber(); Player player = gameDataAccess.gameData.GetHumanPlayers()[0]; - EmitSignal(SignalName.TurnStarted, turnNumber, player.gold, /*goldPerTurn=*/0); + EmitSignal(SignalName.TurnStarted, turnNumber, player.Gold, /*goldPerTurn=*/0); CurrentState = GameState.PlayerTurn; GetNextAutoselectedUnit(gameDataAccess.gameData); @@ -510,12 +510,12 @@ public override void _UnhandledInput(InputEvent @event) { gameDataAccess.gameData.observerMode = !gameDataAccess.gameData.observerMode; if (gameDataAccess.gameData.observerMode) { foreach (Player player in gameDataAccess.gameData.players) { - player.isHuman = false; + player.IsHuman = false; } } else { foreach (Player player in gameDataAccess.gameData.players) { - if (player.id == EngineStorage.uiControllerID) { - player.isHuman = true; + if (player.Id == EngineStorage.uiControllerID) { + player.IsHuman = true; } } } diff --git a/C7/Map/BorderLayer.cs b/C7/Map/BorderLayer.cs index b88394fc..3562bc3b 100644 --- a/C7/Map/BorderLayer.cs +++ b/C7/Map/BorderLayer.cs @@ -75,7 +75,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til { TileDirection.SOUTHEAST, 6 } }; - Color borderColor = Util.LoadColor(tile.owner.colorIndex); + Color borderColor = Util.LoadColor(tile.owner.ColorIndex); foreach (var entry in directionToTextureIdx) { if (tile.neighbors[entry.Key].owner != tile.owner) { diff --git a/C7/Map/CityLabelScene.cs b/C7/Map/CityLabelScene.cs index 4c3fbae7..8eed6698 100644 --- a/C7/Map/CityLabelScene.cs +++ b/C7/Map/CityLabelScene.cs @@ -154,7 +154,7 @@ private Image CreateLabelBackground(int cityLabelWidth, City city, int textAreaW Image labelImage = Image.Create(cityLabelWidth, CITY_LABEL_HEIGHT, false, Image.Format.Rgba8); labelImage.Fill(Color.Color8(0, 0, 0, 0)); byte transparencyLevel = 192; //25% - Color civColor = Util.LoadColor(city.owner.colorIndex); + Color civColor = Util.LoadColor(city.owner.ColorIndex); civColor = new Color(civColor, transparencyLevel); Color civColorDarker = Color.Color8(0, 0, 138, transparencyLevel); //todo: automate the darker() function. maybe less transparency? Color topRowGrey = Color.Color8(32, 32, 32, transparencyLevel); diff --git a/C7/Map/FogOfWarLayer.cs b/C7/Map/FogOfWarLayer.cs index eb88eab9..bcf7816a 100644 --- a/C7/Map/FogOfWarLayer.cs +++ b/C7/Map/FogOfWarLayer.cs @@ -16,7 +16,7 @@ public FogOfWarLayer() { public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { Rect2 screenTarget = new Rect2(tileCenter - tileSize / 2, tileSize); - TileKnowledge tileKnowledge = gameData.GetHumanPlayers()[0].tileKnowledge; + TileKnowledge tileKnowledge = gameData.GetHumanPlayers()[0].TileKnowledge; //N.B. FogOfWar.pcx handles both totally unknown and fogged tiles, indexed in the same file. //Hence the trinary math rather than the more commonplace binary. if (!tileKnowledge.isTileKnown(tile)) { diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitLayer.cs index 1a45fa9e..53160570 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitLayer.cs @@ -149,7 +149,7 @@ public void drawUnitAnimFrame(LooseView looseView, MapUnit unit, MapUnit.Appeara Vector2 position = tileCenter + animOffset - new Vector2(0, inst.FrameSize(animName).Y / 4); inst.SetPosition(position); - Color civColor = Util.LoadColor(unit.owner.colorIndex); + Color civColor = Util.LoadColor(unit.owner.ColorIndex); int nextFrame = inst.GetNextFrameByProgress(animName, appearance.progress); inst.material.SetShaderParameter("tintColor", new Vector3(civColor.R, civColor.G, civColor.B)); diff --git a/C7/MapView.cs b/C7/MapView.cs index 7cd29b72..1c0a9bfb 100644 --- a/C7/MapView.cs +++ b/C7/MapView.cs @@ -517,7 +517,7 @@ private static bool IsTileKnown(Tile tile, UIGameDataAccess gameDataAccess) { if (gameDataAccess.gameData.observerMode) { return true; } - return tile != Tile.NONE && gameDataAccess.gameData.GetHumanPlayers()[0].tileKnowledge.isTileKnown(tile); + return tile != Tile.NONE && gameDataAccess.gameData.GetHumanPlayers()[0].TileKnowledge.isTileKnown(tile); } } diff --git a/C7/UIElements/Advisors/ScienceAdvisor.cs b/C7/UIElements/Advisors/ScienceAdvisor.cs index 44a89635..f1e2b7f5 100644 --- a/C7/UIElements/Advisors/ScienceAdvisor.cs +++ b/C7/UIElements/Advisors/ScienceAdvisor.cs @@ -65,28 +65,28 @@ void DrawTechTree() { using (UIGameDataAccess gameDataAccess = new()) { List techs = gameDataAccess.gameData.techs; Player player = gameDataAccess.gameData.GetHumanPlayers()[0]; - HashSet knownTechs = player.knownTechs; + HashSet knownTechs = player.KnownTechs; // Set the tech background based on the player's era. - if (player.eraCivilopediaName == "ERAS_Ancient_Times") { + if (player.EraCivilopediaName == "ERAS_Ancient_Times") { this.Texture = AncientBackground; - } else if (player.eraCivilopediaName == "ERAS_Middle_Ages") { + } else if (player.EraCivilopediaName == "ERAS_Middle_Ages") { this.Texture = MiddleBackground; - } else if (player.eraCivilopediaName == "ERAS_Industrial_Age") { + } else if (player.EraCivilopediaName == "ERAS_Industrial_Age") { this.Texture = IndustrialBackground; - } else if (player.eraCivilopediaName == "ERAS_Modern_Era") { + } else if (player.EraCivilopediaName == "ERAS_Modern_Era") { this.Texture = ModernBackground; } foreach (Tech tech in techs) { - if (tech.EraCivilopediaName != player.eraCivilopediaName) { + if (tech.EraCivilopediaName != player.EraCivilopediaName) { continue; } TechBox.TechState techState = TechBox.TechState.kBlocked; if (knownTechs.Contains(tech.id)) { techState = TechBox.TechState.kKnown; - } else if (player.currentlyResearchedTech == tech.id) { + } else if (player.CurrentlyResearchedTech == tech.id) { techState = TechBox.TechState.kInProgress; } else { bool prereqsKnown = true; diff --git a/C7/UIElements/Popups/CivilizationDestroyed.cs b/C7/UIElements/Popups/CivilizationDestroyed.cs index c04f1ca0..4adc2e81 100644 --- a/C7/UIElements/Popups/CivilizationDestroyed.cs +++ b/C7/UIElements/Popups/CivilizationDestroyed.cs @@ -10,7 +10,7 @@ public partial class CivilizationDestroyed : Popup { public CivilizationDestroyed(Civilization civ) { alignment = BoxContainer.AlignmentMode.End; margins = new Margins(right: 10); - civNoun = civ.noun; + civNoun = civ.Noun; } public override void _Ready() { diff --git a/C7/UIElements/RightClickMenu.cs b/C7/UIElements/RightClickMenu.cs index 301bde77..faabf1f8 100644 --- a/C7/UIElements/RightClickMenu.cs +++ b/C7/UIElements/RightClickMenu.cs @@ -128,8 +128,8 @@ public void ResetItems(Tile tile, Dictionary uiUpdatedUnitStates = nul RemoveAll(); int fortifiedCount = 0; - List playerUnits = tile.unitsOnTile.FindAll(unit => unit.owner.id == game.controller.id); - List nonPlayerUnits = tile.unitsOnTile.FindAll(unit => unit.owner.id != game.controller.id); + List playerUnits = tile.unitsOnTile.FindAll(unit => unit.owner.Id == game.controller.Id); + List nonPlayerUnits = tile.unitsOnTile.FindAll(unit => unit.owner.Id != game.controller.Id); foreach (MapUnit unit in playerUnits) { bool isFortified = isUnitFortified(unit, uiUpdatedUnitStates); @@ -159,15 +159,15 @@ public void ResetItems(Tile tile, Dictionary uiUpdatedUnitStates = nul if (nonPlayerUnits.Count > 0) { if (tile.cityAtTile == null) { foreach (MapUnit unit in nonPlayerUnits) { - AddItem($"{unit.owner.civilization.noun} {unit.Describe()}", null); + AddItem($"{unit.owner.Civilization.Noun} {unit.Describe()}", null); } - AddItem($"Contact {nonPlayerUnits[0].owner.civilization.name}", null); + AddItem($"Contact {nonPlayerUnits[0].owner.Civilization.Name}", null); } else { // TODO: This isn't necessarily the top unit, get that code to an accessible // location and then use it here. MapUnit unit = nonPlayerUnits[0]; - AddItem($"{unit.owner.civilization.noun} {unit.Describe()}", null); - AddItem($"Contact {unit.owner.civilization.name}", null); + AddItem($"{unit.owner.Civilization.Noun} {unit.Describe()}", null); + AddItem($"Contact {unit.owner.Civilization.Name}", null); } } } diff --git a/C7Engine/AI/BarbarianAI.cs b/C7Engine/AI/BarbarianAI.cs index 27a7b353..0086a83e 100644 --- a/C7Engine/AI/BarbarianAI.cs +++ b/C7Engine/AI/BarbarianAI.cs @@ -12,14 +12,14 @@ public class BarbarianAI { private ILogger log = Log.ForContext(); public void PlayTurn(Player player, GameData gameData) { - if (!player.isBarbarians) { + if (!player.IsBarbarians) { throw new System.Exception("Barbarian AI can only play barbarian players"); } // Copy unit list into temporary array so we can remove units while iterating. // TODO: We also need to handle units spawned during the loop, e.g. leaders, armies, enslaved units. This is not so much an // issue for the barbs but will be for similar loops elsewhere in the AI logic. - foreach (MapUnit unit in player.units.ToArray()) { + foreach (MapUnit unit in player.Units.ToArray()) { if (UnitIsFreeToMove(unit)) { while (unit.movementPoints.canMove) { //Move randomly diff --git a/C7Engine/AI/CityProductionAI.cs b/C7Engine/AI/CityProductionAI.cs index e77d3a91..980cc5c4 100644 --- a/C7Engine/AI/CityProductionAI.cs +++ b/C7Engine/AI/CityProductionAI.cs @@ -29,7 +29,7 @@ public class CityProductionAI { * get hung up on knowing exactly how it should be done the road. */ public static IProducible GetNextItemToBeProduced(City city, IProducible lastProduced) { - List priorities = city.owner.strategicPriorityData; + List priorities = city.owner.StrategicPriorityData; IEnumerable unitPrototypes = city.ListProductionOptions(); log.Information($"Choosing what to produce next in {city.name}"); diff --git a/C7Engine/AI/PlayerAI.cs b/C7Engine/AI/PlayerAI.cs index d7fc0273..05a51900 100644 --- a/C7Engine/AI/PlayerAI.cs +++ b/C7Engine/AI/PlayerAI.cs @@ -8,32 +8,38 @@ using C7Engine.AI.StrategicAI; using C7Engine.AI.UnitAI; using Serilog; +// ReSharper disable ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator +// ReSharper disable InvertIf +// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator +// ReSharper disable CheckNamespace namespace C7Engine { - public class PlayerAI { - private static ILogger log = Log.ForContext(); + // ReSharper disable once ClassNeverInstantiated.Global + public class PlayerAi { + // ReSharper disable once InconsistentNaming + private static readonly ILogger log = Log.ForContext(); public static void PlayTurn(Player player, Random rng) { - if (player.isHuman || player.isBarbarians) { + if (player.IsHuman || player.IsBarbarians) { return; } - log.Information("-> Begin " + player.civilization.cityNames[0] + " turn"); + log.Information("-> Begin " + player.Civilization.CityNames[0] + " turn"); - if (player.turnsUntilPriorityReevaluation == 0) { + if (player.TurnsUntilPriorityReevaluation == 0) { log.Information("Re-evaluating strategic priorities for " + player); List priorities = StrategicPriorityArbitrator.Arbitrate(player); - player.strategicPriorityData.Clear(); + player.StrategicPriorityData.Clear(); foreach (StrategicPriority priority in priorities) { - player.strategicPriorityData.Add(priority); + player.StrategicPriorityData.Add(priority); } - player.turnsUntilPriorityReevaluation = 15 + GameData.rng.Next(10); - log.Information(player.turnsUntilPriorityReevaluation + " turns until next re-evaluation"); + player.TurnsUntilPriorityReevaluation = 15 + GameData.rng.Next(10); + log.Information(player.TurnsUntilPriorityReevaluation + " turns until next re-evaluation"); } else { - player.turnsUntilPriorityReevaluation--; + player.TurnsUntilPriorityReevaluation--; } //Do things with units. Copy into an array first to avoid collection-was-modified exception - foreach (MapUnit unit in player.units.ToArray()) { + foreach (MapUnit unit in player.Units.ToArray()) { //For each unit, if there's already an AI task assigned, it will attempt to complete its goal. //It may fail due to conditions having changed since that goal was assigned; in that case it will //get a new task to try to complete. @@ -43,10 +49,10 @@ public static void PlayTurn(Player player, Random rng) { int maxAttempts = 2; //safety valve so we don't freeze the UI if SetAIForUnit returns something that fails while (!unitDone) { if (unit.currentAIData == null || attempts > 0) { - SetAIForUnit(unit, player); + SetAiForUnit(unit, player); } - UnitAI artificialIntelligence = getAIForUnitStrategy(unit.currentAIData); + UnitAI artificialIntelligence = GetAiForUnitStrategy(unit.currentAIData); unitDone = artificialIntelligence.PlayTurn(player, unit); attempts++; @@ -56,60 +62,56 @@ public static void PlayTurn(Player player, Random rng) { } } - player.tileKnowledge.AddTilesToKnown(unit.location); + player.TileKnowledge.AddTilesToKnown(unit.location); } } - public static void SetAIForUnit(MapUnit unit, Player player) { + public static void SetAiForUnit(MapUnit unit, Player player) { //figure out an AI behavior //TODO: Use strategies, not names if (unit.unitType.name == "Settler") { - SettlerAIData settlerAiData = new SettlerAIData(); - settlerAiData.goal = SettlerAIData.SettlerGoal.BUILD_CITY; + SettlerAiData settlerAiData = new() { Goal = SettlerAiData.SettlerGoal.BuildCity }; //If it's the starting settler, have it settle in place. Otherwise, use an AI to find a location. - if (player.cities.Count == 0 && unit.location.cityAtTile == null) { - settlerAiData.destination = unit.location; - log.Information("No cities yet! Set AI for unit to settler AI with destination of " + settlerAiData.destination); + if (player.Cities.Count == 0 && unit.location.cityAtTile == null) { + settlerAiData.Destination = unit.location; + log.Information("No cities yet! Set AI for unit to settler AI with destination of " + settlerAiData.Destination); } else { - settlerAiData.destination = SettlerLocationAI.findSettlerLocation(unit.location, player); - if (settlerAiData.destination == Tile.NONE) { + settlerAiData.Destination = SettlerLocationAi.FindSettlerLocation(unit.location, player); + if (settlerAiData.Destination == Tile.NONE) { //This is possible if all tiles within 4 tiles of a city are either not land, or already claimed //by another colonist. Longer-term, the AI shouldn't be building settlers if that is the case, //but right now we'll just spike the football to stop the clock and avoid building immediately next to another city. - settlerAiData.goal = SettlerAIData.SettlerGoal.JOIN_CITY; + settlerAiData.Goal = SettlerAiData.SettlerGoal.JoinCity; log.Information("Set AI for unit to JOIN_CITY due to lack of locations to settle"); } else { PathingAlgorithm algorithm = PathingAlgorithmChooser.GetAlgorithm(unit.IsLandUnit()); - settlerAiData.pathToDestination = algorithm.PathFrom(unit.location, settlerAiData.destination); - log.Information("Set AI for unit to BUILD_CITY with destination of " + settlerAiData.destination); + settlerAiData.PathToDestination = algorithm.PathFrom(unit.location, settlerAiData.Destination); + log.Information("Set AI for unit to BUILD_CITY with destination of " + settlerAiData.Destination); } } unit.currentAIData = settlerAiData; } else if (unit.location.cityAtTile != null && unit.location.unitsOnTile.Count(u => u.unitType.defense > 0 && u != unit) == 0) { - DefenderAIData ai = new DefenderAIData(); - ai.goal = DefenderAIData.DefenderGoal.DEFEND_CITY; - ai.destination = unit.location; + DefenderAIData ai = new() { + goal = DefenderAIData.DefenderGoal.DEFEND_CITY, destination = unit.location + }; log.Information("Set defender AI for " + unit + " with destination of " + ai.destination); unit.currentAIData = ai; } else if (UnitCanAttackNearbyBarbCamp(unit, player)) { log.Information("Set unit " + unit + " to take out barb camp"); } else if (unit.unitType.name == "Catapult") { //For now tell catapults to sit tight. It's getting really annoying watching them pointlessly bombard barb camps forever - DefenderAIData ai = new DefenderAIData(); - ai.goal = DefenderAIData.DefenderGoal.DEFEND_CITY; - ai.destination = unit.location; + DefenderAIData ai = new() { + goal = DefenderAIData.DefenderGoal.DEFEND_CITY, destination = unit.location + }; log.Information("Set defender AI for " + unit + " with destination of " + ai.destination); unit.currentAIData = ai; } else { - if (unit.unitType.categories.Contains("Sea")) { - ExplorerAIData ai = new ExplorerAIData(); - ai.type = ExplorerAIData.ExplorationType.COASTLINE; + ExplorerAIData ai = new() { type = ExplorerAIData.ExplorationType.COASTLINE }; unit.currentAIData = ai; log.Information("Set coastline exploration AI for " + unit); } else if (unit.location.unitsOnTile.Exists((x) => x.unitType.categories.Contains("Sea"))) { - ExplorerAIData ai = new ExplorerAIData(); - ai.type = ExplorerAIData.ExplorationType.ON_A_BOAT; + ExplorerAIData ai = new() { type = ExplorerAIData.ExplorationType.ON_A_BOAT }; unit.currentAIData = ai; //TODO: Actually put the unit on the boat log.Information("Set ON_A_BOAT exploration AI for " + unit); @@ -117,19 +119,18 @@ public static void SetAIForUnit(MapUnit unit, Player player) { //Isn't a Settler. If there's a city at the location, it's defended. No boats involved. What's our priority? //If there is land to explore, we'll try to explore it. //Long-term TODO: Should only send tiles on this landmass. - KeyValuePair tileToExplore = ExplorerAI.FindTopScoringTileForExploration(player, player.tileKnowledge.AllKnownTiles().Where(t => t.IsLand()), ExplorerAIData.ExplorationType.RANDOM); + KeyValuePair tileToExplore = ExplorerAI.FindTopScoringTileForExploration(player, player.TileKnowledge.AllKnownTiles().Where(t => t.IsLand()), ExplorerAIData.ExplorationType.RANDOM); if (tileToExplore.Value > 0) { ExplorerAIData ai = new ExplorerAIData(); //What type of exploration should we do? int nearbyExplorers = 0; - foreach (MapUnit mapUnit in player.units) { - if (mapUnit.currentAIData is ExplorerAIData explorerAI) { - if (explorerAI.type == ExplorerAIData.ExplorationType.NEAR_CITIES) { - nearbyExplorers++; - } + foreach (MapUnit mapUnit in player.Units) { + if (mapUnit.currentAIData is ExplorerAIData { type: ExplorerAIData.ExplorationType.NEAR_CITIES }) { + nearbyExplorers++; } } - if (nearbyExplorers < (player.cities.Count + 1)) { + // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression + if (nearbyExplorers < (player.Cities.Count + 1)) { ai.type = ExplorerAIData.ExplorationType.NEAR_CITIES; } else { ai.type = ExplorerAIData.ExplorationType.RANDOM; @@ -148,20 +149,21 @@ public static void SetAIForUnit(MapUnit unit, Player player) { //resources. I expect we'll have some sort of arbiter that decides between competing priorities, with each being given a score as to how important //they are, including a weight by how far away the task is. But this will evolve gradually over a long time) + // ReSharper disable once GrammarMistakeInComment //As of today (4/7/2022), let's tackle just one of those - adequate defense of cities. The AI is really good at losing cities to barbs right now, //and that's a problem. City nearestCityToDefend = FindNearbyCityToDefend(unit, player); - DefenderAIData newUnitAIData = new DefenderAIData(); - newUnitAIData.destination = nearestCityToDefend.location; - newUnitAIData.goal = DefenderAIData.DefenderGoal.DEFEND_CITY; + DefenderAIData newUnitAiData = new() { + destination = nearestCityToDefend.location, goal = DefenderAIData.DefenderGoal.DEFEND_CITY + }; PathingAlgorithm algorithm = PathingAlgorithmChooser.GetAlgorithm(unit.IsLandUnit()); - newUnitAIData.pathToDestination = algorithm.PathFrom(unit.location, newUnitAIData.destination); + newUnitAiData.pathToDestination = algorithm.PathFrom(unit.location, newUnitAiData.destination); log.Information($"Unit {unit} tasked with defending {nearestCityToDefend.name}"); - unit.currentAIData = newUnitAIData; + unit.currentAIData = newUnitAiData; } } } @@ -172,7 +174,7 @@ private static bool UnitCanAttackNearbyBarbCamp(MapUnit unit, Player player) { return false; } - List reachableBarbCampsTiles = player.tileKnowledge.AllKnownTiles() + List reachableBarbCampsTiles = player.TileKnowledge.AllKnownTiles() .Where(t => unit.CanEnterTile(t, true) && t.hasBarbarianCamp).ToList(); Tile closestBarbCamp = Tile.NONE; @@ -186,11 +188,11 @@ private static bool UnitCanAttackNearbyBarbCamp(MapUnit unit, Player player) { } if (closestBarbDistance <= 3) { - CombatAIData caid = new CombatAIData(); + CombatAIData combatAiData = new CombatAIData(); PathingAlgorithm algorithm = PathingAlgorithmChooser.GetAlgorithm(unit.IsLandUnit()); - caid.path = algorithm.PathFrom(unit.location, closestBarbCamp); - unit.currentAIData = caid; + combatAiData.path = algorithm.PathFrom(unit.location, closestBarbCamp); + unit.currentAIData = combatAiData; return true; } return false; @@ -207,9 +209,9 @@ private static bool UnitCanAttackNearbyBarbCamp(MapUnit unit, Player player) { */ private static City FindNearbyCityToDefend(MapUnit unit, Player player) { int minDefenders = int.MaxValue; - //TODO: Just being there doesn't mean a unit is a defender. + // TODO: Just being there doesn't mean a unit is a defender. List citiesWithFewestDefenders = new List(); - foreach (City c in player.cities) { + foreach (City c in player.Cities) { if (c.location.unitsOnTile.Count < minDefenders) { minDefenders = c.location.unitsOnTile.Count; citiesWithFewestDefenders.Clear(); @@ -241,14 +243,14 @@ private static City FindNearbyCityToDefend(MapUnit unit, Player player) { * too, for example, and one AIData class might be able to call up multiple types of AIs. * It also likely will become mod-supporting someday, but we can't add everything on day one. **/ - public static UnitAI getAIForUnitStrategy(UnitAIData aiData) { - if (aiData is SettlerAIData sai) { + private static UnitAI GetAiForUnitStrategy(UnitAIData aiData) { + if (aiData is SettlerAiData) { return new SettlerAI(); - } else if (aiData is DefenderAIData dai) { + } else if (aiData is DefenderAIData) { return new DefenderAI(); - } else if (aiData is ExplorerAIData eai) { + } else if (aiData is ExplorerAIData) { return new ExplorerAI(); - } else if (aiData is CombatAIData cai) { + } else if (aiData is CombatAIData) { return new CombatAI(); } throw new Exception("AI data not recognized" + aiData); diff --git a/C7Engine/AI/StrategicAI/ExpansionPriority.cs b/C7Engine/AI/StrategicAI/ExpansionPriority.cs index 7741e19b..709ba28c 100644 --- a/C7Engine/AI/StrategicAI/ExpansionPriority.cs +++ b/C7Engine/AI/StrategicAI/ExpansionPriority.cs @@ -19,7 +19,7 @@ public ExpansionPriority() { } public override void CalculateWeightAndMetadata(Player player) { - if (player.cities.Count < 2) { + if (player.Cities.Count < 2) { this.calculatedWeight = 1000; } else { int score = UtilityCalculations.CalculateAvailableLandScore(player); diff --git a/C7Engine/AI/StrategicAI/UtilityCalculations.cs b/C7Engine/AI/StrategicAI/UtilityCalculations.cs index 781b317d..a3874b67 100644 --- a/C7Engine/AI/StrategicAI/UtilityCalculations.cs +++ b/C7Engine/AI/StrategicAI/UtilityCalculations.cs @@ -10,15 +10,15 @@ namespace C7Engine.AI.StrategicAI { /// public class UtilityCalculations { - private static readonly int POSSIBLE_CITY_LOCATION_SCORE = 2; //how much weight to give to each possible city location - private static readonly int TILE_SCORE_DIVIDER = 10; //how much to divide each location's tile score by + private static readonly int PossibleCityLocationScore = 2; //how much weight to give to each possible city location + private static readonly int TileScoreDivider = 10; //how much to divide each location's tile score by public static int CalculateAvailableLandScore(Player player) { //Figure out if there's land to settle, and how much - Dictionary possibleLocations = SettlerLocationAI.GetScoredSettlerCandidates(player.cities[0].location, player); - int score = possibleLocations.Count * POSSIBLE_CITY_LOCATION_SCORE; + Dictionary possibleLocations = SettlerLocationAi.GetScoredSettlerCandidates(player.Cities[0].location, player); + int score = possibleLocations.Count * PossibleCityLocationScore; foreach (int i in possibleLocations.Values) { - score += i / TILE_SCORE_DIVIDER; + score += i / TileScoreDivider; } return score; } diff --git a/C7Engine/AI/StrategicAI/WarPriority.cs b/C7Engine/AI/StrategicAI/WarPriority.cs index 6e5bc082..0011486f 100644 --- a/C7Engine/AI/StrategicAI/WarPriority.cs +++ b/C7Engine/AI/StrategicAI/WarPriority.cs @@ -25,7 +25,7 @@ public WarPriority() { /// /// public override void CalculateWeightAndMetadata(Player player) { - if (player.cities.Count < 2) { + if (player.Cities.Count < 2) { this.calculatedWeight = 0; } else { int landScore = UtilityCalculations.CalculateAvailableLandScore(player); @@ -42,7 +42,7 @@ public override void CalculateWeightAndMetadata(Player player) { int rnd = GameData.rng.Next(opponentCount); if (rnd == 0) { //Let's fight this nation! - properties["opponent"] = nation.id.ToString(); + properties["opponent"] = nation.Id.ToString(); calculatedWeight = TEMP_WAR_PRIORITY_WEIGHT; } else { opponentCount--; //guarantees we'll eventually get an opponent selected diff --git a/C7Engine/AI/UnitAI/ExplorerAI.cs b/C7Engine/AI/UnitAI/ExplorerAI.cs index bbfb8b0b..080b2b95 100644 --- a/C7Engine/AI/UnitAI/ExplorerAI.cs +++ b/C7Engine/AI/UnitAI/ExplorerAI.cs @@ -70,7 +70,7 @@ private static bool FindPathToNewExplorationArea(Player player, ExplorerAIData e Stopwatch watch = new Stopwatch(); watch.Start(); List validExplorerTiles = new List(); - foreach (Tile t in player.tileKnowledge.AllKnownTiles() + foreach (Tile t in player.TileKnowledge.AllKnownTiles() .Where(t => unit.CanEnterTile(t, false) && t.cityAtTile == null && numUnknownNeighboringTiles(player, t) > 0)) { validExplorerTiles.Add(t); } @@ -145,7 +145,7 @@ public static KeyValuePair FindTopScoringTileForExploration(Player explorationScore[t] = 0; } int distanceToNearestCity = int.MaxValue; - foreach (City c in player.cities) { + foreach (City c in player.Cities) { int distance = t.distanceTo(c.location); if (distance < distanceToNearestCity) { distanceToNearestCity = distance; @@ -169,11 +169,11 @@ private static int numUnknownNeighboringTiles(Player player, Tile t) { } //Calculate whether it, and its neighbors are in known tiles. int discoverableTiles = 0; - if (!player.tileKnowledge.isTileKnown(t)) { + if (!player.TileKnowledge.isTileKnown(t)) { discoverableTiles++; } foreach (Tile n in t.neighbors.Values) { - if (!player.tileKnowledge.isTileKnown(n)) { + if (!player.TileKnowledge.isTileKnown(n)) { discoverableTiles++; } } diff --git a/C7Engine/AI/UnitAI/SettlerAI.cs b/C7Engine/AI/UnitAI/SettlerAI.cs index 27933e6a..6047ce15 100644 --- a/C7Engine/AI/UnitAI/SettlerAI.cs +++ b/C7Engine/AI/UnitAI/SettlerAI.cs @@ -1,5 +1,4 @@ using System; -using C7Engine.Pathing; using C7GameData; using C7GameData.AIData; using Serilog; @@ -10,15 +9,15 @@ public class SettlerAI : UnitAI { private ILogger log = Log.ForContext(); public bool PlayTurn(Player player, MapUnit unit) { - SettlerAIData settlerAi = (SettlerAIData)unit.currentAIData; + SettlerAiData settlerAi = (SettlerAiData)unit.currentAIData; start: - switch (settlerAi.goal) { - case SettlerAIData.SettlerGoal.BUILD_CITY: - if (IsInvalidCityLocation(settlerAi.destination)) { - log.Information("Seeking new destination for settler " + unit.id + "headed to " + settlerAi.destination); - PlayerAI.SetAIForUnit(unit, player); + switch (settlerAi.Goal) { + case SettlerAiData.SettlerGoal.BuildCity: + if (IsInvalidCityLocation(settlerAi.Destination)) { + log.Information("Seeking new destination for settler " + unit.id + "headed to " + settlerAi.Destination); + PlayerAi.SetAiForUnit(unit, player); //Make sure we're using the new settler AI going forward, including this turn - settlerAi = (SettlerAIData)unit.currentAIData; + settlerAi = (SettlerAiData)unit.currentAIData; //Re-process since the unit's goal may have changed. //TODO: In theory in the future, it might even have a non-settler AI. Maybe we should instead return false, //and have the PlayerAI re-kick the unit based on a possibly different AI class? @@ -26,20 +25,20 @@ public bool PlayTurn(Player player, MapUnit unit) { //very well become a Defender or Attacker if there's no exploration left, for example. goto start; } - if (unit.location == settlerAi.destination) { + if (unit.location == settlerAi.Destination) { log.Information("Building city with " + unit); //TODO: This should use a message, and the message handler should cause the disbanding to happen. - CityInteractions.BuildCity(unit.location.xCoordinate, unit.location.yCoordinate, player.id, unit.owner.GetNextCityName()); + CityInteractions.BuildCity(unit.location.xCoordinate, unit.location.yCoordinate, player.Id, unit.owner.GetNextCityName()); unit.disband(); } else { //If the settler has no destination, then disband rather than crash later. - if (settlerAi.destination == Tile.NONE) { + if (settlerAi.Destination == Tile.NONE) { log.Information("Disbanding settler " + unit.id + " with no valid destination"); unit.disband(); return false; } try { - Tile nextTile = settlerAi.pathToDestination.Next(); + Tile nextTile = settlerAi.PathToDestination.Next(); unit.move(unit.location.directionTo(nextTile)); } catch (Exception ex) { //This occurs when on the previous turn, a settler tries to move to the next location on its path, but cannot, due to another @@ -50,7 +49,7 @@ public bool PlayTurn(Player player, MapUnit unit) { } } break; - case SettlerAIData.SettlerGoal.JOIN_CITY: + case SettlerAiData.SettlerGoal.JoinCity: if (unit.location.cityAtTile != null) { //TODO: Actually join the city. Haven't added that action. //For now, just get rid of the unit. Sorry, bro. @@ -62,7 +61,7 @@ public bool PlayTurn(Player player, MapUnit unit) { } break; default: - log.Warning("Unknown strategy of " + settlerAi.goal + " for unit"); + log.Warning("Unknown strategy of " + settlerAi.Goal + " for unit"); break; } return true; diff --git a/C7Engine/AI/UnitAI/SettlerLocationAI.cs b/C7Engine/AI/UnitAI/SettlerLocationAI.cs index 43e05848..66857e1a 100644 --- a/C7Engine/AI/UnitAI/SettlerLocationAI.cs +++ b/C7Engine/AI/UnitAI/SettlerLocationAI.cs @@ -1,54 +1,46 @@ -using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using C7GameData; using System.Linq; using C7GameData.AIData; using Serilog; +// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator +// ReSharper disable CheckNamespace namespace C7Engine { - public class SettlerLocationAI { - private static ILogger log = Log.ForContext(); - - //Eventually, there should be different weights based on whether the AI already - //has the resource or not (more important to secure ones that they don't have). - //But since we don't have trade networks yet, for now there's only one value. - static int STRATEGIC_RESOURCE_BONUS = 20; - static int LUXURY_RESOURCE_BONUS = 15; + public class SettlerLocationAi { + private static readonly ILogger Log = Serilog.Log.ForContext(); //Figures out where to plant Settlers - public static Tile findSettlerLocation(Tile start, Player player) { - Dictionary scores = GetScoredSettlerCandidates(start, player); + public static Tile FindSettlerLocation(Tile start, Player player) { + Dictionary scores = GetScoredSettlerCandidates(start, player); if (scores.Count == 0 || scores.Values.Max() <= 0) { return Tile.NONE; //nowhere to settle } - IOrderedEnumerable > orderedScores = scores.OrderByDescending(t => t.Value); - log.Debug("Top city location candidates from " + start + ":"); + IOrderedEnumerable > orderedScores = scores.OrderByDescending(t => t.Value); + Log.Debug("Top city location candidates from " + start + ":"); Tile returnValue = null; - foreach (KeyValuePair kvp in orderedScores.Take(5)) { - if (returnValue == null) { - returnValue = kvp.Key; - } + foreach (KeyValuePair kvp in orderedScores.Take(5)) { + returnValue ??= kvp.Key; if (kvp.Value > 0) { - log.Debug(" Tile " + kvp.Key + " scored " + kvp.Value); + Log.Debug(" Tile " + kvp.Key + " scored " + kvp.Value); } } return returnValue; } - public static Dictionary GetScoredSettlerCandidates(Tile start, Player player) { - List playerUnits = player.units; - IEnumerable candidates = player.tileKnowledge.AllKnownTiles().Where(t => !IsInvalidCityLocation(t)); - Dictionary scores = AssignTileScores(start, player, candidates, playerUnits.FindAll(u => u.unitType.name == "Settler")); + public static Dictionary GetScoredSettlerCandidates(Tile start, Player player) { + List playerUnits = player.Units; + IEnumerable candidates = player.TileKnowledge.AllKnownTiles().Where(t => !IsInvalidCityLocation(t)); + Dictionary scores = AssignTileScores(start, player, candidates, playerUnits.FindAll(u => u.unitType.name == "Settler")); return scores; } - private static Dictionary AssignTileScores(Tile startTile, Player player, IEnumerable candidates, List playerSettlers) { - Dictionary scores = new Dictionary(); + private static Dictionary AssignTileScores(Tile startTile, Player player, IEnumerable candidates, List playerSettlers) { + Dictionary scores = new(); candidates = candidates.Where(t => !SettlerAlreadyMovingTowardsTile(t, playerSettlers) && t.IsAllowCities()); foreach (Tile t in candidates) { - int score = GetTileYieldScore(t, player); + float score = GetTileYieldScore(t, player); //For simplicity's sake, I'm only going to look at immediate neighbors here, but //a lot more things should be considered over time. foreach (Tile nt in t.neighbors.Values) { @@ -58,17 +50,17 @@ private static Dictionary AssignTileScores(Tile startTile, Player pla //Prefer hills for defense, and coast for boats and such. if (t.baseTerrainType.Key == "hills") { - score += 10; + score += player.Civilization.Adjustments.HillsBonus; } if (t.NeighborsWater()) { - score += 10; + score += player.Civilization.Adjustments.WaterBonus; } //Lower scores if they are far away - int preDistanceScore = score; + float preDistanceScore = score; int distance = startTile.distanceTo(t); - if (distance > 4) { - score -= distance * 2; + if (distance > player.Civilization.Adjustments.DistancePenaltyRadius) { + score += player.Civilization.Adjustments.DistancePenalty(distance); } //Distance can never lower score beyond 1; the AI will always try to settle those worthless tundras. //(This could actually be modified in the future, but for now is also a safety rail) @@ -80,37 +72,42 @@ private static Dictionary AssignTileScores(Tile startTile, Player pla } return scores; } - private static int GetTileYieldScore(Tile t, Player owner) { - int score = t.foodYield(owner) * 5; - score += t.productionYield(owner) * 3; - score += t.commerceYield(owner) * 2; - if (t.Resource.Category == ResourceCategory.STRATEGIC) { - score += STRATEGIC_RESOURCE_BONUS; - } else if (t.Resource.Category == ResourceCategory.LUXURY) { - score += LUXURY_RESOURCE_BONUS; + private static float GetTileYieldScore(Tile t, Player owner) { + float score = owner.Civilization.Adjustments.FoodYieldBonus(t.foodYield(owner)); + score += owner.Civilization.Adjustments.ProductionYieldBonus(t.productionYield(owner)); + score += owner.Civilization.Adjustments.CommerceYieldBonus(t.commerceYield(owner)); + switch (t.Resource.Category) + { + case ResourceCategory.STRATEGIC: + // TODO: increase bonus if this civ doesn't have this strategic resource yet + score += owner.Civilization.Adjustments.StrategicResourceBonus; + break; + case ResourceCategory.LUXURY: + score += owner.Civilization.Adjustments.LuxuryResourceBonus; + break; } return score; } - public static bool IsInvalidCityLocation(Tile tile) { + private static bool IsInvalidCityLocation(Tile tile) { if (tile.HasCity) { - log.Verbose("Tile " + tile + " is invalid due to existing city of " + tile.cityAtTile.name); + Log.Verbose("Tile " + tile + " is invalid due to existing city of " + tile.cityAtTile.name); return true; } foreach (Tile neighbor in tile.neighbors.Values) { if (neighbor.HasCity) { - log.Verbose("Tile " + tile + " is invalid due to neighboring city of " + neighbor.cityAtTile.name); + Log.Verbose("Tile " + tile + " is invalid due to neighboring city of " + neighbor.cityAtTile.name); return true; } foreach (Tile neighborOfNeighbor in neighbor.neighbors.Values) { if (neighborOfNeighbor.HasCity) { - log.Verbose("Tile " + tile + " is invalid due to nearby city of " + neighborOfNeighbor.cityAtTile.name); + Log.Verbose("Tile " + tile + " is invalid due to nearby city of " + neighborOfNeighbor.cityAtTile.name); return true; } } } - log.Debug("Tile " + tile + " is a valid city location "); + Log.Debug("Tile " + tile + " is a valid city location "); return false; } @@ -123,16 +120,17 @@ public static bool IsInvalidCityLocation(Tile tile) { /// The tile under consideration for a future city. /// The settlers owned by the AI considering building a city. /// - public static bool SettlerAlreadyMovingTowardsTile(Tile tile, List playerSettlers) { + private static bool SettlerAlreadyMovingTowardsTile(Tile tile, List playerSettlers) { foreach (MapUnit otherSettler in playerSettlers) { - if (otherSettler.currentAIData is SettlerAIData otherSettlerAI) { - if (otherSettlerAI.destination == tile) { + // ReSharper disable once InvertIf + if (otherSettler.currentAIData is SettlerAiData otherSettlerAi) { + if (otherSettlerAi.Destination == tile) { return true; } - if (otherSettlerAI.destination.GetLandNeighbors().Exists(innerRingTile => innerRingTile == tile)) { + if (otherSettlerAi.Destination.GetLandNeighbors().Exists(innerRingTile => innerRingTile == tile)) { return true; } - foreach (Tile innerRingTile in otherSettlerAI.destination.GetLandNeighbors()) { + foreach (Tile innerRingTile in otherSettlerAi.Destination.GetLandNeighbors()) { if (innerRingTile.GetLandNeighbors().Exists(outerRingTile => outerRingTile == tile)) { return true; } diff --git a/C7Engine/EntryPoints/CityInteractions.cs b/C7Engine/EntryPoints/CityInteractions.cs index d188f064..3fe63917 100644 --- a/C7Engine/EntryPoints/CityInteractions.cs +++ b/C7Engine/EntryPoints/CityInteractions.cs @@ -13,11 +13,11 @@ public static void BuildCity(int x, int y, ID playerID, string name) { CityResident firstResident = new CityResident(); CityTileAssignmentAI.AssignNewCitizenToTile(newCity, firstResident); newCity.SetItemBeingProduced(CityProductionAI.GetNextItemToBeProduced(newCity, null)); - if (owner.cities.Count == 0) { + if (owner.Cities.Count == 0) { newCity.capital = true; } gameData.cities.Add(newCity); - owner.cities.Add(newCity); + owner.Cities.Add(newCity); tileWithNewCity.cityAtTile = newCity; // Cities are treated as though they have a road, but if @@ -32,7 +32,7 @@ public static void DestroyCity(int x, int y) { Tile tile = EngineStorage.gameData.map.tileAt(x, y); tile.DisbandNonDefendingUnits(); tile.cityAtTile.RemoveAllCitizens(); - tile.cityAtTile.owner.cities.Remove(tile.cityAtTile); + tile.cityAtTile.owner.Cities.Remove(tile.cityAtTile); EngineStorage.gameData.cities.Remove(tile.cityAtTile); new MsgCityDestroyed(tile.cityAtTile).send(); tile.cityAtTile = null; diff --git a/C7Engine/EntryPoints/CreateGame.cs b/C7Engine/EntryPoints/CreateGame.cs index dcb80abb..62738dfa 100644 --- a/C7Engine/EntryPoints/CreateGame.cs +++ b/C7Engine/EntryPoints/CreateGame.cs @@ -28,12 +28,12 @@ public static Player createGame(string loadFilePath, string defaultBicPath, Func // members containing numerous references to eachother, but with SaveGame, each entity is // only defined once in the save file, and references to it are stored as IDs making it easy // to generate and modify valid save files. - Player humanPlayer = gameData.players.Any(p => p.isHuman) switch { - true => gameData.players.Find(p => p.isHuman), + Player humanPlayer = gameData.players.Any(p => p.IsHuman) switch { + true => gameData.players.Find(p => p.IsHuman), false => throw new Exception($"{loadFilePath} does not contain a human player"), }; - EngineStorage.uiControllerID = humanPlayer.id; + EngineStorage.uiControllerID = humanPlayer.Id; TurnHandling.OnBeginTurn(); // Call for the first turn TurnHandling.AdvanceTurn(); diff --git a/C7Engine/EntryPoints/MessageToEngine.cs b/C7Engine/EntryPoints/MessageToEngine.cs index 82c5b022..16830004 100644 --- a/C7Engine/EntryPoints/MessageToEngine.cs +++ b/C7Engine/EntryPoints/MessageToEngine.cs @@ -134,7 +134,7 @@ public MsgChooseResearch(ID techId) { public override void process() { Player player = EngineStorage.gameData.GetHumanPlayers()[0]; - if (player.currentlyResearchedTech == techId) { + if (player.CurrentlyResearchedTech == techId) { return; } Tech requestedTech = EngineStorage.gameData.techs.Find(t => t.id == techId); @@ -143,13 +143,13 @@ public override void process() { // // TODO: do a topological sort to allow a queue of techs to study. foreach (Tech prereq in requestedTech.Prerequisites) { - if (!player.knownTechs.Contains(prereq.id)) { + if (!player.KnownTechs.Contains(prereq.id)) { return; } } // Start researching this tech and update the UI. - player.currentlyResearchedTech = requestedTech.id; + player.CurrentlyResearchedTech = requestedTech.id; new MsgUpdateUiAfterTechSelection().send(); } } @@ -161,14 +161,14 @@ public class MsgEndTurn : MessageToEngine { public override void process() { Player controller = EngineStorage.gameData.GetPlayer(EngineStorage.uiControllerID); - foreach (MapUnit unit in controller.units) { + foreach (MapUnit unit in controller.Units) { log.Debug($"{unit}, path length: {unit.path?.PathLength() ?? 0}"); if (unit.path?.PathLength() > 0) { unit.moveAlongPath(); } } - controller.hasPlayedThisTurn = true; + controller.HasPlayedThisTurn = true; TurnHandling.AdvanceTurn(); } } diff --git a/C7Engine/EntryPoints/TurnHandling.cs b/C7Engine/EntryPoints/TurnHandling.cs index e76b2986..7e500ee2 100644 --- a/C7Engine/EntryPoints/TurnHandling.cs +++ b/C7Engine/EntryPoints/TurnHandling.cs @@ -16,7 +16,7 @@ internal static void OnBeginTurn() { mapUnit.OnBeginTurn(); foreach (Player player in gameData.players) { - player.hasPlayedThisTurn = false; + player.HasPlayedThisTurn = false; } } @@ -56,22 +56,22 @@ internal static void AdvanceTurn() { /// true when it is time for the human to take control again private static bool PlayPlayerTurns(GameData gameData, bool firstTurn) { foreach (Player player in gameData.players) { - if ((!player.hasPlayedThisTurn) && + if ((!player.HasPlayedThisTurn) && !(firstTurn && player.SitsOutFirstTurn())) { - if (player.isBarbarians) { + if (player.IsBarbarians) { //Call the barbarian AI //TODO: The AIs should be stored somewhere on the game state as some of them will store state (plans, //strategy, etc.) For now, we only have a random AI, so that will be in a future commit new BarbarianAI().PlayTurn(player, gameData); - player.hasPlayedThisTurn = true; - } else if (!player.isHuman) { - PlayerAI.PlayTurn(player, GameData.rng); - player.hasPlayedThisTurn = true; - } else if (player.id != EngineStorage.uiControllerID) { - player.hasPlayedThisTurn = true; + player.HasPlayedThisTurn = true; + } else if (!player.IsHuman) { + PlayerAi.PlayTurn(player, GameData.rng); + player.HasPlayedThisTurn = true; + } else if (player.Id != EngineStorage.uiControllerID) { + player.HasPlayedThisTurn = true; } //Human player check. Let the human see what's going on even if they are in observer mode. - if (player.id == EngineStorage.uiControllerID) { + if (player.Id == EngineStorage.uiControllerID) { new MsgStartTurn().send(); return true; } @@ -81,7 +81,7 @@ private static bool PlayPlayerTurns(GameData gameData, bool firstTurn) { } private static void SpawnBarbarians(GameData gameData) { //Generate new barbarian units. - Player barbPlayer = gameData.players.Find(player => player.isBarbarians); + Player barbPlayer = gameData.players.Find(player => player.IsBarbarians); foreach (Tile tile in gameData.map.barbarianCamps) { //7% chance of a new barbarian. Probably should scale based on barbarian activity. int result = GameData.rng.Next(100); @@ -98,7 +98,7 @@ private static void SpawnBarbarians(GameData gameData) { tile.unitsOnTile.Add(newUnit); gameData.mapUnits.Add(newUnit); - barbPlayer.units.Add(newUnit); + barbPlayer.Units.Add(newUnit); log.Debug("New barbarian added at " + tile); } else if (tile.NeighborsWater() && result < 6) { MapUnit newUnit = new MapUnit(gameData.ids.CreateID(gameData.barbarianInfo.barbarianSeaUnit.name)); @@ -112,7 +112,7 @@ private static void SpawnBarbarians(GameData gameData) { tile.unitsOnTile.Add(newUnit); gameData.mapUnits.Add(newUnit); - barbPlayer.units.Add(newUnit); + barbPlayer.Units.Add(newUnit); log.Debug("New barbarian galley added at " + tile); } } @@ -127,7 +127,7 @@ private static void HandleCityResults(GameData gameData) { int newSize = city.size; if (newSize > initialSize) { CityResident newResident = new CityResident(); - newResident.nationality = city.owner.civilization; + newResident.nationality = city.owner.Civilization; CityTileAssignmentAI.AssignNewCitizenToTile(city, newResident); } else if (newSize < initialSize) { int diff = initialSize - newSize; @@ -160,7 +160,7 @@ private static void HandleCityResults(GameData gameData) { city.SetItemBeingProduced(CityProductionAI.GetNextItemToBeProduced(city, producedItem)); } - city.owner.gold += city.CurrentCommerceYield(); + city.owner.Gold += city.CurrentCommerceYield(); } } diff --git a/C7Engine/EntryPoints/UnitInteractions.cs b/C7Engine/EntryPoints/UnitInteractions.cs index 40fa0df1..6ff78853 100644 --- a/C7Engine/EntryPoints/UnitInteractions.cs +++ b/C7Engine/EntryPoints/UnitInteractions.cs @@ -11,11 +11,11 @@ public class UnitInteractions { private static ILogger log = Log.ForContext(); public static MapUnit getNextSelectedUnit(GameData gameData) { - foreach (Player player in gameData.players.Where(p => p.isHuman)) { + foreach (Player player in gameData.players.Where(p => p.IsHuman)) { //TODO: Should pass in a player GUID instead of checking for human //This current limits us to one human player, although it's better //than the old limit of one non-barbarian player. - foreach (MapUnit unit in player.units.Where(u => u.movementPoints.canMove && !u.IsBusy())) { + foreach (MapUnit unit in player.Units.Where(u => u.movementPoints.canMove && !u.IsBusy())) { if (!waitQueue.Contains(unit)) { return unit; } diff --git a/C7Engine/MapUnitExtensions.cs b/C7Engine/MapUnitExtensions.cs index d9f2e106..454fb6cf 100644 --- a/C7Engine/MapUnitExtensions.cs +++ b/C7Engine/MapUnitExtensions.cs @@ -94,7 +94,7 @@ public static bool HasPriorityAsDefender(this MapUnit unit, MapUnit otherDefende public static void RollToPromote(this MapUnit unit, MapUnit opponent, bool waitForAnimation) { double promotionChance = unit.experienceLevel.promotionChance; - if (opponent.owner.isBarbarians) + if (opponent.owner.IsBarbarians) promotionChance /= 2.0; // TODO: Double promotionChance if unit is owned by a militaristic civ @@ -274,10 +274,10 @@ public static void OnBeginTurn(this MapUnit unit) { public static void OnEnterTile(this MapUnit unit, Tile tile) { //Add to player knowledge of tiles - unit.owner.tileKnowledge.AddTilesToKnown(tile); + unit.owner.TileKnowledge.AddTilesToKnown(tile); // Disperse barb camp - if (tile.hasBarbarianCamp && (!unit.owner.isBarbarians)) { + if (tile.hasBarbarianCamp && (!unit.owner.IsBarbarians)) { tile.DisbandNonDefendingUnits(); EngineStorage.gameData.map.barbarianCamps.Remove(tile); tile.hasBarbarianCamp = false; @@ -404,8 +404,8 @@ public static void disband(this MapUnit unit) { unit.location.unitsOnTile.Remove(unit); gameData.mapUnits.Remove(unit); foreach (Player player in gameData.players) { - if (player.units.Contains(unit)) { - player.units.Remove(unit); + if (player.Units.Contains(unit)) { + player.Units.Remove(unit); } } } @@ -430,7 +430,7 @@ public static void buildCity(this MapUnit unit, string cityName) { // TODO: Need to check somewhere that this unit is allowed to build a city on its current tile. Either do that here or in every caller // (probably best to just do it here). - CityInteractions.BuildCity(unit.location.xCoordinate, unit.location.yCoordinate, unit.owner.id, cityName); + CityInteractions.BuildCity(unit.location.xCoordinate, unit.location.yCoordinate, unit.owner.Id, cityName); // TODO: Should directly delete the unit instead of disbanding it. Disbanding in a city will eventually award shields, which we // obviously don't want to do here. diff --git a/C7GameData/AIData/SettlerAIData.cs b/C7GameData/AIData/SettlerAIData.cs index 98cc7cae..fc247c25 100644 --- a/C7GameData/AIData/SettlerAIData.cs +++ b/C7GameData/AIData/SettlerAIData.cs @@ -16,20 +16,20 @@ namespace C7GameData.AIData * I'm also unsure of how much the logic of figuring out what to do should be in these * classes, versus higher-level ones. */ - public class SettlerAIData : UnitAIData + public class SettlerAiData : UnitAIData { public enum SettlerGoal { - BUILD_CITY, - JOIN_CITY + BuildCity, + JoinCity } - public SettlerGoal goal; - public Tile destination; - public TilePath pathToDestination; + public SettlerGoal Goal; + public Tile Destination; + public TilePath PathToDestination; public override string ToString() { - return goal + " at " + destination; + return Goal + " at " + Destination; } } } diff --git a/C7GameData/Civilization.cs b/C7GameData/Civilization.cs index 67b17db0..98cc3df5 100644 --- a/C7GameData/Civilization.cs +++ b/C7GameData/Civilization.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using System.Text.Json.Serialization; namespace C7GameData { /** @@ -12,21 +12,47 @@ public enum Gender { } public class Civilization { - public Civilization() { } - + public Civilization() {} + // ReSharper disable once UnusedMember.Global public Civilization(string name) { - this.name = name; + Name = name; } - public string name; - + public string Name; // `noun` is "Americans" for "America", or "Spanish" for "Spain", etc. - public string noun; - public string leader; - public int colorIndex; - public Gender leaderGender; - public List cityNames = new List(); - + public string Noun; + // ReSharper disable once NotAccessedField.Global + public string Leader; + public int ColorIndex; + // ReSharper disable once NotAccessedField.Global + public Gender LeaderGender; + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public List CityNames = new(); // The IDs of all the techs that this civ starts with. - public HashSet startingTechs = new(); + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public HashSet StartingTechs = new(); + + public class SettlerTileAdjustments { + private const int DefaultDistancePenaltyRadius = 4; + private const float DefaultCommerceYieldBonus = 2; + private const float DefaultDistancePenalty = -2; + private const float DefaultFoodYieldBonus = 5; + private const float DefaultHillsBonus = 10; + private const float DefaultLuxuryResourceBonus = 15; + private const float DefaultProductionYieldBonus = 3; + private const float DefaultStrategicResourceBonus = 20; + private const float DefaultWaterBonus = 10; + + public int DistancePenaltyRadius = DefaultDistancePenaltyRadius; + public Func CommerceYieldBonus = yield => yield * DefaultCommerceYieldBonus; + public Func DistancePenalty = distance => distance * DefaultDistancePenalty; + public Func FoodYieldBonus = yield => yield * DefaultFoodYieldBonus; + public float HillsBonus = DefaultHillsBonus; + public float LuxuryResourceBonus = DefaultLuxuryResourceBonus; + public Func ProductionYieldBonus = yield => yield * DefaultProductionYieldBonus; + public float StrategicResourceBonus = DefaultStrategicResourceBonus; + public float WaterBonus = DefaultWaterBonus; + } + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public SettlerTileAdjustments Adjustments = new(); } } diff --git a/C7GameData/GameData.cs b/C7GameData/GameData.cs index 35592bcc..63e1f211 100644 --- a/C7GameData/GameData.cs +++ b/C7GameData/GameData.cs @@ -54,7 +54,7 @@ public GameData() { } public List GetHumanPlayers() { - return players.FindAll(p => p.isHuman); + return players.FindAll(p => p.IsHuman); } public MapUnit GetUnit(ID id) { @@ -62,7 +62,7 @@ public MapUnit GetUnit(ID id) { } public Player GetPlayer(ID id) { - return players.Find(p => p.id == id); + return players.Find(p => p.Id == id); } public ExperienceLevel GetExperienceLevelAfter(ExperienceLevel experienceLevel) { diff --git a/C7GameData/ImportCiv3.cs b/C7GameData/ImportCiv3.cs index 8750e9ee..758645ab 100644 --- a/C7GameData/ImportCiv3.cs +++ b/C7GameData/ImportCiv3.cs @@ -334,14 +334,14 @@ private void ImportRaces() { int i = 0; foreach (RACE race in theBiq.Race) { Civilization civ = new Civilization{ - name = race.Name, - noun = race.Noun, - leader = race.LeaderName, - leaderGender = race.LeaderGender == 0 ? Gender.Male : Gender.Female, - colorIndex = race.DefaultColor, + Name = race.Name, + Noun = race.Noun, + Leader = race.LeaderName, + LeaderGender = race.LeaderGender == 0 ? Gender.Male : Gender.Female, + ColorIndex = race.DefaultColor, }; foreach (RACE_City city in theBiq.RaceCityName[i]) { - civ.cityNames.Add(city.Name); + civ.CityNames.Add(city.Name); } save.Civilizations.Add(civ); i++; @@ -425,10 +425,10 @@ private void ImportSavLeaders() { private SavePlayer MakeSavePlayerFromCiv(Civilization civ, bool isBarbarian, bool isHuman, int cityNameIndex, string era) { return new SavePlayer { id = ids.CreateID("player"), - colorIndex = civ.colorIndex, + colorIndex = civ.ColorIndex, barbarian = isBarbarian, human = isHuman, - civilization = civ.name, + civilization = civ.Name, hasPlayedCurrentTurn = false, // TODO: find how this information is stored in a .sav cityNameIndex = cityNameIndex, eraCivilopediaName = era, @@ -715,25 +715,25 @@ private void ImportTechs() { RACE race = theBiq.Race[i]; if (race.FreeTech1 > -1) { - sc.startingTechs.Add(save.Techs[race.FreeTech1].id); + sc.StartingTechs.Add(save.Techs[race.FreeTech1].id); } if (race.FreeTech2 > -1) { - sc.startingTechs.Add(save.Techs[race.FreeTech2].id); + sc.StartingTechs.Add(save.Techs[race.FreeTech2].id); } if (race.FreeTech3 > -1) { - sc.startingTechs.Add(save.Techs[race.FreeTech3].id); + sc.StartingTechs.Add(save.Techs[race.FreeTech3].id); } if (race.FreeTech4 > -1) { - sc.startingTechs.Add(save.Techs[race.FreeTech4].id); + sc.StartingTechs.Add(save.Techs[race.FreeTech4].id); } // Remove any invalid starting techs. Some scenarios like // Fall of Rome give starting techs without giving all of the // prereqs, so they should be ignored. - sc.startingTechs.RemoveWhere(t => { + sc.StartingTechs.RemoveWhere(t => { SaveTech st = save.Techs.Find(x => x.id == t); foreach (ID prereqId in st.Prerequisites) { - if (!sc.startingTechs.Contains(prereqId)) { + if (!sc.StartingTechs.Contains(prereqId)) { return true; } } diff --git a/C7GameData/Player.cs b/C7GameData/Player.cs index 4430b73c..92494cd5 100644 --- a/C7GameData/Player.cs +++ b/C7GameData/Player.cs @@ -1,52 +1,55 @@ using System.Collections.Generic; using System.Linq; using C7Engine.AI.StrategicAI; +// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator namespace C7GameData { public class Player { - public ID id { get; internal set; } - public int colorIndex; - public bool isBarbarians = false; - //TODO: Refactor front-end so it sends player GUID with requests. - //We should allow multiple humans, this is a temporary measure. - public bool isHuman = false; - public bool hasPlayedThisTurn = false; - - public Civilization civilization; - internal int cityNameIndex = 0; - - public List units = new List(); - public List cities = new List(); - public TileKnowledge tileKnowledge = new TileKnowledge(); + // ReSharper disable once PropertyCanBeMadeInitOnly.Global + public ID Id { get; internal set; } + public int ColorIndex; + public bool IsBarbarians = false; + // TODO: Refactor front-end so it sends player GUID with requests. + // We should allow multiple humans, this is a temporary measure. + public bool IsHuman = false; + public bool HasPlayedThisTurn = false; + + public Civilization Civilization; + internal int CityNameIndex; + + public List Units = new(); + public List Cities = new(); + public TileKnowledge TileKnowledge = new(); //Ordered list of priority data. First is most important. - public List strategicPriorityData = new List(); + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public List StrategicPriorityData = new(); // The list of techs known by this player. - public HashSet knownTechs = new(); + public HashSet KnownTechs = new(); // The tech the player is currently researching. - public ID currentlyResearchedTech; + public ID CurrentlyResearchedTech; // The civilopedia name of the era this player is in. // // The civilopedia name is what is used for art lookups, not the actual // name. - public string eraCivilopediaName; + public string EraCivilopediaName; - public int turnsUntilPriorityReevaluation = 0; + public int TurnsUntilPriorityReevaluation = 0; // The amount of gold this player has. - public int gold = 0; + public int Gold = 0; public void AddUnit(MapUnit unit) { - this.units.Add(unit); + Units.Add(unit); } public string GetNextCityName() { - string name = civilization.cityNames[cityNameIndex % civilization.cityNames.Count]; - int bonusLoops = cityNameIndex / civilization.cityNames.Count; + string name = Civilization.CityNames[CityNameIndex % Civilization.CityNames.Count]; + int bonusLoops = CityNameIndex / Civilization.CityNames.Count; if (bonusLoops % 2 == 1) { name = "New " + name; } @@ -54,20 +57,18 @@ public string GetNextCityName() { if (suffix > 1) { name = name + " " + suffix; //e.g. for bonusLoops = 2, we'll have "Athens 2" } - cityNameIndex++; + CityNameIndex++; return name; } - public Player() { } - public bool IsAtPeaceWith(Player other) { - // Right now it's a free-for-all but eventually we'll implement peace treaties and alliances + // Right now it's a free-for-all, but eventually we'll implement peace treaties and alliances return other == this; } public bool SitsOutFirstTurn() { // TODO: Scenarios can also specify that certain players sit out the first turn. E.g. WW2 in the Pacific - return isBarbarians; + return IsBarbarians; } // Once we have technologies, not all resources will be known at the start. @@ -80,7 +81,7 @@ public bool KnowsAboutResource(Resource resource) { public int RemainingCities() { int result = 0; - foreach (City city in cities) { + foreach (City city in Cities) { // Destroyed cities have a size of zero. if (city.size > 0) { ++result; @@ -90,9 +91,7 @@ public int RemainingCities() { } public override string ToString() { - if (civilization != null) - return civilization.cityNames.First(); - return ""; + return Civilization != null ? Civilization.CityNames.First() : ""; } } diff --git a/C7GameData/Save/SaveCity.cs b/C7GameData/Save/SaveCity.cs index 571bfdf6..bea6af39 100644 --- a/C7GameData/Save/SaveCity.cs +++ b/C7GameData/Save/SaveCity.cs @@ -23,7 +23,7 @@ public SaveCity() { } public SaveCity(City city) { id = city.id; - owner = city.owner.id; + owner = city.owner.Id; capital = city.capital; location = new TileLocation(city.location); name = city.name; @@ -34,7 +34,7 @@ public SaveCity(City city) { foodNeededToGrow = city.foodNeededToGrow; residents = city.residents.ConvertAll(resident => { return new SaveCityResident { - nationality = resident.nationality?.name, + nationality = resident.nationality?.Name, tileWorked = new TileLocation(resident.tileWorked), }; }); @@ -44,7 +44,7 @@ public City ToCity(GameMap gameMap, List players, List un City city = new City{ id = id, location = gameMap.tileAt(location.x, location.y), - owner = players.Find(p => p.id == owner), + owner = players.Find(p => p.Id == owner), name = name, size = size, itemBeingProduced = unitPrototypes.Find(proto => proto.name == producible), @@ -55,7 +55,7 @@ public City ToCity(GameMap gameMap, List players, List un residents = residents.ConvertAll(resident =>{ return new CityResident{ tileWorked = gameMap.tileAt(resident.tileWorked.x, resident.tileWorked.y), - nationality = civilizations.Find(civ => civ.name == resident.nationality), + nationality = civilizations.Find(civ => civ.Name == resident.nationality), }; }), }; diff --git a/C7GameData/Save/SaveGame.cs b/C7GameData/Save/SaveGame.cs index a4fbdbad..30a8b30a 100644 --- a/C7GameData/Save/SaveGame.cs +++ b/C7GameData/Save/SaveGame.cs @@ -106,7 +106,7 @@ public GameData ToGameData() { // once unit owners are known, players can reference units data.players.ForEach(player => { - player.units = data.mapUnits.Where(unit => unit.owner.id == player.id).ToList(); ; + player.Units = data.mapUnits.Where(unit => unit.owner.Id == player.Id).ToList(); ; }); // cities require game map for location and players for city owner @@ -114,7 +114,7 @@ public GameData ToGameData() { // Once cities are known, players can reference cities. data.players.ForEach(player => { - player.cities = data.cities.Where(city => city.owner.id == player.id).ToList(); ; + player.Cities = data.cities.Where(city => city.owner.Id == player.Id).ToList(); ; }); foreach (City city in data.cities) { diff --git a/C7GameData/Save/SavePlayer.cs b/C7GameData/Save/SavePlayer.cs index 771ad8c9..54e45989 100644 --- a/C7GameData/Save/SavePlayer.cs +++ b/C7GameData/Save/SavePlayer.cs @@ -33,25 +33,25 @@ public class SavePlayer { public Player ToPlayer(GameMap map, List civilizations) { Player player = new Player{ - id = id, - isBarbarians = barbarian, - isHuman = human, - hasPlayedThisTurn = hasPlayedCurrentTurn, - colorIndex = colorIndex, - civilization = civilization is not null ? civilizations.Find(civ => civ.name == civilization) : null, - cityNameIndex = cityNameIndex, - tileKnowledge = new TileKnowledge(), - knownTechs = knownTechs, - currentlyResearchedTech = currentlyResearchedTech, - eraCivilopediaName = eraCivilopediaName, - gold = gold, + Id = id, + IsBarbarians = barbarian, + IsHuman = human, + HasPlayedThisTurn = hasPlayedCurrentTurn, + ColorIndex = colorIndex, + Civilization = civilization is not null ? civilizations.Find(civ => civ.Name == civilization) : null, + CityNameIndex = cityNameIndex, + TileKnowledge = new TileKnowledge(), + KnownTechs = knownTechs, + CurrentlyResearchedTech = currentlyResearchedTech, + EraCivilopediaName = eraCivilopediaName, + Gold = gold, }; foreach (TileLocation tile in tileKnowledge) { - player.tileKnowledge.AddTileToKnown(map.tileAt(tile.x, tile.y)); + player.TileKnowledge.AddTileToKnown(map.tileAt(tile.x, tile.y)); } - foreach (ID techId in player.civilization.startingTechs) { - if (!player.knownTechs.Contains(techId)) { - player.knownTechs.Add(techId); + foreach (ID techId in player.Civilization.StartingTechs) { + if (!player.KnownTechs.Contains(techId)) { + player.KnownTechs.Add(techId); } } return player; @@ -60,21 +60,21 @@ public Player ToPlayer(GameMap map, List civilizations) { public SavePlayer() { } public SavePlayer(Player player) { - id = player.id; - colorIndex = player.colorIndex; - barbarian = player.isBarbarians; - human = player.isHuman; - hasPlayedCurrentTurn = player.hasPlayedThisTurn; - civilization = player.civilization?.name; + id = player.Id; + colorIndex = player.ColorIndex; + barbarian = player.IsBarbarians; + human = player.IsHuman; + hasPlayedCurrentTurn = player.HasPlayedThisTurn; + civilization = player.Civilization?.Name; // TODO: this should be computed by looking at cities defined in the save // so that adding cities in the save structure doesn't require updating this value - cityNameIndex = player.cityNameIndex; - tileKnowledge = player.tileKnowledge.AllKnownTiles().ConvertAll(tile => new TileLocation(tile)); - turnsUntilPriorityReevaluation = player.turnsUntilPriorityReevaluation; - knownTechs = player.knownTechs; - currentlyResearchedTech = player.currentlyResearchedTech; - eraCivilopediaName = player.eraCivilopediaName; - gold = player.gold; + cityNameIndex = player.CityNameIndex; + tileKnowledge = player.TileKnowledge.AllKnownTiles().ConvertAll(tile => new TileLocation(tile)); + turnsUntilPriorityReevaluation = player.TurnsUntilPriorityReevaluation; + knownTechs = player.KnownTechs; + currentlyResearchedTech = player.CurrentlyResearchedTech; + eraCivilopediaName = player.EraCivilopediaName; + gold = player.Gold; } } } diff --git a/C7GameData/Save/SaveUnit.cs b/C7GameData/Save/SaveUnit.cs index 3543a141..803aea66 100644 --- a/C7GameData/Save/SaveUnit.cs +++ b/C7GameData/Save/SaveUnit.cs @@ -20,7 +20,7 @@ public SaveUnit() { } public SaveUnit(MapUnit unit, GameMap map) { id = unit.id; prototype = unit.unitType.name; - owner = unit.owner.id; + owner = unit.owner.Id; if (unit.previousLocation is not null) { previousLocation = new TileLocation(unit.previousLocation); } @@ -41,7 +41,7 @@ public MapUnit ToMapUnit(List prototypes, List e unitType = prototypes.Find(p => p.name == prototype), experienceLevelKey = experience, experienceLevel = experienceLevels.Find(el => el.key == experience), - owner = players.Find(player => player.id == owner), + owner = players.Find(player => player.Id == owner), location = map.tileAt(currentLocation.x, currentLocation.y), previousLocation = currentLocation.x == - 1 ? Tile.NONE : map.tileAt(previousLocation.x, previousLocation.y), hitPointsRemaining = hitPointsRemaining, diff --git a/C7GameDataTests/SaveTest.cs b/C7GameDataTests/SaveTest.cs index 86f59f46..9bdbb8ce 100644 --- a/C7GameDataTests/SaveTest.cs +++ b/C7GameDataTests/SaveTest.cs @@ -200,26 +200,26 @@ public void CheckScenariosInCiv3Subfolder(string subfolder) { foreach (Player player in gd.players) { int settlerCount = 0; int totalUnitCount = 0; - foreach (MapUnit mu in player.units) { + foreach (MapUnit mu in player.Units) { ++totalUnitCount; if (mu.unitType.name == "Settler") { ++settlerCount; } } - int cityCount = player.cities.Count; + int cityCount = player.Cities.Count; - Console.WriteLine(name + " : " + player.civilization.name + " has " + + Console.WriteLine(name + " : " + player.Civilization.Name + " has " + settlerCount + " settlers, " + cityCount + " cities, " + totalUnitCount + " units, and is " + - (player.isHuman ? "" : "not ") + + (player.IsHuman ? "" : "not ") + "the human player"); // The human player should always have either a city or a settler. - if (player.isHuman) { + if (player.IsHuman) { Assert.True(cityCount + settlerCount > 0, - name + " : " + player.civilization.name); + name + " : " + player.Civilization.Name); } }