Electrified grille sparks effect (#15178)
* use file namespace * shorter systems name * replace SoundSystem with AudioSystem * refactor update function * refactor * refactor 2 * remove setters * uh oh * remove getters * active checks * refactor 3 * better way * update state * have to remove this for now * move electrified component to shared * forgot this * fix airlocks * add effect * Revert "move electrified component to shared" This reverts commit 6457e8fc9c3b674a705a61034831ce6f084e2b01. * Revert "forgot this" This reverts commit ed361cee2d5b8b958830ba0af07fcc2627eb7845. * functioning effects * use animation by Aleksh * make effect part of grille * optimisation? * remove timing * file name * only activate when touched * refactor electrocution comp too * make it 1 sec * formatting * replace all entity query with enumerator * queue del
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Content.Server.Electrocution;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates every frame for short duration to check if electrifed entity is powered when activated, e.g to play animation
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ActivatedElectrifiedComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How long electrified entity will remain active
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public float TimeLeft = 1f;
|
||||||
|
}
|
||||||
@@ -1,77 +1,76 @@
|
|||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
namespace Content.Server.Electrocution
|
namespace Content.Server.Electrocution;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component for things that shock users on touch.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ElectrifiedComponent : Component
|
||||||
{
|
{
|
||||||
|
[DataField("enabled")]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
[DataField("onBump")]
|
||||||
|
public bool OnBump = true;
|
||||||
|
|
||||||
|
[DataField("onAttacked")]
|
||||||
|
public bool OnAttacked = true;
|
||||||
|
|
||||||
|
[DataField("noWindowInTile")]
|
||||||
|
public bool NoWindowInTile = false;
|
||||||
|
|
||||||
|
[DataField("onHandInteract")]
|
||||||
|
public bool OnHandInteract = true;
|
||||||
|
|
||||||
|
[DataField("onInteractUsing")]
|
||||||
|
public bool OnInteractUsing = true;
|
||||||
|
|
||||||
|
[DataField("requirePower")]
|
||||||
|
public bool RequirePower = true;
|
||||||
|
|
||||||
|
[DataField("usesApcPower")]
|
||||||
|
public bool UsesApcPower = false;
|
||||||
|
|
||||||
|
[DataField("highVoltageNode")]
|
||||||
|
public string? HighVoltageNode;
|
||||||
|
|
||||||
|
[DataField("mediumVoltageNode")]
|
||||||
|
public string? MediumVoltageNode;
|
||||||
|
|
||||||
|
[DataField("lowVoltageNode")]
|
||||||
|
public string? LowVoltageNode;
|
||||||
|
|
||||||
|
[DataField("highVoltageDamageMultiplier")]
|
||||||
|
public float HighVoltageDamageMultiplier = 3f;
|
||||||
|
|
||||||
|
[DataField("highVoltageTimeMultiplier")]
|
||||||
|
public float HighVoltageTimeMultiplier = 1.5f;
|
||||||
|
|
||||||
|
[DataField("mediumVoltageDamageMultiplier")]
|
||||||
|
public float MediumVoltageDamageMultiplier = 2f;
|
||||||
|
|
||||||
|
[DataField("mediumVoltageTimeMultiplier")]
|
||||||
|
public float MediumVoltageTimeMultiplier = 1.25f;
|
||||||
|
|
||||||
|
[DataField("shockDamage")]
|
||||||
|
public int ShockDamage = 20;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Component for things that shock users on touch.
|
/// Shock time, in seconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[DataField("shockTime")]
|
||||||
public sealed class ElectrifiedComponent : Component
|
public float ShockTime = 8f;
|
||||||
{
|
|
||||||
[DataField("enabled")]
|
|
||||||
public bool Enabled { get; set; } = true;
|
|
||||||
|
|
||||||
[DataField("onBump")]
|
[DataField("siemensCoefficient")]
|
||||||
public bool OnBump { get; set; } = true;
|
public float SiemensCoefficient = 1f;
|
||||||
|
|
||||||
[DataField("onAttacked")]
|
[DataField("shockNoises")]
|
||||||
public bool OnAttacked { get; set; } = true;
|
public SoundSpecifier ShockNoises = new SoundCollectionSpecifier("sparks");
|
||||||
|
|
||||||
[DataField("noWindowInTile")]
|
[DataField("playSoundOnShock")]
|
||||||
public bool NoWindowInTile { get; set; } = false;
|
public bool PlaySoundOnShock = true;
|
||||||
|
|
||||||
[DataField("onHandInteract")]
|
[DataField("shockVolume")]
|
||||||
public bool OnHandInteract { get; set; } = true;
|
public float ShockVolume = 20;
|
||||||
|
|
||||||
[DataField("onInteractUsing")]
|
|
||||||
public bool OnInteractUsing { get; set; } = true;
|
|
||||||
|
|
||||||
[DataField("requirePower")]
|
|
||||||
public bool RequirePower { get; } = true;
|
|
||||||
|
|
||||||
[DataField("usesApcPower")]
|
|
||||||
public bool UsesApcPower { get; } = false;
|
|
||||||
|
|
||||||
[DataField("highVoltageNode")]
|
|
||||||
public string? HighVoltageNode { get; }
|
|
||||||
|
|
||||||
[DataField("mediumVoltageNode")]
|
|
||||||
public string? MediumVoltageNode { get; }
|
|
||||||
|
|
||||||
[DataField("lowVoltageNode")]
|
|
||||||
public string? LowVoltageNode { get; }
|
|
||||||
|
|
||||||
[DataField("highVoltageDamageMultiplier")]
|
|
||||||
public float HighVoltageDamageMultiplier { get; } = 3f;
|
|
||||||
|
|
||||||
[DataField("highVoltageTimeMultiplier")]
|
|
||||||
public float HighVoltageTimeMultiplier { get; } = 1.5f;
|
|
||||||
|
|
||||||
[DataField("mediumVoltageDamageMultiplier")]
|
|
||||||
public float MediumVoltageDamageMultiplier { get; } = 2f;
|
|
||||||
|
|
||||||
[DataField("mediumVoltageTimeMultiplier")]
|
|
||||||
public float MediumVoltageTimeMultiplier { get; } = 1.25f;
|
|
||||||
|
|
||||||
[DataField("shockDamage")]
|
|
||||||
public int ShockDamage { get; } = 20;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shock time, in seconds.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("shockTime")]
|
|
||||||
public float ShockTime { get; } = 8f;
|
|
||||||
|
|
||||||
[DataField("siemensCoefficient")]
|
|
||||||
public float SiemensCoefficient { get; } = 1f;
|
|
||||||
|
|
||||||
[DataField("shockNoises")]
|
|
||||||
public SoundSpecifier ShockNoises { get; } = new SoundCollectionSpecifier("sparks");
|
|
||||||
|
|
||||||
[DataField("playSoundOnShock")]
|
|
||||||
public bool PlaySoundOnShock { get; } = true;
|
|
||||||
|
|
||||||
[DataField("shockVolume")]
|
|
||||||
public float ShockVolume { get; } = 20;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
namespace Content.Server.Electrocution
|
namespace Content.Server.Electrocution;
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Component for virtual electrocution entities (representing an in-progress shock).
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
[Access(typeof(ElectrocutionSystem))]
|
|
||||||
public sealed class ElectrocutionComponent : Component
|
|
||||||
{
|
|
||||||
[DataField("timeLeft")] public float TimeLeft { get; set; }
|
|
||||||
[DataField("electrocuting")] public EntityUid Electrocuting { get; set; }
|
|
||||||
[DataField("accumDamage")] public float AccumulatedDamage { get; set; }
|
|
||||||
[DataField("source")] public EntityUid Source { get; set; }
|
|
||||||
|
|
||||||
}
|
/// <summary>
|
||||||
|
/// Component for virtual electrocution entities (representing an in-progress shock).
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(ElectrocutionSystem))]
|
||||||
|
public sealed class ElectrocutionComponent : Component
|
||||||
|
{
|
||||||
|
[DataField("timeLeft")]
|
||||||
|
public float TimeLeft;
|
||||||
|
|
||||||
|
[DataField("electrocuting")]
|
||||||
|
public EntityUid Electrocuting;
|
||||||
|
|
||||||
|
[DataField("accumDamage")]
|
||||||
|
public float AccumulatedDamage;
|
||||||
|
|
||||||
|
[DataField("source")]
|
||||||
|
public EntityUid Source;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,107 +23,138 @@ using Content.Shared.Tag;
|
|||||||
using Content.Shared.Weapons.Melee;
|
using Content.Shared.Weapons.Melee;
|
||||||
using Content.Shared.Weapons.Melee.Events;
|
using Content.Shared.Weapons.Melee.Events;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Physics.Dynamics;
|
|
||||||
using Robust.Shared.Physics.Events;
|
using Robust.Shared.Physics.Events;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Server.Electrocution
|
namespace Content.Server.Electrocution;
|
||||||
|
|
||||||
|
public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||||
{
|
{
|
||||||
public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||||
|
[Dependency] private readonly SharedJitteringSystem _jittering = default!;
|
||||||
|
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||||
|
[Dependency] private readonly SharedStutteringSystem _stuttering = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||||
|
[Dependency] private readonly NodeGroupSystem _nodeGroup = default!;
|
||||||
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
|
||||||
|
private const string StatusEffectKey = "Electrocution";
|
||||||
|
private const string DamageType = "Shock";
|
||||||
|
|
||||||
|
// Yes, this is absurdly small for a reason.
|
||||||
|
private const float ElectrifiedDamagePerWatt = 0.0015f;
|
||||||
|
|
||||||
|
private const float RecursiveDamageMultiplier = 0.75f;
|
||||||
|
private const float RecursiveTimeMultiplier = 0.8f;
|
||||||
|
|
||||||
|
private const float ParalyzeTimeMultiplier = 1f;
|
||||||
|
|
||||||
|
private const float StutteringTimeMultiplier = 1.5f;
|
||||||
|
|
||||||
|
private const float JitterTimeMultiplier = 0.75f;
|
||||||
|
private const float JitterAmplitude = 80f;
|
||||||
|
private const float JitterFrequency = 8f;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
base.Initialize();
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
|
||||||
[Dependency] private readonly SharedJitteringSystem _jitteringSystem = default!;
|
|
||||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
|
||||||
[Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!;
|
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
|
||||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
|
||||||
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
|
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger= default!;
|
|
||||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
|
||||||
|
|
||||||
private const string StatusEffectKey = "Electrocution";
|
SubscribeLocalEvent<ElectrifiedComponent, StartCollideEvent>(OnElectrifiedStartCollide);
|
||||||
private const string DamageType = "Shock";
|
SubscribeLocalEvent<ElectrifiedComponent, AttackedEvent>(OnElectrifiedAttacked);
|
||||||
|
SubscribeLocalEvent<ElectrifiedComponent, InteractHandEvent>(OnElectrifiedHandInteract);
|
||||||
|
SubscribeLocalEvent<ElectrifiedComponent, InteractUsingEvent>(OnElectrifiedInteractUsing);
|
||||||
|
SubscribeLocalEvent<RandomInsulationComponent, MapInitEvent>(OnRandomInsulationMapInit);
|
||||||
|
|
||||||
// Yes, this is absurdly small for a reason.
|
UpdatesAfter.Add(typeof(PowerNetSystem));
|
||||||
private const float ElectrifiedDamagePerWatt = 0.0015f;
|
}
|
||||||
|
|
||||||
private const float RecursiveDamageMultiplier = 0.75f;
|
public override void Update(float frameTime)
|
||||||
private const float RecursiveTimeMultiplier = 0.8f;
|
{
|
||||||
|
UpdateElectrocutions(frameTime);
|
||||||
|
UpdateState(frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
private const float ParalyzeTimeMultiplier = 1f;
|
private void UpdateElectrocutions(float frameTime)
|
||||||
|
{
|
||||||
private const float StutteringTimeMultiplier = 1.5f;
|
var query = EntityQueryEnumerator<ElectrocutionComponent, PowerConsumerComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var electrocution, out var consumer))
|
||||||
private const float JitterTimeMultiplier = 0.75f;
|
|
||||||
private const float JitterAmplitude = 80f;
|
|
||||||
private const float JitterFrequency = 8f;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
{
|
||||||
base.Initialize();
|
var timePassed = Math.Min(frameTime, electrocution.TimeLeft);
|
||||||
|
|
||||||
SubscribeLocalEvent<ElectrifiedComponent, StartCollideEvent>(OnElectrifiedStartCollide);
|
electrocution.TimeLeft -= timePassed;
|
||||||
SubscribeLocalEvent<ElectrifiedComponent, AttackedEvent>(OnElectrifiedAttacked);
|
electrocution.AccumulatedDamage += consumer.ReceivedPower * ElectrifiedDamagePerWatt * timePassed;
|
||||||
SubscribeLocalEvent<ElectrifiedComponent, InteractHandEvent>(OnElectrifiedHandInteract);
|
|
||||||
SubscribeLocalEvent<ElectrifiedComponent, InteractUsingEvent>(OnElectrifiedInteractUsing);
|
|
||||||
SubscribeLocalEvent<RandomInsulationComponent, MapInitEvent>(OnRandomInsulationMapInit);
|
|
||||||
|
|
||||||
UpdatesAfter.Add(typeof(PowerNetSystem));
|
if (!MathHelper.CloseTo(electrocution.TimeLeft, 0))
|
||||||
}
|
continue;
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
if (EntityManager.EntityExists(electrocution.Electrocuting))
|
||||||
{
|
|
||||||
// Update "in progress" electrocutions
|
|
||||||
|
|
||||||
RemQueue<ElectrocutionComponent> finishedElectrocutionsQueue = new();
|
|
||||||
foreach (var (electrocution, consumer) in EntityManager
|
|
||||||
.EntityQuery<ElectrocutionComponent, PowerConsumerComponent>())
|
|
||||||
{
|
{
|
||||||
var ftAdjusted = Math.Min(frameTime, electrocution.TimeLeft);
|
// TODO: damage should be scaled by shock damage multiplier
|
||||||
|
// TODO: better paralyze/jitter timing
|
||||||
|
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(DamageType), (int) electrocution.AccumulatedDamage);
|
||||||
|
|
||||||
electrocution.TimeLeft -= ftAdjusted;
|
var actual = _damageable.TryChangeDamage(electrocution.Electrocuting, damage, origin: electrocution.Source);
|
||||||
electrocution.AccumulatedDamage += consumer.ReceivedPower * ElectrifiedDamagePerWatt * ftAdjusted;
|
if (actual != null)
|
||||||
|
|
||||||
if (MathHelper.CloseTo(electrocution.TimeLeft, 0))
|
|
||||||
finishedElectrocutionsQueue.Add(electrocution);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var finished in finishedElectrocutionsQueue)
|
|
||||||
{
|
|
||||||
var uid = finished.Owner;
|
|
||||||
if (EntityManager.EntityExists(finished.Electrocuting))
|
|
||||||
{
|
{
|
||||||
// TODO: damage should be scaled by shock damage multiplier
|
_adminLogger.Add(LogType.Electrocution,
|
||||||
// TODO: better paralyze/jitter timing
|
$"{ToPrettyString(electrocution.Electrocuting):entity} received {actual.Total:damage} powered electrocution damage from {ToPrettyString(electrocution.Source):source}");
|
||||||
var damage = new DamageSpecifier(
|
|
||||||
_prototypeManager.Index<DamageTypePrototype>(DamageType),
|
|
||||||
(int) finished.AccumulatedDamage);
|
|
||||||
|
|
||||||
var actual = _damageableSystem.TryChangeDamage(finished.Electrocuting, damage, origin: finished.Source);
|
|
||||||
if (actual != null)
|
|
||||||
{
|
|
||||||
_adminLogger.Add(LogType.Electrocution,
|
|
||||||
$"{ToPrettyString(finished.Electrocuting):entity} received {actual.Total:damage} powered electrocution damage from {ToPrettyString(finished.Source):source}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
QueueDel(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EntityManager.DeleteEntity(uid);
|
private void UpdateState(float frameTime)
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<ActivatedElectrifiedComponent, ElectrifiedComponent, TransformComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var activated, out var electrified, out var transform))
|
||||||
|
{
|
||||||
|
activated.TimeLeft -= frameTime;
|
||||||
|
if (activated.TimeLeft <= 0 || !IsPowered(uid, electrified, transform))
|
||||||
|
{
|
||||||
|
_appearance.SetData(uid, ElectrifiedVisuals.IsPowered, false);
|
||||||
|
RemComp<ActivatedElectrifiedComponent>(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnElectrifiedStartCollide(EntityUid uid, ElectrifiedComponent electrified, ref StartCollideEvent args)
|
private bool IsPowered(EntityUid uid, ElectrifiedComponent electrified, TransformComponent transform)
|
||||||
|
{
|
||||||
|
if (!electrified.Enabled)
|
||||||
|
return false;
|
||||||
|
if (electrified.NoWindowInTile)
|
||||||
{
|
{
|
||||||
if (!electrified.OnBump)
|
foreach (var entity in transform.Coordinates.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, _entityLookup))
|
||||||
return;
|
{
|
||||||
|
if (_tag.HasTag(entity, "Window"))
|
||||||
TryDoElectrifiedAct(uid, args.OtherFixture.Body.Owner, 1, electrified);
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (electrified.UsesApcPower)
|
||||||
|
{
|
||||||
|
if (!this.IsPowered(uid, EntityManager))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (electrified.RequirePower && PoweredNode(uid, electrified) == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnElectrifiedStartCollide(EntityUid uid, ElectrifiedComponent electrified, ref StartCollideEvent args)
|
||||||
|
{
|
||||||
|
if (electrified.OnBump)
|
||||||
|
TryDoElectrifiedAct(uid, args.OtherFixture.Body.Owner, 1, electrified);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnElectrifiedAttacked(EntityUid uid, ElectrifiedComponent electrified, AttackedEvent args)
|
private void OnElectrifiedAttacked(EntityUid uid, ElectrifiedComponent electrified, AttackedEvent args)
|
||||||
{
|
{
|
||||||
@@ -138,129 +169,112 @@ namespace Content.Server.Electrocution
|
|||||||
}
|
}
|
||||||
|
|
||||||
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnElectrifiedHandInteract(EntityUid uid, ElectrifiedComponent electrified, InteractHandEvent args)
|
|
||||||
{
|
|
||||||
if (!electrified.OnHandInteract)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
private void OnElectrifiedHandInteract(EntityUid uid, ElectrifiedComponent electrified, InteractHandEvent args)
|
||||||
|
{
|
||||||
|
if (electrified.OnHandInteract)
|
||||||
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
TryDoElectrifiedAct(uid, args.User, 1, electrified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args)
|
private void OnElectrifiedInteractUsing(EntityUid uid, ElectrifiedComponent electrified, InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (!electrified.OnInteractUsing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var siemens = TryComp<InsulatedComponent>(args.Used, out var insulation)
|
||||||
|
? insulation.SiemensCoefficient
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
TryDoElectrifiedAct(uid, args.User, siemens, electrified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
||||||
|
float siemens = 1,
|
||||||
|
ElectrifiedComponent? electrified = null,
|
||||||
|
NodeContainerComponent? nodeContainer = null,
|
||||||
|
TransformComponent? transform = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref electrified, ref transform, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!IsPowered(uid, electrified, transform))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EnsureComp<ActivatedElectrifiedComponent>(uid);
|
||||||
|
_appearance.SetData(uid, ElectrifiedVisuals.IsPowered, true);
|
||||||
|
|
||||||
|
siemens *= electrified.SiemensCoefficient;
|
||||||
|
if (siemens <= 0 || !DoCommonElectrocutionAttempt(targetUid, uid, ref siemens))
|
||||||
|
return false; // If electrocution would fail, do nothing.
|
||||||
|
|
||||||
|
var targets = new List<(EntityUid entity, int depth)>();
|
||||||
|
GetChainedElectrocutionTargets(targetUid, targets);
|
||||||
|
if (!electrified.RequirePower || electrified.UsesApcPower)
|
||||||
{
|
{
|
||||||
if (!electrified.OnInteractUsing)
|
var lastRet = true;
|
||||||
return;
|
for (var i = targets.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
var siemens = TryComp(args.Used, out InsulatedComponent? insulation)
|
var (entity, depth) = targets[i];
|
||||||
? insulation.SiemensCoefficient
|
lastRet = TryDoElectrocution(
|
||||||
: 1;
|
entity,
|
||||||
|
uid,
|
||||||
TryDoElectrifiedAct(uid, args.User, siemens, electrified);
|
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth)),
|
||||||
|
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth)),
|
||||||
|
true,
|
||||||
|
electrified.SiemensCoefficient
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return lastRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid,
|
var node = PoweredNode(uid, electrified, nodeContainer);
|
||||||
float siemens = 1,
|
if (node == null)
|
||||||
ElectrifiedComponent? electrified = null,
|
return false;
|
||||||
NodeContainerComponent? nodeContainer = null,
|
|
||||||
TransformComponent? transform = null)
|
var (damageMult, timeMult) = node.NodeGroupID switch
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref electrified, ref transform, false))
|
NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier),
|
||||||
return false;
|
NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier, electrified.MediumVoltageTimeMultiplier),
|
||||||
|
_ => (1f, 1f)
|
||||||
|
};
|
||||||
|
|
||||||
if (!electrified.Enabled)
|
{
|
||||||
return false;
|
var lastRet = true;
|
||||||
|
for (var i = targets.Count - 1; i >= 0; i--)
|
||||||
if (electrified.NoWindowInTile)
|
|
||||||
{
|
{
|
||||||
foreach (var entity in transform.Coordinates.GetEntitiesInTile(
|
var (entity, depth) = targets[i];
|
||||||
LookupFlags.Approximate | LookupFlags.Static, _entityLookup))
|
lastRet = TryDoElectrocutionPowered(
|
||||||
{
|
entity,
|
||||||
if (_tagSystem.HasTag(entity, "Window"))
|
uid,
|
||||||
return false;
|
node,
|
||||||
}
|
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageMult),
|
||||||
}
|
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) * timeMult),
|
||||||
|
true,
|
||||||
siemens *= electrified.SiemensCoefficient;
|
electrified.SiemensCoefficient);
|
||||||
if (!DoCommonElectrocutionAttempt(targetUid, uid, ref siemens) || siemens <= 0)
|
|
||||||
return false; // If electrocution would fail, do nothing.
|
|
||||||
|
|
||||||
var targets = new List<(EntityUid entity, int depth)>();
|
|
||||||
GetChainedElectrocutionTargets(targetUid, targets);
|
|
||||||
if (!electrified.RequirePower || electrified.UsesApcPower)
|
|
||||||
{
|
|
||||||
// Does it use APC power for its electrification check? Check if it's powered, and then
|
|
||||||
// attempt an electrocution if all the checks succeed.
|
|
||||||
|
|
||||||
if (electrified.UsesApcPower && !this.IsPowered(uid, EntityManager))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastRet = true;
|
|
||||||
for (var i = targets.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var (entity, depth) = targets[i];
|
|
||||||
lastRet = TryDoElectrocution(
|
|
||||||
entity,
|
|
||||||
uid,
|
|
||||||
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth)),
|
|
||||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth)), true,
|
|
||||||
electrified.SiemensCoefficient);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Resolve(uid, ref nodeContainer, false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var node = TryNode(electrified.HighVoltageNode) ??
|
|
||||||
TryNode(electrified.MediumVoltageNode) ??
|
|
||||||
TryNode(electrified.LowVoltageNode);
|
|
||||||
|
|
||||||
if (node == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var (damageMult, timeMult) = node.NodeGroupID switch
|
|
||||||
{
|
|
||||||
NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier),
|
|
||||||
NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier,
|
|
||||||
electrified.MediumVoltageTimeMultiplier),
|
|
||||||
_ => (1f, 1f)
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
var lastRet = true;
|
|
||||||
for (var i = targets.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var (entity, depth) = targets[i];
|
|
||||||
lastRet = TryDoElectrocutionPowered(
|
|
||||||
entity,
|
|
||||||
uid,
|
|
||||||
node,
|
|
||||||
(int) (electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageMult),
|
|
||||||
TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) *
|
|
||||||
timeMult), true,
|
|
||||||
electrified.SiemensCoefficient);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Node? TryNode(string? id)
|
|
||||||
{
|
|
||||||
if (id != null && nodeContainer.TryGetNode<Node>(id, out var tryNode)
|
|
||||||
&& tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastCombinedSupply: >0 } })
|
|
||||||
{
|
|
||||||
return tryNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return lastRet;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node? PoweredNode(EntityUid uid, ElectrifiedComponent electrified, NodeContainerComponent? nodeContainer = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref nodeContainer, false))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return TryNode(electrified.HighVoltageNode) ?? TryNode(electrified.MediumVoltageNode) ?? TryNode(electrified.LowVoltageNode);
|
||||||
|
|
||||||
|
Node? TryNode(string? id)
|
||||||
|
{
|
||||||
|
if (id != null &&
|
||||||
|
nodeContainer.TryGetNode<Node>(id, out var tryNode) &&
|
||||||
|
tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastCombinedSupply: > 0 } })
|
||||||
|
{
|
||||||
|
return tryNode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <param name="uid">Entity being electrocuted.</param>
|
/// <param name="uid">Entity being electrocuted.</param>
|
||||||
/// <param name="sourceUid">Source entity of the electrocution.</param>
|
/// <param name="sourceUid">Source entity of the electrocution.</param>
|
||||||
@@ -283,184 +297,178 @@ namespace Content.Server.Electrocution
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryDoElectrocutionPowered(
|
private bool TryDoElectrocutionPowered(
|
||||||
EntityUid uid,
|
EntityUid uid,
|
||||||
EntityUid sourceUid,
|
EntityUid sourceUid,
|
||||||
Node node,
|
Node node,
|
||||||
int shockDamage,
|
int shockDamage,
|
||||||
TimeSpan time,
|
TimeSpan time,
|
||||||
bool refresh,
|
bool refresh,
|
||||||
float siemensCoefficient = 1f,
|
float siemensCoefficient = 1f,
|
||||||
StatusEffectsComponent? statusEffects = null,
|
StatusEffectsComponent? statusEffects = null,
|
||||||
TransformComponent? sourceTransform = null)
|
TransformComponent? sourceTransform = null)
|
||||||
{
|
{
|
||||||
if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient))
|
if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Coefficient needs to be higher than this to do a powered electrocution!
|
// Coefficient needs to be higher than this to do a powered electrocution!
|
||||||
if (siemensCoefficient <= 0.5f)
|
if (siemensCoefficient <= 0.5f)
|
||||||
return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects);
|
return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects);
|
||||||
|
|
||||||
if (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects))
|
if (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!Resolve(sourceUid, ref sourceTransform)) // This shouldn't really happen, but just in case...
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var electrocutionEntity = EntityManager.SpawnEntity(
|
|
||||||
$"VirtualElectrocutionLoad{node.NodeGroupID}", sourceTransform.Coordinates);
|
|
||||||
|
|
||||||
var electrocutionNode = EntityManager.GetComponent<NodeContainerComponent>(electrocutionEntity)
|
|
||||||
.GetNode<ElectrocutionNode>("electrocution");
|
|
||||||
|
|
||||||
var electrocutionComponent = EntityManager.GetComponent<ElectrocutionComponent>(electrocutionEntity);
|
|
||||||
|
|
||||||
electrocutionNode.CableEntity = sourceUid;
|
|
||||||
electrocutionNode.NodeName = node.Name;
|
|
||||||
|
|
||||||
_nodeGroupSystem.QueueReflood(electrocutionNode);
|
|
||||||
|
|
||||||
electrocutionComponent.TimeLeft = 1f;
|
|
||||||
electrocutionComponent.Electrocuting = uid;
|
|
||||||
electrocutionComponent.Source = sourceUid;
|
|
||||||
|
|
||||||
RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient), true);
|
|
||||||
|
|
||||||
|
if (!Resolve(sourceUid, ref sourceTransform)) // This shouldn't really happen, but just in case...
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
private bool DoCommonElectrocutionAttempt(EntityUid uid, EntityUid? sourceUid, ref float siemensCoefficient, bool ignoreInsulation = false)
|
var electrocutionEntity = Spawn($"VirtualElectrocutionLoad{node.NodeGroupID}", sourceTransform.Coordinates);
|
||||||
|
var electrocutionNode = Comp<NodeContainerComponent>(electrocutionEntity).GetNode<ElectrocutionNode>("electrocution");
|
||||||
|
var electrocutionComponent = Comp<ElectrocutionComponent>(electrocutionEntity);
|
||||||
|
|
||||||
|
electrocutionNode.CableEntity = sourceUid;
|
||||||
|
electrocutionNode.NodeName = node.Name;
|
||||||
|
|
||||||
|
_nodeGroup.QueueReflood(electrocutionNode);
|
||||||
|
|
||||||
|
electrocutionComponent.TimeLeft = 1f;
|
||||||
|
electrocutionComponent.Electrocuting = uid;
|
||||||
|
electrocutionComponent.Source = sourceUid;
|
||||||
|
|
||||||
|
RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient), true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DoCommonElectrocutionAttempt(EntityUid uid, EntityUid? sourceUid, ref float siemensCoefficient, bool ignoreInsulation = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
var attemptEvent = new ElectrocutionAttemptEvent(uid, sourceUid, siemensCoefficient,
|
||||||
|
ignoreInsulation ? SlotFlags.NONE : ~SlotFlags.POCKET);
|
||||||
|
RaiseLocalEvent(uid, attemptEvent, true);
|
||||||
|
|
||||||
|
// Cancel the electrocution early, so we don't recursively electrocute anything.
|
||||||
|
if (attemptEvent.Cancelled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
siemensCoefficient = attemptEvent.SiemensCoefficient;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DoCommonElectrocution(EntityUid uid, EntityUid? sourceUid,
|
||||||
|
int? shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f,
|
||||||
|
StatusEffectsComponent? statusEffects = null)
|
||||||
|
{
|
||||||
|
if (siemensCoefficient <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (shockDamage != null)
|
||||||
{
|
{
|
||||||
|
shockDamage = (int) (shockDamage * siemensCoefficient);
|
||||||
|
|
||||||
var attemptEvent = new ElectrocutionAttemptEvent(uid, sourceUid, siemensCoefficient,
|
if (shockDamage.Value <= 0)
|
||||||
ignoreInsulation ? SlotFlags.NONE : ~SlotFlags.POCKET);
|
|
||||||
RaiseLocalEvent(uid, attemptEvent, true);
|
|
||||||
|
|
||||||
// Cancel the electrocution early, so we don't recursively electrocute anything.
|
|
||||||
if (attemptEvent.Cancelled)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
siemensCoefficient = attemptEvent.SiemensCoefficient;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DoCommonElectrocution(EntityUid uid, EntityUid? sourceUid,
|
if (!Resolve(uid, ref statusEffects, false) ||
|
||||||
int? shockDamage, TimeSpan time, bool refresh, float siemensCoefficient = 1f,
|
!_statusEffects.CanApplyEffect(uid, StatusEffectKey, statusEffects))
|
||||||
StatusEffectsComponent? statusEffects = null)
|
|
||||||
{
|
{
|
||||||
if (siemensCoefficient <= 0)
|
return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (shockDamage != null)
|
|
||||||
{
|
|
||||||
shockDamage = (int) (shockDamage * siemensCoefficient);
|
|
||||||
|
|
||||||
if (shockDamage.Value <= 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Resolve(uid, ref statusEffects, false) ||
|
|
||||||
!_statusEffectsSystem.CanApplyEffect(uid, StatusEffectKey, statusEffects))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!_statusEffectsSystem.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh,
|
|
||||||
statusEffects))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var shouldStun = siemensCoefficient > 0.5f;
|
|
||||||
|
|
||||||
if (shouldStun)
|
|
||||||
_stunSystem.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects);
|
|
||||||
|
|
||||||
// TODO: Sparks here.
|
|
||||||
|
|
||||||
if(shockDamage is {} dmg)
|
|
||||||
{
|
|
||||||
var actual = _damageableSystem.TryChangeDamage(uid,
|
|
||||||
new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(DamageType), dmg), origin: sourceUid);
|
|
||||||
|
|
||||||
if (actual != null)
|
|
||||||
{
|
|
||||||
_adminLogger.Add(LogType.Electrocution,
|
|
||||||
$"{ToPrettyString(statusEffects.Owner):entity} received {actual.Total:damage} powered electrocution damage{(sourceUid != null ? " from " + ToPrettyString(sourceUid.Value) : ""):source}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_stutteringSystem.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects);
|
|
||||||
_jitteringSystem.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true,
|
|
||||||
statusEffects);
|
|
||||||
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid, uid);
|
|
||||||
|
|
||||||
var filter = Filter.PvsExcept(uid, entityManager: EntityManager);
|
|
||||||
|
|
||||||
// TODO: Allow being able to pass EntityUid to Loc...
|
|
||||||
if (sourceUid != null)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-by-source-popup-others",
|
|
||||||
("mob", uid), ("source", (sourceUid.Value))), uid, filter, true);
|
|
||||||
PlayElectrocutionSound(uid, sourceUid.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-others",
|
|
||||||
("mob", uid)), uid, filter, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh, statusEffects))
|
||||||
|
return false;
|
||||||
|
|
||||||
private void GetChainedElectrocutionTargets(EntityUid source, List<(EntityUid entity, int depth)> all)
|
var shouldStun = siemensCoefficient > 0.5f;
|
||||||
|
|
||||||
|
if (shouldStun)
|
||||||
|
_stun.TryParalyze(uid, time * ParalyzeTimeMultiplier, refresh, statusEffects);
|
||||||
|
|
||||||
|
// TODO: Sparks here.
|
||||||
|
|
||||||
|
if (shockDamage is { } dmg)
|
||||||
{
|
{
|
||||||
var visited = new HashSet<EntityUid>();
|
var actual = _damageable.TryChangeDamage(uid,
|
||||||
|
new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>(DamageType), dmg), origin: sourceUid);
|
||||||
|
|
||||||
GetChainedElectrocutionTargetsRecurse(source, 1, visited, all);
|
if (actual != null)
|
||||||
}
|
|
||||||
|
|
||||||
private void GetChainedElectrocutionTargetsRecurse(
|
|
||||||
EntityUid entity,
|
|
||||||
int depth,
|
|
||||||
HashSet<EntityUid> visited,
|
|
||||||
List<(EntityUid entity, int depth)> all)
|
|
||||||
{
|
|
||||||
all.Add((entity, depth));
|
|
||||||
visited.Add(entity);
|
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(entity, out SharedPullableComponent? pullable)
|
|
||||||
&& pullable.Puller is {Valid: true} pullerId
|
|
||||||
&& !visited.Contains(pullerId))
|
|
||||||
{
|
{
|
||||||
GetChainedElectrocutionTargetsRecurse(pullerId, depth + 1, visited, all);
|
_adminLogger.Add(LogType.Electrocution,
|
||||||
}
|
$"{ToPrettyString(uid):entity} received {actual.Total:damage} powered electrocution damage{(sourceUid != null ? " from " + ToPrettyString(sourceUid.Value) : ""):source}");
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(entity, out SharedPullerComponent? puller)
|
|
||||||
&& puller.Pulling is {Valid: true} pullingId
|
|
||||||
&& !visited.Contains(pullingId))
|
|
||||||
{
|
|
||||||
GetChainedElectrocutionTargetsRecurse(pullingId, depth + 1, visited, all);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRandomInsulationMapInit(EntityUid uid, RandomInsulationComponent randomInsulation,
|
_stuttering.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects);
|
||||||
MapInitEvent args)
|
_jittering.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true, statusEffects);
|
||||||
|
|
||||||
|
_popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid, uid);
|
||||||
|
|
||||||
|
var filter = Filter.PvsExcept(uid, entityManager: EntityManager);
|
||||||
|
|
||||||
|
// TODO: Allow being able to pass EntityUid to Loc...
|
||||||
|
if (sourceUid != null)
|
||||||
{
|
{
|
||||||
if (!EntityManager.TryGetComponent(uid, out InsulatedComponent? insulated))
|
_popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-by-source-popup-others",
|
||||||
return;
|
("mob", uid), ("source", (sourceUid.Value))), uid, filter, true);
|
||||||
|
PlayElectrocutionSound(uid, sourceUid.Value);
|
||||||
if (randomInsulation.List.Length == 0)
|
}
|
||||||
return;
|
else
|
||||||
|
{
|
||||||
SetInsulatedSiemensCoefficient(uid, _random.Pick(randomInsulation.List), insulated);
|
_popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-others",
|
||||||
|
("mob", uid)), uid, filter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayElectrocutionSound(EntityUid targetUid, EntityUid sourceUid, ElectrifiedComponent? electrified = null)
|
return true;
|
||||||
{
|
}
|
||||||
if (!Resolve(sourceUid, ref electrified) || !electrified.PlaySoundOnShock)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SoundSystem.Play(electrified.ShockNoises.GetSound(), Filter.Pvs(targetUid), targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
|
private void GetChainedElectrocutionTargets(EntityUid source, List<(EntityUid entity, int depth)> all)
|
||||||
|
{
|
||||||
|
var visited = new HashSet<EntityUid>();
|
||||||
|
|
||||||
|
GetChainedElectrocutionTargetsRecurse(source, 1, visited, all);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetChainedElectrocutionTargetsRecurse(
|
||||||
|
EntityUid entity,
|
||||||
|
int depth,
|
||||||
|
HashSet<EntityUid> visited,
|
||||||
|
List<(EntityUid entity, int depth)> all)
|
||||||
|
{
|
||||||
|
all.Add((entity, depth));
|
||||||
|
visited.Add(entity);
|
||||||
|
|
||||||
|
if (TryComp<SharedPullableComponent>(entity, out var pullable) &&
|
||||||
|
pullable.Puller is { Valid: true } pullerId &&
|
||||||
|
!visited.Contains(pullerId))
|
||||||
|
{
|
||||||
|
GetChainedElectrocutionTargetsRecurse(pullerId, depth + 1, visited, all);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp<SharedPullerComponent>(entity, out var puller) &&
|
||||||
|
puller.Pulling is { Valid: true } pullingId &&
|
||||||
|
!visited.Contains(pullingId))
|
||||||
|
{
|
||||||
|
GetChainedElectrocutionTargetsRecurse(pullingId, depth + 1, visited, all);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnRandomInsulationMapInit(EntityUid uid, RandomInsulationComponent randomInsulation,
|
||||||
|
MapInitEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<InsulatedComponent>(uid, out var insulated))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (randomInsulation.List.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetInsulatedSiemensCoefficient(uid, _random.Pick(randomInsulation.List), insulated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayElectrocutionSound(EntityUid targetUid, EntityUid sourceUid, ElectrifiedComponent? electrified = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(sourceUid, ref electrified) || !electrified.PlaySoundOnShock)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_audio.PlayPvs(electrified.ShockNoises, targetUid, AudioParams.Default.WithVolume(electrified.ShockVolume));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
Content.Shared/Electrocution/SharedElectrocution.cs
Normal file
15
Content.Shared/Electrocution/SharedElectrocution.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Electrocution;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum ElectrifiedLayers : byte
|
||||||
|
{
|
||||||
|
Powered
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum ElectrifiedVisuals : byte
|
||||||
|
{
|
||||||
|
IsPowered
|
||||||
|
}
|
||||||
@@ -17,7 +17,13 @@
|
|||||||
netsync: false
|
netsync: false
|
||||||
drawdepth: Walls
|
drawdepth: Walls
|
||||||
sprite: Structures/Walls/grille.rsi
|
sprite: Structures/Walls/grille.rsi
|
||||||
state: grille
|
layers:
|
||||||
|
- state: grille
|
||||||
|
- state: electrified
|
||||||
|
sprite: Effects/electricity.rsi
|
||||||
|
map: ["enum.ElectrifiedLayers.Powered"]
|
||||||
|
shader: unshaded
|
||||||
|
visible: false
|
||||||
- type: Icon
|
- type: Icon
|
||||||
sprite: Structures/Walls/grille.rsi
|
sprite: Structures/Walls/grille.rsi
|
||||||
state: grille
|
state: grille
|
||||||
@@ -71,6 +77,14 @@
|
|||||||
node: grilleBroken
|
node: grilleBroken
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Breakage"]
|
acts: ["Breakage"]
|
||||||
|
- type: Appearance
|
||||||
|
- type: GenericVisualizer
|
||||||
|
visuals:
|
||||||
|
enum.ElectrifiedVisuals.IsPowered:
|
||||||
|
enum.ElectrifiedLayers.Powered:
|
||||||
|
True: { visible: True }
|
||||||
|
False: { visible: False }
|
||||||
|
- type: AnimationPlayer
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: GrilleBroken
|
id: GrilleBroken
|
||||||
|
|||||||
BIN
Resources/Textures/Effects/electricity.rsi/electrified.png
Normal file
BIN
Resources/Textures/Effects/electricity.rsi/electrified.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
24
Resources/Textures/Effects/electricity.rsi/meta.json
Normal file
24
Resources/Textures/Effects/electricity.rsi/meta.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "made by Aleksh#7552 (Discord) / Alekshhh (Github) for SS14",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "electrified",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user