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:
Slava0135
2023-04-29 23:05:10 +03:00
committed by GitHub
parent 174b28e62c
commit 4ade6f60ff
8 changed files with 504 additions and 425 deletions

View File

@@ -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;
}

View File

@@ -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;
}
} }

View File

@@ -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;
} }

View File

@@ -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));
}
} }

View 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
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View 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
]
]
}
]
}