Defibrillator (#15922)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Nemanja
2023-05-02 20:10:19 -04:00
committed by GitHub
parent 32b29fb05a
commit 0604c93d50
38 changed files with 796 additions and 56 deletions

View File

@@ -1,4 +1,4 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" xmlns="https://spacestation14.io"
xmlns:adminTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminTab" xmlns:adminTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminTab"
xmlns:adminbusTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminbusTab" xmlns:adminbusTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminbusTab"

View File

@@ -31,3 +31,4 @@ namespace Content.Client.Administration.UI
} }
} }
} }

View File

@@ -0,0 +1,44 @@
using Content.Client.Eui;
using Content.Shared.Ghost;
using JetBrains.Annotations;
using Robust.Client.Graphics;
namespace Content.Client.Ghost.UI;
[UsedImplicitly]
public sealed class ReturnToBodyEui : BaseEui
{
private readonly ReturnToBodyMenu _menu;
public ReturnToBodyEui()
{
_menu = new ReturnToBodyMenu();
_menu.DenyButton.OnPressed += _ =>
{
SendMessage(new ReturnToBodyMessage(false));
_menu.Close();
};
_menu.AcceptButton.OnPressed += _ =>
{
SendMessage(new ReturnToBodyMessage(true));
_menu.Close();
};
}
public override void Opened()
{
IoCManager.Resolve<IClyde>().RequestWindowAttention();
_menu.OpenCentered();
}
public override void Closed()
{
base.Closed();
SendMessage(new ReturnToBodyMessage(false));
_menu.Close();
}
}

View File

@@ -0,0 +1,59 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Ghost.UI;
public sealed class ReturnToBodyMenu : DefaultWindow
{
public readonly Button DenyButton;
public readonly Button AcceptButton;
public ReturnToBodyMenu()
{
Title = Loc.GetString("ghost-return-to-body-title");
Contents.AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
(new Label()
{
Text = Loc.GetString("ghost-return-to-body-text")
}),
new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Align = AlignMode.Center,
Children =
{
(AcceptButton = new Button
{
Text = Loc.GetString("accept-cloning-window-accept-button"),
}),
(new Control()
{
MinSize = (20, 0)
}),
(DenyButton = new Button
{
Text = Loc.GetString("accept-cloning-window-deny-button"),
})
}
},
}
},
}
});
}
}

View File

@@ -132,6 +132,7 @@ namespace Content.Server.Atmos.Miasma
// Core rotting stuff // Core rotting stuff
SubscribeLocalEvent<RottingComponent, ComponentShutdown>(OnShutdown); SubscribeLocalEvent<RottingComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<RottingComponent, OnTemperatureChangeEvent>(OnTempChange); SubscribeLocalEvent<RottingComponent, OnTemperatureChangeEvent>(OnTempChange);
SubscribeLocalEvent<RottingComponent, MobStateChangedEvent>(OnRottingMobStateChanged);
SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<PerishableComponent, BeingGibbedEvent>(OnGibbed); SubscribeLocalEvent<PerishableComponent, BeingGibbedEvent>(OnGibbed);
SubscribeLocalEvent<PerishableComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<PerishableComponent, ExaminedEvent>(OnExamined);
@@ -174,10 +175,24 @@ namespace Content.Server.Atmos.Miasma
} }
} }
private void OnRottingMobStateChanged(EntityUid uid, RottingComponent component, MobStateChangedEvent args)
{
if (args.NewMobState == MobState.Dead)
return;
RemCompDeferred(uid, component);
}
public bool IsRotting(EntityUid uid, PerishableComponent? perishable = null, MetaDataComponent? metadata = null)
{
if (!Resolve(uid, ref perishable, ref metadata, false))
return true;
return IsRotting(perishable, metadata);
}
/// <summary> /// <summary>
/// Has enough time passed for <paramref name="perishable"/> to start rotting? /// Has enough time passed for <paramref name="perishable"/> to start rotting?
/// </summary> /// </summary>
private bool IsRotting(PerishableComponent perishable, MetaDataComponent? metadata = null) public bool IsRotting(PerishableComponent perishable, MetaDataComponent? metadata = null)
{ {
if (perishable.TimeOfDeath == TimeSpan.Zero) if (perishable.TimeOfDeath == TimeSpan.Zero)
return false; return false;

View File

@@ -0,0 +1,32 @@
using Content.Server.EUI;
using Content.Server.Players;
using Content.Shared.Eui;
using Content.Shared.Ghost;
namespace Content.Server.Ghost;
public sealed class ReturnToBodyEui : BaseEui
{
private readonly Mind.Mind _mind;
public ReturnToBodyEui(Mind.Mind mind)
{
_mind = mind;
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
if (msg is not ReturnToBodyMessage choice ||
!choice.Accepted)
{
Close();
return;
}
if (_mind.TryGetSession(out var session))
session.ContentData()!.Mind?.UnVisit();
Close();
}
}

View File

@@ -0,0 +1,257 @@
using Content.Server.Atmos.Miasma;
using Content.Server.Chat.Systems;
using Content.Server.DoAfter;
using Content.Server.Electrocution;
using Content.Server.EUI;
using Content.Server.Ghost;
using Content.Server.Mind.Components;
using Content.Server.Popups;
using Content.Server.PowerCell;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Medical;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Timing;
using Content.Shared.Toggleable;
using Robust.Server.Player;
using Robust.Shared.Timing;
namespace Content.Server.Medical;
/// <summary>
/// This handles interactions and logic relating to <see cref="DefibrillatorComponent"/>
/// </summary>
public sealed class DefibrillatorSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ChatSystem _chatManager = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly MiasmaSystem _miasma = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<DefibrillatorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<DefibrillatorComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<DefibrillatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
}
private void OnUnpaused(EntityUid uid, DefibrillatorComponent component, ref EntityUnpausedEvent args)
{
component.NextZapTime += args.PausedTime;
}
private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
{
if (args.Handled || _useDelay.ActiveDelay(uid))
return;
if (!TryToggle(uid, component, args.User))
return;
args.Handled = true;
_useDelay.BeginDelay(uid);
}
private void OnPowerCellSlotEmpty(EntityUid uid, DefibrillatorComponent component, ref PowerCellSlotEmptyEvent args)
{
TryDisable(uid, component);
}
private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, AfterInteractEvent args)
{
if (args.Handled || args.Target is not { } target)
return;
args.Handled = TryStartZap(uid, target, args.User, component);
}
private void OnDoAfter(EntityUid uid, DefibrillatorComponent component, DefibrillatorZapDoAfterEvent args)
{
if (args.Handled || args.Cancelled)
return;
if (args.Target is not { } target)
return;
if (!CanZap(uid, target, args.User, component))
return;
args.Handled = true;
Zap(uid, target, args.User, component);
}
public bool TryToggle(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
return false;
return component.Enabled
? TryDisable(uid, component)
: TryEnable(uid, component, user);
}
public bool TryEnable(EntityUid uid, DefibrillatorComponent? component = null, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
return false;
if (component.Enabled)
return false;
if (_powerCell.HasActivatableCharge(uid))
return false;
component.Enabled = true;
_appearance.SetData(uid, ToggleVisuals.Toggled, true);
_audio.PlayPvs(component.PowerOnSound, uid);
return true;
}
public bool TryDisable(EntityUid uid, DefibrillatorComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!component.Enabled)
return false;
component.Enabled = false;
_appearance.SetData(uid, ToggleVisuals.Toggled, false);
_audio.PlayPvs(component.PowerOffSound, uid);
return true;
}
public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, DefibrillatorComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!component.Enabled)
{
if (user != null)
_popup.PopupEntity(Loc.GetString("defibrillator-not-on"), uid, user.Value);
return false;
}
if (_timing.CurTime < component.NextZapTime)
return false;
if (!TryComp<MobStateComponent>(target, out var mobState) || _miasma.IsRotting(target))
return false;
if (!_powerCell.HasActivatableCharge(uid, user: user))
return false;
if (_mobState.IsAlive(target, mobState))
return false;
return true;
}
public bool TryStartZap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!CanZap(uid, target, user, component))
return false;
_audio.PlayPvs(component.ChargeSound, uid);
return _doAfter.TryStartDoAfter(new DoAfterArgs(user, component.DoAfterDuration, new DefibrillatorZapDoAfterEvent(),
uid, target, uid)
{
BlockDuplicate = true,
BreakOnUserMove = true,
BreakOnTargetMove = true,
BreakOnHandChange = true,
NeedHand = true
});
}
public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorComponent? component = null, MobStateComponent? mob = null, MobThresholdsComponent? thresholds = null)
{
if (!Resolve(uid, ref component) || !Resolve(target, ref mob, ref thresholds, false))
return;
// clowns zap themselves
if (HasComp<ClumsyComponent>(user) && user != target)
{
Zap(uid, user, user, component, mob, thresholds);
return;
}
if (!_powerCell.TryUseActivatableCharge(uid, user: user))
return;
_mobThreshold.SetAllowRevives(target, true, thresholds);
_audio.PlayPvs(component.ZapSound, uid);
_electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true);
if (_mobState.IsIncapacitated(target, mob))
_damageable.TryChangeDamage(target, component.ZapHeal, true, origin: uid);
component.NextZapTime = _timing.CurTime + component.ZapDelay;
_appearance.SetData(uid, DefibrillatorVisuals.Ready, false);
_mobState.ChangeMobState(target, MobState.Critical, mob, uid);
_mobThreshold.SetAllowRevives(target, false, thresholds);
IPlayerSession? session = null;
if (TryComp<MindComponent>(target, out var mindComp) &&
mindComp.Mind?.UserId != null &&
_playerManager.TryGetSessionById(mindComp.Mind.UserId.Value, out session))
{
// notify them they're being revived.
if (mindComp.Mind.CurrentEntity != target)
{
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-ghosted"),
InGameICChatType.Speak, true, true);
_euiManager.OpenEui(new ReturnToBodyEui(mindComp.Mind), session);
}
}
else
{
_chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-no-mind"),
InGameICChatType.Speak, true, true);
}
var sound = _mobState.IsAlive(target, mob) && session != null
? component.SuccessSound
: component.FailureSound;
_audio.PlayPvs(sound, uid);
// if we don't have enough power left for another shot, turn it off
if (!_powerCell.HasActivatableCharge(uid))
TryDisable(uid, component);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<DefibrillatorComponent>();
while (query.MoveNext(out var uid, out var defib))
{
if (_timing.CurTime < defib.NextZapTime)
continue;
_audio.PlayPvs(defib.ReadySound, uid);
_appearance.SetData(uid, DefibrillatorVisuals.Ready, true);
}
}
}

View File

@@ -30,7 +30,6 @@ public sealed class HealingSystem : EntitySystem
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StackSystem _stacks = default!; [Dependency] private readonly StackSystem _stacks = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -49,7 +48,7 @@ public sealed class HealingSystem : EntitySystem
if (!TryComp(args.Used, out HealingComponent? healing)) if (!TryComp(args.Used, out HealingComponent? healing))
return; return;
if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid)) if (args.Handled || args.Cancelled)
return; return;
if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID)) if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID))
@@ -138,7 +137,7 @@ public sealed class HealingSystem : EntitySystem
private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component) private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
{ {
if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage)) if (!TryComp<DamageableComponent>(target, out var targetDamage))
return false; return false;
if (component.DamageContainerID is not null && if (component.DamageContainerID is not null &&

View File

@@ -2,6 +2,8 @@ using System.Linq;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Radiation.Events; using Content.Shared.Radiation.Events;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -16,6 +18,7 @@ namespace Content.Shared.Damage
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -264,7 +267,10 @@ namespace Content.Shared.Damage
private void OnRejuvenate(EntityUid uid, DamageableComponent component, RejuvenateEvent args) private void OnRejuvenate(EntityUid uid, DamageableComponent component, RejuvenateEvent args)
{ {
TryComp<MobThresholdsComponent>(uid, out var thresholds);
_mobThreshold.SetAllowRevives(uid, true, thresholds); // do this so that the state changes when we set the damage
SetAllDamage(uid, component, 0); SetAllDamage(uid, component, 0);
_mobThreshold.SetAllowRevives(uid, false, thresholds);
} }
private void DamageableHandleState(EntityUid uid, DamageableComponent component, ref ComponentHandleState args) private void DamageableHandleState(EntityUid uid, DamageableComponent component, ref ComponentHandleState args)

View File

@@ -0,0 +1,15 @@
using Content.Shared.Eui;
using Robust.Shared.Serialization;
namespace Content.Shared.Ghost;
[Serializable, NetSerializable]
public sealed class ReturnToBodyMessage : EuiMessageBase
{
public readonly bool Accepted;
public ReturnToBodyMessage(bool accepted)
{
Accepted = accepted;
}
}

View File

@@ -0,0 +1,100 @@
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Medical;
/// <summary>
/// This is used for defibrillators; a machine that shocks a dead
/// person back into the world of the living.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class DefibrillatorComponent : Component
{
/// <summary>
/// Whether or not it's turned on and able to be used.
/// </summary>
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
public bool Enabled;
/// <summary>
/// The time at which the zap cooldown will be completed
/// </summary>
[DataField("nextZapTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextZapTime = TimeSpan.Zero;
/// <summary>
/// The minimum time between zaps
/// </summary>
[DataField("zapDelay"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ZapDelay = TimeSpan.FromSeconds(5);
/// <summary>
/// How much damage is healed from getting zapped.
/// </summary>
[DataField("zapHeal", required: true), ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier ZapHeal = default!;
/// <summary>
/// The electrical damage from getting zapped.
/// </summary>
[DataField("zapDamage"), ViewVariables(VVAccess.ReadWrite)]
public int ZapDamage = 5;
/// <summary>
/// How long the victim will be electrocuted after getting zapped.
/// </summary>
[DataField("writheDuration"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan WritheDuration = TimeSpan.FromSeconds(3);
/// <summary>
/// How long the doafter for zapping someone takes
/// </summary>
/// <remarks>
/// This is synced with the audio; do not change one but not the other.
/// </remarks>
[DataField("doAfterDuration"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(3);
/// <summary>
/// The sound when someone is zapped.
/// </summary>
[DataField("zapSound")]
public SoundSpecifier? ZapSound;
/// <summary>
/// The sound when the defib is powered on.
/// </summary>
[DataField("powerOnSound")]
public SoundSpecifier? PowerOnSound;
[DataField("powerOffSound")]
public SoundSpecifier? PowerOffSound;
[DataField("chargeSound")]
public SoundSpecifier? ChargeSound;
[DataField("failureSound")]
public SoundSpecifier? FailureSound;
[DataField("successSound")]
public SoundSpecifier? SuccessSound;
[DataField("readySound")]
public SoundSpecifier? ReadySound;
}
[Serializable, NetSerializable]
public enum DefibrillatorVisuals : byte
{
Ready
}
[Serializable, NetSerializable]
public sealed class DefibrillatorZapDoAfterEvent : SimpleDoAfterEvent
{
}

View File

@@ -1,7 +1,6 @@
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Mobs.Components; namespace Content.Shared.Mobs.Components;
@@ -9,23 +8,18 @@ namespace Content.Shared.Mobs.Components;
[Access(typeof(MobThresholdSystem))] [Access(typeof(MobThresholdSystem))]
public sealed class MobThresholdsComponent : Component public sealed class MobThresholdsComponent : Component
{ {
[DataField("thresholds", required:true)]public SortedDictionary<FixedPoint2, MobState> Thresholds = new(); [DataField("thresholds", required:true), AutoNetworkedField(true)]
public SortedDictionary<FixedPoint2, MobState> Thresholds = new();
[DataField("triggersAlerts")] public bool TriggersAlerts = true; [DataField("triggersAlerts"), AutoNetworkedField]
public bool TriggersAlerts = true;
[DataField("currentThresholdState"), AutoNetworkedField]
public MobState CurrentThresholdState; public MobState CurrentThresholdState;
}
[Serializable, NetSerializable]
public sealed class MobThresholdComponentState : ComponentState
{
public Dictionary<FixedPoint2, MobState> Thresholds;
public MobState CurrentThresholdState;
public MobThresholdComponentState(MobState currentThresholdState,
Dictionary<FixedPoint2, MobState> thresholds)
{
CurrentThresholdState = currentThresholdState;
Thresholds = thresholds;
}
/// <summary>
/// Whether or not this entity can be revived out of a dead state.
/// </summary>
[DataField("allowRevives"), AutoNetworkedField]
public bool AllowRevives;
} }

View File

@@ -48,7 +48,7 @@ public partial class MobStateSystem
if (!Resolve(entity, ref component)) if (!Resolve(entity, ref component))
return; return;
var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin}; var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin, State = mobState};
RaiseLocalEvent(entity, ref ev); RaiseLocalEvent(entity, ref ev);
ChangeState(entity, component, ev.State); ChangeState(entity, component, ev.State);
} }

View File

@@ -4,7 +4,7 @@ using Content.Shared.Alert;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Robust.Shared.GameStates;
namespace Content.Shared.Mobs.Systems; namespace Content.Shared.Mobs.Systems;
public sealed class MobThresholdSystem : EntitySystem public sealed class MobThresholdSystem : EntitySystem
@@ -17,8 +17,6 @@ public sealed class MobThresholdSystem : EntitySystem
SubscribeLocalEvent<MobThresholdsComponent, ComponentShutdown>(MobThresholdShutdown); SubscribeLocalEvent<MobThresholdsComponent, ComponentShutdown>(MobThresholdShutdown);
SubscribeLocalEvent<MobThresholdsComponent, ComponentStartup>(MobThresholdStartup); SubscribeLocalEvent<MobThresholdsComponent, ComponentStartup>(MobThresholdStartup);
SubscribeLocalEvent<MobThresholdsComponent, DamageChangedEvent>(OnDamaged); SubscribeLocalEvent<MobThresholdsComponent, DamageChangedEvent>(OnDamaged);
SubscribeLocalEvent<MobThresholdsComponent, ComponentGetState>(OnGetComponentState);
SubscribeLocalEvent<MobThresholdsComponent, ComponentHandleState>(OnHandleComponentState);
SubscribeLocalEvent<MobThresholdsComponent, UpdateMobStateEvent>(OnUpdateMobState); SubscribeLocalEvent<MobThresholdsComponent, UpdateMobStateEvent>(OnUpdateMobState);
} }
@@ -249,6 +247,14 @@ public sealed class MobThresholdSystem : EntitySystem
UpdateAlerts(target, mobState.CurrentState, threshold, damageable); UpdateAlerts(target, mobState.CurrentState, threshold, damageable);
} }
public void SetAllowRevives(EntityUid uid, bool val, MobThresholdsComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return;
component.AllowRevives = val;
Dirty(component);
}
#endregion #endregion
#region Private Implementation #region Private Implementation
@@ -278,7 +284,8 @@ public sealed class MobThresholdSystem : EntitySystem
return; return;
} }
thresholds.CurrentThresholdState = newState; if (mobState.CurrentState != MobState.Dead || thresholds.AllowRevives)
thresholds.CurrentThresholdState = newState;
_mobStateSystem.UpdateMobState(target, mobState); _mobStateSystem.UpdateMobState(target, mobState);
Dirty(target); Dirty(target);
@@ -334,32 +341,6 @@ public sealed class MobThresholdSystem : EntitySystem
UpdateAlerts(target, mobState.CurrentState, thresholds, args.Damageable); UpdateAlerts(target, mobState.CurrentState, thresholds, args.Damageable);
} }
private void OnHandleComponentState(EntityUid target, MobThresholdsComponent component,
ref ComponentHandleState args)
{
if (args.Current is not MobThresholdComponentState state)
return;
if (component.Thresholds.Count != state.Thresholds.Count ||
!component.Thresholds.SequenceEqual(state.Thresholds))
{
component.Thresholds.Clear();
foreach (var threshold in state.Thresholds)
{
component.Thresholds.Add(threshold.Key, threshold.Value);
}
}
component.CurrentThresholdState = state.CurrentThresholdState;
}
private void OnGetComponentState(EntityUid target, MobThresholdsComponent component, ref ComponentGetState args)
{
args.State = new MobThresholdComponentState(component.CurrentThresholdState,
new Dictionary<FixedPoint2, MobState>(component.Thresholds));
}
private void MobThresholdStartup(EntityUid target, MobThresholdsComponent thresholds, ComponentStartup args) private void MobThresholdStartup(EntityUid target, MobThresholdsComponent thresholds, ComponentStartup args)
{ {
if (!TryComp<MobStateComponent>(target, out var mobState) || !TryComp<DamageableComponent>(target, out var damageable)) if (!TryComp<MobStateComponent>(target, out var mobState) || !TryComp<DamageableComponent>(target, out var damageable))
@@ -378,8 +359,14 @@ public sealed class MobThresholdSystem : EntitySystem
private void OnUpdateMobState(EntityUid target, MobThresholdsComponent component, ref UpdateMobStateEvent args) private void OnUpdateMobState(EntityUid target, MobThresholdsComponent component, ref UpdateMobStateEvent args)
{ {
if (component.CurrentThresholdState != MobState.Invalid) if (!component.AllowRevives && component.CurrentThresholdState == MobState.Dead)
{
args.State = MobState.Dead;
}
else if (component.CurrentThresholdState != MobState.Invalid)
{
args.State = component.CurrentThresholdState; args.State = component.CurrentThresholdState;
}
} }
#endregion #endregion
@@ -394,6 +381,4 @@ public sealed class MobThresholdSystem : EntitySystem
/// <param name="Damageable">Damageable Component owned by the Target</param> /// <param name="Damageable">Damageable Component owned by the Target</param>
[ByRefEvent] [ByRefEvent]
public readonly record struct MobThresholdChecked(EntityUid Target, MobStateComponent MobState, public readonly record struct MobThresholdChecked(EntityUid Target, MobStateComponent MobState,
MobThresholdsComponent Threshold, DamageableComponent Damageable) MobThresholdsComponent Threshold, DamageableComponent Damageable);
{
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -20,3 +20,6 @@ ghost-roles-window-request-role-button-timer = Request ({$time}s)
ghost-roles-window-follow-role-button = Follow ghost-roles-window-follow-role-button = Follow
ghost-roles-window-no-roles-available-label = There are currently no available ghost roles. ghost-roles-window-no-roles-available-label = There are currently no available ghost roles.
ghost-roles-window-rules-footer = The button will enable after {$time} seconds (this delay is to make sure you read the rules). ghost-roles-window-rules-footer = The button will enable after {$time} seconds (this delay is to make sure you read the rules).
ghost-return-to-body-title = Return to Body
ghost-return-to-body-text = You are being revived! Return to your body?

View File

@@ -0,0 +1,3 @@
defibrillator-not-on = The defibrillator isn't turned on.
defibrillator-no-mind = No intelligence pattern can be detected in patient's brain. Further attempts futile
defibrillator-ghosted = Resuscitation failed - Mental interface error. Further attempts may be successful.

View File

@@ -0,0 +1,67 @@
- type: entity
id: Defibrillator
parent: [ BaseItem, PowerCellSlotMediumItem ]
name: defibrillator
description: CLEAR! Zzzzat!
components:
- type: Sprite
sprite: Objects/Specific/Medical/defib.rsi
layers:
- state: icon
- state: screen
map: [ "enum.ToggleVisuals.Layer" ]
visible: false
shader: unshaded
- state: ready
map: ["enum.PowerDeviceVisualLayers.Powered"]
shader: unshaded
- type: GenericVisualizer
visuals:
enum.ToggleVisuals.Toggled:
enum.ToggleVisuals.Layer:
True: { visible: true }
False: { visible: false }
enum.DefibrillatorVisuals.Ready:
enum.PowerDeviceVisualLayers.Powered:
True: { visible: true }
False: { visible: false }
- type: Item
size: 50
- type: ItemCooldown
- type: MultiHandedItem
- type: Speech
- type: Defibrillator
zapHeal:
types:
Asphyxiation: -40
zapSound:
path: /Audio/Items/Defib/defib_zap.ogg
powerOnSound:
path: /Audio/Items/Defib/defib_SaftyOn.ogg
powerOffSound:
path: /Audio/Items/Defib/defib_saftyOff.ogg
chargeSound:
path: /Audio/Items/Defib/defib_charge.ogg
failureSound:
path: /Audio/Items/Defib/defib_failed.ogg
successSound:
path: /Audio/Items/Defib/defib_success.ogg
readySound:
path: /Audio/Items/Defib/defib_ready.ogg
- type: PowerCellDraw
useRate: 100
- type: Appearance
- type: DoAfter
- type: UseDelay
- type: StaticPrice
price: 100
- type: entity
id: DefibrillatorEmpty
parent: Defibrillator
suffix: Empty
components:
- type: ItemSlots
slots:
cell_slot:
name: power-cell-slot-component-slot-name-default

View File

@@ -466,6 +466,7 @@
idleState: icon idleState: icon
runningState: icon runningState: icon
staticRecipes: staticRecipes:
- Defibrillator
- HandheldHealthAnalyzer - HandheldHealthAnalyzer
- ClothingHandsGlovesLatex - ClothingHandsGlovesLatex
- ClothingMaskSterile - ClothingMaskSterile

View File

@@ -0,0 +1,101 @@
- type: entity
id: DefibrillatorCabinet
name: defibrillator cabinet
description: A small wall mounted cabinet designed to hold a defibrillator.
components:
- type: WallMount
arc: 90
- type: Transform
anchored: true
- type: Clickable
- type: InteractionOutline
- type: Sprite
sprite: Structures/Wallmounts/defib_cabinet.rsi
netsync: false
noRot: false
layers:
- state: frame
- state: fill
map: ["enum.ItemCabinetVisualLayers.ContainsItem"]
visible: true
- state: closed
map: ["enum.ItemCabinetVisualLayers.Door"]
- type: ItemCabinet
cabinetSlot:
ejectOnInteract: true
whitelist:
components:
- Defibrillator
doorSound:
path: /Audio/Machines/machine_switch.ogg
openState: open
closedState: closed
- type: Appearance
- type: ItemSlots
- type: ContainerContainer
containers:
ItemCabinet: !type:ContainerSlot
- type: Damageable
damageContainer: Inorganic
damageModifierSet: Metallic
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 80
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- trigger:
!type:DamageTrigger
damage: 40
behaviors:
- !type:EmptyAllContainersBehaviour
- !type:DoActsBehavior
acts: [ "Destruction" ]
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/metalbreak.ogg
placement:
mode: SnapgridCenter
- type: entity
id: DefibrillatorCabinetOpen
parent: DefibrillatorCabinet
suffix: Open
components:
- type: ItemCabinet
opened: true
doorSound:
path: /Audio/Machines/machine_switch.ogg
openState: open
closedState: closed
- type: entity
id: DefibrillatorCabinetFilled
parent: DefibrillatorCabinet
suffix: Filled
components:
- type: ItemCabinet
cabinetSlot:
ejectOnInteract: true
startingItem: Defibrillator
whitelist:
components:
- Defibrillator
doorSound:
path: /Audio/Machines/machine_switch.ogg
openState: open
closedState: closed
- type: entity
id: DefibrillatorCabinetFilledOpen
parent: DefibrillatorCabinetFilled
suffix: Filled, Open
components:
- type: ItemCabinet
opened: true
doorSound:
path: /Audio/Machines/machine_switch.ogg
openState: open
closedState: closed

View File

@@ -103,6 +103,13 @@
Glass: 500 Glass: 500
Steel: 500 Steel: 500
- type: latheRecipe
id: Defibrillator
result: DefibrillatorEmpty
completetime: 2
materials:
Steel: 300
- type: latheRecipe - type: latheRecipe
id: Medkit id: Medkit
result: Medkit result: Medkit

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

View File

@@ -0,0 +1,28 @@
{
"version": 1,
"license": "CC0-1.0",
"copyright": "Created by EmoGarbage404 (github) for Space Staiton 14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "ready"
},
{
"name": "screen"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

View File

@@ -0,0 +1,23 @@
{
"version": 1,
"license": "CC0-1.0",
"copyright": "Created by EmoGarbage404 (github) for Space Station 14",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "closed"
},
{
"name": "fill"
},
{
"name": "frame"
},
{
"name": "open"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B