2024-08-14 16:04:00 +03:00
using System.Numerics ;
2024-08-10 22:31:32 +03:00
using System.Text ;
using Content.Shared.Chemistry.EntitySystems ;
using Content.Shared.Interaction ;
2024-08-14 16:04:00 +03:00
using Content.Shared.Mobs.Systems ;
2024-08-10 22:31:32 +03:00
using Content.Shared.Nutrition.Components ;
2024-09-08 09:22:27 +03:00
using Content.Shared.Nutrition.Prototypes ;
2024-08-10 22:31:32 +03:00
using Content.Shared.Popups ;
2025-04-25 20:54:14 -05:00
using Content.Shared.Storage.Components ;
2024-08-14 15:47:03 -04:00
using Content.Shared.Tag ;
2024-09-08 09:22:27 +03:00
using Robust.Shared.Prototypes ;
2024-08-14 16:04:00 +03:00
using Robust.Shared.Random ;
2024-08-10 22:31:32 +03:00
2025-06-09 07:36:04 -07:00
namespace Content.Shared.Nutrition.EntitySystems ;
2024-08-10 22:31:32 +03:00
public sealed class FoodSequenceSystem : SharedFoodSequenceSystem
{
2024-08-14 16:04:00 +03:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2024-09-08 09:22:27 +03:00
[Dependency] private readonly IPrototypeManager _proto = default ! ;
2025-09-06 02:18:06 -07:00
[Dependency] private readonly MetaDataSystem _metaData = default ! ;
[Dependency] private readonly MobStateSystem _mobState = default ! ;
[Dependency] private readonly IngestionSystem _ingestion = default ! ;
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default ! ;
2025-06-09 07:36:04 -07:00
[Dependency] private readonly SharedTransformSystem _transform = default ! ;
2025-09-06 02:18:06 -07:00
[Dependency] private readonly TagSystem _tag = default ! ;
2024-08-10 22:31:32 +03:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < FoodSequenceStartPointComponent , InteractUsingEvent > ( OnInteractUsing ) ;
2024-09-08 09:22:27 +03:00
SubscribeLocalEvent < FoodMetamorphableByAddingComponent , FoodSequenceIngredientAddedEvent > ( OnIngredientAdded ) ;
2024-08-10 22:31:32 +03:00
}
private void OnInteractUsing ( Entity < FoodSequenceStartPointComponent > ent , ref InteractUsingEvent args )
{
if ( TryComp < FoodSequenceElementComponent > ( args . Used , out var sequenceElement ) )
2025-02-14 09:31:38 +13:00
args . Handled = TryAddFoodElement ( ent , ( args . Used , sequenceElement ) , args . User ) ;
2024-08-10 22:31:32 +03:00
}
2024-09-08 09:22:27 +03:00
private void OnIngredientAdded ( Entity < FoodMetamorphableByAddingComponent > ent , ref FoodSequenceIngredientAddedEvent args )
{
if ( ! TryComp < FoodSequenceStartPointComponent > ( args . Start , out var start ) )
return ;
2025-09-09 18:17:56 +02:00
if ( ! _proto . Resolve ( args . Proto , out var elementProto ) )
2024-09-08 09:22:27 +03:00
return ;
if ( ! ent . Comp . OnlyFinal | | elementProto . Final | | start . FoodLayers . Count = = start . MaxLayers )
{
TryMetamorph ( ( ent , start ) ) ;
}
}
private bool TryMetamorph ( Entity < FoodSequenceStartPointComponent > start )
2024-08-10 22:31:32 +03:00
{
2024-09-08 09:22:27 +03:00
List < MetamorphRecipePrototype > availableRecipes = new ( ) ;
foreach ( var recipe in _proto . EnumeratePrototypes < MetamorphRecipePrototype > ( ) )
2024-08-10 22:31:32 +03:00
{
2024-09-08 09:22:27 +03:00
if ( recipe . Key ! = start . Comp . Key )
continue ;
bool allowed = true ;
foreach ( var rule in recipe . Rules )
2024-08-10 22:31:32 +03:00
{
2024-09-08 09:22:27 +03:00
if ( ! rule . Check ( _proto , EntityManager , start , start . Comp . FoodLayers ) )
{
allowed = false ;
break ;
}
2024-08-10 22:31:32 +03:00
}
2024-09-08 09:22:27 +03:00
if ( allowed )
availableRecipes . Add ( recipe ) ;
2024-08-10 22:31:32 +03:00
}
2024-09-08 09:22:27 +03:00
if ( availableRecipes . Count < = 0 )
return true ;
Metamorf ( start , _random . Pick ( availableRecipes ) ) ; //In general, if there's more than one recipe, the yml-guys screwed up. Maybe some kind of unit test is needed.
2025-09-06 02:18:06 -07:00
PredictedQueueDel ( start . Owner ) ;
2024-09-08 09:22:27 +03:00
return true ;
}
private void Metamorf ( Entity < FoodSequenceStartPointComponent > start , MetamorphRecipePrototype recipe )
{
2025-09-06 02:18:06 -07:00
var result = PredictedSpawnNextToOrDrop ( recipe . Result , start ) ;
2024-09-08 09:22:27 +03:00
//Try putting in container
_transform . DropNextTo ( result , ( start , Transform ( start ) ) ) ;
if ( ! _solutionContainer . TryGetSolution ( result , start . Comp . Solution , out var resultSoln , out var resultSolution ) )
return ;
if ( ! _solutionContainer . TryGetSolution ( start . Owner , start . Comp . Solution , out var startSoln , out var startSolution ) )
return ;
_solutionContainer . RemoveAllSolution ( resultSoln . Value ) ; //Remove all YML reagents
resultSoln . Value . Comp . Solution . MaxVolume = startSoln . Value . Comp . Solution . MaxVolume ;
_solutionContainer . TryAddSolution ( resultSoln . Value , startSolution ) ;
MergeFlavorProfiles ( start , result ) ;
2025-09-06 02:18:06 -07:00
MergeTrash ( start . Owner , result ) ;
2024-09-08 09:22:27 +03:00
MergeTags ( start , result ) ;
}
2025-09-06 02:18:06 -07:00
private bool TryAddFoodElement ( Entity < FoodSequenceStartPointComponent > start , Entity < FoodSequenceElementComponent , EdibleComponent ? > element , EntityUid ? user = null )
2024-09-08 09:22:27 +03:00
{
// we can't add a live mouse to a burger.
2025-09-06 02:18:06 -07:00
if ( ! Resolve ( element , ref element . Comp2 , false ) )
2024-09-08 09:22:27 +03:00
return false ;
2025-09-06 02:18:06 -07:00
if ( element . Comp2 . RequireDead & & _mobState . IsAlive ( element ) )
2024-08-10 22:31:32 +03:00
return false ;
2024-09-08 09:22:27 +03:00
//looking for a suitable FoodSequence prototype
2025-09-06 02:18:06 -07:00
if ( ! element . Comp1 . Entries . TryGetValue ( start . Comp . Key , out var elementProto ) )
2024-09-10 16:08:04 +03:00
return false ;
2025-09-06 02:18:06 -07:00
2025-09-09 18:17:56 +02:00
if ( ! _proto . Resolve ( elementProto , out var elementIndexed ) )
2024-09-08 09:22:27 +03:00
return false ;
2024-08-14 16:04:00 +03:00
2024-08-10 22:31:32 +03:00
//if we run out of space, we can still put in one last, final finishing element.
2024-09-08 09:22:27 +03:00
if ( start . Comp . FoodLayers . Count > = start . Comp . MaxLayers & & ! elementIndexed . Final | | start . Comp . Finished )
2024-08-10 22:31:32 +03:00
{
if ( user is not null )
2025-06-09 07:36:04 -07:00
_popup . PopupClient ( Loc . GetString ( "food-sequence-no-space" ) , start , user . Value ) ;
2024-08-10 22:31:32 +03:00
return false ;
}
2025-04-25 20:54:14 -05:00
// Prevents plushies with items hidden in them from being added to prevent deletion of items
// If more of these types of checks need to be added, this should be changed to an event or something.
if ( TryComp < SecretStashComponent > ( element , out var stashComponent ) & & stashComponent . ItemContainer . Count ! = 0 )
{
return false ;
}
2024-09-08 09:22:27 +03:00
//Generate new visual layer
var flip = start . Comp . AllowHorizontalFlip & & _random . Prob ( 0.5f ) ;
var layer = new FoodSequenceVisualLayer ( elementIndexed ,
_random . Pick ( elementIndexed . Sprites ) ,
2024-09-13 16:02:54 +02:00
new Vector2 ( flip ? - elementIndexed . Scale . X : elementIndexed . Scale . X , elementIndexed . Scale . Y ) ,
2024-09-08 09:22:27 +03:00
new Vector2 (
_random . NextFloat ( start . Comp . MinLayerOffset . X , start . Comp . MaxLayerOffset . X ) ,
_random . NextFloat ( start . Comp . MinLayerOffset . Y , start . Comp . MaxLayerOffset . Y ) )
) ;
start . Comp . FoodLayers . Add ( layer ) ;
2024-08-14 16:04:00 +03:00
Dirty ( start ) ;
2024-09-08 09:22:27 +03:00
if ( elementIndexed . Final )
2024-08-10 22:31:32 +03:00
start . Comp . Finished = true ;
UpdateFoodName ( start ) ;
2025-09-06 02:18:06 -07:00
MergeFoodSolutions ( start . Owner , element . Owner ) ;
2024-08-10 22:31:32 +03:00
MergeFlavorProfiles ( start , element ) ;
2025-09-06 02:18:06 -07:00
MergeTrash ( start . Owner , element . Owner ) ;
2024-08-14 15:47:03 -04:00
MergeTags ( start , element ) ;
2024-09-08 09:22:27 +03:00
var ev = new FoodSequenceIngredientAddedEvent ( start , element , elementProto , user ) ;
RaiseLocalEvent ( start , ev ) ;
2025-09-06 02:18:06 -07:00
PredictedQueueDel ( element . Owner ) ;
2024-08-10 22:31:32 +03:00
return true ;
}
private void UpdateFoodName ( Entity < FoodSequenceStartPointComponent > start )
{
if ( start . Comp . NameGeneration is null )
return ;
var content = new StringBuilder ( ) ;
var separator = "" ;
if ( start . Comp . ContentSeparator is not null )
separator = Loc . GetString ( start . Comp . ContentSeparator ) ;
2024-09-08 09:22:27 +03:00
HashSet < ProtoId < FoodSequenceElementPrototype > > existedContentNames = new ( ) ;
2024-08-10 22:31:32 +03:00
foreach ( var layer in start . Comp . FoodLayers )
{
2024-09-08 09:22:27 +03:00
if ( ! existedContentNames . Contains ( layer . Proto ) )
existedContentNames . Add ( layer . Proto ) ;
2024-08-14 16:04:00 +03:00
}
var nameCounter = 1 ;
2024-09-08 09:22:27 +03:00
foreach ( var proto in existedContentNames )
2024-08-14 16:04:00 +03:00
{
2025-09-09 18:17:56 +02:00
if ( ! _proto . Resolve ( proto , out var protoIndexed ) )
2024-09-08 09:22:27 +03:00
continue ;
if ( protoIndexed . Name is null )
continue ;
content . Append ( Loc . GetString ( protoIndexed . Name . Value ) ) ;
2024-08-10 22:31:32 +03:00
2024-08-14 16:04:00 +03:00
if ( nameCounter < existedContentNames . Count )
content . Append ( separator ) ;
nameCounter + + ;
2024-08-10 22:31:32 +03:00
}
var newName = Loc . GetString ( start . Comp . NameGeneration . Value ,
( "prefix" , start . Comp . NamePrefix is not null ? Loc . GetString ( start . Comp . NamePrefix ) : "" ) ,
( "content" , content ) ,
( "suffix" , start . Comp . NameSuffix is not null ? Loc . GetString ( start . Comp . NameSuffix ) : "" ) ) ;
_metaData . SetEntityName ( start , newName ) ;
}
2025-09-06 02:18:06 -07:00
private void MergeFoodSolutions ( Entity < EdibleComponent ? > start , Entity < EdibleComponent ? > element )
2024-08-10 22:31:32 +03:00
{
2025-09-06 02:18:06 -07:00
if ( ! Resolve ( start , ref start . Comp , false ) )
2024-09-08 09:22:27 +03:00
return ;
2025-09-06 02:18:06 -07:00
if ( ! Resolve ( element , ref element . Comp , false ) )
2024-09-08 09:22:27 +03:00
return ;
2025-09-06 02:18:06 -07:00
if ( ! _solutionContainer . TryGetSolution ( start . Owner , start . Comp . Solution , out var startSolutionEntity , out var startSolution ) )
2024-08-10 22:31:32 +03:00
return ;
2025-09-06 02:18:06 -07:00
if ( ! _solutionContainer . TryGetSolution ( element . Owner , element . Comp . Solution , out _ , out var elementSolution ) )
2024-08-10 22:31:32 +03:00
return ;
startSolution . MaxVolume + = elementSolution . MaxVolume ;
_solutionContainer . TryAddSolution ( startSolutionEntity . Value , elementSolution ) ;
}
2024-09-08 09:22:27 +03:00
private void MergeFlavorProfiles ( EntityUid start , EntityUid element )
2024-08-10 22:31:32 +03:00
{
if ( ! TryComp < FlavorProfileComponent > ( start , out var startProfile ) )
return ;
if ( ! TryComp < FlavorProfileComponent > ( element , out var elementProfile ) )
return ;
foreach ( var flavor in elementProfile . Flavors )
{
if ( startProfile ! = null & & ! startProfile . Flavors . Contains ( flavor ) )
startProfile . Flavors . Add ( flavor ) ;
}
}
2025-09-06 02:18:06 -07:00
private void MergeTrash ( Entity < EdibleComponent ? > start , Entity < EdibleComponent ? > element )
2024-08-10 22:31:32 +03:00
{
2025-09-06 02:18:06 -07:00
if ( ! Resolve ( start , ref start . Comp , false ) )
2024-08-10 22:31:32 +03:00
return ;
2025-09-06 02:18:06 -07:00
if ( ! Resolve ( element , ref element . Comp , false ) )
2024-08-10 22:31:32 +03:00
return ;
2025-09-06 02:18:06 -07:00
_ingestion . AddTrash ( ( start , start . Comp ) , element . Comp . Trash ) ;
2024-08-10 22:31:32 +03:00
}
2024-08-14 15:47:03 -04:00
2024-09-08 09:22:27 +03:00
private void MergeTags ( EntityUid start , EntityUid element )
2024-08-14 15:47:03 -04:00
{
if ( ! TryComp < TagComponent > ( element , out var elementTags ) )
return ;
2024-09-08 09:22:27 +03:00
EnsureComp < TagComponent > ( start ) ;
2024-08-14 15:47:03 -04:00
2024-09-08 09:22:27 +03:00
_tag . TryAddTags ( start , elementTags . Tags ) ;
2024-08-14 15:47:03 -04:00
}
2024-08-10 22:31:32 +03:00
}