2022-02-07 00:34:13 +11:00
using Content.Server.Body.Components ;
using Content.Server.Chemistry.Components ;
using Content.Server.Chemistry.Components.SolutionManager ;
2023-04-03 13:13:48 +12:00
using Content.Shared.Chemistry ;
2022-02-07 00:34:13 +11:00
using Content.Shared.Chemistry.Components ;
using Content.Shared.Chemistry.Reagent ;
using Content.Shared.Database ;
using Content.Shared.FixedPoint ;
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 ;
2023-02-24 19:01:25 -05:00
using Content.Shared.DoAfter ;
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 ;
2023-05-06 10:23:05 +03:00
using Content.Shared.Stacks ;
2022-10-26 01:40:06 -05:00
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 ) ;
2023-04-03 13:13:48 +12:00
SubscribeLocalEvent < InjectorComponent , InjectorDoAfterEvent > ( OnInjectDoAfter ) ;
2022-02-07 00:34:13 +11:00
SubscribeLocalEvent < InjectorComponent , ComponentStartup > ( OnInjectorStartup ) ;
SubscribeLocalEvent < InjectorComponent , UseInHandEvent > ( OnInjectorUse ) ;
SubscribeLocalEvent < InjectorComponent , AfterInteractEvent > ( OnInjectorAfterInteract ) ;
SubscribeLocalEvent < InjectorComponent , ComponentGetState > ( OnInjectorGetState ) ;
}
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 ) ;
2023-02-24 19:01:25 -05:00
_popup . PopupEntity ( Loc . GetString ( "comp-solution-transfer-set-amount" , ( "amount" , amount ) ) , args . User , args . User ) ;
2022-10-26 01:40:06 -05:00
} ;
// we want to sort by size, not alphabetically by the verb text.
verb . Priority = priority ;
priority - - ;
args . Verbs . Add ( verb ) ;
}
}
2023-02-24 19:01:25 -05:00
private void UseInjector ( EntityUid target , EntityUid user , EntityUid injector , 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 ) )
{
2023-02-24 19:01:25 -05:00
TryInject ( component , injector , target , injectableSolution , user , false ) ;
2022-02-07 00:34:13 +11:00
}
else if ( _solutions . TryGetRefillableSolution ( target , out var refillableSolution ) )
{
2023-02-24 19:01:25 -05:00
TryInject ( component , injector , target , refillableSolution , user , true ) ;
2022-02-07 00:34:13 +11:00
}
else if ( TryComp < BloodstreamComponent > ( target , out var bloodstream ) )
{
2023-02-24 19:01:25 -05:00
TryInjectIntoBloodstream ( component , injector , target , bloodstream , user ) ;
2022-02-07 00:34:13 +11:00
}
else
{
_popup . PopupEntity ( Loc . GetString ( "injector-component-cannot-transfer-message" ,
2023-02-24 19:01:25 -05:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , injector , 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 ) )
{
2023-02-24 19:01:25 -05:00
TryDraw ( component , injector , target , stream . BloodSolution , user , stream ) ;
2022-07-04 20:37:21 -04:00
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 ) )
{
2023-02-24 19:01:25 -05:00
TryDraw ( component , injector , target , drawableSolution , user ) ;
2022-02-07 00:34:13 +11:00
}
else
{
_popup . PopupEntity ( Loc . GetString ( "injector-component-cannot-draw-message" ,
2023-02-24 19:01:25 -05:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , injector , user ) ;
2022-02-07 00:34:13 +11:00
}
}
}
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 ) ;
}
2023-02-24 19:01:25 -05:00
private void OnInjectDoAfter ( EntityUid uid , InjectorComponent component , DoAfterEvent args )
2022-02-07 00:34:13 +11:00
{
2023-04-03 13:13:48 +12:00
if ( args . Cancelled | | args . Handled | | args . Args . Target = = null )
2022-08-25 23:56:56 +10:00
return ;
2022-02-07 00:34:13 +11:00
2023-02-24 19:01:25 -05:00
UseInjector ( args . Args . Target . Value , args . Args . User , uid , component ) ;
args . Handled = true ;
}
private void OnInjectorAfterInteract ( EntityUid uid , InjectorComponent component , AfterInteractEvent args )
{
if ( args . Handled | | ! args . CanReach )
2022-02-07 00:34:13 +11:00
return ;
//Make sure we have the attacking entity
2023-02-24 19:01:25 -05:00
if ( args . Target is not { Valid : true } target | | ! HasComp < SolutionContainerManagerComponent > ( uid ) )
2022-02-07 00:34:13 +11:00
return ;
// Is the target a mob? If yes, use a do-after to give them time to respond.
2023-02-24 19:01:25 -05:00
if ( HasComp < MobStateComponent > ( target ) | | HasComp < BloodstreamComponent > ( target ) )
2022-02-07 00:34:13 +11:00
{
2022-11-09 16:59:54 -08:00
// Are use using an injector capible of targeting a mob?
if ( component . IgnoreMobs )
return ;
2023-02-24 19:01:25 -05:00
InjectDoAfter ( component , args . User , target , uid ) ;
2022-02-07 00:34:13 +11:00
args . Handled = true ;
return ;
}
2022-02-10 04:08:59 +13:00
2023-02-24 19:01:25 -05:00
UseInjector ( target , args . User , uid , component ) ;
2022-02-10 04:08:59 +13:00
args . Handled = true ;
2022-02-07 00:34:13 +11:00
}
private void OnInjectorStartup ( EntityUid uid , InjectorComponent component , ComponentStartup args )
{
2023-01-19 03:56:45 +01: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
2023-02-24 19:01:25 -05:00
Toggle ( component , args . User , uid ) ;
2022-02-07 00:34:13 +11:00
args . Handled = true ;
}
/// <summary>
/// Toggle between draw/inject state if applicable
/// </summary>
2023-02-24 19:01:25 -05:00
private void Toggle ( InjectorComponent component , EntityUid user , EntityUid injector )
2022-02-07 00:34:13 +11:00
{
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 ( ) ;
}
2023-02-24 19:01:25 -05:00
_popup . PopupEntity ( Loc . GetString ( msg ) , injector , user ) ;
2022-02-07 00:34:13 +11:00
}
/// <summary>
/// Send informative pop-up messages and wait for a do-after to complete.
/// </summary>
2023-02-24 19:01:25 -05:00
private void InjectDoAfter ( InjectorComponent component , EntityUid user , EntityUid target , EntityUid injector )
2022-02-07 00:34:13 +11:00
{
// 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
2023-02-24 19:01:25 -05:00
if ( ! _solutions . TryGetSolution ( injector , InjectorComponent . SolutionName , out var solution ) )
2022-02-07 00:34:13 +11:00
return ;
var actualDelay = MathF . Max ( component . Delay , 1f ) ;
2022-10-26 01:40:06 -05:00
2023-04-18 10:23:54 -04:00
// Injections take 0.5 seconds longer per additional 5u
actualDelay + = ( float ) component . TransferAmount / component . Delay - 0.5f ;
2022-10-26 01:40:06 -05:00
2023-02-26 00:33:06 -05:00
var isTarget = user ! = target ;
if ( isTarget )
2022-02-07 00:34:13 +11:00
{
// 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
{
2023-04-18 10:23:54 -04:00
actualDelay / = 2.5f ;
2022-02-07 00:34:13 +11:00
}
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 )
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . Ingestion , $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}." ) ;
2022-02-07 00:34:13 +11:00
}
2023-04-03 13:13:48 +12:00
_doAfter . TryStartDoAfter ( new DoAfterArgs ( user , actualDelay , new InjectorDoAfterEvent ( ) , injector , target : target , used : injector )
2022-02-07 00:34:13 +11:00
{
BreakOnUserMove = true ,
BreakOnDamage = true ,
BreakOnTargetMove = true ,
2023-04-03 13:13:48 +12:00
MovementThreshold = 0.1f ,
2022-02-07 00:34:13 +11:00
} ) ;
}
2023-02-24 19:01:25 -05:00
private void TryInjectIntoBloodstream ( InjectorComponent component , EntityUid injector , EntityUid target , BloodstreamComponent targetBloodstream , EntityUid user )
2022-02-07 00:34:13 +11:00
{
// 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 )
{
2023-02-24 19:01:25 -05:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-cannot-inject-message" , ( "target" , Identity . Entity ( target , EntityManager ) ) ) , injector , 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
2023-02-24 19:01:25 -05:00
_blood . TryAddToChemicals ( target , removedSolution , targetBloodstream ) ;
2022-02-07 00:34:13 +11:00
2023-02-24 19:01:25 -05:00
_reactiveSystem . DoEntityReaction ( target , removedSolution , ReactionMethod . Injection ) ;
2022-02-07 00:34:13 +11:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-inject-success-message" ,
2023-01-12 16:41:40 +13:00
( "amount" , removedSolution . Volume ) ,
2023-02-24 19:01:25 -05:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , injector , user ) ;
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
2023-02-24 19:01:25 -05:00
AfterInject ( component , injector ) ;
2022-02-07 00:34:13 +11:00
}
2023-02-24 19:01:25 -05:00
private void TryInject ( InjectorComponent component , EntityUid injector , EntityUid targetEntity , Solution targetSolution , EntityUid user , bool asRefill )
2022-02-07 00:34:13 +11:00
{
2023-02-24 19:01:25 -05:00
if ( ! _solutions . TryGetSolution ( injector , 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 ) ) ) ,
2023-02-24 19:01:25 -05:00
injector , user ) ;
2022-02-07 00:34:13 +11:00
return ;
}
// Move units from attackSolution to targetSolution
2023-05-06 10:23:05 +03:00
Solution removedSolution ;
if ( TryComp < StackComponent > ( targetEntity , out var stack ) )
removedSolution = _solutions . SplitStackSolution ( injector , solution , realTransferAmount , stack . Count ) ;
else
removedSolution = _solutions . SplitSolution ( injector , solution , realTransferAmount ) ;
2022-02-07 00:34:13 +11:00
2023-02-24 19:01:25 -05:00
_reactiveSystem . DoEntityReaction ( targetEntity , removedSolution , ReactionMethod . Injection ) ;
2022-02-07 00:34:13 +11:00
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 ) ,
2023-02-24 19:01:25 -05:00
( "target" , Identity . Entity ( targetEntity , EntityManager ) ) ) , injector , user ) ;
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
2023-02-24 19:01:25 -05:00
AfterInject ( component , injector ) ;
2022-02-07 00:34:13 +11:00
}
2023-02-24 19:01:25 -05:00
private void AfterInject ( InjectorComponent component , EntityUid injector )
2022-02-07 00:34:13 +11:00
{
// Automatically set syringe to draw after completely draining it.
2023-02-24 19:01:25 -05:00
if ( _solutions . TryGetSolution ( injector , 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 ;
}
}
2023-02-24 19:01:25 -05:00
private void AfterDraw ( InjectorComponent component , EntityUid injector )
2022-02-07 00:34:13 +11:00
{
// Automatically set syringe to inject after completely filling it.
2023-02-24 19:01:25 -05:00
if ( _solutions . TryGetSolution ( injector , InjectorComponent . SolutionName , out var solution )
2022-02-07 00:34:13 +11:00
& & solution . AvailableVolume = = 0 )
{
component . ToggleState = SharedInjectorComponent . InjectorToggleMode . Inject ;
}
}
2023-02-24 19:01:25 -05:00
private void TryDraw ( InjectorComponent component , EntityUid injector , EntityUid targetEntity , Solution targetSolution , EntityUid user , BloodstreamComponent ? stream = null )
2022-02-07 00:34:13 +11:00
{
2023-02-24 19:01:25 -05:00
if ( ! _solutions . TryGetSolution ( injector , InjectorComponent . SolutionName , out var solution )
2022-02-07 00:34:13 +11:00
| | 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 ) ) ) ,
2023-02-24 19:01:25 -05:00
injector , 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 )
{
2023-02-24 19:01:25 -05:00
DrawFromBlood ( user , injector , 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 ) ;
2023-02-24 19:01:25 -05:00
if ( ! _solutions . TryAddSolution ( injector , 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 ) ,
2023-02-24 19:01:25 -05:00
( "target" , Identity . Entity ( targetEntity , EntityManager ) ) ) , injector , user ) ;
2022-02-07 00:34:13 +11:00
Dirty ( component ) ;
2023-02-24 19:01:25 -05:00
AfterDraw ( component , injector ) ;
2022-02-07 00:34:13 +11:00
}
2023-02-24 19:01:25 -05:00
private void DrawFromBlood ( EntityUid user , EntityUid injector , 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 ) ;
2023-02-24 19:01:25 -05:00
_solutions . TryAddSolution ( injector , injectorSolution , bloodTemp ) ;
_solutions . TryAddSolution ( injector , injectorSolution , chemTemp ) ;
2022-07-04 20:37:21 -04:00
_popup . PopupEntity ( Loc . GetString ( "injector-component-draw-success-message" ,
2022-07-17 02:32:19 -04:00
( "amount" , transferAmount ) ,
2023-02-24 19:01:25 -05:00
( "target" , Identity . Entity ( target , EntityManager ) ) ) , injector , user ) ;
2022-07-04 20:37:21 -04:00
Dirty ( component ) ;
2023-02-24 19:01:25 -05:00
AfterDraw ( component , injector ) ;
2022-02-07 00:34:13 +11:00
}
2023-04-03 13:13:48 +12:00
2022-02-07 00:34:13 +11:00
}