diff --git a/C7/Game.cs b/C7/Game.cs index 77a56514..fd01ed9b 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -7,6 +7,7 @@ using C7Engine.Pathing; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; public partial class Game : Node { [Signal] public delegate void TurnEndedEventHandler(); @@ -94,44 +95,7 @@ public override async void _Ready() { Global = GetNode("/root/GlobalSingleton"); try { - // Ensure we clear out our image caches, as scenarios and games will - // use the same filenames but have different content for them. - Util.ClearCaches(); - - controller = await CreateGame.createGame( - Global.LoadGamePath, - GamePaths.LuaRulesDir, - GamePaths.DefaultBicPath, - (scenarioSearchPath) => { - // When the game loading logic tries to load the PediaIcons file, set the - // scenario search path and then use our Civ3MediaPath searching logic to - // find the correct version of the file. - // - // This weird bit of indirection is necessary because the C7GameData project - // can't depend on the C7 project without a circular dependency, and the - // search logic has a Godot dependency, so it doesn't make sense to live - // in the C7GameData project. - // - // This also helps ensure the weird stateful behavior of the Util class works, - // since the search path/mod path is a static global variable - we want to - // be sure it is always set properly, so doing it during game creation - // is reasonable. - Util.setModPath(scenarioSearchPath); - log.Debug("RelativeModPath ", scenarioSearchPath); - return Util.Civ3MediaPath("Text/PediaIcons.txt"); - }); // Spawns engine thread - - Global.ResetLoadGamePath(); - - InitializeMapView(); - - log.Information("Now in game!"); - - loadTimer.Stop(); - TimeSpan stopwatchElapsed = loadTimer.Elapsed; - log.Information("Game scene load time: " + Convert.ToInt32(stopwatchElapsed.TotalMilliseconds) + " ms"); - - EmitSignal(SignalName.GameInitialized); + await LoadGame(); } catch (Exception ex) { errorOnLoad = true; string message = ex.Message; @@ -145,6 +109,54 @@ 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) + { + GetPediaIconsPath = (scenarioSearchPath) => { + // When the game loading logic tries to load the PediaIcons file, set the + // scenario search path and then use our Civ3MediaPath searching logic to + // find the correct version of the file. + // + // This weird bit of indirection is necessary because the C7GameData project + // can't depend on the C7 project without a circular dependency, and the + // search logic has a Godot dependency, so it doesn't make sense to live + // in the C7GameData project. + // + // This also helps ensure the weird stateful behavior of the Util class works, + // since the search path/mod path is a static global variable - we want to + // be sure it is always set properly, so doing it during game creation + // is reasonable. + Util.setModPath(scenarioSearchPath); + log.Debug("RelativeModPath ", scenarioSearchPath); + return Util.Civ3MediaPath("Text/PediaIcons.txt"); + } + }; + + if (Global.SaveGame != null) { + controller = await CreateGame.createGame(Global.SaveGame, options); + } else if (Global.LoadGamePath != null) { + controller = await CreateGame.createGame(Global.LoadGamePath, options); + } else { + throw new InvalidOperationException("Save data was not set"); + } + + Global.ResetLoadGameFields(); + + InitializeMapView(); + + log.Information("Now in game!"); + + loadTimer.Stop(); + TimeSpan stopwatchElapsed = loadTimer.Elapsed; + log.Information("Game scene load time: " + Convert.ToInt32(stopwatchElapsed.TotalMilliseconds) + " ms"); + + EmitSignal(SignalName.GameInitialized); + } + private void InitializeMapView() { EngineStorage.ReadGameData((GameData gameData) => { GameMap map = gameData.map; diff --git a/C7/GamePaths.cs b/C7/GamePaths.cs index 27d576f7..9bf9712a 100644 --- a/C7/GamePaths.cs +++ b/C7/GamePaths.cs @@ -11,11 +11,6 @@ public static string DefaultGamePath { public const string ModernGraphicsConfig = "c7.lua"; public const string ClassicGraphicsConfig = "civ3.lua"; - // The file where a generated map is saved, until we get more advanced ways - // to generate new games. - // TODO: improve this. - public const string DefaultGeneratedGamePath = "./Text/c7-autosave-turn-0.json"; - // 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 274bb662..1472128b 100644 --- a/C7/GlobalSingleton.cs +++ b/C7/GlobalSingleton.cs @@ -1,6 +1,6 @@ using Godot; -using QueryCiv3; using C7Engine; +using C7GameData.Save; /**** Need to pass values from one scene to another, particularly when loading @@ -13,6 +13,9 @@ public partial class GlobalSingleton : Node { // and back to game public string LoadGamePath; + // Generated game data used when starting a new game + public SaveGame SaveGame; + public bool ModernGraphicsActive { get; private set; } public GlobalSingleton() { @@ -26,8 +29,9 @@ public GlobalSingleton() { // setup screen, which is what actually kicks off the world generation. public WorldCharacteristics WorldCharacteristics; - public void ResetLoadGamePath() { + public void ResetLoadGameFields() { LoadGamePath = GamePaths.DefaultGamePath; + SaveGame = null; } public void ToggleModernGraphics() { diff --git a/C7/Text/c7-static-map-save-standalone.json b/C7/Text/c7-static-map-save-standalone.json index 1c386e57..0e7cab4f 100644 --- a/C7/Text/c7-static-map-save-standalone.json +++ b/C7/Text/c7-static-map-save-standalone.json @@ -71318,7 +71318,6 @@ { "id": "player-1", "colorIndex": 0, - "barbarian": true, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71337,7 +71336,6 @@ { "id": "player-2", "colorIndex": 2, - "barbarian": false, "human": true, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71415,7 +71413,6 @@ { "id": "player-3", "colorIndex": 5, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71493,7 +71490,6 @@ { "id": "player-4", "colorIndex": 4, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71567,7 +71563,6 @@ { "id": "player-5", "colorIndex": 7, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71645,7 +71640,6 @@ { "id": "player-6", "colorIndex": 10, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71727,7 +71721,6 @@ { "id": "player-7", "colorIndex": 6, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71805,7 +71798,6 @@ { "id": "player-8", "colorIndex": 8, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -71998,7 +71990,8 @@ "Toltec", "Kushite", "Barbarian" - ] + ], + "isBarbarian": true }, { "name": "Rome", @@ -72060,7 +72053,8 @@ "traits": [ "militaristic", "commercial" - ] + ], + "isBarbarian": false }, { "name": "Egypt", @@ -72107,7 +72101,8 @@ "traits": [ "religious", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Greece", @@ -72154,7 +72149,8 @@ "traits": [ "commercial", "scientific" - ] + ], + "isBarbarian": false }, { "name": "Babylon", @@ -72199,7 +72195,8 @@ "traits": [ "scientific", "religious" - ] + ], + "isBarbarian": false }, { "name": "Germany", @@ -72233,7 +72230,8 @@ "traits": [ "militaristic", "scientific" - ] + ], + "isBarbarian": false }, { "name": "Russia", @@ -72290,7 +72288,8 @@ "traits": [ "expansionist", "scientific" - ] + ], + "isBarbarian": false }, { "name": "China", @@ -72326,7 +72325,8 @@ "traits": [ "militaristic", "industrious" - ] + ], + "isBarbarian": false }, { "name": "America", @@ -72384,7 +72384,8 @@ "traits": [ "expansionist", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Japan", @@ -72428,7 +72429,8 @@ "traits": [ "militaristic", "religious" - ] + ], + "isBarbarian": false }, { "name": "France", @@ -72467,7 +72469,8 @@ "traits": [ "commercial", "industrious" - ] + ], + "isBarbarian": false }, { "name": "India", @@ -72502,7 +72505,8 @@ "traits": [ "commercial", "religious" - ] + ], + "isBarbarian": false }, { "name": "Persia", @@ -72549,7 +72553,8 @@ "traits": [ "scientific", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Aztecs", @@ -72603,7 +72608,8 @@ "traits": [ "militaristic", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Zululand", @@ -72637,7 +72643,8 @@ "traits": [ "militaristic", "expansionist" - ] + ], + "isBarbarian": false }, { "name": "Iroquois", @@ -72684,7 +72691,8 @@ "traits": [ "commercial", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "England", @@ -72731,7 +72739,8 @@ "traits": [ "commercial", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Mongols", @@ -72779,7 +72788,8 @@ "traits": [ "militaristic", "expansionist" - ] + ], + "isBarbarian": false }, { "name": "Spain", @@ -72828,7 +72838,8 @@ "traits": [ "religious", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Scandinavia", @@ -72879,7 +72890,8 @@ "traits": [ "militaristic", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Ottomans", @@ -72926,7 +72938,8 @@ "traits": [ "scientific", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Celts", @@ -72974,7 +72987,8 @@ "traits": [ "religious", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Arabia", @@ -73020,7 +73034,8 @@ "traits": [ "expansionist", "religious" - ] + ], + "isBarbarian": false }, { "name": "Carthage", @@ -73068,7 +73083,8 @@ "traits": [ "industrious", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Korea", @@ -73116,7 +73132,8 @@ "traits": [ "commercial", "scientific" - ] + ], + "isBarbarian": false }, { "name": "Sumeria", @@ -73159,7 +73176,8 @@ "traits": [ "scientific", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Hittites", @@ -73202,7 +73220,8 @@ "traits": [ "commercial", "expansionist" - ] + ], + "isBarbarian": false }, { "name": "Netherlands", @@ -73245,7 +73264,8 @@ "traits": [ "agricultural", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Portugal", @@ -73299,7 +73319,8 @@ "traits": [ "expansionist", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Byzantines", @@ -73342,7 +73363,8 @@ "traits": [ "scientific", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Inca", @@ -73385,7 +73407,8 @@ "traits": [ "expansionist", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Maya", @@ -73429,7 +73452,8 @@ "traits": [ "industrious", "agricultural" - ] + ], + "isBarbarian": false } ], "strengthBonuses": [ diff --git a/C7/Text/c7-static-map-save.json b/C7/Text/c7-static-map-save.json index 310b27c7..879ba77d 100644 --- a/C7/Text/c7-static-map-save.json +++ b/C7/Text/c7-static-map-save.json @@ -73891,7 +73891,6 @@ { "id": "player-1", "colorIndex": 0, - "barbarian": true, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -73910,7 +73909,6 @@ { "id": "player-2", "colorIndex": 2, - "barbarian": false, "human": true, "hasPlayedCurrentTurn": false, "defeated": false, @@ -73988,7 +73986,6 @@ { "id": "player-3", "colorIndex": 5, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -74066,7 +74063,6 @@ { "id": "player-4", "colorIndex": 4, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -74140,7 +74136,6 @@ { "id": "player-5", "colorIndex": 7, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -74218,7 +74213,6 @@ { "id": "player-6", "colorIndex": 10, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -74300,7 +74294,6 @@ { "id": "player-7", "colorIndex": 6, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -74378,7 +74371,6 @@ { "id": "player-8", "colorIndex": 8, - "barbarian": false, "human": false, "hasPlayedCurrentTurn": false, "defeated": false, @@ -74571,7 +74563,8 @@ "Toltec", "Kushite", "Barbarian" - ] + ], + "isBarbarian": true }, { "name": "Rome", @@ -74633,7 +74626,8 @@ "traits": [ "militaristic", "commercial" - ] + ], + "isBarbarian": false }, { "name": "Egypt", @@ -74680,7 +74674,8 @@ "traits": [ "religious", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Greece", @@ -74727,7 +74722,8 @@ "traits": [ "commercial", "scientific" - ] + ], + "isBarbarian": false }, { "name": "Babylon", @@ -74772,7 +74768,8 @@ "traits": [ "scientific", "religious" - ] + ], + "isBarbarian": false }, { "name": "Germany", @@ -74806,7 +74803,8 @@ "traits": [ "militaristic", "scientific" - ] + ], + "isBarbarian": false }, { "name": "Russia", @@ -74863,7 +74861,8 @@ "traits": [ "expansionist", "scientific" - ] + ], + "isBarbarian": false }, { "name": "China", @@ -74899,7 +74898,8 @@ "traits": [ "militaristic", "industrious" - ] + ], + "isBarbarian": false }, { "name": "America", @@ -74957,7 +74957,8 @@ "traits": [ "expansionist", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Japan", @@ -75001,7 +75002,8 @@ "traits": [ "militaristic", "religious" - ] + ], + "isBarbarian": false }, { "name": "France", @@ -75040,7 +75042,8 @@ "traits": [ "commercial", "industrious" - ] + ], + "isBarbarian": false }, { "name": "India", @@ -75075,7 +75078,8 @@ "traits": [ "commercial", "religious" - ] + ], + "isBarbarian": false }, { "name": "Persia", @@ -75122,7 +75126,8 @@ "traits": [ "scientific", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Aztecs", @@ -75176,7 +75181,8 @@ "traits": [ "militaristic", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Zululand", @@ -75210,7 +75216,8 @@ "traits": [ "militaristic", "expansionist" - ] + ], + "isBarbarian": false }, { "name": "Iroquois", @@ -75257,7 +75264,8 @@ "traits": [ "commercial", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "England", @@ -75304,7 +75312,8 @@ "traits": [ "commercial", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Mongols", @@ -75352,7 +75361,8 @@ "traits": [ "militaristic", "expansionist" - ] + ], + "isBarbarian": false }, { "name": "Spain", @@ -75401,7 +75411,8 @@ "traits": [ "religious", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Scandinavia", @@ -75452,7 +75463,8 @@ "traits": [ "militaristic", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Ottomans", @@ -75499,7 +75511,8 @@ "traits": [ "scientific", "industrious" - ] + ], + "isBarbarian": false }, { "name": "Celts", @@ -75547,7 +75560,8 @@ "traits": [ "religious", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Arabia", @@ -75593,7 +75607,8 @@ "traits": [ "expansionist", "religious" - ] + ], + "isBarbarian": false }, { "name": "Carthage", @@ -75641,7 +75656,8 @@ "traits": [ "industrious", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Korea", @@ -75689,7 +75705,8 @@ "traits": [ "commercial", "scientific" - ] + ], + "isBarbarian": false }, { "name": "Sumeria", @@ -75732,7 +75749,8 @@ "traits": [ "scientific", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Hittites", @@ -75775,7 +75793,8 @@ "traits": [ "commercial", "expansionist" - ] + ], + "isBarbarian": false }, { "name": "Netherlands", @@ -75818,7 +75837,8 @@ "traits": [ "agricultural", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Portugal", @@ -75872,7 +75892,8 @@ "traits": [ "expansionist", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Byzantines", @@ -75915,7 +75936,8 @@ "traits": [ "scientific", "seafaring" - ] + ], + "isBarbarian": false }, { "name": "Inca", @@ -75958,7 +75980,8 @@ "traits": [ "expansionist", "agricultural" - ] + ], + "isBarbarian": false }, { "name": "Maya", @@ -76002,7 +76025,8 @@ "traits": [ "industrious", "agricultural" - ] + ], + "isBarbarian": false } ], "strengthBonuses": [ diff --git a/C7/UIElements/MainMenu/MainMenu.cs b/C7/UIElements/MainMenu/MainMenu.cs index 247b594c..021816f5 100644 --- a/C7/UIElements/MainMenu/MainMenu.cs +++ b/C7/UIElements/MainMenu/MainMenu.cs @@ -37,7 +37,7 @@ public override void _Ready() { private void DisplayTitleScreen() { // To pass data between scenes, putting path string in a global singleton and reading it later in createGame Global = GetNode("/root/GlobalSingleton"); - Global.ResetLoadGamePath(); + Global.ResetLoadGameFields(); LoadDialog.SetDirectoryForLoading(@"Conquests/Saves"); LoadScenarioDialog.SetDirectoryForLoading(@"Conquests/Scenarios"); diff --git a/C7/UIElements/NewGame/PlayerSetup.cs b/C7/UIElements/NewGame/PlayerSetup.cs index 6310c8c6..b0018c36 100644 --- a/C7/UIElements/NewGame/PlayerSetup.cs +++ b/C7/UIElements/NewGame/PlayerSetup.cs @@ -14,8 +14,6 @@ public partial class PlayerSetup : Control { [Export] TextureRect background; [Export] GridContainer playerListContainer; - List civilizations = new(); - Civilization civilization = null; ButtonGroup playerListButtonGroup = new(); TextureRect leaderHead = new(); [Export] Label civLabel; @@ -25,7 +23,6 @@ public partial class PlayerSetup : Control { [Export] GridContainer difficultyContainer; - Difficulty difficulty = null; ButtonGroup difficultyButtonGroup = new(); [Export] TextureButton confirm; @@ -35,19 +32,21 @@ public partial class PlayerSetup : Control { ID.Factory ids; + SaveGame save; + GameSetup gameSetup = new(); + // TODO: read this from the rules based on the world size const int NUM_OPPONENTS = 7; // Called when the node enters the scene tree for the first time. public override void _Ready() { - SaveGame save = GetSave(); + save = GetSave(); // Set up buttons for the civs the player can play as. - civilizations = save.Civilizations; playerListContainer.Columns = (int)Math.Ceiling(save.Civilizations.Count / 12.0); string initiallySelectedCiv = save.Civilizations.Any(x => x.name == "Netherlands") ? "Netherlands" : save.Civilizations[1].name; foreach (Civilization civ in save.Civilizations) { - if (civ.name == "A Barbarian Chiefdom") { + if (civ.isBarbarian) { continue; } @@ -59,7 +58,7 @@ public override void _Ready() { ToggleMode = true, }; button.Pressed += () => { - this.civilization = civ; + gameSetup.civilization = civ; UpdateOpponentSelectors(); DisplaySelectedLeader(); }; @@ -67,7 +66,7 @@ public override void _Ready() { if (civ.name == initiallySelectedCiv) { button.ButtonPressed = true; - this.civilization = civ; + gameSetup.civilization = civ; } } background.AddChild(leaderHead); @@ -88,11 +87,11 @@ public override void _Ready() { ButtonGroup = difficultyButtonGroup, ToggleMode = true, }; - button.Pressed += () => { this.difficulty = difficulty; }; + button.Pressed += () => { gameSetup.difficulty = difficulty; }; container.AddChild(button); if (difficulty.Name == initiallySelectedDifficulty) { button.ButtonPressed = true; - this.difficulty = difficulty; + gameSetup.difficulty = difficulty; } container.CustomMinimumSize = new Vector2(843.0f / difficultyContainer.Columns, 0); } @@ -138,8 +137,8 @@ private void AddOpponentSelectors() { container.CustomMinimumSize = new Vector2(312.0f / opponentListContainer.Columns, 315.0f / NUM_OPPONENTS); optionButton.CustomMinimumSize = new Vector2(290.0f / opponentListContainer.Columns, optionButton.CustomMinimumSize.Y); - foreach (Civilization civ in civilizations) { - if (civ.name == "A Barbarian Chiefdom") { + foreach (Civilization civ in save.Civilizations) { + if (civ.isBarbarian) { continue; } optionButton.AddItem(civ.name); @@ -162,7 +161,7 @@ private void AddOpponentSelectors() { private void UpdateOpponentSelectors() { HashSet civsTaken = new(); - civsTaken.Add(civilization.name); + civsTaken.Add(gameSetup.civilization.name); foreach (OptionButton ob in opponentSelectors) { string selection = ob.GetItemText(ob.Selected); @@ -183,6 +182,8 @@ private void UpdateOpponentSelectors() { } private void DisplaySelectedLeader() { + Civilization civilization = gameSetup.civilization; + leaderHead.Texture = TextureLoader.Load("leader_heads", civilization); leaderHead.Scale = new Vector2(1.7f, 1.7f); leaderHead.SetPosition(new Vector2(414, 46)); @@ -195,117 +196,36 @@ private SaveGame GetSave() { return SaveManager.LoadSave(GamePaths.DefaultGamePath, GamePaths.DefaultBicPath, (string unused) => { return unused; }); } - private void CreateGame() { - loadingLabel.Visible = true; - GlobalSingleton global = GetNode("/root/GlobalSingleton"); - SaveGame save = GetSave(); - - // World generation can take a bit of time if multiple attempts are - // needed, so we don't want to tie up the UI thread. - Thread thread = new(() => { DoWorldGenerationAndstartGame(save, global); }); - thread.Start(); - } - - private void DoWorldGenerationAndstartGame(SaveGame save, GlobalSingleton Global) { - log.Information("Starting map generation"); - save.Map = new SaveMap(MapGenerator.GenerateMap(Global.WorldCharacteristics)); - save.Seed = Global.WorldCharacteristics.mapSeed; - log.Information("Done with map generation"); - Random rand = new(Global.WorldCharacteristics.mapSeed + 0x531); - ids = new(save); - - // Hack: reuse the save but clear out the non-barbarian players. - // - // Longer term we'll need to split out our own - // "conquests.bic" type file and load that - until then we'll use this - // hack of reusing the static save. - // - // Start at index 1 to skip the barbarians. - save.Players.RemoveRange(1, save.Players.Count - 1); - - // Clear out the units, we'll add new ones. - save.Units.Clear(); - - // Add the human player. - AddPlayer(save, this.civilization, - Global.WorldCharacteristics.defaultGovernment.id, - isHuman: true); - - // Add the opponents. - HashSet taken = new(); - taken.Add(this.civilization.name); + private List CollectSelectedOpponents() { + List opponents = []; foreach (OptionButton ob in opponentSelectors) { string selectedName = ob.GetItemText(ob.Selected); - if (taken.Contains(selectedName)) { - selectedName = "Random"; - } if (selectedName == "Random") { - do { - selectedName = civilizations[rand.Next(1, civilizations.Count)].name; - } while (taken.Contains(selectedName)); + opponents.Add(new SelectedOpponent { isRandom = true }); + } else { + opponents.Add(new SelectedOpponent { isRandom = false, Name = selectedName }); } - taken.Add(selectedName); - - Civilization civ = civilizations.Find(x => x.name == selectedName); - AddPlayer(save, civ, - Global.WorldCharacteristics.defaultGovernment.id, - isHuman: false); } - save.GameDifficulty = difficulty; - - log.Information("saving generated map"); - save.Save(GamePaths.DefaultGeneratedGamePath); - Global.LoadGamePath = GamePaths.DefaultGeneratedGamePath; - - log.Information("opening map"); - CallDeferred("StartGame"); + return opponents; } - private void AddPlayer(SaveGame save, Civilization civ, ID defaultGovernment, bool isHuman) { - SavePlayer player = new() { - human = isHuman, - id = ids.CreateID("Player"), - colorIndex = civ.colorIndex, - barbarian = false, - civilization = civ.name, - knownTechs = civ.startingTechs, - // TODO: stop hardcoding this - eraCivilopediaName = "ERAS_Ancient_Times", - // TODO: load this from the rules - gold = 10, - governmentId = defaultGovernment, - }; - save.Players.Add(player); - - SaveTile startingTile = save.Map.startingLocations[save.Players.Count - 2]; - TileLocation startingLocation = new TileLocation(startingTile.X, startingTile.Y); - - AddUnit(save, player, save.Rules.StartUnitType1, startingLocation); - AddUnit(save, player, save.Rules.StartUnitType2, startingLocation); - if (civ.traits.Contains(Civilization.Trait.Expansionist)) { - AddUnit(save, player, save.Rules.ScoutUnitType, startingLocation); - } - } + private void CreateGame() { + loadingLabel.Visible = true; + GlobalSingleton global = GetNode("/root/GlobalSingleton"); + + // World generation can take a bit of time if multiple attempts are + // needed, so we don't want to tie up the UI thread. + Thread thread = new(() => { + gameSetup.Populate(save, global.WorldCharacteristics, CollectSelectedOpponents()); + global.SaveGame = save; - private void AddUnit(SaveGame save, SavePlayer player, string unitType, TileLocation location) { - SaveUnit unit = new() { - id = ids.CreateID(unitType), - prototype = unitType, - owner = player.id, - previousLocation = new TileLocation(-1, -1), - currentLocation = location, - - // TODO: stop hardcoding these - hitPointsRemaining = 3, - movePointsRemaining = 1, - experience = "Regular", - - facingDirection = TileDirection.NORTH, - }; - save.Units.Add(unit); + log.Information("opening map"); + CallDeferred(nameof(StartGame)); + }); + thread.Start(); } private void StartGame() { diff --git a/C7/UIElements/NewGame/ScenarioSetup.cs b/C7/UIElements/NewGame/ScenarioSetup.cs index 5f2e3588..af0fc588 100644 --- a/C7/UIElements/NewGame/ScenarioSetup.cs +++ b/C7/UIElements/NewGame/ScenarioSetup.cs @@ -5,7 +5,6 @@ using System.Threading; using C7GameData; using C7Engine; -using ConvertCiv3Media; using C7GameData.Save; using Serilog; @@ -40,7 +39,7 @@ public override void _Ready() { playerListContainer.Columns = (int)Math.Ceiling(save.Civilizations.Count / 12.0); string initiallySelectedCiv = save.Civilizations[1].name; foreach (Civilization civ in save.Civilizations) { - if (civ.name == "A Barbarian Chiefdom") { + if (civ.isBarbarian) { continue; } @@ -133,8 +132,7 @@ private void UpdateSaveAndStartGame(SaveGame save, GlobalSingleton Global) { save.GameDifficulty = difficulty; log.Information("saving updated scenario"); - save.Save(GamePaths.DefaultGeneratedGamePath); - Global.LoadGamePath = GamePaths.DefaultGeneratedGamePath; + Global.SaveGame = save; log.Information("opening map"); CallDeferred("StartGame"); diff --git a/C7/UIElements/NewGame/WorldSetup.cs b/C7/UIElements/NewGame/WorldSetup.cs index 07df6147..a429dc86 100644 --- a/C7/UIElements/NewGame/WorldSetup.cs +++ b/C7/UIElements/NewGame/WorldSetup.cs @@ -313,7 +313,7 @@ private void ResetAgeGraphics() { private void CreateGame() { GlobalSingleton Global = GetNode("/root/GlobalSingleton"); - Global.ResetLoadGamePath(); + Global.ResetLoadGameFields(); SaveGame save = SaveManager.LoadSave(GamePaths.DefaultGamePath, GamePaths.DefaultBicPath, (string unused) => { return unused; }); Global.WorldCharacteristics = new WorldCharacteristics() { diff --git a/C7Engine/C7GameData/Civilization.cs b/C7Engine/C7GameData/Civilization.cs index 1f93a8b6..010a33be 100644 --- a/C7Engine/C7GameData/Civilization.cs +++ b/C7Engine/C7GameData/Civilization.cs @@ -47,6 +47,8 @@ public Civilization(string name) { // The traits that this civilization has. public HashSet traits = new(); + public bool isBarbarian = false; + [JsonIgnore] public UnitPrototype uniqueUnit; diff --git a/C7Engine/C7GameData/ImportCiv3.cs b/C7Engine/C7GameData/ImportCiv3.cs index 7aba0325..d00e3547 100644 --- a/C7Engine/C7GameData/ImportCiv3.cs +++ b/C7Engine/C7GameData/ImportCiv3.cs @@ -363,6 +363,7 @@ private void ImportRaces() { leader = race.LeaderName, leaderGender = race.LeaderGender == 0 ? Gender.Male : Gender.Female, colorIndex = race.DefaultColor, + isBarbarian = i == 0 ? true : false, }; foreach (RACE_City city in theBiq.RaceCityName[i]) { civ.cityNames.Add(city.Name); @@ -525,7 +526,6 @@ private SavePlayer MakeSavePlayerFromCiv(Civilization civ, bool isBarbarian, boo return new SavePlayer { id = ids.CreateID("player"), colorIndex = civ.colorIndex, - barbarian = isBarbarian, human = isHuman, civilization = civ.name, diff --git a/C7Engine/C7GameData/Player.cs b/C7Engine/C7GameData/Player.cs index c8659538..4b598393 100644 --- a/C7Engine/C7GameData/Player.cs +++ b/C7Engine/C7GameData/Player.cs @@ -13,7 +13,7 @@ public class Player { public ID id { get; internal set; } public int colorIndex; - public bool isBarbarians = false; + public bool isBarbarians { get => civilization.isBarbarian; } //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; diff --git a/C7Engine/C7GameData/Save/SavePlayer.cs b/C7Engine/C7GameData/Save/SavePlayer.cs index 08cccd2d..40a76152 100644 --- a/C7Engine/C7GameData/Save/SavePlayer.cs +++ b/C7Engine/C7GameData/Save/SavePlayer.cs @@ -22,7 +22,6 @@ public class PlayerRelationship { public class SavePlayer { public ID id; public int colorIndex; - public bool barbarian; public bool human = false; public bool hasPlayedCurrentTurn = false; public bool defeated = false; @@ -80,7 +79,6 @@ public class SavePlayer { public Player ToPlayer(GameMap map, List civilizations, List governments, List techs, Rules rules) { Player player = new Player{ id = id, - isBarbarians = barbarian, isHuman = human, hasPlayedThisTurn = hasPlayedCurrentTurn, defeated = defeated, @@ -130,7 +128,6 @@ public SavePlayer() { } public SavePlayer(Player player) { id = player.id; colorIndex = player.colorIndex; - barbarian = player.isBarbarians; human = player.isHuman; hasPlayedCurrentTurn = player.hasPlayedThisTurn; defeated = player.defeated; diff --git a/C7Engine/EntryPoints/CreateGame.cs b/C7Engine/EntryPoints/CreateGame.cs index ef3b7983..8ce102d1 100644 --- a/C7Engine/EntryPoints/CreateGame.cs +++ b/C7Engine/EntryPoints/CreateGame.cs @@ -4,42 +4,47 @@ using C7GameData; using C7GameData.Save; -namespace C7Engine { - - public class CreateGame { - /** - * For now, I'm making the methods that the C7 client can call be static. - * We may want a different solution in the end, but this lets us start prototyping - * 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 async Task createGame(string loadFilePath, - string luaRulesDir, - string defaultBicPath, - Func getPediaIconsPath) { - SaveGame save = SaveManager.LoadSave(loadFilePath, defaultBicPath, getPediaIconsPath); - GameData gameData = save.ToGameData(luaRulesDir); - - EngineStorage.gameData = gameData; - EngineStorage.gameData.onGameCreation(); - - // TODO: (pcen) initially, in the false branch I assigned gameData.CreateDummyGameData - // to humanPlayer, but this is not correct since there are already players and units in - // the .sav - instead, we should remove CreateDummyGameData and implement simple save - // generation using the new SaveGame class. This would be difficult before due to GameData's - // 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), - false => throw new Exception($"{loadFilePath} does not contain a human player"), - }; - - EngineStorage.uiControllerID = humanPlayer.id; - TurnHandling.OnBeginTurn(); // Call for the first turn - await TurnHandling.AdvanceTurn(); - - return humanPlayer; - } +namespace C7Engine; + +public class CreateGameParams { + public string LuaRulesDir; + public string DefaultBicPath; + + public Func GetPediaIconsPath = s => s; + + public CreateGameParams(string LuaRulesDir, string DefaultBicPath) { + this.LuaRulesDir = LuaRulesDir; + this.DefaultBicPath = DefaultBicPath; + } +} + +public class CreateGame { + /** + * For now, I'm making the methods that the C7 client can call be static. + * We may want a different solution in the end, but this lets us start prototyping + * 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 async Task createGame(string loadFilePath, CreateGameParams options) { + SaveGame save = SaveManager.LoadSave(loadFilePath, options.DefaultBicPath, options.GetPediaIconsPath); + return await createGame(save, options); + } + + public static async Task createGame(SaveGame save, CreateGameParams options) { + GameData gameData = save.ToGameData(options.LuaRulesDir); + + EngineStorage.gameData = gameData; + EngineStorage.gameData.onGameCreation(); + + Player humanPlayer = gameData.players.Any(p => p.isHuman) switch { + true => gameData.players.Find(p => p.isHuman), + false => throw new Exception($"The provided save does not contain a human player"), + }; + + EngineStorage.uiControllerID = humanPlayer.id; + TurnHandling.OnBeginTurn(); // Call for the first turn + await TurnHandling.AdvanceTurn(); + + return humanPlayer; } } diff --git a/C7Engine/GameSetup.cs b/C7Engine/GameSetup.cs new file mode 100644 index 00000000..001b03c9 --- /dev/null +++ b/C7Engine/GameSetup.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using C7GameData; +using C7GameData.Save; +using Serilog; + +namespace C7Engine; + +public struct SelectedOpponent { + public bool isRandom; + public string Name; +} + +public class GameSetup { + private static ILogger log = Log.ForContext(); + + public Civilization civilization; + public Difficulty difficulty; + + ID.Factory ids; + + public void Populate(SaveGame save, WorldCharacteristics WorldCharacteristics, List opponents) { + log.Information("Starting map generation"); + save.Map = new SaveMap(MapGenerator.GenerateMap(WorldCharacteristics)); + save.Seed = WorldCharacteristics.mapSeed; + log.Information("Done with map generation"); + Random rand = new(WorldCharacteristics.mapSeed + 0x531); + ids = new(save); + + // Hack: reuse the save but clear out the non-barbarian players. + // + // Longer term we'll need to split out our own + // "conquests.bic" type file and load that - until then we'll use this + // hack of reusing the static save. + // + // Start at index 1 to skip the barbarians. + save.Players.RemoveRange(1, save.Players.Count - 1); + + // Clear out the units, we'll add new ones. + save.Units.Clear(); + + // Add the human player. + AddPlayer(save, this.civilization, + WorldCharacteristics.defaultGovernment.id, + isHuman: true); + + // Add the opponents. + HashSet taken = new(); + taken.Add(this.civilization.name); + + foreach (SelectedOpponent opponent in opponents) { + bool isRandom = opponent.isRandom; + string selectedName = opponent.Name; + + if (taken.Contains(opponent.Name)) { + isRandom = true; + } + + if (isRandom) { + do { + selectedName = save.Civilizations[rand.Next(1, save.Civilizations.Count)].name; + } while (taken.Contains(selectedName)); + } + taken.Add(selectedName); + + Civilization civ = save.Civilizations.Find(x => x.name == selectedName); + AddPlayer(save, civ, + WorldCharacteristics.defaultGovernment.id, + isHuman: false); + } + + save.GameDifficulty = difficulty; + } + + private void AddPlayer(SaveGame save, Civilization civ, ID defaultGovernment, bool isHuman) { + SavePlayer player = new() { + human = isHuman, + id = ids.CreateID("Player"), + colorIndex = civ.colorIndex, + civilization = civ.name, + knownTechs = civ.startingTechs, + // TODO: stop hardcoding this + eraCivilopediaName = "ERAS_Ancient_Times", + // TODO: load this from the rules + gold = 10, + governmentId = defaultGovernment, + }; + save.Players.Add(player); + + SaveTile startingTile = save.Map.startingLocations[save.Players.Count - 2]; + TileLocation startingLocation = new TileLocation(startingTile.X, startingTile.Y); + + AddUnit(save, player, save.Rules.StartUnitType1, startingLocation); + AddUnit(save, player, save.Rules.StartUnitType2, startingLocation); + if (civ.traits.Contains(Civilization.Trait.Expansionist)) { + AddUnit(save, player, save.Rules.ScoutUnitType, startingLocation); + } + } + + private void AddUnit(SaveGame save, SavePlayer player, string unitType, TileLocation location) { + SaveUnit unit = new() { + id = ids.CreateID(unitType), + prototype = unitType, + owner = player.id, + previousLocation = new TileLocation(-1, -1), + currentLocation = location, + + // TODO: stop hardcoding these + hitPointsRemaining = 3, + movePointsRemaining = 1, + experience = "Regular", + + facingDirection = TileDirection.NORTH, + }; + save.Units.Add(unit); + } +} diff --git a/EngineTests/GameData/SaveTest.cs b/EngineTests/GameData/SaveTest.cs index dc4d17fa..deaec714 100644 --- a/EngineTests/GameData/SaveTest.cs +++ b/EngineTests/GameData/SaveTest.cs @@ -108,11 +108,12 @@ private void WaitForStartTurnMessage() { } private Player CreateHeadlessGame(string path, string biqPath = "", Func getPediaIconsPath = null) { - if (getPediaIconsPath == null) { - getPediaIconsPath = (string unused) => { return unused; }; + CreateGameParams options = new(luaRulesDir, biqPath); + if (getPediaIconsPath != null) { + options.GetPediaIconsPath = getPediaIconsPath; } - return CreateGame.createGame(path, luaRulesDir, biqPath, getPediaIconsPath).Result; + return CreateGame.createGame(path, options).Result; } private C7GameData.GameData ToGameData(SaveGame game) {