Add Syringe DoAfter (#5658)

* syringe do after

* fix spacing

* delay and log tweaks

* Apply suggestions from code review

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* clean up comments

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2021-12-05 00:33:21 +13:00
committed by GitHub
parent 08c2aeec1b
commit 49a5696fd6
3 changed files with 113 additions and 7 deletions

View File

@@ -1,18 +1,25 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Administration.Logs;
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Server.CombatMode;
using Content.Server.DoAfter;
using Content.Shared.ActionBlocker;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.MobState.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Players; using Robust.Shared.Players;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -41,16 +48,25 @@ namespace Content.Server.Chemistry.Components
/// Amount to inject or draw on each usage. If the injector is inject only, it will /// Amount to inject or draw on each usage. If the injector is inject only, it will
/// attempt to inject it's entire contents upon use. /// attempt to inject it's entire contents upon use.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables(VVAccess.ReadWrite)]
[DataField("transferAmount")] [DataField("transferAmount")]
private FixedPoint2 _transferAmount = FixedPoint2.New(5); private FixedPoint2 _transferAmount = FixedPoint2.New(5);
/// <summary> /// <summary>
/// Initial storage volume of the injector /// Injection delay (seconds) when the target is a mob.
/// </summary> /// </summary>
[ViewVariables] /// <remarks>
[DataField("initialMaxVolume")] /// The base delay has a minimum of 1 second, but this will still be modified if the target is incapacitated or
private FixedPoint2 _initialMaxVolume = FixedPoint2.New(15); /// in combat mode.
/// </remarks>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("delay")]
public float Delay = 5;
/// <summary>
/// Is this component currently being used in a DoAfter?
/// </summary>
public bool InUse = false;
private InjectorToggleMode _toggleState; private InjectorToggleMode _toggleState;
@@ -111,9 +127,15 @@ namespace Content.Server.Chemistry.Components
/// <param name="eventArgs"></param> /// <param name="eventArgs"></param>
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
if (InUse)
return false;
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
return false; return false;
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(eventArgs.User.Uid))
return false;
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>(); var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
//Make sure we have the attacking entity //Make sure we have the attacking entity
if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerManagerComponent>()) if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerManagerComponent>())
@@ -123,6 +145,13 @@ namespace Content.Server.Chemistry.Components
var targetEntity = eventArgs.Target; var targetEntity = eventArgs.Target;
// Is the target a mob? If yes, use a do-after to give them time to respond.
if (Owner.EntityManager.HasComponent<MobStateComponent>(targetEntity.Uid) ||
Owner.EntityManager.HasComponent<BloodstreamComponent>(targetEntity.Uid))
{
if (!await TryInjectDoAfter(eventArgs.User.Uid, eventArgs.Target.Uid))
return true;
}
// Handle injecting/drawing for solutions // Handle injecting/drawing for solutions
if (ToggleState == InjectorToggleMode.Inject) if (ToggleState == InjectorToggleMode.Inject)
@@ -163,6 +192,77 @@ namespace Content.Server.Chemistry.Components
return true; return true;
} }
/// <summary>
/// Send informative pop-up messages and wait for a do-after to complete.
/// </summary>
public async Task<bool> TryInjectDoAfter(EntityUid user, EntityUid target)
{
InUse = true;
var popupSys = EntitySystem.Get<SharedPopupSystem>();
// Create a pop-up for the user
popupSys.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, Filter.Entities(user));
// Get entity for logging. Log with EntityUids when?
var userEntity = Owner.EntityManager.GetEntity(user);
var logSys = EntitySystem.Get<AdminLogSystem>();
var actualDelay = MathF.Max(Delay, 1f);
if (user != target)
{
// Create a pop-up for the target
var userName = Owner.EntityManager.GetComponent<MetaDataComponent>(user).EntityName;
popupSys.PopupEntity(Loc.GetString("injector-component-injecting-target",
("user", userName)), user, Filter.Entities(target));
// Check if the target is incapacitated or in combat mode and modify time accordingly.
if (Owner.EntityManager.TryGetComponent<MobStateComponent>(target, out var mobState) &&
mobState.IsIncapacitated())
{
actualDelay /= 2;
}
else if (Owner.EntityManager.TryGetComponent<CombatModeComponent>(target, out var combat) &&
combat.IsInCombatMode)
{
// Slightly increase the delay when the target is in combat mode. Helps prevents cheese injections in
// combat with fast syringes & lag.
actualDelay += 1;
}
// Add an admin log, using the "force feed" log type. It's not quite feeding, but the effect is the same.
var targetEntity = Owner.EntityManager.GetEntity(target);
if (ToggleState == InjectorToggleMode.Inject)
{
logSys.Add(LogType.ForceFeed,
$"{userEntity} is attempting to inject a solution into {targetEntity}");
// TODO solution pretty string.
}
}
else
{
// Self-injections take half as long.
actualDelay /= 2;
if (ToggleState == InjectorToggleMode.Inject)
logSys.Add(LogType.Ingestion,
$"{userEntity} is attempting to inject themselves with a solution.");
//TODO solution pretty string.
}
var status = await EntitySystem.Get<DoAfterSystem>().WaitDoAfter(
new DoAfterEventArgs(user, actualDelay, target: target)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
MovementThreshold = 1.0f
});
InUse = false;
return status == DoAfterStatus.Finished;
}
/// <summary> /// <summary>
/// Called when use key is pressed when held in active hand /// Called when use key is pressed when held in active hand
/// </summary> /// </summary>

View File

@@ -1,4 +1,4 @@
namespace Content.Shared.Database; namespace Content.Shared.Database;
// DO NOT CHANGE THE NUMERIC VALUES OF THESE // DO NOT CHANGE THE NUMERIC VALUES OF THESE
public enum LogType public enum LogType
@@ -40,7 +40,8 @@ public enum LogType
Pickup = 36, Pickup = 36,
Drop = 37, Drop = 37,
BulletHit = 38, BulletHit = 38,
ForceFeed = 40, ForceFeed = 40, // involuntary
Ingestion = 53, // voluntary
MeleeHit = 41, MeleeHit = 41,
HitScanHit = 42, HitScanHit = 42,
Suicide = 43, Suicide = 43,

View File

@@ -17,3 +17,8 @@ injector-component-transfer-success-message = You transfer {$amount}u into {$tar
injector-component-draw-success-message = You draw {$amount}u from {$target}. injector-component-draw-success-message = You draw {$amount}u from {$target}.
injector-component-target-already-full-message = {$target} is already full! injector-component-target-already-full-message = {$target} is already full!
injector-component-target-is-empty-message = {$target} is empty! injector-component-target-is-empty-message = {$target} is empty!
## mob-inject doafter messages
injector-component-injecting-user = You start inserting the needle.
injector-component-injecting-target = {$user} is trying to stick a needle into you!