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.
156 lines
5.6 KiB
C#
156 lines
5.6 KiB
C#
using Chessistics.Engine.Events;
|
|
using Chessistics.Engine.Model;
|
|
|
|
namespace Chessistics.Engine.Commands;
|
|
|
|
/// <summary>
|
|
/// Load a campaign: initializes the board with mission 0's terrain, stock, and unlocked pieces.
|
|
/// </summary>
|
|
public class LoadCampaignCommand : WorldCommand
|
|
{
|
|
public override void AssertApplicationConditions(BoardState state)
|
|
{
|
|
if (state.Campaign == null)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(LoadCampaignCommand), "No campaign defined."));
|
|
|
|
if (state.Campaign.CampaignDef.Missions.Count == 0)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(LoadCampaignCommand), "Campaign has no missions."));
|
|
}
|
|
|
|
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
|
|
{
|
|
var campaign = state.Campaign!;
|
|
var mission = campaign.CurrentMission;
|
|
|
|
// Apply terrain patch for mission 0
|
|
state.ApplyTerrainPatch(mission.TerrainPatch, campaign.CurrentMissionIndex);
|
|
|
|
// Add stock
|
|
state.AddStock(mission.Stock);
|
|
|
|
// Unlock pieces and levels
|
|
foreach (var kind in mission.UnlockedPieces)
|
|
{
|
|
campaign.AvailablePieceKinds.Add(kind);
|
|
changeList.Add(new PieceUnlockedEvent(kind, 1));
|
|
}
|
|
foreach (var upgrade in mission.UnlockedLevels)
|
|
{
|
|
campaign.AvailableLevels.Add(upgrade);
|
|
changeList.Add(new PieceUnlockedEvent(upgrade.Kind, upgrade.Level));
|
|
}
|
|
|
|
state.Phase = SimPhase.Paused;
|
|
|
|
changeList.Add(new CampaignLoadedEvent(campaign.CampaignDef.Name, 0));
|
|
changeList.Add(new MissionStartedEvent(0, state.Width, state.Height));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advance to the next mission: applies the terrain patch, adds stock, unlocks pieces.
|
|
/// </summary>
|
|
public class AdvanceMissionCommand : WorldCommand
|
|
{
|
|
public override void AssertApplicationConditions(BoardState state)
|
|
{
|
|
if (state.Campaign == null)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(AdvanceMissionCommand), "No campaign defined."));
|
|
|
|
if (state.Phase != SimPhase.MissionComplete)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(AdvanceMissionCommand), "Current mission is not complete."));
|
|
|
|
if (state.Campaign.IsLastMission)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(AdvanceMissionCommand), "No more missions."));
|
|
}
|
|
|
|
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
|
|
{
|
|
var campaign = state.Campaign!;
|
|
campaign.CurrentMissionIndex++;
|
|
var mission = campaign.CurrentMission;
|
|
|
|
// Apply terrain expansion
|
|
var oldWidth = state.Width;
|
|
var oldHeight = state.Height;
|
|
state.ApplyTerrainPatch(mission.TerrainPatch, campaign.CurrentMissionIndex);
|
|
|
|
if (state.Width != oldWidth || state.Height != oldHeight)
|
|
changeList.Add(new TerrainExpandedEvent(state.Width, state.Height, mission.TerrainPatch.Cells));
|
|
|
|
// Add stock
|
|
state.AddStock(mission.Stock);
|
|
|
|
// Unlock pieces and levels
|
|
foreach (var kind in mission.UnlockedPieces)
|
|
{
|
|
campaign.AvailablePieceKinds.Add(kind);
|
|
changeList.Add(new PieceUnlockedEvent(kind, 1));
|
|
}
|
|
foreach (var upgrade in mission.UnlockedLevels)
|
|
{
|
|
campaign.AvailableLevels.Add(upgrade);
|
|
changeList.Add(new PieceUnlockedEvent(upgrade.Kind, upgrade.Level));
|
|
}
|
|
|
|
state.Phase = SimPhase.Paused;
|
|
changeList.Add(new MissionStartedEvent(campaign.CurrentMissionIndex, state.Width, state.Height));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move a piece already on the board (drag & drop). Validates the new placement.
|
|
/// </summary>
|
|
public class MovePieceCommand : WorldCommand
|
|
{
|
|
public int PieceId { get; }
|
|
public Coords NewStart { get; }
|
|
public Coords NewEnd { get; }
|
|
|
|
public MovePieceCommand(int pieceId, Coords newStart, Coords newEnd)
|
|
{
|
|
PieceId = pieceId;
|
|
NewStart = newStart;
|
|
NewEnd = newEnd;
|
|
}
|
|
|
|
public override void AssertApplicationConditions(BoardState state)
|
|
{
|
|
var piece = state.GetPieceById(PieceId);
|
|
if (piece == null)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(MovePieceCommand), $"Piece {PieceId} not found."));
|
|
|
|
if (!state.IsOnBoard(NewStart) || !state.IsOnBoard(NewEnd))
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(MovePieceCommand), "Position is off the board."));
|
|
|
|
if (state.GetCell(NewStart) == CellType.Wall)
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(MovePieceCommand), "Cannot place on a wall."));
|
|
|
|
if (!Rules.MoveValidator.IsLegalPlacement(piece.Kind, NewStart, NewEnd, state))
|
|
throw new CommandRejectedException(
|
|
new CommandRejectedEvent(nameof(MovePieceCommand), "Illegal move for this piece type."));
|
|
}
|
|
|
|
protected override void DoApply(BoardState state, List<IWorldEvent> changeList)
|
|
{
|
|
var piece = state.GetPieceById(PieceId)!;
|
|
var oldStart = piece.StartCell;
|
|
var oldEnd = piece.EndCell;
|
|
|
|
piece.SetPosition(NewStart, NewEnd);
|
|
piece.Cargo = null;
|
|
|
|
state.OccupiedCells.Add(NewStart);
|
|
state.OccupiedCells.Add(NewEnd);
|
|
|
|
changeList.Add(new PieceMovedByPlayerEvent(PieceId, oldStart, oldEnd, NewStart, NewEnd));
|
|
}
|
|
}
|