Electrocution. (#4958)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
This commit is contained in:
committed by
GitHub
parent
66a3d5bf29
commit
ed3bf94a3b
7
Content.Client/Electrocution/ElectrocutionSystem.cs
Normal file
7
Content.Client/Electrocution/ElectrocutionSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Electrocution;
|
||||
|
||||
namespace Content.Client.Electrocution
|
||||
{
|
||||
public sealed class ElectrocutionSystem : SharedElectrocutionSystem
|
||||
{ }
|
||||
}
|
||||
@@ -58,6 +58,9 @@ namespace Content.Client.Entry
|
||||
"FloorTile",
|
||||
"ShuttleController",
|
||||
"HumanInventoryController",
|
||||
"RandomInsulation",
|
||||
"Electrified",
|
||||
"Electrocution",
|
||||
"Pourable",
|
||||
"Paper",
|
||||
"Write",
|
||||
|
||||
@@ -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<ElectrocutionSystem>().TryDoElectrifiedAct(entity.Uid, user.Uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Content.Server/Content.Server.csproj.DotSettings
Normal file
2
Content.Server/Content.Server.csproj.DotSettings
Normal file
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=electrocution_005Ccomponents/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,65 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Electrocution
|
||||
{
|
||||
/// <summary>
|
||||
/// Component for things that shock users on touch.
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Shock time, in seconds.
|
||||
/// </summary>
|
||||
[DataField("shockTime")]
|
||||
public float ShockTime { get; } = 30f;
|
||||
|
||||
[DataField("siemensCoefficient")]
|
||||
public float SiemensCoefficient { get; } = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Electrocution
|
||||
{
|
||||
/// <summary>
|
||||
/// Component for virtual electrocution entities (representing an in-progress shock).
|
||||
/// </summary>
|
||||
[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; }
|
||||
|
||||
}
|
||||
}
|
||||
29
Content.Server/Electrocution/ElectrocutionNode.cs
Normal file
29
Content.Server/Electrocution/ElectrocutionNode.cs
Normal file
@@ -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<Node> GetReachableNodes()
|
||||
{
|
||||
var ent = IoCManager.Resolve<IEntityManager>();
|
||||
if (!ent.TryGetComponent(CableEntity, out NodeContainerComponent? nodeContainer))
|
||||
yield break;
|
||||
|
||||
if (nodeContainer.TryGetNode(NodeName, out Node? node))
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
407
Content.Server/Electrocution/ElectrocutionSystem.cs
Normal file
407
Content.Server/Electrocution/ElectrocutionSystem.cs
Normal file
@@ -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<ElectrifiedComponent, StartCollideEvent>(OnElectrifiedStartCollide);
|
||||
SubscribeLocalEvent<ElectrifiedComponent, AttackedEvent>(OnElectrifiedAttacked);
|
||||
SubscribeLocalEvent<ElectrifiedComponent, InteractHandEvent>(OnElectrifiedHandInteract);
|
||||
SubscribeLocalEvent<RandomInsulationComponent, MapInitEvent>(OnRandomInsulationMapInit);
|
||||
|
||||
UpdatesAfter.Add(typeof(PowerNetSystem));
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
// Update "in progress" electrocutions
|
||||
|
||||
RemQueue<ElectrocutionComponent> finishedElectrocutionsQueue = new();
|
||||
foreach (var (electrocution, consumer) in EntityManager
|
||||
.EntityQuery<ElectrocutionComponent, PowerConsumerComponent>())
|
||||
{
|
||||
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<DamageTypePrototype>(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<WindowComponent>())
|
||||
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<Node>(id, out var node)
|
||||
&& node.NodeGroup is IBasePowerNet { NetworkNode: { LastAvailableSupplySum: >0 } })
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>Whether the entity <see cref="uid"/> was stunned by the shock.</returns>
|
||||
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<NodeContainerComponent>()
|
||||
.GetNode<ElectrocutionNode>("electrocution");
|
||||
|
||||
var electrocutionComponent = electrocutionEntity.GetComponent<ElectrocutionComponent>();
|
||||
|
||||
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<ElectrocutedComponent>(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<DamageTypePrototype>(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<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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Content.Server/Electrocution/RandomInsulationComponent.cs
Normal file
14
Content.Server/Electrocution/RandomInsulationComponent.cs
Normal file
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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<InventoryComponent, HighPressureEvent>(OnHighPressureEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, LowPressureEvent>(OnLowPressureEvent);
|
||||
SubscribeLocalEvent<InventoryComponent, DamageModifyEvent>(OnDamageModify);
|
||||
SubscribeLocalEvent<InventoryComponent, ElectrocutionAttemptEvent>(OnElectrocutionAttempt);
|
||||
SubscribeLocalEvent<InventoryComponent, SlipAttemptEvent>(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())
|
||||
|
||||
@@ -8,7 +8,14 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
public abstract class BaseNetConnectorComponent<TNetType> : Component
|
||||
public interface IBaseNetConnectorComponent<in TNetType>
|
||||
{
|
||||
public TNetType? Net { set; }
|
||||
public Voltage Voltage { get; }
|
||||
public string? NodeId { get; }
|
||||
}
|
||||
|
||||
public abstract class BaseNetConnectorComponent<TNetType> : Component, IBaseNetConnectorComponent<TNetType>
|
||||
{
|
||||
[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()
|
||||
{
|
||||
|
||||
@@ -3,6 +3,11 @@ using Content.Server.Power.NodeGroups;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
public interface IBasePowerNetComponent : IBaseNetConnectorComponent<IPowerNet>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public abstract class BasePowerNetComponent : BaseNetConnectorComponent<IPowerNet>
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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<ToolSystem>().UseTool(eventArgs.Using.Uid, eventArgs.User.Uid, Owner.Uid, 0f, 0.25f, _cuttingQuality)) return false;
|
||||
|
||||
if (EntitySystem.Get<ElectrocutionSystem>().TryDoElectrifiedAct(Owner.Uid, eventArgs.User.Uid)) return false;
|
||||
|
||||
Owner.Delete();
|
||||
var droppedEnt = Owner.EntityManager.SpawnEntity(_cableDroppedOnCutPrototype, eventArgs.ClickLocation);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Server.Power.Components
|
||||
/// Draws power directly from an MV or HV wire it is on top of.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PowerConsumerComponent : BasePowerNetComponent
|
||||
public class PowerConsumerComponent : BaseNetConnectorComponent<IBasePowerNet>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<PowerNetworkBatteryComponent>();
|
||||
|
||||
@@ -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<BaseApcNetComponent, IApcNet>, IApcNet
|
||||
public class ApcNet : BaseNetConnectorNodeGroup<IApcNet>, IApcNet
|
||||
{
|
||||
private readonly PowerNetSystem _powerNetSystem = EntitySystem.Get<PowerNetSystem>();
|
||||
|
||||
[ViewVariables] public readonly List<ApcComponent> Apcs = new();
|
||||
|
||||
[ViewVariables] public readonly List<ApcPowerProviderComponent> Providers = new();
|
||||
[ViewVariables] public readonly List<PowerConsumerComponent> 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<IApcNet> netConnectorComponent)
|
||||
{
|
||||
netConnectorComponent.Net = this;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ using Content.Server.Power.Components;
|
||||
|
||||
namespace Content.Server.Power.NodeGroups
|
||||
{
|
||||
public abstract class BaseNetConnectorNodeGroup<TNetConnector, TNetType> : BaseNodeGroup
|
||||
where TNetConnector : BaseNetConnectorComponent<TNetType>
|
||||
public abstract class BaseNetConnectorNodeGroup<TNetType> : BaseNodeGroup
|
||||
{
|
||||
public override void LoadNodes(List<Node> groupNodes)
|
||||
{
|
||||
@@ -16,7 +15,7 @@ namespace Content.Server.Power.NodeGroups
|
||||
foreach (var node in groupNodes)
|
||||
{
|
||||
var newNetConnectorComponents = node.Owner
|
||||
.GetAllComponents<TNetConnector>()
|
||||
.GetAllComponents<IBaseNetConnectorComponent<TNetType>>()
|
||||
.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<TNetType> netConnectorComponent);
|
||||
}
|
||||
}
|
||||
|
||||
14
Content.Server/Power/NodeGroups/IBasePowerNet.cs
Normal file
14
Content.Server/Power/NodeGroups/IBasePowerNet.cs
Normal file
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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<BasePowerNetComponent, IPowerNet>, IPowerNet
|
||||
public class PowerNet : BaseNetConnectorNodeGroup<IPowerNet>, IPowerNet
|
||||
{
|
||||
private readonly PowerNetSystem _powerNetSystem = EntitySystem.Get<PowerNetSystem>();
|
||||
|
||||
@@ -59,7 +55,7 @@ namespace Content.Server.Power.NodeGroups
|
||||
_powerNetSystem.DestroyPowerNet(this);
|
||||
}
|
||||
|
||||
protected override void SetNetConnectorNet(BasePowerNetComponent netConnectorComponent)
|
||||
protected override void SetNetConnectorNet(IBaseNetConnectorComponent<IPowerNet> netConnectorComponent)
|
||||
{
|
||||
netConnectorComponent.Net = this;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<NodeId> BatteriesDischarging = new();
|
||||
|
||||
[ViewVariables] public float LastAvailableSupplySum = 0f;
|
||||
[ViewVariables] public float LastMaxSupplySum = 0f;
|
||||
|
||||
[ViewVariables] [JsonIgnore] public int Height;
|
||||
[JsonIgnore] public bool HeightTouched;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
10
Content.Shared/Electrocution/ElectrocutedComponent.cs
Normal file
10
Content.Shared/Electrocution/ElectrocutedComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Electrocution
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class ElectrocutedComponent : Component
|
||||
{
|
||||
public override string Name => "Electrocuted";
|
||||
}
|
||||
}
|
||||
32
Content.Shared/Electrocution/ElectrocutionEvents.cs
Normal file
32
Content.Shared/Electrocution/ElectrocutionEvents.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Content.Shared/Electrocution/InsulatedComponent.cs
Normal file
35
Content.Shared/Electrocution/InsulatedComponent.cs
Normal file
@@ -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";
|
||||
|
||||
/// <summary>
|
||||
/// Siemens coefficient. Zero means completely insulated.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Content.Shared/Electrocution/SharedElectrocutionSystem.cs
Normal file
45
Content.Shared/Electrocution/SharedElectrocutionSystem.cs
Normal file
@@ -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<InsulatedComponent, ElectrocutionAttemptEvent>(OnInsulatedElectrocutionAttempt);
|
||||
SubscribeLocalEvent<InsulatedComponent, ComponentGetState>(OnInsulatedGetState);
|
||||
SubscribeLocalEvent<InsulatedComponent, ComponentHandleState>(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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,10 @@ namespace Content.Shared.Jittering
|
||||
/// <param name="frequency">Frequency for jittering. See <see cref="MaxFrequency"/> and <see cref="MinFrequency"/>.</param>
|
||||
/// <param name="forceValueChange">Whether to change any existing jitter value even if they're greater than the ones we're setting.</param>
|
||||
/// <param name="status">The status effects component to modify.</param>
|
||||
/// <param name="alerts">The alerts component.</param>
|
||||
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<JitteringComponent>(uid, "Jitter", time, status))
|
||||
if (StatusEffects.TryAddStatusEffect<JitteringComponent>(uid, "Jitter", time, status, alerts))
|
||||
{
|
||||
var jittering = EntityManager.GetComponent<JitteringComponent>(uid);
|
||||
|
||||
|
||||
@@ -73,4 +73,32 @@ namespace Content.Shared.Weapons.Melee
|
||||
ClickLocation = clickLocation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on entities that have been attacked.
|
||||
/// </summary>
|
||||
public class AttackedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity used to attack, for broadcast purposes.
|
||||
/// </summary>
|
||||
public IEntity Used { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Entity that triggered the attack.
|
||||
/// </summary>
|
||||
public IEntity User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The original location that was clicked by the user.
|
||||
/// </summary>
|
||||
public EntityCoordinates ClickLocation { get; }
|
||||
|
||||
public AttackedEvent(IEntity used, IEntity user, EntityCoordinates clickLocation)
|
||||
{
|
||||
Used = used;
|
||||
User = user;
|
||||
ClickLocation = clickLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
- KnockedDown
|
||||
- SlowedDown
|
||||
- Stutter
|
||||
- Electrocution
|
||||
# Other
|
||||
- type: Inventory
|
||||
- type: Clickable
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
44
Resources/Prototypes/Entities/Virtual/electrocution.yml
Normal file
44
Resources/Prototypes/Entities/Virtual/electrocution.yml
Normal file
@@ -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
|
||||
@@ -18,3 +18,6 @@
|
||||
|
||||
- type: statusEffect
|
||||
id: Stutter
|
||||
|
||||
- type: statusEffect
|
||||
id: Electrocution
|
||||
|
||||
Reference in New Issue
Block a user