From 492cc99dc626f4ecf43c699b140d4d84ec9f27de Mon Sep 17 00:00:00 2001 From: egor Date: Thu, 26 Feb 2026 16:16:06 +0400 Subject: [PATCH] Unify Lua modding under GameMode --- C7/Game.cs | 23 +- C7/GamePaths.cs | 15 +- C7/GlobalSingleton.cs | 44 +++- C7/Lua/civ3/behaviors.lua | 12 + .../civ3 => civ3/behaviors}/buildings.lua | 0 .../civ3 => civ3/behaviors}/gameplay.lua | 0 .../civ3 => civ3/behaviors}/inflows.lua | 0 .../civ3 => civ3/behaviors}/terraforms.lua | 0 .../behaviors}/terrain_improvements.lua | 0 .../base-ruleset.json => civ3/ruleset.json} | 0 .../civ3.lua => civ3/textures.lua} | 32 +-- .../civ3 => civ3/textures}/advisor_heads.lua | 0 .../civ3 => civ3/textures}/borders.lua | 0 .../civ3 => civ3/textures}/building_icons.lua | 0 .../civ3 => civ3/textures}/cities.lua | 0 .../civ3 => civ3/textures}/civ_colors.lua | 0 .../civ3 => civ3/textures}/leader_heads.lua | 0 .../civ3 => civ3/textures}/player_setup.lua | 0 .../civ3 => civ3/textures}/popheads.lua | 0 .../civ3 => civ3/textures}/resources.lua | 0 .../civ3 => civ3/textures}/tech_boxes.lua | 0 .../civ3 => civ3/textures}/tech_icons.lua | 0 .../civ3 => civ3/textures}/terrain.lua | 0 .../textures}/terrain_improvements.lua | 0 .../civ3 => civ3/textures}/unit_control.lua | 0 .../civ3 => civ3/textures}/unit_icons.lua | 0 .../civ3 => civ3/textures}/world_setup.lua | 0 C7/Lua/rules/civ3.lua | 12 - .../standalone.lua => standalone/ruleset.lua} | 2 +- C7/Lua/standalone/textures.lua | 198 ++++++++++++++ .../{texture_configs => standalone}/utils.lua | 0 C7/Lua/texture_configs/c7.lua | 181 ------------- C7/Textures/TextureLoader.cs | 34 +-- C7/UIElements/MainMenu/MainMenu.cs | 14 +- C7/UIElements/NewGame/PlayerSetup.cs | 4 - C7/UIElements/NewGame/WorldSetup.cs | 13 +- C7/Util.cs | 1 - C7Engine/C7GameData/Building.cs | 2 +- C7Engine/C7GameData/GameData.cs | 7 +- C7Engine/C7GameData/Inflow.cs | 4 +- C7Engine/C7GameData/Save/SaveGame.cs | 25 +- C7Engine/C7GameData/Terraform.cs | 4 +- C7Engine/C7GameData/TerrainImprovement.cs | 2 +- C7Engine/EntryPoints/CreateGame.cs | 15 +- C7Engine/Lua/BehaviorEngine.cs | 74 ++++++ C7Engine/Lua/GameMode.cs | 243 ++++++++++++++++++ C7Engine/Lua/GameModeLoader.cs | 65 ----- C7Engine/Lua/RulesEngine.cs | 158 ------------ .../GameData/PlayerRelationshipTest.cs | 2 +- EngineTests/GameData/SaveTest.cs | 31 ++- 50 files changed, 666 insertions(+), 551 deletions(-) create mode 100644 C7/Lua/civ3/behaviors.lua rename C7/Lua/{rules/civ3 => civ3/behaviors}/buildings.lua (100%) rename C7/Lua/{rules/civ3 => civ3/behaviors}/gameplay.lua (100%) rename C7/Lua/{rules/civ3 => civ3/behaviors}/inflows.lua (100%) rename C7/Lua/{rules/civ3 => civ3/behaviors}/terraforms.lua (100%) rename C7/Lua/{rules/civ3 => civ3/behaviors}/terrain_improvements.lua (100%) rename C7/Lua/{game_modes/base-ruleset.json => civ3/ruleset.json} (100%) rename C7/Lua/{texture_configs/civ3.lua => civ3/textures.lua} (92%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/advisor_heads.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/borders.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/building_icons.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/cities.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/civ_colors.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/leader_heads.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/player_setup.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/popheads.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/resources.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/tech_boxes.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/tech_icons.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/terrain.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/terrain_improvements.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/unit_control.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/unit_icons.lua (100%) rename C7/Lua/{texture_configs/civ3 => civ3/textures}/world_setup.lua (100%) delete mode 100644 C7/Lua/rules/civ3.lua rename C7/Lua/{game_modes/standalone.lua => standalone/ruleset.lua} (97%) create mode 100644 C7/Lua/standalone/textures.lua rename C7/Lua/{texture_configs => standalone}/utils.lua (100%) delete mode 100644 C7/Lua/texture_configs/c7.lua create mode 100644 C7Engine/Lua/BehaviorEngine.cs create mode 100644 C7Engine/Lua/GameMode.cs delete mode 100644 C7Engine/Lua/GameModeLoader.cs delete mode 100644 C7Engine/Lua/RulesEngine.cs diff --git a/C7/Game.cs b/C7/Game.cs index 50491d9c9..e80901462 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -110,11 +110,7 @@ public override async void _Ready() { } private async Task LoadGame() { - // Ensure we clear out our image caches, as scenarios and games will - // use the same filenames but have different content for them. - Util.ClearCaches(); - - CreateGameParams options = new(GamePaths.LuaRulesDir, GamePaths.DefaultBicPath) + CreateGameParams options = new(GamePaths.DefaultBicPath) { GetPediaIconsPath = (scenarioSearchPath) => { // When the game loading logic tries to load the PediaIcons file, set the @@ -133,11 +129,14 @@ private async Task LoadGame() { Util.setModPath(scenarioSearchPath); log.Debug("RelativeModPath ", scenarioSearchPath); return Util.Civ3MediaPath("Text/PediaIcons.txt"); - } + }, + GameModeLoader = (config) => { + Global.ActivateGameMode(config); + return Global.GameMode.behaviors; + }, }; - if (Global.SaveGame != null) { - controller = await CreateGame.createGame(Global.SaveGame, options); + controller = await CreateGame.createGame(Global.SaveGame, options.GameModeLoader); } else if (Global.LoadGamePath != null) { controller = await CreateGame.createGame(Global.LoadGamePath, options); } else { @@ -597,9 +596,6 @@ private void HandleKeyboardInput(InputEventKey eventKeyDown) { if (eventKeyDown.Keycode == Godot.Key.O && eventKeyDown.ShiftPressed && eventKeyDown.IsCommandOrControlPressed() && eventKeyDown.AltPressed) { ToggleObserverMode(); } - if (eventKeyDown.Keycode == Godot.Key.T && eventKeyDown.ShiftPressed && eventKeyDown.IsCommandOrControlPressed() && eventKeyDown.AltPressed) { - ToggleC7Graphics(); - } if (eventKeyDown.Keycode == Godot.Key.F1) { EmitSignal(SignalName.ShowSpecificAdvisor, "F1"); } @@ -687,11 +683,6 @@ private void ToggleGridCoordinates() { }); } - private void ToggleC7Graphics() { - Global.ToggleModernGraphics(); - InitializeMapView(); - } - private void HandleMagnifyGesture(InputEventMagnifyGesture magnifyGesture) { double newScale = mapView.cameraZoom * magnifyGesture.Factor; diff --git a/C7/GamePaths.cs b/C7/GamePaths.cs index d09810cae..5b43e0776 100644 --- a/C7/GamePaths.cs +++ b/C7/GamePaths.cs @@ -27,20 +27,13 @@ public static string BaseDir { } } - // This is the 'static map' used in lieu of terrain generation - public static GameModeConfig GameMode { + public static GameMode.Config GameMode { get => C7Engine.C7Settings.UseStandaloneMode() ? standalone : basic; } - public static GameModeConfig basic = new("base-ruleset.json"); - public static GameModeConfig standalone = new("base-ruleset.json", ["standalone.lua"]); - - public static string LuaRulesDir => Path.Combine(BaseDir, "Lua/rules/"); - public static string TextureConfigsDir => Path.Combine(BaseDir, "Lua/texture_configs/"); - public static string GameModesDir => Path.Combine(BaseDir, "Lua/game_modes/"); - - public const string ModernGraphicsConfig = "c7.lua"; - public const string ClassicGraphicsConfig = "civ3.lua"; + public static string GameModesDir => Path.Combine(BaseDir, "Lua"); + public static GameMode.Config basic = new("civ3"); + public static GameMode.Config standalone = new("civ3", ["standalone"]); // For now this needs to get passed to QueryCiv3 when importing. public static string DefaultBicPath { get => Util.GetCiv3Path() + "/Conquests/conquests.biq"; } diff --git a/C7/GlobalSingleton.cs b/C7/GlobalSingleton.cs index 455414b25..dbd4b801b 100644 --- a/C7/GlobalSingleton.cs +++ b/C7/GlobalSingleton.cs @@ -1,6 +1,7 @@ using Godot; using C7Engine; using C7GameData.Save; +using C7Engine.Lua; /**** Need to pass values from one scene to another, particularly when loading @@ -8,6 +9,8 @@ a game in main menu. This script is set to auto load in project settings. See https://docs.godotengine.org/en/stable/getting_started/step_by_step/singletons_autoload.html ****/ public partial class GlobalSingleton : Node { + public GameMode GameMode; + // Will have main menu file picker set this and Game.cs pass it to C7Engine.createGame // which then should blank it again to prevent reloading same if going back to main menu // and back to game @@ -16,27 +19,46 @@ public partial class GlobalSingleton : Node { // Generated game data used when starting a new game public SaveGame SaveGame; - public bool ModernGraphicsActive { get; private set; } + // The characteristics of the world to generate. This exists in the singleton + // to allow the world setup screen to pass the information to the player + // setup screen, which is what actually kicks off the world generation. + public WorldCharacteristics WorldCharacteristics; public GlobalSingleton() { if (C7Settings.UseStandaloneMode()) { - ToggleModernGraphics(); + ActivateGameMode(GamePaths.standalone); + } else { + ActivateGameMode(GamePaths.basic); } } - // The characteristics of the world to generate. This exists in the singleton - // to allow the world setup screen to pass the information to the player - // setup screen, which is what actually kicks off the world generation. - public WorldCharacteristics WorldCharacteristics; - public void ResetLoadGameFields() { LoadGamePath = null; SaveGame = null; } - public void ToggleModernGraphics() { - string newConfig = ModernGraphicsActive ? GamePaths.ClassicGraphicsConfig : GamePaths.ModernGraphicsConfig; - TextureLoader.SetConfig(GamePaths.TextureConfigsDir, newConfig); - ModernGraphicsActive = !ModernGraphicsActive; + public void ActivateGameMode(GameMode.Config config) { + // Ensure we clear out our image caches, as scenarios and games will + // use the same filenames but have different content for them. + Util.ClearCaches(); + + GameMode = GameMode.Load(GamePaths.GameModesDir, config); + + var (script, textureConfig) = GameMode.textures; + TextureLoader.SetConfig(script, textureConfig); + + if (config.addonPaths.Contains("standalone")) { + C7Settings.SetValue("locations", "useStandaloneMode", "true"); + } else { + C7Settings.SetValue("locations", "useStandaloneMode", "false"); + } + + C7Settings.SaveSettings(); + } + + public void ToggleStandaloneMode() { + GameMode.Config newConfig = C7Settings.UseStandaloneMode() ? GamePaths.basic : GamePaths.standalone; + + ActivateGameMode(newConfig); } } diff --git a/C7/Lua/civ3/behaviors.lua b/C7/Lua/civ3/behaviors.lua new file mode 100644 index 000000000..2228188c4 --- /dev/null +++ b/C7/Lua/civ3/behaviors.lua @@ -0,0 +1,12 @@ +return { + buildings = require "behaviors.buildings", + + --[[ + terraforms module provides rules for worker actions (e.g., clearing forest or mining), + while terrain_improvements describes improvements placed on a map (e.g., mine, irrigation, railroad) + --]] + terraforms = require "behaviors.terraforms", + terrain_improvements = require "behaviors.terrain_improvements", + inflows = require "behaviors.inflows", + gameplay = require "behaviors.gameplay", +} diff --git a/C7/Lua/rules/civ3/buildings.lua b/C7/Lua/civ3/behaviors/buildings.lua similarity index 100% rename from C7/Lua/rules/civ3/buildings.lua rename to C7/Lua/civ3/behaviors/buildings.lua diff --git a/C7/Lua/rules/civ3/gameplay.lua b/C7/Lua/civ3/behaviors/gameplay.lua similarity index 100% rename from C7/Lua/rules/civ3/gameplay.lua rename to C7/Lua/civ3/behaviors/gameplay.lua diff --git a/C7/Lua/rules/civ3/inflows.lua b/C7/Lua/civ3/behaviors/inflows.lua similarity index 100% rename from C7/Lua/rules/civ3/inflows.lua rename to C7/Lua/civ3/behaviors/inflows.lua diff --git a/C7/Lua/rules/civ3/terraforms.lua b/C7/Lua/civ3/behaviors/terraforms.lua similarity index 100% rename from C7/Lua/rules/civ3/terraforms.lua rename to C7/Lua/civ3/behaviors/terraforms.lua diff --git a/C7/Lua/rules/civ3/terrain_improvements.lua b/C7/Lua/civ3/behaviors/terrain_improvements.lua similarity index 100% rename from C7/Lua/rules/civ3/terrain_improvements.lua rename to C7/Lua/civ3/behaviors/terrain_improvements.lua diff --git a/C7/Lua/game_modes/base-ruleset.json b/C7/Lua/civ3/ruleset.json similarity index 100% rename from C7/Lua/game_modes/base-ruleset.json rename to C7/Lua/civ3/ruleset.json diff --git a/C7/Lua/texture_configs/civ3.lua b/C7/Lua/civ3/textures.lua similarity index 92% rename from C7/Lua/texture_configs/civ3.lua rename to C7/Lua/civ3/textures.lua index 98b117f58..8044f819a 100644 --- a/C7/Lua/texture_configs/civ3.lua +++ b/C7/Lua/civ3/textures.lua @@ -154,9 +154,9 @@ textures.ui = { }, } -textures.terrain = require "civ3.terrain" +textures.terrain = require "textures.terrain" -textures.resources = require "civ3.resources" +textures.resources = require "textures.resources" textures.credits = { background = CREDITS .. "credits_background.pcx", @@ -336,8 +336,8 @@ textures.palace = { background = PALACE .. "bkgr.pcx", } -textures.world_setup = require "civ3.world_setup" -textures.player_setup = require "civ3.player_setup" +textures.world_setup = require "textures.world_setup" +textures.player_setup = require "textures.player_setup" textures.diplomacy = { deal = "Art/Diplomacy/counter.pcx", @@ -448,17 +448,17 @@ textures.animations = { }, } -textures.popheads = require "civ3.popheads" -textures.cities = require "civ3.cities" -textures.advisor_heads = require "civ3.advisor_heads" -textures.ui.unit_control = require "civ3.unit_control" -textures.terrain_improvements = require "civ3.terrain_improvements" -textures.civ_colors = require "civ3.civ_colors" -textures.unit_icons = require "civ3.unit_icons" -textures.building_icons = require "civ3.building_icons" -textures.tech_icons = require "civ3.tech_icons" -textures.tech_boxes = require "civ3.tech_boxes" -textures.leader_heads = require "civ3.leader_heads" -textures.borders = require "civ3.borders" +textures.popheads = require "textures.popheads" +textures.cities = require "textures.cities" +textures.advisor_heads = require "textures.advisor_heads" +textures.ui.unit_control = require "textures.unit_control" +textures.terrain_improvements = require "textures.terrain_improvements" +textures.civ_colors = require "textures.civ_colors" +textures.unit_icons = require "textures.unit_icons" +textures.building_icons = require "textures.building_icons" +textures.tech_icons = require "textures.tech_icons" +textures.tech_boxes = require "textures.tech_boxes" +textures.leader_heads = require "textures.leader_heads" +textures.borders = require "textures.borders" return textures diff --git a/C7/Lua/texture_configs/civ3/advisor_heads.lua b/C7/Lua/civ3/textures/advisor_heads.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/advisor_heads.lua rename to C7/Lua/civ3/textures/advisor_heads.lua diff --git a/C7/Lua/texture_configs/civ3/borders.lua b/C7/Lua/civ3/textures/borders.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/borders.lua rename to C7/Lua/civ3/textures/borders.lua diff --git a/C7/Lua/texture_configs/civ3/building_icons.lua b/C7/Lua/civ3/textures/building_icons.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/building_icons.lua rename to C7/Lua/civ3/textures/building_icons.lua diff --git a/C7/Lua/texture_configs/civ3/cities.lua b/C7/Lua/civ3/textures/cities.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/cities.lua rename to C7/Lua/civ3/textures/cities.lua diff --git a/C7/Lua/texture_configs/civ3/civ_colors.lua b/C7/Lua/civ3/textures/civ_colors.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/civ_colors.lua rename to C7/Lua/civ3/textures/civ_colors.lua diff --git a/C7/Lua/texture_configs/civ3/leader_heads.lua b/C7/Lua/civ3/textures/leader_heads.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/leader_heads.lua rename to C7/Lua/civ3/textures/leader_heads.lua diff --git a/C7/Lua/texture_configs/civ3/player_setup.lua b/C7/Lua/civ3/textures/player_setup.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/player_setup.lua rename to C7/Lua/civ3/textures/player_setup.lua diff --git a/C7/Lua/texture_configs/civ3/popheads.lua b/C7/Lua/civ3/textures/popheads.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/popheads.lua rename to C7/Lua/civ3/textures/popheads.lua diff --git a/C7/Lua/texture_configs/civ3/resources.lua b/C7/Lua/civ3/textures/resources.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/resources.lua rename to C7/Lua/civ3/textures/resources.lua diff --git a/C7/Lua/texture_configs/civ3/tech_boxes.lua b/C7/Lua/civ3/textures/tech_boxes.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/tech_boxes.lua rename to C7/Lua/civ3/textures/tech_boxes.lua diff --git a/C7/Lua/texture_configs/civ3/tech_icons.lua b/C7/Lua/civ3/textures/tech_icons.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/tech_icons.lua rename to C7/Lua/civ3/textures/tech_icons.lua diff --git a/C7/Lua/texture_configs/civ3/terrain.lua b/C7/Lua/civ3/textures/terrain.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/terrain.lua rename to C7/Lua/civ3/textures/terrain.lua diff --git a/C7/Lua/texture_configs/civ3/terrain_improvements.lua b/C7/Lua/civ3/textures/terrain_improvements.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/terrain_improvements.lua rename to C7/Lua/civ3/textures/terrain_improvements.lua diff --git a/C7/Lua/texture_configs/civ3/unit_control.lua b/C7/Lua/civ3/textures/unit_control.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/unit_control.lua rename to C7/Lua/civ3/textures/unit_control.lua diff --git a/C7/Lua/texture_configs/civ3/unit_icons.lua b/C7/Lua/civ3/textures/unit_icons.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/unit_icons.lua rename to C7/Lua/civ3/textures/unit_icons.lua diff --git a/C7/Lua/texture_configs/civ3/world_setup.lua b/C7/Lua/civ3/textures/world_setup.lua similarity index 100% rename from C7/Lua/texture_configs/civ3/world_setup.lua rename to C7/Lua/civ3/textures/world_setup.lua diff --git a/C7/Lua/rules/civ3.lua b/C7/Lua/rules/civ3.lua deleted file mode 100644 index 8cf7273c1..000000000 --- a/C7/Lua/rules/civ3.lua +++ /dev/null @@ -1,12 +0,0 @@ -return { - buildings = require "civ3.buildings", - - --[[ - terraforms module provides rules for worker actions (e.g., clearing forest or mining), - while terrain_improvements describes improvements placed on a map (e.g., mine, irrigation, railroad) - --]] - terraforms = require "civ3.terraforms", - terrain_improvements = require "civ3.terrain_improvements", - inflows = require "civ3.inflows", - gameplay = require "civ3.gameplay", -} diff --git a/C7/Lua/game_modes/standalone.lua b/C7/Lua/standalone/ruleset.lua similarity index 97% rename from C7/Lua/game_modes/standalone.lua rename to C7/Lua/standalone/ruleset.lua index 96213bd86..4d20e49b4 100644 --- a/C7/Lua/game_modes/standalone.lua +++ b/C7/Lua/standalone/ruleset.lua @@ -36,7 +36,7 @@ local unit_replacement_art_map = { --[[ This function defines the standalone addon to the base ruleset. -This function takes the initial save data from `base-ruleset.json` and +This function takes the initial save data from `civ3/ruleset.json` and removes the units for which we don't have graphics replacements. On the C# side, this function is called as part of addon loading logic diff --git a/C7/Lua/standalone/textures.lua b/C7/Lua/standalone/textures.lua new file mode 100644 index 000000000..f8c9dd95f --- /dev/null +++ b/C7/Lua/standalone/textures.lua @@ -0,0 +1,198 @@ +--[[ + To add new replacement texture to the config modify the "c7_texture_list" table. +--]] +local utils = require "utils" + +local c7_texture_list = { + "Art/buttonsFINAL.png", + "Art/X-o_ALLstates-sprite.png", + "Art/SmallHeads/popHeads.png", + "Art/Terrain/Mountains-snow.png", + "Art/Terrain/Mountains.png", + "Art/Terrain/TerrainBuildings.png", + "Art/Terrain/Volcanos forests.png", + "Art/Terrain/Volcanos jungles.png", + "Art/Terrain/Volcanos.png", + "Art/Terrain/grassland forests.png", + "Art/Terrain/hill forests.png", + "Art/Terrain/hill jungle.png", + "Art/Terrain/irrigation DESETT.png", + "Art/Terrain/irrigation PLAINS.png", + "Art/Terrain/irrigation TUNDRA.png", + "Art/Terrain/irrigation.png", + "Art/Terrain/marsh.png", + "Art/Terrain/mountain forests.png", + "Art/Terrain/mountain jungles.png", + "Art/Terrain/mtnRivers.png", + "Art/Terrain/plains forests.png", + "Art/Terrain/tnt.png", + "Art/Terrain/roads.png", + "Art/Terrain/railroads.png", + "Art/Terrain/tundra forests.png", + "Art/Terrain/wCSO.png", + "Art/Terrain/wOOO.png", + "Art/Terrain/wSSS.png", + "Art/Terrain/xdgc.png", + "Art/Terrain/xdgp.png", + "Art/Terrain/xdpc.png", + "Art/Terrain/xggc.png", + "Art/Terrain/xhills.png", + "Art/Terrain/xpgc.png", + "Art/Terrain/xtgc.png", + "Art/Cities/rMIDEAST.png", + "Art/Cities/MIDEASTWALL.png", + "Art/city screen/CityIcons.png", + "Art/city screen/ProdButton.png", + "Art/city screen/background.png", + "Art/city screen/cityMgmtButtons.png", + "Art/resources.png", + "Art/WorldSetup/CLIMTEMPAGEDepress.png", + "Art/WorldSetup/CLIMTEMPAGERollovers.png", + "Art/WorldSetup/age.png", + "Art/WorldSetup/background.png", + "Art/WorldSetup/climate.png", + "Art/WorldSetup/landmassWaterSMALL.png", + "Art/WorldSetup/landmassWaterSMALLdepress.png", + "Art/WorldSetup/landmassWaterSMALLrollovers.png", + "Art/WorldSetup/landmassWaterlarge.png", + "Art/WorldSetup/temperature.png", + "Art/interface/NormButtons.png", + "Art/interface/rolloverbuttons.png", + "Art/interface/highlightedbuttons.png", + "Art/interface/box right color.png", + "Art/interface/nextturn states color.png", + "Art/interface/consoleButtons.png", + "Art/SmallHeads/popupDOMESTIC.png", + "Art/SmallHeads/popupTRADE.png", + "Art/SmallHeads/popupMILITARY.png", + "Art/SmallHeads/popupFOREIGN.png", + "Art/SmallHeads/popupCULTURE.png", + "Art/SmallHeads/popupSCIENCE.png", + "Art/Advisors/domestic_icons_aux.png", + "Art/Advisors/domesticBUTTON.png", + "Art/Cities/city icons.png", + "Art/popupborders.png", + "Art/interface/menuButtons.png", + "Art/Advisors/dialogbox.png", + "Art/Advisors/domestic.png", + "Art/Tech Chooser/scienceNAV.png", + "Art/Credits/credits_background.png", + "Art/city screen/ProductionQueueBox.png", + "Art/Advisors/non_required.png", + "Art/Advisors/techboxes.png", + "Art/Advisors/military.png", + "Art/interface/MovementLED.png", + "Art/Advisors/science_ancient.png", + "Art/Advisors/science_middle.png", + "Art/Advisors/science_industrial_new.png", + "Art/Advisors/science_modern.png", + "Art/exitBox-backgroundStates.png", + "Art/PlayerSetup/playerSetup.png", + "Art/Diplomacy/talk_offer.png", + "Art/Diplomacy/counter.png", + "Art/PalaceView/bkgr.png", + "Art/city screen/luxuryicons_small.png", + "Art/Terrain/FogOfWar.png", + "Art/Terrain/Territory.png", + "Art/Units/units_32.png", + "Art/city screen/buildings-small.png", + "Art/city screen/buildings-large.png", +} + +--- For ease of editing, we define the civ colors as hex codes, not 1x1 px images +local civ_colors = { + "F0F8FF", -- Alice Blue (whiteish) + "E6194B", -- Red + "F58231", -- Orange + "FFE119", -- Yellow + "3CB44B", -- Green + "4363D8", -- Blue + "000075", -- Navy + "FABED4", -- Pink + "911EB4", -- Purple + "9A6324", -- Brown + "AAFFC3", -- Mint + "42D4F4", -- Cyan + "F032E6", -- Magenta + "808000", -- Olive + "DCBEFF", -- Lavender + "A9A9A9", -- Grey + "008080", -- Teal + "FFD700", -- Gold + "800000", -- Maroon + "00FF00", -- Lime + "FFC0CB", -- Hot Pink + "4682B4", -- Steel Blue + "D2B48C", -- Tan + "FF7F50", -- Coral + "6A5ACD", -- Slate Blue + "2E8B57", -- Sea Green + "DAA520", -- Goldenrod + "C71585", -- Medium Violet Red + "556B2F", -- Dark Olive Green + "8B4513", -- Saddle Brown + "B0C4DE", -- Light Steel Blue + "696969" -- Dim Gray +} + +-- Build lookup table from c7_texture_list without extensions +local lookup = {} +for _, path in ipairs(c7_texture_list) do + lookup[utils.strip_extension(path)] = path +end + +local function path_transformer(path) + local stripped = utils.strip_extension(path) + return lookup[stripped] or path +end + + +--[[ + This function returns a table with texture definitions for "modern" graphics. + + It produces this table by copying the config of the civ3 assets and + replacing the paths to the original Civilization 3 PCX textures with + modern PNG versions. +--]] +return function(civ3_textures) + local c7_textures = utils.transform_paths(civ3_textures, path_transformer) + + -- TODO: Add proper replacement for the asset + c7_textures.terrain.river_delta = "Art/Terrain/mtnRivers.png" + + c7_textures.civ_colors = {} + for i, hex_color in ipairs(civ_colors) do + c7_textures.civ_colors["color_" .. (i-1)] = { path = "", hex_color = hex_color } + end + + c7_textures.animations.cursor = { + path = "Art/Animations/Cursor.png", + animation_rows = 2, + animation_cols = 9, + frame_duration = 0.6, + } + c7_textures.animations.disorder = { + path = "Art/Animations/DisorderDefault.png", + animation_rows = 1, + animation_cols = 6, + frame_duration = 0.6, + } + + function c7_textures.tech_icons.small:map_object_to_sprite(tech) + return { + path = "Art/Tech Chooser/Icons/placeholder.png", + } + end + + function c7_textures.leader_heads:map_object_to_sprite(player_or_civ) + return { + path = "Art/Advisors/placeholder_leaderhead.png", + } + end + + function c7_textures.borders:find_column(_) + return 0 + end + + return c7_textures +end diff --git a/C7/Lua/texture_configs/utils.lua b/C7/Lua/standalone/utils.lua similarity index 100% rename from C7/Lua/texture_configs/utils.lua rename to C7/Lua/standalone/utils.lua diff --git a/C7/Lua/texture_configs/c7.lua b/C7/Lua/texture_configs/c7.lua deleted file mode 100644 index 1524db0ca..000000000 --- a/C7/Lua/texture_configs/c7.lua +++ /dev/null @@ -1,181 +0,0 @@ ---[[ - This configuration file returns a table with texture definitions for "modern" graphics. - It produces this table by copying the config of the civ3 assets and replaces the paths to the original Civilization 3 PCX textures with modern PNG versions. - To add new replacement texture to the config modify the "c7_texture_list" table. ---]] -local civ3_textures = require "civ3" -local utils = require "utils" - -local c7_texture_list = { - "Art/buttonsFINAL.png", - "Art/X-o_ALLstates-sprite.png", - "Art/SmallHeads/popHeads.png", - "Art/Terrain/Mountains-snow.png", - "Art/Terrain/Mountains.png", - "Art/Terrain/TerrainBuildings.png", - "Art/Terrain/Volcanos forests.png", - "Art/Terrain/Volcanos jungles.png", - "Art/Terrain/Volcanos.png", - "Art/Terrain/grassland forests.png", - "Art/Terrain/hill forests.png", - "Art/Terrain/hill jungle.png", - "Art/Terrain/irrigation DESETT.png", - "Art/Terrain/irrigation PLAINS.png", - "Art/Terrain/irrigation TUNDRA.png", - "Art/Terrain/irrigation.png", - "Art/Terrain/marsh.png", - "Art/Terrain/mountain forests.png", - "Art/Terrain/mountain jungles.png", - "Art/Terrain/mtnRivers.png", - "Art/Terrain/plains forests.png", - "Art/Terrain/tnt.png", - "Art/Terrain/roads.png", - "Art/Terrain/railroads.png", - "Art/Terrain/tundra forests.png", - "Art/Terrain/wCSO.png", - "Art/Terrain/wOOO.png", - "Art/Terrain/wSSS.png", - "Art/Terrain/xdgc.png", - "Art/Terrain/xdgp.png", - "Art/Terrain/xdpc.png", - "Art/Terrain/xggc.png", - "Art/Terrain/xhills.png", - "Art/Terrain/xpgc.png", - "Art/Terrain/xtgc.png", - "Art/Cities/rMIDEAST.png", - "Art/Cities/MIDEASTWALL.png", - "Art/city screen/CityIcons.png", - "Art/city screen/ProdButton.png", - "Art/city screen/background.png", - "Art/city screen/cityMgmtButtons.png", - "Art/resources.png", - "Art/WorldSetup/CLIMTEMPAGEDepress.png", - "Art/WorldSetup/CLIMTEMPAGERollovers.png", - "Art/WorldSetup/age.png", - "Art/WorldSetup/background.png", - "Art/WorldSetup/climate.png", - "Art/WorldSetup/landmassWaterSMALL.png", - "Art/WorldSetup/landmassWaterSMALLdepress.png", - "Art/WorldSetup/landmassWaterSMALLrollovers.png", - "Art/WorldSetup/landmassWaterlarge.png", - "Art/WorldSetup/temperature.png", - "Art/interface/NormButtons.png", - "Art/interface/rolloverbuttons.png", - "Art/interface/highlightedbuttons.png", - "Art/interface/box right color.png", - "Art/interface/nextturn states color.png", - "Art/interface/consoleButtons.png", - "Art/SmallHeads/popupDOMESTIC.png", - "Art/SmallHeads/popupTRADE.png", - "Art/SmallHeads/popupMILITARY.png", - "Art/SmallHeads/popupFOREIGN.png", - "Art/SmallHeads/popupCULTURE.png", - "Art/SmallHeads/popupSCIENCE.png", - "Art/Advisors/domestic_icons_aux.png", - "Art/Advisors/domesticBUTTON.png", - "Art/Cities/city icons.png", - "Art/popupborders.png", - "Art/interface/menuButtons.png", - "Art/Advisors/dialogbox.png", - "Art/Advisors/domestic.png", - "Art/Tech Chooser/scienceNAV.png", - "Art/Credits/credits_background.png", - "Art/city screen/ProductionQueueBox.png", - "Art/Advisors/non_required.png", - "Art/Advisors/techboxes.png", - "Art/Advisors/military.png", - "Art/interface/MovementLED.png", - "Art/Advisors/science_ancient.png", - "Art/Advisors/science_middle.png", - "Art/Advisors/science_industrial_new.png", - "Art/Advisors/science_modern.png", - "Art/exitBox-backgroundStates.png", - "Art/PlayerSetup/playerSetup.png", - "Art/Diplomacy/talk_offer.png", - "Art/Diplomacy/counter.png", - "Art/PalaceView/bkgr.png", - "Art/city screen/luxuryicons_small.png", - "Art/Terrain/FogOfWar.png", - "Art/Terrain/Territory.png", - "Art/Units/units_32.png", - "Art/city screen/buildings-small.png", - "Art/city screen/buildings-large.png", -} - --- Build lookup table from c7_texture_list without extensions -local lookup = {} -for _, path in ipairs(c7_texture_list) do - lookup[utils.strip_extension(path)] = path -end - -local function path_transformer(path) - local stripped = utils.strip_extension(path) - return lookup[stripped] or path -end - -local c7_textures = utils.transform_paths(civ3_textures, path_transformer) - --- For ease of editing, we define the civ colors as hex codes, not 1x1 px images -c7_textures.civ_colors.color_0 = { path = "", hex_color = "F0F8FF" } -- Alice Blue (whiteish) -c7_textures.civ_colors.color_1 = { path = "", hex_color = "E6194B" } -- Red -c7_textures.civ_colors.color_2 = { path = "", hex_color = "F58231" } -- Orange -c7_textures.civ_colors.color_3 = { path = "", hex_color = "FFE119" } -- Yellow -c7_textures.civ_colors.color_4 = { path = "", hex_color = "3CB44B" } -- Green -c7_textures.civ_colors.color_5 = { path = "", hex_color = "4363D8" } -- Blue -c7_textures.civ_colors.color_6 = { path = "", hex_color = "000075" } -- Navy -c7_textures.civ_colors.color_7 = { path = "", hex_color = "FABED4" } -- Pink -c7_textures.civ_colors.color_8 = { path = "", hex_color = "911EB4" } -- Purple -c7_textures.civ_colors.color_9 = { path = "", hex_color = "9A6324" } -- Brown -c7_textures.civ_colors.color_10 = { path = "", hex_color = "AAFFC3" } -- Mint -c7_textures.civ_colors.color_11 = { path = "", hex_color = "42D4F4" } -- Cyan -c7_textures.civ_colors.color_12 = { path = "", hex_color = "F032E6" } -- Magenta -c7_textures.civ_colors.color_13 = { path = "", hex_color = "808000" } -- Olive -c7_textures.civ_colors.color_14 = { path = "", hex_color = "DCBEFF" } -- Lavender -c7_textures.civ_colors.color_15 = { path = "", hex_color = "A9A9A9" } -- Grey -c7_textures.civ_colors.color_16 = { path = "", hex_color = "008080" } -- Teal -c7_textures.civ_colors.color_17 = { path = "", hex_color = "FFD700" } -- Gold -c7_textures.civ_colors.color_18 = { path = "", hex_color = "800000" } -- Maroon -c7_textures.civ_colors.color_19 = { path = "", hex_color = "00FF00" } -- Lime -c7_textures.civ_colors.color_20 = { path = "", hex_color = "FFC0CB" } -- Hot Pink -c7_textures.civ_colors.color_21 = { path = "", hex_color = "4682B4" } -- Steel Blue -c7_textures.civ_colors.color_22 = { path = "", hex_color = "D2B48C" } -- Tan -c7_textures.civ_colors.color_23 = { path = "", hex_color = "FF7F50" } -- Coral -c7_textures.civ_colors.color_24 = { path = "", hex_color = "6A5ACD" } -- Slate Blue -c7_textures.civ_colors.color_25 = { path = "", hex_color = "2E8B57" } -- Sea Green -c7_textures.civ_colors.color_26 = { path = "", hex_color = "DAA520" } -- Goldenrod -c7_textures.civ_colors.color_27 = { path = "", hex_color = "C71585" } -- Medium Violet Red -c7_textures.civ_colors.color_28 = { path = "", hex_color = "556B2F" } -- Dark Olive Green -c7_textures.civ_colors.color_29 = { path = "", hex_color = "8B4513" } -- Saddle Brown -c7_textures.civ_colors.color_30 = { path = "", hex_color = "B0C4DE" } -- Light Steel Blue -c7_textures.civ_colors.color_31 = { path = "", hex_color = "696969" } -- Dim Gray - -c7_textures.animations.cursor = { - path = "Art/Animations/Cursor.png", - animation_rows = 2, - animation_cols = 9, - frame_duration = 0.6, -} -c7_textures.animations.disorder = { - path = "Art/Animations/DisorderDefault.png", - animation_rows = 1, - animation_cols = 6, - frame_duration = 0.6, -} - -function c7_textures.tech_icons.small:map_object_to_sprite(tech) - return { - path = "Art/Tech Chooser/Icons/placeholder.png", - } -end - -function c7_textures.leader_heads:map_object_to_sprite(player_or_civ) - return { - path = "Art/Advisors/placeholder_leaderhead.png", - } -end - -function c7_textures.borders:find_column(_) - return 0 -end - -return c7_textures diff --git a/C7/Textures/TextureLoader.cs b/C7/Textures/TextureLoader.cs index 63a3a9de9..93c49cae8 100644 --- a/C7/Textures/TextureLoader.cs +++ b/C7/Textures/TextureLoader.cs @@ -5,7 +5,6 @@ using System.IO; using MoonSharp.Interpreter; using Script = MoonSharp.Interpreter.Script; -using MoonSharp.Interpreter.Loaders; using C7.Map; using C7Engine.Lua; @@ -81,8 +80,7 @@ public ConfigEntry() { private static Dictionary<(string configKey, string animationName), SpriteFrames> animationCache = []; static TextureLoader() { - // Note: classes in the C7GameData namespace are already registered in - // the C7Engine.Lua.RulesEngine static constructor. + // Note: classes in the C7GameData namespace are already registered as part of GameModeLoader logic UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); @@ -92,33 +90,25 @@ static TextureLoader() { // in the class as well. UserData.RegisterType(); - // We need to register Civilization here, despite it being in - // the C7GameData namespace, since it can be passed to - // Moonsharp before LuaRulesEngine initialization, in the game - // setup screen - UserData.RegisterType(); - // We need to register the "Type" type to be able to inspect // the types of C# objects in the Lua code UserData.RegisterType(); - SetConfig(GamePaths.TextureConfigsDir, GamePaths.ClassicGraphicsConfig); + // Initialize the TextureLoader when running in the editor + // In game it is done by GlobalSingleton, but it's not accessible in the editor + if (Engine.IsEditorHint()) { + GameMode gameMode = GameMode.Load(GamePaths.GameModesDir, GamePaths.basic); + + var (script, textureConfig) = gameMode.textures; + TextureLoader.SetConfig(script, textureConfig); + } } - public static void SetConfig(string configDir, string configScript) { + public static void SetConfig(Script lua, Table textureConfig) { ClearCache(); - lua = new Script(); - lua.Options.ScriptLoader = new FileSystemScriptLoader { - ModulePaths = [ - Path.Combine(configDir, "?.lua"), - Path.Combine(configDir, "*", "?.lua") - ] - }; - - string fullScriptPath = Path.Combine(configDir, configScript); - DynValue res = lua.SafeDoFile(fullScriptPath); - textureConfig = res.Table; + TextureLoader.lua = lua; + TextureLoader.textureConfig = textureConfig; } /// Returns a texture based on the config key. diff --git a/C7/UIElements/MainMenu/MainMenu.cs b/C7/UIElements/MainMenu/MainMenu.cs index 32205d565..0186a6c87 100644 --- a/C7/UIElements/MainMenu/MainMenu.cs +++ b/C7/UIElements/MainMenu/MainMenu.cs @@ -69,13 +69,13 @@ private void DisplayTitleScreen() { ButtonContainer.Exit.Pressed += _on_Exit_pressed; ButtonContainer.ToggleGraphics.Pressed += () => { - Global.ToggleModernGraphics(); - SetToggleGraphicsText(); + Global.ToggleStandaloneMode(); + GetTree().ChangeSceneToFile("res://UIElements/MainMenu/main_menu.tscn"); }; SetToggleGraphicsText(); // We can't toggle to using civ3 graphics in standalone mode. - if (C7Settings.UseStandaloneMode()) { + if (C7Settings.UseStandaloneMode() && !ClassicGraphicsAvailable()) { ButtonContainer.ToggleGraphics.Visible = false; } @@ -100,7 +100,7 @@ private bool ClassicGraphicsAvailable() { } private void SetToggleGraphicsText() { - if (Global.ModernGraphicsActive) { + if (C7Settings.UseStandaloneMode()) { ButtonContainer.ToggleGraphics.Text = "Import Civilization III Graphics"; } else { ButtonContainer.ToggleGraphics.Text = "Use OpenCiv3 Graphics"; @@ -169,11 +169,7 @@ private void _on_SetCiv3HomeDialog_dir_selected(string path) { } private void UseStandaloneModePressed() { - if (!Global.ModernGraphicsActive) { - Global.ToggleModernGraphics(); - } - C7Settings.SetValue("locations", "useStandaloneMode", "true"); - C7Settings.SaveSettings(); + Global.ActivateGameMode(GamePaths.standalone); DisplayTitleScreen(); } } diff --git a/C7/UIElements/NewGame/PlayerSetup.cs b/C7/UIElements/NewGame/PlayerSetup.cs index 249b2f289..5f4243923 100644 --- a/C7/UIElements/NewGame/PlayerSetup.cs +++ b/C7/UIElements/NewGame/PlayerSetup.cs @@ -192,10 +192,6 @@ private void DisplaySelectedLeader() { civLabel.Text = $"{civilization.leader} of the {civilization.noun}\n({traits})"; } - private SaveGame GetSave() { - return GameModeLoader.Load(GamePaths.GameModesDir, GamePaths.GameMode); - } - private List CollectSelectedOpponents() { List opponents = []; diff --git a/C7/UIElements/NewGame/WorldSetup.cs b/C7/UIElements/NewGame/WorldSetup.cs index 26105c3f6..02fd8c0a6 100644 --- a/C7/UIElements/NewGame/WorldSetup.cs +++ b/C7/UIElements/NewGame/WorldSetup.cs @@ -1,10 +1,9 @@ using Godot; using System; using C7Engine; -using C7Engine.Lua; +using Serilog; using C7GameData; using C7GameData.Save; -using Serilog; [Tool] public partial class WorldSetup : Control { @@ -72,11 +71,10 @@ public partial class WorldSetup : Control { WorldCharacteristics.Climate clim = WorldCharacteristics.Climate.Normal; private WorldSize _worldSize = WorldSize.Generic(); + private SaveGame _saveGame; private int GameSeed => int.Parse(seedInput.Text); - private SaveGame _saveGame; - // Called when the node enters the scene tree for the first time. public override void _Ready() { background.Texture = TextureLoader.Load("world_setup.background"); @@ -268,7 +266,8 @@ public override void _Ready() { billion4Large.Visible = true; billion4.ButtonPressed = true; - _saveGame = GameModeLoader.Load(GamePaths.GameModesDir, GamePaths.GameMode); + GlobalSingleton Global = GetNode("/root/GlobalSingleton"); + _saveGame = Global.GameMode.GetSave(); InitMapSizes(); } @@ -292,7 +291,7 @@ private void InitMapSizes() { }; try { - // Dynamically create a new button for each world size in the game + // Dynamically create a new button for each world size in the game foreach (var ws in _saveGame.WorldSizes) { var worldSizeButton = new Civ3MenuButton { @@ -311,7 +310,7 @@ private void InitMapSizes() { _worldSize = ws; } - // Move random as last in the list and drop default map option and + // Move random as last in the list and drop default map option and worldSizeButtonsContainer.AddChild(randomSizeButton); } catch (Exception ex) { log.Warning(ex, "Failed to load map sizes from game mode."); diff --git a/C7/Util.cs b/C7/Util.cs index 0ef13566a..efb49d2a8 100644 --- a/C7/Util.cs +++ b/C7/Util.cs @@ -397,6 +397,5 @@ public static void ClearCaches() { TextureLoader.ClearCache(); AnimationManager.ClearCache(); PlayerTextureUtil.ClearCache(); - RulesEngine.ClearCache(); } } diff --git a/C7Engine/C7GameData/Building.cs b/C7Engine/C7GameData/Building.cs index 94219e56a..36acfcac8 100644 --- a/C7Engine/C7GameData/Building.cs +++ b/C7Engine/C7GameData/Building.cs @@ -186,7 +186,7 @@ public override string ToString() { } private void LoadLuaFunctions(GameData gameData) { - RulesEngine luaEngine = gameData.luaRulesEngine; + BehaviorEngine luaEngine = gameData.luaBehaviorEngine; foreach (var path in dataSource.productionPrerequisites) { var rule = luaEngine.ImportFunc>(path); diff --git a/C7Engine/C7GameData/GameData.cs b/C7Engine/C7GameData/GameData.cs index 9449ad60e..fc1d27738 100644 --- a/C7Engine/C7GameData/GameData.cs +++ b/C7Engine/C7GameData/GameData.cs @@ -62,7 +62,8 @@ public class GameData { public string scenarioSearchPath; //legacy from Civ3, we'll probably have a more modern format someday but this keeps legacy compatibility - internal RulesEngine luaRulesEngine = new(); + internal BehaviorEngine luaBehaviorEngine; + internal GameMode.Config gameModeConfig; // The cached trade network for all players. This is invalidated whenever // a road is built or a city is created/destroyed. @@ -190,7 +191,7 @@ public void UpdateTileOwnersOnCityDestruction(City city) { // This clears out tile ownership due to Law VII or Law VIII. // Regular tile ownership update will re-assign ownership where needed. foreach (var fringeTile in tile.GetEdgeNeighbors().Where(x => !borderTileIds.Contains(x.Id))) { - if (fringeTile.HasCity) // skip encountered cities, just in case + if (fringeTile.HasCity) // skip encountered cities, just in case continue; fringeTile.owningCity = null; @@ -237,7 +238,7 @@ public void SendMessageToUiFromLua(string message, Tile location) { public async Task DisbandUnit(MapUnit unit) { log.Information($"Player {unit.owner} disbands unit: {unit}"); - var disbandFunction = this.luaRulesEngine.ImportFunc>("gameplay.units.disband", true); + var disbandFunction = this.luaBehaviorEngine.ImportFunc>("gameplay.units.disband"); disbandFunction.Invoke(unit); await unit.animateAsync(MapUnit.AnimatedAction.DEATH, AnimationEnding.Pause); diff --git a/C7Engine/C7GameData/Inflow.cs b/C7Engine/C7GameData/Inflow.cs index 47d60be00..392cc0f1d 100644 --- a/C7Engine/C7GameData/Inflow.cs +++ b/C7Engine/C7GameData/Inflow.cs @@ -44,7 +44,7 @@ public bool CanProduce(City city, HashSet accessibleResources) { return true; } - public Inflow(SaveInflow saveInflow, RulesEngine luaRulesEngine) { + public Inflow(SaveInflow saveInflow, BehaviorEngine luaRulesEngine) { this.name = saveInflow.name; this.iconRowIndex = saveInflow.iconRowIndex; this.localYield = saveInflow.localYield.ConvertAll(y => new LocalYield(y.yieldType, luaRulesEngine, y.yieldCalculation)); @@ -71,7 +71,7 @@ public struct LocalYield { public readonly Func yieldCalculation; public LocalYield() { } - public LocalYield(InflowYield type, RulesEngine rulesEngine, string yieldCalculation = null) { + public LocalYield(InflowYield type, BehaviorEngine rulesEngine, string yieldCalculation = null) { this.yieldType = type; if (yieldCalculation != null) { this.yieldCalculation = rulesEngine.ImportFunc>(yieldCalculation); diff --git a/C7Engine/C7GameData/Save/SaveGame.cs b/C7Engine/C7GameData/Save/SaveGame.cs index 0d65ea5f2..dd3f23c43 100644 --- a/C7Engine/C7GameData/Save/SaveGame.cs +++ b/C7Engine/C7GameData/Save/SaveGame.cs @@ -7,6 +7,7 @@ using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using C7Engine; +using C7Engine.Lua; using Serilog; namespace C7GameData.Save { @@ -76,7 +77,8 @@ public static SaveGame FromGameData(GameData data) { Difficulties = data.difficulties, GameDifficulty = data.gameDifficulty, Rules = data.rules, - TerrainImprovements = data.terrainImprovements.ConvertAll(ti => ti.ToSaveTerrainImprovement()) + TerrainImprovements = data.terrainImprovements.ConvertAll(ti => ti.ToSaveTerrainImprovement()), + GameModeConfig = data.gameModeConfig, }; save.StrengthBonuses.Add(data.fortificationBonus); save.StrengthBonuses.Add(data.riverCrossingBonus); @@ -98,13 +100,11 @@ private void populateGameDataTileUnitsAndCities(GameData data) { } } - public GameData ToGameData(string luaRulesDir) { + public GameData ToGameData(BehaviorEngine behaviors) { GameData data = InitializeGameData(); - // TODO: In the future the path to the Lua script should be loaded from a save - // to allow a modded game to rely on a specific Lua ruleset. - string rulesScript = "civ3.lua"; - data.luaRulesEngine.Initialize(luaRulesDir, rulesScript); + data.luaBehaviorEngine = behaviors; + data.gameModeConfig = GameModeConfig; ConvertTerrainImprovements(data); ConvertTerraforms(data); @@ -211,7 +211,7 @@ TerrainImprovement Create(string key) { upgradesFrom = Create(save.upgradesFrom); } - var improvement = new TerrainImprovement(save, gameData.luaRulesEngine, ResolveTerrainType, upgradesFrom); + var improvement = new TerrainImprovement(save, gameData.luaBehaviorEngine, ResolveTerrainType, upgradesFrom); created[key] = improvement; return improvement; } @@ -239,7 +239,7 @@ private void ConvertTechnologies(GameData data) { } private void ConvertInflow(GameData data) { - data.Inflows = Inflows.ConvertAll(saveInflow => new Inflow(saveInflow, data.luaRulesEngine)); + data.Inflows = Inflows.ConvertAll(saveInflow => new Inflow(saveInflow, data.luaBehaviorEngine)); } private void ConvertBuildings(GameData data) { @@ -416,11 +416,20 @@ private void ConvertHealRates(GameData data) { public List Difficulties = new(); public Difficulty GameDifficulty = new(); + + public GameMode.Config GameModeConfig = new("civ3"); + public void Save(string path) { byte[] json = JsonSerializer.SerializeToUtf8Bytes(this, JsonOptions); File.WriteAllBytes(path, json); } + // Makes a deep copy of a SaveGame instance via JSON serialization + public SaveGame Clone() { + byte[] json = JsonSerializer.SerializeToUtf8Bytes(this, JsonOptions); + return JsonSerializer.Deserialize(json, JsonOptions); + } + public static SaveGame Load(string path, Func getPediaIconsPath) { SaveGame result = LoadFromJSON(File.ReadAllText(path)); diff --git a/C7Engine/C7GameData/Terraform.cs b/C7Engine/C7GameData/Terraform.cs index 74ab5ed00..406610de2 100644 --- a/C7Engine/C7GameData/Terraform.cs +++ b/C7Engine/C7GameData/Terraform.cs @@ -53,14 +53,14 @@ public Terraform(SaveTerraform saveTerraform, GameData gameData) { if (saveTerraform.Improvement != null) Improvement = gameData.terrainImprovements.Find(ti => ti.key == saveTerraform.Improvement); - SetRules(gameData.luaRulesEngine); + SetRules(gameData.luaBehaviorEngine); } public string ToString() { return Name; } - private void SetRules(RulesEngine engine) { + private void SetRules(BehaviorEngine engine) { foreach (string functionPath in dataSource.Effects) { Effect += engine.ImportFunc>(functionPath); } diff --git a/C7Engine/C7GameData/TerrainImprovement.cs b/C7Engine/C7GameData/TerrainImprovement.cs index e64d5d1fe..f4685c7a4 100644 --- a/C7Engine/C7GameData/TerrainImprovement.cs +++ b/C7Engine/C7GameData/TerrainImprovement.cs @@ -48,7 +48,7 @@ public TerrainImprovement( public TerrainImprovement( SaveTerrainImprovement save, - RulesEngine rulesEngine, + BehaviorEngine rulesEngine, Func resolveTerrainType, TerrainImprovement upgradesFrom = null ) { diff --git a/C7Engine/EntryPoints/CreateGame.cs b/C7Engine/EntryPoints/CreateGame.cs index 8ce102d16..8dbfa9ee4 100644 --- a/C7Engine/EntryPoints/CreateGame.cs +++ b/C7Engine/EntryPoints/CreateGame.cs @@ -1,19 +1,19 @@ using System; using System.Linq; using System.Threading.Tasks; +using C7Engine.Lua; using C7GameData; using C7GameData.Save; namespace C7Engine; public class CreateGameParams { - public string LuaRulesDir; public string DefaultBicPath; + public Func GameModeLoader; public Func GetPediaIconsPath = s => s; - public CreateGameParams(string LuaRulesDir, string DefaultBicPath) { - this.LuaRulesDir = LuaRulesDir; + public CreateGameParams(string DefaultBicPath) { this.DefaultBicPath = DefaultBicPath; } } @@ -27,11 +27,14 @@ public class CreateGame { **/ public static async Task createGame(string loadFilePath, CreateGameParams options) { SaveGame save = SaveManager.LoadSave(loadFilePath, options.DefaultBicPath, options.GetPediaIconsPath); - return await createGame(save, options); + + return await createGame(save, options.GameModeLoader); } - public static async Task createGame(SaveGame save, CreateGameParams options) { - GameData gameData = save.ToGameData(options.LuaRulesDir); + public static async Task createGame(SaveGame save, Func gameModeLoader) { + BehaviorEngine behaviors = gameModeLoader(save.GameModeConfig); + + GameData gameData = save.ToGameData(behaviors); EngineStorage.gameData = gameData; EngineStorage.gameData.onGameCreation(); diff --git a/C7Engine/Lua/BehaviorEngine.cs b/C7Engine/Lua/BehaviorEngine.cs new file mode 100644 index 000000000..53003bf0b --- /dev/null +++ b/C7Engine/Lua/BehaviorEngine.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using MoonSharp.Interpreter; +using Serilog; + +namespace C7Engine.Lua; + +/// A rules engine that loads game behaviors defined in a Lua script. +/// +/// Works with Lua scripts that return a nested table where the leaves +/// are functions. +/// +/// The intended use case is during the game initialization (e.g., +/// when loading a JSON save), to transform given Lua function paths +/// into C# delegates. +public class BehaviorEngine { + static ILogger log = Log.ForContext(); + Script script = new(); + Table rules; + + private Dictionary funcCache = new Dictionary(); + + public BehaviorEngine(Script script, Table rules) { + this.script = script; + this.rules = rules; + } + + /// Loads a Lua function from a rules table by a given path and + /// transforms it into C# delegate of the given generic type. + /// + /// The path to the function should be a dot-separated string + /// representing the path through the rules table + /// (e.g. "building.production_rules.must_be_coastal") + /// + /// If specified, the method will return the cached Delegate + /// associated with the functionPath string. + public T ImportFunc(string functionPath) where T : Delegate { + if (funcCache.TryGetValue(functionPath, out Delegate func)) { + return (T)(object)func; + } + + if (script == null || rules == null) + throw new InvalidOperationException("Engine is not initialized"); + if (functionPath == null) + throw new InvalidOperationException("Non-null function path expected"); + + Closure closure = ResolveFunctionPath(functionPath); + Delegate del = DelegateConverter.CreateDelegate(script, closure, typeof(T)); + + funcCache[functionPath] = del; + + return (T)(object)del; + } + + Closure ResolveFunctionPath(string functionPath) { + string[] parts = functionPath.Split('.'); + Table current = rules; + + for (int i = 0; i < parts.Length; i++) { + string part = parts[i]; + DynValue value = current.Get(part); + + if (value.Type == DataType.Function && i == parts.Length - 1) + return value.Function; + + if (value.Type == DataType.Table) + current = value.Table; + else + throw new ArgumentException($"Unexpected type at '{part}': '{value.Type}'"); + } + + throw new ArgumentException($"Function path '{functionPath}' did not resolve to a function."); + } +} diff --git a/C7Engine/Lua/GameMode.cs b/C7Engine/Lua/GameMode.cs new file mode 100644 index 000000000..fe6a871c5 --- /dev/null +++ b/C7Engine/Lua/GameMode.cs @@ -0,0 +1,243 @@ +using System; +using System.IO; +using System.Collections.Generic; +using Serilog; +using C7GameData.Save; +using MoonSharp.Interpreter; +using System.Reflection; +using System.Linq; +using MoonSharp.Interpreter.Loaders; + +namespace C7Engine.Lua; + +public class GameMode { + public class Config { + // The directory with the base scenario definition + public string baseModeDir; + + // Directories with addon definitions. + public List addonPaths = []; + + public Config(string baseModeDir, List addonPaths = null) { + this.baseModeDir = baseModeDir; + this.addonPaths = addonPaths ?? []; + } + } + + internal SaveGame ruleset; + public BehaviorEngine behaviors; + public (Script, Table) textures; + + // Returns a deep copy of the ruleset + // + // We don't return the ruleset directly, since SaveGame is + // modified during game creation (see GameSetup). By returning a + // copy we make it possible to reuse the ruleset for creating + // other games + public SaveGame GetSave() { + return ruleset.Clone(); + } + + // gameModesDir is a directory where game mode definitions are located. + // It is used as a prefix to paths specified in GameMode.Config + public static GameMode Load(string gameModesDir, Config config) { + return new GameModeLoader(gameModesDir, config).Load(); + + } +} + +// Loads OpenCiv3 scenario definitions +internal class GameModeLoader { + enum ScriptType { + Textures, + Ruleset, + Behaviors, + } + + private static ILogger log = Log.ForContext(); + + Script lua; + JsonConverter converter; + GameMode.Config config; + string gameModesDir; + + public GameModeLoader(string gameModesDir, GameMode.Config config) { + lua = ScriptInitializer.Initialize(); + converter = new(lua); + + this.config = config; + this.gameModesDir = gameModesDir; + } + + public GameMode Load() { + var LoadWithAddons = (ScriptType scriptType) => { + return LoadAddons(LoadBase(scriptType), scriptType); + }; + + Table behaviors = LoadWithAddons(ScriptType.Behaviors).Table; + Table textures = LoadWithAddons(ScriptType.Textures).Table; + + return new() { + ruleset = LoadRuleset(), + behaviors = new(lua, behaviors), + textures = (lua, textures), + }; + } + + private SaveGame LoadRuleset() { + // Special case: we allow to store the base ruleset both as Lua and JSON + DynValue ruleset; + + string jsonPath = Path.Combine(gameModesDir, config.baseModeDir, "ruleset.json"); + if (File.Exists(jsonPath)) { + ruleset = converter.Decode(File.ReadAllText(jsonPath)); + } else { + ruleset = LoadBase(ScriptType.Ruleset); + } + + ruleset = LoadAddons(ruleset, ScriptType.Ruleset); + + // Convert final Lua table to JSON for deserialization + string json = converter.Encode(ruleset); + + var saveGame = SaveGame.LoadFromJSON(json); + saveGame.GameModeConfig = config; + + return saveGame; + } + + private DynValue LoadBase(ScriptType scriptType) { + string fullBaseModePath = GetScriptPath(config.baseModeDir, scriptType); + log.Information("Loading base game mode file: {baseModePath}", fullBaseModePath); + + return LoadScript(config.baseModeDir, fullBaseModePath); + } + + private DynValue LoadAddons(DynValue baseTable, ScriptType scriptType) { + // Load base scenario from JSON or Lua file + DynValue current = baseTable; + + // Process through addon scripts + foreach (string addonPath in config.addonPaths) { + string scriptPath = GetScriptPath(addonPath, scriptType); + + if (!File.Exists(scriptPath)) { + continue; + } + + log.Information("Loading addon: {scriptPath}", scriptPath); + + DynValue addon = LoadScript(addonPath, scriptPath); + + if (addon.Type != DataType.Function) + throw new InvalidOperationException( + $"Addon '{scriptPath}' must return a function" + ); + + // Each addon function modifies and returns the scenario data + current = lua.SafeCall(addon.Function, current); + } + + return current; + } + + private string GetScriptPath(string addonDir, ScriptType scriptType) { + string scriptFile = scriptType switch { + ScriptType.Textures => "textures.lua", + ScriptType.Behaviors => "behaviors.lua", + ScriptType.Ruleset => "ruleset.lua", + _ => throw new InvalidOperationException("Unknown script type"), + }; + return Path.Combine(gameModesDir, addonDir, scriptFile); + } + + private DynValue LoadScript(string addonDir, string scriptPath) { + SetLoaderPath(addonDir); + + DynValue script = lua.SafeDoFile(scriptPath); + + UnsetLoaderPath(); + + return script; + } + + private void SetLoaderPath(string addonDir) { + lua.Options.ScriptLoader = new FileSystemScriptLoader { + ModulePaths = [ + Path.Combine(gameModesDir, addonDir, "?.lua"), + Path.Combine(gameModesDir, addonDir, "*", "?.lua") + ] + }; + } + + private void UnsetLoaderPath() { + lua.Options.ScriptLoader = new FileSystemScriptLoader { + ModulePaths = [] + }; + } +} + +/// When initializing the Lua state, this class registers all public +/// types in the C7GameData namespace as Moonsharp userdata, which +/// exposes them to the scripting engine. +/// +/// The class registers the following Lua globals: +/// - GAME_DATA(): Function returning the current game data instance. +/// - ENUMS: Table containing definitions of public enums from the C7GameData namespace. +/// Note that nested enums are registered under a name concatenating the outer class and the enum names (e.g., Tile_YieldType) +internal class ScriptInitializer { + static ILogger log = Log.ForContext(); + + static ScriptInitializer() { + RegisterTypes(); + } + + static public Script Initialize() { + Script script = new(); + RegisterEnums(script); + RegisterGlobals(script); + return script; + } + + static void RegisterTypes() { + var types = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => + (t.IsPublic || t.IsNestedPublic) + && t.Namespace == "C7GameData"); + + foreach (var type in types) { + log.Debug("Registering type: {typeName}", type.FullName); + UserData.RegisterType(type); + } + } + + static void RegisterEnums(Script script) { + var enumTypes = Assembly.GetExecutingAssembly() + .GetTypes() + .Where(t => + t.IsEnum && + (t.IsPublic || t.IsNestedPublic) && + t.Namespace == "C7GameData"); + + Table enumTable = new(script); + + foreach (var enumType in enumTypes) { + // For nested enums, concatenate the outer class name with the enum name + string registrationName = enumType.DeclaringType != null + ? $"{enumType.DeclaringType.Name}_{enumType.Name}" // e.g., Tile_YieldType + : enumType.Name; + + log.Debug("Registering enum: {EnumType} as {RegistrationName}", enumType.FullName, registrationName); + + var enumUserData = UserData.CreateStatic(enumType); + enumTable[registrationName] = enumUserData; + } + + script.Globals["ENUMS"] = enumTable; + } + + static void RegisterGlobals(Script script) { + script.Globals["GAME_DATA"] = () => DynValue.FromObject(script, EngineStorage.gameData); + } +} diff --git a/C7Engine/Lua/GameModeLoader.cs b/C7Engine/Lua/GameModeLoader.cs deleted file mode 100644 index 6bd8e8ce1..000000000 --- a/C7Engine/Lua/GameModeLoader.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using Serilog; -using C7GameData.Save; -using MoonSharp.Interpreter; - -namespace C7Engine.Lua; - -public class GameModeConfig { - // The base scenario definition file. Should be a JSON or a Lua - // script returning a table - public string baseModePath; - - // Optional addon Lua scripts. Each script should return a - // function that modifies the scenario data and returns it. - public List addonPaths = []; - - public GameModeConfig(string baseModePath, List addonPaths = null) { - this.baseModePath = baseModePath; - this.addonPaths = addonPaths ?? []; - } -} - -// Loads OpenCiv3 scenario definitions -public class GameModeLoader { - private static ILogger log = Log.ForContext(); - - public static SaveGame Load(string gameModesDir, GameModeConfig config) { - Script lua = new(); - JsonConverter converter = new (lua); - - string baseModePath = config.baseModePath; - - string fullBaseModePath = Path.Combine(gameModesDir, baseModePath); - log.Information("Loading base game mode file: {baseModePath}", fullBaseModePath); - - // Load base scenario from JSON or Lua file - DynValue current = Path.GetExtension(fullBaseModePath).ToUpper() switch { - ".JSON" => converter.Decode(File.ReadAllText(fullBaseModePath)), - ".LUA" => lua.SafeDoFile(fullBaseModePath), - _ => throw new InvalidOperationException("Invalid base mode file format") - }; - - // Process through addon scripts - foreach (string addonPath in config.addonPaths) { - log.Information("Loading addon: {addonPath}", addonPath); - - DynValue addon = lua.SafeDoFile(Path.Combine(gameModesDir, addonPath)); - - if (addon.Type != DataType.Function) - throw new InvalidOperationException( - $"Addon '{addonPath}' must return a function" - ); - - // Each addon function modifies and returns the scenario data - current = lua.SafeCall(addon.Function, current); - } - - // Convert final Lua table to JSON for deserialization - string json = converter.Encode(current); - - return SaveGame.LoadFromJSON(json); - } -} diff --git a/C7Engine/Lua/RulesEngine.cs b/C7Engine/Lua/RulesEngine.cs deleted file mode 100644 index 358276a29..000000000 --- a/C7Engine/Lua/RulesEngine.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Linq; -using MoonSharp.Interpreter; -using Serilog; -using System.IO; -using MoonSharp.Interpreter.Loaders; - -namespace C7Engine.Lua; - -/// A rules engine that loads game behaviors defined in a Lua script. -/// -/// Works with Lua scripts that return a nested table where the leaves -/// are functions. -/// -/// The intended use case is during the game initialization (e.g., -/// when loading a JSON save), to transform given Lua function paths -/// into C# delegates. -/// -/// When initializing the Lua state, this class registers all public -/// types in the C7GameData namespace as Moonsharp userdata, which -/// exposes them to the scripting engine. -/// -/// The engine registers the following Lua globals: -/// - GAME_DATA(): Function returning the current game data instance. -/// - ENUMS: Table containing definitions of public enums from the C7GameData namespace. -/// Note that nested enums are registered under a name concatenating the outer class and the enum names (e.g., Tile_YieldType) -public class RulesEngine { - static ILogger log = Log.ForContext(); - Script script = new(); - Table rules; - - private static Dictionary funcCache = new Dictionary(); - - static RulesEngine() { - RegisterTypes(); - } - - /// Initializes the Lua state. - /// - /// Accepts the path to the directory containing Lua scripts, - /// and the name of the script that should return a rules table. - public void Initialize(string luaRulesDir, string rulesScript) { - if (rules != null) - throw new InvalidOperationException("Engine already initialized"); - - RegisterEnums(); - RegisterGlobals(); - - script.Options.ScriptLoader = new FileSystemScriptLoader { - ModulePaths = [ - Path.Combine(luaRulesDir, "?.lua"), - Path.Combine(luaRulesDir, "*", "?.lua") - ] - }; - - string fullScriptPath = Path.Combine(luaRulesDir, rulesScript); - log.Information("Loading Lua rules from file: {filePath}", fullScriptPath); - - DynValue res = script.SafeDoFile(fullScriptPath); - rules = res.Table; - } - - /// Loads a Lua function from a rules table by a given path and - /// transforms it into C# delegate of the given generic type. - /// - /// The path to the function should be a dot-separated string - /// representing the path through the rules table - /// (e.g. "building.production_rules.must_be_coastal") - /// - /// If specified, the method will return the cached Delegate - /// associated with the functionPath string. - public T ImportFunc(string functionPath, bool cache = false) where T : Delegate { - if (cache && funcCache.TryGetValue(functionPath, out Delegate func)) { - return (T)(object)func; - } - - if (script == null || rules == null) - throw new InvalidOperationException("Engine is not initialized"); - if (functionPath == null) - throw new InvalidOperationException("Non-null function path expected"); - - Closure closure = ResolveFunctionPath(functionPath); - Delegate del = DelegateConverter.CreateDelegate(script, closure, typeof(T)); - - if (cache) - funcCache[functionPath] = del; - - return (T)(object)del; - } - - Closure ResolveFunctionPath(string functionPath) { - string[] parts = functionPath.Split('.'); - Table current = rules; - - for (int i = 0; i < parts.Length; i++) { - string part = parts[i]; - DynValue value = current.Get(part); - - if (value.Type == DataType.Function && i == parts.Length - 1) - return value.Function; - - if (value.Type == DataType.Table) - current = value.Table; - else - throw new ArgumentException($"Unexpected type at '{part}': '{value.Type}'"); - } - - throw new ArgumentException($"Function path '{functionPath}' did not resolve to a function."); - } - - static void RegisterTypes() { - var types = Assembly.GetExecutingAssembly() - .GetTypes() - .Where(t => - (t.IsPublic || t.IsNestedPublic) - && t.Namespace == "C7GameData"); - - foreach (var type in types) { - log.Debug("Registering type: {typeName}", type.FullName); - UserData.RegisterType(type); - } - } - - void RegisterEnums() { - var enumTypes = Assembly.GetExecutingAssembly() - .GetTypes() - .Where(t => - t.IsEnum && - (t.IsPublic || t.IsNestedPublic) && - t.Namespace == "C7GameData"); - - Table enumTable = new(script); - - foreach (var enumType in enumTypes) { - // For nested enums, concatenate the outer class name with the enum name - string registrationName = enumType.DeclaringType != null - ? $"{enumType.DeclaringType.Name}_{enumType.Name}" // e.g., Tile_YieldType - : enumType.Name; - - log.Debug("Registering enum: {EnumType} as {RegistrationName}", enumType.FullName, registrationName); - - var enumUserData = UserData.CreateStatic(enumType); - enumTable[registrationName] = enumUserData; - } - - script.Globals["ENUMS"] = enumTable; - } - - void RegisterGlobals() { - script.Globals["GAME_DATA"] = () => DynValue.FromObject(script, EngineStorage.gameData); - } - - public static void ClearCache() { - funcCache.Clear(); - } -} diff --git a/EngineTests/GameData/PlayerRelationshipTest.cs b/EngineTests/GameData/PlayerRelationshipTest.cs index 5ecffaf35..4550d80aa 100644 --- a/EngineTests/GameData/PlayerRelationshipTest.cs +++ b/EngineTests/GameData/PlayerRelationshipTest.cs @@ -10,7 +10,7 @@ public class PlayerRelationshipTest : IClassFixture { C7GameData.GameData gameData; public PlayerRelationshipTest(SaveGameFixture fixture) { - gameData = fixture.saveGame.ToGameData(PathUtils.luaRulesDir); + gameData = fixture.saveGame.ToGameData(fixture.behaviors); EngineStorage.InitializeGameDataForTests(gameData); } diff --git a/EngineTests/GameData/SaveTest.cs b/EngineTests/GameData/SaveTest.cs index b84b845e9..04b6104ef 100644 --- a/EngineTests/GameData/SaveTest.cs +++ b/EngineTests/GameData/SaveTest.cs @@ -41,26 +41,33 @@ public static string testDirectory { } } - public static string luaRulesDir => getBasePath("../C7/Lua/rules"); - public static string gameModesDir => getBasePath("../C7/Lua/game_modes/"); + public static string gameModesDir => getBasePath("../C7/Lua/"); } public class SaveGameFixture : IDisposable { internal SaveGame saveGame; internal SaveGame standaloneSaveGame; + internal BehaviorEngine behaviors; const int TestSeed = 123456; public SaveGameFixture() { - GameModeConfig basic = new("base-ruleset.json"); - GameModeConfig standalone = new("base-ruleset.json", ["standalone.lua"]); + GameMode.Config basic = new("civ3"); + GameMode.Config standalone = new("civ3", ["standalone"]); saveGame = LoadSave(basic); standaloneSaveGame = LoadSave(standalone); + + // Standalone and basic modes should use the same set of behaviors + behaviors = LoadGameMode(basic).behaviors; + } + + private static GameMode LoadGameMode(GameMode.Config gameModeConfig) { + return GameMode.Load(PathUtils.gameModesDir, gameModeConfig); } - private static SaveGame LoadSave(GameModeConfig gameModeConfig) { - SaveGame save = GameModeLoader.Load(PathUtils.gameModesDir, gameModeConfig); + private static SaveGame LoadSave(GameMode.Config gameModeConfig) { + SaveGame save = LoadGameMode(gameModeConfig).GetSave(); WorldSize worldSize = new() { width = 100, @@ -176,28 +183,26 @@ private void WaitForStartTurnMessage() { continue; default: throw new Exception($"{msg}"); - continue; } } } } private Player CreateHeadlessGame(string path, string biqPath, Func getPediaIconsPath) { - CreateGameParams options = new(PathUtils.luaRulesDir, biqPath) { - GetPediaIconsPath = getPediaIconsPath + CreateGameParams options = new(biqPath) { + GetPediaIconsPath = getPediaIconsPath, + GameModeLoader = (_) => { return fixture.behaviors; }, }; return CreateGame.createGame(path, options).Result; } private Player CreateHeadlessGame(SaveGame game) { - CreateGameParams options = new(PathUtils.luaRulesDir, ""); - - return CreateGame.createGame(game, options).Result; + return CreateGame.createGame(game, (_) => { return fixture.behaviors; }).Result; } private C7GameData.GameData ToGameData(SaveGame game) { - return game.ToGameData(PathUtils.luaRulesDir); + return game.ToGameData(fixture.behaviors); } private void CheckAiInvariants() {