diff --git a/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs b/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs index c290d34b1b..89add6753e 100644 --- a/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs +++ b/Content.Server/Chemistry/Components/SmokeSolutionAreaEffectComponent.cs @@ -37,7 +37,7 @@ namespace Content.Server.Chemistry.Components internals.AreInternalsWorking()) return; - var chemistry = EntitySystem.Get(); + var chemistry = EntitySystem.Get(); var cloneSolution = solution.Clone(); var transferAmount = FixedPoint2.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.EmptyVolume); var transferSolution = cloneSolution.SplitSolution(transferAmount); diff --git a/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs b/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs index eb803d7df6..07507fb443 100644 --- a/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionAreaEffectComponent.cs @@ -126,7 +126,7 @@ namespace Content.Server.Chemistry.Components if (!EntitySystem.Get().TryGetSolution(Owner.Uid, SolutionName, out var solution)) return; - var chemistry = EntitySystem.Get(); + var chemistry = EntitySystem.Get(); var mapGrid = MapManager.GetGrid(Owner.Transform.GridID); var tile = mapGrid.GetTileRef(Owner.Transform.Coordinates.ToVector2i(Owner.EntityManager, MapManager)); diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs index 36b177c49c..3a70812bc3 100644 --- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.cs @@ -17,7 +17,7 @@ namespace Content.Server.Nutrition.EntitySystems { public partial class SmokingSystem : EntitySystem { - [Dependency] private readonly ChemistrySystem _chemistrySystem = default!; + [Dependency] private readonly ReactiveSystem _reactiveSystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; private const float UpdateTimer = 3f; @@ -98,7 +98,7 @@ namespace Content.Server.Nutrition.EntitySystems !containerManager.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) continue; - _chemistrySystem.ReactionEntity(containerManager.Owner.Uid, ReactionMethod.Ingestion, inhaledSolution); + _reactiveSystem.ReactionEntity(containerManager.Owner.Uid, ReactionMethod.Ingestion, inhaledSolution); bloodstream.TryTransferSolution(inhaledSolution); } diff --git a/Content.Shared/Chemistry/Components/Solution.cs b/Content.Shared/Chemistry/Components/Solution.cs index 885e960e03..862abcfff1 100644 --- a/Content.Shared/Chemistry/Components/Solution.cs +++ b/Content.Shared/Chemistry/Components/Solution.cs @@ -334,7 +334,7 @@ namespace Content.Shared.Chemistry.Components public void DoEntityReaction(EntityUid uid, ReactionMethod method) { - var chemistry = EntitySystem.Get(); + var chemistry = EntitySystem.Get(); foreach (var (reagentId, quantity) in Contents.ToArray()) { diff --git a/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs b/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs index 4d08016256..0aa95ca216 100644 --- a/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs +++ b/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs @@ -1,31 +1,45 @@ -using System; using System.Collections.Generic; using Content.Shared.Chemistry.Reagent; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -namespace Content.Shared.Chemistry.Reaction +namespace Content.Shared.Chemistry.Reaction; + +[RegisterComponent] +public class ReactiveComponent : Component { - [RegisterComponent] - public class ReactiveComponent : Component - { - public override string Name => "Reactive"; + public override string Name => "Reactive"; - [DataField("reactions", required: true, readOnly: true, serverOnly: true)] - public List Reactions { get; } = default!; - } + /// + /// A dictionary of reactive groups -> methods that work on them. + /// + [DataField("groups", readOnly: true, serverOnly: true, + customTypeSerializer: + typeof(PrototypeIdDictionarySerializer, ReactiveGroupPrototype>))] + public Dictionary>? ReactiveGroups; - [DataDefinition] - public class ReactiveReagentEffectEntry - { - [DataField("methods")] - public HashSet Methods = default!; - - [DataField("reagents", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet? Reagents = null; - - [DataField("effects", required: true)] - public List Effects = default!; - } + /// + /// Special reactions that this prototype can specify, outside of any that reagents already apply. + /// Useful for things like monkey cubes, which have a really prototype-specific effect. + /// + [DataField("reactions", true, serverOnly: true)] + public List? Reactions; +} + +[DataDefinition] +public class ReactiveReagentEffectEntry +{ + [DataField("methods")] + public HashSet Methods = default!; + + [DataField("reagents", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet? Reagents = null; + + [DataField("effects", required: true)] + public List Effects = default!; + [DataField("groups", required: true, readOnly: true, serverOnly: true, + customTypeSerializer:typeof(PrototypeIdDictionarySerializer, ReactiveGroupPrototype>))] + public Dictionary> ReactiveGroups { get; } = default!; } diff --git a/Content.Shared/Chemistry/Reaction/ReactiveGroupPrototype.cs b/Content.Shared/Chemistry/Reaction/ReactiveGroupPrototype.cs new file mode 100644 index 0000000000..d375793364 --- /dev/null +++ b/Content.Shared/Chemistry/Reaction/ReactiveGroupPrototype.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; + +namespace Content.Shared.Chemistry.Reaction; + +[Prototype("reactiveGroup")] +public class ReactiveGroupPrototype : IPrototype +{ + [DataField("id", required: true)] + public string ID { get; } = default!; +} diff --git a/Content.Shared/Chemistry/ChemistrySystem.cs b/Content.Shared/Chemistry/ReactiveSystem.cs similarity index 54% rename from Content.Shared/Chemistry/ChemistrySystem.cs rename to Content.Shared/Chemistry/ReactiveSystem.cs index 9917248a3d..8f65e4fae2 100644 --- a/Content.Shared/Chemistry/ChemistrySystem.cs +++ b/Content.Shared/Chemistry/ReactiveSystem.cs @@ -11,7 +11,7 @@ using Robust.Shared.Random; namespace Content.Shared.Chemistry { [UsedImplicitly] - public partial class ChemistrySystem : EntitySystem + public class ReactiveSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; @@ -40,20 +40,48 @@ namespace Content.Shared.Chemistry var args = new ReagentEffectArgs(uid, null, source, reagent, source?.GetReagentQuantity(reagent.ID) ?? reactVolume, EntityManager, method); - foreach (var entry in reactive.Reactions) + // First, check if the reagent wants to apply any effects. + if (reagent.ReactiveEffects != null && reactive.ReactiveGroups != null) { - if (!entry.Methods.Contains(method)) - continue; - - if (entry.Reagents != null && !entry.Reagents.Contains(reagent.ID)) - continue; - - foreach (var effect in entry.Effects) + foreach (var (key, val) in reagent.ReactiveEffects) { - if (!effect.ShouldApply(args, _robustRandom)) + if (!val.Methods.Contains(method)) continue; - effect.Effect(args); + if (!reactive.ReactiveGroups.ContainsKey(key)) + continue; + + if (!reactive.ReactiveGroups[key].Contains(method)) + continue; + + foreach (var effect in val.Effects) + { + if (!effect.ShouldApply(args, _robustRandom)) + continue; + + effect.Effect(args); + } + } + } + + // Then, check if the prototype has any effects it can apply as well. + if (reactive.Reactions != null) + { + foreach (var entry in reactive.Reactions) + { + if (!entry.Methods.Contains(method)) + continue; + + if (entry.Reagents != null && !entry.Reagents.Contains(reagent.ID)) + continue; + + foreach (var effect in entry.Effects) + { + if (!effect.ShouldApply(args, _robustRandom)) + continue; + + effect.Effect(args); + } } } } diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index 040e134cf1..88c4bdf21f 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -23,15 +23,6 @@ namespace Content.Shared.Chemistry.Reagent [DataDefinition] public class ReagentPrototype : IPrototype, IInheritingPrototype { - [DataField("metabolisms", serverOnly: true, customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - public Dictionary? Metabolisms = null; - - [DataField("tileReactions", serverOnly: true)] - private readonly List _tileReactions = new(0); - - [DataField("plantMetabolism", serverOnly: true)] - private readonly List _plantMetabolism = new(0); - [ViewVariables] [DataField("id", required: true)] public string ID { get; } = default!; @@ -64,9 +55,17 @@ namespace Content.Shared.Chemistry.Reagent [DataField("spritePath")] public string SpriteReplacementPath { get; } = string.Empty; - //List of metabolism effects this reagent has, should really only be used server-side. - public IReadOnlyList TileReactions => _tileReactions; - public IReadOnlyList PlantMetabolism => _plantMetabolism; + [DataField("metabolisms", serverOnly: true, customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + public Dictionary? Metabolisms = null; + + [DataField("reactiveEffects", serverOnly: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer))] + public Dictionary? ReactiveEffects = null; + + [DataField("tileReactions", serverOnly: true)] + public readonly List TileReactions = new(0); + + [DataField("plantMetabolism", serverOnly: true)] + public readonly List PlantMetabolisms = new(0); /// /// If the substance color is too dark we user a lighter version to make the text color readable when the user examines a solution. @@ -93,7 +92,7 @@ namespace Content.Shared.Chemistry.Reagent if (tile.Tile.IsEmpty) return removed; - foreach (var reaction in _tileReactions) + foreach (var reaction in TileReactions) { removed += reaction.TileReact(tile, this, reactVolume - removed); @@ -115,7 +114,7 @@ namespace Content.Shared.Chemistry.Reagent var entMan = IoCManager.Resolve(); var random = IoCManager.Resolve(); var args = new ReagentEffectArgs(plantHolder.Value, null, solution, this, amount.Quantity, entMan, null); - foreach (var plantMetabolizable in _plantMetabolism) + foreach (var plantMetabolizable in PlantMetabolisms) { if (!plantMetabolizable.ShouldApply(args, random)) continue; @@ -140,4 +139,14 @@ namespace Content.Shared.Chemistry.Reagent [DataField("effects", required: true)] public ReagentEffect[] Effects = default!; } + + [DataDefinition] + public class ReactiveReagentEffectEntry + { + [DataField("methods", required: true)] + public HashSet Methods = default!; + + [DataField("effects", required: true)] + public ReagentEffect[] Effects = default!; + } } diff --git a/Resources/Prototypes/Metabolism/metabolism_groups.yml b/Resources/Prototypes/Chemistry/metabolism_groups.yml similarity index 100% rename from Resources/Prototypes/Metabolism/metabolism_groups.yml rename to Resources/Prototypes/Chemistry/metabolism_groups.yml diff --git a/Resources/Prototypes/Metabolism/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml similarity index 100% rename from Resources/Prototypes/Metabolism/metabolizer_types.yml rename to Resources/Prototypes/Chemistry/metabolizer_types.yml diff --git a/Resources/Prototypes/Chemistry/reactive_groups.yml b/Resources/Prototypes/Chemistry/reactive_groups.yml new file mode 100644 index 0000000000..264df6e394 --- /dev/null +++ b/Resources/Prototypes/Chemistry/reactive_groups.yml @@ -0,0 +1,5 @@ +- type: reactiveGroup + id: Flammable + +- type: reactiveGroup + id: Extinguish diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index d4c974565a..08d033583c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -5,15 +5,9 @@ suffix: AI components: - type: Reactive - reactions: - - reagents: [Water] - methods: [Touch] - effects: - - !type:ExtinguishReaction - - reagents: [WeldingFuel, Thermite, Plasma, Ethanol] - methods: [Touch] - effects: - - !type:FlammableReaction + groups: + Flammable: [Touch] + Extinguish: [Touch] - type: UtilityAI behaviorSets: # - Clothing diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 054f803438..c1dee05a76 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -11,15 +11,10 @@ - CanPilot - FootstepSound - type: Reactive + groups: + Flammable: [ Touch ] + Extinguish: [ Touch ] reactions: - - reagents: [Water] - methods: [Touch] - effects: - - !type:ExtinguishReaction - - reagents: [WeldingFuel, Thermite, Plasma, Ethanol] - methods: [Touch] - effects: - - !type:FlammableReaction - reagents: [Water, SpaceCleaner] methods: [Touch] effects: diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml index a0ea35bc44..85385fa138 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml @@ -60,14 +60,12 @@ - type: reagent id: Ethanol name: ethanol + parent: BaseAlcohol desc: A simple alcohol, makes you drunk if consumed, flammable. physicalDesc: strong-smelling color: "#b05b3c" boilingPoint: 78.2 meltingPoint: -114.1 - tileReactions: - - !type:FlammableTileReaction - temperatureMultiplier: 1.35 metabolisms: Poison: effects: diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml b/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml index eeb3c045d4..35da9dcabb 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml @@ -6,6 +6,11 @@ effects: - !type:SatiateThirst factor: 3 + reactiveEffects: + Extinguish: + methods: [ Touch ] + effects: + - !type:ExtinguishReaction plantMetabolism: - !type:PlantAdjustWater amount: 1 @@ -38,6 +43,11 @@ effects: - !type:SatiateThirst factor: 2 + reactiveEffects: + Flammable: + methods: [ Touch ] + effects: + - !type:FlammableReaction tileReactions: - !type:FlammableTileReaction temperatureMultiplier: 1.35 diff --git a/Resources/Prototypes/Reagents/pyrotechnic.yml b/Resources/Prototypes/Reagents/pyrotechnic.yml index 3b5a003a2b..b663c1df06 100644 --- a/Resources/Prototypes/Reagents/pyrotechnic.yml +++ b/Resources/Prototypes/Reagents/pyrotechnic.yml @@ -1,6 +1,15 @@ - type: reagent + id: BasePyrotechnic + reactiveEffects: + Flammable: + methods: [ Touch ] + effects: + - !type:FlammableReaction + +- type: reagent id: Thermite name: thermite + parent: BasePyrotechnic desc: A mixture that becomes extremely hot when ignited, and which can burn straight through walls when applied and ignited. It'll slowly inflict burn damage to anybody dumb enough to ingest it, but can't be ignited inside inside said dumb person. physicalDesc: grainy color: "#757245" @@ -20,6 +29,7 @@ - type: reagent id: FoamingAgent name: foaming agent + parent: BasePyrotechnic desc: Makes foam such as that's required in metal foam grenades. physicalDesc: foamy color: "#215263" @@ -29,6 +39,7 @@ - type: reagent id: WeldingFuel name: welding fuel + parent: BasePyrotechnic desc: Used by welders to weld. physicalDesc: oily color: "#a76b1c" @@ -40,6 +51,7 @@ - type: reagent id: Fluorosurfactant name: fluorosurfactant + parent: BasePyrotechnic desc: A perfluoronated sulfonic acid that forms a foam when mixed with water. physicalDesc: opaque color: "#9e6b38" diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index bd63000e18..9812112a30 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -115,6 +115,11 @@ - !type:RemoveReagent reagent: Inaprovaline amount: 2 + reactiveEffects: + Flammable: + methods: [ Touch ] + effects: + - !type:FlammableReaction - type: reagent id: UnstableMutagen