diff --git a/Content.Server/Electrocution/Components/ActivatedElectrifiedComponent.cs b/Content.Server/Electrocution/Components/ActivatedElectrifiedComponent.cs new file mode 100644 index 0000000000..60c3cff880 --- /dev/null +++ b/Content.Server/Electrocution/Components/ActivatedElectrifiedComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Electrocution; + +/// +/// Updates every frame for short duration to check if electrifed entity is powered when activated, e.g to play animation +/// +[RegisterComponent] +public sealed class ActivatedElectrifiedComponent : Component +{ + /// + /// How long electrified entity will remain active + /// + [ViewVariables] + public float TimeLeft = 1f; +} diff --git a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs index 0d2ed0a244..97a24ffe0c 100644 --- a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs +++ b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs @@ -1,77 +1,76 @@ using Robust.Shared.Audio; -namespace Content.Server.Electrocution +namespace Content.Server.Electrocution; + +/// +/// Component for things that shock users on touch. +/// +[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; + /// - /// Component for things that shock users on touch. + /// Shock time, in seconds. /// - [RegisterComponent] - public sealed class ElectrifiedComponent : Component - { - [DataField("enabled")] - public bool Enabled { get; set; } = true; + [DataField("shockTime")] + public float ShockTime = 8f; - [DataField("onBump")] - public bool OnBump { get; set; } = true; + [DataField("siemensCoefficient")] + public float SiemensCoefficient = 1f; - [DataField("onAttacked")] - public bool OnAttacked { get; set; } = true; + [DataField("shockNoises")] + public SoundSpecifier ShockNoises = new SoundCollectionSpecifier("sparks"); - [DataField("noWindowInTile")] - public bool NoWindowInTile { get; set; } = false; + [DataField("playSoundOnShock")] + public bool PlaySoundOnShock = true; - [DataField("onHandInteract")] - public bool OnHandInteract { get; set; } = true; - - [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; - - /// - /// Shock time, in seconds. - /// - [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; - } + [DataField("shockVolume")] + public float ShockVolume = 20; } diff --git a/Content.Server/Electrocution/Components/ElectrocutionComponent.cs b/Content.Server/Electrocution/Components/ElectrocutionComponent.cs index a93b55a525..60c752c34d 100644 --- a/Content.Server/Electrocution/Components/ElectrocutionComponent.cs +++ b/Content.Server/Electrocution/Components/ElectrocutionComponent.cs @@ -1,16 +1,21 @@ -namespace Content.Server.Electrocution -{ - /// - /// Component for virtual electrocution entities (representing an in-progress shock). - /// - [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; } +namespace Content.Server.Electrocution; - } +/// +/// Component for virtual electrocution entities (representing an in-progress shock). +/// +[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; } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 811232a86a..11db6184e3 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -23,107 +23,138 @@ using Content.Shared.Tag; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Events; using Robust.Shared.Player; using Robust.Shared.Prototypes; 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!; - [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!; + base.Initialize(); - private const string StatusEffectKey = "Electrocution"; - private const string DamageType = "Shock"; + SubscribeLocalEvent(OnElectrifiedStartCollide); + SubscribeLocalEvent(OnElectrifiedAttacked); + SubscribeLocalEvent(OnElectrifiedHandInteract); + SubscribeLocalEvent(OnElectrifiedInteractUsing); + SubscribeLocalEvent(OnRandomInsulationMapInit); - // Yes, this is absurdly small for a reason. - private const float ElectrifiedDamagePerWatt = 0.0015f; + UpdatesAfter.Add(typeof(PowerNetSystem)); + } - private const float RecursiveDamageMultiplier = 0.75f; - private const float RecursiveTimeMultiplier = 0.8f; + public override void Update(float frameTime) + { + UpdateElectrocutions(frameTime); + UpdateState(frameTime); + } - 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() + private void UpdateElectrocutions(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var electrocution, out var consumer)) { - base.Initialize(); + var timePassed = Math.Min(frameTime, electrocution.TimeLeft); - SubscribeLocalEvent(OnElectrifiedStartCollide); - SubscribeLocalEvent(OnElectrifiedAttacked); - SubscribeLocalEvent(OnElectrifiedHandInteract); - SubscribeLocalEvent(OnElectrifiedInteractUsing); - SubscribeLocalEvent(OnRandomInsulationMapInit); + electrocution.TimeLeft -= timePassed; + electrocution.AccumulatedDamage += consumer.ReceivedPower * ElectrifiedDamagePerWatt * timePassed; - UpdatesAfter.Add(typeof(PowerNetSystem)); - } + if (!MathHelper.CloseTo(electrocution.TimeLeft, 0)) + continue; - public override void Update(float frameTime) - { - // Update "in progress" electrocutions - - RemQueue finishedElectrocutionsQueue = new(); - foreach (var (electrocution, consumer) in EntityManager - .EntityQuery()) + if (EntityManager.EntityExists(electrocution.Electrocuting)) { - 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(DamageType), (int) electrocution.AccumulatedDamage); - electrocution.TimeLeft -= ftAdjusted; - electrocution.AccumulatedDamage += consumer.ReceivedPower * ElectrifiedDamagePerWatt * ftAdjusted; - - if (MathHelper.CloseTo(electrocution.TimeLeft, 0)) - finishedElectrocutionsQueue.Add(electrocution); - } - - foreach (var finished in finishedElectrocutionsQueue) - { - var uid = finished.Owner; - if (EntityManager.EntityExists(finished.Electrocuting)) + var actual = _damageable.TryChangeDamage(electrocution.Electrocuting, damage, origin: electrocution.Source); + if (actual != null) { - // TODO: damage should be scaled by shock damage multiplier - // TODO: better paralyze/jitter timing - var damage = new DamageSpecifier( - _prototypeManager.Index(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}"); - } + _adminLogger.Add(LogType.Electrocution, + $"{ToPrettyString(electrocution.Electrocuting):entity} received {actual.Total:damage} powered electrocution damage from {ToPrettyString(electrocution.Source):source}"); } + } + QueueDel(uid); + } + } - EntityManager.DeleteEntity(uid); + private void UpdateState(float frameTime) + { + var query = EntityQueryEnumerator(); + 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(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) - return; - - TryDoElectrifiedAct(uid, args.OtherFixture.Body.Owner, 1, electrified); + foreach (var entity in transform.Coordinates.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, _entityLookup)) + { + if (_tag.HasTag(entity, "Window")) + 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) { @@ -138,129 +169,112 @@ namespace Content.Server.Electrocution } 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); - } + } - 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(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(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) - return; - - var siemens = TryComp(args.Used, out InsulatedComponent? insulation) - ? insulation.SiemensCoefficient - : 1; - - TryDoElectrifiedAct(uid, args.User, siemens, electrified); + 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; } - public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, - float siemens = 1, - ElectrifiedComponent? electrified = null, - NodeContainerComponent? nodeContainer = null, - TransformComponent? transform = null) + var node = PoweredNode(uid, electrified, nodeContainer); + if (node == null) + return false; + + var (damageMult, timeMult) = node.NodeGroupID switch { - if (!Resolve(uid, ref electrified, ref transform, false)) - return false; + NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier), + NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier, electrified.MediumVoltageTimeMultiplier), + _ => (1f, 1f) + }; - if (!electrified.Enabled) - return false; - - if (electrified.NoWindowInTile) + { + var lastRet = true; + for (var i = targets.Count - 1; i >= 0; i--) { - foreach (var entity in transform.Coordinates.GetEntitiesInTile( - LookupFlags.Approximate | LookupFlags.Static, _entityLookup)) - { - if (_tagSystem.HasTag(entity, "Window")) - return false; - } - } - - siemens *= 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(id, out var tryNode) - && tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastCombinedSupply: >0 } }) - { - return tryNode; - } - - return null; + 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; } + } + + 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(id, out var tryNode) && + tryNode.NodeGroup is IBasePowerNet { NetworkNode: { LastCombinedSupply: > 0 } }) + { + return tryNode; + } + return null; + } + } /// Entity being electrocuted. /// Source entity of the electrocution. @@ -283,184 +297,178 @@ namespace Content.Server.Electrocution return true; } - private bool TryDoElectrocutionPowered( - EntityUid uid, - EntityUid sourceUid, - Node node, - int shockDamage, - TimeSpan time, - bool refresh, - float siemensCoefficient = 1f, - StatusEffectsComponent? statusEffects = null, - TransformComponent? sourceTransform = null) - { - if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient)) - return false; + private bool TryDoElectrocutionPowered( + EntityUid uid, + EntityUid sourceUid, + Node node, + int shockDamage, + TimeSpan time, + bool refresh, + float siemensCoefficient = 1f, + StatusEffectsComponent? statusEffects = null, + TransformComponent? sourceTransform = null) + { + if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient)) + return false; - // Coefficient needs to be higher than this to do a powered electrocution! - if (siemensCoefficient <= 0.5f) - return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects); + // Coefficient needs to be higher than this to do a powered electrocution! + if (siemensCoefficient <= 0.5f) + return DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects); - if (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects)) - 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(electrocutionEntity) - .GetNode("electrocution"); - - var electrocutionComponent = EntityManager.GetComponent(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 (!DoCommonElectrocution(uid, sourceUid, null, time, refresh, siemensCoefficient, statusEffects)) + return false; + if (!Resolve(sourceUid, ref sourceTransform)) // This shouldn't really happen, but just in case... 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(electrocutionEntity).GetNode("electrocution"); + var electrocutionComponent = Comp(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, - ignoreInsulation ? SlotFlags.NONE : ~SlotFlags.POCKET); - RaiseLocalEvent(uid, attemptEvent, true); - - // Cancel the electrocution early, so we don't recursively electrocute anything. - if (attemptEvent.Cancelled) + if (shockDamage.Value <= 0) 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 (!Resolve(uid, ref statusEffects, false) || + !_statusEffects.CanApplyEffect(uid, StatusEffectKey, statusEffects)) { - if (siemensCoefficient <= 0) - 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(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(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; + return false; } + + if (!_statusEffects.TryAddStatusEffect(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(); + var actual = _damageable.TryChangeDamage(uid, + new DamageSpecifier(_prototypeManager.Index(DamageType), dmg), origin: sourceUid); - GetChainedElectrocutionTargetsRecurse(source, 1, visited, all); - } - - private void GetChainedElectrocutionTargetsRecurse( - EntityUid entity, - int depth, - HashSet 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)) + if (actual != null) { - GetChainedElectrocutionTargetsRecurse(pullerId, depth + 1, visited, all); - } - - if (EntityManager.TryGetComponent(entity, out SharedPullerComponent? puller) - && puller.Pulling is {Valid: true} pullingId - && !visited.Contains(pullingId)) - { - GetChainedElectrocutionTargetsRecurse(pullingId, 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}"); } } - private void OnRandomInsulationMapInit(EntityUid uid, RandomInsulationComponent randomInsulation, - MapInitEvent args) + _stuttering.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects); + _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)) - return; - - if (randomInsulation.List.Length == 0) - return; - - SetInsulatedSiemensCoefficient(uid, _random.Pick(randomInsulation.List), insulated); + _popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-by-source-popup-others", + ("mob", uid), ("source", (sourceUid.Value))), uid, filter, true); + PlayElectrocutionSound(uid, sourceUid.Value); + } + else + { + _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) - { - if (!Resolve(sourceUid, ref electrified) || !electrified.PlaySoundOnShock) - { - return; - } + return true; + } - 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(); + + GetChainedElectrocutionTargetsRecurse(source, 1, visited, all); + } + + private void GetChainedElectrocutionTargetsRecurse( + EntityUid entity, + int depth, + HashSet visited, + List<(EntityUid entity, int depth)> all) + { + all.Add((entity, depth)); + visited.Add(entity); + + if (TryComp(entity, out var pullable) && + pullable.Puller is { Valid: true } pullerId && + !visited.Contains(pullerId)) + { + GetChainedElectrocutionTargetsRecurse(pullerId, depth + 1, visited, all); + } + + if (TryComp(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(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)); + } } diff --git a/Content.Shared/Electrocution/SharedElectrocution.cs b/Content.Shared/Electrocution/SharedElectrocution.cs new file mode 100644 index 0000000000..4060856d4d --- /dev/null +++ b/Content.Shared/Electrocution/SharedElectrocution.cs @@ -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 +} diff --git a/Resources/Prototypes/Entities/Structures/Walls/grille.yml b/Resources/Prototypes/Entities/Structures/Walls/grille.yml index b3123dc6a7..9ebacb7779 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/grille.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/grille.yml @@ -17,7 +17,13 @@ netsync: false drawdepth: Walls 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 sprite: Structures/Walls/grille.rsi state: grille @@ -71,6 +77,14 @@ node: grilleBroken - !type:DoActsBehavior acts: ["Breakage"] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ElectrifiedVisuals.IsPowered: + enum.ElectrifiedLayers.Powered: + True: { visible: True } + False: { visible: False } + - type: AnimationPlayer - type: entity id: GrilleBroken diff --git a/Resources/Textures/Effects/electricity.rsi/electrified.png b/Resources/Textures/Effects/electricity.rsi/electrified.png new file mode 100644 index 0000000000..ec6a863bf9 Binary files /dev/null and b/Resources/Textures/Effects/electricity.rsi/electrified.png differ diff --git a/Resources/Textures/Effects/electricity.rsi/meta.json b/Resources/Textures/Effects/electricity.rsi/meta.json new file mode 100644 index 0000000000..0b762debd0 --- /dev/null +++ b/Resources/Textures/Effects/electricity.rsi/meta.json @@ -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 + ] + ] + } + ] +} \ No newline at end of file