You feel a tiny prick (Hypospray) (#3034)

This commit is contained in:
Pieter-Jan Briers
2021-01-24 14:18:12 +01:00
committed by GitHub
parent 72b5ae9468
commit c947bb75e6
10 changed files with 253 additions and 1 deletions

View File

@@ -0,0 +1,68 @@
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
#nullable enable
namespace Content.Client.GameObjects.Components.Chemistry
{
[RegisterComponent]
public sealed class HyposprayComponent : SharedHyposprayComponent, IItemStatus
{
[ViewVariables] private ReagentUnit CurrentVolume { get; set; }
[ViewVariables] private ReagentUnit TotalVolume { get; set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not HyposprayComponentState cState)
return;
CurrentVolume = cState.CurVolume;
TotalVolume = cState.MaxVolume;
_uiUpdateNeeded = true;
}
Control IItemStatus.MakeControl()
{
return new StatusControl(this);
}
private sealed class StatusControl : Control
{
private readonly HyposprayComponent _parent;
private readonly RichTextLabel _label;
public StatusControl(HyposprayComponent parent)
{
_parent = parent;
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
AddChild(_label);
parent._uiUpdateNeeded = true;
}
protected override void Update(FrameEventArgs args)
{
base.Update(args);
if (!_parent._uiUpdateNeeded)
{
return;
}
_parent._uiUpdateNeeded = false;
_label.SetMarkup(Loc.GetString(
"Volume: [color=white]{0}/{1}[/color]",
_parent.CurrentVolume, _parent.TotalVolume));
}
}
}
}

View File

@@ -0,0 +1,141 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Mobs.State;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.ComponentDependencies;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
#nullable enable
namespace Content.Server.GameObjects.Components.Chemistry
{
[RegisterComponent]
public sealed class HyposprayComponent : SharedHyposprayComponent, IAttack, ISolutionChange, IAfterInteract
{
[ViewVariables(VVAccess.ReadWrite)] public float ClumsyFailChance { get; set; }
[ViewVariables(VVAccess.ReadWrite)] public ReagentUnit TransferAmount { get; set; }
[ComponentDependency] private readonly SolutionContainerComponent? _solution = default!;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.ClumsyFailChance, "ClumsyFailChance", 0.5f);
serializer.DataField(this, x => x.TransferAmount, "TransferAmount", ReagentUnit.New(5));
}
public override void Initialize()
{
base.Initialize();
Dirty();
}
bool IAttack.ClickAttack(AttackEventArgs eventArgs)
{
var target = eventArgs.TargetEntity;
var user = eventArgs.User;
return TryDoInject(target, user);
}
Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
TryDoInject(eventArgs.Target, eventArgs.User);
return Task.CompletedTask;
}
private bool TryDoInject(IEntity? target, IEntity user)
{
if (target == null || !EligibleEntity(target))
return false;
var msgFormat = "You inject {0:TheName}.";
if (target == user)
{
msgFormat = "You inject yourself.";
}
else if (EligibleEntity(user) && ClumsyComponent.TryRollClumsy(user, ClumsyFailChance))
{
msgFormat = "Oops! You injected yourself!";
target = user;
}
if (_solution == null || _solution.CurrentVolume == 0)
{
user.PopupMessageCursor(Loc.GetString("It's empty!"));
return true;
}
user.PopupMessage(Loc.GetString(msgFormat, target));
if (target != user)
{
target.PopupMessage(Loc.GetString("You feel a tiny prick!"));
var meleeSys = EntitySystem.Get<MeleeWeaponSystem>();
var angle = new Angle(target.Transform.WorldPosition - user.Transform.WorldPosition);
meleeSys.SendLunge(angle, user);
}
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/hypospray.ogg", user);
var targetSolution = target.GetComponent<SolutionContainerComponent>();
// Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(TransferAmount, targetSolution.EmptyVolume);
if (realTransferAmount <= 0)
{
user.PopupMessage(user, Loc.GetString("{0:TheName} is already full!", targetSolution.Owner));
return true;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solution.SplitSolution(realTransferAmount);
if (!targetSolution.CanAddSolution(removedSolution))
{
return true;
}
removedSolution.DoEntityReaction(target, ReactionMethod.Injection);
targetSolution.TryAddSolution(removedSolution);
static bool EligibleEntity(IEntity entity)
{
// TODO: Does checking for BodyComponent make sense as a "can be hypospray'd" tag?
// In SS13 the hypospray ONLY works on mobs, NOT beakers or anything else.
return entity.HasComponent<SolutionContainerComponent>() && entity.HasComponent<MobStateComponent>();
}
return true;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
Dirty();
}
public override ComponentState GetComponentState()
{
if (_solution == null)
return new HyposprayComponentState(ReagentUnit.Zero, ReagentUnit.Zero);
return new HyposprayComponentState(_solution.CurrentVolume, _solution.MaxVolume);
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Chemistry
{
public abstract class SharedHyposprayComponent : Component
{
public sealed override string Name => "Hypospray";
public sealed override uint? NetID => ContentNetIDs.HYPOSPRAY;
[Serializable, NetSerializable]
protected sealed class HyposprayComponentState : ComponentState
{
public ReagentUnit CurVolume { get; }
public ReagentUnit MaxVolume { get; }
public HyposprayComponentState(ReagentUnit curVolume, ReagentUnit maxVolume) : base(ContentNetIDs.HYPOSPRAY)
{
CurVolume = curVolume;
MaxVolume = maxVolume;
}
}
}
}

View File

@@ -3,7 +3,8 @@
// Starting from 1000 to avoid crossover with engine.
public static class ContentNetIDs
{
// 1000
// As a CMO main I hereby declare the hypospray worthy of ID #1000.
public const uint HYPOSPRAY = 1000;
public const uint DESTRUCTIBLE = 1001;
public const uint MAGAZINE_BARREL = 1002;
public const uint HANDS = 1003;

Binary file not shown.

View File

@@ -0,0 +1,15 @@
- type: entity
name: hypospray
parent: BaseItem
description: A sterile injector for rapid administration of drugs to patients.
id: Hypospray
components:
- type: Sprite
sprite: Objects/Specific/Medical/hypospray.rsi
state: hypo
- type: Item
sprite: Objects/Specific/Medical/hypospray.rsi
- type: SolutionContainer
maxVol: 30
caps: AddTo, CanExamine
- type: Hypospray

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

View File

@@ -0,0 +1 @@
{"version": 1, "size": {"x": 32, "y": 32}, "license": "Taken from https://github.com/tgstation/tgstation/tree/727eb0a445bccbdc2d472e158e96b87fc0e997a1", "copyright": "CC-BY-SA-3.0", "states": [{"name": "hypo", "directions": 1, "delays": [[1.0]]}, {"name": "inhand-left", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "inhand-right", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]}