Skip to content

Commit

Permalink
Provide tile assignments for cities in scenarios
Browse files Browse the repository at this point in the history
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
  • Loading branch information
TomWerner committed Feb 12, 2025
1 parent c9baef9 commit 06e0e99
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 8 deletions.
12 changes: 12 additions & 0 deletions C7/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();

Expand Down
6 changes: 4 additions & 2 deletions C7Engine/EntryPoints/CreateGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> getPediaIconsPath) {
public static Player createGame(string loadFilePath, string defaultBicPath,
Func<string, string> getPediaIconsPath,
Action<City, CitizenType> 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;

Expand Down
17 changes: 16 additions & 1 deletion C7GameData/Save/SaveCity.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;

namespace C7GameData.Save {
Expand Down Expand Up @@ -43,7 +44,12 @@ public SaveCity(City city) {
});
}

public City ToCity(GameMap gameMap, List<Player> players, List<UnitPrototype> unitPrototypes, List<Civilization> civilizations, List<CitizenType> citizenTypes) {
public City ToCity(GameMap gameMap,
List<Player> players,
List<UnitPrototype> unitPrototypes,
List<Civilization> civilizations,
List<CitizenType> citizenTypes,
Action<City, CitizenType> assignScenarioResidents) {
City city = new City{
id = id,
location = gameMap.tileAt(location.X, location.Y),
Expand All @@ -70,6 +76,15 @@ public City ToCity(GameMap gameMap, List<Player> players, List<UnitPrototype> 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;
}
}
Expand Down
5 changes: 3 additions & 2 deletions C7GameData/Save/SaveGame.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -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<City, CitizenType> assignScenarioResidents) {
// copy data without references
GameData data = new GameData{
seed = Seed,
Expand All @@ -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 => {
Expand Down
12 changes: 9 additions & 3 deletions C7GameDataTests/SaveTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 06e0e99

Please sign in to comment.