Merge branch 'master' into expl_int_analyzer
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.ActionBlocking;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -46,24 +44,20 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
private Container _container = default!;
|
||||
|
||||
private float _interactRange;
|
||||
private IHandsComponent _hands;
|
||||
// TODO: Make a component message
|
||||
public event Action? OnCuffedStateChanged;
|
||||
|
||||
public event Action OnCuffedStateChanged;
|
||||
private bool _uncuffing;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
|
||||
_interactRange = SharedInteractionSystem.InteractionRange / 2;
|
||||
|
||||
Owner.EntityManager.EventBus.SubscribeEvent<HandCountChangedEvent>(EventSource.Local, this, HandleHandCountChange);
|
||||
|
||||
if (!Owner.TryGetComponent(out _hands))
|
||||
{
|
||||
Logger.Warning("Player does not have an IHandsComponent!");
|
||||
}
|
||||
Owner.EnsureComponentWarn<HandsComponent>();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
@@ -99,27 +93,35 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
/// Add a set of cuffs to an existing CuffedComponent.
|
||||
/// </summary>
|
||||
/// <param name="prototype"></param>
|
||||
public void AddNewCuffs(IEntity handcuff)
|
||||
public bool TryAddNewCuffs(IEntity user, IEntity handcuff)
|
||||
{
|
||||
if (!handcuff.HasComponent<HandcuffComponent>())
|
||||
{
|
||||
Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handcuff.InRangeUnobstructed(Owner, _interactRange))
|
||||
if (!handcuff.InRangeUnobstructed(Owner))
|
||||
{
|
||||
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Success!
|
||||
if (user.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.IsHolding(handcuff))
|
||||
{
|
||||
// Good lord handscomponent is scuffed, I hope some smug person will fix it someday
|
||||
handsComponent.Drop(handcuff);
|
||||
}
|
||||
|
||||
_container.Insert(handcuff);
|
||||
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
||||
CanStillInteract = Owner.TryGetComponent(out HandsComponent? ownerHands) && ownerHands.Hands.Count() > CuffedHandCount;
|
||||
|
||||
OnCuffedStateChanged?.Invoke();
|
||||
UpdateAlert();
|
||||
UpdateHeldItems();
|
||||
Dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,7 +130,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
private void UpdateHandCount()
|
||||
{
|
||||
var dirty = false;
|
||||
var handCount = _hands.Hands.Count();
|
||||
var handCount = Owner.TryGetComponent(out HandsComponent? handsComponent) ? handsComponent.Hands.Count() : 0;
|
||||
|
||||
while (CuffedHandCount > handCount && CuffedHandCount > 0)
|
||||
{
|
||||
@@ -142,7 +144,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
if (dirty)
|
||||
{
|
||||
CanStillInteract = handCount > CuffedHandCount;
|
||||
OnCuffedStateChanged.Invoke();
|
||||
OnCuffedStateChanged?.Invoke();
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
@@ -160,17 +162,19 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
/// </summary>
|
||||
public void UpdateHeldItems()
|
||||
{
|
||||
var itemCount = _hands.GetAllHeldItems().Count();
|
||||
var freeHandCount = _hands.Hands.Count() - CuffedHandCount;
|
||||
if (!Owner.TryGetComponent(out HandsComponent? handsComponent)) return;
|
||||
|
||||
var itemCount = handsComponent.GetAllHeldItems().Count();
|
||||
var freeHandCount = handsComponent.Hands.Count() - CuffedHandCount;
|
||||
|
||||
if (freeHandCount < itemCount)
|
||||
{
|
||||
foreach (var item in _hands.GetAllHeldItems())
|
||||
foreach (var item in handsComponent.GetAllHeldItems())
|
||||
{
|
||||
if (freeHandCount < itemCount)
|
||||
{
|
||||
freeHandCount++;
|
||||
_hands.Drop(item.Owner, false);
|
||||
handsComponent.Drop(item.Owner, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -185,7 +189,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
/// </summary>
|
||||
private void UpdateAlert()
|
||||
{
|
||||
if (Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||
if (Owner.TryGetComponent(out ServerAlertsComponent? status))
|
||||
{
|
||||
if (CanStillInteract)
|
||||
{
|
||||
@@ -204,8 +208,10 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
/// </summary>
|
||||
/// <param name="user">The cuffed entity</param>
|
||||
/// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param>
|
||||
public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null)
|
||||
public async void TryUncuff(IEntity user, IEntity? cuffsToRemove = null)
|
||||
{
|
||||
if (_uncuffing) return;
|
||||
|
||||
var isOwner = user == Owner;
|
||||
|
||||
if (cuffsToRemove == null)
|
||||
@@ -232,13 +238,13 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isOwner && !user.InRangeUnobstructed(Owner, _interactRange))
|
||||
if (!isOwner && !user.InRangeUnobstructed(Owner))
|
||||
{
|
||||
user.PopupMessage(Loc.GetString("You are too far away to remove the cuffs."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cuffsToRemove.InRangeUnobstructed(Owner, _interactRange))
|
||||
if (!cuffsToRemove.InRangeUnobstructed(Owner))
|
||||
{
|
||||
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
|
||||
return;
|
||||
@@ -247,7 +253,17 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
user.PopupMessage(Loc.GetString("You start removing the cuffs."));
|
||||
|
||||
var audio = EntitySystem.Get<AudioSystem>();
|
||||
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
|
||||
if (isOwner)
|
||||
{
|
||||
if (cuff.StartBreakoutSound != null)
|
||||
audio.PlayFromEntity(cuff.StartBreakoutSound, Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cuff.StartUncuffSound != null)
|
||||
audio.PlayFromEntity(cuff.StartUncuffSound, Owner);
|
||||
}
|
||||
|
||||
|
||||
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime)
|
||||
@@ -259,11 +275,16 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
};
|
||||
|
||||
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
_uncuffing = true;
|
||||
|
||||
var result = await doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
|
||||
_uncuffing = false;
|
||||
|
||||
if (result != DoAfterStatus.Cancelled)
|
||||
{
|
||||
audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
|
||||
if (cuff.EndUncuffSound != null)
|
||||
audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
|
||||
|
||||
_container.ForceRemove(cuffsToRemove);
|
||||
cuffsToRemove.Transform.AttachToGridOrMap();
|
||||
@@ -276,14 +297,14 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
cuffsToRemove.Name = cuff.BrokenName;
|
||||
cuffsToRemove.Description = cuff.BrokenDesc;
|
||||
|
||||
if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite))
|
||||
if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite) && cuff.BrokenState != null)
|
||||
{
|
||||
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
|
||||
}
|
||||
}
|
||||
|
||||
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
|
||||
OnCuffedStateChanged.Invoke();
|
||||
CanStillInteract = Owner.TryGetComponent(out HandsComponent? handsComponent) && handsComponent.Hands.Count() > CuffedHandCount;
|
||||
OnCuffedStateChanged?.Invoke();
|
||||
UpdateAlert();
|
||||
Dirty();
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Shared.GameObjects.Components.ActionBlocking;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -14,7 +14,6 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -22,6 +21,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedHandcuffComponent))]
|
||||
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
|
||||
{
|
||||
/// <summary>
|
||||
@@ -58,31 +58,31 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
/// The path of the RSI file used for the player cuffed overlay.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string CuffedRSI { get; set; }
|
||||
public string? CuffedRSI { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used with the RSI file for the player cuffed overlay.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string OverlayIconState { get; set; }
|
||||
public string? OverlayIconState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used for broken handcuffs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string BrokenState { get; set; }
|
||||
public string? BrokenState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used for broken handcuffs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string BrokenName { get; set; }
|
||||
public string BrokenName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The iconstate used for broken handcuffs
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string BrokenDesc { get; set; }
|
||||
public string BrokenDesc { get; set; } = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Broken
|
||||
@@ -102,25 +102,20 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
}
|
||||
}
|
||||
|
||||
public string StartCuffSound { get; set; }
|
||||
public string EndCuffSound { get; set; }
|
||||
public string StartBreakoutSound { get; set; }
|
||||
public string StartUncuffSound { get; set; }
|
||||
public string EndUncuffSound { get; set; }
|
||||
public string? StartCuffSound { get; set; }
|
||||
public string? EndCuffSound { get; set; }
|
||||
public string? StartBreakoutSound { get; set; }
|
||||
public string? StartUncuffSound { get; set; }
|
||||
public string? EndUncuffSound { get; set; }
|
||||
public Color Color { get; set; }
|
||||
|
||||
// Non-exposed data fields
|
||||
private bool _isBroken = false;
|
||||
private float _interactRange;
|
||||
private AudioSystem _audioSystem;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_audioSystem = EntitySystem.Get<AudioSystem>();
|
||||
_interactRange = SharedInteractionSystem.InteractionRange / 2;
|
||||
}
|
||||
/// <summary>
|
||||
/// Used to prevent DoAfter getting spammed.
|
||||
/// </summary>
|
||||
private bool _cuffing;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
@@ -150,6 +145,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (_cuffing) return true;
|
||||
|
||||
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed))
|
||||
{
|
||||
return false;
|
||||
@@ -179,7 +176,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true))
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true))
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!"));
|
||||
return true;
|
||||
@@ -187,7 +184,9 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
|
||||
eventArgs.User.PopupMessage(eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
|
||||
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
|
||||
|
||||
if (StartCuffSound != null)
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(StartCuffSound, Owner);
|
||||
|
||||
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
|
||||
return true;
|
||||
@@ -214,22 +213,21 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
_cuffing = true;
|
||||
|
||||
var result = await EntitySystem.Get<DoAfterSystem>().DoAfter(doAfterEventArgs);
|
||||
|
||||
_cuffing = false;
|
||||
|
||||
if (result != DoAfterStatus.Cancelled)
|
||||
{
|
||||
_audioSystem.PlayFromEntity(EndCuffSound, Owner);
|
||||
user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
|
||||
target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
|
||||
if (cuffs.TryAddNewCuffs(user, Owner))
|
||||
{
|
||||
if (EndCuffSound != null)
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(EndCuffSound, Owner);
|
||||
|
||||
if (user.TryGetComponent<HandsComponent>(out var hands))
|
||||
{
|
||||
hands.Drop(Owner);
|
||||
cuffs.AddNewCuffs(Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!");
|
||||
user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
|
||||
target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
|
||||
_appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_initialInletDirection, _initialOutletDirection, PumpEnabled));
|
||||
}
|
||||
|
||||
public void Activate(ActivateEventArgs eventArgs)
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
PumpEnabled = !PumpEnabled;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Observer;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
@@ -56,6 +58,13 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||
newEntity.EnsureComponent<MindComponent>();
|
||||
var oldMind = oldEntity.EnsureComponent<MindComponent>();
|
||||
|
||||
if (!newEntity.HasComponent<IGhostOnMove>())
|
||||
newEntity.AddComponent<GhostOnMoveComponent>();
|
||||
|
||||
// TODO: This is an awful solution.
|
||||
if (!newEntity.HasComponent<IMoverComponent>())
|
||||
newEntity.AddComponent<SharedDummyInputMoverComponent>();
|
||||
|
||||
oldMind.Mind?.TransferTo(newEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Commands.Observer;
|
||||
using Content.Server.GameObjects.Components.Observer;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
@@ -25,7 +26,8 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedBodyComponent))]
|
||||
[ComponentReference(typeof(IBody))]
|
||||
public class BodyComponent : SharedBodyComponent, IRelayMoveInput
|
||||
[ComponentReference(typeof(IGhostOnMove))]
|
||||
public class BodyComponent : SharedBodyComponent, IRelayMoveInput, IGhostOnMove
|
||||
{
|
||||
private Container _partContainer = default!;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.GameObjects.Components.Construction;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
@@ -87,6 +88,9 @@ namespace Content.Server.GameObjects.Components
|
||||
|
||||
if(!container.Insert(board))
|
||||
Logger.Warning($"Couldn't insert board {board} to computer {Owner}!");
|
||||
|
||||
if (Owner.TryGetComponent(out ConstructionComponent construction))
|
||||
construction.AddContainer("board");
|
||||
}
|
||||
|
||||
public void MapInit()
|
||||
|
||||
@@ -22,22 +22,15 @@ namespace Content.Server.GameObjects.Components.Destructible
|
||||
|
||||
public override string Name => "Destructible";
|
||||
|
||||
[ViewVariables]
|
||||
private SortedDictionary<int, Threshold> _lowestToHighestThresholds = new();
|
||||
[ViewVariables] private List<Threshold> _thresholds = new();
|
||||
|
||||
[ViewVariables] private int PreviousTotalDamage { get; set; }
|
||||
|
||||
public IReadOnlyDictionary<int, Threshold> LowestToHighestThresholds => _lowestToHighestThresholds;
|
||||
public IReadOnlyList<Threshold> Thresholds => _thresholds;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"thresholds",
|
||||
new Dictionary<int, Threshold>(),
|
||||
thresholds => _lowestToHighestThresholds = new SortedDictionary<int, Threshold>(thresholds),
|
||||
() => new Dictionary<int, Threshold>(_lowestToHighestThresholds));
|
||||
serializer.DataField(ref _thresholds, "thresholds", new List<Threshold>());
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -60,32 +53,17 @@ namespace Content.Server.GameObjects.Components.Destructible
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (damage, threshold) in _lowestToHighestThresholds)
|
||||
foreach (var threshold in _thresholds)
|
||||
{
|
||||
if (threshold.Triggered)
|
||||
if (threshold.Reached(msg.Damageable, _destructibleSystem))
|
||||
{
|
||||
if (threshold.TriggersOnce)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (PreviousTotalDamage >= damage)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.Damageable.TotalDamage >= damage)
|
||||
{
|
||||
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold, msg.Damageable.TotalDamage, damage);
|
||||
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold);
|
||||
SendMessage(thresholdMessage);
|
||||
|
||||
threshold.Trigger(Owner, _destructibleSystem);
|
||||
threshold.Execute(Owner, _destructibleSystem);
|
||||
}
|
||||
}
|
||||
|
||||
PreviousTotalDamage = msg.Damageable.TotalDamage;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,26 +5,14 @@ namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public class DestructibleThresholdReachedMessage : ComponentMessage
|
||||
{
|
||||
public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold, int totalDamage, int thresholdAmount)
|
||||
public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold)
|
||||
{
|
||||
Parent = parent;
|
||||
Threshold = threshold;
|
||||
TotalDamage = totalDamage;
|
||||
ThresholdAmount = thresholdAmount;
|
||||
}
|
||||
|
||||
public DestructibleComponent Parent { get; }
|
||||
|
||||
public Threshold Threshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of total damage currently had that triggered this threshold.
|
||||
/// </summary>
|
||||
public int TotalDamage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of damage at which this threshold triggers.
|
||||
/// </summary>
|
||||
public int ThresholdAmount { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using System;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
[Serializable]
|
||||
public class DoActsBehavior : IThresholdBehavior
|
||||
{
|
||||
private int _acts;
|
||||
@@ -30,7 +32,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
return (_acts & (int) act) != 0;
|
||||
}
|
||||
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
if (HasAct(ThresholdActs.Breakage))
|
||||
{
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GibBehavior : IThresholdBehavior
|
||||
@@ -17,7 +17,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
serializer.DataField(ref _recursive, "recursive", true);
|
||||
}
|
||||
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
if (owner.TryGetComponent(out IBody body))
|
||||
{
|
||||
@@ -2,18 +2,18 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
public interface IThresholdBehavior : IExposeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers this behavior.
|
||||
/// Executes this behavior.
|
||||
/// </summary>
|
||||
/// <param name="owner">The entity that owns this behavior.</param>
|
||||
/// <param name="system">
|
||||
/// An instance of <see cref="DestructibleSystem"/> to pull dependencies
|
||||
/// and other systems from.
|
||||
/// </param>
|
||||
void Trigger(IEntity owner, DestructibleSystem system);
|
||||
void Execute(IEntity owner, DestructibleSystem system);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using System;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
[Serializable]
|
||||
public class PlaySoundBehavior : IThresholdBehavior
|
||||
{
|
||||
/// <summary>
|
||||
@@ -18,7 +20,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
serializer.DataField(this, x => x.Sound, "sound", string.Empty);
|
||||
}
|
||||
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Sound))
|
||||
{
|
||||
@@ -1,11 +1,13 @@
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using System;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
[Serializable]
|
||||
public class PlaySoundCollectionBehavior : IThresholdBehavior
|
||||
{
|
||||
/// <summary>
|
||||
@@ -18,7 +20,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
serializer.DataField(this, x => x.SoundCollection, "soundCollection", string.Empty);
|
||||
}
|
||||
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SoundCollection))
|
||||
{
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
@@ -7,8 +8,9 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
[Serializable]
|
||||
public class SpawnEntitiesBehavior : IThresholdBehavior
|
||||
{
|
||||
/// <summary>
|
||||
@@ -21,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
serializer.DataField(this, x => x.Spawn, "spawn", new Dictionary<string, MinMax>());
|
||||
}
|
||||
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
foreach (var (entityId, minMax) in Spawn)
|
||||
{
|
||||
@@ -7,14 +7,14 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class SpillBehavior : IThresholdBehavior
|
||||
{
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer) { }
|
||||
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
if (!owner.TryGetComponent(out SolutionContainerComponent? solutionContainer))
|
||||
return;
|
||||
@@ -1,9 +1,11 @@
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using System;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds
|
||||
{
|
||||
[Serializable]
|
||||
public struct MinMax : IExposeData
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -13,6 +15,12 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
|
||||
{
|
||||
private List<IThresholdBehavior> _behaviors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this threshold was triggered in the previous call to
|
||||
/// <see cref="Reached"/>.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool OldTriggered { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this threshold has already been triggered.
|
||||
/// </summary>
|
||||
@@ -26,6 +34,11 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
|
||||
/// </summary>
|
||||
[ViewVariables] public bool TriggersOnce { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The trigger that decides if this threshold has been reached.
|
||||
/// </summary>
|
||||
[ViewVariables] public IThresholdTrigger? Trigger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Behaviors to activate once this threshold is triggered.
|
||||
/// </summary>
|
||||
@@ -35,9 +48,37 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
|
||||
{
|
||||
serializer.DataField(this, x => x.Triggered, "triggered", false);
|
||||
serializer.DataField(this, x => x.TriggersOnce, "triggersOnce", false);
|
||||
serializer.DataField(this, x => x.Trigger, "trigger", null);
|
||||
serializer.DataField(ref _behaviors, "behaviors", new List<IThresholdBehavior>());
|
||||
}
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
if (Trigger == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Triggered && TriggersOnce)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OldTriggered)
|
||||
{
|
||||
OldTriggered = Trigger.Reached(damageable, system);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Trigger.Reached(damageable, system))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OldTriggered = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers this threshold.
|
||||
/// </summary>
|
||||
@@ -46,7 +87,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
|
||||
/// An instance of <see cref="DestructibleSystem"/> to get dependency and
|
||||
/// system references from, if relevant.
|
||||
/// </param>
|
||||
public void Trigger(IEntity owner, DestructibleSystem system)
|
||||
public void Execute(IEntity owner, DestructibleSystem system)
|
||||
{
|
||||
Triggered = true;
|
||||
|
||||
@@ -56,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Destructible.Thresholds
|
||||
if (owner.Deleted)
|
||||
return;
|
||||
|
||||
behavior.Trigger(owner, system);
|
||||
behavior.Execute(owner, system);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
|
||||
{
|
||||
public interface IThresholdTrigger : IExposeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if this trigger has been reached.
|
||||
/// </summary>
|
||||
/// <param name="damageable">The damageable component to check with.</param>
|
||||
/// <param name="system">
|
||||
/// An instance of <see cref="DestructibleSystem"/> to pull
|
||||
/// dependencies from, if any.
|
||||
/// </param>
|
||||
/// <returns>true if this trigger has been reached, false otherwise.</returns>
|
||||
bool Reached(IDamageableComponent damageable, DestructibleSystem system);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// A trigger that will activate when all of the damage classes received
|
||||
/// are above the specified threshold.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TotalDamageClassesTrigger : IThresholdTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of damage at which this threshold will trigger.
|
||||
/// The damage requirements of all <see cref="DamageClass"/> must be met.
|
||||
/// </summary>
|
||||
private Dictionary<DamageClass, int> Damage { get; set; } = new();
|
||||
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => x.Damage, "damage", new Dictionary<DamageClass, int>());
|
||||
}
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
foreach (var (type, damageRequired) in Damage)
|
||||
{
|
||||
if (!damageable.TryGetDamage(type, out var damageReceived))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (damageReceived < damageRequired)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// A trigger that will activate when the amount of total damage received
|
||||
/// is above the specified threshold.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TotalDamageTrigger : IThresholdTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of total damage at which this threshold will trigger.
|
||||
/// </summary>
|
||||
public int Damage { get; private set; }
|
||||
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => x.Damage, "damage", 0);
|
||||
}
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
return damageable.TotalDamage >= Damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// A trigger that will activate when all of the damage types received
|
||||
/// are above the specified threshold.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TotalDamageTypesTrigger : IThresholdTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of damage at which this threshold will trigger.
|
||||
/// The damage requirements of all <see cref="DamageType"/> must be met.
|
||||
/// </summary>
|
||||
private Dictionary<DamageType, int> Damage { get; set; } = new();
|
||||
|
||||
void IExposeData.ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => x.Damage, "damage", new Dictionary<DamageType, int>());
|
||||
}
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
foreach (var (type, damageRequired) in Damage)
|
||||
{
|
||||
if (!damageable.TryGetDamage(type, out var damageReceived))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (damageReceived < damageRequired)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,9 +80,18 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private TimeSpan _flushDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Delay from trying to enter disposals ourselves.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _entryDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Delay from trying to shove someone else into disposals.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _draggedEntryDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Token used to cancel the automatic engage of a disposal unit
|
||||
/// after an entity enters it.
|
||||
@@ -192,11 +201,15 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
if (!CanInsert(entity))
|
||||
return false;
|
||||
|
||||
if (user != null && _entryDelay > 0f)
|
||||
var delay = user == entity ? _entryDelay : _draggedEntryDelay;
|
||||
|
||||
if (user != null && delay > 0.0f)
|
||||
{
|
||||
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
|
||||
var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner)
|
||||
// Can't check if our target AND disposals moves currently so we'll just check target.
|
||||
// if you really want to check if disposals moves then add a predicate.
|
||||
var doAfterArgs = new DoAfterEventArgs(user, delay, default, entity)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true,
|
||||
@@ -209,7 +222,6 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
if (result == DoAfterStatus.Cancelled)
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
if (!_container.Insert(entity))
|
||||
@@ -535,7 +547,8 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
() => (int) _flushDelay.TotalSeconds);
|
||||
|
||||
serializer.DataField(this, x => x.Air, "air", new GasMixture(Atmospherics.CellVolume));
|
||||
serializer.DataField(ref _entryDelay, "entryDelay", 0.5f);
|
||||
serializer.DataField(ref _entryDelay, "entryDelay", 1.0f);
|
||||
serializer.DataField(ref _draggedEntryDelay, "draggedEntryDelay", 3.0f);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -24,6 +24,7 @@ using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefine
|
||||
namespace Content.Server.GameObjects.Components.GUI
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedStrippableComponent))]
|
||||
public sealed class StrippableComponent : SharedStrippableComponent
|
||||
{
|
||||
public const float StripDelay = 2f;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
namespace Content.Server.GameObjects.Components.Mobs.Speech
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Mobs.Speech;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.Speech
|
||||
{
|
||||
internal interface IAccentComponent
|
||||
{
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Mobs;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
@@ -8,6 +9,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
public override string Name => "VisitingMind";
|
||||
|
||||
[ViewVariables]
|
||||
public Mind Mind { get; set; }
|
||||
|
||||
public override void OnRemove()
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using System.Linq;
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Body.Behavior;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Fluids;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -36,22 +38,39 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
public override string Name => "Drink";
|
||||
|
||||
[ViewVariables]
|
||||
private SolutionContainerComponent _contents;
|
||||
private bool _opened;
|
||||
|
||||
[ViewVariables]
|
||||
private string _useSound;
|
||||
private string _useSound = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _defaultToOpened;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit TransferAmount { get; private set; } = ReagentUnit.New(2);
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Opened { get; protected set; }
|
||||
[ViewVariables]
|
||||
public bool Empty => _contents.CurrentVolume.Float() <= 0;
|
||||
|
||||
private AppearanceComponent _appearanceComponent;
|
||||
private string _soundCollection;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit TransferAmount { get; [UsedImplicitly] private set; } = ReagentUnit.New(2);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Opened
|
||||
{
|
||||
get => _opened;
|
||||
set
|
||||
{
|
||||
if (_opened == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_opened = value;
|
||||
OpenedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool Empty => Owner.GetComponentOrNull<ISolutionInteractionsComponent>()?.DrainAvailable <= 0;
|
||||
|
||||
private string _soundCollection = string.Empty;
|
||||
private bool _pressurized;
|
||||
private string _burstSound;
|
||||
private string _burstSound = string.Empty;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
@@ -66,18 +85,28 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.TryGetComponent(out _appearanceComponent);
|
||||
|
||||
if (!Owner.TryGetComponent(out _contents))
|
||||
{
|
||||
_contents = Owner.AddComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
_contents.Capabilities = SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable;
|
||||
Opened = _defaultToOpened;
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private void OpenedChanged()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? contents))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Opened)
|
||||
{
|
||||
contents.Capabilities |= SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable;
|
||||
}
|
||||
else
|
||||
{
|
||||
contents.Capabilities &= ~(SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable);
|
||||
}
|
||||
}
|
||||
|
||||
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
|
||||
{
|
||||
UpdateAppearance();
|
||||
@@ -85,7 +114,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.Visual, _contents.CurrentVolume.Float());
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent? appearance) ||
|
||||
!Owner.TryGetComponent(out ISolutionInteractionsComponent? contents))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
appearance.SetData(SharedFoodComponent.FoodVisuals.Visual, contents.DrainAvailable.Float());
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs args)
|
||||
@@ -101,7 +136,8 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_contents.CurrentVolume.Float() <= 0)
|
||||
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? contents) ||
|
||||
contents.DrainAvailable <= 0)
|
||||
{
|
||||
args.User.PopupMessage(Loc.GetString("{0:theName} is empty!", Owner));
|
||||
return true;
|
||||
@@ -113,7 +149,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
//Force feeding a drink to someone.
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
TryUseDrink(eventArgs.Target, forced: true);
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TryUseDrink(eventArgs.Target, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -130,18 +172,15 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
private bool TryUseDrink(IEntity target, bool forced = false)
|
||||
{
|
||||
if (target == null || !_contents.CanDrain)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Opened)
|
||||
{
|
||||
target.PopupMessage(Loc.GetString("Open {0:theName} first!", Owner));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_contents.CurrentVolume.Float() <= 0)
|
||||
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? interactions) ||
|
||||
!interactions.CanDrain ||
|
||||
interactions.DrainAvailable <= 0)
|
||||
{
|
||||
if (!forced)
|
||||
{
|
||||
@@ -151,26 +190,33 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target.TryGetComponent(out IBody body) ||
|
||||
if (!target.TryGetComponent(out IBody? body) ||
|
||||
!body.TryGetMechanismBehaviors<StomachBehavior>(out var stomachs))
|
||||
{
|
||||
target.PopupMessage(Loc.GetString("You can't drink {0:theName}!", Owner));
|
||||
return false;
|
||||
}
|
||||
|
||||
var transferAmount = ReagentUnit.Min(TransferAmount, _contents.CurrentVolume);
|
||||
var split = _contents.SplitSolution(transferAmount);
|
||||
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split));
|
||||
|
||||
var transferAmount = ReagentUnit.Min(TransferAmount, interactions.DrainAvailable);
|
||||
var drain = interactions.Drain(transferAmount);
|
||||
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(drain));
|
||||
|
||||
// All stomach are full or can't handle whatever solution we have.
|
||||
if (firstStomach == null)
|
||||
{
|
||||
_contents.TryAddSolution(split);
|
||||
target.PopupMessage(Loc.GetString("You've had enough {0:theName}!", Owner));
|
||||
|
||||
if (!interactions.CanRefill)
|
||||
{
|
||||
drain.SpillAt(target, "PuddleSmear");
|
||||
return false;
|
||||
}
|
||||
|
||||
interactions.Refill(drain);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_useSound != null)
|
||||
if (!string.IsNullOrEmpty(_useSound))
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_useSound, target, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
@@ -180,9 +226,9 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
// TODO: Account for partial transfer.
|
||||
|
||||
split.DoEntityReaction(target, ReactionMethod.Ingestion);
|
||||
drain.DoEntityReaction(target, ReactionMethod.Ingestion);
|
||||
|
||||
firstStomach.TryTransferSolution(split);
|
||||
firstStomach.TryTransferSolution(drain);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -192,11 +238,16 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
if (_pressurized &&
|
||||
!Opened &&
|
||||
_random.Prob(0.25f) &&
|
||||
Owner.TryGetComponent(out SolutionContainerComponent component))
|
||||
Owner.TryGetComponent(out ISolutionInteractionsComponent? interactions))
|
||||
{
|
||||
Opened = true;
|
||||
|
||||
var solution = component.SplitSolution(component.CurrentVolume);
|
||||
if (!interactions.CanDrain)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var solution = interactions.Drain(interactions.DrainAvailable);
|
||||
solution.SpillAt(Owner, "PuddleSmear");
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_burstSound, Owner,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Commands.Observer;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Observer
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IGhostOnMove))]
|
||||
public class GhostOnMoveComponent : Component, IRelayMoveInput, IGhostOnMove
|
||||
{
|
||||
public override string Name => "GhostOnMove";
|
||||
|
||||
public bool CanReturn { get; set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.CanReturn, "canReturn", true);
|
||||
}
|
||||
|
||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
||||
{
|
||||
// Let's not ghost if our mind is visiting...
|
||||
if (Owner.HasComponent<VisitingMindComponent>()) return;
|
||||
if (!Owner.TryGetComponent(out MindComponent? mind) || !mind.HasMind || mind.Mind!.IsVisitingEntity) return;
|
||||
|
||||
var host = IoCManager.Resolve<IServerConsoleHost>();
|
||||
new Ghost().Execute(new ConsoleShell(host, session), string.Empty, Array.Empty<string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Observer
|
||||
{
|
||||
public interface IGhostOnMove
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
|
||||
private static readonly TimeSpan _thunkDelay = TimeSpan.FromSeconds(2);
|
||||
private TimeSpan _lastThunk;
|
||||
private bool _hasLampOnSpawn;
|
||||
|
||||
[ViewVariables] private bool _on;
|
||||
|
||||
@@ -148,6 +149,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
{
|
||||
serializer.DataField(ref BulbType, "bulb", LightBulbType.Tube);
|
||||
serializer.DataField(ref _on, "on", true);
|
||||
serializer.DataField(ref _hasLampOnSpawn, "hasLampOnSpawn", true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -229,15 +231,19 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
|
||||
void IMapInit.MapInit()
|
||||
{
|
||||
var prototype = BulbType switch
|
||||
if (_hasLampOnSpawn)
|
||||
{
|
||||
LightBulbType.Bulb => "LightBulb",
|
||||
LightBulbType.Tube => "LightTube",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
var prototype = BulbType switch
|
||||
{
|
||||
LightBulbType.Bulb => "LightBulb",
|
||||
LightBulbType.Tube => "LightTube",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var entity = Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.Coordinates);
|
||||
_lightBulbContainer.Insert(entity);
|
||||
var entity = Owner.EntityManager.SpawnEntity(prototype, Owner.Transform.Coordinates);
|
||||
_lightBulbContainer.Insert(entity);
|
||||
UpdateLight();
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerSignal(bool signal)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
@@ -7,6 +8,7 @@ using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Server.GameObjects.Components.Destructible;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -22,7 +24,6 @@ namespace Content.Server.GameObjects.Components
|
||||
[ComponentReference(typeof(SharedWindowComponent))]
|
||||
public class WindowComponent : SharedWindowComponent, IExamine, IInteractHand
|
||||
{
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
@@ -40,26 +41,52 @@ namespace Content.Server.GameObjects.Components
|
||||
|
||||
private void UpdateVisuals(int currentDamage)
|
||||
{
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance) &&
|
||||
Owner.TryGetComponent(out DestructibleComponent? destructible))
|
||||
{
|
||||
if (Owner.TryGetComponent(out DestructibleComponent? destructible))
|
||||
foreach (var threshold in destructible.Thresholds)
|
||||
{
|
||||
var damageThreshold = destructible.LowestToHighestThresholds.FirstOrNull()?.Key;
|
||||
if (damageThreshold == null) return;
|
||||
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / damageThreshold);
|
||||
if (threshold.Trigger is not TotalDamageTrigger trigger)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / trigger.Damage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
var damage = Owner.GetComponentOrNull<IDamageableComponent>()?.TotalDamage;
|
||||
var damageThreshold = Owner.GetComponentOrNull<DestructibleComponent>()?.LowestToHighestThresholds.FirstOrNull()?.Key;
|
||||
if (damage == null || damageThreshold == null) return;
|
||||
var fraction = ((damage == 0 || damageThreshold == 0)
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable) ||
|
||||
!Owner.TryGetComponent(out DestructibleComponent? destructible))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var damage = damageable.TotalDamage;
|
||||
TotalDamageTrigger? trigger = null;
|
||||
|
||||
// TODO: Pretend this does not exist until https://github.com/space-wizards/space-station-14/pull/2783 is merged
|
||||
foreach (var threshold in destructible.Thresholds)
|
||||
{
|
||||
if ((trigger = threshold.Trigger as TotalDamageTrigger) != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var damageThreshold = trigger.Damage;
|
||||
var fraction = damage == 0 || damageThreshold == 0
|
||||
? 0f
|
||||
: (float) damage / damageThreshold);
|
||||
: (float) damage / damageThreshold;
|
||||
var level = Math.Min(ContentHelpers.RoundToLevels((double) fraction, 1, 7), 5);
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case 0:
|
||||
|
||||
Reference in New Issue
Block a user