Make Reactions conserve thermal energy (#16190)

This commit is contained in:
Leon Friedrich
2023-05-13 15:10:32 +12:00
committed by GitHub
parent 99fceaf2e4
commit 0c4002bbd3
13 changed files with 70 additions and 79 deletions

View File

@@ -1,9 +0,0 @@
using Content.Shared.Chemistry.Reaction;
namespace Content.Client.Chemistry
{
public sealed class ChemicalReactionSystem : SharedChemicalReactionSystem
{
}
}

View File

@@ -181,7 +181,7 @@ namespace Content.Server.Body.Systems
if (effect.ShouldLog) if (effect.ShouldLog)
{ {
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact, _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
$"Metabolism effect {effect.GetType().Name:effect} of reagent {args.Reagent.LocalizedName:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}"); $"Metabolism effect {effect.GetType().Name:effect} of reagent {proto.LocalizedName:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}");
} }
effect.Effect(args); effect.Effect(args);

View File

@@ -1,25 +0,0 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Server.Chemistry.EntitySystems
{
public sealed class ChemicalReactionSystem : SharedChemicalReactionSystem
{
protected override void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype randomReagent, EntityUid owner, FixedPoint2 unitReactions)
{
base.OnReaction(solution, reaction, randomReagent, owner, unitReactions);
var coordinates = Transform(owner).Coordinates;
AdminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(owner):metabolizer} at {coordinates}");
SoundSystem.Play(reaction.Sound.GetSound(), Filter.Pvs(owner, entityManager:EntityManager), owner);
}
}
}

View File

@@ -34,7 +34,7 @@ public sealed class SolutionChangedEvent : EntityEventArgs
public sealed partial class SolutionContainerSystem : EntitySystem public sealed partial class SolutionContainerSystem : EntitySystem
{ {
[Dependency] [Dependency]
private readonly SharedChemicalReactionSystem _chemistrySystem = default!; private readonly ChemicalReactionSystem _chemistrySystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;

View File

@@ -23,13 +23,14 @@ namespace Content.Server.Chemistry.ReagentEffectConditions
public override bool Condition(ReagentEffectArgs args) public override bool Condition(ReagentEffectArgs args)
{ {
if (Reagent == null) var reagent = Reagent ?? args.Reagent?.ID;
Reagent = args.Reagent.ID; if (reagent == null)
return true; // No condition to apply.
var quant = FixedPoint2.Zero; var quant = FixedPoint2.Zero;
if (args.Source != null && args.Source.ContainsReagent(Reagent)) if (args.Source != null && args.Source.ContainsReagent(reagent))
{ {
quant = args.Source.GetReagentQuantity(args.Reagent.ID); quant = args.Source.GetReagentQuantity(reagent);
} }
return quant >= Min && quant <= Max; return quant >= Min && quant <= Max;

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Chemistry.ReactionEffects
public float CleanseRate = 3.0f; public float CleanseRate = 3.0f;
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (args.Source == null) if (args.Source == null || args.Reagent == null)
return; return;
var cleanseRate = CleanseRate; var cleanseRate = CleanseRate;

View File

@@ -18,9 +18,10 @@ public sealed class Electrocute : ReagentEffect
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
EntitySystem.Get<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null, args.EntityManager.System<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null,
Math.Max((args.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true); Math.Max((args.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity); if (args.Reagent != null)
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
} }
} }

View File

@@ -17,10 +17,13 @@ namespace Content.Server.Chemistry.ReagentEffects
public override void Effect(ReagentEffectArgs args) public override void Effect(ReagentEffectArgs args)
{ {
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable)) return; if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable))
return;
EntitySystem.Get<FlammableSystem>().AdjustFireStacks(args.SolutionEntity, args.Quantity.Float() * Multiplier, flammable); args.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.SolutionEntity, args.Quantity.Float() * Multiplier, flammable);
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
if (args.Reagent != null)
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
} }
} }
} }

View File

@@ -114,6 +114,10 @@ namespace Content.Shared.Chemistry.Components
return _heatCapacity; return _heatCapacity;
} }
public float GetThermalEnergy(IPrototypeManager? protoMan)
{
return GetHeatCapacity(protoMan) * Temperature;
}
/// <summary> /// <summary>
/// Constructs an empty solution (ex. an empty beaker). /// Constructs an empty solution (ex. an empty beaker).

View File

@@ -1,27 +1,23 @@
using System.Linq; using System.Linq;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Interaction.Events;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.Chemistry.Reaction namespace Content.Shared.Chemistry.Reaction
{ {
public abstract class SharedChemicalReactionSystem : EntitySystem public sealed class ChemicalReactionSystem : EntitySystem
{ {
/// <summary> /// <summary>
/// The maximum number of reactions that may occur when a solution is changed. /// The maximum number of reactions that may occur when a solution is changed.
/// </summary> /// </summary>
private const int MaxReactionIterations = 20; private const int MaxReactionIterations = 20;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
/// <summary> /// <summary>
/// A cache of all existant chemical reactions indexed by one of their /// A cache of all existant chemical reactions indexed by one of their
@@ -170,13 +166,12 @@ namespace Content.Shared.Chemistry.Reaction
/// <summary> /// <summary>
/// Perform a reaction on a solution. This assumes all reaction criteria are met. /// Perform a reaction on a solution. This assumes all reaction criteria are met.
/// Removes the reactants from the solution, then returns a solution with all products. /// Removes the reactants from the solution, adds products, and returns a list of products.
/// </summary> /// </summary>
private Solution PerformReaction(Solution solution, EntityUid owner, ReactionPrototype reaction, FixedPoint2 unitReactions) private List<string> PerformReaction(Solution solution, EntityUid owner, ReactionPrototype reaction, FixedPoint2 unitReactions)
{ {
// We do this so that ReagentEffect can have something to work with, even if it's var energy = reaction.ConserveEnergy ? solution.GetThermalEnergy(_prototypeManager) : 0;
// a little meaningless.
var randomReagent = _prototypeManager.Index<ReagentPrototype>(_random.Pick(reaction.Reactants).Key);
//Remove reactants //Remove reactants
foreach (var reactant in reaction.Reactants) foreach (var reactant in reaction.Reactants)
{ {
@@ -188,24 +183,35 @@ namespace Content.Shared.Chemistry.Reaction
} }
//Create products //Create products
var products = new Solution(); var products = new List<string>();
foreach (var product in reaction.Products) foreach (var product in reaction.Products)
{ {
products.AddReagent(product.Key, product.Value * unitReactions); products.Add(product.Key);
solution.AddReagent(product.Key, product.Value * unitReactions);
} }
// Trigger reaction effects if (reaction.ConserveEnergy)
OnReaction(solution, reaction, randomReagent, owner, unitReactions); {
var newCap = solution.GetHeatCapacity(_prototypeManager);
if (newCap > 0)
solution.Temperature = energy / newCap;
}
OnReaction(solution, reaction, null, owner, unitReactions);
return products; return products;
} }
protected virtual void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype randomReagent, EntityUid owner, FixedPoint2 unitReactions) private void OnReaction(Solution solution, ReactionPrototype reaction, ReagentPrototype? reagent, EntityUid owner, FixedPoint2 unitReactions)
{ {
var args = new ReagentEffectArgs(owner, null, solution, var args = new ReagentEffectArgs(owner, null, solution,
randomReagent, reagent,
unitReactions, EntityManager, null, 1f); unitReactions, EntityManager, null, 1f);
var coordinates = Transform(owner).Coordinates;
_adminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(owner):metabolizer} at {coordinates}");
foreach (var effect in reaction.Effects) foreach (var effect in reaction.Effects)
{ {
if (!effect.ShouldApply(args)) if (!effect.ShouldApply(args))
@@ -214,12 +220,14 @@ namespace Content.Shared.Chemistry.Reaction
if (effect.ShouldLog) if (effect.ShouldLog)
{ {
var entity = args.SolutionEntity; var entity = args.SolutionEntity;
AdminLogger.Add(LogType.ReagentEffect, effect.LogImpact, _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
$"Reaction effect {effect.GetType().Name:effect} of reaction ${reaction.ID:reaction} applied on entity {ToPrettyString(entity):entity} at {Transform(entity).Coordinates:coordinates}"); $"Reaction effect {effect.GetType().Name:effect} of reaction ${reaction.ID:reaction} applied on entity {ToPrettyString(entity):entity} at {Transform(entity).Coordinates:coordinates}");
} }
effect.Effect(args); effect.Effect(args);
} }
_audio.PlayPvs(reaction.Sound, owner);
} }
/// <summary> /// <summary>
@@ -230,7 +238,7 @@ namespace Content.Shared.Chemistry.Reaction
private bool ProcessReactions(Solution solution, EntityUid owner, FixedPoint2 maxVolume, SortedSet<ReactionPrototype> reactions, ReactionMixerComponent? mixerComponent) private bool ProcessReactions(Solution solution, EntityUid owner, FixedPoint2 maxVolume, SortedSet<ReactionPrototype> reactions, ReactionMixerComponent? mixerComponent)
{ {
HashSet<ReactionPrototype> toRemove = new(); HashSet<ReactionPrototype> toRemove = new();
Solution? products = null; List<string>? products = null;
// attempt to perform any applicable reaction // attempt to perform any applicable reaction
foreach (var reaction in reactions) foreach (var reaction in reactions)
@@ -249,30 +257,23 @@ namespace Content.Shared.Chemistry.Reaction
if (products == null) if (products == null)
return false; return false;
// Remove any reactions that were not applicable. Avoids re-iterating over them in future. if (products.Count == 0)
foreach (var proto in toRemove)
{
reactions.Remove(proto);
}
if (products.Volume <= 0)
return true; return true;
// remove excess product // remove excess product
// TODO spill excess? // TODO spill excess?
var excessVolume = solution.Volume + products.Volume - maxVolume; var excessVolume = solution.Volume - maxVolume;
if (excessVolume > 0) if (excessVolume > 0)
products.RemoveSolution(excessVolume); solution.RemoveSolution(excessVolume);
// Add any reactions associated with the new products. This may re-add reactions that were already iterated // Add any reactions associated with the new products. This may re-add reactions that were already iterated
// over previously. The new product may mean the reactions are applicable again and need to be processed. // over previously. The new product may mean the reactions are applicable again and need to be processed.
foreach (var reactant in products.Contents) foreach (var product in products)
{ {
if (_reactions.TryGetValue(reactant.ReagentId, out var reactantReactions)) if (_reactions.TryGetValue(product, out var reactantReactions))
reactions.UnionWith(reactantReactions); reactions.UnionWith(reactantReactions);
} }
solution.AddSolution(products, _prototypeManager);
return true; return true;
} }

View File

@@ -32,6 +32,12 @@ namespace Content.Shared.Chemistry.Reaction
[DataField("minTemp")] [DataField("minTemp")]
public float MinimumTemperature = 0.0f; public float MinimumTemperature = 0.0f;
/// <summary>
/// If true, this reaction will attempt to conserve thermal energy.
/// </summary>
[DataField("conserveEnergy")]
public bool ConserveEnergy = true;
/// <summary> /// <summary>
/// The maximum temperature the reaction can occur at. /// The maximum temperature the reaction can occur at.
/// </summary> /// </summary>

View File

@@ -79,7 +79,7 @@ namespace Content.Shared.Chemistry.Reagent
EntityUid SolutionEntity, EntityUid SolutionEntity,
EntityUid? OrganEntity, EntityUid? OrganEntity,
Solution? Source, Solution? Source,
ReagentPrototype Reagent, ReagentPrototype? Reagent,
FixedPoint2 Quantity, FixedPoint2 Quantity,
IEntityManager EntityManager, IEntityManager EntityManager,
ReactionMethod? Method, ReactionMethod? Method,

View File

@@ -2,6 +2,7 @@
id: Curdling id: Curdling
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Milk: Milk:
amount: 40 amount: 40
@@ -16,6 +17,7 @@
id: CreateDough id: CreateDough
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Flour: Flour:
amount: 15 amount: 15
@@ -29,6 +31,7 @@
id: CreateCornmealDough id: CreateCornmealDough
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Cornmeal: Cornmeal:
amount: 15 amount: 15
@@ -44,6 +47,7 @@
id: CreateCakeBatter id: CreateCakeBatter
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Flour: Flour:
amount: 15 amount: 15
@@ -59,6 +63,7 @@
id: CreateButter id: CreateButter
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Milk: Milk:
amount: 30 amount: 30
@@ -73,6 +78,7 @@
id: CreatePieDough id: CreatePieDough
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Flour: Flour:
amount: 15 amount: 15
@@ -91,6 +97,7 @@
id: CreateVeganCakeBatter id: CreateVeganCakeBatter
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
Flour: Flour:
amount: 15 amount: 15
@@ -106,6 +113,7 @@
id: CreateTofu id: CreateTofu
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
MilkSoy: MilkSoy:
amount: 30 amount: 30
@@ -221,6 +229,7 @@
id: CreateMeatball id: CreateMeatball
impact: Low impact: Low
quantized: true quantized: true
conserveEnergy: false
reactants: reactants:
UncookedAnimalProteins: UncookedAnimalProteins:
amount: 5 amount: 5