Command seam fixes: the player's switches, ping and stance flips are PilotCommand presses; triggers, switches and ping are internal

ActivatedBehavior replaces IActivatedBehavior so Activate/Deactivate can be internal under Unity's C# 9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-22 04:33:20 +02:00
commit bc05417450
14 changed files with 70 additions and 66 deletions

View file

@ -87,10 +87,15 @@ public abstract class Behavior
}
}
public interface IActivatedBehavior
// A behaviour its pilot triggers. Only PilotApplier pulls and releases it, from the pilot's PilotCommand (Pull, Release),
// so Activate and Deactivate are internal to the simulation: no script outside it triggers one in place.
public abstract class ActivatedBehavior : Behavior
{
void Activate();
void Deactivate();
protected ActivatedBehavior(BehaviorData data, EquippedItem item) : base(data, item) { }
protected ActivatedBehavior(BehaviorData data, ConsumableItemEffect consumable) : base(data, consumable) { }
internal abstract void Activate();
internal abstract void Deactivate();
}
public interface IEventBehavior

View file

@ -167,7 +167,7 @@ public class ChargedWeapon : InstantWeapon
return base.Execute(dt);
}
public override void Activate()
internal override void Activate()
{
// CanFire also holds a weapon whose released charge has a burst pending: its cooldown starts at its first round,
// so a charge started before then would overlap it.
@ -180,7 +180,7 @@ public class ChargedWeapon : InstantWeapon
}
}
public override void Deactivate()
internal override void Deactivate()
{
if (_charging)
{

View file

@ -198,7 +198,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerC
return true;
}
public override void Activate()
internal override void Activate()
{
// A refused beam never starts (operator ruling 2026-09-30): OnStartFiring drives the beam's visuals and
// audio, so starting it only for Execute to stop it a tick later would flash a beam that fires nothing.
@ -209,7 +209,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerC
}
}
public override void Deactivate()
internal override void Deactivate()
{
if (_firing)
{

View file

@ -286,7 +286,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior, IPowerCo
return true;
}
public override void Activate()
internal override void Activate()
{
if(CanFire)
Trigger();

View file

@ -136,7 +136,7 @@ public class Sensor : Behavior, IEventBehavior, IPowerConsumer
// Cut 5 (docs/stats-and-power-cut.md §1.3, PowerTiers.cs): Utility -- everything else.
public int DefaultPowerTier => PowerTiers.Utility;
public void Ping()
internal void Ping()
{
// Whole-or-nothing against the ping's own input capacitor -- fires only at full charge (operator
// ruling: no fraction of a shot). A consumable-hosted instance (Item == null) has no PowerBus entry, so

View file

@ -20,11 +20,11 @@ public class SwitchData : BehaviorData
}
}
public class Switch : Behavior, IActivatedBehavior
public class Switch : ActivatedBehavior
{
private SwitchData _data;
public bool Activated { get; set; }
public bool Activated { get; private set; }
public Switch(SwitchData data, EquippedItem item) : base(data, item)
{
@ -40,12 +40,12 @@ public class Switch : Behavior, IActivatedBehavior
return Activated;
}
public void Activate()
internal override void Activate()
{
Activated = true;
}
public void Deactivate()
internal override void Deactivate()
{
Activated = false;
}

View file

@ -20,9 +20,9 @@ public class TriggerData : BehaviorData
}
}
public class Trigger : Behavior, IActivatedBehavior
public class Trigger : ActivatedBehavior
{
public bool _pulled;
private bool _pulled;
public Trigger(TriggerData data, EquippedItem item) : base(data, item) { }
public Trigger(TriggerData data, ConsumableItemEffect item) : base(data, item) { }
@ -38,12 +38,12 @@ public class Trigger : Behavior, IActivatedBehavior
return false;
}
public void Activate()
internal override void Activate()
{
_pulled = true;
}
public void Deactivate()
internal override void Deactivate()
{
}
}

View file

@ -100,7 +100,7 @@ public abstract class WeaponData : BehaviorData
public float RatedRange => Range.Max;
}
public abstract class Weapon : Behavior, IActivatedBehavior
public abstract class Weapon : ActivatedBehavior
{
private WeaponData _data;
@ -179,12 +179,12 @@ public abstract class Weapon : Behavior, IActivatedBehavior
return true;
}
public virtual void Activate()
internal override void Activate()
{
_firing = true;
}
public virtual void Deactivate()
internal override void Deactivate()
{
_firing = false;
}

View file

@ -173,7 +173,7 @@ public abstract class Entity
public bool HeatsinksEnabled
{
get => _heatsinksEnabled;
set
internal set
{
if (value == _heatsinksEnabled) return;
_heatsinksEnabled = value;
@ -186,7 +186,7 @@ public abstract class Entity
public EntitySettings Settings { get; }
public bool OverrideShutdown { get; set; }
public bool OverrideShutdown { get; internal set; }
// The pilot's tractor command (0..1); the Pickup behaviour spins its beam toward it.
public float TractorCommand { get; internal set; }
@ -2170,7 +2170,7 @@ public class EquippedItem : IStatContext
public ReactiveProperty<bool> ThermalOnline { get; } = new ReactiveProperty<bool>(false);
public ReactiveProperty<bool> DurabilityOnline { get; } = new ReactiveProperty<bool>(false);
public ReadOnlyReactiveProperty<bool> Online { get; }
public ReactiveProperty<bool> Enabled { get; } = new ReactiveProperty<bool>(true);
internal ReactiveProperty<bool> Enabled { get; } = new ReactiveProperty<bool>(true);
public ReadOnlyReactiveProperty<bool> Active { get; }
public ItemManager ItemManager { get; }

View file

@ -53,27 +53,35 @@ public sealed class PilotCommand
internal readonly List<EquippedItem> Selections = new List<EquippedItem>();
internal readonly List<string> Hails = new List<string>();
internal readonly List<ConsumableItemData> Uses = new List<ConsumableItemData>();
internal readonly List<(IActivatedBehavior Behavior, bool Pull)> Triggers = new List<(IActivatedBehavior, bool)>();
internal readonly List<(Entity Other, bool? Hostile)> Iffs = new List<(Entity, bool?)>();
internal readonly List<(ActivatedBehavior Behavior, bool Pull)> Triggers = new List<(ActivatedBehavior, bool)>();
internal readonly List<(Entity Other, bool? Hostile, bool Toggle)> Iffs = new List<(Entity, bool?, bool)>();
internal TargetVerb TargetVerb;
internal int AimCycles;
internal int ShutdownOverrideToggles, HeatsinkToggles, ShieldToggles, Pings;
public void Designate(TargetRef target) => Designations.Add(target);
public void SelectItem(EquippedItem item) => Selections.Add(item);
public void Hail(string message) => Hails.Add(message);
public void Use(ConsumableItemData consumable) => Uses.Add(consumable);
public void Pull(IActivatedBehavior behavior) => Triggers.Add((behavior, true));
public void Release(IActivatedBehavior behavior) => Triggers.Add((behavior, false));
public void Pull(ActivatedBehavior behavior) => Triggers.Add((behavior, true));
public void Release(ActivatedBehavior behavior) => Triggers.Add((behavior, false));
// The last verb in a step wins.
public void Verb(TargetVerb verb) => TargetVerb = verb;
public void CycleAim() => AimCycles++;
public void Iff(Entity other, bool? hostile) => Iffs.Add((other, hostile));
public void Iff(Entity other, bool? hostile) => Iffs.Add((other, hostile, false));
// A press that flips the stance on `other` as it stands when applied, in order with Iff, so two in a step cancel.
public void ToggleStance(Entity other) => Iffs.Add((other, null, true));
// The entity's own switches, pressed: each press flips its switch when applied, so two in a step cancel.
public void ToggleOverrideShutdown() => ShutdownOverrideToggles++;
public void ToggleHeatsinks() => HeatsinkToggles++;
public void ToggleShield() => ShieldToggles++;
public void Ping() => Pings++;
// The pilot's own pending decisions, which it reads back instead of the ship (that is last step's applied state).
public TargetRef? Designated => Designations.Count > 0 ? Designations[Designations.Count - 1] : (TargetRef?) null;
// Whether the last trigger this pilot commanded for `behavior` this step is a pull.
public bool Pulls(IActivatedBehavior behavior)
public bool Pulls(ActivatedBehavior behavior)
{
for (var i = Triggers.Count - 1; i >= 0; i--)
if (ReferenceEquals(Triggers[i].Behavior, behavior)) return Triggers[i].Pull;
@ -90,6 +98,7 @@ public sealed class PilotCommand
Iffs.Clear();
TargetVerb = TargetVerb.None;
AimCycles = 0;
ShutdownOverrideToggles = HeatsinkToggles = ShieldToggles = Pings = 0;
}
}
@ -112,7 +121,12 @@ public static class PilotApplier
}
foreach (var item in command.Selections) entity.TrySelectTargetItem(item);
for (var i = 0; i < command.AimCycles; i++) entity.CycleAim();
foreach (var (other, hostile) in command.Iffs) entity.SetIff(other, hostile);
foreach (var (other, hostile, toggle) in command.Iffs) entity.SetIff(other, toggle ? !entity.IsHostileTo(other) : hostile);
for (var i = 0; i < command.ShutdownOverrideToggles; i++) entity.OverrideShutdown = !entity.OverrideShutdown;
for (var i = 0; i < command.HeatsinkToggles; i++) entity.HeatsinksEnabled = !entity.HeatsinksEnabled;
if (entity.Shield != null)
for (var i = 0; i < command.ShieldToggles; i++) entity.Shield.Item.Enabled.Value = !entity.Shield.Item.Enabled.Value;
for (var i = 0; i < command.Pings; i++) entity.Sensor?.Ping();
foreach (var hail in command.Hails) entity.SetMessage(hail);
foreach (var consumable in command.Uses) entity.TryActivateConsumable(consumable);
if (command.Aim is float3 aim) entity.Aim = aim;