using Godot; using System.Collections.Generic; using System.Text.Json; public static class SaveGameManager { private const string SaveDirectory = "user://savegame"; private const string GameDataPath = SaveDirectory + "/gamedata.json"; private const string RobotsPath = SaveDirectory + "/robots.json"; private const string ResearchPath = SaveDirectory + "/research.json"; private const string LayerPrefix = SaveDirectory + "/layer_"; private const string JsonExtension = ".json"; private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = true }; public static bool SaveExists() { return FileAccess.FileExists(GameDataPath); } public static void SaveGame() { SaveGameData saveGame = CreateSaveData(); CreateSaveDirectory(); ClearOldLayerFiles(); SaveJson(GameDataPath, CreateCoreSaveData(saveGame)); SaveJson(RobotsPath, saveGame.Robots); SaveJson(ResearchPath, saveGame.Research); foreach (LayerSaveData layer in saveGame.Layers) { SaveJson(GetLayerPath(layer.Level), layer); } } public static SaveGameData LoadSaveData() { if (!SaveExists()) return null; SaveGameData saveGame = LoadJson(GameDataPath); if (saveGame == null) return null; saveGame.Robots = LoadJson>(RobotsPath) ?? new List(); saveGame.Research = LoadJson>(ResearchPath) ?? new List(); saveGame.Layers = LoadLayerSaveData(); return saveGame; } public static SaveGameData CreateSaveData() { return new SaveGameData { Seed = GameData.seed, CurrentLayer = GameData.currentLayer, VisibleLayer = GameData.visibleLayer, LowestLayer = GameData.lowestLayer, MaxRobotCount = GameData.maxRobotCount, CanMove = GameData.canMove, Survival = CreateSurvivalSaveData(), Inventory = CreateInventorySaveData(), Research = CreateResearchSaveData(), Layers = CreateLayerSaveData(), Robots = CreateRobotSaveData() }; } public static void ApplyWorldData(SaveGameData saveGame) { if (saveGame == null) return; GameData.seed = saveGame.Seed; GameData.currentLayer = saveGame.CurrentLayer; GameData.visibleLayer = saveGame.VisibleLayer; GameData.lowestLayer = saveGame.LowestLayer; GameData.maxRobotCount = saveGame.MaxRobotCount; GameData.canMove = saveGame.CanMove; ApplySurvivalData(saveGame.Survival); ApplyInventoryData(saveGame.Inventory); ApplyResearchData(saveGame.Research); ApplyLayerData(saveGame.Layers); } private static SurvivalSaveData CreateSurvivalSaveData() { return new SurvivalSaveData { Hunger = GameData.survival.hunger, Thirst = GameData.survival.thirst, Energy = GameData.survival.energy, IsDead = GameData.survival.isDead, DeathReason = GameData.survival.deathReason, CurrentStatus = GameData.survival.currentStatus }; } private static List CreateInventorySaveData() { List result = new List(); foreach (Item item in GameData.inventory.items) { result.Add(new ItemSaveData { Id = item.data.Id, Amount = item.currentAmount }); } return result; } private static List CreateResearchSaveData() { List result = new List(); foreach (Research research in GameData.availableResearch.Values) { result.Add(new ResearchSaveData { Id = research.data.Id, State = research.state, ElapsedResearchTime = research.elapsedResearchTime, PaidResources = research.paidResources }); } return result; } private static List CreateLayerSaveData() { List result = new List(); if (GameData.map == null) return result; foreach (Layer layer in GameData.map) { if (layer == null) continue; result.Add(new LayerSaveData { Level = layer.level, IsGateOpen = layer.isGateOpen, HasContentGenerated = layer.hasContentGenerated, GateIngredients = new List(layer.gateIngredients), CurrentResources = new List(layer.currentResources), Tiles = CreateTileSaveData(layer) }); } return result; } private static List CreateTileSaveData(Layer layer) { List result = new List(); foreach (Tile tile in layer.tiles) { result.Add(new TileSaveData { X = tile.GridPosition.X, Y = tile.GridPosition.Y, CollapsedMesh = tile.collapsedMesh, ContainsLight = tile.containsLight, ContainsDecoration = tile.containsDecoration, ContainsResource = tile.containsResource, WasVisited = tile.wasVisited, Resource = tile.resource == null ? null : tile.resource.CreateSaveData() }); } return result; } private static List CreateRobotSaveData() { List result = new List(); foreach (Robot robot in GameData.robots) { result.Add(robot.CreateSaveData()); } return result; } private static SaveGameData CreateCoreSaveData(SaveGameData saveGame) { return new SaveGameData { Seed = saveGame.Seed, CurrentLayer = saveGame.CurrentLayer, VisibleLayer = saveGame.VisibleLayer, LowestLayer = saveGame.LowestLayer, MaxRobotCount = saveGame.MaxRobotCount, CanMove = saveGame.CanMove, Survival = saveGame.Survival, Inventory = saveGame.Inventory, Research = new List(), Layers = new List(), Robots = new List() }; } private static List LoadLayerSaveData() { List result = new List(); for (int i = 0; i < GameData.ruinSize; i++) { string path = GetLayerPath(i); if (!FileAccess.FileExists(path)) continue; LayerSaveData layer = LoadJson(path); if (layer != null) { result.Add(layer); } } return result; } private static string GetLayerPath(int level) { return LayerPrefix + level + JsonExtension; } private static void CreateSaveDirectory() { DirAccess.MakeDirRecursiveAbsolute(SaveDirectory); } private static void ClearOldLayerFiles() { DirAccess directory = DirAccess.Open(SaveDirectory); if (directory == null) return; directory.ListDirBegin(); while (true) { string fileName = directory.GetNext(); if (fileName == "") break; if (directory.CurrentIsDir()) continue; if (!fileName.StartsWith("layer_") || !fileName.EndsWith(JsonExtension)) continue; directory.Remove(fileName); } directory.ListDirEnd(); } private static void SaveJson(string path, T data) { string json = JsonSerializer.Serialize(data, JsonOptions); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); file.StoreString(json); file.Flush(); } private static T LoadJson(string path) { if (!FileAccess.FileExists(path)) return default; FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); string json = file.GetAsText(); return JsonSerializer.Deserialize(json); } private static void ApplySurvivalData(SurvivalSaveData survival) { if (survival == null) return; GameData.survival.hunger = survival.Hunger; GameData.survival.thirst = survival.Thirst; GameData.survival.energy = survival.Energy; GameData.survival.isDead = survival.IsDead; GameData.survival.deathReason = survival.DeathReason ?? ""; GameData.survival.currentStatus = survival.CurrentStatus ?? ""; } private static void ApplyInventoryData(List savedItems) { GameData.inventory = new Inventory(); if (savedItems == null) return; foreach (ItemSaveData savedItem in savedItems) { if (!GameData.availableItems.ContainsKey(savedItem.Id)) continue; GameData.inventory.AddItem( new Item { data = GameData.availableItems[savedItem.Id] }, savedItem.Amount ); } } private static void ApplyResearchData(List savedResearch) { if (savedResearch == null) return; foreach (ResearchSaveData savedState in savedResearch) { if (!GameData.availableResearch.ContainsKey(savedState.Id)) continue; Research research = GameData.availableResearch[savedState.Id]; research.state = savedState.State; research.elapsedResearchTime = savedState.ElapsedResearchTime; research.paidResources = savedState.PaidResources; } GameData.RebuildRobotStatsFromResearch(); } private static void ApplyLayerData(List savedLayers) { if (savedLayers == null || GameData.map == null) return; foreach (LayerSaveData savedLayer in savedLayers) { if (savedLayer.Level < 0 || savedLayer.Level >= GameData.map.Length) continue; Layer layer = GameData.map[savedLayer.Level]; layer.isGateOpen = savedLayer.IsGateOpen; layer.hasContentGenerated = savedLayer.HasContentGenerated; layer.gateIngredients = savedLayer.GateIngredients ?? new List(); layer.currentResources = savedLayer.CurrentResources ?? new List(); ApplyTileData(layer, savedLayer.Tiles); } } private static void ApplyTileData(Layer layer, List savedTiles) { if (savedTiles == null) return; foreach (TileSaveData savedTile in savedTiles) { if (savedTile.X < 0 || savedTile.X >= GameData.layerSize) continue; if (savedTile.Y < 0 || savedTile.Y >= GameData.layerSize) continue; Tile tile = layer.tiles[savedTile.X, savedTile.Y]; tile.collapsedMesh = savedTile.CollapsedMesh; tile.containsLight = savedTile.ContainsLight; tile.containsDecoration = savedTile.ContainsDecoration; tile.containsResource = savedTile.ContainsResource; tile.wasVisited = savedTile.WasVisited; tile.resource = savedTile.Resource == null ? null : GameResource.FromSaveData(savedTile.Resource); tile.ContentNode.Visible = savedTile.WasVisited || tile.collapsedMesh == "gate"; } } }