Add sprinkler valve simulation contract
This commit is contained in:
@@ -155,6 +155,8 @@ public abstract class Balancing
|
||||
public abstract float LeakBaseAmount { get; }
|
||||
public abstract float LeakAmountScale { get; }
|
||||
public abstract float LeakIntensityScale { get; }
|
||||
public abstract float SprinklerWaterPerStep { get; }
|
||||
public abstract float SprinklerPressureDebt { get; }
|
||||
public abstract float FlowTransferRatio { get; }
|
||||
public abstract float WarmCautionAmount { get; }
|
||||
public abstract float WarmCriticalAmount { get; }
|
||||
|
||||
@@ -54,6 +54,8 @@ public class NormalBalancing : Balancing
|
||||
public override float LeakBaseAmount => 0.5f;
|
||||
public override float LeakAmountScale => 0.15f;
|
||||
public override float LeakIntensityScale => 0.1f;
|
||||
public override float SprinklerWaterPerStep => 0.8f;
|
||||
public override float SprinklerPressureDebt => 3.0f;
|
||||
public override float FlowTransferRatio => 0.05f;
|
||||
public override float WarmCautionAmount => 0.5f;
|
||||
public override float WarmCriticalAmount => 1.0f;
|
||||
|
||||
@@ -12,6 +12,8 @@ public enum EEditorTool
|
||||
Junction,
|
||||
Door,
|
||||
AllSeeingEyeTerminal,
|
||||
SprinklerControl,
|
||||
SprinklerValve,
|
||||
RemedySupply,
|
||||
ReactorControl,
|
||||
Leak,
|
||||
|
||||
@@ -144,6 +144,8 @@ public static class LevelEditor
|
||||
EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }),
|
||||
EEditorTool.Door => ToggleOrSetDoor(level, position),
|
||||
EEditorTool.AllSeeingEyeTerminal => SetFloorProp(level, position, new() { Type = EPropType.AllSeeingEyeTerminal }),
|
||||
EEditorTool.SprinklerControl => SetFloorProp(level, position, new() { Type = EPropType.SprinklerControl, SwitchState = EPropSwitchState.Enabled }),
|
||||
EEditorTool.SprinklerValve => SetWallProp(level, position, new() { Type = EPropType.SprinklerValve, Carrier = ECarrierType.Water, OutletPosition = position.Neighbors().FirstOrDefault(level.IsFloor) }),
|
||||
EEditorTool.RemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = command.RemedyType }),
|
||||
EEditorTool.ReactorControl => SetReactorControl(level, position),
|
||||
EEditorTool.Leak => SetLeak(level, position, command.Carrier),
|
||||
@@ -210,6 +212,11 @@ public static class LevelEditor
|
||||
return level.IsFloor(position) ? level.SetProp(position, prop) : level;
|
||||
}
|
||||
|
||||
private static LevelState SetWallProp(LevelState level, GridPosition position, PropState prop)
|
||||
{
|
||||
return level.InBounds(position) && level.GetTerrain(position) == ECellTerrain.Wall ? level.SetProp(position, prop) : level;
|
||||
}
|
||||
|
||||
private static LevelState ToggleOrSetDoor(LevelState level, GridPosition position)
|
||||
{
|
||||
if (!level.IsFloor(position))
|
||||
|
||||
@@ -33,7 +33,7 @@ public static class LevelSerializer
|
||||
return level;
|
||||
}
|
||||
|
||||
private const int c_CurrentVersion = 3;
|
||||
private const int c_CurrentVersion = 4;
|
||||
|
||||
private static readonly JsonSerializerOptions s_Options = new() {
|
||||
WriteIndented = true,
|
||||
|
||||
@@ -12,6 +12,7 @@ public sealed class LevelValidator
|
||||
ValidateCells(level, errors);
|
||||
ValidateDoors(level, errors);
|
||||
ValidateIsolationValves(level, errors);
|
||||
ValidateSprinklers(level, errors);
|
||||
ValidateLeaks(level, errors);
|
||||
ValidateReactors(level, errors, warnings);
|
||||
ValidateJunctions(level, errors);
|
||||
@@ -70,7 +71,7 @@ public sealed class LevelValidator
|
||||
if (surface.Fuel > 0 || surface.Water > 0 || surface.Electricity > 0 || surface.Heat > 0)
|
||||
errors.Add(new("Wall cell cannot store surface hazards.", position));
|
||||
|
||||
if (prop.Type != EPropType.None)
|
||||
if (prop.Type is not (EPropType.None or EPropType.SprinklerValve))
|
||||
errors.Add(new("Prop must be placed on floor terrain.", position));
|
||||
}
|
||||
}
|
||||
@@ -97,6 +98,39 @@ public sealed class LevelValidator
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateSprinklers(LevelState level, List<ValidationIssue> errors)
|
||||
{
|
||||
foreach (var position in LevelTraversal.AllPositions(level))
|
||||
{
|
||||
var prop = level.GetProp(position);
|
||||
if (prop.Type == EPropType.SprinklerControl)
|
||||
{
|
||||
if (!level.IsFloor(position))
|
||||
errors.Add(new("Sprinkler control must be placed on a floor cell.", position));
|
||||
|
||||
if (prop.LinkedPosition is not { } linked || !level.InBounds(linked) || level.GetProp(linked).Type != EPropType.SprinklerValve)
|
||||
errors.Add(new("Sprinkler control must link to exactly one sprinkler valve.", position));
|
||||
}
|
||||
|
||||
if (prop.Type != EPropType.SprinklerValve)
|
||||
continue;
|
||||
|
||||
if (level.GetTerrain(position) != ECellTerrain.Wall)
|
||||
errors.Add(new("Sprinkler valve must be wall-mounted.", position));
|
||||
|
||||
if (prop.OutletPosition is not { } outlet || !level.IsFloor(outlet) || position.ManhattanDistance(outlet) != 1)
|
||||
errors.Add(new("Sprinkler valve must have one adjacent floor outlet.", position));
|
||||
|
||||
if (!level.GetUnderground(position, ECarrierType.Water).IsPresent)
|
||||
errors.Add(new("Sprinkler valve must connect to a water underground cell.", position));
|
||||
|
||||
var linkedControls = LevelTraversal.AllPositions(level)
|
||||
.Count(controlPosition => level.GetProp(controlPosition) is { Type: EPropType.SprinklerControl, LinkedPosition: var linkedPosition } && linkedPosition == position);
|
||||
if (linkedControls != 1)
|
||||
errors.Add(new("Sprinkler valve must have exactly one linked control.", position));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateLeaks(LevelState level, List<ValidationIssue> errors)
|
||||
{
|
||||
foreach (var leak in level.Leaks)
|
||||
|
||||
@@ -9,6 +9,8 @@ public enum EPropType
|
||||
Junction,
|
||||
Door,
|
||||
AllSeeingEyeTerminal,
|
||||
SprinklerControl,
|
||||
SprinklerValve,
|
||||
RemedySupply,
|
||||
ReactorControl
|
||||
}
|
||||
@@ -24,6 +24,8 @@ public sealed record PropState
|
||||
public bool Depleted { get; init; }
|
||||
public int ReactorId { get; init; }
|
||||
public EDoorState DoorState { get; init; } = EDoorState.Closed;
|
||||
public GridPosition? LinkedPosition { get; init; }
|
||||
public GridPosition? OutletPosition { get; init; }
|
||||
|
||||
public bool IsEnabled => SwitchState == EPropSwitchState.Enabled;
|
||||
public bool IsOpen => SwitchState == EPropSwitchState.Enabled;
|
||||
|
||||
@@ -96,6 +96,7 @@ public sealed class SimulationEngine
|
||||
|
||||
var next = level;
|
||||
next = NetworkPropagationSystem.Propagate(next);
|
||||
next = SprinklerSystem.ApplyPressureDebt(next);
|
||||
next = ConsumerSystem.Resolve(next);
|
||||
next = StructuralIntegritySystem.Resolve(next);
|
||||
return next;
|
||||
@@ -104,6 +105,7 @@ public sealed class SimulationEngine
|
||||
private static LevelState ResolveStepContent(LevelState level)
|
||||
{
|
||||
var next = level;
|
||||
next = SprinklerSystem.Discharge(next);
|
||||
next = LeakSystem.Inject(next);
|
||||
next = SurfaceInteractionSystem.Resolve(next);
|
||||
next = next with { Global = next.Global with { Step = next.Global.Step + 1 } };
|
||||
|
||||
@@ -8,7 +8,7 @@ internal static class LeakSystem
|
||||
foreach (var leak in level.Leaks.Where(leak => !leak.Repaired))
|
||||
{
|
||||
var underground = level.GetUnderground(leak.UndergroundPosition, leak.Carrier);
|
||||
if (underground.State != EUndergroundState.Leaking)
|
||||
if (underground is not { State: EUndergroundState.Leaking, Amount: > 0, Intensity: > 0 })
|
||||
continue;
|
||||
|
||||
var accessIndex = level.Index(leak.AccessPosition);
|
||||
|
||||
@@ -27,17 +27,23 @@ internal static class PlayerActionSystem
|
||||
if (prop.Type == EPropType.None)
|
||||
return Refuse(level, "NO PROP");
|
||||
|
||||
var accepted = true;
|
||||
var next = prop.Type switch {
|
||||
EPropType.Flow or EPropType.Consumer or EPropType.IsolationValve => ToggleProp(level, position, prop),
|
||||
EPropType.SprinklerControl => ToggleProp(level, position, prop),
|
||||
EPropType.Junction => CycleJunctionMode(level, position, prop),
|
||||
EPropType.Door => ToggleDoor(level, position, prop),
|
||||
EPropType.AllSeeingEyeTerminal => level with { Global = level.Global with { Status = "ALL-SEEING-EYE AVAILABLE" } },
|
||||
EPropType.RemedySupply => PickUpRemedy(level, position, prop),
|
||||
EPropType.ReactorControl => ReactorSystem.Activate(level),
|
||||
_ => level
|
||||
_ => Refuse(level, "PROP NOT INTERACTIVE")
|
||||
};
|
||||
|
||||
return prop.Type == EPropType.AllSeeingEyeTerminal || prop.Type == EPropType.ReactorControl ? next : resolveLengthyAction(next);
|
||||
accepted = next.Global.Status != "PROP NOT INTERACTIVE";
|
||||
if (!accepted || prop.Type == EPropType.ReactorControl)
|
||||
return next;
|
||||
|
||||
return resolveLengthyAction(next);
|
||||
}
|
||||
|
||||
public static LevelState InteractLeak(LevelState level, ECarrierType carrier, bool useRemedy, Func<LevelState, LevelState> resolveLengthyAction)
|
||||
|
||||
63
src/ReactorMaintenance.Simulation/Systems/SprinklerSystem.cs
Normal file
63
src/ReactorMaintenance.Simulation/Systems/SprinklerSystem.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace ReactorMaintenance.Simulation;
|
||||
|
||||
internal static class SprinklerSystem
|
||||
{
|
||||
public static LevelState ApplyPressureDebt(LevelState level)
|
||||
{
|
||||
var water = level.Water.ToArray();
|
||||
foreach (var valvePosition in ActiveFedValvePositions(level))
|
||||
{
|
||||
var index = level.Index(valvePosition);
|
||||
water[index] = water[index] with {
|
||||
Intensity = Balancing.Current.ClampValue(water[index].Intensity - Balancing.Current.SprinklerPressureDebt)
|
||||
};
|
||||
}
|
||||
|
||||
return level with { Water = water };
|
||||
}
|
||||
|
||||
public static LevelState Discharge(LevelState level)
|
||||
{
|
||||
var surface = level.Surface.ToArray();
|
||||
foreach (var valvePosition in ActiveFedValvePositions(level))
|
||||
{
|
||||
var valve = level.GetProp(valvePosition);
|
||||
if (valve.OutletPosition is not { } outlet || !level.IsFloor(outlet))
|
||||
continue;
|
||||
|
||||
var index = level.Index(outlet);
|
||||
if (surface[index].Blocks(ECarrierType.Water))
|
||||
continue;
|
||||
|
||||
surface[index] = surface[index] with {
|
||||
Water = Balancing.Current.ClampValue(surface[index].Water + Balancing.Current.SprinklerWaterPerStep)
|
||||
};
|
||||
}
|
||||
|
||||
return level with { Surface = surface };
|
||||
}
|
||||
|
||||
private static IEnumerable<GridPosition> ActiveFedValvePositions(LevelState level)
|
||||
{
|
||||
foreach (var position in LevelTraversal.AllPositions(level))
|
||||
{
|
||||
var valve = level.GetProp(position);
|
||||
if (valve.Type != EPropType.SprinklerValve || !HasEnabledLinkedControl(level, position))
|
||||
continue;
|
||||
|
||||
var underground = level.GetUnderground(position, ECarrierType.Water);
|
||||
if (underground is { Amount: > 0, Intensity: > 0 })
|
||||
yield return position;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasEnabledLinkedControl(LevelState level, GridPosition valvePosition)
|
||||
{
|
||||
return LevelTraversal.AllPositions(level)
|
||||
.Any(position => level.GetProp(position) is {
|
||||
Type: EPropType.SprinklerControl,
|
||||
SwitchState: EPropSwitchState.Enabled,
|
||||
LinkedPosition: var linkedPosition
|
||||
} && linkedPosition == valvePosition);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user