2021-11-21 10:35:09 +03:00
using Content.Server.Body.Components ;
using Content.Server.Body.Systems ;
using Content.Server.Chemistry.EntitySystems ;
2021-11-29 16:27:15 +13:00
using Content.Server.DoAfter ;
2021-11-21 10:35:09 +03:00
using Content.Server.Hands.Components ;
using Content.Server.Nutrition.Components ;
using Content.Server.Popups ;
2021-11-29 16:27:15 +13:00
using Content.Shared.Administration.Logs ;
2021-11-21 10:35:09 +03:00
using Content.Shared.Body.Components ;
2023-02-24 19:01:25 -05:00
using Content.Shared.Chemistry ;
using Content.Shared.Chemistry.Components ;
2021-11-21 10:35:09 +03:00
using Content.Shared.Chemistry.Reagent ;
2021-11-29 16:27:15 +13:00
using Content.Shared.Database ;
2023-02-24 19:01:25 -05:00
using Content.Shared.DoAfter ;
2021-11-21 10:35:09 +03:00
using Content.Shared.FixedPoint ;
2022-07-29 14:13:12 +12:00
using Content.Shared.Hands.EntitySystems ;
using Content.Shared.IdentityManagement ;
2021-11-21 10:35:09 +03:00
using Content.Shared.Interaction ;
2022-07-29 14:13:12 +12:00
using Content.Shared.Interaction.Events ;
using Content.Shared.Inventory ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Components ;
using Content.Shared.Mobs.Systems ;
2021-11-21 10:35:09 +03:00
using Content.Shared.Verbs ;
using Robust.Shared.Audio ;
using Robust.Shared.Player ;
2021-11-30 18:25:02 -07:00
using Robust.Shared.Utility ;
2021-11-21 10:35:09 +03:00
namespace Content.Server.Nutrition.EntitySystems
{
/// <summary>
/// Handles feeding attempts both on yourself and on the target.
/// </summary>
2022-02-07 00:37:38 +11:00
public sealed class FoodSystem : EntitySystem
2021-11-21 10:35:09 +03:00
{
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default ! ;
Flavor profiles (#10991)
* flavor profiles
TODO: every single flavor! yeah!!!
* adds basic localization, and flavors/lastFlavor values for when you get the flavor profile message
* multiple and single flavor messages
* start on flavor localization, multiple flavors in localized flavors
* flavor prototypes
* a few more flavors, descriptions on what each section of the flavor file should be doing
* localization for flavor profiles in drink/food system
* adds an event that allows a flavor profile list to be transformed base on the user entity
* raises it on the food entity too
* changes a field in flavor, adds some more flavors, starts adding flavor prototypes
* adds basic flavors to several entities, and consumable drinks, renames flavor field to 'flavors'
* changes call ordering in flavorprofile, adds flavor to ignored components server-side
flavor is really just a popup message, and those are all processed server-side
* fixes where food tried to get the flavor of the user instead of the food
* single flavors will now get the localized string
* getting the flavor message now ensures that flavors are deduplicated
* makes flavor processing more strictly unique bu making everything hashsets
* yeah, that could just not have distinctby now
* adds flavorprofile directly to food base instead for generic food taste
* FlavorProfileModificationEvent now passes a hashset of strings and not flavorprototypes
* flavorprofilesystem now broadcasts the flavor profile modification event
* adds more flavors to the flavor profile loc file
* skips a flavor, if the flavor string is null/empty
* adds some more flavors, adds generic medicine flavor to medicinal chemicals
* more food flavors, adds flavors to swallowing
* adds some cocktails to the set of flavor profiles
* regenerates flavor prototypes
* adds flavor type to all flavors, adds whitespace between variants
* adds more flavors, adds flavors to several chemicals and food items
this is the part that took the longest
* changes backup flavor message
* spelling mistake
* more flavors, and flavors on food
* readds all the type fields, whoops
* fixes localization strings for forcefeeding food/drink
* fixes multiple flavor profile
* adds flavor limit for flavors
* makes that fetch the cvardef instead
2022-09-08 16:14:49 -07:00
[Dependency] private readonly FlavorProfileSystem _flavorProfileSystem = default ! ;
2021-11-21 10:35:09 +03:00
[Dependency] private readonly BodySystem _bodySystem = default ! ;
[Dependency] private readonly StomachSystem _stomachSystem = default ! ;
[Dependency] private readonly PopupSystem _popupSystem = default ! ;
2022-12-19 19:25:35 -08:00
[Dependency] private readonly MobStateSystem _mobStateSystem = default ! ;
2021-11-21 10:35:09 +03:00
[Dependency] private readonly UtensilSystem _utensilSystem = default ! ;
2021-11-29 16:27:15 +13:00
[Dependency] private readonly DoAfterSystem _doAfterSystem = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2021-12-30 22:56:10 +01:00
[Dependency] private readonly InventorySystem _inventorySystem = default ! ;
2022-02-17 15:40:03 +13:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
2022-03-17 20:13:31 +13:00
[Dependency] private readonly SharedHandsSystem _handsSystem = default ! ;
2023-02-24 19:01:25 -05:00
[Dependency] private readonly ReactiveSystem _reaction = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2021-11-21 10:35:09 +03:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < FoodComponent , UseInHandEvent > ( OnUseFoodInHand ) ;
SubscribeLocalEvent < FoodComponent , AfterInteractEvent > ( OnFeedFood ) ;
2022-03-28 13:53:20 -07:00
SubscribeLocalEvent < FoodComponent , GetVerbsEvent < AlternativeVerb > > ( AddEatVerb ) ;
2023-02-24 19:01:25 -05:00
SubscribeLocalEvent < FoodComponent , DoAfterEvent < FoodData > > ( OnDoAfter ) ;
2021-12-05 07:56:27 +13:00
SubscribeLocalEvent < InventoryComponent , IngestionAttemptEvent > ( OnInventoryIngestAttempt ) ;
2021-11-21 10:35:09 +03:00
}
/// <summary>
/// Eat item
/// </summary>
private void OnUseFoodInHand ( EntityUid uid , FoodComponent foodComponent , UseInHandEvent ev )
{
if ( ev . Handled )
return ;
2023-02-24 19:01:25 -05:00
ev . Handled = TryFeed ( ev . User , ev . User , uid , foodComponent ) ;
2021-11-21 10:35:09 +03:00
}
/// <summary>
/// Feed someone else
/// </summary>
2021-11-29 16:27:15 +13:00
private void OnFeedFood ( EntityUid uid , FoodComponent foodComponent , AfterInteractEvent args )
2021-11-21 10:35:09 +03:00
{
2022-02-05 15:39:01 +13:00
if ( args . Handled | | args . Target = = null | | ! args . CanReach )
2021-11-29 16:27:15 +13:00
return ;
2023-02-24 19:01:25 -05:00
args . Handled = TryFeed ( args . User , args . Target . Value , uid , foodComponent ) ;
2021-11-21 10:35:09 +03:00
}
2023-02-24 19:01:25 -05:00
public bool TryFeed ( EntityUid user , EntityUid target , EntityUid food , FoodComponent foodComp )
2021-11-21 10:35:09 +03:00
{
2023-02-24 19:01:25 -05:00
//Suppresses self-eating
if ( food = = user | | EntityManager . TryGetComponent < MobStateComponent > ( food , out var mobState ) & & _mobStateSystem . IsAlive ( food , mobState ) ) // Suppresses eating alive mobs
2022-02-07 00:37:38 +11:00
return false ;
2023-03-05 00:26:03 -05:00
// Target can't be fed or they're already eating
if ( ! EntityManager . HasComponent < BodyComponent > ( target ) | | foodComp . Eating )
2021-11-30 13:45:33 +03:00
return false ;
2023-02-24 19:01:25 -05:00
if ( ! _solutionContainerSystem . TryGetSolution ( food , foodComp . SolutionName , out var foodSolution ) )
2021-11-21 10:35:09 +03:00
return false ;
2023-02-24 19:01:25 -05:00
var flavors = _flavorProfileSystem . GetLocalizedFlavorsMessage ( food , user , foodSolution ) ;
2022-09-13 19:36:19 -07:00
2023-02-24 19:01:25 -05:00
if ( foodComp . UsesRemaining < = 0 )
2021-11-21 10:35:09 +03:00
{
2023-02-24 19:01:25 -05:00
_popupSystem . PopupEntity ( Loc . GetString ( "food-system-try-use-food-is-empty" , ( "entity" , food ) ) , user , user ) ;
DeleteAndSpawnTrash ( foodComp , food , user ) ;
2022-02-07 00:37:38 +11:00
return false ;
2021-11-21 10:35:09 +03:00
}
2022-02-07 00:37:38 +11:00
if ( IsMouthBlocked ( target , user ) )
2021-11-21 10:35:09 +03:00
return false ;
2023-02-24 19:01:25 -05:00
if ( ! TryGetRequiredUtensils ( user , foodComp , out var utensils ) )
2022-02-07 00:37:38 +11:00
return false ;
2023-02-24 19:01:25 -05:00
if ( ! _interactionSystem . InRangeUnobstructed ( user , food , popup : true ) )
2021-12-03 03:51:05 +13:00
return true ;
2022-02-07 00:37:38 +11:00
2023-03-05 00:26:03 -05:00
foodComp . Eating = true ;
2023-02-26 00:33:06 -05:00
foodComp . ForceFeed = user ! = target ;
2022-02-07 00:37:38 +11:00
2023-02-26 00:33:06 -05:00
if ( foodComp . ForceFeed )
2022-02-07 00:37:38 +11:00
{
2022-07-31 15:43:38 +12:00
var userName = Identity . Entity ( user , EntityManager ) ;
2022-02-07 00:37:38 +11:00
_popupSystem . PopupEntity ( Loc . GetString ( "food-system-force-feed" , ( "user" , userName ) ) ,
2022-12-19 10:41:47 +13:00
user , target ) ;
2022-02-07 00:37:38 +11:00
// logging
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . ForceFeed , LogImpact . Medium , $"{ToPrettyString(user):user} is forcing {ToPrettyString(target):target} to eat {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}" ) ;
2021-12-03 03:51:05 +13:00
}
2022-12-02 19:19:44 -06:00
else
{
// log voluntary eating
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . Ingestion , LogImpact . Low , $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}" ) ;
2022-12-02 19:19:44 -06:00
}
2021-12-03 03:51:05 +13:00
2023-02-24 19:01:25 -05:00
var foodData = new FoodData ( foodSolution , flavors , utensils ) ;
2023-02-26 00:33:06 -05:00
var doAfterEventArgs = new DoAfterEventArgs ( user , foodComp . ForceFeed ? foodComp . ForceFeedDelay : foodComp . Delay , target : target , used : food )
2022-02-07 00:37:38 +11:00
{
2023-02-26 00:33:06 -05:00
RaiseOnTarget = foodComp . ForceFeed ,
2023-03-09 03:45:19 -05:00
RaiseOnUser = false , //causes a crash if mice eat if true
2023-02-26 00:33:06 -05:00
BreakOnUserMove = foodComp . ForceFeed ,
2022-02-07 00:37:38 +11:00
BreakOnDamage = true ,
BreakOnStun = true ,
2023-02-26 00:33:06 -05:00
BreakOnTargetMove = foodComp . ForceFeed ,
2022-02-07 00:37:38 +11:00
MovementThreshold = 0.01f ,
2022-07-24 07:33:52 -04:00
DistanceThreshold = 1.0f ,
2023-02-24 19:01:25 -05:00
NeedHand = true
} ;
_doAfterSystem . DoAfter ( doAfterEventArgs , foodData ) ;
2022-02-07 00:37:38 +11:00
return true ;
2021-11-21 10:35:09 +03:00
2022-02-07 00:37:38 +11:00
}
2021-11-21 10:35:09 +03:00
2023-02-24 19:01:25 -05:00
private void OnDoAfter ( EntityUid uid , FoodComponent component , DoAfterEvent < FoodData > args )
2022-02-07 00:37:38 +11:00
{
2023-02-26 00:33:06 -05:00
//Prevents the target from being force fed food but allows the user to chow down
2023-03-05 00:26:03 -05:00
if ( args . Cancelled )
2023-02-26 00:33:06 -05:00
{
2023-03-05 00:26:03 -05:00
component . Eating = false ;
2023-02-26 00:33:06 -05:00
component . ForceFeed = false ;
return ;
}
2023-03-09 03:45:19 -05:00
if ( args . Handled | | component . Deleted | | args . Args . Target = = null )
2022-02-07 00:37:38 +11:00
return ;
2023-02-24 19:01:25 -05:00
if ( ! TryComp < BodyComponent > ( args . Args . Target . Value , out var body ) )
2022-02-07 00:37:38 +11:00
return ;
2021-11-21 10:35:09 +03:00
2023-02-24 19:01:25 -05:00
if ( ! _bodySystem . TryGetBodyOrganComponents < StomachComponent > ( args . Args . Target . Value , out var stomachs , body ) )
return ;
Flavor profiles (#10991)
* flavor profiles
TODO: every single flavor! yeah!!!
* adds basic localization, and flavors/lastFlavor values for when you get the flavor profile message
* multiple and single flavor messages
* start on flavor localization, multiple flavors in localized flavors
* flavor prototypes
* a few more flavors, descriptions on what each section of the flavor file should be doing
* localization for flavor profiles in drink/food system
* adds an event that allows a flavor profile list to be transformed base on the user entity
* raises it on the food entity too
* changes a field in flavor, adds some more flavors, starts adding flavor prototypes
* adds basic flavors to several entities, and consumable drinks, renames flavor field to 'flavors'
* changes call ordering in flavorprofile, adds flavor to ignored components server-side
flavor is really just a popup message, and those are all processed server-side
* fixes where food tried to get the flavor of the user instead of the food
* single flavors will now get the localized string
* getting the flavor message now ensures that flavors are deduplicated
* makes flavor processing more strictly unique bu making everything hashsets
* yeah, that could just not have distinctby now
* adds flavorprofile directly to food base instead for generic food taste
* FlavorProfileModificationEvent now passes a hashset of strings and not flavorprototypes
* flavorprofilesystem now broadcasts the flavor profile modification event
* adds more flavors to the flavor profile loc file
* skips a flavor, if the flavor string is null/empty
* adds some more flavors, adds generic medicine flavor to medicinal chemicals
* more food flavors, adds flavors to swallowing
* adds some cocktails to the set of flavor profiles
* regenerates flavor prototypes
* adds flavor type to all flavors, adds whitespace between variants
* adds more flavors, adds flavors to several chemicals and food items
this is the part that took the longest
* changes backup flavor message
* spelling mistake
* more flavors, and flavors on food
* readds all the type fields, whoops
* fixes localization strings for forcefeeding food/drink
* fixes multiple flavor profile
* adds flavor limit for flavors
* makes that fetch the cvardef instead
2022-09-08 16:14:49 -07:00
2023-03-05 00:26:03 -05:00
component . Eating = false ;
2023-02-24 19:01:25 -05:00
var transferAmount = component . TransferAmount ! = null ? FixedPoint2 . Min ( ( FixedPoint2 ) component . TransferAmount , args . AdditionalData . FoodSolution . Volume ) : args . AdditionalData . FoodSolution . Volume ;
Flavor profiles (#10991)
* flavor profiles
TODO: every single flavor! yeah!!!
* adds basic localization, and flavors/lastFlavor values for when you get the flavor profile message
* multiple and single flavor messages
* start on flavor localization, multiple flavors in localized flavors
* flavor prototypes
* a few more flavors, descriptions on what each section of the flavor file should be doing
* localization for flavor profiles in drink/food system
* adds an event that allows a flavor profile list to be transformed base on the user entity
* raises it on the food entity too
* changes a field in flavor, adds some more flavors, starts adding flavor prototypes
* adds basic flavors to several entities, and consumable drinks, renames flavor field to 'flavors'
* changes call ordering in flavorprofile, adds flavor to ignored components server-side
flavor is really just a popup message, and those are all processed server-side
* fixes where food tried to get the flavor of the user instead of the food
* single flavors will now get the localized string
* getting the flavor message now ensures that flavors are deduplicated
* makes flavor processing more strictly unique bu making everything hashsets
* yeah, that could just not have distinctby now
* adds flavorprofile directly to food base instead for generic food taste
* FlavorProfileModificationEvent now passes a hashset of strings and not flavorprototypes
* flavorprofilesystem now broadcasts the flavor profile modification event
* adds more flavors to the flavor profile loc file
* skips a flavor, if the flavor string is null/empty
* adds some more flavors, adds generic medicine flavor to medicinal chemicals
* more food flavors, adds flavors to swallowing
* adds some cocktails to the set of flavor profiles
* regenerates flavor prototypes
* adds flavor type to all flavors, adds whitespace between variants
* adds more flavors, adds flavors to several chemicals and food items
this is the part that took the longest
* changes backup flavor message
* spelling mistake
* more flavors, and flavors on food
* readds all the type fields, whoops
* fixes localization strings for forcefeeding food/drink
* fixes multiple flavor profile
* adds flavor limit for flavors
* makes that fetch the cvardef instead
2022-09-08 16:14:49 -07:00
2023-02-24 19:01:25 -05:00
var split = _solutionContainerSystem . SplitSolution ( uid , args . AdditionalData . FoodSolution , transferAmount ) ;
//TODO: Get the stomach UID somehow without nabbing owner
var firstStomach = stomachs . FirstOrNull ( stomach = > _stomachSystem . CanTransferSolution ( stomach . Comp . Owner , split ) ) ;
2021-11-21 10:35:09 +03:00
2022-02-07 00:37:38 +11:00
// No stomach so just popup a message that they can't eat.
2021-11-21 10:35:09 +03:00
if ( firstStomach = = null )
{
2023-02-24 19:01:25 -05:00
_solutionContainerSystem . TryAddSolution ( uid , args . AdditionalData . FoodSolution , split ) ;
2023-02-26 00:33:06 -05:00
_popupSystem . PopupEntity ( component . ForceFeed ? Loc . GetString ( "food-system-you-cannot-eat-any-more-other" ) : Loc . GetString ( "food-system-you-cannot-eat-any-more" ) , args . Args . Target . Value , args . Args . User ) ;
2023-02-24 19:01:25 -05:00
args . Handled = true ;
2022-02-07 00:37:38 +11:00
return ;
2021-11-21 10:35:09 +03:00
}
2023-02-24 19:01:25 -05:00
_reaction . DoEntityReaction ( args . Args . Target . Value , args . AdditionalData . FoodSolution , ReactionMethod . Ingestion ) ;
2022-02-07 00:37:38 +11:00
_stomachSystem . TryTransferSolution ( firstStomach . Value . Comp . Owner , split , firstStomach . Value . Comp ) ;
2021-11-21 10:35:09 +03:00
2023-02-24 19:01:25 -05:00
var flavors = args . AdditionalData . FlavorMessage ;
Flavor profiles (#10991)
* flavor profiles
TODO: every single flavor! yeah!!!
* adds basic localization, and flavors/lastFlavor values for when you get the flavor profile message
* multiple and single flavor messages
* start on flavor localization, multiple flavors in localized flavors
* flavor prototypes
* a few more flavors, descriptions on what each section of the flavor file should be doing
* localization for flavor profiles in drink/food system
* adds an event that allows a flavor profile list to be transformed base on the user entity
* raises it on the food entity too
* changes a field in flavor, adds some more flavors, starts adding flavor prototypes
* adds basic flavors to several entities, and consumable drinks, renames flavor field to 'flavors'
* changes call ordering in flavorprofile, adds flavor to ignored components server-side
flavor is really just a popup message, and those are all processed server-side
* fixes where food tried to get the flavor of the user instead of the food
* single flavors will now get the localized string
* getting the flavor message now ensures that flavors are deduplicated
* makes flavor processing more strictly unique bu making everything hashsets
* yeah, that could just not have distinctby now
* adds flavorprofile directly to food base instead for generic food taste
* FlavorProfileModificationEvent now passes a hashset of strings and not flavorprototypes
* flavorprofilesystem now broadcasts the flavor profile modification event
* adds more flavors to the flavor profile loc file
* skips a flavor, if the flavor string is null/empty
* adds some more flavors, adds generic medicine flavor to medicinal chemicals
* more food flavors, adds flavors to swallowing
* adds some cocktails to the set of flavor profiles
* regenerates flavor prototypes
* adds flavor type to all flavors, adds whitespace between variants
* adds more flavors, adds flavors to several chemicals and food items
this is the part that took the longest
* changes backup flavor message
* spelling mistake
* more flavors, and flavors on food
* readds all the type fields, whoops
* fixes localization strings for forcefeeding food/drink
* fixes multiple flavor profile
* adds flavor limit for flavors
* makes that fetch the cvardef instead
2022-09-08 16:14:49 -07:00
2023-02-26 00:33:06 -05:00
if ( component . ForceFeed )
2022-02-07 00:37:38 +11:00
{
2023-02-24 19:01:25 -05:00
var targetName = Identity . Entity ( args . Args . Target . Value , EntityManager ) ;
var userName = Identity . Entity ( args . Args . User , EntityManager ) ;
Flavor profiles (#10991)
* flavor profiles
TODO: every single flavor! yeah!!!
* adds basic localization, and flavors/lastFlavor values for when you get the flavor profile message
* multiple and single flavor messages
* start on flavor localization, multiple flavors in localized flavors
* flavor prototypes
* a few more flavors, descriptions on what each section of the flavor file should be doing
* localization for flavor profiles in drink/food system
* adds an event that allows a flavor profile list to be transformed base on the user entity
* raises it on the food entity too
* changes a field in flavor, adds some more flavors, starts adding flavor prototypes
* adds basic flavors to several entities, and consumable drinks, renames flavor field to 'flavors'
* changes call ordering in flavorprofile, adds flavor to ignored components server-side
flavor is really just a popup message, and those are all processed server-side
* fixes where food tried to get the flavor of the user instead of the food
* single flavors will now get the localized string
* getting the flavor message now ensures that flavors are deduplicated
* makes flavor processing more strictly unique bu making everything hashsets
* yeah, that could just not have distinctby now
* adds flavorprofile directly to food base instead for generic food taste
* FlavorProfileModificationEvent now passes a hashset of strings and not flavorprototypes
* flavorprofilesystem now broadcasts the flavor profile modification event
* adds more flavors to the flavor profile loc file
* skips a flavor, if the flavor string is null/empty
* adds some more flavors, adds generic medicine flavor to medicinal chemicals
* more food flavors, adds flavors to swallowing
* adds some cocktails to the set of flavor profiles
* regenerates flavor prototypes
* adds flavor type to all flavors, adds whitespace between variants
* adds more flavors, adds flavors to several chemicals and food items
this is the part that took the longest
* changes backup flavor message
* spelling mistake
* more flavors, and flavors on food
* readds all the type fields, whoops
* fixes localization strings for forcefeeding food/drink
* fixes multiple flavor profile
* adds flavor limit for flavors
* makes that fetch the cvardef instead
2022-09-08 16:14:49 -07:00
_popupSystem . PopupEntity ( Loc . GetString ( "food-system-force-feed-success" , ( "user" , userName ) , ( "flavors" , flavors ) ) ,
2022-12-19 10:41:47 +13:00
uid , uid ) ;
2022-02-07 00:37:38 +11:00
2023-02-24 19:01:25 -05:00
_popupSystem . PopupEntity ( Loc . GetString ( "food-system-force-feed-success-user" , ( "target" , targetName ) ) , args . Args . User , args . Args . User ) ;
2022-12-02 19:19:44 -06:00
// log successful force feed
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . ForceFeed , LogImpact . Medium , $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to eat {ToPrettyString(uid):food}" ) ;
2023-03-09 03:45:19 -05:00
component . ForceFeed = false ;
2022-02-07 00:37:38 +11:00
}
else
2021-11-21 10:35:09 +03:00
{
2023-03-03 12:19:55 +05:00
_popupSystem . PopupEntity ( Loc . GetString ( component . EatMessage , ( "food" , uid ) , ( "flavors" , flavors ) ) , args . Args . User , args . Args . User ) ;
2022-12-02 19:19:44 -06:00
// log successful voluntary eating
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . Ingestion , LogImpact . Low , $"{ToPrettyString(args.Args.User):target} ate {ToPrettyString(uid):food}" ) ;
2021-11-21 10:35:09 +03:00
}
2023-02-24 19:01:25 -05:00
_audio . Play ( component . UseSound , Filter . Pvs ( args . Args . Target . Value ) , args . Args . Target . Value , true , AudioParams . Default . WithVolume ( - 1f ) ) ;
2022-02-07 00:37:38 +11:00
// Try to break all used utensils
2023-02-24 19:01:25 -05:00
//TODO: Replace utensil owner with actual UID
foreach ( var utensil in args . AdditionalData . Utensils )
2021-11-21 10:35:09 +03:00
{
2023-02-24 19:01:25 -05:00
_utensilSystem . TryBreak ( utensil . Owner , args . Args . User ) ;
2021-11-21 10:35:09 +03:00
}
2023-02-24 19:01:25 -05:00
if ( component . UsesRemaining > 0 )
{
args . Handled = true ;
2022-02-07 00:37:38 +11:00
return ;
2023-02-24 19:01:25 -05:00
}
if ( string . IsNullOrEmpty ( component . TrashPrototype ) )
EntityManager . QueueDeleteEntity ( uid ) ;
2021-11-21 10:35:09 +03:00
2022-02-07 00:37:38 +11:00
else
2023-02-24 19:01:25 -05:00
DeleteAndSpawnTrash ( component , uid , args . Args . User ) ;
args . Handled = true ;
2021-11-21 10:35:09 +03:00
}
2023-02-24 19:01:25 -05:00
private void DeleteAndSpawnTrash ( FoodComponent component , EntityUid food , EntityUid ? user = null )
2021-11-21 10:35:09 +03:00
{
//We're empty. Become trash.
2023-02-24 19:01:25 -05:00
var position = Transform ( food ) . MapPosition ;
2021-12-05 18:09:01 +01:00
var finisher = EntityManager . SpawnEntity ( component . TrashPrototype , position ) ;
2021-11-21 10:35:09 +03:00
// If the user is holding the item
2023-02-24 19:01:25 -05:00
if ( user ! = null & & _handsSystem . IsHolding ( user . Value , food , out var hand ) )
2021-11-21 10:35:09 +03:00
{
2023-02-24 19:01:25 -05:00
EntityManager . DeleteEntity ( food ) ;
2021-11-21 10:35:09 +03:00
// Put the trash in the user's hand
2022-03-17 20:13:31 +13:00
_handsSystem . TryPickup ( user . Value , finisher , hand ) ;
2021-11-29 16:27:15 +13:00
return ;
2021-11-21 10:35:09 +03:00
}
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
EntityManager . QueueDeleteEntity ( food ) ;
2021-11-21 10:35:09 +03:00
}
2022-03-28 13:53:20 -07:00
private void AddEatVerb ( EntityUid uid , FoodComponent component , GetVerbsEvent < AlternativeVerb > ev )
2021-11-21 10:35:09 +03:00
{
2021-12-05 18:09:01 +01:00
if ( uid = = ev . User | |
2021-11-30 13:45:33 +03:00
! ev . CanInteract | |
2021-11-25 05:16:56 +03:00
! ev . CanAccess | |
2022-10-23 00:46:28 +02:00
! EntityManager . TryGetComponent ( ev . User , out BodyComponent ? body ) | |
! _bodySystem . TryGetBodyOrganComponents < StomachComponent > ( ev . User , out var stomachs , body ) )
2021-11-30 13:45:33 +03:00
return ;
2022-12-19 19:25:35 -08:00
if ( EntityManager . TryGetComponent < MobStateComponent > ( uid , out var mobState ) & & _mobStateSystem . IsAlive ( uid , mobState ) )
2021-11-21 10:35:09 +03:00
return ;
2022-03-28 13:53:20 -07:00
AlternativeVerb verb = new ( )
2021-11-21 10:35:09 +03:00
{
2021-12-05 18:09:01 +01:00
Act = ( ) = >
{
2023-02-24 19:01:25 -05:00
TryFeed ( ev . User , ev . User , uid , component ) ;
2021-12-05 18:09:01 +01:00
} ,
2023-02-26 18:48:57 +11:00
Icon = new SpriteSpecifier . Texture ( new ResourcePath ( "/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png" ) ) ,
2021-12-05 18:09:01 +01:00
Text = Loc . GetString ( "food-system-verb-eat" ) ,
Priority = - 1
2021-11-21 10:35:09 +03:00
} ;
2021-11-30 18:25:02 -07:00
2021-11-21 10:35:09 +03:00
ev . Verbs . Add ( verb ) ;
}
2021-11-29 16:27:15 +13:00
/// <summary>
/// Force feeds someone remotely. Does not require utensils (well, not the normal type anyways).
/// </summary>
public void ProjectileForceFeed ( EntityUid uid , EntityUid target , EntityUid ? user , FoodComponent ? food = null , BodyComponent ? body = null )
{
2022-02-07 00:37:38 +11:00
// TODO: Combine with regular feeding because holy code duplication batman.
2022-01-24 13:06:51 -07:00
if ( ! Resolve ( uid , ref food , false ) | | ! Resolve ( target , ref body , false ) )
2021-11-29 16:27:15 +13:00
return ;
2021-12-05 07:56:27 +13:00
if ( IsMouthBlocked ( target ) )
2021-12-03 03:51:05 +13:00
return ;
2021-11-29 16:27:15 +13:00
if ( ! _solutionContainerSystem . TryGetSolution ( uid , food . SolutionName , out var foodSolution ) )
return ;
2022-10-23 00:46:28 +02:00
if ( ! _bodySystem . TryGetBodyOrganComponents < StomachComponent > ( target , out var stomachs , body ) )
2021-11-29 16:27:15 +13:00
return ;
if ( food . UsesRemaining < = 0 )
2023-02-24 19:01:25 -05:00
DeleteAndSpawnTrash ( food , uid ) ;
2021-11-29 16:27:15 +13:00
2021-11-30 18:25:02 -07:00
var firstStomach = stomachs . FirstOrNull (
2021-12-03 16:30:34 +01:00
stomach = > _stomachSystem . CanTransferSolution ( ( ( IComponent ) stomach . Comp ) . Owner , foodSolution ) ) ;
2021-11-30 18:25:02 -07:00
2021-11-29 16:27:15 +13:00
if ( firstStomach = = null )
return ;
// logging
2021-12-05 18:09:01 +01:00
if ( user = = null )
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . ForceFeed , $"{ToPrettyString(uid):food} {SolutionContainerSystem.ToPrettyString(foodSolution):solution} was thrown into the mouth of {ToPrettyString(target):target}" ) ;
2021-11-29 16:27:15 +13:00
else
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . ForceFeed , $"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):food} {SolutionContainerSystem.ToPrettyString(foodSolution):solution} into the mouth of {ToPrettyString(target):target}" ) ;
2021-12-03 03:51:05 +13:00
2021-12-05 18:09:01 +01:00
var filter = user = = null ? Filter . Entities ( target ) : Filter . Entities ( target , user . Value ) ;
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( food . EatMessage , ( "food" , food . Owner ) ) , target , filter , true ) ;
2021-11-29 16:27:15 +13:00
foodSolution . DoEntityReaction ( uid , ReactionMethod . Ingestion ) ;
2021-12-03 16:30:34 +01:00
_stomachSystem . TryTransferSolution ( ( ( IComponent ) firstStomach . Value . Comp ) . Owner , foodSolution , firstStomach . Value . Comp ) ;
2022-06-12 19:45:47 -04:00
SoundSystem . Play ( food . UseSound . GetSound ( ) , Filter . Pvs ( target ) , target , AudioParams . Default . WithVolume ( - 1f ) ) ;
2021-11-29 16:27:15 +13:00
if ( string . IsNullOrEmpty ( food . TrashPrototype ) )
2022-02-07 00:37:38 +11:00
EntityManager . QueueDeleteEntity ( food . Owner ) ;
2021-11-29 16:27:15 +13:00
else
2023-02-24 19:01:25 -05:00
DeleteAndSpawnTrash ( food , uid ) ;
2021-11-29 16:27:15 +13:00
}
2021-12-05 18:09:01 +01:00
private bool TryGetRequiredUtensils ( EntityUid user , FoodComponent component ,
2021-11-29 16:27:15 +13:00
out List < UtensilComponent > utensils , HandsComponent ? hands = null )
{
2022-02-07 00:37:38 +11:00
utensils = new List < UtensilComponent > ( ) ;
2021-11-29 16:27:15 +13:00
if ( component . Utensil ! = UtensilType . None )
return true ;
2021-12-05 18:09:01 +01:00
if ( ! Resolve ( user , ref hands , false ) )
2021-11-29 16:27:15 +13:00
return false ;
var usedTypes = UtensilType . None ;
2022-03-17 20:13:31 +13:00
foreach ( var item in _handsSystem . EnumerateHeld ( user , hands ) )
2021-11-29 16:27:15 +13:00
{
// Is utensil?
2022-03-17 20:13:31 +13:00
if ( ! EntityManager . TryGetComponent ( item , out UtensilComponent ? utensil ) )
2021-11-29 16:27:15 +13:00
continue ;
if ( ( utensil . Types & component . Utensil ) ! = 0 & & // Acceptable type?
( usedTypes & utensil . Types ) ! = utensil . Types ) // Type is not used already? (removes usage of identical utensils)
{
// Add to used list
usedTypes | = utensil . Types ;
utensils . Add ( utensil ) ;
}
}
// If "required" field is set, try to block eating without proper utensils used
if ( component . UtensilRequired & & ( usedTypes & component . Utensil ) ! = component . Utensil )
{
2022-12-19 10:41:47 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "food-you-need-to-hold-utensil" , ( "utensil" , component . Utensil ^ usedTypes ) ) , user , user ) ;
2021-11-29 16:27:15 +13:00
return false ;
}
return true ;
}
2021-12-03 03:51:05 +13:00
/// <summary>
2021-12-05 07:56:27 +13:00
/// Block ingestion attempts based on the equipped mask or head-wear
2021-12-03 03:51:05 +13:00
/// </summary>
2021-12-05 07:56:27 +13:00
private void OnInventoryIngestAttempt ( EntityUid uid , InventoryComponent component , IngestionAttemptEvent args )
2021-12-03 03:51:05 +13:00
{
2021-12-05 07:56:27 +13:00
if ( args . Cancelled )
return ;
2021-12-03 03:51:05 +13:00
2022-06-04 19:17:48 +12:00
IngestionBlockerComponent ? blocker ;
2021-12-03 03:51:05 +13:00
2021-12-30 22:56:10 +01:00
if ( _inventorySystem . TryGetSlotEntity ( uid , "mask" , out var maskUid ) & &
EntityManager . TryGetComponent ( maskUid , out blocker ) & &
2021-12-05 07:56:27 +13:00
blocker . Enabled )
2021-12-03 03:51:05 +13:00
{
2021-12-30 22:56:10 +01:00
args . Blocker = maskUid ;
2021-12-05 07:56:27 +13:00
args . Cancel ( ) ;
return ;
2021-12-03 03:51:05 +13:00
}
2021-12-30 22:56:10 +01:00
if ( _inventorySystem . TryGetSlotEntity ( uid , "head" , out var headUid ) & &
EntityManager . TryGetComponent ( headUid , out blocker ) & &
2021-12-05 07:56:27 +13:00
blocker . Enabled )
2021-12-03 03:51:05 +13:00
{
2021-12-30 22:56:10 +01:00
args . Blocker = headUid ;
2021-12-05 07:56:27 +13:00
args . Cancel ( ) ;
2021-12-03 03:51:05 +13:00
}
}
2021-11-29 16:27:15 +13:00
2021-12-05 07:56:27 +13:00
/// <summary>
/// Check whether the target's mouth is blocked by equipment (masks or head-wear).
/// </summary>
/// <param name="uid">The target whose equipment is checked</param>
/// <param name="popupUid">Optional entity that will receive an informative pop-up identifying the blocking
/// piece of equipment.</param>
/// <returns></returns>
public bool IsMouthBlocked ( EntityUid uid , EntityUid ? popupUid = null )
2021-11-29 16:27:15 +13:00
{
2021-12-05 07:56:27 +13:00
var attempt = new IngestionAttemptEvent ( ) ;
RaiseLocalEvent ( uid , attempt , false ) ;
if ( attempt . Cancelled & & attempt . Blocker ! = null & & popupUid ! = null )
{
var name = EntityManager . GetComponent < MetaDataComponent > ( attempt . Blocker . Value ) . EntityName ;
_popupSystem . PopupEntity ( Loc . GetString ( "food-system-remove-mask" , ( "entity" , name ) ) ,
2022-12-19 10:41:47 +13:00
uid , popupUid . Value ) ;
2021-12-05 07:56:27 +13:00
}
2021-11-29 16:27:15 +13:00
2021-12-05 07:56:27 +13:00
return attempt . Cancelled ;
2021-11-29 16:27:15 +13:00
}
2023-02-24 19:01:25 -05:00
private record struct FoodData ( Solution FoodSolution , string FlavorMessage , List < UtensilComponent > Utensils )
{
public readonly Solution FoodSolution = FoodSolution ;
public readonly string FlavorMessage = FlavorMessage ;
public readonly List < UtensilComponent > Utensils = Utensils ;
}
2021-11-21 10:35:09 +03:00
}
}