From 31902a5c9a89f02395442d061a4f9bf6d94e8159 Mon Sep 17 00:00:00 2001 From: Antti Halme Date: Mon, 31 Aug 2026 23:18:22 +0100 Subject: [PATCH] Load history from save, add basic history book keeping --- C7Engine/C7GameData/GameData.cs | 3 +- C7Engine/C7GameData/HistTurnRecord.cs | 9 ++++++ C7Engine/C7GameData/ImportCiv3.cs | 40 +++++++++++++++++++++++++++ C7Engine/C7GameData/Player.cs | 35 +++++++++++++++++++++++ C7Engine/C7GameData/Save/SaveGame.cs | 14 ++++++++++ C7Engine/EntryPoints/TurnHandling.cs | 2 ++ 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 C7Engine/C7GameData/HistTurnRecord.cs diff --git a/C7Engine/C7GameData/GameData.cs b/C7Engine/C7GameData/GameData.cs index fbf1c5e09..9a2e0ae93 100644 --- a/C7Engine/C7GameData/GameData.cs +++ b/C7Engine/C7GameData/GameData.cs @@ -49,6 +49,7 @@ public class GameData { public ExperienceLevel defaultExperienceLevel; public Rules rules; public TimeOptions timeOptions; + public Dictionary> history; public BarbarianInfo barbarianInfo = new BarbarianInfo(); @@ -324,7 +325,7 @@ internal void RemoveUnit(MapUnit unit) { } internal void SpawnUnit(Player player, UnitPrototype proto, Tile tile) { - // TODO: consolidate unit spawning routines (here) + // TODO: consolidate unit spawning routines (here) var defaultExpLevel = this.defaultExperienceLevel; var barbExpLevel = this.experienceLevels.First(e => e.baseHitPoints == this.barbarianInfo.maxHitpoints); diff --git a/C7Engine/C7GameData/HistTurnRecord.cs b/C7Engine/C7GameData/HistTurnRecord.cs new file mode 100644 index 000000000..332548467 --- /dev/null +++ b/C7Engine/C7GameData/HistTurnRecord.cs @@ -0,0 +1,9 @@ +namespace C7GameData; + +public class HistTurnRecord { + public int Date { get; set; } + public int Power { get; set; } + public int Score { get; set; } + public int Culture { get; set; } + public int VP { get; set; } +} diff --git a/C7Engine/C7GameData/ImportCiv3.cs b/C7Engine/C7GameData/ImportCiv3.cs index dd03b1aef..eba798037 100644 --- a/C7Engine/C7GameData/ImportCiv3.cs +++ b/C7Engine/C7GameData/ImportCiv3.cs @@ -105,6 +105,8 @@ private SaveGame importSav(string savePath, string defaultBicPath, Func civPlayerMap = BuildCivIdPlayerMap(); + + foreach (ID player in civPlayerMap.Values) { + save.History[player.ToString()] = new List(); + } + + foreach (var turn in savData.HistTurn) { + var civs = savData.TurnCiv[turn.TurnNumber]; + var powers = savData.TurnPower[turn.TurnNumber]; + var scores = savData.TurnScore[turn.TurnNumber]; + var cultures = savData.TurnCulture[turn.TurnNumber]; + var vps = savData.TurnVP[turn.TurnNumber]; + foreach ((int civ, int idx) in civs.Select((civ, idx) => (civ, idx))) { + var player = civPlayerMap[civ]; + save.History[player.ToString()].Add(new HistTurnRecord { + Date = turn.Date, + Power = powers[idx], + Score = scores[idx], + Culture = cultures[idx], + VP = vps == null ? 0 : vps[idx] + }); + } + } + } + + private Dictionary BuildCivIdPlayerMap() { + var civ3CivIdToPlayerId = new Dictionary(); + var civs = savData.TurnCiv[0]; + foreach (int civ in civs) { + var raceId = savData.Lead[civ].RaceID; + var race = savData.Bic.Race[raceId]; + var player = save.Players.First(p => p.civilization == race.Name); + civ3CivIdToPlayerId[civ] = player.id; + } + return civ3CivIdToPlayerId; + } + /** * defaultBiqPath is used in case some sections (map, rules, player data) are not * present. diff --git a/C7Engine/C7GameData/Player.cs b/C7Engine/C7GameData/Player.cs index 0b366c90c..9af6d3585 100644 --- a/C7Engine/C7GameData/Player.cs +++ b/C7Engine/C7GameData/Player.cs @@ -1130,6 +1130,41 @@ public int ShieldCost(IProducible producible) { return producible.ShieldCost(civilization.traits, costFactor); } + + public void UpdateHistory(GameData gameData) { + if (!gameData.history.ContainsKey(id.ToString())) + return; + + int n = gameData.history[id.ToString()].Count; + HistTurnRecord lastTurn = gameData.history[id.ToString()].LastOrDefault(); + + // TODO: Make formulas moddable + + // "Power is an amalgam of cities, gold, culture, advances, resources, military strength, + // nuclear weapons, and wonder." + // Exact formula is unknown, so we repeat the previous value. + // TODO: Figure out power formula + int power = (lastTurn?.Power ?? 100); + + // Score is the _average_ of "turn scores". + // Here we calculate the cumulative moving average: S[n+1] = S[n] + (x[n+1] - S[N])/(n+1) + int lastScore = (lastTurn?.Score ?? 0); + // TODO: Victory scoring + // float turnScore = ScoreVictory.ComputeTurnScore(this, gameData); + // int score = (int) Math.Floor(lastScore + (turnScore - lastScore) / (1f * (n+1))); + int score = lastScore; + + // Culture is "the sum of the cultural value of all your cities" + int totalCulture = cities.Sum(c => c.GetCulture()); + + gameData.history[id.ToString()].Add(new HistTurnRecord { + Date = gameData.timeOptions.GetRawNumber(gameData.turn), + Power = power, + Score = score, + Culture = totalCulture, + VP = 0 // TODO: victory points + }); + } } } diff --git a/C7Engine/C7GameData/Save/SaveGame.cs b/C7Engine/C7GameData/Save/SaveGame.cs index 8150fd24f..214107325 100644 --- a/C7Engine/C7GameData/Save/SaveGame.cs +++ b/C7Engine/C7GameData/Save/SaveGame.cs @@ -80,6 +80,7 @@ public static SaveGame FromGameData(GameData data) { GameDifficulty = data.gameDifficulty, Rules = data.rules, TimeOptions = data.timeOptions, + History = data.history, TerrainImprovements = data.terrainImprovements.ConvertAll(ti => ti.ToSaveTerrainImprovement()), GameModeConfig = data.gameModeConfig, }; @@ -138,6 +139,8 @@ public GameData ToGameData(BehaviorEngine behaviors) { ConvertAlliances(data); ConvertAllianceWars(data); + BeginHistory(data); + data.defaultExperienceLevelKey = DefaultExperienceLevel; data.defaultExperienceLevel = data.experienceLevels.Find(el => el.key == DefaultExperienceLevel); @@ -149,6 +152,15 @@ public GameData ToGameData(BehaviorEngine behaviors) { return data; } + private void BeginHistory(GameData data) { + foreach (var player in data.players) { + if (!player.isBarbarians) { + if (!data.history.ContainsKey(player.id.ToString())) + data.history[player.id.ToString()] = new List(); + } + } + } + private void OnGameCreation() { foreach (Player p in EngineStorage.gameData.players) { // Backfill citizen assignments for scenarios that don't specify @@ -193,6 +205,7 @@ private GameData InitializeGameData() { experienceLevels = ExperienceLevels, rules = Rules, timeOptions = TimeOptions, + history = History, GreatWondersBuilt = GreatWondersBuilt, }; @@ -449,6 +462,7 @@ private void ConvertAllianceWars(GameData data) { public List TerraForms = new(); public List Governments = new(); public List WorldSizes = new(); + public Dictionary> History = new(); // The relative directory that can be used to find scenario-specific // assets. diff --git a/C7Engine/EntryPoints/TurnHandling.cs b/C7Engine/EntryPoints/TurnHandling.cs index 2a5cd6aa9..54a3c2d68 100644 --- a/C7Engine/EntryPoints/TurnHandling.cs +++ b/C7Engine/EntryPoints/TurnHandling.cs @@ -74,6 +74,8 @@ public static async Task AdvanceTurn() { // unhappiness of a new citizen during their turn. log.Information($"\n*** City growth/production for turn {gameData.turn}, player {player} ***"); player.HandleCityUpdates(gameData); + + player.UpdateHistory(gameData); } // Now that the turn is ending, do all the bookkeeping for the