2022-02-07 00:34:13 +11:00
using Content.Server.Body.Components ;
using Content.Server.Chemistry.Components ;
using Content.Server.Chemistry.Components.SolutionManager ;
using Content.Server.CombatMode ;
using Content.Server.DoAfter ;
using Content.Shared.Chemistry.Components ;
using Content.Shared.Chemistry.Reagent ;
using Content.Shared.Database ;
using Content.Shared.FixedPoint ;
using Content.Shared.Hands ;
2022-07-10 18:36:53 -07:00
using Content.Shared.IdentityManagement ;
2022-02-07 00:34:13 +11:00
using Content.Shared.Interaction ;
2022-03-13 01:33:23 +13:00
using Content.Shared.Interaction.Events ;
2022-02-07 00:34:13 +11:00
using Robust.Shared.GameStates ;
using Robust.Shared.Player ;
2022-07-29 14:13:12 +12:00
using System.Threading ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Components ;
2022-10-26 01:40:06 -05:00
using Content.Shared.Verbs ;
using Robust.Server.GameObjects ;
using Content.Shared.Popups ;
2022-02-07 00:34:13 +11:00
namespace Content.Server.Chemistry.EntitySystems ;
public sealed partial class ChemistrySystem
{
2022-10-26 01:40:06 -05:00
/// <summary>
/// Default transfer amounts for the set-transfer verb.
/// </summary>
2022-11-09 16:59:54 -08:00
public static readonly List < int > TransferAmounts = new ( ) { 1 , 5 , 10 , 15 } ;
2022-02-07 00:34:13 +11:00
private void InitializeInjector ( )
{
2022-10-26 01:40:06 -05:00
SubscribeLocalEvent < InjectorComponent , GetVerbsEvent < AlternativeVerb > > ( AddSetTransferVerbs ) ;
2022-02-07 00:34:13 +11:00
SubscribeLocalEvent < InjectorComponent , SolutionChangedEvent > ( OnSolutionChange ) ;
SubscribeLocalEvent < InjectorComponent , HandDeselectedEvent > ( OnInjectorDeselected ) ;
SubscribeLocalEvent < InjectorComponent , ComponentStartup > ( OnInjectorStartup ) ;
SubscribeLocalEvent < InjectorComponent , UseInHandEvent > ( OnInjectorUse ) ;
SubscribeLocalEvent < InjectorComponent , AfterInteractEvent > ( OnInjectorAfterInteract ) ;
SubscribeLocalEvent < InjectorComponent , ComponentGetState > ( OnInjectorGetState ) ;
SubscribeLocalEvent < InjectionCompleteEvent > ( OnInjectionComplete ) ;
SubscribeLocalEvent < InjectionCancelledEvent > ( OnInjectionCancelled ) ;
}
2022-10-26 01:40:06 -05:00
private void AddSetTransferVerbs ( EntityUid uid , InjectorComponent component , GetVerbsEvent < AlternativeVerb > args )
{
if ( ! args . CanAccess | | ! args . CanInteract | | args . Hands = = null )
return ;
if ( ! EntityManager . TryGetComponent < ActorComponent ? > ( args . User , out var actor ) )
return ;
// Add specific transfer verbs according to the container's size
var priority = 0 ;
foreach ( var amount in TransferAmounts )
{
if ( amount < component . MinimumTransferAmount . Int ( ) | | amount > component . MaximumTransferAmount . Int ( ) )
continue ;
AlternativeVerb verb = new ( ) ;
verb . Text = Loc . GetString ( "comp-solution-transfer-verb-amount" , ( "amount" , amount ) ) ;
verb . Category = VerbCategory . SetTransferAmount ;
verb . Act = ( ) = >
{
component . TransferAmount = FixedPoint2 . New ( amount ) ;
args . User . PopupMessage ( Loc . GetString ( "comp-solution-transfer-set-amount" , ( "amount" , amount ) ) ) ;
} ;
// we want to sort by size, not alphabetically by the verb text.
verb . Priority = priority ;
priority - - ;
args . Verbs . Add ( verb ) ;
}
}
2022-02-07 00:34:13 +11:00
private static void OnInjectionCancelled ( InjectionCancelledEvent ev )
{
ev . Component . CancelToken = null ;
}
private void OnInjectionComplete ( InjectionCompleteEvent ev )
{
2022-02-10 04:08:59 +13:00
ev . Component . CancelToken = null ;
UseInjector ( ev . Target , ev . User , ev . Component ) ;
}
2022-02-07 00:34:13 +11:00
2022-02-10 04:08:59 +13:00
private void UseInjector ( EntityUid target , EntityUid user , InjectorComponent component )
2022-02-17 15:00:41 -07:00
{
2022-02-07 00:34:13 +11:00
// Handle injecting/drawing for solutions
if ( component . ToggleState = = SharedInjectorComponent . InjectorToggleMode . Inject )
{
if ( _solutions . TryGetInjectableSolution ( target , out var injectableSolution ) )
{
TryInject ( component , target , injectableSolution , user , false ) ;
}
else if ( _solutions . TryGetRefillableSolution ( target , out var refillableSolution ) )
{
TryInject ( component , target , refillableSolution , user , true ) ;
}
else if ( TryComp < BloodstreamComponent > ( target , out var bloodstream ) )
{
TryInjectIntoBloodstream ( component , bloodstream , user ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "injector-component-cannot-transfer-message" ,
2022-12-19 10:41:47 +13:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
}
}
else if ( component . ToggleState = = SharedInjectorComponent . InjectorToggleMode . Draw )
{
2022-08-25 23:56:56 +10:00
// Draw from a bloodstream, if the target has that
2022-07-04 20:37:21 -04:00
if ( TryComp < BloodstreamComponent > ( target , out var stream ) )
{
TryDraw ( component , target , stream . BloodSolution , user , stream ) ;
return ;
}
2022-08-25 23:56:56 +10:00
// Draw from an object (food, beaker, etc)
2022-02-07 00:34:13 +11:00
if ( _solutions . TryGetDrawableSolution ( target , out var drawableSolution ) )
{
TryDraw ( component , target , drawableSolution , user ) ;
}
else
{
_popup . PopupEntity ( Loc . GetString ( "injector-component-cannot-draw-message" ,
2022-12-19 10:41:47 +13:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
}
}
}
private static void OnInjectorDeselected ( EntityUid uid , InjectorComponent component , HandDeselectedEvent args )
{
component . CancelToken ? . Cancel ( ) ;
component . CancelToken = null ;
}
private void OnSolutionChange ( EntityUid uid , InjectorComponent component , SolutionChangedEvent args )
{
Dirty ( component ) ;
}
private void OnInjectorGetState ( EntityUid uid , InjectorComponent component , ref ComponentGetState args )
{
_solutions . TryGetSolution ( uid , InjectorComponent . SolutionName , out var solution ) ;
2023-01-12 16:41:40 +13:00
var currentVolume = solution ? . Volume ? ? FixedPoint2 . Zero ;
2022-02-07 00:34:13 +11:00
var maxVolume = solution ? . MaxVolume ? ? FixedPoint2 . Zero ;
args . State = new SharedInjectorComponent . InjectorComponentState ( currentVolume , maxVolume , component . ToggleState ) ;
}
private void OnInjectorAfterInteract ( EntityUid uid , InjectorComponent component , AfterInteractEvent args )
{
2022-08-25 23:56:56 +10:00
if ( args . Handled | | ! args . CanReach )
return ;
2022-02-07 00:34:13 +11:00
if ( component . CancelToken ! = null )
{
args . Handled = true ;
return ;
}
//Make sure we have the attacking entity
if ( args . Target is not { Valid : true } target | |
! HasComp < SolutionContainerManagerComponent > ( uid ) )
{
return ;
}
// Is the target a mob? If yes, use a do-after to give them time to respond.
if ( HasComp < MobStateComponent > ( target ) | |
HasComp < BloodstreamComponent > ( target ) )
{
2022-11-09 16:59:54 -08:00
// Are use using an injector capible of targeting a mob?
if ( component . IgnoreMobs )
return ;
2022-02-07 00:34:13 +11:00
InjectDoAfter ( component , args . User , target ) ;
args . Handled = true ;
return ;
}
2022-02-10 04:08:59 +13:00
UseInjector ( target , args . User , component ) ;
args . Handled = true ;
2022-02-07 00:34:13 +11:00
}
private void OnInjectorStartup ( EntityUid uid , InjectorComponent component , ComponentStartup args )
{
2022-09-07 20:35:34 +12:00
/// ???? why ?????
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
}
private void OnInjectorUse ( EntityUid uid , InjectorComponent component , UseInHandEvent args )
{
2022-08-25 23:56:56 +10:00
if ( args . Handled )
return ;
2022-02-07 00:34:13 +11:00
Toggle ( component , args . User ) ;
args . Handled = true ;
}
/// <summary>
/// Toggle between draw/inject state if applicable
/// </summary>
private void Toggle ( InjectorComponent component , EntityUid user )
{
if ( component . InjectOnly )
{
return ;
}
string msg ;
switch ( component . ToggleState )
{
case SharedInjectorComponent . InjectorToggleMode . Inject :
component . ToggleState = SharedInjectorComponent . InjectorToggleMode . Draw ;
msg = "injector-component-drawing-text" ;
break ;
case SharedInjectorComponent . InjectorToggleMode . Draw :
component . ToggleState = SharedInjectorComponent . InjectorToggleMode . Inject ;
msg = "injector-component-injecting-text" ;
break ;
default :
throw new ArgumentOutOfRangeException ( ) ;
}
2022-12-19 10:41:47 +13:00
_popup . PopupEntity ( Loc . GetString ( msg ) , component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
}
/// <summary>
/// Send informative pop-up messages and wait for a do-after to complete.
/// </summary>
private void InjectDoAfter ( InjectorComponent component , EntityUid user , EntityUid target )
{
// Create a pop-up for the user
2022-12-19 10:41:47 +13:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-injecting-user" ) , target , user ) ;
2022-02-07 00:34:13 +11:00
if ( ! _solutions . TryGetSolution ( component . Owner , InjectorComponent . SolutionName , out var solution ) )
return ;
var actualDelay = MathF . Max ( component . Delay , 1f ) ;
2022-10-26 01:40:06 -05:00
// Injections take 1 second longer per additional 5u
actualDelay + = ( float ) component . TransferAmount / component . Delay - 1 ;
2022-02-07 00:34:13 +11:00
if ( user ! = target )
{
// Create a pop-up for the target
2022-07-31 15:43:38 +12:00
var userName = Identity . Entity ( user , EntityManager ) ;
2022-02-07 00:34:13 +11:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-injecting-target" ,
2022-12-19 10:41:47 +13:00
( "user" , userName ) ) , user , target ) ;
2022-02-07 00:34:13 +11:00
// Check if the target is incapacitated or in combat mode and modify time accordingly.
2022-08-25 23:56:56 +10:00
if ( _mobState . IsIncapacitated ( target ) )
2022-02-07 00:34:13 +11:00
{
actualDelay / = 2 ;
}
2022-08-25 23:56:56 +10:00
else if ( _combat . IsInCombatMode ( target ) )
2022-02-07 00:34:13 +11:00
{
// 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.
if ( component . ToggleState = = SharedInjectorComponent . InjectorToggleMode . Inject )
{
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . ForceFeed ,
2022-02-07 00:34:13 +11:00
$"{EntityManager.ToPrettyString(user):user} is attempting to inject {EntityManager.ToPrettyString(target):target} with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}" ) ;
}
}
else
{
// Self-injections take half as long.
actualDelay / = 2 ;
if ( component . ToggleState = = SharedInjectorComponent . InjectorToggleMode . Inject )
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Ingestion ,
2022-02-07 00:34:13 +11:00
$"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}." ) ;
}
component . CancelToken = new CancellationTokenSource ( ) ;
_doAfter . DoAfter ( new DoAfterEventArgs ( user , actualDelay , component . CancelToken . Token , target )
{
BreakOnUserMove = true ,
BreakOnDamage = true ,
BreakOnStun = true ,
BreakOnTargetMove = true ,
MovementThreshold = 0.1f ,
BroadcastFinishedEvent = new InjectionCompleteEvent ( )
{
Component = component ,
User = user ,
Target = target ,
} ,
BroadcastCancelledEvent = new InjectionCancelledEvent ( )
{
Component = component ,
}
} ) ;
}
private void TryInjectIntoBloodstream ( InjectorComponent component , BloodstreamComponent targetBloodstream , EntityUid user )
{
// Get transfer amount. May be smaller than _transferAmount if not enough room
2022-02-17 15:00:41 -07:00
var realTransferAmount = FixedPoint2 . Min ( component . TransferAmount , targetBloodstream . ChemicalSolution . AvailableVolume ) ;
2022-02-07 00:34:13 +11:00
if ( realTransferAmount < = 0 )
{
2022-07-10 18:36:53 -07:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-cannot-inject-message" , ( "target" , Identity . Entity ( targetBloodstream . Owner , EntityManager ) ) ) ,
2022-12-19 10:41:47 +13:00
component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
return ;
}
// Move units from attackSolution to targetSolution
2022-02-17 15:00:41 -07:00
var removedSolution = _solutions . SplitSolution ( user , targetBloodstream . ChemicalSolution , realTransferAmount ) ;
2022-02-07 00:34:13 +11:00
2022-02-17 15:00:41 -07:00
_blood . TryAddToChemicals ( ( targetBloodstream ) . Owner , removedSolution , targetBloodstream ) ;
2022-02-07 00:34:13 +11:00
removedSolution . DoEntityReaction ( targetBloodstream . Owner , ReactionMethod . Injection ) ;
_popup . PopupEntity ( Loc . GetString ( "injector-component-inject-success-message" ,
2023-01-12 16:41:40 +13:00
( "amount" , removedSolution . Volume ) ,
2022-12-19 10:41:47 +13:00
( "target" , Identity . Entity ( targetBloodstream . Owner , EntityManager ) ) ) , component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
AfterInject ( component ) ;
}
private void TryInject ( InjectorComponent component , EntityUid targetEntity , Solution targetSolution , EntityUid user , bool asRefill )
{
if ( ! _solutions . TryGetSolution ( component . Owner , InjectorComponent . SolutionName , out var solution )
2023-01-12 16:41:40 +13:00
| | solution . Volume = = 0 )
2022-02-07 00:34:13 +11:00
{
return ;
}
// Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = FixedPoint2 . Min ( component . TransferAmount , targetSolution . AvailableVolume ) ;
if ( realTransferAmount < = 0 )
{
2022-07-10 18:36:53 -07:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-target-already-full-message" , ( "target" , Identity . Entity ( targetEntity , EntityManager ) ) ) ,
2022-12-19 10:41:47 +13:00
component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
return ;
}
// Move units from attackSolution to targetSolution
var removedSolution = _solutions . SplitSolution ( component . Owner , solution , realTransferAmount ) ;
removedSolution . DoEntityReaction ( targetEntity , ReactionMethod . Injection ) ;
if ( ! asRefill )
{
_solutions . Inject ( targetEntity , targetSolution , removedSolution ) ;
}
else
{
_solutions . Refill ( targetEntity , targetSolution , removedSolution ) ;
}
_popup . PopupEntity ( Loc . GetString ( "injector-component-transfer-success-message" ,
2023-01-12 16:41:40 +13:00
( "amount" , removedSolution . Volume ) ,
2022-12-19 10:41:47 +13:00
( "target" , Identity . Entity ( targetEntity , EntityManager ) ) ) , component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
AfterInject ( component ) ;
}
private void AfterInject ( InjectorComponent component )
{
// Automatically set syringe to draw after completely draining it.
if ( _solutions . TryGetSolution ( component . Owner , InjectorComponent . SolutionName , out var solution )
2023-01-12 16:41:40 +13:00
& & solution . Volume = = 0 )
2022-02-07 00:34:13 +11:00
{
component . ToggleState = SharedInjectorComponent . InjectorToggleMode . Draw ;
}
}
private void AfterDraw ( InjectorComponent component )
{
// Automatically set syringe to inject after completely filling it.
if ( _solutions . TryGetSolution ( component . Owner , InjectorComponent . SolutionName , out var solution )
& & solution . AvailableVolume = = 0 )
{
component . ToggleState = SharedInjectorComponent . InjectorToggleMode . Inject ;
}
}
2022-07-04 20:37:21 -04:00
private void TryDraw ( InjectorComponent component , EntityUid targetEntity , Solution targetSolution , EntityUid user , BloodstreamComponent ? stream = null )
2022-02-07 00:34:13 +11:00
{
if ( ! _solutions . TryGetSolution ( component . Owner , InjectorComponent . SolutionName , out var solution )
| | solution . AvailableVolume = = 0 )
{
return ;
}
2022-11-03 17:16:31 -07:00
// Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector
2023-01-12 16:41:40 +13:00
var realTransferAmount = FixedPoint2 . Min ( component . TransferAmount , targetSolution . Volume , solution . AvailableVolume ) ;
2022-02-07 00:34:13 +11:00
if ( realTransferAmount < = 0 )
{
2022-07-10 18:36:53 -07:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-target-is-empty-message" , ( "target" , Identity . Entity ( targetEntity , EntityManager ) ) ) ,
2022-12-19 10:41:47 +13:00
component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
return ;
}
2022-07-17 02:32:19 -04:00
// We have some snowflaked behavior for streams.
2022-07-04 20:37:21 -04:00
if ( stream ! = null )
{
2022-07-17 02:32:19 -04:00
DrawFromBlood ( user , targetEntity , component , solution , stream , realTransferAmount ) ;
2022-07-04 20:37:21 -04:00
return ;
}
2022-02-07 00:34:13 +11:00
// Move units from attackSolution to targetSolution
var removedSolution = _solutions . Draw ( targetEntity , targetSolution , realTransferAmount ) ;
2022-02-10 04:08:59 +13:00
if ( ! _solutions . TryAddSolution ( component . Owner , solution , removedSolution ) )
2022-02-07 00:34:13 +11:00
{
return ;
}
_popup . PopupEntity ( Loc . GetString ( "injector-component-draw-success-message" ,
2023-01-12 16:41:40 +13:00
( "amount" , removedSolution . Volume ) ,
2022-12-19 10:41:47 +13:00
( "target" , Identity . Entity ( targetEntity , EntityManager ) ) ) , component . Owner , user ) ;
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
AfterDraw ( component ) ;
}
2022-07-17 02:32:19 -04:00
private void DrawFromBlood ( EntityUid user , EntityUid target , InjectorComponent component , Solution injectorSolution , BloodstreamComponent stream , FixedPoint2 transferAmount )
2022-07-04 20:37:21 -04:00
{
2022-07-17 02:32:19 -04:00
var drawAmount = ( float ) transferAmount ;
var bloodAmount = drawAmount ;
var chemAmount = 0f ;
2023-01-12 16:41:40 +13:00
if ( stream . ChemicalSolution . Volume > 0f ) // If they have stuff in their chem stream, we'll draw some of that
2022-07-04 20:37:21 -04:00
{
bloodAmount = drawAmount * 0.85f ;
chemAmount = drawAmount * 0.15f ;
}
var bloodTemp = stream . BloodSolution . SplitSolution ( bloodAmount ) ;
var chemTemp = stream . ChemicalSolution . SplitSolution ( chemAmount ) ;
_solutions . TryAddSolution ( component . Owner , injectorSolution , bloodTemp ) ;
_solutions . TryAddSolution ( component . Owner , injectorSolution , chemTemp ) ;
_popup . PopupEntity ( Loc . GetString ( "injector-component-draw-success-message" ,
2022-07-17 02:32:19 -04:00
( "amount" , transferAmount ) ,
2022-12-19 10:41:47 +13:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , component . Owner , user ) ;
2022-07-04 20:37:21 -04:00
Dirty ( component ) ;
AfterDraw ( component ) ;
}
2022-02-07 00:34:13 +11:00
private sealed class InjectionCompleteEvent : EntityEventArgs
{
public InjectorComponent Component { get ; init ; } = default ! ;
public EntityUid User { get ; init ; }
public EntityUid Target { get ; init ; }
}
private sealed class InjectionCancelledEvent : EntityEventArgs
{
public InjectorComponent Component { get ; init ; } = default ! ;
}
}