Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Электрический стул. #148

Merged
merged 7 commits into from
Dec 10, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: formatting
- remove useless comments
- if statement in OnSignalRecieved() should be handled in switch-case
- Logger class shouldn't be used in logging, there is sawmills for this
- Remove unnecessary methods
JerryImMouse committed Dec 10, 2024
commit 4284630cc1f345dc7ba01ad2c67faecad32d06ee
Original file line number Diff line number Diff line change
@@ -66,20 +66,20 @@ public sealed partial class ExecutionChairComponent : Component
/// The name of the device link port used to toggle the chair's state. Receiving a signal on this port
/// switches the enabled state from on to off or from off to on.
/// </summary>
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
[DataField]
public string TogglePort = "Toggle";

/// <summary>
/// The name of the device link port used to force the chair's state to enabled (on).
/// Receiving a signal here ensures the chair is active.
/// </summary>
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
[DataField]
public string OnPort = "On";

/// <summary>
/// The name of the device link port used to force the chair's state to disabled (off).
/// Receiving a signal here ensures the chair is inactive.
/// </summary>
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
[DataField]
public string OffPort = "Off";
}
104 changes: 18 additions & 86 deletions Content.Server/_CorvaxNext/ExecutionChair/ExecutionChairSystem.cs
Original file line number Diff line number Diff line change
@@ -8,142 +8,84 @@
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Content.Server._CorvaxNext.ExecutionChair;

namespace Content.Server._CorvaxNext.ExecutionChair
{
/// <summary>
/// This system manages the logic and state of the Execution Chair entity, including responding to
/// incoming signals, applying electrocution damage to entities strapped into it, and handling sound
/// and popups when it activates or deactivates.
/// </summary>
public sealed partial class ExecutionChairSystem : EntitySystem
{
// Dependencies automatically resolved by the IoC container.
[Dependency] private readonly IGameTiming _gameTimer = default!;
[Dependency] private readonly IRobustRandom _randomGen = default!;
[Dependency] private readonly DeviceLinkSystem _deviceSystem = default!;
[Dependency] private readonly ElectrocutionSystem _shockSystem = default!;
[Dependency] private readonly SharedAudioSystem _soundSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;

// Volume variation range for the shock sound effects to add some randomness.
private ISawmill _sawmill = default!;

private const float VolumeVariationMin = 0.8f;
private const float VolumeVariationMax = 1.2f;

/// <summary>
/// Initializes the system and sets up event subscriptions for when the chair is spawned
/// and when signals are received (e.g., toggle, on, off) from a device network.
/// </summary>
public override void Initialize()
{
base.Initialize();
SetupEventSubscriptions();
}

/// <summary>
/// Subscribes the system to relevant local events:
/// - MapInitEvent: when the chair is placed on the map, ensuring the correct device ports.
/// - SignalReceivedEvent: when the chair receives device link signals to turn on/off or toggle.
/// </summary>
private void SetupEventSubscriptions()
{
SubscribeLocalEvent<ExecutionChairComponent, MapInitEvent>(OnChairSpawned);
SubscribeLocalEvent<ExecutionChairComponent, SignalReceivedEvent>(OnSignalReceived);

_sawmill = Logger.GetSawmill("execution_chair");
}

/// <summary>
/// Called when the Execution Chair is initialized on the map. Ensures that the chair's
/// device link ports (Toggle, On, Off) are correctly created so it can receive signals.
/// </summary>
private void OnChairSpawned(EntityUid uid, ExecutionChairComponent component, ref MapInitEvent args)
{
// Ensure that all required device ports are available for linking.
_deviceSystem.EnsureSinkPorts(uid, component.TogglePort, component.OnPort, component.OffPort);
}

/// <summary>
/// Called when the Execution Chair receives a signal from linked devices.
/// Depending on the port signaled, the chair will toggle, turn on, or turn off.
/// Any unexpected port signals are logged.
/// </summary>
private void OnSignalReceived(EntityUid uid, ExecutionChairComponent component, ref SignalReceivedEvent args)
{
var portSignal = args.Port;
// default case for switch below
bool DefaultCase(EntityUid uid, string port, ExecutionChairComponent component) {
_sawmill.Debug($"Receieved unexpected port signal: {port} on chair {ToPrettyString(uid)}");
return component.Enabled;
}

// Determine new state based on received signal.
var newState = portSignal switch
var newState = args.Port switch
{
var p when p == component.TogglePort => !component.Enabled,
var p when p == component.OnPort => true,
var p when p == component.OffPort => false,
_ => component.Enabled // If port does not match expected, state remains unchanged.
_ => DefaultCase(uid, args.Port, component)
};

// Log a debug message if the port signal is unexpected.
if (portSignal != component.TogglePort && portSignal != component.OnPort && portSignal != component.OffPort)
{
Logger.DebugS("execution_chair", $"Received unexpected port signal: {portSignal} on chair {ToPrettyString(uid)}");
}

// Update the chair state based on the new determined state.
UpdateChairState(uid, newState, component);
}

/// <summary>
/// Updates the Execution Chair's active state (enabled or disabled), synchronizes that state,
/// and shows a popup message indicating the new state to nearby players.
/// </summary>
private void UpdateChairState(EntityUid uid, bool activated, ExecutionChairComponent? component = null)
{
// Resolve the component if not provided, ensuring we have a valid reference.
if (!Resolve(uid, ref component))
return;

component.Enabled = activated;

// Mark the component as "Dirty" so that any networked clients update their state.
Dirty(uid, component);
var message = activated
? Loc.GetString("execution-chair-turn-on")
: Loc.GetString("execution-chair-chair-turn-off");

// Display a popup message to indicate the chair has been turned on or off.
var message = activated
? Loc.GetString("execution-chair-turn-on")
: Loc.GetString("execution-chair-chair-turn-off");

_popup.PopupEntity(message, uid, PopupType.Medium);
}

/// <summary>
/// Called each frame (or tick). If a chair is active, powered, anchored, and has entities strapped in,
/// it attempts to electrocute those entities at regular intervals.
/// </summary>
public override void Update(float deltaTime)
public override void Update(float frameTime)
{
base.Update(deltaTime);
ProcessActiveChairs();
}
base.Update(frameTime);

/// <summary>
/// Iterates over all Execution Chairs currently in the game.
/// For each chair, if it is enabled, anchored, and powered, and if the time has come for the next damage tick,
/// applies an electrocution effect to all buckled entities.
/// </summary>
private void ProcessActiveChairs()
{
var query = EntityQueryEnumerator<ExecutionChairComponent>();

// Process each chair found in the world.
while (query.MoveNext(out var uid, out var chair))
{
// Validate that the chair can operate (is anchored, powered, enabled, and ready for next damage tick).
if (!ValidateChairOperation(uid, chair))
continue;

// Check if the chair has a StrapComponent and actually has entities buckled to it.
if (!TryComp<StrapComponent>(uid, out var restraint) || restraint.BuckledEntities.Count == 0)
continue;

// Apply shock damage and effects to all entities buckled into the chair.
ApplyShockEffect(uid, chair, restraint);
}
}
@@ -164,41 +106,31 @@ private bool ValidateChairOperation(EntityUid uid, ExecutionChairComponent chair
_gameTimer.CurTime >= chair.NextDamageTick;
}

/// <summary>
/// Attempts to electrocute all entities currently strapped to the chair, causing them damage.
/// If successful, plays shock sound effects (if configured).
/// After applying the shocks, sets the next damage tick to one second later.
/// </summary>
private void ApplyShockEffect(EntityUid uid, ExecutionChairComponent chair, StrapComponent restraint)
{
// Calculate the duration for which each shock is applied.
var shockDuration = TimeSpan.FromSeconds(chair.DamageTime);

// For each buckled entity, try to perform an electrocution action.
foreach (var target in restraint.BuckledEntities)
{
// Randomize volume a bit to make each shock sound slightly different.
var volumeModifier = _randomGen.NextFloat(VolumeVariationMin, VolumeVariationMax);

// Attempt to electrocute the target. Ignore insulation to ensure damage.
var shockSuccess = _shockSystem.TryDoElectrocution(
target,
uid,
chair.DamagePerTick,
shockDuration,
true,
volumeModifier,
ignoreInsulation: true);
ignoreInsulation: true
);

// If the shock was applied and chair is configured to play sounds, play shock sound.
if (shockSuccess && chair.PlaySoundOnShock && chair.ShockNoises != null)
{
var audioParams = AudioParams.Default.WithVolume(chair.ShockVolume);
_soundSystem.PlayPvs(chair.ShockNoises, target, audioParams);
}
}

// Schedule the next damage tick one second in the future.
chair.NextDamageTick = _gameTimer.CurTime + TimeSpan.FromSeconds(1);
}
}