Metamorphosis - FoodSequence 3 (#31012)

* setup some data

* cheeseburger recipe

* Update FoodSequenceSystem.cs

* finalize cheseburger recipe

* remove fun

* return old taco sprites

* full foodsequence data refactor

* return tacos

* well done

* add cutlets to burger

* chickenburger recipe

* +2 burger recipes

* more fun

* Update brain.png

* some slice produce added

* documentation

* watermelon

* skewer work

* flipping

* tomato

* skewer watermelon

* Update skewer.yml

* oopsie, ok, im go to sleep

* fix checks

* Update produce.yml

* screwed

* cheeeeeeeese

* all cooked meat added

* produce added

* aaaaand suppermatter

* key to Tag

* More

* proto string remove

* raw snail

* fix

* Update FoodMetamorphableByAddingComponent.cs

* fixes

* fix3

* fififififx
This commit is contained in:
Ed
2024-09-08 09:22:27 +03:00
committed by GitHub
parent 680b9c9cfb
commit 230ea9be46
49 changed files with 2282 additions and 1255 deletions

View File

@@ -0,0 +1,17 @@
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Shared.Nutrition.Components;
/// <summary>
/// Attempts to metamorphose a modular food when a new ingredient is added.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedFoodSequenceSystem))]
public sealed partial class FoodMetamorphableByAddingComponent : Component
{
/// <summary>
/// if true, the metamorphosis will only be attempted when the sequence ends, not when each element is added.
/// </summary>
[DataField]
public bool OnlyFinal = true;
}

View File

@@ -1,55 +1,25 @@
using System.Numerics;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Content.Shared.Nutrition.Prototypes;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared.Nutrition.Components;
/// <summary>
/// Tndicates that this entity can be inserted into FoodSequence, which will transfer all reagents to the target.
/// Indicates that this entity can be inserted into FoodSequence, which will transfer all reagents to the target.
/// </summary>
[RegisterComponent, Access(typeof(SharedFoodSequenceSystem))]
public sealed partial class FoodSequenceElementComponent : Component
{
/// <summary>
/// the same object can be used in different sequences, and it will have a different sprite in different sequences.
/// The same object can be used in different sequences, and it will have a different data in then.
/// </summary>
[DataField(required: true)]
public Dictionary<string, FoodSequenceElementEntry> Entries = new();
public Dictionary<ProtoId<TagPrototype>, ProtoId<FoodSequenceElementPrototype>> Entries = new();
/// <summary>
/// which solution we will add to the main dish
/// Which solution we will add to the main dish
/// </summary>
[DataField]
public string Solution = "food";
/// <summary>
/// state used to generate the appearance of the added layer
/// </summary>
[DataField]
public SpriteSpecifier? Sprite;
}
[DataRecord, Serializable, NetSerializable]
public sealed class FoodSequenceElementEntry
{
/// <summary>
/// A localized name piece to build into the item name generator.
/// </summary>
public LocId? Name { get; set; } = null;
/// <summary>
/// overriding default sprite
/// </summary>
public SpriteSpecifier? Sprite { get; set; } = null;
/// <summary>
/// If the layer is the final one, it can be added over the limit, but no other layers can be added after it.
/// </summary>
public bool Final { get; set; } = false;
/// <summary>
/// the shear of a particular layer. Allows a little "randomization" of each layer.
/// </summary>
public Vector2 LocalOffset { get; set; } = Vector2.Zero;
}

View File

@@ -1,6 +1,11 @@
using System.Numerics;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Nutrition.Prototypes;
using Content.Shared.Tag;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Nutrition.Components;
@@ -14,7 +19,7 @@ public sealed partial class FoodSequenceStartPointComponent : Component
/// A key that determines which types of food elements can be attached to a food.
/// </summary>
[DataField(required: true)]
public string Key = string.Empty;
public ProtoId<TagPrototype> Key = string.Empty;
/// <summary>
/// The maximum number of layers of food that can be placed on this item.
@@ -22,62 +27,20 @@ public sealed partial class FoodSequenceStartPointComponent : Component
[DataField]
public int MaxLayers = 10;
/// <summary>
/// Start shift from the center of the sprite where the first layer of food will be placed.
/// </summary>
[DataField]
public Vector2 StartPosition = Vector2.Zero;
/// <summary>
/// Shift from the start position applied to each subsequent layer.
/// </summary>
[DataField]
public Vector2 Offset = Vector2.Zero;
/// <summary>
/// Can we put more layers?
/// </summary>
[DataField]
public bool Finished;
/// <summary>
/// list of sprite states to be displayed on this object.
/// </summary>
[DataField, AutoNetworkedField]
public List<FoodSequenceElementEntry> FoodLayers = new();
public HashSet<string> RevealedLayers = new();
/// <summary>
/// target layer, where new layers will be added. This allows you to control the order of generative layers and static layers.
/// </summary>
[DataField]
public string TargetLayerMap = "foodSequenceLayers";
/// <summary>
/// If true, the generative layers will be placed in reverse order.
/// </summary>
[DataField]
public bool InverseLayers;
/// <summary>
/// each layer will get a random offset in the specified range
/// </summary>
[DataField]
public Vector2 MaxLayerOffset = Vector2.Zero;
/// <summary>
/// each layer will get a random offset in the specified range
/// </summary>
[DataField]
public Vector2 MinLayerOffset = Vector2.Zero;
/// <summary>
/// solution where reagents will be added from newly added ingredients
/// </summary>
[DataField]
public string Solution = "food";
#region name generation
/// <summary>
/// LocId with a name generation pattern.
/// </summary>
@@ -101,4 +64,96 @@ public sealed partial class FoodSequenceStartPointComponent : Component
/// </summary>
[DataField]
public LocId? NameSuffix;
#endregion
#region visual
/// <summary>
/// list of sprite states to be displayed on this object.
/// </summary>
[DataField, AutoNetworkedField]
public List<FoodSequenceVisualLayer> FoodLayers = new();
/// <summary>
/// If true, the generative layers will be placed in reverse order.
/// </summary>
[DataField]
public bool InverseLayers;
/// <summary>
/// target layer, where new layers will be added. This allows you to control the order of generative layers and static layers.
/// </summary>
[DataField]
public string TargetLayerMap = "foodSequenceLayers";
/// <summary>
/// Start shift from the center of the sprite where the first layer of food will be placed.
/// </summary>
[DataField]
public Vector2 StartPosition = Vector2.Zero;
/// <summary>
/// Shift from the start position applied to each subsequent layer.
/// </summary>
[DataField]
public Vector2 Offset = Vector2.Zero;
/// <summary>
/// each layer will get a random offset in the specified range
/// </summary>
[DataField]
public Vector2 MaxLayerOffset = Vector2.Zero;
/// <summary>
/// each layer will get a random offset in the specified range
/// </summary>
[DataField]
public Vector2 MinLayerOffset = Vector2.Zero;
[DataField]
public bool AllowHorizontalFlip = true;
public HashSet<string> RevealedLayers = new();
#endregion
}
/// <summary>
/// class that synchronizes with the client
/// Stores all the necessary information for rendering the FoodSequence element
/// </summary>
[DataRecord, Serializable, NetSerializable]
public record struct FoodSequenceVisualLayer
{
/// <summary>
/// reference to the original prototype of the layer. Used to edit visual layers.
/// </summary>
public ProtoId<FoodSequenceElementPrototype> Proto;
/// <summary>
/// Sprite rendered in sequence
/// </summary>
public SpriteSpecifier? Sprite { get; set; } = SpriteSpecifier.Invalid;
/// <summary>
/// Relative size of the sprite displayed in FoodSequence
/// </summary>
public Vector2 Scale { get; set; } = Vector2.One;
/// <summary>
/// The offset of a particular layer. Allows a little position randomization of each layer.
/// </summary>
public Vector2 LocalOffset { get; set; } = Vector2.Zero;
public FoodSequenceVisualLayer(ProtoId<FoodSequenceElementPrototype> proto,
SpriteSpecifier? sprite,
Vector2 scale,
Vector2 offset)
{
Proto = proto;
Sprite = sprite;
Scale = scale;
LocalOffset = offset;
}
}

View File

@@ -1,5 +1,8 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.DoAfter;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Nutrition;
@@ -67,3 +70,8 @@ public record struct SliceFoodEvent();
public sealed partial class SliceFoodDoAfterEvent : SimpleDoAfterEvent
{
}
/// <summary>
/// Raised on FoodSequence start element entity when new ingredient is added to FoodSequence
/// </summary>
public record struct FoodSequenceIngredientAddedEvent(EntityUid Start, EntityUid Element, ProtoId<FoodSequenceElementPrototype> Proto, EntityUid? User = null);

View File

@@ -0,0 +1,218 @@
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Destructible.Thresholds;
using Content.Shared.Nutrition.Components;
using Content.Shared.Tag;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Nutrition.FoodMetamorphRules;
/// <summary>
/// abstract rules that are used to verify the correct foodSequence for recipe
/// </summary>
[ImplicitDataDefinitionForInheritors]
[Serializable, NetSerializable]
public abstract partial class FoodMetamorphRule
{
public abstract bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients);
}
/// <summary>
/// The requirement that the sequence be within the specified size limit
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class SequenceLength : FoodMetamorphRule
{
[DataField(required: true)]
public MinMax Range;
public override bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients)
{
return ingredients.Count <= Range.Max && ingredients.Count >= Range.Min;
}
}
/// <summary>
/// A requirement that the last element of the sequence have one or all of the required tags
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LastElementHasTags : FoodMetamorphRule
{
[DataField(required: true)]
public List<ProtoId<TagPrototype>> Tags = new ();
[DataField]
public bool NeedAll = true;
public override bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients)
{
var lastIngredient = ingredients[ingredients.Count - 1];
if (!protoMan.TryIndex(lastIngredient.Proto, out var protoIndexed))
return false;
foreach (var tag in Tags)
{
var containsTag = protoIndexed.Tags.Contains(tag);
if (NeedAll && !containsTag)
{
return false;
}
if (!NeedAll && containsTag)
{
return true;
}
}
return NeedAll;
}
}
/// <summary>
/// A requirement that the specified sequence element have one or all of the required tags
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class ElementHasTags : FoodMetamorphRule
{
[DataField(required: true)]
public int ElementNumber = 0;
[DataField(required: true)]
public List<ProtoId<TagPrototype>> Tags = new ();
[DataField]
public bool NeedAll = true;
public override bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients)
{
if (ingredients.Count < ElementNumber + 1)
return false;
if (!protoMan.TryIndex(ingredients[ElementNumber].Proto, out var protoIndexed))
return false;
foreach (var tag in Tags)
{
var containsTag = protoIndexed.Tags.Contains(tag);
if (NeedAll && !containsTag)
{
return false;
}
if (!NeedAll && containsTag)
{
return true;
}
}
return NeedAll;
}
}
/// <summary>
/// requirement that the food contains certain reagents (e.g. sauces)
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class FoodHasReagent : FoodMetamorphRule
{
[DataField(required: true)]
public ProtoId<ReagentPrototype> Reagent = new();
[DataField(required: true)]
public MinMax Count;
[DataField]
public string Solution = "food";
public override bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients)
{
if (!entMan.TryGetComponent<SolutionContainerManagerComponent>(food, out var solMan))
return false;
var solutionMan = entMan.System<SharedSolutionContainerSystem>();
if (!solutionMan.TryGetSolution(food, Solution, out var foodSoln, out var foodSolution))
return false;
foreach (var (id, quantity) in foodSoln.Value.Comp.Solution.Contents)
{
if (id.Prototype != Reagent.Id)
continue;
if (quantity < Count.Min || quantity > Count.Max)
break;
return true;
}
return false;
}
}
/// <summary>
/// A requirement that there be X ingredients in the sequence that have one or all of the specified tags.
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class IngredientsWithTags : FoodMetamorphRule
{
[DataField(required: true)]
public List<ProtoId<TagPrototype>> Tags = new ();
[DataField(required: true)]
public MinMax Count = new();
[DataField]
public bool NeedAll = true;
public override bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients)
{
var count = 0;
foreach (var ingredient in ingredients)
{
if (!protoMan.TryIndex(ingredient.Proto, out var protoIndexed))
continue;
var allowed = false;
if (NeedAll)
{
allowed = true;
foreach (var tag in Tags)
{
if (!protoIndexed.Tags.Contains(tag))
{
allowed = false;
break;
}
}
}
else
{
allowed = false;
foreach (var tag in Tags)
{
if (protoIndexed.Tags.Contains(tag))
{
allowed = true;
break;
}
}
}
if (allowed)
count++;
}
return count >= Count.Min && count <= Count.Max;
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Nutrition.Prototypes;
/// <summary>
/// Unique data storage block for different FoodSequence layers
/// </summary>
[Prototype("foodSequenceElement")]
public sealed partial class FoodSequenceElementPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;
/// <summary>
/// sprite options. A random one will be selected and used to display the layer.
/// </summary>
[DataField]
public List<SpriteSpecifier> Sprites { get; private set; } = new();
/// <summary>
/// A localized name piece to build into the item name generator.
/// </summary>
[DataField]
public LocId? Name { get; private set; }
/// <summary>
/// If the layer is the final one, it can be added over the limit, but no other layers can be added after it.
/// </summary>
[DataField]
public bool Final { get; private set; }
/// <summary>
/// Tag list of this layer. Used for recipes for food metamorphosis.
/// </summary>
[DataField]
public List<ProtoId<TagPrototype>> Tags { get; set; } = new();
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.Nutrition.FoodMetamorphRules;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Shared.Nutrition.Prototypes;
/// <summary>
/// Stores a recipe so that FoodSequence assembled in the right sequence can turn into a special meal.
/// </summary>
[Prototype]
public sealed partial class MetamorphRecipePrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;
/// <summary>
/// The key of the FoodSequence being collected. For example “burger” “taco” etc.
/// </summary>
[DataField(required: true)]
public ProtoId<TagPrototype> Key = string.Empty;
/// <summary>
/// The entity that will be created as a result of this recipe, and into which all the reagents will be transferred.
/// </summary>
[DataField(required: true)]
public EntProtoId Result = default!;
/// <summary>
/// A sequence of rules that must be followed for FoodSequence to metamorphose into a special food.
/// </summary>
[DataField]
public List<FoodMetamorphRule> Rules = new();
}