Chessistics/chessistics-engine/Loading/CampaignLoader.cs

217 lines
7.1 KiB
C#
Raw Normal View History

using System.Text.Json;
using System.Text.Json.Serialization;
using Chessistics.Engine.Model;
namespace Chessistics.Engine.Loading;
public static class CampaignLoader
{
private static readonly JsonSerializerOptions Options = new()
{
PropertyNameCaseInsensitive = true,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
};
public static CampaignDef Load(string json)
{
var dto = JsonSerializer.Deserialize<CampaignDto>(json, Options)
?? throw new JsonException("Failed to deserialize campaign JSON.");
Validate(dto);
return new CampaignDef
{
Name = dto.Name,
InitialWidth = dto.InitialWidth,
InitialHeight = dto.InitialHeight,
Missions = dto.Missions.Select(ParseMission).ToList()
};
}
public static CampaignDef LoadFromFile(string path)
{
var json = File.ReadAllText(path);
return Load(json);
}
private static MissionDef ParseMission(MissionDto m)
{
var patch = new TerrainPatch
{
NewWidth = m.TerrainPatch.NewWidth,
NewHeight = m.TerrainPatch.NewHeight,
Cells = m.TerrainPatch.Cells.Select(ParsePatchCell).ToList()
};
return new MissionDef
{
Id = m.Id,
Name = m.Name,
Description = m.Description ?? "",
Flavor = m.Flavor ?? "",
TerrainPatch = patch,
Stock = m.Stock?.Select(s => new PieceStock(ParseKind(s.Kind), s.Count, s.Level)).ToList() ?? [],
UnlockedPieces = m.UnlockedPieces?.Select(ParseKind).ToList() ?? [],
UnlockedLevels = m.UnlockedLevels?.Select(u => new PieceUpgrade(ParseKind(u.Kind), u.Level)).ToList() ?? []
};
}
private static PatchCell ParsePatchCell(CellDto c)
{
var cellType = c.Type.ToLowerInvariant() switch
{
"empty" => CellType.Empty,
"wall" => CellType.Wall,
"production" => CellType.Production,
"demand" => CellType.Demand,
"transformer" => CellType.Transformer,
_ => throw new JsonException($"Unknown cell type: '{c.Type}'")
};
ProductionDef? prod = null;
if (cellType == CellType.Production && c.Production != null)
{
prod = new ProductionDef(new Coords(c.Col, c.Row), c.Production.Name, ParseCargo(c.Production.Cargo), c.Production.Amount);
}
DemandDef? demand = null;
if (cellType == CellType.Demand && c.Demand != null)
{
demand = new DemandDef(new Coords(c.Col, c.Row), c.Demand.Name, ParseCargo(c.Demand.Cargo), c.Demand.Amount);
}
TransformerDef? transformer = null;
if (cellType == CellType.Transformer && c.Transformer != null)
{
transformer = new TransformerDef(
new Coords(c.Col, c.Row), c.Transformer.Name,
ParseCargo(c.Transformer.InputCargo), c.Transformer.InputRequired,
ParseCargo(c.Transformer.OutputCargo), c.Transformer.OutputAmount);
}
return new PatchCell
{
Col = c.Col,
Row = c.Row,
Type = cellType,
Production = prod,
Demand = demand,
Transformer = transformer
};
}
private static CargoType ParseCargo(string cargo) => cargo.ToLowerInvariant() switch
{
"wood" => CargoType.Wood,
"stone" => CargoType.Stone,
"tools" => CargoType.Tools,
"arms" => CargoType.Arms,
"gold" => CargoType.Gold,
_ => throw new JsonException($"Unknown cargo type: '{cargo}'")
};
private static PieceKind ParseKind(string kind) => kind.ToLowerInvariant() switch
{
"pawn" => PieceKind.Pawn,
"rook" => PieceKind.Rook,
"bishop" => PieceKind.Bishop,
"knight" => PieceKind.Knight,
"queen" => PieceKind.Queen,
_ => throw new JsonException($"Unknown piece kind: '{kind}'")
};
private static void Validate(CampaignDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Name))
throw new JsonException("Campaign name is required.");
if (dto.InitialWidth <= 0 || dto.InitialHeight <= 0)
throw new JsonException("Campaign dimensions must be positive.");
if (dto.Missions.Count == 0)
throw new JsonException("Campaign must have at least one mission.");
int prevWidth = dto.InitialWidth;
int prevHeight = dto.InitialHeight;
foreach (var mission in dto.Missions)
{
if (mission.TerrainPatch.NewWidth < prevWidth || mission.TerrainPatch.NewHeight < prevHeight)
throw new JsonException($"Mission '{mission.Name}': terrain cannot shrink (was {prevWidth}x{prevHeight}, got {mission.TerrainPatch.NewWidth}x{mission.TerrainPatch.NewHeight}).");
prevWidth = mission.TerrainPatch.NewWidth;
prevHeight = mission.TerrainPatch.NewHeight;
}
}
// DTOs
private class CampaignDto
{
public string Name { get; set; } = "";
public int InitialWidth { get; set; }
public int InitialHeight { get; set; }
public List<MissionDto> Missions { get; set; } = [];
}
private class MissionDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string? Description { get; set; }
public string? Flavor { get; set; }
public TerrainPatchDto TerrainPatch { get; set; } = new();
public List<StockDto>? Stock { get; set; }
public List<string>? UnlockedPieces { get; set; }
public List<UpgradeDto>? UnlockedLevels { get; set; }
}
private class TerrainPatchDto
{
public int NewWidth { get; set; }
public int NewHeight { get; set; }
public List<CellDto> Cells { get; set; } = [];
}
private class CellDto
{
public int Col { get; set; }
public int Row { get; set; }
public string Type { get; set; } = "empty";
public ProductionDto? Production { get; set; }
public DemandDto? Demand { get; set; }
public TransformerDto? Transformer { get; set; }
}
private class ProductionDto
{
public string Name { get; set; } = "";
public string Cargo { get; set; } = "";
public int Amount { get; set; } = 1;
}
private class DemandDto
{
public string Name { get; set; } = "";
public string Cargo { get; set; } = "";
public int Amount { get; set; }
}
private class TransformerDto
{
public string Name { get; set; } = "";
public string InputCargo { get; set; } = "";
public int InputRequired { get; set; } = 1;
public string OutputCargo { get; set; } = "";
public int OutputAmount { get; set; } = 1;
}
private class StockDto
{
public string Kind { get; set; } = "";
public int Count { get; set; }
public int Level { get; set; } = 1;
}
private class UpgradeDto
{
public string Kind { get; set; } = "";
public int Level { get; set; }
}
}