Add editor image badges and drag moves

This commit is contained in:
2026-05-11 23:03:29 +02:00
parent 884cc4503f
commit dfe0cb3b6a
7 changed files with 538 additions and 46 deletions

View File

@@ -2,26 +2,106 @@
public static class LevelEditor
{
public sealed record MoveOccupantResult(bool Success, LevelState Level, string Reason)
{
public static MoveOccupantResult Succeeded(LevelState level)
{
return new(true, level, string.Empty);
}
public static MoveOccupantResult Failed(LevelState level, string reason)
{
return new(false, level, reason);
}
}
public static LevelState MoveOccupant(LevelState level, GridPosition source, GridPosition destination)
{
if (!level.InBounds(source) || !level.IsFloor(destination) || source == destination)
return level;
return TryMoveOccupant(level, source, destination).Level;
}
public static MoveOccupantResult TryMoveOccupant(LevelState level, GridPosition source, GridPosition destination)
{
if (!level.InBounds(source))
return MoveOccupantResult.Failed(level, "Drag start is outside the level.");
if (!level.InBounds(destination))
return MoveOccupantResult.Failed(level, "Drop target is outside the level.");
if (source == destination)
return MoveOccupantResult.Failed(level, "Drop target is the same cell.");
var prop = level.GetProp(source);
if (prop.Type == EPropType.None)
return level.Robot.Position == source ? level with { Robot = level.Robot with { Position = destination } } : level;
if (prop.Type != EPropType.None)
return TryMoveProp(level, source, destination, prop);
var leak = level.Leaks.FirstOrDefault(leak => !leak.Repaired && (leak.AccessPosition == source || leak.UndergroundPosition == source));
if (leak is not null)
return TryMoveLeak(level, leak, destination);
return level.Robot.Position == source
? TryMoveRobot(level, destination)
: MoveOccupantResult.Failed(level, "No movable robot, prop, source, or leak starts here.");
}
private static MoveOccupantResult TryMoveRobot(LevelState level, GridPosition destination)
{
if (!level.IsFloor(destination))
return MoveOccupantResult.Failed(level, "Robot destination must be a floor cell.");
return MoveOccupantResult.Succeeded(level with { Robot = level.Robot with { Position = destination } });
}
private static MoveOccupantResult TryMoveProp(LevelState level, GridPosition source, GridPosition destination, PropState prop)
{
if (!level.IsFloor(destination))
return MoveOccupantResult.Failed(level, "Prop destination must be a floor cell.");
if (level.GetProp(destination).Type != EPropType.None)
return level;
return MoveOccupantResult.Failed(level, "Prop destination is already occupied.");
var next = level.SetProp(source, new()).SetProp(destination, prop);
if (prop.Type != EPropType.ReactorControl)
return next;
return MoveOccupantResult.Succeeded(next);
return next with {
return MoveOccupantResult.Succeeded(next with {
Reactors = next.Reactors
.Select(reactor => reactor.ControlPosition == source ? reactor with { ControlPosition = destination } : reactor)
.ToArray()
});
}
private static MoveOccupantResult TryMoveLeak(LevelState level, LeakState leak, GridPosition destination)
{
if (leak.Carrier is ECarrierType.Fuel or ECarrierType.Coolant)
{
if (!level.IsFloor(destination))
return MoveOccupantResult.Failed(level, "Fuel and coolant leaks must move to a floor cell.");
var next = ClearLeak(level, leak)
.SetUnderground(leak.UndergroundPosition, leak.Carrier, new());
return MoveOccupantResult.Succeeded(SetLeak(next, destination, destination, leak.Carrier));
}
if (leak.Carrier == ECarrierType.Electricity)
{
if (!level.IsFloor(destination))
return MoveOccupantResult.Failed(level, "Electric leak destination must be an adjacent floor access cell.");
var undergroundPosition = leak.UndergroundPosition;
if (undergroundPosition.ManhattanDistance(destination) != 1)
return MoveOccupantResult.Failed(level, "Electric leak destination must stay adjacent to its underground wall cell.");
return MoveOccupantResult.Succeeded(SetLeak(ClearLeak(level, leak), undergroundPosition, destination, leak.Carrier));
}
return MoveOccupantResult.Failed(level, "Unsupported leak carrier.");
}
private static LevelState ClearLeak(LevelState level, LeakState leak)
{
return level with {
Leaks = level.Leaks.Where(candidate => candidate != leak).ToArray()
};
}
@@ -61,7 +141,7 @@ public static class LevelEditor
EEditorTool.Flow => SetCarrierProp(level, position, EPropType.Flow, command.Carrier),
EEditorTool.Consumer => SetFloorProp(level, position, new() { Type = EPropType.Consumer, SwitchState = EPropSwitchState.Enabled }),
EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }),
EEditorTool.Door => SetFloorProp(level, position, new() { Type = EPropType.Door }),
EEditorTool.Door => ToggleOrSetDoor(level, position),
EEditorTool.AllSeeingEyeTerminal => SetFloorProp(level, position, new() { Type = EPropType.AllSeeingEyeTerminal }),
EEditorTool.RemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = command.RemedyType }),
EEditorTool.ReactorControl => SetReactorControl(level, position),
@@ -129,6 +209,21 @@ public static class LevelEditor
return level.IsFloor(position) ? level.SetProp(position, prop) : level;
}
private static LevelState ToggleOrSetDoor(LevelState level, GridPosition position)
{
if (!level.IsFloor(position))
return level;
var prop = level.GetProp(position);
if (prop.Type == EPropType.Door)
{
var nextState = prop.DoorState == EDoorState.Open ? EDoorState.Closed : EDoorState.Open;
return level.SetProp(position, prop with { DoorState = nextState });
}
return level.SetProp(position, new() { Type = EPropType.Door, DoorState = EDoorState.Closed });
}
private static LevelState SetReactorControl(LevelState level, GridPosition position)
{
if (!level.IsFloor(position))