Implement surface safety and powered props

This commit is contained in:
2026-05-14 10:19:21 +02:00
parent 2ad7feef96
commit 830c7aef14
9 changed files with 267 additions and 29 deletions

View File

@@ -43,11 +43,14 @@ public abstract class Balancing
if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Electricity)
return Ignite(rowBand, colBand);
if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Water)
return Dilute(rowBand, colBand);
if (rowCarrier == ECarrierType.Fuel && colCarrier is null)
return rowBand == EBand.Critical || colBand == EBand.Critical ? Ignite(rowBand, colBand) : Warm(rowBand, colBand);
if (rowCarrier == ECarrierType.Water && colCarrier == ECarrierType.Electricity)
return Short(rowBand, colBand);
return Conduct(rowBand, colBand);
if (rowCarrier == ECarrierType.Water && colCarrier is null)
return Quench(rowBand, colBand);
@@ -88,14 +91,23 @@ public abstract class Balancing
};
}
private SurfaceInteractionEffect Short(EBand rowBand, EBand colBand)
private SurfaceInteractionEffect Dilute(EBand rowBand, EBand colBand)
{
return new() {
Verb = ESurfaceInteractionVerb.Dilute,
Quantity = ESurfaceQuantity.Fuel,
Amount = Strongest(rowBand, colBand) == EBand.Critical ? DiluteCriticalAmount : DiluteCautionAmount
};
}
private SurfaceInteractionEffect Conduct(EBand rowBand, EBand colBand)
{
var critical = Strongest(rowBand, colBand) == EBand.Critical;
return new() {
Verb = ESurfaceInteractionVerb.Short,
Verb = ESurfaceInteractionVerb.Conduct,
Quantity = ESurfaceQuantity.Electricity,
Amount = critical ? ShortCriticalHeat : ShortCautionHeat,
SecondaryAmount = critical ? ShortCriticalDischarge : ShortCautionDischarge
Amount = critical ? ConductCriticalHeat : ConductCautionHeat,
SecondaryAmount = critical ? ConductCriticalDischarge : ConductCautionDischarge
};
}
@@ -157,15 +169,21 @@ public abstract class Balancing
public abstract float LeakIntensityScale { get; }
public abstract float SprinklerWaterPerStep { get; }
public abstract float SprinklerPressureDebt { get; }
public abstract float AmbientEvaporationPerStep { get; }
public abstract float HeatEvaporationScale { get; }
public abstract float EvaporationCoolingScale { get; }
public abstract float FlowTransferRatio { get; }
public abstract float WetElectricityFlowMultiplier { get; }
public abstract float WarmCautionAmount { get; }
public abstract float WarmCriticalAmount { get; }
public abstract float DiluteCautionAmount { get; }
public abstract float DiluteCriticalAmount { get; }
public abstract float QuenchCautionAmount { get; }
public abstract float QuenchCriticalAmount { get; }
public abstract float ShortCautionHeat { get; }
public abstract float ShortCautionDischarge { get; }
public abstract float ShortCriticalHeat { get; }
public abstract float ShortCriticalDischarge { get; }
public abstract float ConductCautionHeat { get; }
public abstract float ConductCautionDischarge { get; }
public abstract float ConductCriticalHeat { get; }
public abstract float ConductCriticalDischarge { get; }
public abstract float IgniteCautionHeat { get; }
public abstract float IgniteCautionFuelConsumption { get; }
public abstract float IgniteCriticalHeat { get; }

View File

@@ -56,15 +56,21 @@ public class NormalBalancing : Balancing
public override float LeakIntensityScale => 0.1f;
public override float SprinklerWaterPerStep => 0.8f;
public override float SprinklerPressureDebt => 3.0f;
public override float AmbientEvaporationPerStep => 0.1f;
public override float HeatEvaporationScale => 0.08f;
public override float EvaporationCoolingScale => 0.25f;
public override float FlowTransferRatio => 0.05f;
public override float WetElectricityFlowMultiplier => 3.0f;
public override float WarmCautionAmount => 0.5f;
public override float WarmCriticalAmount => 1.0f;
public override float DiluteCautionAmount => 0.5f;
public override float DiluteCriticalAmount => 1.0f;
public override float QuenchCautionAmount => 0.6f;
public override float QuenchCriticalAmount => 1.2f;
public override float ShortCautionHeat => 0.8f;
public override float ShortCautionDischarge => 0.8f;
public override float ShortCriticalHeat => 1.6f;
public override float ShortCriticalDischarge => 1.5f;
public override float ConductCautionHeat => 0.2f;
public override float ConductCautionDischarge => 0.4f;
public override float ConductCriticalHeat => 0.5f;
public override float ConductCriticalDischarge => 0.8f;
public override float IgniteCautionHeat => 1.2f;
public override float IgniteCautionFuelConsumption => 0.4f;
public override float IgniteCriticalHeat => 2.4f;

View File

@@ -45,6 +45,27 @@ public static class LevelStateExtensions
return DoorBlocksEdge(level, a, b) || DoorBlocksEdge(level, b, a);
}
public static bool HasActivePoweredTerminalAccess(this LevelState level)
{
var position = level.Robot.Position;
return level.InBounds(position)
&& level.GetProp(position) is { Type: EPropType.AllSeeingEyeTerminal, Active: true }
&& level.IsPowered(position);
}
public static bool IsPowered(this LevelState level, GridPosition position)
{
if (!level.InBounds(position))
return false;
var current = level.GetUnderground(position, ECarrierType.Electricity);
if (current is { Amount: > 0, Intensity: > 0 })
return true;
var propagated = NetworkPropagationSystem.Propagate(level);
return propagated.GetUnderground(position, ECarrierType.Electricity) is { Amount: > 0, Intensity: > 0 };
}
public static LevelState SetTerrain(this LevelState level, GridPosition position, ECellTerrain terrain)
{
var next = level.Terrain.ToArray();

View File

@@ -4,8 +4,10 @@ public enum ESurfaceInteractionVerb
{
Hold,
Flow,
Dilute,
Warm,
Quench,
Short,
Evaporate,
Conduct,
Ignite
}

View File

@@ -24,6 +24,7 @@ public sealed record PropState
public bool Depleted { get; init; }
public int ReactorId { get; init; }
public EDoorState DoorState { get; init; } = EDoorState.Closed;
public bool Active { get; init; }
public GridPosition? LinkedPosition { get; init; }
public GridPosition? OutletPosition { get; init; }

View File

@@ -51,6 +51,9 @@ public sealed class SimulationEngine
public IReadOnlyList<Forecast> Forecast(LevelState level)
{
if (!level.HasActivePoweredTerminalAccess())
return Array.Empty<Forecast>();
return ForecastSystem.Forecast(level, simulated => ResolveStep(simulated, false));
}

View File

@@ -14,7 +14,8 @@ internal static class PlayerActionSystem
}
};
return RobotSafetySystem.ResolveEntry(next);
next = RobotSafetySystem.ResolveEntry(next);
return next.HasActivePoweredTerminalAccess() ? next : next with { Forecasts = Array.Empty<Forecast>() };
}
public static LevelState InteractProp(LevelState level, Func<LevelState, LevelState> resolveLengthyAction)
@@ -33,7 +34,7 @@ internal static class PlayerActionSystem
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.AllSeeingEyeTerminal => ActivateTerminal(level, position, prop),
EPropType.RemedySupply => PickUpRemedy(level, position, prop),
EPropType.ReactorControl => ReactorSystem.Activate(level),
_ => Refuse(level, "PROP NOT INTERACTIVE")
@@ -78,10 +79,21 @@ internal static class PlayerActionSystem
private static LevelState ToggleDoor(LevelState level, GridPosition position, PropState prop)
{
if (!level.IsPowered(position))
return level with { Global = level.Global with { Status = "DOOR UNPOWERED" } };
var doorState = prop.DoorState == EDoorState.Open ? EDoorState.Closed : EDoorState.Open;
return level.SetProp(position, prop with { DoorState = doorState });
}
private static LevelState ActivateTerminal(LevelState level, GridPosition position, PropState prop)
{
if (!level.IsPowered(position))
return level.SetProp(position, prop with { Active = false }) with { Global = level.Global with { Status = "TERMINAL UNPOWERED" } };
return level.SetProp(position, prop with { Active = true }) with { Global = level.Global with { Status = "ALL-SEEING-EYE AVAILABLE" } };
}
private static LevelState PickUpRemedy(LevelState level, GridPosition position, PropState prop)
{
if (prop.Depleted || level.Robot.Count(prop.RemedyType) >= Balancing.Current.InventoryCapacityPerRemedy)

View File

@@ -23,6 +23,12 @@ internal static class SurfaceInteractionSystem
public static LevelState Resolve(LevelState level)
{
var deltas = Enumerable.Range(0, level.Width * level.Height).Select(_ => new SurfaceDelta()).ToArray();
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
ApplyWaterMitigation(level, position, deltas);
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
ApplyEvaporation(level, position, deltas);
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
ApplySameCellInteractions(level, position, deltas);
@@ -62,9 +68,27 @@ internal static class SurfaceInteractionSystem
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), null, SimulationBands.Heat(surface.Heat), deltas);
ApplyPair(level, position, ECarrierType.Water, SimulationBands.Water(surface.Water), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
}
private static void ApplyWaterMitigation(LevelState level, GridPosition position, SurfaceDelta[] deltas)
{
var surface = level.GetSurface(position);
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), ECarrierType.Water, SimulationBands.Water(surface.Water), deltas);
ApplyPair(level, position, ECarrierType.Water, SimulationBands.Water(surface.Water), null, SimulationBands.Heat(surface.Heat), deltas);
}
private static void ApplyEvaporation(LevelState level, GridPosition position, SurfaceDelta[] deltas)
{
var surface = level.GetSurface(position);
if (surface.Water <= 0)
return;
var evaporated = Math.Min(surface.Water, Balancing.Current.AmbientEvaporationPerStep + (surface.Heat * Balancing.Current.HeatEvaporationScale));
var index = level.Index(position);
deltas[index].Water -= evaporated;
deltas[index].Heat -= evaporated * Balancing.Current.EvaporationCoolingScale;
}
private static void ApplyAdjacentInteractions(LevelState level, GridPosition a, GridPosition b, SurfaceDelta[] deltas)
{
var surfaceA = level.GetSurface(a);
@@ -88,10 +112,13 @@ internal static class SurfaceInteractionSystem
case ESurfaceInteractionVerb.Warm:
deltas[index].Heat += effect.Amount;
break;
case ESurfaceInteractionVerb.Dilute:
deltas[index].Fuel -= effect.Amount;
break;
case ESurfaceInteractionVerb.Quench:
deltas[index].Heat -= effect.Amount;
break;
case ESurfaceInteractionVerb.Short:
case ESurfaceInteractionVerb.Conduct:
deltas[index].Heat += effect.Amount;
deltas[index].Electricity -= effect.SecondaryAmount;
break;
@@ -109,6 +136,8 @@ internal static class SurfaceInteractionSystem
return;
var amount = difference * effect.Amount;
if (effect.Quantity == ESurfaceQuantity.Electricity && (level.GetSurface(a).Water > Balancing.Current.WaterSafe || level.GetSurface(b).Water > Balancing.Current.WaterSafe))
amount *= Balancing.Current.WetElectricityFlowMultiplier;
var indexA = level.Index(a);
var indexB = level.Index(b);