diff --git a/Content.Client/Electrocution/ElectrocutionSystem.cs b/Content.Client/Electrocution/ElectrocutionSystem.cs new file mode 100644 index 0000000000..01bf119277 --- /dev/null +++ b/Content.Client/Electrocution/ElectrocutionSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Electrocution; + +namespace Content.Client.Electrocution +{ + public sealed class ElectrocutionSystem : SharedElectrocutionSystem + { } +} diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 8804b81dad..b1cf404d07 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -58,6 +58,9 @@ namespace Content.Client.Entry "FloorTile", "ShuttleController", "HumanInventoryController", + "RandomInsulation", + "Electrified", + "Electrocution", "Pourable", "Paper", "Write", diff --git a/Content.Server/Construction/Completions/AttemptElectrocute.cs b/Content.Server/Construction/Completions/AttemptElectrocute.cs new file mode 100644 index 0000000000..944dbcc83e --- /dev/null +++ b/Content.Server/Construction/Completions/AttemptElectrocute.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Content.Server.Electrocution; +using Content.Shared.Construction; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Construction.Completions +{ + [DataDefinition] + public class AttemptElectrocute : IGraphAction + { + public async Task PerformAction(IEntity entity, IEntity? user) + { + if (user == null) + return; + + EntitySystem.Get().TryDoElectrifiedAct(entity.Uid, user.Uid); + } + } +} diff --git a/Content.Server/Content.Server.csproj.DotSettings b/Content.Server/Content.Server.csproj.DotSettings new file mode 100644 index 0000000000..7c7f227e21 --- /dev/null +++ b/Content.Server/Content.Server.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs new file mode 100644 index 0000000000..c34a5e4d19 --- /dev/null +++ b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs @@ -0,0 +1,65 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Electrocution +{ + /// + /// Component for things that shock users on touch. + /// + [RegisterComponent] + public class ElectrifiedComponent : Component + { + public override string Name => "Electrified"; + + [DataField("enabled")] + public bool Enabled { get; set; } = true; + + [DataField("onBump")] + public bool OnBump { get; set; } = true; + + [DataField("onAttacked")] + public bool OnAttacked { get; set; } = true; + + [DataField("noWindowInTile")] + public bool NoWindowInTile { get; set; } = false; + + [DataField("onHandInteract")] + public bool OnHandInteract { get; set; } = true; + + [DataField("requirePower")] + public bool RequirePower { get; } = true; + + [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; } = 30f; + + [DataField("siemensCoefficient")] + public float SiemensCoefficient { get; } = 1f; + } +} diff --git a/Content.Server/Electrocution/Components/ElectrocutionComponent.cs b/Content.Server/Electrocution/Components/ElectrocutionComponent.cs new file mode 100644 index 0000000000..55d4e1e4a5 --- /dev/null +++ b/Content.Server/Electrocution/Components/ElectrocutionComponent.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Electrocution +{ + /// + /// Component for virtual electrocution entities (representing an in-progress shock). + /// + [RegisterComponent] + [Friend(typeof(ElectrocutionSystem))] + public sealed class ElectrocutionComponent : Component + { + public override string Name => "Electrocution"; + + [DataField("timeLeft")] public float TimeLeft { get; set; } + [DataField("electrocuting")] public EntityUid Electrocuting { get; set; } + [DataField("accumDamage")] public float AccumulatedDamage { get; set; } + + } +} diff --git a/Content.Server/Electrocution/ElectrocutionNode.cs b/Content.Server/Electrocution/ElectrocutionNode.cs new file mode 100644 index 0000000000..c84cbfd789 --- /dev/null +++ b/Content.Server/Electrocution/ElectrocutionNode.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Nodes; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Electrocution +{ + [DataDefinition] + public sealed class ElectrocutionNode : Node + { + [DataField("cable")] + public EntityUid CableEntity; + [DataField("node")] + public string NodeName = default!; + + public override IEnumerable GetReachableNodes() + { + var ent = IoCManager.Resolve(); + if (!ent.TryGetComponent(CableEntity, out NodeContainerComponent? nodeContainer)) + yield break; + + if (nodeContainer.TryGetNode(NodeName, out Node? node)) + yield return node; + } + } +} diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs new file mode 100644 index 0000000000..57a4ed1f9e --- /dev/null +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.NodeGroups; +using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.Power.NodeGroups; +using Content.Server.Window; +using Content.Shared.Alert; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Electrocution; +using Content.Shared.Interaction; +using Content.Shared.Jittering; +using Content.Shared.Maps; +using Content.Shared.Popups; +using Content.Shared.Pulling.Components; +using Content.Shared.Speech.EntitySystems; +using Content.Shared.StatusEffect; +using Content.Shared.Stunnable; +using Content.Shared.Weapons.Melee; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Electrocution +{ + public sealed class ElectrocutionSystem : SharedElectrocutionSystem + { + [Dependency] private readonly IEntityLookup _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!; + + protected const string StatusEffectKey = "Electrocution"; + protected 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() + { + base.Initialize(); + + SubscribeLocalEvent(OnElectrifiedStartCollide); + SubscribeLocalEvent(OnElectrifiedAttacked); + SubscribeLocalEvent(OnElectrifiedHandInteract); + SubscribeLocalEvent(OnRandomInsulationMapInit); + + UpdatesAfter.Add(typeof(PowerNetSystem)); + } + + public override void Update(float frameTime) + { + // Update "in progress" electrocutions + + RemQueue finishedElectrocutionsQueue = new(); + foreach (var (electrocution, consumer) in EntityManager + .EntityQuery()) + { + var ftAdjusted = Math.Min(frameTime, electrocution.TimeLeft); + + 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.Uid; + if (EntityManager.EntityExists(finished.Electrocuting)) + { + // TODO: damage should be scaled by shock damage multiplier + // TODO: better paralyze/jitter timing + var damage = new DamageSpecifier( + _prototypeManager.Index(DamageType), + (int) finished.AccumulatedDamage); + + _damageableSystem.TryChangeDamage(finished.Electrocuting, damage); + } + + EntityManager.DeleteEntity(uid); + } + } + + private void OnElectrifiedStartCollide(EntityUid uid, ElectrifiedComponent electrified, StartCollideEvent args) + { + if (!electrified.OnBump) + return; + + TryDoElectrifiedAct(uid, args.OtherFixture.Body.Owner.Uid, electrified); + } + + private void OnElectrifiedAttacked(EntityUid uid, ElectrifiedComponent electrified, AttackedEvent args) + { + if (!electrified.OnAttacked) + return; + + TryDoElectrifiedAct(uid, args.User.Uid, electrified); + } + + private void OnElectrifiedHandInteract(EntityUid uid, ElectrifiedComponent electrified, InteractHandEvent args) + { + if (!electrified.OnHandInteract) + return; + + TryDoElectrifiedAct(uid, args.User.Uid, electrified); + } + + public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, + ElectrifiedComponent? electrified = null, + NodeContainerComponent? nodeContainer = null, + ITransformComponent? transform = null) + { + if (!Resolve(uid, ref electrified, ref transform, false)) + return false; + + if (!electrified.Enabled) + return false; + + if (electrified.NoWindowInTile) + { + foreach (var entity in transform.Coordinates.GetEntitiesInTile( + LookupFlags.Approximate | LookupFlags.IncludeAnchored, _entityLookup)) + { + if (entity.HasComponent()) + return false; + } + } + + var targets = new List<(EntityUid entity, int depth)>(); + GetChainedElectrocutionTargets(targetUid, targets); + if (!electrified.RequirePower) + { + 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)), + 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), + electrified.SiemensCoefficient); + } + + return lastRet; + } + + + Node? TryNode(string? id) + { + if (id != null && nodeContainer.TryGetNode(id, out var node) + && node.NodeGroup is IBasePowerNet { NetworkNode: { LastAvailableSupplySum: >0 } }) + { + return node; + } + + return null; + } + } + + /// Whether the entity was stunned by the shock. + public bool TryDoElectrocution( + EntityUid uid, EntityUid? sourceUid, int shockDamage, TimeSpan time, float siemensCoefficient = 1f, + StatusEffectsComponent? statusEffects = null, + SharedAlertsComponent? alerts = null) + { + if (!DoCommonElectrocutionAttempt(uid, sourceUid, ref siemensCoefficient) + || !DoCommonElectrocution(uid, sourceUid, shockDamage, time, siemensCoefficient, statusEffects, alerts)) + return false; + + RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient)); + return true; + + } + + private bool TryDoElectrocutionPowered( + EntityUid uid, + EntityUid sourceUid, + Node node, + int shockDamage, + TimeSpan time, + float siemensCoefficient = 1f, + StatusEffectsComponent? statusEffects = null, + SharedAlertsComponent? alerts = null, + ITransformComponent? 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, siemensCoefficient, statusEffects, alerts); + + if (!DoCommonElectrocution(uid, sourceUid, null, time, siemensCoefficient, statusEffects, alerts)) + 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 = electrocutionEntity + .GetComponent() + .GetNode("electrocution"); + + var electrocutionComponent = electrocutionEntity.GetComponent(); + + electrocutionNode.CableEntity = sourceUid; + electrocutionNode.NodeName = node.Name; + + _nodeGroupSystem.QueueReflood(electrocutionNode); + + electrocutionComponent.TimeLeft = 1f; + electrocutionComponent.Electrocuting = uid; + + RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient)); + + return true; + } + + private bool DoCommonElectrocutionAttempt(EntityUid uid, EntityUid? sourceUid, ref float siemensCoefficient) + { + var attemptEvent = new ElectrocutionAttemptEvent(uid, sourceUid, siemensCoefficient); + RaiseLocalEvent(uid, attemptEvent); + + // 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, float siemensCoefficient = 1f, + StatusEffectsComponent? statusEffects = null, + SharedAlertsComponent? alerts = null) + { + if (siemensCoefficient <= 0) + return false; + + if (shockDamage != null) + { + shockDamage = (int) (shockDamage * siemensCoefficient); + + if (shockDamage.Value <= 0) + return false; + } + + // Optional component. + Resolve(uid, ref alerts, false); + + if (!Resolve(uid, ref statusEffects, false) || + !_statusEffectsSystem.CanApplyEffect(uid, StatusEffectKey, statusEffects)) + return false; + + if (!_statusEffectsSystem.TryAddStatusEffect(uid, StatusEffectKey, time, + statusEffects, alerts)) + return false; + + var shouldStun = siemensCoefficient > 0.5f; + + if (shouldStun) + _stunSystem.TryParalyze(uid, time * ParalyzeTimeMultiplier, statusEffects, alerts); + + // TODO: Sparks here. + + if(shockDamage is {} dmg) + _damageableSystem.TryChangeDamage(uid, + new DamageSpecifier(_prototypeManager.Index(DamageType), dmg)); + + _stutteringSystem.DoStutter(uid, time * StutteringTimeMultiplier, statusEffects, alerts); + _jitteringSystem.DoJitter(uid, time * JitterTimeMultiplier, JitterAmplitude, JitterFrequency, true, + statusEffects, alerts); + + _popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid, + Filter.Entities(uid).Unpredicted()); + + var filter = Filter.Pvs(uid, 2f, EntityManager).RemoveWhereAttachedEntity(puid => puid == uid) + .Unpredicted(); + + // 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", EntityManager.GetEntity(uid)), ("source", EntityManager.GetEntity(sourceUid.Value))), + uid, filter); + } + else + { + _popupSystem.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-others", + ("mob", EntityManager.GetEntity(uid))), uid, filter); + } + + return true; + } + + 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 (EntityManager.TryGetComponent(entity, out SharedPullableComponent? pullable) + && pullable.Puller != null + && !visited.Contains(pullable.Puller.Uid)) + { + GetChainedElectrocutionTargetsRecurse(pullable.Puller.Uid, depth + 1, visited, all); + } + + if (EntityManager.TryGetComponent(entity, out SharedPullerComponent? puller) + && puller.Pulling != null + && !visited.Contains(puller.Pulling.Uid)) + { + GetChainedElectrocutionTargetsRecurse(puller.Pulling.Uid, depth + 1, visited, all); + } + } + + private void OnRandomInsulationMapInit(EntityUid uid, RandomInsulationComponent randomInsulation, + MapInitEvent args) + { + if (!EntityManager.TryGetComponent(uid, out InsulatedComponent? insulated)) + return; + + if (randomInsulation.List.Length == 0) + return; + + SetInsulatedSiemensCoefficient(uid, _random.Pick(randomInsulation.List), insulated); + } + } +} diff --git a/Content.Server/Electrocution/RandomInsulationComponent.cs b/Content.Server/Electrocution/RandomInsulationComponent.cs new file mode 100644 index 0000000000..255d6d3a30 --- /dev/null +++ b/Content.Server/Electrocution/RandomInsulationComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Server.Electrocution +{ + [RegisterComponent] + public class RandomInsulationComponent : Component + { + public override string Name => "RandomInsulation"; + + [DataField("list")] + public readonly float[] List = { 0f }; + } +} diff --git a/Content.Server/Inventory/InventorySystem.cs b/Content.Server/Inventory/InventorySystem.cs index 515d328bb3..23211e1564 100644 --- a/Content.Server/Inventory/InventorySystem.cs +++ b/Content.Server/Inventory/InventorySystem.cs @@ -4,6 +4,7 @@ using Content.Server.Items; using Content.Shared.Inventory; using Content.Shared.Slippery; using Content.Shared.Damage; +using Content.Shared.Electrocution; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -20,6 +21,7 @@ namespace Content.Server.Inventory SubscribeLocalEvent(OnHighPressureEvent); SubscribeLocalEvent(OnLowPressureEvent); SubscribeLocalEvent(OnDamageModify); + SubscribeLocalEvent(OnElectrocutionAttempt); SubscribeLocalEvent(OnSlipAttemptEvent); } @@ -51,6 +53,14 @@ namespace Content.Server.Inventory RelayPressureEvent(component, args); } + private void OnElectrocutionAttempt(EntityUid uid, InventoryComponent component, ElectrocutionAttemptEvent args) + { + foreach (var equipped in component.GetAllHeldItems()) + { + RaiseLocalEvent(equipped.Uid, args, false); + } + } + private void OnDamageModify(EntityUid uid, InventoryComponent component, DamageModifyEvent args) { foreach (var equipped in component.GetAllHeldItems()) diff --git a/Content.Server/Power/Components/BaseNetConnectorComponent.cs b/Content.Server/Power/Components/BaseNetConnectorComponent.cs index 3cdf243ca2..beea86d6d5 100644 --- a/Content.Server/Power/Components/BaseNetConnectorComponent.cs +++ b/Content.Server/Power/Components/BaseNetConnectorComponent.cs @@ -8,7 +8,14 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Power.Components { - public abstract class BaseNetConnectorComponent : Component + public interface IBaseNetConnectorComponent + { + public TNetType? Net { set; } + public Voltage Voltage { get; } + public string? NodeId { get; } + } + + public abstract class BaseNetConnectorComponent : Component, IBaseNetConnectorComponent { [ViewVariables(VVAccess.ReadWrite)] public Voltage Voltage { get => _voltage; set => SetVoltage(value); } @@ -22,7 +29,7 @@ namespace Content.Server.Power.Components [ViewVariables] private bool _needsNet => _net != null; - [DataField("node")] [ViewVariables] public string? NodeId; + [DataField("node")] [ViewVariables] public string? NodeId { get; set; } protected override void Initialize() { diff --git a/Content.Server/Power/Components/BasePowerNetComponent.cs b/Content.Server/Power/Components/BasePowerNetComponent.cs index 98f24618f5..a0c81321e6 100644 --- a/Content.Server/Power/Components/BasePowerNetComponent.cs +++ b/Content.Server/Power/Components/BasePowerNetComponent.cs @@ -3,6 +3,11 @@ using Content.Server.Power.NodeGroups; namespace Content.Server.Power.Components { + public interface IBasePowerNetComponent : IBaseNetConnectorComponent + { + + } + public abstract class BasePowerNetComponent : BaseNetConnectorComponent { } diff --git a/Content.Server/Power/Components/CableComponent.cs b/Content.Server/Power/Components/CableComponent.cs index d1538f291c..e74fbcc8b2 100644 --- a/Content.Server/Power/Components/CableComponent.cs +++ b/Content.Server/Power/Components/CableComponent.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Content.Server.Electrocution; using Content.Server.Stack; using Content.Server.Tools; using Content.Server.Tools.Components; @@ -43,6 +44,8 @@ namespace Content.Server.Power.Components if (!await EntitySystem.Get().UseTool(eventArgs.Using.Uid, eventArgs.User.Uid, Owner.Uid, 0f, 0.25f, _cuttingQuality)) return false; + if (EntitySystem.Get().TryDoElectrifiedAct(Owner.Uid, eventArgs.User.Uid)) return false; + Owner.Delete(); var droppedEnt = Owner.EntityManager.SpawnEntity(_cableDroppedOnCutPrototype, eventArgs.ClickLocation); diff --git a/Content.Server/Power/Components/PowerConsumerComponent.cs b/Content.Server/Power/Components/PowerConsumerComponent.cs index 7e4e24de58..0eb3f728ff 100644 --- a/Content.Server/Power/Components/PowerConsumerComponent.cs +++ b/Content.Server/Power/Components/PowerConsumerComponent.cs @@ -10,7 +10,7 @@ namespace Content.Server.Power.Components /// Draws power directly from an MV or HV wire it is on top of. /// [RegisterComponent] - public class PowerConsumerComponent : BasePowerNetComponent + public class PowerConsumerComponent : BaseNetConnectorComponent { public override string Name => "PowerConsumer"; @@ -31,12 +31,12 @@ namespace Content.Server.Power.Components public PowerState.Load NetworkLoad { get; } = new(); - protected override void AddSelfToNet(IPowerNet powerNet) + protected override void AddSelfToNet(IBasePowerNet powerNet) { powerNet.AddConsumer(this); } - protected override void RemoveSelfFromNet(IPowerNet powerNet) + protected override void RemoveSelfFromNet(IBasePowerNet powerNet) { powerNet.RemoveConsumer(this); } diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 5a89923ea5..74b6783e14 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -279,6 +279,12 @@ namespace Content.Server.Power.EntitySystems } } + foreach (var consumer in net.Consumers) + { + netNode.Loads.Add(consumer.NetworkLoad.Id); + consumer.NetworkLoad.LinkedNetwork = netNode.Id; + } + foreach (var apc in net.Apcs) { var netBattery = apc.Owner.GetComponent(); diff --git a/Content.Server/Power/NodeGroups/ApcNet.cs b/Content.Server/Power/NodeGroups/ApcNet.cs index 142dbff1bd..3043e4dd03 100644 --- a/Content.Server/Power/NodeGroups/ApcNet.cs +++ b/Content.Server/Power/NodeGroups/ApcNet.cs @@ -13,7 +13,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Power.NodeGroups { - public interface IApcNet + public interface IApcNet : IBasePowerNet { void AddApc(ApcComponent apc); @@ -25,20 +25,18 @@ namespace Content.Server.Power.NodeGroups void QueueNetworkReconnect(); - PowerState.Network NetworkNode { get; } - GridId? GridId { get; } } [NodeGroup(NodeGroupID.Apc)] [UsedImplicitly] - public class ApcNet : BaseNetConnectorNodeGroup, IApcNet + public class ApcNet : BaseNetConnectorNodeGroup, IApcNet { private readonly PowerNetSystem _powerNetSystem = EntitySystem.Get(); [ViewVariables] public readonly List Apcs = new(); - [ViewVariables] public readonly List Providers = new(); + [ViewVariables] public readonly List Consumers = new(); //Debug property [ViewVariables] private int TotalReceivers => Providers.Sum(provider => provider.LinkedReceivers.Count); @@ -71,7 +69,7 @@ namespace Content.Server.Power.NodeGroups if (apc.Owner.TryGetComponent(out PowerNetworkBatteryComponent? netBattery)) netBattery.NetworkBattery.LinkedNetworkDischarging = default; - _powerNetSystem.QueueReconnectApcNet(this); + QueueNetworkReconnect(); Apcs.Add(apc); } @@ -80,7 +78,7 @@ namespace Content.Server.Power.NodeGroups if (apc.Owner.TryGetComponent(out PowerNetworkBatteryComponent? netBattery)) netBattery.NetworkBattery.LinkedNetworkDischarging = default; - _powerNetSystem.QueueReconnectApcNet(this); + QueueNetworkReconnect(); Apcs.Remove(apc); } @@ -88,14 +86,28 @@ namespace Content.Server.Power.NodeGroups { Providers.Add(provider); - _powerNetSystem.QueueReconnectApcNet(this); + QueueNetworkReconnect(); } public void RemovePowerProvider(ApcPowerProviderComponent provider) { Providers.Remove(provider); - _powerNetSystem.QueueReconnectApcNet(this); + QueueNetworkReconnect(); + } + + public void AddConsumer(PowerConsumerComponent consumer) + { + consumer.NetworkLoad.LinkedNetwork = default; + Consumers.Add(consumer); + QueueNetworkReconnect(); + } + + public void RemoveConsumer(PowerConsumerComponent consumer) + { + consumer.NetworkLoad.LinkedNetwork = default; + Consumers.Remove(consumer); + QueueNetworkReconnect(); } public void QueueNetworkReconnect() @@ -103,7 +115,7 @@ namespace Content.Server.Power.NodeGroups _powerNetSystem.QueueReconnectApcNet(this); } - protected override void SetNetConnectorNet(BaseApcNetComponent netConnectorComponent) + protected override void SetNetConnectorNet(IBaseNetConnectorComponent netConnectorComponent) { netConnectorComponent.Net = this; } diff --git a/Content.Server/Power/NodeGroups/BaseNetConnectorNodeGroup.cs b/Content.Server/Power/NodeGroups/BaseNetConnectorNodeGroup.cs index 8b69fd8712..9bdee30633 100644 --- a/Content.Server/Power/NodeGroups/BaseNetConnectorNodeGroup.cs +++ b/Content.Server/Power/NodeGroups/BaseNetConnectorNodeGroup.cs @@ -6,8 +6,7 @@ using Content.Server.Power.Components; namespace Content.Server.Power.NodeGroups { - public abstract class BaseNetConnectorNodeGroup : BaseNodeGroup - where TNetConnector : BaseNetConnectorComponent + public abstract class BaseNetConnectorNodeGroup : BaseNodeGroup { public override void LoadNodes(List groupNodes) { @@ -16,7 +15,7 @@ namespace Content.Server.Power.NodeGroups foreach (var node in groupNodes) { var newNetConnectorComponents = node.Owner - .GetAllComponents() + .GetAllComponents>() .Where(powerComp => (powerComp.NodeId == null || powerComp.NodeId == node.Name) && (NodeGroupID) powerComp.Voltage == node.NodeGroupID) .ToList(); @@ -28,6 +27,6 @@ namespace Content.Server.Power.NodeGroups } } - protected abstract void SetNetConnectorNet(TNetConnector netConnectorComponent); + protected abstract void SetNetConnectorNet(IBaseNetConnectorComponent netConnectorComponent); } } diff --git a/Content.Server/Power/NodeGroups/IBasePowerNet.cs b/Content.Server/Power/NodeGroups/IBasePowerNet.cs new file mode 100644 index 0000000000..626b02c693 --- /dev/null +++ b/Content.Server/Power/NodeGroups/IBasePowerNet.cs @@ -0,0 +1,14 @@ +using Content.Server.Power.Components; +using Content.Server.Power.Pow3r; + +namespace Content.Server.Power.NodeGroups +{ + public interface IBasePowerNet + { + void AddConsumer(PowerConsumerComponent consumer); + + void RemoveConsumer(PowerConsumerComponent consumer); + + PowerState.Network NetworkNode { get; } + } +} diff --git a/Content.Server/Power/NodeGroups/PowerNet.cs b/Content.Server/Power/NodeGroups/PowerNet.cs index 01bae9f93f..c3fee34dfd 100644 --- a/Content.Server/Power/NodeGroups/PowerNet.cs +++ b/Content.Server/Power/NodeGroups/PowerNet.cs @@ -12,16 +12,12 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Power.NodeGroups { - public interface IPowerNet + public interface IPowerNet : IBasePowerNet { void AddSupplier(PowerSupplierComponent supplier); void RemoveSupplier(PowerSupplierComponent supplier); - void AddConsumer(PowerConsumerComponent consumer); - - void RemoveConsumer(PowerConsumerComponent consumer); - void AddDischarger(BatteryDischargerComponent discharger); void RemoveDischarger(BatteryDischargerComponent discharger); @@ -33,7 +29,7 @@ namespace Content.Server.Power.NodeGroups [NodeGroup(NodeGroupID.HVPower, NodeGroupID.MVPower)] [UsedImplicitly] - public class PowerNet : BaseNetConnectorNodeGroup, IPowerNet + public class PowerNet : BaseNetConnectorNodeGroup, IPowerNet { private readonly PowerNetSystem _powerNetSystem = EntitySystem.Get(); @@ -59,7 +55,7 @@ namespace Content.Server.Power.NodeGroups _powerNetSystem.DestroyPowerNet(this); } - protected override void SetNetConnectorNet(BasePowerNetComponent netConnectorComponent) + protected override void SetNetConnectorNet(IBaseNetConnectorComponent netConnectorComponent) { netConnectorComponent.Net = this; } diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 591e4add55..f21a37d824 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -171,6 +171,9 @@ namespace Content.Server.Power.Pow3r battery.LoadingDemandMarked = true; } + network.LastAvailableSupplySum = availableSupplySum; + network.LastMaxSupplySum = maxSupplySum; + var met = Math.Min(demand, availableSupplySum); if (met != 0) diff --git a/Content.Server/Power/Pow3r/PowerState.cs b/Content.Server/Power/Pow3r/PowerState.cs index d10b55a3b7..8ebf27b1c7 100644 --- a/Content.Server/Power/Pow3r/PowerState.cs +++ b/Content.Server/Power/Pow3r/PowerState.cs @@ -451,6 +451,9 @@ namespace Content.Server.Power.Pow3r // "Supplying" means the network is connected to the OUTPUT port of the battery. [ViewVariables] public List BatteriesDischarging = new(); + [ViewVariables] public float LastAvailableSupplySum = 0f; + [ViewVariables] public float LastMaxSupplySum = 0f; + [ViewVariables] [JsonIgnore] public int Height; [JsonIgnore] public bool HeightTouched; } diff --git a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs index f57880af12..6a69eb155f 100644 --- a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs @@ -90,6 +90,8 @@ namespace Content.Server.Weapon.Melee var targets = new[] { target }; SendAnimation(comp.ClickArc, angle, args.User, owner, targets, comp.ClickAttackEffect, false); + RaiseLocalEvent(target.Uid, new AttackedEvent(args.Used, args.User, args.ClickLocation)); + _damageableSystem.TryChangeDamage(target.Uid, DamageSpecifier.ApplyModifierSets(comp.Damage, hitEvent.ModifiersList)); SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), target); @@ -156,6 +158,8 @@ namespace Content.Server.Weapon.Melee foreach (var entity in hitEntities) { + RaiseLocalEvent(entity.Uid, new AttackedEvent(args.Used, args.User, args.ClickLocation)); + _damageableSystem.TryChangeDamage(entity.Uid, DamageSpecifier.ApplyModifierSets(comp.Damage, hitEvent.ModifiersList)); } diff --git a/Content.Shared/Construction/IGraphAction.cs b/Content.Shared/Construction/IGraphAction.cs index c51a84fc18..c54f3f0eaa 100644 --- a/Content.Shared/Construction/IGraphAction.cs +++ b/Content.Shared/Construction/IGraphAction.cs @@ -1,8 +1,10 @@ using System.Threading.Tasks; using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Shared.Construction { + [ImplicitDataDefinitionForInheritors] public interface IGraphAction { Task PerformAction(IEntity entity, IEntity? user); diff --git a/Content.Shared/Electrocution/ElectrocutedComponent.cs b/Content.Shared/Electrocution/ElectrocutedComponent.cs new file mode 100644 index 0000000000..0a3afaaf04 --- /dev/null +++ b/Content.Shared/Electrocution/ElectrocutedComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameObjects; + +namespace Content.Shared.Electrocution +{ + [RegisterComponent] + public sealed class ElectrocutedComponent : Component + { + public override string Name => "Electrocuted"; + } +} diff --git a/Content.Shared/Electrocution/ElectrocutionEvents.cs b/Content.Shared/Electrocution/ElectrocutionEvents.cs new file mode 100644 index 0000000000..7d9d48320c --- /dev/null +++ b/Content.Shared/Electrocution/ElectrocutionEvents.cs @@ -0,0 +1,32 @@ +using Robust.Shared.GameObjects; + +namespace Content.Shared.Electrocution +{ + public class ElectrocutionAttemptEvent : CancellableEntityEventArgs + { + public readonly EntityUid TargetUid; + public readonly EntityUid? SourceUid; + public float SiemensCoefficient = 1f; + + public ElectrocutionAttemptEvent(EntityUid targetUid, EntityUid? sourceUid, float siemensCoefficient) + { + TargetUid = targetUid; + SourceUid = sourceUid; + SiemensCoefficient = siemensCoefficient; + } + } + + public class ElectrocutedEvent : EntityEventArgs + { + public readonly EntityUid TargetUid; + public readonly EntityUid? SourceUid; + public readonly float SiemensCoefficient; + + public ElectrocutedEvent(EntityUid targetUid, EntityUid? sourceUid, float siemensCoefficient) + { + TargetUid = targetUid; + SourceUid = sourceUid; + SiemensCoefficient = siemensCoefficient; + } + } +} diff --git a/Content.Shared/Electrocution/InsulatedComponent.cs b/Content.Shared/Electrocution/InsulatedComponent.cs new file mode 100644 index 0000000000..ed235ac28f --- /dev/null +++ b/Content.Shared/Electrocution/InsulatedComponent.cs @@ -0,0 +1,35 @@ +using System; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Electrocution +{ + [Friend(typeof(SharedElectrocutionSystem))] + [RegisterComponent, NetworkedComponent] + public class InsulatedComponent : Component + { + public override string Name => "Insulated"; + + /// + /// Siemens coefficient. Zero means completely insulated. + /// + [DataField("coefficient")] + public float SiemensCoefficient { get; set; } = 0f; + } + + // Technically, people could cheat and figure out which budget insulated gloves are gud and which ones are bad. + // We might want to rethink this a little bit. + [NetSerializable, Serializable] + public class InsulatedComponentState : ComponentState + { + public float SiemensCoefficient { get; private set; } + + public InsulatedComponentState(float siemensCoefficient) + { + SiemensCoefficient = siemensCoefficient; + } + } +} diff --git a/Content.Shared/Electrocution/SharedElectrocutionSystem.cs b/Content.Shared/Electrocution/SharedElectrocutionSystem.cs new file mode 100644 index 0000000000..05ea50700e --- /dev/null +++ b/Content.Shared/Electrocution/SharedElectrocutionSystem.cs @@ -0,0 +1,45 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Shared.Electrocution +{ + public abstract class SharedElectrocutionSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInsulatedElectrocutionAttempt); + SubscribeLocalEvent(OnInsulatedGetState); + SubscribeLocalEvent(OnInsulatedHandleState); + } + + public void SetInsulatedSiemensCoefficient(EntityUid uid, float siemensCoefficient, InsulatedComponent? insulated = null) + { + if (!Resolve(uid, ref insulated)) + return; + + insulated.SiemensCoefficient = siemensCoefficient; + insulated.Dirty(); + } + + private void OnInsulatedElectrocutionAttempt(EntityUid uid, InsulatedComponent insulated, ElectrocutionAttemptEvent args) + { + args.SiemensCoefficient *= insulated.SiemensCoefficient; + } + + private void OnInsulatedGetState(EntityUid uid, InsulatedComponent insulated, ref ComponentGetState args) + { + args.State = new InsulatedComponentState(insulated.SiemensCoefficient); + } + + private void OnInsulatedHandleState(EntityUid uid, InsulatedComponent insulated, ref ComponentHandleState args) + { + if (args.Current is not InsulatedComponentState state) + return; + + insulated.SiemensCoefficient = state.SiemensCoefficient; + } + + } +} diff --git a/Content.Shared/Jittering/SharedJitteringSystem.cs b/Content.Shared/Jittering/SharedJitteringSystem.cs index 64b5a23c1a..b30fd0e3fb 100644 --- a/Content.Shared/Jittering/SharedJitteringSystem.cs +++ b/Content.Shared/Jittering/SharedJitteringSystem.cs @@ -57,8 +57,10 @@ namespace Content.Shared.Jittering /// Frequency for jittering. See and . /// Whether to change any existing jitter value even if they're greater than the ones we're setting. /// The status effects component to modify. + /// The alerts component. public void DoJitter(EntityUid uid, TimeSpan time, float amplitude = 10f, float frequency = 4f, bool forceValueChange = false, - StatusEffectsComponent? status=null) + StatusEffectsComponent? status = null, + SharedAlertsComponent? alerts = null) { if (!Resolve(uid, ref status, false)) return; @@ -66,7 +68,7 @@ namespace Content.Shared.Jittering amplitude = Math.Clamp(amplitude, MinAmplitude, MaxAmplitude); frequency = Math.Clamp(frequency, MinFrequency, MaxFrequency); - if (StatusEffects.TryAddStatusEffect(uid, "Jitter", time, status)) + if (StatusEffects.TryAddStatusEffect(uid, "Jitter", time, status, alerts)) { var jittering = EntityManager.GetComponent(uid); diff --git a/Content.Shared/Weapons/Melee/AttackEvent.cs b/Content.Shared/Weapons/Melee/AttackEvent.cs index fb5d1a638d..c053694d46 100644 --- a/Content.Shared/Weapons/Melee/AttackEvent.cs +++ b/Content.Shared/Weapons/Melee/AttackEvent.cs @@ -73,4 +73,32 @@ namespace Content.Shared.Weapons.Melee ClickLocation = clickLocation; } } + + /// + /// Event raised on entities that have been attacked. + /// + public class AttackedEvent : EntityEventArgs + { + /// + /// Entity used to attack, for broadcast purposes. + /// + public IEntity Used { get; } + + /// + /// Entity that triggered the attack. + /// + public IEntity User { get; } + + /// + /// The original location that was clicked by the user. + /// + public EntityCoordinates ClickLocation { get; } + + public AttackedEvent(IEntity used, IEntity user, EntityCoordinates clickLocation) + { + Used = used; + User = user; + ClickLocation = clickLocation; + } + } } diff --git a/Resources/Locale/en-US/electrocution/electrocuted-component.ftl b/Resources/Locale/en-US/electrocution/electrocuted-component.ftl new file mode 100644 index 0000000000..b02387c283 --- /dev/null +++ b/Resources/Locale/en-US/electrocution/electrocuted-component.ftl @@ -0,0 +1,3 @@ +electrocuted-component-mob-shocked-by-source-popup-others = { CAPITALIZE(THE($mob)) } is shocked by { THE($source) }! +electrocuted-component-mob-shocked-popup-others = { CAPITALIZE(THE($mob)) } is shocked! +electrocuted-component-mob-shocked-popup-player = You feel a powerful shock coursing through your body! diff --git a/Resources/Prototypes/Entities/Clothing/Hands/colored.yml b/Resources/Prototypes/Entities/Clothing/Hands/colored.yml index c544615174..6a06adce03 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/colored.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/colored.yml @@ -120,3 +120,30 @@ - type: Clothing sprite: Clothing/Hands/Gloves/Color/yellow.rsi HeatResistance: 1400 + - type: Insulated + +- type: entity + parent: ClothingHandsGlovesColorYellow + id: ClothingHandsGlovesColorYellowCheap + name: budget insulated gloves + description: These gloves are cheap knockoffs of the coveted ones - no way this can end badly. + components: + - type: Clothing + HeatResistance: 0 + - type: Insulated + - type: RandomInsulation + # Why repeated numbers? So some numbers are more common, of course! + list: + - 0 + - 0 + - 0 + - 0.5 + - 0.5 + - 0.5 + - 0.75 + - 1.25 + - 1.25 + - 1.5 + - 1.5 + - 1.5 + - 1.5 diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 96abcf2142..d62fa2343a 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -59,6 +59,7 @@ - type: Clothing sprite: Clothing/Hands/Gloves/captain.rsi HeatResistance: 1400 + - type: Insulated - type: entity parent: ClothingHandsBase @@ -126,6 +127,8 @@ sprite: Clothing/Hands/Gloves/spaceninja.rsi - type: Clothing sprite: Clothing/Hands/Gloves/spaceninja.rsi + HeatResistance: 1400 + - type: Insulated - type: entity parent: ClothingHandsBase diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index d26b84579b..a6920120ad 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -68,6 +68,7 @@ - KnockedDown - SlowedDown - Stutter + - Electrocution # Other - type: Inventory - type: Clickable diff --git a/Resources/Prototypes/Entities/Structures/Power/cables.yml b/Resources/Prototypes/Entities/Structures/Power/cables.yml index 75ec4726aa..9f6c33e79d 100644 --- a/Resources/Prototypes/Entities/Structures/Power/cables.yml +++ b/Resources/Prototypes/Entities/Structures/Power/cables.yml @@ -30,6 +30,12 @@ - !type:DoActsBehavior acts: ["Destruction"] - type: SubFloorHide + - type: Electrified + onBump: false + requirePower: true + highVoltageNode: power + mediumVoltageNode: power + lowVoltageNode: power - type: CableVis node: power diff --git a/Resources/Prototypes/Entities/Structures/Walls/grille.yml b/Resources/Prototypes/Entities/Structures/Walls/grille.yml index db80c0114a..a6d61bc08e 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/grille.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/grille.yml @@ -23,6 +23,36 @@ - type: Damageable damageContainer: Inorganic damageModifierSet: FlimsyMetallic + - type: PowerConsumer + - type: Electrified + requirePower: true + noWindowInTile: true + highVoltageNode: high + mediumVoltageNode: medium + lowVoltageNode: low + - type: NodeContainer + nodes: + high: + !type:CableDeviceNode + nodeGroupID: HVPower + medium: + !type:CableDeviceNode + nodeGroupID: MVPower + low: + !type:CableDeviceNode + nodeGroupID: Apc + - type: Physics + bodyType: Static + fixtures: + - shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + layer: + - Opaque + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable - type: Destructible thresholds: - trigger: @@ -36,7 +66,7 @@ - type: entity id: GrilleBroken - parent: Grille + parent: BaseStructure name: grille description: A flimsy framework of iron rods. It has seen better days. components: @@ -59,12 +89,15 @@ fixtures: - shape: !type:PhysShapeAabb - bounds: "-0.45,-0.45,0.45,0.45" + bounds: "-0.5,-0.5,0.5,0.5" mass: 50 layer: - Passable mask: - Passable + - type: Damageable + damageContainer: Inorganic + damageModifierSet: FlimsyMetallic - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Virtual/electrocution.yml b/Resources/Prototypes/Entities/Virtual/electrocution.yml new file mode 100644 index 0000000000..72a70cac1d --- /dev/null +++ b/Resources/Prototypes/Entities/Virtual/electrocution.yml @@ -0,0 +1,44 @@ +# Special entity used to attach to power networks as load when somebody gets electrocuted. +- type: entity + id: VirtualElectrocutionLoadHVPower + name: ELECTROCUTION ENTITY YOU SHOULD NOT SEE THIS + abstract: true + components: + - type: NodeContainer + nodes: + electrocution: !type:ElectrocutionNode + nodeGroupID: HVPower + + - type: PowerConsumer + voltage: High + drawRate: 50000 + - type: Electrocution + +- type: entity + id: VirtualElectrocutionLoadMVPower + name: ELECTROCUTION ENTITY YOU SHOULD NOT SEE THIS + abstract: true + components: + - type: NodeContainer + nodes: + electrocution: !type:ElectrocutionNode + nodeGroupID: MVPower + + - type: PowerConsumer + voltage: Medium + drawRate: 50000 + - type: Electrocution + +- type: entity + id: VirtualElectrocutionLoadApc + name: ELECTROCUTION ENTITY YOU SHOULD NOT SEE THIS + abstract: true + components: + - type: NodeContainer + nodes: + electrocution: !type:ElectrocutionNode + nodeGroupID: Apc + - type: PowerConsumer + voltage: Apc + drawRate: 50000 + - type: Electrocution diff --git a/Resources/Prototypes/status_effects.yml b/Resources/Prototypes/status_effects.yml index 866de48908..fb5ce212bf 100644 --- a/Resources/Prototypes/status_effects.yml +++ b/Resources/Prototypes/status_effects.yml @@ -18,3 +18,6 @@ - type: statusEffect id: Stutter + +- type: statusEffect + id: Electrocution