From 06e0e990cdace9a53499c2374711573c55536f52 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 12 Feb 2025 11:04:27 -0600 Subject: [PATCH] Provide tile assignments for cities in scenarios Unlike with `.SAV` files, scenarios don't specify the tile assignments for each of the citizens in cities. To handle this we backfill them using the tile assignment AI. This is slightly complicated by fact that the `C7Engine` code can't depend directly on the AI logic, so we have to do some dependency injection via an `Action`. #541 --- C7/Game.cs | 12 ++++++++++++ C7Engine/EntryPoints/CreateGame.cs | 6 ++++-- C7GameData/Save/SaveCity.cs | 17 ++++++++++++++++- C7GameData/Save/SaveGame.cs | 5 +++-- C7GameDataTests/SaveTest.cs | 12 +++++++++--- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index 092497b0..e47705c8 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -6,6 +6,7 @@ using Serilog; using C7Engine.Pathing; using System.Collections.Generic; +using C7Engine.AI; public partial class Game : Node2D { [Signal] public delegate void TurnStartedEventHandler(); @@ -105,6 +106,17 @@ public override void _Ready() { Util.setModPath(scenarioSearchPath); log.Debug("RelativeModPath ", scenarioSearchPath); return Util.Civ3MediaPath("Text/PediaIcons.txt"); + }, (City city, CitizenType ct) => { + // Provide tile assignments for scenarios, which don't specify + // this in the BIC file. + for (int i = 0; i < city.size; ++i) { + CityResident newResident = new() { + citizenType = ct, + nationality = city.owner.civilization, + city = city + }; + CityTileAssignmentAI.AssignNewCitizenToTile(newResident); + } }); // Spawns engine thread Global.ResetLoadGamePath(); diff --git a/C7Engine/EntryPoints/CreateGame.cs b/C7Engine/EntryPoints/CreateGame.cs index dcb80abb..1baee8fe 100644 --- a/C7Engine/EntryPoints/CreateGame.cs +++ b/C7Engine/EntryPoints/CreateGame.cs @@ -12,12 +12,14 @@ public class CreateGame { * quickly. By keeping all the client-callable APIs in the EntryPoints folder, * hopefully it won't be too much of a goose hunt to refactor it later if we decide to do so. **/ - public static Player createGame(string loadFilePath, string defaultBicPath, Func getPediaIconsPath) { + public static Player createGame(string loadFilePath, string defaultBicPath, + Func getPediaIconsPath, + Action assignScenarioResidents) { EngineStorage.createThread(); EngineStorage.gameDataMutex.WaitOne(); SaveGame save = SaveManager.LoadSave(loadFilePath, defaultBicPath, getPediaIconsPath); - GameData gameData = save.ToGameData(); + GameData gameData = save.ToGameData(assignScenarioResidents); EngineStorage.gameData = gameData; diff --git a/C7GameData/Save/SaveCity.cs b/C7GameData/Save/SaveCity.cs index 5987d711..0f5ee86e 100644 --- a/C7GameData/Save/SaveCity.cs +++ b/C7GameData/Save/SaveCity.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace C7GameData.Save { @@ -43,7 +44,12 @@ public SaveCity(City city) { }); } - public City ToCity(GameMap gameMap, List players, List unitPrototypes, List civilizations, List citizenTypes) { + public City ToCity(GameMap gameMap, + List players, + List unitPrototypes, + List civilizations, + List citizenTypes, + Action assignScenarioResidents) { City city = new City{ id = id, location = gameMap.tileAt(location.X, location.Y), @@ -70,6 +76,15 @@ public City ToCity(GameMap gameMap, List players, List un foreach (CityResident cr in city.residents) { cr.tileWorked.personWorkingTile = cr; } + + // Scenarios don't specify the citizens of each city, only the city + // size. So we need to do that assignment now. This requires using + // the tile assignment AI, which due to dependency reasons we can't + // access directly, so we do this via a lambda. + if (city.residents.Count == 0) { + assignScenarioResidents(city, citizenTypes.Find(x => x.IsDefaultCitizen)); + } + return city; } } diff --git a/C7GameData/Save/SaveGame.cs b/C7GameData/Save/SaveGame.cs index 65cc0197..85a86141 100644 --- a/C7GameData/Save/SaveGame.cs +++ b/C7GameData/Save/SaveGame.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using System.IO; @@ -86,7 +87,7 @@ private void populateGameDataTileUnitsAndCities(GameData data) { // TODO: GameData should store Civilizations, otherwise the round trip from // SaveGame to GameData and back loses Civilization instances that are not // assigned to a player. - public GameData ToGameData() { + public GameData ToGameData(Action assignScenarioResidents) { // copy data without references GameData data = new GameData{ seed = Seed, @@ -113,7 +114,7 @@ public GameData ToGameData() { }); // cities require game map for location and players for city owner - data.cities = Cities.ConvertAll(city => city.ToCity(data.map, data.players, UnitPrototypes, Civilizations, CitizenTypes)); + data.cities = Cities.ConvertAll(city => city.ToCity(data.map, data.players, UnitPrototypes, Civilizations, CitizenTypes, assignScenarioResidents)); // Once cities are known, players can reference cities. data.players.ForEach(player => { diff --git a/C7GameDataTests/SaveTest.cs b/C7GameDataTests/SaveTest.cs index 83e3fa45..ea22a2ee 100644 --- a/C7GameDataTests/SaveTest.cs +++ b/C7GameDataTests/SaveTest.cs @@ -61,7 +61,9 @@ public void SimpleSave() { SaveGame saveNeverGameData = SaveGame.Load(developerSave); saveNeverGameData.Save(outputNeverGameDataPath); - GameData gameData = saveNeverGameData.ToGameData(); + GameData gameData = saveNeverGameData.ToGameData((City c, CitizenType ct) => { + // We don't care about scenario tile assignment here. + }); SaveGame saveWasGameData = SaveGame.FromGameData(gameData); saveWasGameData.Save(outputWasGameDataPath); @@ -110,7 +112,9 @@ public async void LoadSampleSaves() { }); Assert.Null(ex); ex = Record.Exception(() => { - gd = game.ToGameData(); + gd = game.ToGameData((City c, CitizenType ct) => { + // We don't care about scenario tile assignment here. + }); }); Assert.Null(ex); Assert.NotNull(game); @@ -162,7 +166,9 @@ public void CheckScenariosInCiv3Subfolder(string subfolder) { }); Assert.True(ex == null, name + ": " + ex?.ToString()); ex = Record.Exception(() => { - gd = game.ToGameData(); + gd = game.ToGameData((City c, CitizenType ct) => { + // We don't care about scenario tile assignment here. + }); }); Assert.True(ex == null, name + ":" + ex?.ToString()); Assert.NotNull(game);