-<DefaultWindow
+<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:adminTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminTab"
xmlns:adminbusTab="clr-namespace:Content.Client.Administration.UI.Tabs.AdminbusTab"
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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"),
+ })
+ }
+ },
+ }
+ },
+ }
+ });
+ }
+}
+
// Core rotting stuff
SubscribeLocalEvent<RottingComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<RottingComponent, OnTemperatureChangeEvent>(OnTempChange);
+ SubscribeLocalEvent<RottingComponent, MobStateChangedEvent>(OnRottingMobStateChanged);
SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<PerishableComponent, BeingGibbedEvent>(OnGibbed);
SubscribeLocalEvent<PerishableComponent, ExaminedEvent>(OnExamined);
}
}
+ 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>
/// Has enough time passed for <paramref name="perishable"/> to start rotting?
/// </summary>
- private bool IsRotting(PerishableComponent perishable, MetaDataComponent? metadata = null)
+ public bool IsRotting(PerishableComponent perishable, MetaDataComponent? metadata = null)
{
if (perishable.TimeOfDeath == TimeSpan.Zero)
return false;
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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);
+ }
+ }
+}
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StackSystem _stacks = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
if (!TryComp(args.Used, out HealingComponent? healing))
return;
- if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid))
+ if (args.Handled || args.Cancelled)
return;
if (component.DamageContainerID is not null && !component.DamageContainerID.Equals(component.DamageContainerID))
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;
if (component.DamageContainerID is not null &&
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
using Content.Shared.Radiation.Events;
using Content.Shared.Rejuvenate;
using Robust.Shared.GameStates;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly INetManager _netMan = default!;
+ [Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
public override void Initialize()
{
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);
+ _mobThreshold.SetAllowRevives(uid, false, thresholds);
}
private void DamageableHandleState(EntityUid uid, DamageableComponent component, ref ComponentHandleState args)
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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
+{
+
+}
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Systems;
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
namespace Content.Shared.Mobs.Components;
[Access(typeof(MobThresholdSystem))]
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;
-}
-
-[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;
}
if (!Resolve(entity, ref component))
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);
ChangeState(entity, component, ev.State);
}
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Components;
-using Robust.Shared.GameStates;
+
namespace Content.Shared.Mobs.Systems;
public sealed class MobThresholdSystem : EntitySystem
SubscribeLocalEvent<MobThresholdsComponent, ComponentShutdown>(MobThresholdShutdown);
SubscribeLocalEvent<MobThresholdsComponent, ComponentStartup>(MobThresholdStartup);
SubscribeLocalEvent<MobThresholdsComponent, DamageChangedEvent>(OnDamaged);
- SubscribeLocalEvent<MobThresholdsComponent, ComponentGetState>(OnGetComponentState);
- SubscribeLocalEvent<MobThresholdsComponent, ComponentHandleState>(OnHandleComponentState);
SubscribeLocalEvent<MobThresholdsComponent, UpdateMobStateEvent>(OnUpdateMobState);
}
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
#region Private Implementation
return;
}
- thresholds.CurrentThresholdState = newState;
+ if (mobState.CurrentState != MobState.Dead || thresholds.AllowRevives)
+ thresholds.CurrentThresholdState = newState;
_mobStateSystem.UpdateMobState(target, mobState);
Dirty(target);
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)
{
if (!TryComp<MobStateComponent>(target, out var mobState) || !TryComp<DamageableComponent>(target, out var damageable))
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;
+ }
}
#endregion
/// <param name="Damageable">Damageable Component owned by the Target</param>
[ByRefEvent]
public readonly record struct MobThresholdChecked(EntityUid Target, MobStateComponent MobState,
- MobThresholdsComponent Threshold, DamageableComponent Damageable)
-{
-}
+ MobThresholdsComponent Threshold, DamageableComponent Damageable);
ghost-roles-window-follow-role-button = Follow
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-return-to-body-title = Return to Body
+ghost-return-to-body-text = You are being revived! Return to your body?
\ No newline at end of file
--- /dev/null
+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.
\ No newline at end of file
--- /dev/null
+- 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
idleState: icon
runningState: icon
staticRecipes:
+ - Defibrillator
- HandheldHealthAnalyzer
- ClothingHandsGlovesLatex
- ClothingMaskSterile
--- /dev/null
+- 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
Glass: 500
Steel: 500
+- type: latheRecipe
+ id: Defibrillator
+ result: DefibrillatorEmpty
+ completetime: 2
+ materials:
+ Steel: 300
+
- type: latheRecipe
id: Medkit
result: Medkit
--- /dev/null
+{
+ "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"
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "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"
+ }
+ ]
+}