252 lines
11 KiB
C#
252 lines
11 KiB
C#
namespace ReactorMaintenance.Simulation;
|
|
|
|
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)
|
|
{
|
|
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 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 MoveOccupantResult.Failed(level, "Prop destination is already occupied.");
|
|
|
|
var next = level.SetProp(source, new()).SetProp(destination, prop);
|
|
if (prop.Type != EPropType.ReactorControl)
|
|
return MoveOccupantResult.Succeeded(next);
|
|
|
|
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.Water)
|
|
{
|
|
if (!level.IsFloor(destination))
|
|
return MoveOccupantResult.Failed(level, "Fuel and water 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()
|
|
};
|
|
}
|
|
|
|
public static LevelState CycleElectricityLeakAccess(LevelState level, GridPosition undergroundPosition)
|
|
{
|
|
if (!level.InBounds(undergroundPosition))
|
|
return level;
|
|
|
|
if (!level.GetUnderground(undergroundPosition, ECarrierType.Electricity).IsPresent)
|
|
return level;
|
|
|
|
var accessPositions = undergroundPosition.Neighbors().Where(level.IsFloor).ToArray();
|
|
if (accessPositions.Length == 0)
|
|
return level;
|
|
|
|
var existingLeak = level.Leaks.FirstOrDefault(leak => leak.Carrier == ECarrierType.Electricity && leak.UndergroundPosition == undergroundPosition);
|
|
var nextAccessPosition = accessPositions[0];
|
|
if (existingLeak is not null)
|
|
{
|
|
var index = Array.IndexOf(accessPositions, existingLeak.AccessPosition);
|
|
nextAccessPosition = accessPositions[(index + 1) % accessPositions.Length];
|
|
}
|
|
|
|
return SetLeak(level, undergroundPosition, nextAccessPosition, ECarrierType.Electricity);
|
|
}
|
|
|
|
public static LevelState Apply(LevelState level, GridPosition position, EditorToolCommand command)
|
|
{
|
|
if (!level.InBounds(position))
|
|
return level;
|
|
|
|
return command.Tool switch {
|
|
EEditorTool.Cursor => level,
|
|
EEditorTool.Floor => level.SetTerrain(position, ECellTerrain.Floor),
|
|
EEditorTool.Wall => level.SetTerrain(position, ECellTerrain.Wall),
|
|
EEditorTool.Underground => SetUnderground(level, position, command.Carrier),
|
|
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 => 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),
|
|
EEditorTool.Leak => SetLeak(level, position, command.Carrier),
|
|
EEditorTool.SurfaceHazard => AddSurfaceHazard(level, position, command.Carrier),
|
|
EEditorTool.Heat => level.SetSurface(position, level.GetSurface(position) with { Heat = level.GetSurface(position).Heat + 1 }),
|
|
EEditorTool.Robot => level.IsFloor(position) ? level with { Robot = level.Robot with { Position = position } } : level,
|
|
_ => level
|
|
};
|
|
}
|
|
|
|
public static LevelState SetLeak(LevelState level, GridPosition undergroundPosition, GridPosition accessPosition, ECarrierType carrier)
|
|
{
|
|
if (!level.InBounds(undergroundPosition) || !level.IsFloor(accessPosition))
|
|
return level;
|
|
|
|
if (carrier is ECarrierType.Fuel or ECarrierType.Water && undergroundPosition != accessPosition)
|
|
return level;
|
|
|
|
if (carrier == ECarrierType.Electricity && undergroundPosition.ManhattanDistance(accessPosition) != 1)
|
|
return level;
|
|
|
|
var next = level.SetUnderground(undergroundPosition, carrier, new() {
|
|
State = EUndergroundState.Leaking,
|
|
StructuralIntegrity = Balancing.Current.StructuralIntegrityLeakThreshold
|
|
});
|
|
return next with {
|
|
Leaks = [
|
|
.. next.Leaks.Where(leak => leak.UndergroundPosition != undergroundPosition || leak.Carrier != carrier),
|
|
new() {
|
|
Carrier = carrier,
|
|
UndergroundPosition = undergroundPosition,
|
|
AccessPosition = accessPosition
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
private static LevelState SetUnderground(LevelState level, GridPosition position, ECarrierType carrier)
|
|
{
|
|
return level.SetUnderground(position, carrier, new() {
|
|
State = EUndergroundState.Intact,
|
|
StructuralIntegrity = Balancing.Current.MaxStructuralIntegrity
|
|
});
|
|
}
|
|
|
|
private static LevelState SetCarrierProp(LevelState level, GridPosition position, EPropType type, ECarrierType carrier)
|
|
{
|
|
return SetFloorProp(level, position, new() { Type = type, Carrier = carrier, SwitchState = EPropSwitchState.Enabled });
|
|
}
|
|
|
|
private static LevelState AddSurfaceHazard(LevelState level, GridPosition position, ECarrierType carrier)
|
|
{
|
|
var surface = level.GetSurface(position);
|
|
return carrier switch {
|
|
ECarrierType.Fuel => level.SetSurface(position, surface with { Fuel = surface.Fuel + 1 }),
|
|
ECarrierType.Water => level.SetSurface(position, surface with { Water = surface.Water + 1 }),
|
|
ECarrierType.Electricity => level.SetSurface(position, surface with { Electricity = surface.Electricity + 1 }),
|
|
_ => level
|
|
};
|
|
}
|
|
|
|
private static LevelState SetFloorProp(LevelState level, GridPosition position, PropState prop)
|
|
{
|
|
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))
|
|
return level;
|
|
|
|
var id = level.Reactors.Count == 0 ? 1 : level.Reactors.Max(reactor => reactor.ReactorId) + 1;
|
|
var levelWithProp = level.SetProp(position, new() { Type = EPropType.ReactorControl, ReactorId = id });
|
|
return levelWithProp with {
|
|
Reactors = [
|
|
.. level.Reactors,
|
|
new() {
|
|
ReactorId = id,
|
|
ControlPosition = position
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
private static LevelState SetLeak(LevelState level, GridPosition position, ECarrierType carrier)
|
|
{
|
|
if (!level.InBounds(position))
|
|
return level;
|
|
|
|
return SetLeak(level, position, position, carrier);
|
|
}
|
|
} |