Bundles in-flight work on the campaign/missions system (CampaignDef, MissionDef, TerrainPatch, TransformerDef, MissionChecker, CampaignLoader, FlavorBanner, transformer rules), plan files, and matching tests. Baseline commit so the upcoming automation testing harness lands on a clean tree.
216 lines
7.1 KiB
C#
216 lines
7.1 KiB
C#
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; }
|
|
}
|
|
}
|