2021-11-11 16:10:57 -07:00
using Content.Server.Body.Components ;
using Content.Server.Body.Systems ;
2021-10-29 13:40:15 +01:00
using Content.Server.Chemistry.Components.SolutionManager ;
using Content.Server.Chemistry.EntitySystems ;
2021-11-29 16:27:15 +13:00
using Content.Server.DoAfter ;
2021-12-05 04:18:30 +01:00
using Content.Server.Fluids.EntitySystems ;
2021-09-12 16:22:58 +10:00
using Content.Server.Nutrition.Components ;
2021-11-09 11:28:27 +01:00
using Content.Server.Popups ;
2021-11-29 16:27:15 +13:00
using Content.Shared.Administration.Logs ;
2021-11-02 11:40:55 +11: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-02 11:40:55 +11: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-02 11:40:55 +11:00
using Content.Shared.Examine ;
2021-11-03 16:48:03 -07:00
using Content.Shared.FixedPoint ;
2022-07-10 18:36:53 -07:00
using Content.Shared.IdentityManagement ;
2021-11-02 11:40:55 +11:00
using Content.Shared.Interaction ;
2022-03-13 01:33:23 +13:00
using Content.Shared.Interaction.Events ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Components ;
using Content.Shared.Mobs.Systems ;
2021-10-03 06:56:29 +02:00
using Content.Shared.Nutrition.Components ;
2021-09-12 16:22:58 +10:00
using Content.Shared.Throwing ;
2022-04-23 19:38:21 -04:00
using Content.Shared.Verbs ;
2021-09-06 15:49:44 +02:00
using JetBrains.Annotations ;
2021-09-12 16:22:58 +10:00
using Robust.Shared.Audio ;
using Robust.Shared.Player ;
using Robust.Shared.Random ;
2021-11-30 18:25:02 -07:00
using Robust.Shared.Utility ;
2021-09-06 15:49:44 +02:00
namespace Content.Server.Nutrition.EntitySystems
{
[UsedImplicitly]
2022-02-16 00:23:23 -07:00
public sealed class DrinkSystem : EntitySystem
2021-09-06 15:49:44 +02:00
{
2021-12-03 03:51:05 +13:00
[Dependency] private readonly FoodSystem _foodSystem = 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-09-12 16:22:58 +10:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2021-09-06 15:49:44 +02:00
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default ! ;
2021-11-09 11:28:27 +01:00
[Dependency] private readonly PopupSystem _popupSystem = default ! ;
2021-11-11 16:10:57 -07:00
[Dependency] private readonly BodySystem _bodySystem = default ! ;
[Dependency] private readonly StomachSystem _stomachSystem = 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 ! ;
2022-12-19 19:25:35 -08:00
[Dependency] private readonly MobStateSystem _mobStateSystem = default ! ;
2021-12-05 04:18:30 +01:00
[Dependency] private readonly SpillableSystem _spillableSystem = default ! ;
2022-02-17 15:40:03 +13:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
2023-01-10 06:59:43 -05:00
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2023-02-24 19:01:25 -05:00
[Dependency] private readonly ReactiveSystem _reaction = default ! ;
2021-09-06 15:49:44 +02:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < DrinkComponent , SolutionChangedEvent > ( OnSolutionChange ) ;
SubscribeLocalEvent < DrinkComponent , ComponentInit > ( OnDrinkInit ) ;
2021-09-12 16:22:58 +10:00
SubscribeLocalEvent < DrinkComponent , LandEvent > ( HandleLand ) ;
2021-11-02 11:40:55 +11:00
SubscribeLocalEvent < DrinkComponent , UseInHandEvent > ( OnUse ) ;
SubscribeLocalEvent < DrinkComponent , AfterInteractEvent > ( AfterInteract ) ;
2022-04-23 19:38:21 -04:00
SubscribeLocalEvent < DrinkComponent , GetVerbsEvent < AlternativeVerb > > ( AddDrinkVerb ) ;
2021-11-02 11:40:55 +11:00
SubscribeLocalEvent < DrinkComponent , ExaminedEvent > ( OnExamined ) ;
2022-04-15 23:17:48 +02:00
SubscribeLocalEvent < DrinkComponent , SolutionTransferAttemptEvent > ( OnTransferAttempt ) ;
2023-02-24 19:01:25 -05:00
SubscribeLocalEvent < DrinkComponent , DoAfterEvent < DrinkData > > ( OnDoAfter ) ;
2021-12-07 19:19:26 +13:00
}
2021-11-02 11:40:55 +11:00
public bool IsEmpty ( EntityUid uid , DrinkComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return true ;
return _solutionContainerSystem . DrainAvailable ( uid ) < = 0 ;
}
private void OnExamined ( EntityUid uid , DrinkComponent component , ExaminedEvent args )
{
if ( ! component . Opened | | ! args . IsInDetailsRange )
return ;
var color = IsEmpty ( uid , component ) ? "gray" : "yellow" ;
var openedText =
Loc . GetString ( IsEmpty ( uid , component ) ? "drink-component-on-examine-is-empty" : "drink-component-on-examine-is-opened" ) ;
2021-11-04 23:16:28 -05:00
args . Message . AddMarkup ( $"\n{Loc.GetString(" drink - component - on - examine - details - text ", (" colorName ", color), (" text ", openedText))}" ) ;
2022-06-21 22:02:24 -04:00
if ( ! IsEmpty ( uid , component ) )
{
if ( TryComp < ExaminableSolutionComponent > ( component . Owner , out var comp ) )
{
//provide exact measurement for beakers
args . Message . AddMarkup ( $" - {Loc.GetString(" drink - component - on - examine - exact - volume ", (" amount ", _solutionContainerSystem.DrainAvailable(uid)))}" ) ;
}
else
{
//general approximation
2023-01-19 03:56:45 +01:00
var remainingString = ( int ) _solutionContainerSystem . PercentFull ( uid ) switch
2022-06-21 22:02:24 -04:00
{
2023-01-19 03:56:45 +01:00
100 = > "drink-component-on-examine-is-full" ,
> 66 = > "drink-component-on-examine-is-mostly-full" ,
> 33 = > HalfEmptyOrHalfFull ( args ) ,
_ = > "drink-component-on-examine-is-mostly-empty" ,
} ;
2022-06-21 22:02:24 -04:00
args . Message . AddMarkup ( $" - {Loc.GetString(remainingString)}" ) ;
}
}
2021-11-02 11:40:55 +11:00
}
private void SetOpen ( EntityUid uid , bool opened = false , DrinkComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return ;
if ( opened = = component . Opened )
return ;
component . Opened = opened ;
if ( ! _solutionContainerSystem . TryGetSolution ( uid , component . SolutionName , out _ ) )
return ;
if ( EntityManager . TryGetComponent < AppearanceComponent > ( uid , out var appearance ) )
{
2023-01-10 06:59:43 -05:00
_appearanceSystem . SetData ( uid , DrinkCanStateVisual . Opened , opened , appearance ) ;
2021-11-02 11:40:55 +11:00
}
}
private void AfterInteract ( EntityUid uid , DrinkComponent component , AfterInteractEvent args )
{
2022-02-05 15:39:01 +13:00
if ( args . Handled | | args . Target = = null | | ! args . CanReach )
2021-11-02 11:40:55 +11:00
return ;
2023-02-24 19:01:25 -05:00
args . Handled = TryDrink ( args . User , args . Target . Value , component , uid ) ;
2021-11-02 11:40:55 +11:00
}
private void OnUse ( EntityUid uid , DrinkComponent component , UseInHandEvent args )
{
2023-02-24 19:01:25 -05:00
if ( args . Handled )
return ;
2021-11-29 16:27:15 +13:00
2021-11-02 11:40:55 +11:00
if ( ! component . Opened )
{
//Do the opening stuff like playing the sounds.
2023-01-10 06:59:43 -05:00
_audio . PlayPvs ( _audio . GetSound ( component . OpenSounds ) , args . User ) ;
2021-11-02 11:40:55 +11:00
SetOpen ( uid , true , component ) ;
return ;
}
2023-02-24 19:01:25 -05:00
args . Handled = TryDrink ( args . User , args . User , component , uid ) ;
2021-09-12 16:22:58 +10:00
}
2023-01-18 05:25:32 +11:00
private void HandleLand ( EntityUid uid , DrinkComponent component , ref LandEvent args )
2021-09-12 16:22:58 +10:00
{
if ( component . Pressurized & &
! component . Opened & &
_random . Prob ( 0.25f ) & &
_solutionContainerSystem . TryGetDrainableSolution ( uid , out var interactions ) )
{
component . Opened = true ;
2021-10-03 06:56:29 +02:00
UpdateAppearance ( component ) ;
2021-09-12 16:22:58 +10:00
2023-01-12 16:41:40 +13:00
var solution = _solutionContainerSystem . Drain ( uid , interactions , interactions . Volume ) ;
2021-12-07 17:48:49 +01:00
_spillableSystem . SpillAt ( uid , solution , "PuddleSmear" ) ;
2021-09-12 16:22:58 +10:00
2023-01-10 06:59:43 -05:00
_audio . PlayPvs ( _audio . GetSound ( component . BurstSound ) , uid , AudioParams . Default . WithVolume ( - 4 ) ) ;
2021-09-12 16:22:58 +10:00
}
2021-09-06 15:49:44 +02:00
}
private void OnDrinkInit ( EntityUid uid , DrinkComponent component , ComponentInit args )
{
2021-11-02 11:40:55 +11:00
SetOpen ( uid , component . DefaultToOpened , component ) ;
2021-09-06 15:49:44 +02:00
2021-11-02 11:40:55 +11:00
if ( EntityManager . TryGetComponent ( uid , out DrainableSolutionComponent ? existingDrainable ) )
2021-09-06 15:49:44 +02:00
{
// Beakers have Drink component but they should use the existing Drainable
component . SolutionName = existingDrainable . Solution ;
}
else
{
2021-11-02 11:40:55 +11:00
_solutionContainerSystem . EnsureSolution ( uid , component . SolutionName ) ;
2021-09-06 15:49:44 +02:00
}
2021-10-03 06:56:29 +02:00
UpdateAppearance ( component ) ;
2022-04-15 23:17:48 +02:00
2022-08-17 12:47:58 +12:00
if ( TryComp ( uid , out RefillableSolutionComponent ? refillComp ) )
refillComp . Solution = component . SolutionName ;
if ( TryComp ( uid , out DrainableSolutionComponent ? drainComp ) )
drainComp . Solution = component . SolutionName ;
2021-09-06 15:49:44 +02:00
}
private void OnSolutionChange ( EntityUid uid , DrinkComponent component , SolutionChangedEvent args )
{
2021-10-03 06:56:29 +02:00
UpdateAppearance ( component ) ;
}
public void UpdateAppearance ( DrinkComponent component )
{
2021-12-07 22:22:34 +11:00
if ( ! EntityManager . TryGetComponent ( ( component ) . Owner , out AppearanceComponent ? appearance ) | |
! EntityManager . HasComponent < SolutionContainerManagerComponent > ( ( component ) . Owner ) )
2021-10-03 06:56:29 +02:00
{
return ;
}
2021-12-07 22:22:34 +11:00
var drainAvailable = _solutionContainerSystem . DrainAvailable ( ( component ) . Owner ) ;
2023-01-10 06:59:43 -05:00
_appearanceSystem . SetData ( component . Owner , FoodVisuals . Visual , drainAvailable . Float ( ) , appearance ) ;
_appearanceSystem . SetData ( component . Owner , DrinkCanStateVisual . Opened , component . Opened , appearance ) ;
2021-09-06 15:49:44 +02:00
}
2021-11-02 11:40:55 +11:00
2022-04-15 23:17:48 +02:00
private void OnTransferAttempt ( EntityUid uid , DrinkComponent component , SolutionTransferAttemptEvent args )
{
if ( ! component . Opened )
{
args . Cancel ( Loc . GetString ( "drink-component-try-use-drink-not-open" ,
( "owner" , EntityManager . GetComponent < MetaDataComponent > ( component . Owner ) . EntityName ) ) ) ;
}
}
2023-02-24 19:01:25 -05:00
private bool TryDrink ( EntityUid user , EntityUid target , DrinkComponent drink , EntityUid item )
2021-11-02 11:40:55 +11:00
{
2023-03-05 00:26:03 -05:00
if ( ! EntityManager . HasComponent < BodyComponent > ( target ) | | drink . Drinking )
2022-02-07 00:37:38 +11:00
return false ;
2021-11-29 16:27:15 +13:00
if ( ! drink . Opened )
2021-11-02 11:40:55 +11:00
{
2021-11-29 16:27:15 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "drink-component-try-use-drink-not-open" ,
2023-02-24 19:01:25 -05:00
( "owner" , EntityManager . GetComponent < MetaDataComponent > ( item ) . EntityName ) ) , item , user ) ;
2021-11-29 16:27:15 +13:00
return true ;
2021-11-02 11:40:55 +11:00
}
2023-02-24 19:01:25 -05:00
if ( ! _solutionContainerSystem . TryGetDrainableSolution ( item , out var drinkSolution ) | |
2023-01-12 16:41:40 +13:00
drinkSolution . Volume < = 0 )
2021-11-02 11:40:55 +11:00
{
2021-11-29 16:27:15 +13:00
_popupSystem . PopupEntity ( Loc . GetString ( "drink-component-try-use-drink-is-empty" ,
2023-02-24 19:01:25 -05:00
( "entity" , EntityManager . GetComponent < MetaDataComponent > ( item ) . EntityName ) ) , item , user ) ;
2021-11-29 16:27:15 +13:00
return true ;
}
2021-11-02 11:40:55 +11:00
2022-02-07 00:37:38 +11:00
if ( _foodSystem . IsMouthBlocked ( target , user ) )
2021-12-03 03:51:05 +13:00
return true ;
2023-02-24 19:01:25 -05:00
if ( ! _interactionSystem . InRangeUnobstructed ( user , item , popup : true ) )
2021-11-29 16:27:15 +13:00
return true ;
2021-11-02 11:40:55 +11:00
2023-03-05 00:26:03 -05:00
drink . Drinking = true ;
2023-02-26 00:33:06 -05:00
drink . ForceDrink = user ! = target ;
2021-11-29 16:27:15 +13:00
2023-02-26 00:33:06 -05:00
if ( drink . ForceDrink )
2021-12-07 19:19:26 +13:00
{
2022-07-31 15:43:38 +12:00
var userName = Identity . Entity ( user , EntityManager ) ;
2021-11-29 16:27:15 +13:00
2022-02-07 00:37:38 +11:00
_popupSystem . PopupEntity ( Loc . GetString ( "drink-component-force-feed" , ( "user" , userName ) ) ,
2022-12-19 10:41:47 +13:00
user , target ) ;
2021-11-29 16:27:15 +13:00
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 drink {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}" ) ;
2021-11-29 16:27:15 +13:00
}
2022-12-02 19:19:44 -06:00
else
{
// log voluntary drinking
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . Ingestion , LogImpact . Low , $"{ToPrettyString(target):target} is drinking {ToPrettyString(item):drink} {SolutionContainerSystem.ToPrettyString(drinkSolution)}" ) ;
2022-12-02 19:19:44 -06:00
}
2022-03-17 12:46:18 +11:00
2022-09-13 19:36:19 -07:00
var flavors = _flavorProfileSystem . GetLocalizedFlavorsMessage ( user , drinkSolution ) ;
2023-02-24 19:01:25 -05:00
var drinkData = new DrinkData ( drinkSolution , flavors ) ;
2023-02-26 00:33:06 -05:00
var doAfterEventArgs = new DoAfterEventArgs ( user , drink . ForceDrink ? drink . ForceFeedDelay : drink . Delay ,
2023-02-24 19:01:25 -05:00
target : target , used : item )
2021-11-29 16:27:15 +13:00
{
2023-03-09 03:45:19 -05:00
RaiseOnTarget = user ! = target ,
RaiseOnUser = false ,
2023-03-05 23:52:10 -08:00
BreakOnUserMove = drink . ForceDrink ,
2021-11-29 16:27:15 +13:00
BreakOnDamage = true ,
BreakOnStun = true ,
2023-03-05 23:52:10 -08:00
BreakOnTargetMove = drink . ForceDrink ,
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 , drinkData ) ;
2021-11-29 16:27:15 +13:00
return true ;
}
/// <summary>
/// Raised directed at a victim when someone has force fed them a drink.
/// </summary>
2023-02-24 19:01:25 -05:00
private void OnDoAfter ( EntityUid uid , DrinkComponent component , DoAfterEvent < DrinkData > args )
2021-11-29 16:27:15 +13:00
{
2023-03-05 00:26:03 -05:00
if ( args . Cancelled )
2023-02-26 00:33:06 -05:00
{
component . ForceDrink = false ;
2023-03-05 00:26:03 -05:00
component . Drinking = false ;
2023-02-26 00:33:06 -05:00
return ;
}
2023-03-09 03:45:19 -05:00
if ( args . Handled | | component . Deleted )
2023-02-24 19:01:25 -05:00
return ;
if ( ! TryComp < BodyComponent > ( args . Args . Target , out var body ) )
2021-12-07 19:19:26 +13:00
return ;
2023-03-09 03:45:19 -05:00
component . Drinking = false ;
2023-02-24 19:01:25 -05:00
var transferAmount = FixedPoint2 . Min ( component . TransferAmount , args . AdditionalData . DrinkSolution . Volume ) ;
var drained = _solutionContainerSystem . Drain ( uid , args . AdditionalData . DrinkSolution , transferAmount ) ;
2022-02-07 00:37:38 +11:00
2023-02-24 19:01:25 -05:00
if ( ! _bodySystem . TryGetBodyOrganComponents < StomachComponent > ( args . Args . Target . Value , out var stomachs , body ) )
2021-11-29 16:27:15 +13:00
{
2023-02-26 00:33:06 -05:00
_popupSystem . PopupEntity ( component . ForceDrink ? Loc . GetString ( "drink-component-try-use-drink-cannot-drink-other" ) : Loc . GetString ( "drink-component-try-use-drink-had-enough" ) , args . Args . Target . Value , args . Args . User ) ;
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
if ( HasComp < RefillableSolutionComponent > ( args . Args . Target . Value ) )
2022-02-07 00:37:38 +11:00
{
2023-02-24 19:01:25 -05:00
_spillableSystem . SpillAt ( args . Args . User , drained , "PuddleSmear" ) ;
args . Handled = true ;
2022-02-07 00:37:38 +11:00
return ;
}
2023-02-24 19:01:25 -05:00
_solutionContainerSystem . Refill ( args . Args . Target . Value , args . AdditionalData . DrinkSolution , drained ) ;
args . Handled = true ;
2021-11-29 16:27:15 +13:00
return ;
}
2023-02-24 19:01:25 -05:00
var firstStomach = stomachs . FirstOrNull ( stomach = > _stomachSystem . CanTransferSolution ( stomach . Comp . Owner , drained ) ) ;
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
//All stomachs are full or can't handle whatever solution we have.
2021-11-29 16:27:15 +13:00
if ( firstStomach = = null )
{
2023-02-24 19:01:25 -05:00
_popupSystem . PopupEntity ( Loc . GetString ( "drink-component-try-use-drink-had-enough" ) , args . Args . Target . Value , args . Args . Target . Value ) ;
2022-07-07 11:21:26 -07:00
2023-02-26 00:33:06 -05:00
if ( component . ForceDrink )
2022-07-07 11:21:26 -07:00
{
2023-02-24 19:01:25 -05:00
_popupSystem . PopupEntity ( Loc . GetString ( "drink-component-try-use-drink-had-enough-other" ) , args . Args . Target . Value , args . Args . User ) ;
_spillableSystem . SpillAt ( args . Args . Target . Value , drained , "PuddleSmear" ) ;
2022-07-07 11:21:26 -07:00
}
else
2023-02-24 19:01:25 -05:00
_solutionContainerSystem . TryAddSolution ( uid , args . AdditionalData . DrinkSolution , drained ) ;
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
args . Handled = true ;
2021-11-29 16:27:15 +13:00
return ;
}
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 . ForceDrink )
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 ) ;
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
_popupSystem . PopupEntity ( Loc . GetString ( "drink-component-force-feed-success" , ( "user" , userName ) , ( "flavors" , flavors ) ) , args . Args . Target . Value , args . Args . Target . Value ) ;
2021-11-29 16:27:15 +13:00
2022-02-07 00:37:38 +11:00
_popupSystem . PopupEntity (
Loc . GetString ( "drink-component-force-feed-success-user" , ( "target" , targetName ) ) ,
2023-02-24 19:01:25 -05:00
args . Args . User , args . Args . User ) ;
2022-12-02 19:19:44 -06:00
// log successful forced drinking
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . ForceFeed , LogImpact . Medium , $"{ToPrettyString(uid):user} forced {ToPrettyString(args.Args.User):target} to drink {ToPrettyString(component.Owner):drink}" ) ;
2022-02-07 00:37:38 +11:00
}
else
{
_popupSystem . PopupEntity (
2023-02-24 19:01:25 -05:00
Loc . GetString ( "drink-component-try-use-drink-success-slurp-taste" , ( "flavors" , flavors ) ) , args . Args . User ,
args . Args . User ) ;
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 (
2023-02-24 19:01:25 -05:00
Loc . GetString ( "drink-component-try-use-drink-success-slurp" ) , args . Args . User , Filter . PvsExcept ( args . Args . User ) , true ) ;
2022-12-02 19:19:44 -06:00
// log successful voluntary drinking
2023-02-24 19:01:25 -05:00
_adminLogger . Add ( LogType . Ingestion , LogImpact . Low , $"{ToPrettyString(args.Args.User):target} drank {ToPrettyString(uid):drink}" ) ;
2022-02-07 00:37:38 +11:00
}
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
_audio . PlayPvs ( _audio . GetSound ( component . UseSound ) , args . Args . Target . Value , AudioParams . Default . WithVolume ( - 2f ) ) ;
2021-11-29 16:27:15 +13:00
2023-02-24 19:01:25 -05:00
_reaction . DoEntityReaction ( args . Args . Target . Value , args . AdditionalData . DrinkSolution , ReactionMethod . Ingestion ) ;
//TODO: Grab the stomach UIDs somehow without using Owner
2022-02-07 00:37:38 +11:00
_stomachSystem . TryTransferSolution ( firstStomach . Value . Comp . Owner , drained , firstStomach . Value . Comp ) ;
2021-11-29 16:27:15 +13:00
2023-02-26 00:33:06 -05:00
component . ForceDrink = false ;
2023-02-24 19:01:25 -05:00
args . Handled = true ;
2021-11-29 16:27:15 +13:00
}
2022-04-23 19:38:21 -04:00
private void AddDrinkVerb ( EntityUid uid , DrinkComponent component , GetVerbsEvent < AlternativeVerb > ev )
{
if ( uid = = ev . User | |
! ev . CanInteract | |
! 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 ) )
2022-04-23 19:38:21 -04:00
return ;
2022-12-19 19:25:35 -08:00
if ( EntityManager . TryGetComponent < MobStateComponent > ( uid , out var mobState ) & & _mobStateSystem . IsAlive ( uid , mobState ) )
2022-04-23 19:38:21 -04:00
return ;
AlternativeVerb verb = new ( )
{
Act = ( ) = >
{
2023-02-24 19:01:25 -05:00
TryDrink ( ev . User , ev . User , component , uid ) ;
2022-04-23 19:38:21 -04:00
} ,
2023-02-26 18:48:57 +11:00
Icon = new SpriteSpecifier . Texture ( new ResourcePath ( "/Textures/Interface/VerbIcons/drink.svg.192dpi.png" ) ) ,
2022-04-23 19:38:21 -04:00
Text = Loc . GetString ( "drink-system-verb-drink" ) ,
2023-02-20 04:29:15 -08:00
Priority = 2
2022-04-23 19:38:21 -04:00
} ;
ev . Verbs . Add ( verb ) ;
}
2022-06-21 22:02:24 -04:00
// some see half empty, and others see half full
private string HalfEmptyOrHalfFull ( ExaminedEvent args )
{
string remainingString = "drink-component-on-examine-is-half-full" ;
if ( TryComp < MetaDataComponent > ( args . Examiner , out var examiner ) & & examiner . EntityName . Length > 0
& & string . Compare ( examiner . EntityName . Substring ( 0 , 1 ) , "m" , StringComparison . InvariantCultureIgnoreCase ) > 0 )
remainingString = "drink-component-on-examine-is-half-empty" ;
return remainingString ;
}
2023-02-24 19:01:25 -05:00
private record struct DrinkData ( Solution DrinkSolution , string FlavorMessage )
{
public readonly Solution DrinkSolution = DrinkSolution ;
public readonly string FlavorMessage = FlavorMessage ;
}
2021-09-06 15:49:44 +02:00
}
}