diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 14f591ec87..defe08ef23 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,6 +28,7 @@ /Content.*/Stunnable/ @Princess-Cheeseballs /Content.*/Nutrition/ @Princess-Cheeseballs +/Content.*/EntityEffects @Princess-Cheeseballs @sowelipililimute # SKREEEE /Content.*.Database/ @PJB3005 @DrSmugleaf diff --git a/Content.Client/Temperature/Systems/TemperatureSystem.cs b/Content.Client/Temperature/Systems/TemperatureSystem.cs new file mode 100644 index 0000000000..94a1e836e8 --- /dev/null +++ b/Content.Client/Temperature/Systems/TemperatureSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Temperature.Systems; + +namespace Content.Client.Temperature.Systems; + +/// +/// This exists so runs on client/> +/// +public sealed class TemperatureSystem : SharedTemperatureSystem; diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index 424f52ed58..81bd4e5c6c 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; using Content.Server.Stunnable; -using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Server.Damage.Components; using Content.Shared.ActionBlocker; @@ -24,6 +23,7 @@ using Content.Shared.Toggleable; using Content.Shared.Weapons.Melee.Events; using Content.Shared.FixedPoint; using Content.Shared.Hands; +using Content.Shared.Temperature.Components; using Robust.Server.Audio; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; diff --git a/Content.Server/Atmos/Rotting/RottingSystem.cs b/Content.Server/Atmos/Rotting/RottingSystem.cs index 6f14debc3d..57c1504b16 100644 --- a/Content.Server/Atmos/Rotting/RottingSystem.cs +++ b/Content.Server/Atmos/Rotting/RottingSystem.cs @@ -1,9 +1,9 @@ using Content.Server.Atmos.EntitySystems; -using Content.Server.Temperature.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Rotting; using Content.Shared.Body.Events; using Content.Shared.Damage; +using Content.Shared.Temperature.Components; using Robust.Server.Containers; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; diff --git a/Content.Server/Body/Components/MetabolizerComponent.cs b/Content.Server/Body/Components/MetabolizerComponent.cs index 5821b0d944..46d2fdd8e8 100644 --- a/Content.Server/Body/Components/MetabolizerComponent.cs +++ b/Content.Server/Body/Components/MetabolizerComponent.cs @@ -10,13 +10,13 @@ namespace Content.Server.Body.Components /// /// Handles metabolizing various reagents with given effects. /// - [RegisterComponent, Access(typeof(MetabolizerSystem))] + [RegisterComponent, AutoGenerateComponentPause, Access(typeof(MetabolizerSystem))] public sealed partial class MetabolizerComponent : Component { /// /// The next time that reagents will be metabolized. /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [DataField, AutoPausedField] public TimeSpan NextUpdate; /// diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs index c59f87f576..6679bfea54 100644 --- a/Content.Server/Body/Systems/MetabolizerSystem.cs +++ b/Content.Server/Body/Systems/MetabolizerSystem.cs @@ -1,14 +1,18 @@ using Content.Server.Body.Components; -using Content.Shared.Administration.Logs; using Content.Shared.Body.Events; using Content.Shared.Body.Organ; +using Content.Shared.Body.Prototypes; using Content.Shared.Body.Systems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Database; +using Content.Shared.EntityConditions; +using Content.Shared.EntityConditions.Conditions; +using Content.Shared.EntityConditions.Conditions.Body; using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Body; +using Content.Shared.EntityEffects.Effects.Solution; using Content.Shared.FixedPoint; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; @@ -17,210 +21,258 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; -namespace Content.Server.Body.Systems +namespace Content.Server.Body.Systems; + +/// +public sealed class MetabolizerSystem : SharedMetabolizerSystem { - /// - public sealed class MetabolizerSystem : SharedMetabolizerSystem + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!; + [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; + + private EntityQuery _organQuery; + private EntityQuery _solutionQuery; + private static readonly ProtoId Gas = "Gas"; + + public override void Initialize() { - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; + base.Initialize(); - private EntityQuery _organQuery; - private EntityQuery _solutionQuery; + _organQuery = GetEntityQuery(); + _solutionQuery = GetEntityQuery(); - public override void Initialize() + SubscribeLocalEvent(OnMetabolizerInit); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnApplyMetabolicMultiplier); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval; + } + + private void OnMetabolizerInit(Entity entity, ref ComponentInit args) + { + if (!entity.Comp.SolutionOnBody) { - base.Initialize(); - - _organQuery = GetEntityQuery(); - _solutionQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnMetabolizerInit); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnUnpaused); - SubscribeLocalEvent(OnApplyMetabolicMultiplier); + _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _); } - - private void OnMapInit(Entity ent, ref MapInitEvent args) + else if (_organQuery.CompOrNull(entity)?.Body is { } body) { - ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval; - } - - private void OnUnpaused(Entity ent, ref EntityUnpausedEvent args) - { - ent.Comp.NextUpdate += args.PausedTime; - } - - private void OnMetabolizerInit(Entity entity, ref ComponentInit args) - { - if (!entity.Comp.SolutionOnBody) - { - _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _); - } - else if (_organQuery.CompOrNull(entity)?.Body is { } body) - { - _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _); - } - } - - private void OnApplyMetabolicMultiplier(Entity ent, ref ApplyMetabolicMultiplierEvent args) - { - ent.Comp.UpdateIntervalMultiplier = args.Multiplier; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count()); - var query = EntityQueryEnumerator(); - - while (query.MoveNext(out var uid, out var comp)) - { - metabolizers.Add((uid, comp)); - } - - foreach (var (uid, metab) in metabolizers) - { - // Only update as frequently as it should - if (_gameTiming.CurTime < metab.NextUpdate) - continue; - - metab.NextUpdate += metab.AdjustedUpdateInterval; - TryMetabolize((uid, metab)); - } - } - - private void TryMetabolize(Entity ent) - { - _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false); - - // First step is get the solution we actually care about - var solutionName = ent.Comp1.SolutionName; - Solution? solution = null; - Entity? soln = default!; - EntityUid? solutionEntityUid = null; - - if (ent.Comp1.SolutionOnBody) - { - if (ent.Comp2?.Body is { } body) - { - if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false)) - return; - - _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution); - solutionEntityUid = body; - } - } - else - { - if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false)) - return; - - _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution); - solutionEntityUid = ent; - } - - if (solutionEntityUid is null - || soln is null - || solution is null - || solution.Contents.Count == 0) - { - return; - } - - // randomize the reagent list so we don't have any weird quirks - // like alphabetical order or insertion order mattering for processing - var list = solution.Contents.ToArray(); - _random.Shuffle(list); - - int reagents = 0; - foreach (var (reagent, quantity) in list) - { - if (!_prototypeManager.TryIndex(reagent.Prototype, out var proto)) - continue; - - var mostToRemove = FixedPoint2.Zero; - if (proto.Metabolisms is null) - { - if (ent.Comp1.RemoveEmpty) - { - solution.RemoveReagent(reagent, FixedPoint2.New(1)); - } - - continue; - } - - // we're done here entirely if this is true - if (reagents >= ent.Comp1.MaxReagentsProcessable) - return; - - - // loop over all our groups and see which ones apply - if (ent.Comp1.MetabolismGroups is null) - continue; - - foreach (var group in ent.Comp1.MetabolismGroups) - { - if (!proto.Metabolisms.TryGetValue(group.Id, out var entry)) - continue; - - var rate = entry.MetabolismRate * group.MetabolismRateModifier; - - // Remove $rate, as long as there's enough reagent there to actually remove that much - mostToRemove = FixedPoint2.Clamp(rate, 0, quantity); - - float scale = (float) mostToRemove / (float) rate; - - // if it's possible for them to be dead, and they are, - // then we shouldn't process any effects, but should probably - // still remove reagents - if (TryComp(solutionEntityUid.Value, out var state)) - { - if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state)) - continue; - } - - var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value; - var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale); - - // do all effects, if conditions apply - foreach (var effect in entry.Effects) - { - if (!effect.ShouldApply(args, _random)) - continue; - - if (effect.ShouldLog) - { - _adminLogger.Add( - LogType.ReagentEffect, - effect.LogImpact, - $"Metabolism effect {effect.GetType().Name:effect}" - + $" of reagent {proto.LocalizedName:reagent}" - + $" applied on entity {actualEntity:entity}" - + $" at {Transform(actualEntity).Coordinates:coordinates}" - ); - } - - effect.Effect(args); - } - } - - // remove a certain amount of reagent - if (mostToRemove > FixedPoint2.Zero) - { - solution.RemoveReagent(reagent, mostToRemove); - - // We have processed a reagant, so count it towards the cap - reagents += 1; - } - } - - _solutionContainerSystem.UpdateChemicals(soln.Value); + _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _); } } + + private void OnApplyMetabolicMultiplier(Entity ent, ref ApplyMetabolicMultiplierEvent args) + { + ent.Comp.UpdateIntervalMultiplier = args.Multiplier; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count()); + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + metabolizers.Add((uid, comp)); + } + + foreach (var (uid, metab) in metabolizers) + { + // Only update as frequently as it should + if (_gameTiming.CurTime < metab.NextUpdate) + continue; + + metab.NextUpdate += metab.AdjustedUpdateInterval; + TryMetabolize((uid, metab)); + } + } + + private void TryMetabolize(Entity ent) + { + _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false); + + // First step is get the solution we actually care about + var solutionName = ent.Comp1.SolutionName; + Solution? solution = null; + Entity? soln = default!; + EntityUid? solutionEntityUid = null; + + if (ent.Comp1.SolutionOnBody) + { + if (ent.Comp2?.Body is { } body) + { + if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false)) + return; + + _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution); + solutionEntityUid = body; + } + } + else + { + if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false)) + return; + + _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution); + solutionEntityUid = ent; + } + + if (solutionEntityUid is null + || soln is null + || solution is null + || solution.Contents.Count == 0) + { + return; + } + + // randomize the reagent list so we don't have any weird quirks + // like alphabetical order or insertion order mattering for processing + var list = solution.Contents.ToArray(); + _random.Shuffle(list); + + int reagents = 0; + foreach (var (reagent, quantity) in list) + { + if (!_prototypeManager.TryIndex(reagent.Prototype, out var proto)) + continue; + + var mostToRemove = FixedPoint2.Zero; + if (proto.Metabolisms is null) + { + if (ent.Comp1.RemoveEmpty) + { + solution.RemoveReagent(reagent, FixedPoint2.New(1)); + } + + continue; + } + + // we're done here entirely if this is true + if (reagents >= ent.Comp1.MaxReagentsProcessable) + return; + + + // loop over all our groups and see which ones apply + if (ent.Comp1.MetabolismGroups is null) + continue; + + // TODO: Kill MetabolismGroups! + foreach (var group in ent.Comp1.MetabolismGroups) + { + if (!proto.Metabolisms.TryGetValue(group.Id, out var entry)) + continue; + + var rate = entry.MetabolismRate * group.MetabolismRateModifier; + + // Remove $rate, as long as there's enough reagent there to actually remove that much + mostToRemove = FixedPoint2.Clamp(rate, 0, quantity); + + var scale = (float) mostToRemove; + + // TODO: This is a very stupid workaround to lungs heavily relying on scale = reagent quantity. Needs lung and metabolism refactors to remove. + // TODO: Lungs just need to have their scale be equal to the mols consumed, scale needs to be not hardcoded either and configurable per metabolizer... + if (group.Id != Gas) + scale /= (float) entry.MetabolismRate; + + // if it's possible for them to be dead, and they are, + // then we shouldn't process any effects, but should probably + // still remove reagents + if (TryComp(solutionEntityUid.Value, out var state)) + { + if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state)) + continue; + } + + var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value; + + // do all effects, if conditions apply + foreach (var effect in entry.Effects) + { + if (scale < effect.MinScale) + continue; + + // See if conditions apply + if (effect.Conditions != null && !CanMetabolizeEffect(actualEntity, ent, soln.Value, effect.Conditions)) + continue; + + ApplyEffect(effect); + + } + + // TODO: We should have to do this with metabolism. ReagentEffect struct needs refactoring and so does metabolism! + void ApplyEffect(EntityEffect effect) + { + switch (effect) + { + case ModifyLungGas: + _entityEffects.ApplyEffect(ent, effect, scale); + break; + case AdjustReagent: + _entityEffects.ApplyEffect(soln.Value, effect, scale); + break; + default: + _entityEffects.ApplyEffect(actualEntity, effect, scale); + break; + } + } + } + + // remove a certain amount of reagent + if (mostToRemove > FixedPoint2.Zero) + { + solution.RemoveReagent(reagent, mostToRemove); + + // We have processed a reagant, so count it towards the cap + reagents += 1; + } + } + + _solutionContainerSystem.UpdateChemicals(soln.Value); + } + + /// + /// Public API to check if a certain metabolism effect can be applied to an entity. + /// TODO: With metabolism refactor make this logic smarter and unhardcode the old hardcoding entity effects used to have for metabolism! + /// + /// The body metabolizing the effects + /// The organ doing the metabolizing + /// The solution we are metabolizing from + /// The conditions that need to be met to metabolize + /// True if we can metabolize! False if we cannot! + public bool CanMetabolizeEffect(EntityUid body, EntityUid organ, Entity solution, EntityCondition[] conditions) + { + foreach (var condition in conditions) + { + switch (condition) + { + // Need specific handling of specific conditions since Metabolism is funny like that. + // TODO: MetabolizerTypes should be handled well before this stage by metabolism itself. + case MetabolizerTypeCondition: + if (_entityConditions.TryCondition(organ, condition)) + continue; + break; + case ReagentCondition: + if (_entityConditions.TryCondition(solution, condition)) + continue; + break; + default: + if (_entityConditions.TryCondition(body, condition)) + continue; + break; + } + + return false; + } + + return true; + } } + diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 63b04adc6a..2af7b24f26 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Components; using Content.Server.Chat.Systems; -using Content.Server.EntityEffects; using Content.Shared.Body.Systems; using Content.Shared.Alert; using Content.Shared.Atmos; @@ -14,9 +13,11 @@ using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; using Content.Shared.Database; +using Content.Shared.EntityConditions; +using Content.Shared.EntityConditions.Conditions.Body; using Content.Shared.EntityEffects; -using Content.Shared.EntityEffects.EffectConditions; using Content.Shared.EntityEffects.Effects; +using Content.Shared.EntityEffects.Effects.Body; using Content.Shared.Mobs.Systems; using JetBrains.Annotations; using Robust.Shared.Prototypes; @@ -29,16 +30,16 @@ public sealed class RespiratorSystem : EntitySystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly AtmosphereSystem _atmosSys = default!; [Dependency] private readonly BodySystem _bodySystem = default!; + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly DamageableSystem _damageableSys = default!; [Dependency] private readonly LungSystem _lungSystem = default!; [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly EntityEffectSystem _entityEffect = default!; private static readonly ProtoId GasId = new("Gas"); @@ -340,7 +341,6 @@ public sealed class RespiratorSystem : EntitySystem } } - // TODO generalize condition checks // this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture // Applying actual reaction effects require a full ReagentEffectArgs struct. bool CanMetabolize(EntityEffect effect) @@ -348,9 +348,10 @@ public sealed class RespiratorSystem : EntitySystem if (effect.Conditions == null) return true; + // TODO: Use Metabolism Public API to do this instead, once that API has been built. foreach (var cond in effect.Conditions) { - if (cond is OrganType organ && !_entityEffect.OrganCondition(organ, lung)) + if (cond is MetabolizerTypeCondition organ && !_entityConditions.TryCondition(lung, organ)) return false; } diff --git a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs index 3ba9e6af31..af7b17643e 100644 --- a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs +++ b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs @@ -1,7 +1,7 @@ using Content.Server.Body.Components; -using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; using Content.Shared.ActionBlocker; +using Content.Shared.Temperature.Components; using Robust.Shared.Timing; namespace Content.Server.Body.Systems; diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs index ee7ca4f584..253eea2df9 100644 --- a/Content.Server/Botany/SeedPrototype.cs +++ b/Content.Server/Botany/SeedPrototype.cs @@ -1,8 +1,9 @@ using Content.Server.Botany.Components; using Content.Server.Botany.Systems; -using Content.Server.EntityEffects; +using Content.Server.EntityEffects.Effects.Botany; using Content.Shared.Atmos; using Content.Shared.Database; +using Content.Shared.EntityEffects; using Content.Shared.Random; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -79,9 +80,13 @@ public partial struct SeedChemQuantity [DataField("Inherent")] public bool Inherent = true; } -// TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component. +// TODO Make Botany ECS and give it a proper API. I removed the limited access of this class because it's egregious how many systems needed access to it due to a lack of an actual API. +/// +/// SeedData is no longer restricted because the number of friends is absolutely unreasonable. +/// This entire data definition is unreasonable. I felt genuine fear looking at this, this is horrific. Send help. +/// +// TODO: Hit Botany with hammers [Virtual, DataDefinition] -[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffectSystem), typeof(MutationSystem))] public partial class SeedData { #region Tracking diff --git a/Content.Server/Botany/Systems/BotanySystem.Produce.cs b/Content.Server/Botany/Systems/BotanySystem.Produce.cs index f6f3f99c09..7d8f8652c7 100644 --- a/Content.Server/Botany/Systems/BotanySystem.Produce.cs +++ b/Content.Server/Botany/Systems/BotanySystem.Produce.cs @@ -7,6 +7,8 @@ namespace Content.Server.Botany.Systems; public sealed partial class BotanySystem { + [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; + public void ProduceGrown(EntityUid uid, ProduceComponent produce) { if (!TryGetSeed(produce, out var seed)) @@ -15,10 +17,7 @@ public sealed partial class BotanySystem foreach (var mutation in seed.Mutations) { if (mutation.AppliesToProduce) - { - var args = new EntityEffectBaseArgs(uid, EntityManager); - mutation.Effect.Effect(args); - } + _entityEffects.TryApplyEffect(uid, mutation.Effect); } if (!_solutionContainerSystem.EnsureSolution(uid, diff --git a/Content.Server/Botany/Systems/MutationSystem.cs b/Content.Server/Botany/Systems/MutationSystem.cs index ee35db48e3..834fd9e8ef 100644 --- a/Content.Server/Botany/Systems/MutationSystem.cs +++ b/Content.Server/Botany/Systems/MutationSystem.cs @@ -13,6 +13,7 @@ public sealed class MutationSystem : EntitySystem [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; private RandomPlantMutationListPrototype _randomMutations = default!; public override void Initialize() @@ -32,10 +33,8 @@ public sealed class MutationSystem : EntitySystem if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f))) { if (mutation.AppliesToPlant) - { - var args = new EntityEffectBaseArgs(plantHolder, EntityManager); - mutation.Effect.Effect(args); - } + _entityEffects.TryApplyEffect(plantHolder, mutation.Effect); + // Stat adjustments do not persist by being an attached effect, they just change the stat. if (mutation.Persists && !seed.Mutations.Any(m => m.Name == mutation.Name)) seed.Mutations.Add(mutation); diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index caa796efe2..2554f95455 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -24,8 +24,10 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; using Content.Shared.Administration.Logs; +using Content.Shared.Chemistry.Reaction; using Content.Shared.Containers.ItemSlots; using Content.Shared.Database; +using Content.Shared.EntityEffects; using Content.Shared.Kitchen.Components; using Content.Shared.Labels.Components; @@ -48,6 +50,7 @@ public sealed class PlantHolderSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; public const float HydroponicsSpeedMultiplier = 1f; public const float HydroponicsConsumptionMultiplier = 2f; @@ -887,7 +890,7 @@ public sealed class PlantHolderSystem : EntitySystem foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt)) { var reagentProto = _prototype.Index(entry.Reagent.Prototype); - reagentProto.ReactionPlant(uid, entry, solution, EntityManager, _random, _adminLogger); + _entityEffects.ApplyEffects(uid, reagentProto.PlantMetabolisms.ToArray()); } } diff --git a/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs b/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs index a70c2196ab..58c86f058a 100644 --- a/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs +++ b/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs @@ -39,7 +39,7 @@ public sealed class DumpReagentGuideText : LocalizedEntityCommands { foreach (var effect in entry.Effects) { - shell.WriteLine(effect.GuidebookEffectDescription(_prototype, EntityManager.EntitySysManager) ?? + shell.WriteLine(reagent.GuidebookReagentEffectDescription(_prototype, EntityManager.EntitySysManager, effect, entry.MetabolismRate) ?? Loc.GetString($"cmd-dumpreagentguidetext-skipped", ("effect", effect.GetType()))); } } diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 3dd5a5b794..77a1a63e02 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -13,6 +13,7 @@ using Content.Shared.Prying.Systems; using Content.Shared.Radio.EntitySystems; using Content.Shared.Stacks; using Content.Shared.Temperature; +using Content.Shared.Temperature.Components; using Content.Shared.Tools.Systems; using Robust.Shared.Containers; using Robust.Shared.Utility; diff --git a/Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs b/Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs new file mode 100644 index 0000000000..e7b8aaf22b --- /dev/null +++ b/Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs @@ -0,0 +1,19 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Shared.EntityConditions; +using Content.Shared.EntityConditions.Conditions.Body; + +namespace Content.Server.EntityConditions.Conditions; + +/// +/// Returns true if this entity is both able to breathe and is currently breathing. +/// +/// +public sealed partial class IsBreathingEntityConditionSystem : EntityConditionSystem +{ + [Dependency] private readonly RespiratorSystem _respirator = default!; + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + args.Result = _respirator.IsBreathing(entity.AsNullable()); + } +} diff --git a/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs b/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs new file mode 100644 index 0000000000..3b4fb5292b --- /dev/null +++ b/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs @@ -0,0 +1,21 @@ +using System.Linq; +using Content.Server.Body.Components; +using Content.Shared.EntityConditions; +using Content.Shared.EntityConditions.Conditions.Body; + +namespace Content.Server.EntityConditions.Conditions; + +/// +/// Returns true if this entity has any of the listed metabolizer types. +/// +/// +public sealed partial class MetabolizerTypeEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + if (entity.Comp.MetabolizerTypes == null) + return; + + args.Result = entity.Comp.MetabolizerTypes.Overlaps(args.Condition.Type); + } +} diff --git a/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs new file mode 100644 index 0000000000..033704ffcd --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs @@ -0,0 +1,22 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Atmos; + +namespace Content.Server.EntityEffects.Effects.Atmos; + +/// +/// This effect adjusts a gas at the tile this entity is currently on. +/// The amount changed is modified by scale. +/// +/// +public sealed partial class CreateGasEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var tileMix = _atmosphere.GetContainingMixture(entity.AsNullable(), false, true); + + tileMix?.AdjustMoles(args.Effect.Gas, args.Scale * args.Effect.Moles); + } +} diff --git a/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs new file mode 100644 index 0000000000..65c818f143 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs @@ -0,0 +1,25 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Atmos; + +namespace Content.Server.EntityEffects.Effects.Atmos; + +/// +/// Adds a number of FireStacks modified by scale to this entity. +/// The amount of FireStacks added is modified by scale. +/// +/// +public sealed partial class FlammableEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly FlammableSystem _flammable = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + // The multiplier is determined by if the entity is already on fire, and if the multiplier for existing FireStacks has a value. + // If both of these are true, we use the MultiplierOnExisting value, otherwise we use the standard Multiplier. + var multiplier = entity.Comp.FireStacks == 0f || args.Effect.MultiplierOnExisting == null ? args.Effect.Multiplier : args.Effect.MultiplierOnExisting.Value; + + _flammable.AdjustFireStacks(entity, args.Scale * multiplier, entity.Comp); + } +} diff --git a/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs new file mode 100644 index 0000000000..de90656c66 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs @@ -0,0 +1,23 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Atmos; + +namespace Content.Server.EntityEffects.Effects.Atmos; + +/// +/// Sets this entity on fire. +/// +/// +public sealed partial class IngiteEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly FlammableSystem _flammable = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + // TODO: Proper BodySystem Metabolism Effect relay... + // TODO: If this fucks over downstream shitmed, I give you full approval to use whatever shitcode method you need to fix it. Metabolism is awful. + _flammable.Ignite(entity, entity, flammable: entity.Comp); + } +} + diff --git a/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs b/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs new file mode 100644 index 0000000000..0cbf0b3864 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs @@ -0,0 +1,20 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Body; + +namespace Content.Server.EntityEffects.Effects.Body; + +/// +/// This effect adjusts a respirator's saturation value. +/// The saturation adjustment is modified by scale. +/// +/// +public sealed partial class OxygenateEntityEffectsSystem : EntityEffectSystem +{ + [Dependency] private readonly RespiratorSystem _respirator = default!; + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _respirator.UpdateSaturation(entity, args.Scale * args.Effect.Factor, entity.Comp); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs new file mode 100644 index 0000000000..64f61f5b11 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs @@ -0,0 +1,20 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustHealthEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + entity.Comp.MutationLevel += args.Effect.Amount * entity.Comp.MutationMod; + _plantHolder.CheckHealth(entity, entity.Comp); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs new file mode 100644 index 0000000000..f35ff25b25 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs @@ -0,0 +1,20 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustMutationLevelEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + entity.Comp.Health += args.Effect.Amount; + _plantHolder.CheckHealth(entity, entity.Comp); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs new file mode 100644 index 0000000000..3163ee374c --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustMutationModEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + entity.Comp.MutationMod += args.Effect.Amount; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs new file mode 100644 index 0000000000..56c016700d --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustNutritionEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _plantHolder.AdjustNutrient(entity, args.Effect.Amount, entity); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs new file mode 100644 index 0000000000..0495034b38 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustPestsEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + entity.Comp.PestLevel += args.Effect.Amount; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs new file mode 100644 index 0000000000..ebe5c83181 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs @@ -0,0 +1,19 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustPotencyEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + _plantHolder.EnsureUniqueSeed(entity, entity.Comp); + entity.Comp.Seed.Potency = Math.Max(entity.Comp.Seed.Potency + args.Effect.Amount, 1); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs new file mode 100644 index 0000000000..31dc328977 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustToxinsEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + entity.Comp.Toxins += args.Effect.Amount; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs new file mode 100644 index 0000000000..706eeb2ffe --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustWaterEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _plantHolder.AdjustWater(entity, args.Effect.Amount, entity.Comp); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs new file mode 100644 index 0000000000..34aa51e4ff --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustWeedsEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + entity.Comp.WeedLevel += args.Effect.Amount; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs new file mode 100644 index 0000000000..b0faa6255e --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs @@ -0,0 +1,19 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAffectGrowthEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + _plantHolder.AffectGrowth(entity, (int)args.Effect.Amount, entity); + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs new file mode 100644 index 0000000000..3d82f74b11 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs @@ -0,0 +1,122 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +/// +/// This system mutates an inputted stat for a PlantHolder, only works for floats, integers, and bools. +/// +/// +public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSystem +{ + // TODO: This is awful. I do not have the strength to refactor this. I want it gone. + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + var effect = args.Effect; + var member = entity.Comp.Seed.GetType().GetField(args.Effect.TargetValue); + + if (member == null) + { + Log.Error($"{ effect.GetType().Name } Error: Member { args.Effect.TargetValue} not found on { entity.Comp.Seed.GetType().Name }. Did you misspell it?"); + return; + } + + var currentValObj = member.GetValue(entity.Comp.Seed); + if (currentValObj == null) + return; + + if (member.FieldType == typeof(float)) + { + var floatVal = (float)currentValObj; + MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps); + member.SetValue(entity.Comp.Seed, floatVal); + } + else if (member.FieldType == typeof(int)) + { + var intVal = (int)currentValObj; + MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps); + member.SetValue(entity.Comp.Seed, intVal); + } + else if (member.FieldType == typeof(bool)) + { + var boolVal = (bool)currentValObj; + boolVal = !boolVal; + member.SetValue(entity.Comp.Seed, boolVal); + } + } + + // Mutate reference 'val' between 'min' and 'max' by pretending the value + // is representable by a thermometer code with 'bits' number of bits and + // randomly flipping some of them. + private void MutateFloat(ref float val, float min, float max, int bits) + { + if (min == max) + { + val = min; + return; + } + + // Starting number of bits that are high, between 0 and bits. + // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. + int valInt = (int)MathF.Round((val - min) / (max - min) * bits); + // val may be outside the range of min/max due to starting prototype values, so clamp. + valInt = Math.Clamp(valInt, 0, bits); + + // Probability that the bit flip increases n. + // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it. + // In other words, it tends to go to the middle. + float probIncrease = 1 - (float)valInt / bits; + int valIntMutated; + if (_random.Prob(probIncrease)) + { + valIntMutated = valInt + 1; + } + else + { + valIntMutated = valInt - 1; + } + + // Set value based on mutated thermometer code. + float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max); + val = valMutated; + } + + private void MutateInt(ref int val, int min, int max, int bits) + { + if (min == max) + { + val = min; + return; + } + + // Starting number of bits that are high, between 0 and bits. + // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. + int valInt = (int)MathF.Round((val - min) / (max - min) * bits); + // val may be outside the range of min/max due to starting prototype values, so clamp. + valInt = Math.Clamp(valInt, 0, bits); + + // Probability that the bit flip increases n. + // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it. + // In other words, it tends to go to the middle. + float probIncrease = 1 - (float)valInt / bits; + int valMutated; + if (_random.Prob(probIncrease)) + { + valMutated = val + 1; + } + else + { + valMutated = val - 1; + } + + valMutated = Math.Clamp(valMutated, min, max); + val = valMutated; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs new file mode 100644 index 0000000000..710bce24dd --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs @@ -0,0 +1,30 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + var deviation = 0; + var seed = entity.Comp.Seed; + if (seed == null) + return; + if (entity.Comp.Age > seed.Maturation) + deviation = (int) Math.Max(seed.Maturation - 1, entity.Comp.Age - _random.Next(7, 10)); + else + deviation = (int) (seed.Maturation / seed.GrowthStages); + entity.Comp.Age -= deviation; + entity.Comp.LastProduce = entity.Comp.Age; + entity.Comp.SkipAging++; + entity.Comp.ForceUpdate = true; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs new file mode 100644 index 0000000000..1661c501be --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs @@ -0,0 +1,31 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Server.Popups; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; +using Content.Shared.Popups; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable) + return; + + if (entity.Comp.Seed.Seedless) + return; + + _plantHolder.EnsureUniqueSeed(entity, entity.Comp); + _popup.PopupEntity( + Loc.GetString("botany-plant-seedsdestroyed"), + entity, + PopupType.SmallCaution + ); + entity.Comp.Seed.Seedless = true; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs new file mode 100644 index 0000000000..f6aebde465 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs @@ -0,0 +1,31 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantDiethylamineEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable) + return; + + if (_random.Prob(0.1f)) + { + _plantHolder.EnsureUniqueSeed(entity, entity); + entity.Comp.Seed!.Lifespan++; + } + + if (_random.Prob(0.1f)) + { + _plantHolder.EnsureUniqueSeed(entity, entity); + entity.Comp.Seed!.Endurance++; + } + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs new file mode 100644 index 0000000000..8a073392e1 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable) + return; + + entity.Comp.Seed.Viable = true; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs new file mode 100644 index 0000000000..4d724be244 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs @@ -0,0 +1,26 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Server.Popups; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable) + return; + + if (!entity.Comp.Seed.Seedless) + return; + + _plantHolder.EnsureUniqueSeed(entity, entity.Comp); + _popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), entity); + entity.Comp.Seed.Seedless = false; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs new file mode 100644 index 0000000000..68ea3319ef --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs @@ -0,0 +1,41 @@ +using Content.Server.Botany.Components; +using Content.Server.Botany.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes; + +/// +/// This effect directly increases the potency of a PlantHolder's plant provided it exists and isn't dead. +/// Potency directly correlates to the size of the plant's produce. +/// +/// +public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PlantHolderSystem _plantHolder = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Dead) + return; + + if (entity.Comp.Seed.Potency < args.Effect.PotencyLimit) + { + _plantHolder.EnsureUniqueSeed(entity, entity.Comp); + entity.Comp.Seed.Potency = Math.Min(entity.Comp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit); + + if (entity.Comp.Seed.Potency > args.Effect.PotencySeedlessThreshold) + { + entity.Comp.Seed.Seedless = true; + } + } + else if (entity.Comp.Seed.Yield > 1 && _random.Prob(0.1f)) + { + // Too much of a good thing reduces yield + _plantHolder.EnsureUniqueSeed(entity, entity.Comp); + entity.Comp.Seed.Yield--; + } + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs new file mode 100644 index 0000000000..120ae6e881 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs @@ -0,0 +1,43 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany; + +public sealed partial class PlantMutateChemicalsEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null) + return; + + var chemicals = entity.Comp.Seed.Chemicals; + var randomChems = _proto.Index(args.Effect.RandomPickBotanyReagent).Fills; + + // Add a random amount of a random chemical to this set of chemicals + var pick = _random.Pick(randomChems); + var chemicalId = _random.Pick(pick.Reagents); + var amount = _random.Next(1, (int)pick.Quantity); + var seedChemQuantity = new SeedChemQuantity(); + if (chemicals.ContainsKey(chemicalId)) + { + seedChemQuantity.Min = chemicals[chemicalId].Min; + seedChemQuantity.Max = chemicals[chemicalId].Max + amount; + } + else + { + seedChemQuantity.Min = 1; + seedChemQuantity.Max = 1 + amount; + seedChemQuantity.Inherent = false; + } + var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max); + seedChemQuantity.PotencyDivisor = potencyDivisor; + chemicals[chemicalId] = seedChemQuantity; + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs new file mode 100644 index 0000000000..e2376ba186 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs @@ -0,0 +1,53 @@ +using System.Linq; +using Content.Server.Botany.Components; +using Content.Shared.Atmos; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany; + +public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null) + return; + + var gasses = entity.Comp.Seed.ExudeGasses; + + // Add a random amount of a random gas to this gas dictionary + float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue); + var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); + + if (!gasses.TryAdd(gas, amount)) + { + gasses[gas] += amount; + } + } +} + +public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null) + return; + + var gasses = entity.Comp.Seed.ConsumeGasses; + + // Add a random amount of a random gas to this gas dictionary + var amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue); + var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); + + if (!gasses.TryAdd(gas, amount)) + { + gasses[gas] += amount; + } + } +} + diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs new file mode 100644 index 0000000000..95d7f97bbe --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs @@ -0,0 +1,25 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany; + +namespace Content.Server.EntityEffects.Effects.Botany; + +public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null) + return; + + switch (entity.Comp.Seed.HarvestRepeat) + { + case HarvestType.NoRepeat: + entity.Comp.Seed.HarvestRepeat = HarvestType.Repeat; + break; + case HarvestType.Repeat: + entity.Comp.Seed.HarvestRepeat = HarvestType.SelfHarvest; + break; + } + } +} diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs new file mode 100644 index 0000000000..c26e1e08cf --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs @@ -0,0 +1,31 @@ +using Content.Server.Botany; +using Content.Server.Botany.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Botany; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.EntityEffects.Effects.Botany; + +public sealed partial class PlantMutateSpeciesChangeEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (entity.Comp.Seed == null || entity.Comp.Seed.MutationPrototypes.Count == 0) + return; + + var targetProto = _random.Pick(entity.Comp.Seed.MutationPrototypes); + _proto.TryIndex(targetProto, out SeedPrototype? protoSeed); + + if (protoSeed == null) + { + Log.Error($"Seed prototype could not be found: {targetProto}!"); + return; + } + + entity.Comp.Seed = entity.Comp.Seed.SpeciesChange(protoSeed); + } +} diff --git a/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs new file mode 100644 index 0000000000..05ab857267 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs @@ -0,0 +1,22 @@ +using Content.Server.Chat.Systems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Makes this entity emote. +/// +/// +public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly ChatSystem _chat = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (args.Effect.ShowInChat) + _chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force); + else + _chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId); + } +} diff --git a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs new file mode 100644 index 0000000000..c623b25857 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs @@ -0,0 +1,42 @@ +using Content.Server.Ghost.Roles.Components; +using Content.Server.Speech.Components; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects; +using Content.Shared.Mind.Components; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Makes this entity sentient. Allows ghost to take it over if it's not already occupied. +/// Optionally also allows this entity to speak. +/// +/// +public sealed partial class MakeSentientEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + // Let affected entities speak normally to make this effect different from, say, the "random sentience" event + // This also works on entities that already have a mind + // We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect + if (args.Effect.AllowSpeech) + { + RemComp(entity); + // TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell. + RemComp(entity); + } + + // Stops from adding a ghost role to things like people who already have a mind + if (TryComp(entity, out var mindContainer) && mindContainer.HasMind) + return; + + // Don't add a ghost role to things that already have ghost roles + if (TryComp(entity, out GhostRoleComponent? ghostRole)) + return; + + ghostRole = AddComp(entity); + EnsureComp(entity); + + ghostRole.RoleName = entity.Comp.EntityName; + ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description"); + } +} diff --git a/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs new file mode 100644 index 0000000000..5f19bcc50b --- /dev/null +++ b/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs @@ -0,0 +1,19 @@ +using Content.Server.Polymorph.Components; +using Content.Server.Polymorph.Systems; +using Content.Shared.EntityEffects; + +namespace Content.Server.EntityEffects.Effects; + +/// +/// Polymorphs this entity into another entity. +/// +/// +public sealed partial class PolymorphEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly PolymorphSystem _polymorph = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _polymorph.PolymorphEntity(entity, args.Effect.Prototype); + } +} diff --git a/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs new file mode 100644 index 0000000000..e5ef488de8 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs @@ -0,0 +1,51 @@ +using Content.Server.Fluids.EntitySystems; +using Content.Server.Spreader; +using Content.Shared.Chemistry.Components; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Solution; +using Content.Shared.Maps; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; + +namespace Content.Server.EntityEffects.Effects.Solution; + +/// +/// This effect creates smoke at this solution's position. +/// The amount of smoke created is modified by scale. +/// +/// +public sealed partial class AreaReactionEntityEffectsSystem : EntityEffectSystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; + [Dependency] private readonly SmokeSystem _smoke = default!; + [Dependency] private readonly SpreaderSystem _spreader = default!; + [Dependency] private readonly TurfSystem _turf = default!; + + // TODO: A sane way to make Smoke without a solution. + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var xform = Transform(entity); + var mapCoords = _xform.GetMapCoordinates(entity); + var spreadAmount = (int) Math.Max(0, Math.Ceiling(args.Scale / args.Effect.OverflowThreshold)); + var effect = args.Effect; + + if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) || + !_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef)) + return; + + if (_spreader.RequiresFloorToSpread(effect.PrototypeId.ToString()) && _turf.IsSpace(tileRef)) + return; + + var coords = _map.MapToGrid(gridUid, mapCoords); + var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid()); + + _smoke.StartSmoke(ent, entity.Comp.Solution, args.Effect.Duration, spreadAmount); + + _audio.PlayPvs(args.Effect.Sound, entity, AudioParams.Default.WithVariation(0.25f)); + } +} diff --git a/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs new file mode 100644 index 0000000000..55fc120051 --- /dev/null +++ b/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs @@ -0,0 +1,28 @@ +using Content.Server.Explosion.EntitySystems; +using Content.Shared.EntityEffects; +using Content.Shared.EntityEffects.Effects.Transform; + +namespace Content.Server.EntityEffects.Effects.Transform; + +/// +/// Creates an explosion at this entity's position. +/// Intensity is modified by scale. +/// +/// +public sealed partial class ExplosionEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly ExplosionSystem _explosion = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var intensity = MathF.Min(args.Effect.IntensityPerUnit * args.Scale, args.Effect.MaxTotalIntensity); + + _explosion.QueueExplosion( + entity, + args.Effect.ExplosionType, + intensity, + args.Effect.IntensitySlope, + args.Effect.MaxIntensity, + args.Effect.TileBreakScale); + } +} diff --git a/Content.Server/EntityEffects/EntityEffectSystem.cs b/Content.Server/EntityEffects/EntityEffectSystem.cs deleted file mode 100644 index 238ef4849d..0000000000 --- a/Content.Server/EntityEffects/EntityEffectSystem.cs +++ /dev/null @@ -1,976 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Content.Server.Atmos.EntitySystems; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; -using Content.Server.Botany.Components; -using Content.Server.Botany.Systems; -using Content.Server.Botany; -using Content.Server.Chat.Systems; -using Content.Server.Emp; -using Content.Server.Explosion.EntitySystems; -using Content.Server.Fluids.EntitySystems; -using Content.Server.Ghost.Roles.Components; -using Content.Server.Polymorph.Components; -using Content.Server.Polymorph.Systems; -using Content.Server.Speech.Components; -using Content.Server.Spreader; -using Content.Server.Temperature.Components; -using Content.Server.Temperature.Systems; -using Content.Server.Zombies; -using Content.Shared.Atmos; -using Content.Shared.Atmos.Components; -using Content.Shared.Body.Components; -using Content.Shared.Coordinates.Helpers; -using Content.Shared.EntityEffects.EffectConditions; -using Content.Shared.EntityEffects.Effects.PlantMetabolism; -using Content.Shared.EntityEffects.Effects; -using Content.Shared.EntityEffects; -using Content.Shared.Flash; -using Content.Shared.Maps; -using Content.Shared.Medical; -using Content.Shared.Mind.Components; -using Content.Shared.Popups; -using Content.Shared.Random; -using Content.Shared.Traits.Assorted; -using Content.Shared.Zombies; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -using TemperatureCondition = Content.Shared.EntityEffects.EffectConditions.Temperature; // disambiguate the namespace -using PolymorphEffect = Content.Shared.EntityEffects.Effects.Polymorph; - -namespace Content.Server.EntityEffects; - -public sealed class EntityEffectSystem : EntitySystem -{ - private static readonly ProtoId RandomPickBotanyReagent = "RandomPickBotanyReagent"; - - [Dependency] private readonly AtmosphereSystem _atmosphere = default!; - [Dependency] private readonly BloodstreamSystem _bloodstream = default!; - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly EmpSystem _emp = default!; - [Dependency] private readonly ExplosionSystem _explosion = default!; - [Dependency] private readonly FlammableSystem _flammable = default!; - [Dependency] private readonly SharedFlashSystem _flash = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IPrototypeManager _protoManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedMapSystem _map = default!; - [Dependency] private readonly MutationSystem _mutation = default!; - [Dependency] private readonly NarcolepsySystem _narcolepsy = default!; - [Dependency] private readonly PlantHolderSystem _plantHolder = default!; - [Dependency] private readonly PolymorphSystem _polymorph = default!; - [Dependency] private readonly RespiratorSystem _respirator = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPointLightSystem _pointLight = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly SmokeSystem _smoke = default!; - [Dependency] private readonly SpreaderSystem _spreader = default!; - [Dependency] private readonly TemperatureSystem _temperature = default!; - [Dependency] private readonly SharedTransformSystem _xform = default!; - [Dependency] private readonly VomitSystem _vomit = default!; - [Dependency] private readonly TurfSystem _turf = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent>(OnCheckTemperature); - SubscribeLocalEvent>(OnCheckBreathing); - SubscribeLocalEvent>(OnCheckOrganType); - SubscribeLocalEvent>(OnExecutePlantAdjustHealth); - SubscribeLocalEvent>(OnExecutePlantAdjustMutationLevel); - SubscribeLocalEvent>(OnExecutePlantAdjustMutationMod); - SubscribeLocalEvent>(OnExecutePlantAdjustNutrition); - SubscribeLocalEvent>(OnExecutePlantAdjustPests); - SubscribeLocalEvent>(OnExecutePlantAdjustPotency); - SubscribeLocalEvent>(OnExecutePlantAdjustToxins); - SubscribeLocalEvent>(OnExecutePlantAdjustWater); - SubscribeLocalEvent>(OnExecutePlantAdjustWeeds); - SubscribeLocalEvent>(OnExecutePlantAffectGrowth); - SubscribeLocalEvent>(OnExecutePlantChangeStat); - SubscribeLocalEvent>(OnExecutePlantCryoxadone); - SubscribeLocalEvent>(OnExecutePlantDestroySeeds); - SubscribeLocalEvent>(OnExecutePlantDiethylamine); - SubscribeLocalEvent>(OnExecutePlantPhalanximine); - SubscribeLocalEvent>(OnExecutePlantRestoreSeeds); - SubscribeLocalEvent>(OnExecuteRobustHarvest); - SubscribeLocalEvent>(OnExecuteAdjustTemperature); - SubscribeLocalEvent>(OnExecuteAreaReactionEffect); - SubscribeLocalEvent>(OnExecuteCauseZombieInfection); - SubscribeLocalEvent>(OnExecuteChemCleanBloodstream); - SubscribeLocalEvent>(OnExecuteChemVomit); - SubscribeLocalEvent>(OnExecuteCreateEntityReactionEffect); - SubscribeLocalEvent>(OnExecuteCreateGas); - SubscribeLocalEvent>(OnExecuteCureZombieInfection); - SubscribeLocalEvent>(OnExecuteEmote); - SubscribeLocalEvent>(OnExecuteEmpReactionEffect); - SubscribeLocalEvent>(OnExecuteExplosionReactionEffect); - SubscribeLocalEvent>(OnExecuteFlammableReaction); - SubscribeLocalEvent>(OnExecuteFlashReactionEffect); - SubscribeLocalEvent>(OnExecuteIgnite); - SubscribeLocalEvent>(OnExecuteMakeSentient); - SubscribeLocalEvent>(OnExecuteModifyBleedAmount); - SubscribeLocalEvent>(OnExecuteModifyBloodLevel); - SubscribeLocalEvent>(OnExecuteModifyLungGas); - SubscribeLocalEvent>(OnExecuteOxygenate); - SubscribeLocalEvent>(OnExecutePlantMutateChemicals); - SubscribeLocalEvent>(OnExecutePlantMutateConsumeGasses); - SubscribeLocalEvent>(OnExecutePlantMutateExudeGasses); - SubscribeLocalEvent>(OnExecutePlantMutateHarvest); - SubscribeLocalEvent>(OnExecutePlantSpeciesChange); - SubscribeLocalEvent>(OnExecutePolymorph); - SubscribeLocalEvent>(OnExecuteResetNarcolepsy); - } - - private void OnCheckTemperature(ref CheckEntityEffectConditionEvent args) - { - args.Result = false; - if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp)) - { - if (temp.CurrentTemperature >= args.Condition.Min && temp.CurrentTemperature <= args.Condition.Max) - args.Result = true; - } - } - - private void OnCheckBreathing(ref CheckEntityEffectConditionEvent args) - { - if (!TryComp(args.Args.TargetEntity, out RespiratorComponent? respiratorComp)) - { - args.Result = !args.Condition.IsBreathing; - return; - } - - var breathingState = _respirator.IsBreathing((args.Args.TargetEntity, respiratorComp)); - args.Result = args.Condition.IsBreathing == breathingState; - } - - private void OnCheckOrganType(ref CheckEntityEffectConditionEvent args) - { - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - if (reagentArgs.OrganEntity == null) - { - args.Result = false; - return; - } - - args.Result = OrganCondition(args.Condition, reagentArgs.OrganEntity.Value); - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - public bool OrganCondition(OrganType condition, Entity metabolizer) - { - metabolizer.Comp ??= EntityManager.GetComponentOrNull(metabolizer.Owner); - if (metabolizer.Comp != null - && metabolizer.Comp.MetabolizerTypes != null - && metabolizer.Comp.MetabolizerTypes.Contains(condition.Type)) - return condition.ShouldHave; - return !condition.ShouldHave; - } - - /// - /// Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default. - /// - /// The entity holding the plant - /// The plant holder component - /// The entity manager - /// Whether to check if it has an alive plant or not - /// - private bool CanMetabolizePlant(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent, - bool mustHaveAlivePlant = true, bool mustHaveMutableSeed = false) - { - plantHolderComponent = null; - - if (!TryComp(plantHolder, out plantHolderComponent)) - return false; - - if (mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead)) - return false; - - if (mustHaveMutableSeed && (plantHolderComponent.Seed == null || plantHolderComponent.Seed.Immutable)) - return false; - - return true; - } - - private void OnExecutePlantAdjustHealth(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - plantHolderComp.Health += args.Effect.Amount; - _plantHolder.CheckHealth(args.Args.TargetEntity, plantHolderComp); - } - - private void OnExecutePlantAdjustMutationLevel(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - plantHolderComp.MutationLevel += args.Effect.Amount * plantHolderComp.MutationMod; - } - - private void OnExecutePlantAdjustMutationMod(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - plantHolderComp.MutationMod += args.Effect.Amount; - } - - private void OnExecutePlantAdjustNutrition(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false)) - return; - - _plantHolder.AdjustNutrient(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp); - } - - private void OnExecutePlantAdjustPests(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - plantHolderComp.PestLevel += args.Effect.Amount; - } - - private void OnExecutePlantAdjustPotency(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - if (plantHolderComp.Seed == null) - return; - - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + args.Effect.Amount, 1); - } - - private void OnExecutePlantAdjustToxins(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - plantHolderComp.Toxins += args.Effect.Amount; - } - - private void OnExecutePlantAdjustWater(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false)) - return; - - _plantHolder.AdjustWater(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp); - } - - private void OnExecutePlantAdjustWeeds(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - plantHolderComp.WeedLevel += args.Effect.Amount; - } - - private void OnExecutePlantAffectGrowth(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - _plantHolder.AffectGrowth(args.Args.TargetEntity, (int) args.Effect.Amount, plantHolderComp); - } - - // Mutate reference 'val' between 'min' and 'max' by pretending the value - // is representable by a thermometer code with 'bits' number of bits and - // randomly flipping some of them. - private void MutateFloat(ref float val, float min, float max, int bits) - { - if (min == max) - { - val = min; - return; - } - - // Starting number of bits that are high, between 0 and bits. - // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. - int valInt = (int)MathF.Round((val - min) / (max - min) * bits); - // val may be outside the range of min/max due to starting prototype values, so clamp. - valInt = Math.Clamp(valInt, 0, bits); - - // Probability that the bit flip increases n. - // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it. - // In other words, it tends to go to the middle. - float probIncrease = 1 - (float)valInt / bits; - int valIntMutated; - if (_random.Prob(probIncrease)) - { - valIntMutated = valInt + 1; - } - else - { - valIntMutated = valInt - 1; - } - - // Set value based on mutated thermometer code. - float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max); - val = valMutated; - } - - private void MutateInt(ref int val, int min, int max, int bits) - { - if (min == max) - { - val = min; - return; - } - - // Starting number of bits that are high, between 0 and bits. - // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded. - int valInt = (int)MathF.Round((val - min) / (max - min) * bits); - // val may be outside the range of min/max due to starting prototype values, so clamp. - valInt = Math.Clamp(valInt, 0, bits); - - // Probability that the bit flip increases n. - // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it. - // In other words, it tends to go to the middle. - float probIncrease = 1 - (float)valInt / bits; - int valMutated; - if (_random.Prob(probIncrease)) - { - valMutated = val + 1; - } - else - { - valMutated = val - 1; - } - - valMutated = Math.Clamp(valMutated, min, max); - val = valMutated; - } - - private void OnExecutePlantChangeStat(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - if (plantHolderComp.Seed == null) - return; - - var member = plantHolderComp.Seed.GetType().GetField(args.Effect.TargetValue); - - if (member == null) - { - _mutation.Log.Error(args.Effect.GetType().Name + " Error: Member " + args.Effect.TargetValue + " not found on " + plantHolderComp.Seed.GetType().Name + ". Did you misspell it?"); - return; - } - - var currentValObj = member.GetValue(plantHolderComp.Seed); - if (currentValObj == null) - return; - - if (member.FieldType == typeof(float)) - { - var floatVal = (float)currentValObj; - MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps); - member.SetValue(plantHolderComp.Seed, floatVal); - } - else if (member.FieldType == typeof(int)) - { - var intVal = (int)currentValObj; - MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps); - member.SetValue(plantHolderComp.Seed, intVal); - } - else if (member.FieldType == typeof(bool)) - { - var boolVal = (bool)currentValObj; - boolVal = !boolVal; - member.SetValue(plantHolderComp.Seed, boolVal); - } - } - - private void OnExecutePlantCryoxadone(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - var deviation = 0; - var seed = plantHolderComp.Seed; - if (seed == null) - return; - if (plantHolderComp.Age > seed.Maturation) - deviation = (int) Math.Max(seed.Maturation - 1, plantHolderComp.Age - _random.Next(7, 10)); - else - deviation = (int) (seed.Maturation / seed.GrowthStages); - plantHolderComp.Age -= deviation; - plantHolderComp.LastProduce = plantHolderComp.Age; - plantHolderComp.SkipAging++; - plantHolderComp.ForceUpdate = true; - } - - private void OnExecutePlantDestroySeeds(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true)) - return; - - if (plantHolderComp.Seed!.Seedless == false) - { - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - _popup.PopupEntity( - Loc.GetString("botany-plant-seedsdestroyed"), - args.Args.TargetEntity, - PopupType.SmallCaution - ); - plantHolderComp.Seed.Seedless = true; - } - } - - private void OnExecutePlantDiethylamine(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true)) - return; - - if (_random.Prob(0.1f)) - { - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - plantHolderComp.Seed!.Lifespan++; - } - - if (_random.Prob(0.1f)) - { - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - plantHolderComp.Seed!.Endurance++; - } - } - - private void OnExecutePlantPhalanximine(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true)) - return; - - plantHolderComp.Seed!.Viable = true; - } - - private void OnExecutePlantRestoreSeeds(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true)) - return; - - if (plantHolderComp.Seed!.Seedless) - { - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - _popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.Args.TargetEntity); - plantHolderComp.Seed.Seedless = false; - } - } - - private void OnExecuteRobustHarvest(ref ExecuteEntityEffectEvent args) - { - if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp)) - return; - - if (plantHolderComp.Seed == null) - return; - - if (plantHolderComp.Seed.Potency < args.Effect.PotencyLimit) - { - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit); - - if (plantHolderComp.Seed.Potency > args.Effect.PotencySeedlessThreshold) - { - plantHolderComp.Seed.Seedless = true; - } - } - else if (plantHolderComp.Seed.Yield > 1 && _random.Prob(0.1f)) - { - // Too much of a good thing reduces yield - _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp); - plantHolderComp.Seed.Yield--; - } - } - - private void OnExecuteAdjustTemperature(ref ExecuteEntityEffectEvent args) - { - if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp)) - { - var amount = args.Effect.Amount; - - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - amount *= reagentArgs.Scale.Float(); - } - - _temperature.ChangeHeat(args.Args.TargetEntity, amount, true, temp); - } - } - - private void OnExecuteAreaReactionEffect(ref ExecuteEntityEffectEvent args) - { - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - if (reagentArgs.Source == null) - return; - - var spreadAmount = (int) Math.Max(0, Math.Ceiling((reagentArgs.Quantity / args.Effect.OverflowThreshold).Float())); - var splitSolution = reagentArgs.Source.SplitSolution(reagentArgs.Source.Volume); - var transform = Comp(reagentArgs.TargetEntity); - var mapCoords = _xform.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform); - - if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) || - !_map.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef)) - { - return; - } - - if (_spreader.RequiresFloorToSpread(args.Effect.PrototypeId) && _turf.IsSpace(tileRef)) - return; - - var coords = _map.MapToGrid(gridUid, mapCoords); - var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid()); - - _smoke.StartSmoke(ent, splitSolution, args.Effect.Duration, spreadAmount); - - _audio.PlayPvs(args.Effect.Sound, reagentArgs.TargetEntity, AudioParams.Default.WithVariation(0.25f)); - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - private void OnExecuteCauseZombieInfection(ref ExecuteEntityEffectEvent args) - { - EnsureComp(args.Args.TargetEntity); - EnsureComp(args.Args.TargetEntity); - } - - private void OnExecuteChemCleanBloodstream(ref ExecuteEntityEffectEvent args) - { - var cleanseRate = args.Effect.CleanseRate; - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - if (reagentArgs.Source == null || reagentArgs.Reagent == null) - return; - - cleanseRate *= reagentArgs.Scale.Float(); - _bloodstream.FlushChemicals(args.Args.TargetEntity, reagentArgs.Reagent, cleanseRate); - } - else - { - _bloodstream.FlushChemicals(args.Args.TargetEntity, null, cleanseRate); - } - } - - private void OnExecuteChemVomit(ref ExecuteEntityEffectEvent args) - { - if (args.Args is EntityEffectReagentArgs reagentArgs) - if (reagentArgs.Scale != 1f) - return; - - _vomit.Vomit(args.Args.TargetEntity, args.Effect.ThirstAmount, args.Effect.HungerAmount); - } - - private void OnExecuteCreateEntityReactionEffect(ref ExecuteEntityEffectEvent args) - { - var transform = Comp(args.Args.TargetEntity); - var quantity = (int)args.Effect.Number; - if (args.Args is EntityEffectReagentArgs reagentArgs) - quantity *= reagentArgs.Quantity.Int(); - - for (var i = 0; i < quantity; i++) - { - var uid = Spawn(args.Effect.Entity, _xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform)); - _xform.AttachToGridOrMap(uid); - - // TODO figure out how to properly spawn inside of containers - // e.g. cheese: - // if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user. - // if reaction happens in a backpack, should insert cheese into backpack. - // --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players). - // if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that. - // presumably having cheese materialize inside of your blood would have "disadvantages". - } - } - - private void OnExecuteCreateGas(ref ExecuteEntityEffectEvent args) - { - var tileMix = _atmosphere.GetContainingMixture(args.Args.TargetEntity, false, true); - - if (tileMix != null) - { - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - tileMix.AdjustMoles(args.Effect.Gas, reagentArgs.Quantity.Float() * args.Effect.Multiplier); - } - else - { - tileMix.AdjustMoles(args.Effect.Gas, args.Effect.Multiplier); - } - } - } - - private void OnExecuteCureZombieInfection(ref ExecuteEntityEffectEvent args) - { - if (HasComp(args.Args.TargetEntity)) - return; - - RemComp(args.Args.TargetEntity); - RemComp(args.Args.TargetEntity); - - if (args.Effect.Innoculate) - { - EnsureComp(args.Args.TargetEntity); - } - } - - private void OnExecuteEmote(ref ExecuteEntityEffectEvent args) - { - if (args.Effect.EmoteId == null) - return; - - if (args.Effect.ShowInChat) - _chat.TryEmoteWithChat(args.Args.TargetEntity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force); - else - _chat.TryEmoteWithoutChat(args.Args.TargetEntity, args.Effect.EmoteId); - } - - private void OnExecuteEmpReactionEffect(ref ExecuteEntityEffectEvent args) - { - var transform = Comp(args.Args.TargetEntity); - - var range = args.Effect.EmpRangePerUnit; - - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - range = MathF.Min((float) (reagentArgs.Quantity * args.Effect.EmpRangePerUnit), args.Effect.EmpMaxRange); - } - - _emp.EmpPulse(_xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform), - range, - args.Effect.EnergyConsumption, - args.Effect.DisableDuration); - } - - private void OnExecuteExplosionReactionEffect(ref ExecuteEntityEffectEvent args) - { - var intensity = args.Effect.IntensityPerUnit; - - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - intensity = MathF.Min((float) reagentArgs.Quantity * args.Effect.IntensityPerUnit, args.Effect.MaxTotalIntensity); - } - - _explosion.QueueExplosion( - args.Args.TargetEntity, - args.Effect.ExplosionType, - intensity, - args.Effect.IntensitySlope, - args.Effect.MaxIntensity, - args.Effect.TileBreakScale); - } - - private void OnExecuteFlammableReaction(ref ExecuteEntityEffectEvent args) - { - if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable)) - return; - - // Sets the multiplier for FireStacks to MultiplierOnExisting is 0 or greater and target already has FireStacks - var multiplier = flammable.FireStacks != 0f && args.Effect.MultiplierOnExisting >= 0 ? args.Effect.MultiplierOnExisting : args.Effect.Multiplier; - var quantity = 1f; - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - quantity = reagentArgs.Quantity.Float(); - _flammable.AdjustFireStacks(args.Args.TargetEntity, quantity * multiplier, flammable); - if (reagentArgs.Reagent != null) - reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity); - } - else - { - _flammable.AdjustFireStacks(args.Args.TargetEntity, multiplier, flammable); - } - } - - private void OnExecuteFlashReactionEffect(ref ExecuteEntityEffectEvent args) - { - var transform = Comp(args.Args.TargetEntity); - - var range = 1f; - - if (args.Args is EntityEffectReagentArgs reagentArgs) - range = MathF.Min((float)(reagentArgs.Quantity * args.Effect.RangePerUnit), args.Effect.MaxRange); - - _flash.FlashArea( - args.Args.TargetEntity, - null, - range, - args.Effect.Duration, - slowTo: args.Effect.SlowTo, - sound: args.Effect.Sound); - - if (args.Effect.FlashEffectPrototype == null) - return; - - var uid = EntityManager.SpawnEntity(args.Effect.FlashEffectPrototype, _xform.GetMapCoordinates(transform)); - _xform.AttachToGridOrMap(uid); - - if (!TryComp(uid, out var pointLightComp)) - return; - - _pointLight.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp); - } - - private void OnExecuteIgnite(ref ExecuteEntityEffectEvent args) - { - if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable)) - return; - - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - _flammable.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable); - } - else - { - _flammable.Ignite(args.Args.TargetEntity, args.Args.TargetEntity, flammable: flammable); - } - } - - private void OnExecuteMakeSentient(ref ExecuteEntityEffectEvent args) - { - var uid = args.Args.TargetEntity; - - // Let affected entities speak normally to make this effect different from, say, the "random sentience" event - // This also works on entities that already have a mind - // We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect - RemComp(uid); - RemComp(uid); - - // Stops from adding a ghost role to things like people who already have a mind - if (TryComp(uid, out var mindContainer) && mindContainer.HasMind) - { - return; - } - - // Don't add a ghost role to things that already have ghost roles - if (TryComp(uid, out GhostRoleComponent? ghostRole)) - { - return; - } - - ghostRole = AddComp(uid); - EnsureComp(uid); - - var entityData = Comp(uid); - ghostRole.RoleName = entityData.EntityName; - ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description"); - } - - private void OnExecuteModifyBleedAmount(ref ExecuteEntityEffectEvent args) - { - if (TryComp(args.Args.TargetEntity, out var blood)) - { - var amt = args.Effect.Amount; - if (args.Args is EntityEffectReagentArgs reagentArgs) { - if (args.Effect.Scaled) - amt *= reagentArgs.Quantity.Float(); - amt *= reagentArgs.Scale.Float(); - } - - _bloodstream.TryModifyBleedAmount((args.Args.TargetEntity, blood), amt); - } - } - - private void OnExecuteModifyBloodLevel(ref ExecuteEntityEffectEvent args) - { - if (TryComp(args.Args.TargetEntity, out var blood)) - { - var amt = args.Effect.Amount; - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - if (args.Effect.Scaled) - amt *= reagentArgs.Quantity; - amt *= reagentArgs.Scale; - } - - _bloodstream.TryModifyBloodLevel((args.Args.TargetEntity, blood), amt); - } - } - - private void OnExecuteModifyLungGas(ref ExecuteEntityEffectEvent args) - { - LungComponent? lung; - float amount = 1f; - - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - if (!TryComp(reagentArgs.OrganEntity, out var organLung)) - return; - lung = organLung; - amount = reagentArgs.Quantity.Float(); - } - else - { - if (!TryComp(args.Args.TargetEntity, out var organLung)) //Likely needs to be modified to ensure it works correctly - return; - lung = organLung; - } - - if (lung != null) - { - foreach (var (gas, ratio) in args.Effect.Ratios) - { - var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier; - if (quantity < 0) - quantity = Math.Max(quantity, -lung.Air[(int) gas]); - lung.Air.AdjustMoles(gas, quantity); - } - } - } - - private void OnExecuteOxygenate(ref ExecuteEntityEffectEvent args) - { - var multiplier = 1f; - if (args.Args is EntityEffectReagentArgs reagentArgs) - { - multiplier = reagentArgs.Quantity.Float(); - } - - if (TryComp(args.Args.TargetEntity, out var resp)) - { - _respirator.UpdateSaturation(args.Args.TargetEntity, multiplier * args.Effect.Factor, resp); - } - } - - private void OnExecutePlantMutateChemicals(ref ExecuteEntityEffectEvent args) - { - var plantholder = Comp(args.Args.TargetEntity); - - if (plantholder.Seed == null) - return; - - var chemicals = plantholder.Seed.Chemicals; - var randomChems = _protoManager.Index(RandomPickBotanyReagent).Fills; - - // Add a random amount of a random chemical to this set of chemicals - if (randomChems != null) - { - var pick = _random.Pick(randomChems); - var chemicalId = _random.Pick(pick.Reagents); - var amount = _random.Next(1, (int)pick.Quantity); - var seedChemQuantity = new SeedChemQuantity(); - if (chemicals.ContainsKey(chemicalId)) - { - seedChemQuantity.Min = chemicals[chemicalId].Min; - seedChemQuantity.Max = chemicals[chemicalId].Max + amount; - } - else - { - seedChemQuantity.Min = 1; - seedChemQuantity.Max = 1 + amount; - seedChemQuantity.Inherent = false; - } - var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max); - seedChemQuantity.PotencyDivisor = potencyDivisor; - chemicals[chemicalId] = seedChemQuantity; - } - } - - private void OnExecutePlantMutateConsumeGasses(ref ExecuteEntityEffectEvent args) - { - var plantholder = Comp(args.Args.TargetEntity); - - if (plantholder.Seed == null) - return; - - var gasses = plantholder.Seed.ConsumeGasses; - - // Add a random amount of a random gas to this gas dictionary - float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue); - Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); - if (gasses.ContainsKey(gas)) - { - gasses[gas] += amount; - } - else - { - gasses.Add(gas, amount); - } - } - - private void OnExecutePlantMutateExudeGasses(ref ExecuteEntityEffectEvent args) - { - var plantholder = Comp(args.Args.TargetEntity); - - if (plantholder.Seed == null) - return; - - var gasses = plantholder.Seed.ExudeGasses; - - // Add a random amount of a random gas to this gas dictionary - float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue); - Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList()); - if (gasses.ContainsKey(gas)) - { - gasses[gas] += amount; - } - else - { - gasses.Add(gas, amount); - } - } - - private void OnExecutePlantMutateHarvest(ref ExecuteEntityEffectEvent args) - { - var plantholder = Comp(args.Args.TargetEntity); - - if (plantholder.Seed == null) - return; - - if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat) - plantholder.Seed.HarvestRepeat = HarvestType.Repeat; - else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat) - plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest; - } - - private void OnExecutePlantSpeciesChange(ref ExecuteEntityEffectEvent args) - { - var plantholder = Comp(args.Args.TargetEntity); - if (plantholder.Seed == null) - return; - - if (plantholder.Seed.MutationPrototypes.Count == 0) - return; - - var targetProto = _random.Pick(plantholder.Seed.MutationPrototypes); - if (!_protoManager.TryIndex(targetProto, out SeedPrototype? protoSeed)) - { - Log.Error($"Seed prototype could not be found: {targetProto}!"); - return; - } - - plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed); - } - - private void OnExecutePolymorph(ref ExecuteEntityEffectEvent args) - { - // Make it into a prototype - EnsureComp(args.Args.TargetEntity); - _polymorph.PolymorphEntity(args.Args.TargetEntity, args.Effect.PolymorphPrototype); - } - - private void OnExecuteResetNarcolepsy(ref ExecuteEntityEffectEvent args) - { - if (args.Args is EntityEffectReagentArgs reagentArgs) - if (reagentArgs.Scale != 1f) - return; - - _narcolepsy.AdjustNarcolepsyTimer(args.Args.TargetEntity, args.Effect.TimerReset); - } -} diff --git a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs index 13695caff1..7c9d02c561 100644 --- a/Content.Server/Fluids/EntitySystems/SmokeSystem.cs +++ b/Content.Server/Fluids/EntitySystems/SmokeSystem.cs @@ -21,7 +21,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; using System.Linq; - +using Content.Shared.EntityEffects.Effects.Solution; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; namespace Content.Server.Fluids.EntitySystems; @@ -278,11 +278,10 @@ public sealed class SmokeSystem : EntitySystem { if (reagentQuantity.Quantity == FixedPoint2.Zero) continue; - var reagentProto = _prototype.Index(reagentQuantity.Reagent.Prototype); - _reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentProto, reagentQuantity, transferSolution); + _reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentQuantity); if (!blockIngestion) - _reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentProto, reagentQuantity, transferSolution); + _reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentQuantity); } if (blockIngestion) diff --git a/Content.Server/GuideGenerator/ChemistryJsonGenerator.cs b/Content.Server/GuideGenerator/ChemistryJsonGenerator.cs index 8fd088408c..930368255c 100644 --- a/Content.Server/GuideGenerator/ChemistryJsonGenerator.cs +++ b/Content.Server/GuideGenerator/ChemistryJsonGenerator.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; +using Content.Shared.EntityConditions; using Content.Shared.EntityEffects; using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; @@ -42,7 +43,7 @@ public sealed class ChemistryJsonGenerator Converters = { new UniversalJsonConverter(), - new UniversalJsonConverter(), + new UniversalJsonConverter(), new UniversalJsonConverter(), new UniversalJsonConverter(), new FixedPointJsonConverter() diff --git a/Content.Server/GuideGenerator/ReagentEntry.cs b/Content.Server/GuideGenerator/ReagentEntry.cs index 8b597ad61b..59a212dbd2 100644 --- a/Content.Server/GuideGenerator/ReagentEntry.cs +++ b/Content.Server/GuideGenerator/ReagentEntry.cs @@ -76,7 +76,7 @@ public sealed class ReactionEntry proto.Products .Select(x => KeyValuePair.Create(x.Key, x.Value.Float())) .ToDictionary(x => x.Key, x => x.Value); - Effects = proto.Effects; + Effects = proto.Effects.ToList(); } } diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index c2d2614a0a..1430f53cdd 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -41,6 +41,7 @@ using Content.Shared.Stacks; using Content.Server.Construction.Components; using Content.Shared.Chat; using Content.Shared.Damage; +using Content.Shared.Temperature.Components; using Robust.Shared.Utility; namespace Content.Server.Kitchen.EntitySystems diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index 20dc114918..8dab21902d 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -5,12 +5,12 @@ using Content.Server.Medical.Components; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; -using Content.Server.Temperature.Components; using Content.Shared.Atmos; using Content.Shared.Body.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Medical.Cryogenics; using Content.Shared.MedicalScanner; +using Content.Shared.Temperature.Components; using Content.Shared.UserInterface; using Robust.Shared.Containers; diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index 11e4ed4fcf..657ac3e636 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Medical.Components; using Content.Server.PowerCell; -using Content.Server.Temperature.Components; using Content.Shared.Body.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; @@ -13,6 +12,7 @@ using Content.Shared.Item.ItemToggle.Components; using Content.Shared.MedicalScanner; using Content.Shared.Mobs.Components; using Content.Shared.Popups; +using Content.Shared.Temperature.Components; using Content.Shared.Traits.Assorted; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index 81f9415121..e8fe9e4c39 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -5,7 +5,6 @@ using Content.Server.NPC.Queries.Considerations; using Content.Server.NPC.Queries.Curves; using Content.Server.NPC.Queries.Queries; using Content.Server.Nutrition.Components; -using Content.Server.Temperature.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; using Content.Shared.Examine; @@ -30,6 +29,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; using Content.Shared.Atmos.Components; using System.Linq; +using Content.Shared.Temperature.Components; namespace Content.Server.NPC.Systems; diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index 6290e8231a..f6a7536994 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -10,14 +10,14 @@ using Content.Shared.Database; using Content.Shared.Inventory; using Content.Shared.Rejuvenate; using Content.Shared.Temperature; -using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; -using Robust.Shared.Physics.Events; using Content.Shared.Projectiles; +using Content.Shared.Temperature.Components; +using Content.Shared.Temperature.Systems; namespace Content.Server.Temperature.Systems; -public sealed class TemperatureSystem : EntitySystem +public sealed class TemperatureSystem : SharedTemperatureSystem { [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!; @@ -125,8 +125,7 @@ public sealed class TemperatureSystem : EntitySystem true); } - public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, - TemperatureComponent? temperature = null) + public override void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null) { if (!Resolve(uid, ref temperature, false)) return; @@ -161,16 +160,6 @@ public sealed class TemperatureSystem : EntitySystem ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature); } - public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null) - { - if (!Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0) - { - return Atmospherics.MinimumHeatCapacity; - } - - return comp.SpecificHeat * physics.FixturesMass; - } - private void OnInit(EntityUid uid, InternalTemperatureComponent comp, MapInitEvent args) { if (!TryComp(uid, out var temp)) diff --git a/Content.Server/Tiles/TileEntityEffectComponent.cs b/Content.Server/Tiles/TileEntityEffectComponent.cs index 4201af47f9..70f11bb060 100644 --- a/Content.Server/Tiles/TileEntityEffectComponent.cs +++ b/Content.Server/Tiles/TileEntityEffectComponent.cs @@ -14,6 +14,6 @@ public sealed partial class TileEntityEffectComponent : Component /// /// List of effects that should be applied. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public List Effects = default!; } diff --git a/Content.Server/Tiles/TileEntityEffectSystem.cs b/Content.Server/Tiles/TileEntityEffectSystem.cs index 4d866cb254..bd4aa789c2 100644 --- a/Content.Server/Tiles/TileEntityEffectSystem.cs +++ b/Content.Server/Tiles/TileEntityEffectSystem.cs @@ -1,13 +1,11 @@ -using Content.Server.Atmos.Components; -using Content.Server.Atmos.EntitySystems; using Content.Shared.StepTrigger.Systems; -using Content.Shared.Chemistry.Reagent; using Content.Shared.EntityEffects; namespace Content.Server.Tiles; public sealed class TileEntityEffectSystem : EntitySystem { + [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; public override void Initialize() { @@ -23,11 +21,7 @@ public sealed class TileEntityEffectSystem : EntitySystem private void OnTileStepTriggered(Entity ent, ref StepTriggeredOffEvent args) { var otherUid = args.Tripper; - var effectArgs = new EntityEffectBaseArgs(otherUid, EntityManager); - foreach (var effect in ent.Comp.Effects) - { - effect.Effect(effectArgs); - } + _entityEffects.ApplyEffects(otherUid, ent.Comp.Effects.ToArray()); } } diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 7cdcec78c2..720fec490a 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -13,7 +13,6 @@ using Content.Server.NPC.HTN; using Content.Server.NPC.Systems; using Content.Server.StationEvents.Components; using Content.Server.Speech.Components; -using Content.Server.Temperature.Components; using Content.Shared.Body.Components; using Content.Shared.Chat; using Content.Shared.CombatMode; @@ -44,6 +43,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Content.Shared.NPC.Prototypes; using Content.Shared.Roles; +using Content.Shared.Temperature.Components; namespace Content.Server.Zombies; diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index 58a41a5f7a..d905cc03ae 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -81,9 +81,9 @@ public enum LogType ChemicalReaction = 17, /// - /// Reagent effects related interactions. + /// EntityEffect related interactions. /// - ReagentEffect = 18, + EntityEffect = 18, /// /// Canister valve was opened or closed. diff --git a/Content.Shared/Body/Components/LungComponent.cs b/Content.Shared/Body/Components/LungComponent.cs index dd31de7722..ab1869f8b1 100644 --- a/Content.Shared/Body/Components/LungComponent.cs +++ b/Content.Shared/Body/Components/LungComponent.cs @@ -22,7 +22,7 @@ public sealed partial class LungComponent : Component /// The name/key of the solution on this entity which these lungs act on. /// [DataField] - public string SolutionName = LungSystem.LungSolutionName; + public string SolutionName = "Lung"; /// /// The solution on this entity that these lungs act on. diff --git a/Content.Shared/Body/Systems/LungSystem.cs b/Content.Shared/Body/Systems/LungSystem.cs index 5f4c1ee4ef..a097a7752a 100644 --- a/Content.Shared/Body/Systems/LungSystem.cs +++ b/Content.Shared/Body/Systems/LungSystem.cs @@ -1,11 +1,14 @@ +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; using Content.Shared.Body.Components; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Atmos; +using Content.Shared.Body.Prototypes; using Content.Shared.Chemistry.Components; -using Content.Shared.Clothing; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Inventory.Events; +using Robust.Shared.Prototypes; +using BreathToolComponent = Content.Shared.Atmos.Components.BreathToolComponent; +using InternalsComponent = Content.Shared.Body.Components.InternalsComponent; namespace Content.Shared.Body.Systems; @@ -15,8 +18,6 @@ public sealed class LungSystem : EntitySystem [Dependency] private readonly SharedInternalsSystem _internals = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; - public static string LungSolutionName = "Lung"; - public override void Initialize() { base.Initialize(); @@ -53,6 +54,7 @@ public sealed class LungSystem : EntitySystem } } + // TODO: JUST METABOLIZE GASES DIRECTLY DON'T CONVERT TO REAGENTS!!! (Needs Metabolism refactor :B) public void GasToReagent(EntityUid uid, LungComponent lung) { if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution)) diff --git a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs index 3b46d3913c..ee85fe65b7 100644 --- a/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs +++ b/Content.Shared/Body/Systems/SharedBloodstreamSystem.cs @@ -6,7 +6,8 @@ using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; -using Content.Shared.EntityEffects.Effects; +using Content.Shared.EntityEffects.Effects.Solution; +using Content.Shared.EntityEffects.Effects.Transform; using Content.Shared.FixedPoint; using Content.Shared.Fluids; using Content.Shared.Forensics.Components; @@ -149,7 +150,9 @@ public abstract class SharedBloodstreamSystem : EntitySystem { switch (effect) { - case CreateEntityReactionEffect: // Prevent entities from spawning in the bloodstream + // TODO: Rather than this, ReactionAttempt should allow systems to remove effects from the list before the reaction. + // TODO: I think there's a PR up on the repo for this and if there isn't I'll make one -Princess + case EntityEffects.Effects.EntitySpawning.SpawnEntity: // Prevent entities from spawning in the bloodstream case AreaReactionEffect: // No spontaneous smoke or foam leaking out of blood vessels. args.Cancelled = true; return; diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs index 54210cf195..671a30dec4 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs @@ -707,6 +707,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem } // Thermal energy and temperature management. + // TODO: ENERGY CONSERVATION!!! Nuke this once we have HeatContainers and use methods which properly conserve energy and model heat transfer correctly! #region Thermal Energy and Temperature @@ -763,6 +764,26 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem UpdateChemicals(soln); } + /// + /// Same as but clamps the value between two temperature values. + /// + /// Solution we're adjusting the energy of + /// Thermal energy we're adding or removing + /// Min desired temperature + /// Max desired temperature + public void AddThermalEnergyClamped(Entity soln, float thermalEnergy, float min, float max) + { + var solution = soln.Comp.Solution; + + if (thermalEnergy == 0.0f) + return; + + var heatCap = solution.GetHeatCapacity(PrototypeManager); + var deltaT = thermalEnergy / heatCap; + solution.Temperature = Math.Clamp(solution.Temperature + deltaT, min, max); + UpdateChemicals(soln); + } + #endregion Thermal Energy and Temperature #region Event Handlers diff --git a/Content.Shared/Chemistry/Reaction/ChemicalReactionSystem.cs b/Content.Shared/Chemistry/Reaction/ChemicalReactionSystem.cs index 351a51ecc1..a995ef90f4 100644 --- a/Content.Shared/Chemistry/Reaction/ChemicalReactionSystem.cs +++ b/Content.Shared/Chemistry/Reaction/ChemicalReactionSystem.cs @@ -31,6 +31,7 @@ namespace Content.Shared.Chemistry.Reaction [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!; /// /// A cache of all reactions indexed by at most ONE of their required reactants. @@ -205,27 +206,12 @@ namespace Content.Shared.Chemistry.Reaction private void OnReaction(Entity soln, ReactionPrototype reaction, ReagentPrototype? reagent, FixedPoint2 unitReactions) { - var args = new EntityEffectReagentArgs(soln, EntityManager, null, soln.Comp.Solution, unitReactions, reagent, null, 1f); - var posFound = _transformSystem.TryGetMapOrGridCoordinates(soln, out var gridPos); _adminLogger.Add(LogType.ChemicalReaction, reaction.Impact, $"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(soln):metabolizer} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found]")}"); - foreach (var effect in reaction.Effects) - { - if (!effect.ShouldApply(args)) - continue; - - if (effect.ShouldLog) - { - var entity = args.TargetEntity; - _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact, - $"Reaction effect {effect.GetType().Name:effect} of reaction {reaction.ID:reaction} applied on entity {ToPrettyString(entity):entity} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found")}"); - } - - effect.Effect(args); - } + _entityEffects.ApplyEffects(soln, reaction.Effects, unitReactions.Float()); // Someday, some brave soul will thread through an optional actor // argument in from every call of OnReaction up, all just to pass diff --git a/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs b/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs index 4bbb972572..c9a24ec550 100644 --- a/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs +++ b/Content.Shared/Chemistry/Reaction/ReactionPrototype.cs @@ -60,7 +60,7 @@ namespace Content.Shared.Chemistry.Reaction /// /// Effects to be triggered when the reaction occurs. /// - [DataField("effects")] public List Effects = new(); + [DataField("effects")] public EntityEffect[] Effects = []; /// /// How dangerous is this effect? Stuff like bicaridine should be low, while things like methamphetamine diff --git a/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs b/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs index cabdee93c1..89fcca900e 100644 --- a/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs +++ b/Content.Shared/Chemistry/Reaction/ReactiveComponent.cs @@ -34,7 +34,7 @@ public sealed partial class ReactiveReagentEffectEntry public HashSet? Reagents = null; [DataField("effects", required: true)] - public List Effects = default!; + public EntityEffect[] Effects = default!; [DataField("groups", readOnly: true, serverOnly: true, customTypeSerializer:typeof(PrototypeIdDictionarySerializer, ReactiveGroupPrototype>))] diff --git a/Content.Shared/Chemistry/ReactiveSystem.cs b/Content.Shared/Chemistry/ReactiveSystem.cs index 6306537324..2ffb848f8a 100644 --- a/Content.Shared/Chemistry/ReactiveSystem.cs +++ b/Content.Shared/Chemistry/ReactiveSystem.cs @@ -1,108 +1,35 @@ -using Content.Shared.Administration.Logs; using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Database; -using Content.Shared.EntityEffects; +using Content.Shared.FixedPoint; using JetBrains.Annotations; using Robust.Shared.Prototypes; -using Robust.Shared.Random; namespace Content.Shared.Chemistry; [UsedImplicitly] public sealed class ReactiveSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; public void DoEntityReaction(EntityUid uid, Solution solution, ReactionMethod method) { foreach (var reagent in solution.Contents.ToArray()) { - ReactionEntity(uid, method, reagent, solution); + ReactionEntity(uid, method, reagent); } } - public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentQuantity reagentQuantity, Solution? source) + public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentQuantity reagentQuantity) { - // We throw if the reagent specified doesn't exist. - var proto = _prototypeManager.Index(reagentQuantity.Reagent.Prototype); - ReactionEntity(uid, method, proto, reagentQuantity, source); - } - - public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentPrototype proto, - ReagentQuantity reagentQuantity, Solution? source) - { - if (!TryComp(uid, out ReactiveComponent? reactive)) + if (reagentQuantity.Quantity == FixedPoint2.Zero) return; - // custom event for bypassing reactivecomponent stuff - var ev = new ReactionEntityEvent(method, proto, reagentQuantity, source); + // We throw if the reagent specified doesn't exist. + if (!_proto.Resolve(reagentQuantity.Reagent.Prototype, out var proto)) + return; + + var ev = new ReactionEntityEvent(method, reagentQuantity, proto); RaiseLocalEvent(uid, ref ev); - - // If we have a source solution, use the reagent quantity we have left. Otherwise, use the reaction volume specified. - var args = new EntityEffectReagentArgs(uid, EntityManager, null, source, source?.GetReagentQuantity(reagentQuantity.Reagent) ?? reagentQuantity.Quantity, proto, method, 1f); - - // First, check if the reagent wants to apply any effects. - if (proto.ReactiveEffects != null && reactive.ReactiveGroups != null) - { - foreach (var (key, val) in proto.ReactiveEffects) - { - if (!val.Methods.Contains(method)) - continue; - - 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; - - if (effect.ShouldLog) - { - var entity = args.TargetEntity; - _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact, - $"Reactive effect {effect.GetType().Name:effect} of reagent {proto.ID:reagent} with method {method} applied on entity {ToPrettyString(entity):entity} at {Transform(entity).Coordinates:coordinates}"); - } - - 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(proto.ID)) - continue; - - foreach (var effect in entry.Effects) - { - if (!effect.ShouldApply(args, _robustRandom)) - continue; - - if (effect.ShouldLog) - { - var entity = args.TargetEntity; - _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact, - $"Reactive effect {effect.GetType().Name:effect} of {ToPrettyString(entity):entity} using reagent {proto.ID:reagent} with method {method} at {Transform(entity).Coordinates:coordinates}"); - } - - effect.Effect(args); - } - } - } } } public enum ReactionMethod @@ -113,9 +40,4 @@ Ingestion, } [ByRefEvent] -public readonly record struct ReactionEntityEvent( - ReactionMethod Method, - ReagentPrototype Reagent, - ReagentQuantity ReagentQuantity, - Solution? Source -); +public readonly record struct ReactionEntityEvent(ReactionMethod Method, ReagentQuantity ReagentQuantity, ReagentPrototype Reagent); diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index b999d8df61..3b16b577cb 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -2,21 +2,17 @@ using System.Collections.Frozen; using System.Linq; using Content.Shared.FixedPoint; using System.Text.Json.Serialization; -using Content.Shared.Administration.Logs; using Content.Shared.Body.Prototypes; -using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; using Content.Shared.Contraband; using Content.Shared.EntityEffects; -using Content.Shared.Database; +using Content.Shared.Localizations; using Content.Shared.Nutrition; -using Content.Shared.Prototypes; using Content.Shared.Roles; using Content.Shared.Slippery; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; using Robust.Shared.Utility; @@ -190,6 +186,7 @@ namespace Content.Shared.Chemistry.Reagent [DataField] public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepPuddle"); + // TODO: Reaction tile doesn't work properly and destroys reagents way too quickly public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List? data) { var removed = FixedPoint2.Zero; @@ -211,33 +208,32 @@ namespace Content.Shared.Chemistry.Reagent return removed; } - public void ReactionPlant(EntityUid? plantHolder, - ReagentQuantity amount, - Solution solution, - EntityManager entityManager, - IRobustRandom random, - ISharedAdminLogManager logger) + public IEnumerable GuidebookReagentEffectsDescription(IPrototypeManager prototype, IEntitySystemManager entSys, IEnumerable effects, FixedPoint2? metabolism = null) { - if (plantHolder == null) - return; + return effects.Select(x => GuidebookReagentEffectDescription(prototype, entSys, x, metabolism)) + .Where(x => x is not null) + .Select(x => x!) + .ToArray(); + } - var args = new EntityEffectReagentArgs(plantHolder.Value, entityManager, null, solution, amount.Quantity, this, null, 1f); - foreach (var plantMetabolizable in PlantMetabolisms) - { - if (!plantMetabolizable.ShouldApply(args, random)) - continue; + public string? GuidebookReagentEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys, EntityEffect effect, FixedPoint2? metabolism) + { + if (effect.EntityEffectGuidebookText(prototype, entSys) is not { } description) + return null; - if (plantMetabolizable.ShouldLog) - { - var entity = args.TargetEntity; - logger.Add( - LogType.ReagentEffect, - plantMetabolizable.LogImpact, - $"Plant metabolism effect {plantMetabolizable.GetType().Name:effect} of reagent {ID} applied on entity {entity}"); - } + var quantity = metabolism == null ? 0f : (double) (effect.MinScale * metabolism); - plantMetabolizable.Effect(args); - } + return Loc.GetString( + "guidebook-reagent-effect-description", + ("reagent", LocalizedName), + ("quantity", quantity), + ("effect", description), + ("chance", effect.Probability), + ("conditionCount", effect.Conditions?.Length ?? 0), + ("conditions", + ContentLocalizationManager.FormatList( + effect.Conditions?.Select(x => x.EntityConditionGuidebookText(prototype)).ToList() ?? new List() + ))); } } @@ -246,6 +242,7 @@ namespace Content.Shared.Chemistry.Reagent { public string ReagentPrototype; + // TODO: Kill Metabolism groups! public Dictionary, ReagentEffectsGuideEntry>? GuideEntries; public List? PlantMetabolisms = null; @@ -254,15 +251,12 @@ namespace Content.Shared.Chemistry.Reagent { ReagentPrototype = proto.ID; GuideEntries = proto.Metabolisms? - .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys))) + .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys, proto))) .ToDictionary(x => x.Key, x => x.Item2); if (proto.PlantMetabolisms.Count > 0) { - PlantMetabolisms = new List(proto.PlantMetabolisms - .Select(x => x.GuidebookEffectDescription(prototype, entSys)) - .Where(x => x is not null) - .Select(x => x!) - .ToArray()); + PlantMetabolisms = + new List(proto.GuidebookReagentEffectsDescription(prototype, entSys, proto.PlantMetabolisms)); } } } @@ -285,14 +279,11 @@ namespace Content.Shared.Chemistry.Reagent [DataField("effects", required: true)] public EntityEffect[] Effects = default!; - public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys) + public string EntityEffectFormat => "guidebook-reagent-effect-description"; + + public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys, ReagentPrototype proto) { - return new ReagentEffectsGuideEntry(MetabolismRate, - Effects - .Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate. - .Where(x => x is not null) - .Select(x => x!) - .ToArray()); + return new ReagentEffectsGuideEntry(MetabolismRate, proto.GuidebookReagentEffectsDescription(prototype, entSys, Effects, MetabolismRate).ToArray()); } } diff --git a/Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs b/Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs new file mode 100644 index 0000000000..d19d326e26 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Body; + +/// +public sealed partial class BreathingCondition : EntityConditionBase +{ + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-breathing", ("isBreathing", !Inverted)); +} diff --git a/Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs new file mode 100644 index 0000000000..f9867b28b0 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Body; + +/// +/// Returns true if this entity's hunger is within a specified minimum and maximum. +/// +/// +public sealed partial class TotalHungerEntityConditionSystem : EntityConditionSystem +{ + [Dependency] private readonly HungerSystem _hunger = default!; + + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + var total = _hunger.GetHunger(entity.Comp); + args.Result = total >= args.Condition.Min && total <= args.Condition.Max; + } +} + +/// +public sealed partial class HungerCondition : EntityConditionBase +{ + [DataField] + public float Min; + + [DataField] + public float Max = float.PositiveInfinity; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-total-hunger", ("max", float.IsPositiveInfinity(Max) ? int.MaxValue : Max), ("min", Min)); +} diff --git a/Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs new file mode 100644 index 0000000000..e87c54a761 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Body.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Body; + +/// +/// Returns true if this entity is using internals. False if they are not or cannot use internals. +/// +/// +public sealed partial class InternalsOnEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + args.Result = entity.Comp.GasTankEntity != null; + } +} + +/// +public sealed partial class InternalsCondition : EntityConditionBase +{ + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", !Inverted)); +} diff --git a/Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs b/Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs new file mode 100644 index 0000000000..2196d271ff --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs @@ -0,0 +1,31 @@ +using Content.Shared.Body.Prototypes; +using Content.Shared.Localizations; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Body; + +/// +public sealed partial class MetabolizerTypeCondition : EntityConditionBase +{ + [DataField(required: true)] + public ProtoId[] Type = default!; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) + { + var typeList = new List(); + + foreach (var type in Type) + { + if (!prototype.Resolve(type, out var proto)) + continue; + + typeList.Add(proto.LocalizedName); + } + + var names = ContentLocalizationManager.FormatListToOr(typeList); + + return Loc.GetString("reagent-effect-condition-guidebook-organ-type", + ("name", names), + ("shouldhave", !Inverted)); + } +} diff --git a/Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs new file mode 100644 index 0000000000..d00481d3e1 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Body; + +/// +/// Returns true if this entity's current mob state matches the condition's specified mob state. +/// +/// +public sealed partial class MobStateEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + if (entity.Comp.CurrentState == args.Condition.Mobstate) + args.Result = true; + } +} + +/// +public sealed partial class MobStateCondition : EntityConditionBase +{ + [DataField] + public MobState Mobstate = MobState.Alive; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate)); +} diff --git a/Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs new file mode 100644 index 0000000000..e07989da36 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs @@ -0,0 +1,59 @@ +using System.Linq; +using Content.Shared.Localizations; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Content.Shared.Roles; +using Content.Shared.Roles.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; + +/// +/// Returns true if this entity has any of the specified jobs. False if the entity has no mind, none of the specified jobs, or is jobless. +/// +/// +public sealed partial class HasJobEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + // We need a mind in our mind container... + if (!TryComp(entity.Comp.Mind, out var mind)) + return; + + foreach (var roleId in mind.MindRoleContainer.ContainedEntities) + { + if (!HasComp(roleId)) + continue; + + if (!TryComp(roleId, out var mindRole)) + { + Log.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}"); + continue; + } + + if (mindRole.JobPrototype == null) + { + Log.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}"); + continue; + } + + if (!args.Condition.Jobs.Contains(mindRole.JobPrototype.Value)) + continue; + + args.Result = true; + return; + } + } +} + +/// +public sealed partial class JobCondition : EntityConditionBase +{ + [DataField(required: true)] public List> Jobs = []; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) + { + var localizedNames = Jobs.Select(jobId => prototype.Index(jobId).LocalizedName).ToList(); + return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames))); + } +} diff --git a/Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs new file mode 100644 index 0000000000..c49c9a3d02 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; + +/// +/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum. +/// +/// +public sealed partial class ReagentThresholdEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + var quant = entity.Comp.Solution.GetTotalPrototypeQuantity(args.Condition.Reagent); + + args.Result = quant >= args.Condition.Min && quant <= args.Condition.Max; + } +} + +/// +public sealed partial class ReagentCondition : EntityConditionBase +{ + [DataField] + public FixedPoint2 Min = FixedPoint2.Zero; + + [DataField] + public FixedPoint2 Max = FixedPoint2.MaxValue; + + [DataField(required: true)] + public ProtoId Reagent; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) + { + if (!prototype.Resolve(Reagent, out var reagentProto)) + return String.Empty; + + return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold", + ("reagent", reagentProto.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")), + ("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()), + ("min", Min.Float())); + } +} diff --git a/Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs new file mode 100644 index 0000000000..99d7206c0a --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared.Localizations; +using Content.Shared.Tag; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Tags; + +/// +/// Returns true if this entity has all the listed tags. +/// +/// +public sealed partial class HasAllTagsEntityConditionSystem : EntityConditionSystem +{ + [Dependency] private readonly TagSystem _tag = default!; + + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + args.Result = _tag.HasAllTags(entity.Comp, args.Condition.Tags); + } +} + +/// +public sealed partial class AllTagsCondition : EntityConditionBase +{ + [DataField(required: true)] + public ProtoId[] Tags = []; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) + { + var tagList = new List(); + + foreach (var type in Tags) + { + if (!prototype.Resolve(type, out var proto)) + continue; + + tagList.Add(proto.ID); + } + + var names = ContentLocalizationManager.FormatList(tagList); + + return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", names), ("invert", Inverted)); + } +} diff --git a/Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs new file mode 100644 index 0000000000..3513353125 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared.Localizations; +using Content.Shared.Tag; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Tags; + +/// +/// Returns true if this entity have any of the listed tags. +/// +/// +public sealed partial class HasAnyTagEntityConditionSystem : EntityConditionSystem +{ + [Dependency] private readonly TagSystem _tag = default!; + + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + args.Result = _tag.HasAnyTag(entity.Comp, args.Condition.Tags); + } +} + +/// +public sealed partial class AnyTagCondition : EntityConditionBase +{ + [DataField(required: true)] + public ProtoId[] Tags = []; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) + { + var tagList = new List(); + + foreach (var type in Tags) + { + if (!prototype.Resolve(type, out var proto)) + continue; + + tagList.Add(proto.ID); + } + + var names = ContentLocalizationManager.FormatListToOr(tagList); + + return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", names), ("invert", Inverted)); + } +} diff --git a/Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs new file mode 100644 index 0000000000..9b67f38282 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Tag; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions.Tags; + +/// +/// Returns true if this entity has the listed tag. +/// +/// +public sealed partial class HasTagEntityConditionSystem : EntityConditionSystem +{ + [Dependency] private readonly TagSystem _tag = default!; + + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + args.Result = _tag.HasTag(entity.Comp, args.Condition.Tag); + } +} + +/// +public sealed partial class TagCondition : EntityConditionBase +{ + [DataField(required: true)] + public ProtoId Tag; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Inverted)); +} diff --git a/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs new file mode 100644 index 0000000000..6585f3bf44 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs @@ -0,0 +1,52 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Temperature.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; + +/// +/// Returns true if this entity has an amount of reagent in it within a specified minimum and maximum. +/// +/// +public sealed partial class TemperatureEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + if (entity.Comp.CurrentTemperature >= args.Condition.Min && entity.Comp.CurrentTemperature <= args.Condition.Max) + args.Result = true; + } +} + +/// +/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum. +/// +/// +public sealed partial class SolutionTemperatureEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + if (entity.Comp.Solution.Temperature >= args.Condition.Min && entity.Comp.Solution.Temperature <= args.Condition.Max) + args.Result = true; + } +} + +/// +public sealed partial class TemperatureCondition : EntityConditionBase +{ + /// + /// Minimum allowed temperature + /// + [DataField] + public float Min = 0; + + /// + /// Maximum allowed temperature + /// + [DataField] + public float Max = float.PositiveInfinity; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-body-temperature", + ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max), + ("min", Min)); +} diff --git a/Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs new file mode 100644 index 0000000000..fbb659b54f --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; +/// +/// A basic summary of this condition. +/// +/// +public sealed partial class TemplateEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + // Condition goes here. + } +} + +/// +public sealed partial class TemplateCondition : EntityConditionBase +{ + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => String.Empty; +} diff --git a/Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs new file mode 100644 index 0000000000..e710e07b17 --- /dev/null +++ b/Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions.Conditions; + +/// +/// Returns true if this entity can take damage and if its total damage is within a specified minimum and maximum. +/// +/// +public sealed partial class TotalDamageEntityConditionSystem : EntityConditionSystem +{ + protected override void Condition(Entity entity, ref EntityConditionEvent args) + { + var total = entity.Comp.TotalDamage; + args.Result = total >= args.Condition.Min && total <= args.Condition.Max; + } +} + +/// +public sealed partial class TotalDamageCondition : EntityConditionBase +{ + [DataField] + public FixedPoint2 Max = FixedPoint2.MaxValue; + + [DataField] + public FixedPoint2 Min = FixedPoint2.Zero; + + public override string EntityConditionGuidebookText(IPrototypeManager prototype) => + Loc.GetString("reagent-effect-condition-guidebook-total-damage", + ("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()), + ("min", Min.Float())); +} diff --git a/Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs b/Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs new file mode 100644 index 0000000000..090a422641 --- /dev/null +++ b/Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs @@ -0,0 +1,152 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityConditions; + +/// +/// This handles entity effects. +/// Specifically it handles the receiving of events for causing entity effects, and provides +/// public API for other systems to take advantage of entity effects. +/// +public sealed partial class SharedEntityConditionsSystem : EntitySystem, IEntityConditionRaiser +{ + /// + /// Checks a list of conditions to verify that they all return true. + /// + /// Target entity we're checking conditions on + /// Conditions we're checking + /// Returns true if all conditions return true, false if any fail + public bool TryConditions(EntityUid target, EntityCondition[]? conditions) + { + // If there's no conditions we can't fail any of them... + if (conditions == null) + return true; + + foreach (var condition in conditions) + { + if (!TryCondition(target, condition)) + return false; + } + + return true; + } + + /// + /// Checks a list of conditions to see if any are true. + /// + /// Target entity we're checking conditions on + /// Conditions we're checking + /// Returns true if any conditions return true + public bool TryAnyCondition(EntityUid target, EntityCondition[]? conditions) + { + // If there's no conditions we can't meet any of them... + if (conditions == null) + return false; + + foreach (var condition in conditions) + { + if (TryCondition(target, condition)) + return true; + } + + return false; + } + + /// + /// Checks a single on an entity. + /// + /// Target entity we're checking conditions on + /// Condition we're checking + /// Returns true if we meet the condition and false otherwise + public bool TryCondition(EntityUid target, EntityCondition condition) + { + return condition.Inverted != condition.RaiseEvent(target, this); + } + + /// + /// Raises a condition to an entity. You should not be calling this unless you know what you're doing. + /// + public bool RaiseConditionEvent(EntityUid target, T effect) where T : EntityConditionBase + { + var effectEv = new EntityConditionEvent(effect); + RaiseLocalEvent(target, ref effectEv); + return effectEv.Result; + } +} + +/// +/// This is a basic abstract entity effect containing all the data an entity effect needs to affect entities with effects... +/// +/// The Component that is required for the effect +/// The Condition we're testing +public abstract partial class EntityConditionSystem : EntitySystem where T : Component where TCon : EntityConditionBase +{ + /// + public override void Initialize() + { + SubscribeLocalEvent>(Condition); + } + protected abstract void Condition(Entity entity, ref EntityConditionEvent args); +} + +/// +/// Used to raise an EntityCondition without losing the type of condition. +/// +public interface IEntityConditionRaiser +{ + bool RaiseConditionEvent(EntityUid target, T effect) where T : EntityConditionBase; +} + +/// +/// Used to store an so it can be raised without losing the type of the condition. +/// +/// The Condition wer are raising. +public abstract partial class EntityConditionBase : EntityCondition where T : EntityConditionBase +{ + public override bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser) + { + if (this is not T type) + return false; + + // If the result of the event matches the result we're looking for then we pass. + return raiser.RaiseConditionEvent(target, type); + } +} + +/// +/// A basic condition which can be checked for on an entity via events. +/// +[ImplicitDataDefinitionForInheritors] +public abstract partial class EntityCondition +{ + public abstract bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser); + + /// + /// If true, invert the result. So false returns true and true returns false! + /// + [DataField] + public bool Inverted; + + /// + /// A basic description of this condition, which displays in the guidebook. + /// + public abstract string EntityConditionGuidebookText(IPrototypeManager prototype); +} + +/// +/// An Event carrying an entity effect. +/// +/// The Condition we're checking +[ByRefEvent] +public record struct EntityConditionEvent(T Condition) where T : EntityConditionBase +{ + /// + /// The result of our check, defaults to false if nothing handles it. + /// + [DataField] + public bool Result; + + /// + /// The Condition being raised in this event + /// + public readonly T Condition = Condition; +} diff --git a/Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs b/Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs deleted file mode 100644 index 351e4ee12c..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -/// -/// Requires the target entity to be above or below a certain temperature. -/// Used for things like cryoxadone and pyroxadone. -/// -public sealed partial class Temperature : EventEntityEffectCondition -{ - [DataField] - public float Min = 0; - - [DataField] - public float Max = float.PositiveInfinity; - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-body-temperature", - ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max), - ("min", Min)); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs b/Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs deleted file mode 100644 index 9de1bfdbf6..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -/// -/// Condition for if the entity is successfully breathing. -/// -public sealed partial class Breathing : EventEntityEffectCondition -{ - /// - /// If true, the entity must not have trouble breathing to pass. - /// - [DataField] - public bool IsBreathing = true; - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-breathing", - ("isBreathing", IsBreathing)); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs b/Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs deleted file mode 100644 index 379a248027..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Tag; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.EffectConditions; - -public sealed partial class HasTag : EntityEffectCondition -{ - [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Tag = default!; - - [DataField] - public bool Invert = false; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (args.EntityManager.TryGetComponent(args.TargetEntity, out var tag)) - return args.EntityManager.System().HasTag(tag, Tag) ^ Invert; - - return false; - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - // this should somehow be made (much) nicer. - return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Invert)); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs b/Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs deleted file mode 100644 index cb30ef70c7..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Shared.Body.Components; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -/// -/// Condition for if the entity is or isn't wearing internals. -/// -public sealed partial class Internals : EntityEffectCondition -{ - /// - /// To pass, the entity's internals must have this same state. - /// - [DataField] - public bool UsingInternals = true; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (!args.EntityManager.TryGetComponent(args.TargetEntity, out InternalsComponent? internalsComp)) - return !UsingInternals; // They have no internals to wear. - - var internalsState = internalsComp.GasTankEntity != null; // If gas tank is not null, they are wearing internals - return UsingInternals == internalsState; - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", UsingInternals)); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs deleted file mode 100644 index 96f3be64c6..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Linq; -using Content.Shared.Localizations; -using Content.Shared.Mind; -using Content.Shared.Mind.Components; -using Content.Shared.Roles; -using Content.Shared.Roles.Components; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -public sealed partial class JobCondition : EntityEffectCondition -{ - [DataField(required: true)] public List> Job; - - public override bool Condition(EntityEffectBaseArgs args) - { - args.EntityManager.TryGetComponent(args.TargetEntity, out var mindContainer); - - if (mindContainer is null - || !args.EntityManager.TryGetComponent(mindContainer.Mind, out var mind)) - return false; - - foreach (var roleId in mind.MindRoleContainer.ContainedEntities) - { - if (!args.EntityManager.HasComponent(roleId)) - continue; - - if (!args.EntityManager.TryGetComponent(roleId, out var mindRole)) - { - Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}"); - continue; - } - - if (mindRole.JobPrototype == null) - { - Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}"); - continue; - } - - if (Job.Contains(mindRole.JobPrototype.Value)) - return true; - } - - return false; - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList(); - return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames))); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs b/Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs deleted file mode 100644 index efe7246b2a..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -public sealed partial class MobStateCondition : EntityEffectCondition -{ - [DataField] - public MobState Mobstate = MobState.Alive; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (args.EntityManager.TryGetComponent(args.TargetEntity, out MobStateComponent? mobState)) - { - if (mobState.CurrentState == Mobstate) - return true; - } - - return false; - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate)); - } -} - diff --git a/Content.Shared/EntityEffects/EffectConditions/OrganType.cs b/Content.Shared/EntityEffects/EffectConditions/OrganType.cs deleted file mode 100644 index f99eb5cc77..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/OrganType.cs +++ /dev/null @@ -1,28 +0,0 @@ -// using Content.Server.Body.Components; -using Content.Shared.Body.Prototypes; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.EffectConditions; - -/// -/// Requires that the metabolizing organ is or is not tagged with a certain MetabolizerType -/// -public sealed partial class OrganType : EventEntityEffectCondition -{ - [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Type = default!; - - /// - /// Does this condition pass when the organ has the type, or when it doesn't have the type? - /// - [DataField] - public bool ShouldHave = true; - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-organ-type", - ("name", prototype.Index(Type).LocalizedName), - ("shouldhave", ShouldHave)); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs b/Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs deleted file mode 100644 index af71f20c8e..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -/// -/// Used for implementing reagent effects that require a certain amount of reagent before it should be applied. -/// For instance, overdoses. -/// -/// This can also trigger on -other- reagents, not just the one metabolizing. By default, it uses the -/// one being metabolized. -/// -public sealed partial class ReagentThreshold : EntityEffectCondition -{ - [DataField] - public FixedPoint2 Min = FixedPoint2.Zero; - - [DataField] - public FixedPoint2 Max = FixedPoint2.MaxValue; - - // TODO use ReagentId - [DataField] - public string? Reagent; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - var reagent = Reagent ?? reagentArgs.Reagent?.ID; - if (reagent == null) - return true; // No condition to apply. - - var quant = FixedPoint2.Zero; - if (reagentArgs.Source != null) - quant = reagentArgs.Source.GetTotalPrototypeQuantity(reagent); - - return quant >= Min && quant <= Max; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - ReagentPrototype? reagentProto = null; - if (Reagent is not null) - prototype.TryIndex(Reagent, out reagentProto); - - return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold", - ("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")), - ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()), - ("min", Min.Float())); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs b/Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs deleted file mode 100644 index e2febd8f48..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -/// -/// Requires the solution to be above or below a certain temperature. -/// Used for things like explosives. -/// -public sealed partial class SolutionTemperature : EntityEffectCondition -{ - [DataField] - public float Min = 0.0f; - - [DataField] - public float Max = float.PositiveInfinity; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - return reagentArgs?.Source != null && - reagentArgs.Source.Temperature >= Min && - reagentArgs.Source.Temperature <= Max; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-solution-temperature", - ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max), - ("min", Min)); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs b/Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs deleted file mode 100644 index a4baeb634a..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Shared.EntityEffects; -using Content.Shared.Damage; -using Content.Shared.FixedPoint; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -public sealed partial class TotalDamage : EntityEffectCondition -{ - [DataField] - public FixedPoint2 Max = FixedPoint2.MaxValue; - - [DataField] - public FixedPoint2 Min = FixedPoint2.Zero; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (args.EntityManager.TryGetComponent(args.TargetEntity, out DamageableComponent? damage)) - { - var total = damage.TotalDamage; - return total >= Min && total <= Max; - } - - return false; - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-total-damage", - ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()), - ("min", Min.Float())); - } -} diff --git a/Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs b/Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs deleted file mode 100644 index cbeb334c47..0000000000 --- a/Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Shared.EntityEffects; -using Content.Shared.Nutrition.Components; -using Content.Shared.Nutrition.EntitySystems; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.EffectConditions; - -public sealed partial class Hunger : EntityEffectCondition -{ - [DataField] - public float Max = float.PositiveInfinity; - - [DataField] - public float Min = 0; - - public override bool Condition(EntityEffectBaseArgs args) - { - if (args.EntityManager.TryGetComponent(args.TargetEntity, out HungerComponent? hunger)) - { - var total = args.EntityManager.System().GetHunger(hunger); - return total >= Min && total <= Max; - } - - return false; - } - - public override string GuidebookExplanation(IPrototypeManager prototype) - { - return Loc.GetString("reagent-effect-condition-guidebook-total-hunger", - ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max), - ("min", Min)); - } -} diff --git a/Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs b/Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs deleted file mode 100644 index 0f2d35d369..0000000000 --- a/Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Content.Shared.Chemistry.EntitySystems; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects -{ - public sealed partial class AddToSolutionReaction : EntityEffect - { - [DataField("solution")] - private string _solution = "reagents"; - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) { - if (reagentArgs.Reagent == null) - return; - - // TODO see if this is correct - var solutionContainerSystem = reagentArgs.EntityManager.System(); - if (!solutionContainerSystem.TryGetSolution(reagentArgs.TargetEntity, _solution, out var solutionContainer)) - return; - - if (solutionContainerSystem.TryAddReagent(solutionContainer.Value, reagentArgs.Reagent.ID, reagentArgs.Quantity, out var accepted)) - reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, accepted); - - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => - Loc.GetString("reagent-effect-guidebook-add-to-solution-reaction", ("chance", Probability)); - } -} diff --git a/Content.Shared/EntityEffects/Effects/AdjustAlert.cs b/Content.Shared/EntityEffects/Effects/AdjustAlert.cs deleted file mode 100644 index 282de0a06c..0000000000 --- a/Content.Shared/EntityEffects/Effects/AdjustAlert.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Content.Shared.Alert; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class AdjustAlert : EntityEffect -{ - /// - /// The specific Alert that will be adjusted - /// - [DataField(required: true)] - public ProtoId AlertType; - - /// - /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately. - /// - [DataField] - public bool Clear; - - /// - /// Visually display cooldown progress over the alert icon. - /// - [DataField] - public bool ShowCooldown; - - /// - /// The length of the cooldown or delay before removing the alert (in seconds). - /// - [DataField] - public float Time; - - //JUSTIFICATION: This just changes some visuals, doesn't need to be documented. - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null; - - public override void Effect(EntityEffectBaseArgs args) - { - var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - if (!args.EntityManager.HasComponent(args.TargetEntity)) - return; - - if (Clear && Time <= 0) - { - alertSys.ClearAlert(args.TargetEntity, AlertType); - } - else - { - var timing = IoCManager.Resolve(); - (TimeSpan, TimeSpan)? cooldown = null; - - if ((ShowCooldown || Clear) && Time > 0) - cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time)); - - alertSys.ShowAlert(args.TargetEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown); - } - - } -} diff --git a/Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs new file mode 100644 index 0000000000..633cde0877 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs @@ -0,0 +1,65 @@ +using Content.Shared.Alert; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// Adjusts a given alert on this entity. +/// +/// +public sealed partial class AdjustAlertEntityEffectSysten : EntityEffectSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var time = args.Effect.Time; + var clear = args.Effect.Clear; + var type = args.Effect.AlertType; + + if (clear && time <= TimeSpan.Zero) + { + _alerts.ClearAlert(entity.AsNullable(), type); + } + else + { + (TimeSpan, TimeSpan)? cooldown = null; + + if ((args.Effect.ShowCooldown || clear) && args.Effect.Time >= TimeSpan.Zero) + cooldown = (_timing.CurTime, _timing.CurTime + time); + + _alerts.ShowAlert(entity.AsNullable(), type, cooldown: cooldown, autoRemove: clear, showCooldown: args.Effect.ShowCooldown); + } + + } +} + +/// +public sealed partial class AdjustAlert : EntityEffectBase +{ + /// + /// The specific Alert that will be adjusted + /// + [DataField(required: true)] + public ProtoId AlertType; + + /// + /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately. + /// + [DataField] + public bool Clear; + + /// + /// Visually display cooldown progress over the alert icon. + /// + [DataField] + public bool ShowCooldown; + + /// + /// The length of the cooldown or delay before removing the alert (in seconds). + /// + [DataField] + public TimeSpan Time; +} diff --git a/Content.Shared/EntityEffects/Effects/AdjustReagent.cs b/Content.Shared/EntityEffects/Effects/AdjustReagent.cs deleted file mode 100644 index bb655b46bc..0000000000 --- a/Content.Shared/EntityEffects/Effects/AdjustReagent.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Content.Shared.Body.Prototypes; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.Effects -{ - public sealed partial class AdjustReagent : EntityEffect - { - /// - /// The reagent ID to remove. Only one of this and should be active. - /// - [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Reagent = null; - // TODO use ReagentId - - /// - /// The metabolism group to remove, if the reagent satisfies any. - /// Only one of this and should be active. - /// - [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Group = null; - - [DataField(required: true)] - public FixedPoint2 Amount = default!; - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - if (reagentArgs.Source == null) - return; - - var amount = Amount; - amount *= reagentArgs.Scale; - - if (Reagent != null) - { - if (amount < 0 && reagentArgs.Source.ContainsPrototype(Reagent)) - reagentArgs.Source.RemoveReagent(Reagent, -amount); - if (amount > 0) - reagentArgs.Source.AddReagent(Reagent, amount); - } - else if (Group != null) - { - var prototypeMan = IoCManager.Resolve(); - foreach (var quant in reagentArgs.Source.Contents.ToArray()) - { - var proto = prototypeMan.Index(quant.Reagent.Prototype); - if (proto.Metabolisms != null && proto.Metabolisms.ContainsKey(Group)) - { - if (amount < 0) - reagentArgs.Source.RemoveReagent(quant.Reagent, amount); - if (amount > 0) - reagentArgs.Source.AddReagent(quant.Reagent, amount); - } - } - } - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - if (Reagent is not null && prototype.TryIndex(Reagent, out ReagentPrototype? reagentProto)) - { - return Loc.GetString("reagent-effect-guidebook-adjust-reagent-reagent", - ("chance", Probability), - ("deltasign", MathF.Sign(Amount.Float())), - ("reagent", reagentProto.LocalizedName), - ("amount", MathF.Abs(Amount.Float()))); - } - else if (Group is not null && prototype.TryIndex(Group, out MetabolismGroupPrototype? groupProto)) - { - return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group", - ("chance", Probability), - ("deltasign", MathF.Sign(Amount.Float())), - ("group", groupProto.LocalizedName), - ("amount", MathF.Abs(Amount.Float()))); - } - - throw new NotImplementedException(); - } - } -} - diff --git a/Content.Shared/EntityEffects/Effects/AdjustTemperature.cs b/Content.Shared/EntityEffects/Effects/AdjustTemperature.cs deleted file mode 100644 index 03dc226e93..0000000000 --- a/Content.Shared/EntityEffects/Effects/AdjustTemperature.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class AdjustTemperature : EventEntityEffect -{ - [DataField] - public float Amount; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-adjust-temperature", - ("chance", Probability), - ("deltasign", MathF.Sign(Amount)), - ("amount", MathF.Abs(Amount))); -} diff --git a/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs new file mode 100644 index 0000000000..adc465f341 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Temperature.Components; +using Content.Shared.Temperature.Systems; + +namespace Content.Shared.EntityEffects.Effects; + +// TODO: When we get a proper temperature/energy struct combine this with the solution temperature effect!!! +/// +/// Adjusts the temperature of this entity. +/// +/// +public sealed partial class AdjustTemperatureEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedTemperatureSystem _temperature = default!; + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var amount = args.Effect.Amount * args.Scale; + + _temperature.ChangeHeat(entity, amount, true, entity.Comp); + } +} + +/// +public sealed partial class AdjustTemperature : EntityEffectBase +{ + /// + /// Amount we're adjusting temperature by. + /// + [DataField] + public float Amount; +} diff --git a/Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs b/Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs deleted file mode 100644 index 45ed261a35..0000000000 --- a/Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Shared.Database; -using Content.Shared.FixedPoint; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Basically smoke and foam reactions. -/// -public sealed partial class AreaReactionEffect : EventEntityEffect -{ - /// - /// How many seconds will the effect stay, counting after fully spreading. - /// - [DataField("duration")] public float Duration = 10; - - /// - /// How many units of reaction for 1 smoke entity. - /// - [DataField] public FixedPoint2 OverflowThreshold = FixedPoint2.New(2.5); - - /// - /// The entity prototype that will be spawned as the effect. - /// - [DataField("prototypeId", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] - public string PrototypeId = default!; - - /// - /// Sound that will get played when this reaction effect occurs. - /// - [DataField("sound", required: true)] public SoundSpecifier Sound = default!; - - public override bool ShouldLog => true; - - protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-area-reaction", - ("duration", Duration) - ); - - public override LogImpact LogImpact => LogImpact.High; -} diff --git a/Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs b/Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs deleted file mode 100644 index 45ca740363..0000000000 --- a/Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Shared.Xenoarchaeology.Artifact.Components; -using Content.Shared.Xenoarchaeology.Artifact; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Restores durability in active artefact nodes. -/// -public sealed partial class ArtifactDurabilityRestore : EntityEffect -{ - /// - /// Amount of durability that will be restored per effect interaction. - /// - [DataField] - public int RestoredDurability = 1; - - public override void Effect(EntityEffectBaseArgs args) - { - var entMan = args.EntityManager; - var xenoArtifactSys = entMan.System(); - - if (!entMan.TryGetComponent(args.TargetEntity, out var xenoArtifact)) - return; - - foreach (var node in xenoArtifactSys.GetActiveNodes((args.TargetEntity, xenoArtifact))) - { - xenoArtifactSys.AdjustNodeDurability(node.Owner, RestoredDurability); - } - } - - protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return Loc.GetString("reagent-effect-guidebook-artifact-durability-restore", ("restored", RestoredDurability)); - } -} diff --git a/Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs new file mode 100644 index 0000000000..563c053b36 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs @@ -0,0 +1,72 @@ +using Content.Shared.Popups; +using Content.Shared.Xenoarchaeology.Artifact; +using Content.Shared.Xenoarchaeology.Artifact.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// Restores durability on this artifact +/// +/// +public sealed partial class ArtifactDurabilityRestoreEntityEffectsSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var durability = args.Effect.RestoredDurability; + + foreach (var node in _xenoArtifact.GetActiveNodes(entity)) + { + _xenoArtifact.AdjustNodeDurability(node.Owner, durability); + } + } +} + +/// +/// Unlocks a node on this artifact. Only works this effect hasn't been applied before. +/// +/// +public sealed partial class ArtifactUnlockEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + if (EnsureComp(entity, out var unlocking)) + { + if (unlocking.ArtifexiumApplied) + return; + + _popup.PopupEntity(Loc.GetString("artifact-activation-artifexium"), entity, PopupType.Medium); + } + else + { + _xenoArtifact.TriggerXenoArtifact(entity, null, force: true); + } + + _xenoArtifact.SetArtifexiumApplied((entity, unlocking), true); + } +} + +/// +public sealed partial class ArtifactDurabilityRestore : EntityEffectBase +{ + /// + /// Amount of durability that will be restored per effect interaction. + /// + [DataField] + public int RestoredDurability = 1; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-artifact-durability-restore", ("restored", RestoredDurability)); +} + +/// +public sealed partial class ArtifactUnlock : EntityEffectBase +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-artifact-unlock", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs b/Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs deleted file mode 100644 index 077e1ebfd2..0000000000 --- a/Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Shared.Xenoarchaeology.Artifact; -using Content.Shared.EntityEffects; -using Content.Shared.Popups; -using Content.Shared.Xenoarchaeology.Artifact.Components; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Sets an artifact into the unlocking state and marks the artifexium effect as true. -/// This is a very specific behavior intended for a specific chem. -/// -public sealed partial class ArtifactUnlock : EntityEffect -{ - public override void Effect(EntityEffectBaseArgs args) - { - var entMan = args.EntityManager; - var xenoArtifactSys = entMan.System(); - var popupSys = entMan.System(); - - if (!entMan.TryGetComponent(args.TargetEntity, out var xenoArtifact)) - return; - - if (!entMan.TryGetComponent(args.TargetEntity, out var unlocking)) - { - xenoArtifactSys.TriggerXenoArtifact((args.TargetEntity, xenoArtifact), null, force: true); - unlocking = entMan.EnsureComponent(args.TargetEntity); - } - else if (!unlocking.ArtifexiumApplied) - { - popupSys.PopupEntity(Loc.GetString("artifact-activation-artifexium"), args.TargetEntity, PopupType.Medium); - } - - if (unlocking.ArtifexiumApplied) - return; - - xenoArtifactSys.SetArtifexiumApplied((args.TargetEntity, unlocking), true); - } - - protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return Loc.GetString("reagent-effect-guidebook-artifact-unlock", ("chance", Probability)); - } -} diff --git a/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs new file mode 100644 index 0000000000..aa5132e596 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs @@ -0,0 +1,35 @@ +using Content.Shared.Atmos; +using Content.Shared.Atmos.EntitySystems; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Atmos; + +/// +/// See serverside system. +/// +/// +public sealed partial class CreateGas : EntityEffectBase +{ + /// + /// The gas we're creating + /// + [DataField] + public Gas Gas; + + /// + /// Amount of moles we're creating + /// + [DataField] + public float Moles = 3f; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + var atmos = entSys.GetEntitySystem(); + var gasProto = atmos.GetGas(Gas); + + return Loc.GetString("entity-effect-guidebook-create-gas", + ("chance", Probability), + ("moles", Moles), + ("gas", gasProto.Name)); + } +} diff --git a/Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs new file mode 100644 index 0000000000..b4b475ec59 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Atmos; + +/// +/// This raises an extinguish event on a given entity, reducing FireStacks. +/// The amount of FireStacks reduced is modified by scale. +/// +/// +public sealed partial class ExtinguishEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var ev = new ExtinguishEvent + { + FireStacksAdjustment = args.Effect.FireStacksAdjustment * args.Scale, + }; + + RaiseLocalEvent(entity, ref ev); + } +} + +/// +public sealed partial class Extinguish : EntityEffectBase +{ + /// + /// Amount of FireStacks reduced. + /// + [DataField] + public float FireStacksAdjustment = -1.5f; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-extinguish-reaction", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs new file mode 100644 index 0000000000..f08b609407 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Atmos; + +/// +/// See serverside system. +/// +/// +public sealed partial class Flammable : EntityEffectBase +{ + /// + /// Fire stack multiplier applied on an entity, + /// unless that entity is already on fire and is not null. + /// + [DataField] + public float Multiplier = 0.05f; + + /// + /// Fire stack multiplier applied if the entity is already on fire. Defaults to if null. + /// + [DataField] + public float? MultiplierOnExisting; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-flammable-reaction", ("chance", Probability)); + + public override bool ShouldLog => true; +} diff --git a/Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs new file mode 100644 index 0000000000..e10aaf3cd1 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs @@ -0,0 +1,18 @@ +using Content.Shared.Database; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Atmos; + +/// +/// See serverside system +/// +/// +public sealed partial class Ignite : EntityEffectBase +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-ignite", ("chance", Probability)); + + public override bool ShouldLog => true; + + public override LogImpact LogImpact => LogImpact.Medium; +} diff --git a/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs new file mode 100644 index 0000000000..402a50538a --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Removes a given amount of chemicals from the bloodstream modified by scale. +/// Optionally ignores a given chemical. +/// +/// +public sealed partial class CleanBloodstreamEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var scale = args.Scale * args.Effect.CleanseRate; + + _bloodstream.FlushChemicals((entity, entity), args.Effect.Excluded, scale); + } +} + +/// +public sealed partial class CleanBloodstream : EntityEffectBase +{ + /// + /// Amount of reagent we're cleaning out of our bloodstream. + /// + [DataField] + public FixedPoint2 CleanseRate = 3.0f; + + /// + /// An optional chemical to ignore when doing removal. + /// + [DataField] + public ProtoId? Excluded; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-clean-bloodstream", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs new file mode 100644 index 0000000000..29fd994e8c --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.Eye.Blinding.Systems; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Modifies eye damage by a given amount, modified by scale, floored to an integer. +/// +/// +public sealed partial class EyeDamageEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly BlindableSystem _blindable = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var amount = (int) Math.Floor(args.Effect.Amount * args.Scale); + _blindable.AdjustEyeDamage(entity.Owner, amount); + } +} + +/// +public sealed partial class EyeDamage : EntityEffectBase +{ + /// + /// The amount of eye damage we're adding or removing + /// + [DataField] + public int Amount = -1; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-eye-damage", ("chance", Probability), ("deltasign", MathF.Sign(Amount))); +} diff --git a/Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs new file mode 100644 index 0000000000..c684ffcdf2 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Modifies bleed by a given amount multiplied by scale. This can increase or decrease bleed. +/// +/// +public sealed partial class ModifyBleedEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _bloodstream.TryModifyBleedAmount(entity.AsNullable(), args.Effect.Amount * args.Scale); + } +} + +/// +public sealed partial class ModifyBleed : EntityEffectBase +{ + /// + /// Amount of bleed we're applying or removing if negative. + /// + [DataField] + public float Amount = -1.0f; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-modify-bleed-amount", ("chance", Probability), ("deltasign", MathF.Sign(Amount))); +} diff --git a/Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs new file mode 100644 index 0000000000..43098c9ddd --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Modifies the amount of blood in this entity's bloodstream by a given amount multiplied by scale. +/// This effect can increase or decrease blood level. +/// +/// +public sealed partial class ModifyBloodLevelEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _bloodstream.TryModifyBloodLevel(entity.AsNullable(), args.Effect.Amount * args.Scale); + } +} + +/// +public sealed partial class ModifyBloodLevel : EntityEffectBase +{ + /// + /// Amount of bleed we're applying or removing if negative. + /// + [DataField] + public FixedPoint2 Amount = 1.0f; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-modify-blood-level", ("chance", Probability), ("deltasign", MathF.Sign(Amount.Float()))); +} diff --git a/Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs new file mode 100644 index 0000000000..29fa1de98d --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Atmos; +using Content.Shared.Body.Components; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Adjust the amount of Moles stored in this set of lungs based on a given dictionary of gasses and ratios. +/// The amount of gas adjusted is modified by scale. +/// +/// +public sealed partial class ModifyLungGasEntityEffectSystem : EntityEffectSystem +{ + // TODO: This shouldn't be an entity effect, gasses should just metabolize and make a byproduct by default... + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var amount = args.Scale; + + foreach (var (gas, ratio) in args.Effect.Ratios) + { + var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier; + if (quantity < 0) + quantity = Math.Max(quantity, -entity.Comp.Air[(int) gas]); + entity.Comp.Air.AdjustMoles(gas, quantity); + } + } +} + +/// +public sealed partial class ModifyLungGas : EntityEffectBase +{ + [DataField(required: true)] + public Dictionary Ratios = default!; +} diff --git a/Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs new file mode 100644 index 0000000000..9790fced4e --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// See serverside system. +/// +/// +public sealed partial class Oxygenate : EntityEffectBase +{ + /// + /// Factor of oxygenation per metabolized quantity. Lungs metabolize at about 50u per tick so we need an equal multiplier to cancel that out! + /// + [DataField] + public float Factor = 1f; +} diff --git a/Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs new file mode 100644 index 0000000000..4fb41bd010 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared.Atmos.Rotting; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Reduces the rotting timer on an entity by a number of seconds, modified by scale. +/// This cannot increase the amount of seconds a body has rotted. +/// +/// +public sealed partial class ReduceRottingEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedRottingSystem _rotting = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var amount = args.Effect.Seconds * args.Scale; + + _rotting.ReduceAccumulator(entity, amount); + } +} + +/// +public sealed partial class ReduceRotting : EntityEffectBase +{ + /// + /// Number of seconds removed from the rotting timer. + /// + [DataField] + public TimeSpan Seconds = TimeSpan.FromSeconds(10); + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-reduce-rotting", + ("chance", Probability), + ("time", Seconds.TotalSeconds)); +} diff --git a/Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs new file mode 100644 index 0000000000..796084206d --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +// TODO: These systems are in the same file since satiation should be one system instead of two. Combine these when that happens. +// TODO: Arguably oxygen saturation should also be added here... +/// +/// Modifies the thirst level of a given entity, multiplied by scale. +/// +/// +public sealed partial class SatiateThirstEntityEffectsSystem : EntityEffectSystem +{ + [Dependency] private readonly ThirstSystem _thirst = default!; + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _thirst.ModifyThirst(entity, entity.Comp, args.Effect.Factor * args.Scale); + } +} + +/// +/// Modifies the hunger level of a given entity, multiplied by scale. +/// +/// +public sealed partial class SatiateHungerEntityEffectsSystem : EntityEffectSystem +{ + [Dependency] private readonly HungerSystem _hunger = default!; + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _hunger.ModifyHunger(entity, args.Effect.Factor * args.Scale, entity.Comp); + } +} + +/// +/// A type of made for satiation effects. +/// +/// The effect inheriting this BaseEffect +/// +public abstract partial class Satiate : EntityEffectBase where T : EntityEffectBase +{ + public const float AverageSatiation = 3f; // Magic number. Not sure how it was calculated since I didn't make it. + + /// + /// Change in satiation. + /// + [DataField] + public float Factor = -1.5f; +} + +/// +public sealed partial class SatiateThirst : Satiate +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", Factor / AverageSatiation)); +} + +/// +public sealed partial class SatiateHunger : Satiate +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", Factor / AverageSatiation)); +} diff --git a/Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs new file mode 100644 index 0000000000..02747057c5 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs @@ -0,0 +1,37 @@ +using Content.Shared.Medical; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Body; + +/// +/// Makes an entity vomit and reduces hunger and thirst by a given amount, modified by scale. +/// +/// +public sealed partial class VomitEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly VomitSystem _vomit = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _vomit.Vomit(entity.Owner, args.Effect.ThirstAmount * args.Scale, args.Effect.HungerAmount * args.Scale); + } +} + +/// +public sealed partial class Vomit : EntityEffectBase +{ + /// + /// How much we adjust our thirst after vomiting. + /// + [DataField] + public float ThirstAmount = -8f; + + /// + /// How much we adjust our hunger after vomiting. + /// + [DataField] + public float HungerAmount = -8f; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-vomit", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs new file mode 100644 index 0000000000..9b235e68d4 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs @@ -0,0 +1,37 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +/// +/// A type of which modifies the attribute of a Seed in a PlantHolder. +/// These are not modified by scale as botany has no concept of scale. +/// +/// The effect inheriting this BaseEffect +/// +public abstract partial class BasePlantAdjustAttribute : EntityEffectBase where T : BasePlantAdjustAttribute +{ + /// + /// How much we're adjusting the given attribute by. + /// + [DataField] + public float Amount { get; protected set; } = 1; + + /// + /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions. + /// + [DataField] + public abstract string GuidebookAttributeName { get; set; } + + /// + /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number. + /// + [DataField] + public virtual bool GuidebookIsAttributePositive { get; protected set; } = true; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-attribute", + ("attribute", Loc.GetString(GuidebookAttributeName)), + ("amount", Amount.ToString("0.00")), + ("positive", GuidebookIsAttributePositive), + ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs new file mode 100644 index 0000000000..069cb320a9 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustHealth : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-health"; +} + diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs new file mode 100644 index 0000000000..32e419e291 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustMutationLevel : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level"; +} + diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs new file mode 100644 index 0000000000..4ea695d135 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustMutationMod : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod"; +} + diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs new file mode 100644 index 0000000000..6f53076fb3 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustNutrition : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition"; +} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPests.cs similarity index 53% rename from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs rename to Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPests.cs index 18c00550d5..c1661ec89e 100644 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPests.cs @@ -1,6 +1,6 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; -public sealed partial class PlantAdjustPests : PlantAdjustAttribute +public sealed partial class PlantAdjustPests : BasePlantAdjustAttribute { public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests"; public override bool GuidebookIsAttributePositive { get; protected set; } = false; diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs new file mode 100644 index 0000000000..4f42adf3e6 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +/// +/// Handles increase or decrease of plant potency. +/// +public sealed partial class PlantAdjustPotency : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency"; +} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxins.cs similarity index 53% rename from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs rename to Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxins.cs index 9123b5847d..04eccb03ec 100644 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxins.cs @@ -1,8 +1,9 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; -public sealed partial class PlantAdjustToxins : PlantAdjustAttribute +public sealed partial class PlantAdjustToxins : BasePlantAdjustAttribute { public override string GuidebookAttributeName { get; set; } = "plant-attribute-toxins"; + public override bool GuidebookIsAttributePositive { get; protected set; } = false; } diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs new file mode 100644 index 0000000000..1d6ef8ea09 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAdjustWater : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-water"; +} + diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeeds.cs similarity index 53% rename from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs rename to Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeeds.cs index 70ff0747dd..e6be25d8c9 100644 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeeds.cs @@ -1,6 +1,6 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; -public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute +public sealed partial class PlantAdjustWeeds : BasePlantAdjustAttribute { public override string GuidebookAttributeName { get; set; } = "plant-attribute-weeds"; public override bool GuidebookIsAttributePositive { get; protected set; } = false; diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs new file mode 100644 index 0000000000..76466f544d --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantAffectGrowth : BasePlantAdjustAttribute +{ + public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth"; +} + diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs new file mode 100644 index 0000000000..dcea56baad --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs @@ -0,0 +1,21 @@ +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantChangeStat : EntityEffectBase +{ + /// + /// This is the worst thing in the code base. + /// It's meant to be generic and expandable I guess? But it's looking for a specific datafield and then + /// sending it into an if else if else if statement that filters by object type and randomly flips bits. + /// + [DataField (required: true)] + public string TargetValue = string.Empty; + + [DataField] + public float MinValue; + + [DataField] + public float MaxValue; + + [DataField] + public int Steps; +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs new file mode 100644 index 0000000000..7dcea240a6 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantCryoxadone : EntityEffectBase +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-cryoxadone", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs new file mode 100644 index 0000000000..d45c0369f7 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +/// +/// Handles removal of seeds on a plant. +/// +public sealed partial class PlantDestroySeeds : EntityEffectBase +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-seeds-remove", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs new file mode 100644 index 0000000000..4355a44593 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantDiethylamine : EntityEffectBase +{ + /// + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-diethylamine", ("chance", Probability)); +} + diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs new file mode 100644 index 0000000000..b56b99038b --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class PlantPhalanximine : EntityEffectBase +{ + /// + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-phalanximine", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs new file mode 100644 index 0000000000..63b06b0ad1 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +/// +/// Handles restoral of seeds on a plant. +/// +public sealed partial class PlantRestoreSeeds : EntityEffectBase +{ + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-seeds-add", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs new file mode 100644 index 0000000000..77bde39b12 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes; + +public sealed partial class RobustHarvest : EntityEffectBase +{ + [DataField] + public int PotencyLimit = 50; + + [DataField] + public int PotencyIncrease = 3; + + [DataField] + public int PotencySeedlessThreshold = 30; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-plant-robust-harvest", + ("seedlesstreshold", PotencySeedlessThreshold), + ("limit", PotencyLimit), + ("increase", PotencyIncrease), + ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs new file mode 100644 index 0000000000..6a3d13e0c6 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs @@ -0,0 +1,16 @@ +using Content.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Botany; + +/// +/// See serverside system. +/// +public sealed partial class PlantMutateChemicals : EntityEffectBase +{ + /// + /// The Reagent list this mutation draws from. + /// + [DataField] + public ProtoId RandomPickBotanyReagent = "RandomPickBotanyReagent"; +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs new file mode 100644 index 0000000000..c617c05b33 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs @@ -0,0 +1,22 @@ +namespace Content.Shared.EntityEffects.Effects.Botany; + +/// +/// See serverside system. +/// +public sealed partial class PlantMutateConsumeGases : EntityEffectBase +{ + [DataField] + public float MinValue = 0.01f; + + [DataField] + public float MaxValue = 0.5f; +} + +public sealed partial class PlantMutateExudeGases : EntityEffectBase +{ + [DataField] + public float MinValue = 0.01f; + + [DataField] + public float MaxValue = 0.5f; +} diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs new file mode 100644 index 0000000000..3602453b16 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.EntityEffects.Effects.Botany; + +/// +/// See serverside system. +/// +public sealed partial class PlantMutateHarvest : EntityEffectBase; diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs new file mode 100644 index 0000000000..91e8947044 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.EntityEffects.Effects.Botany; + +/// +/// See serverside system. +/// +public sealed partial class PlantMutateSpeciesChange : EntityEffectBase; diff --git a/Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs b/Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs deleted file mode 100644 index 3f8c58b74c..0000000000 --- a/Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class CauseZombieInfection : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-cause-zombie-infection", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs b/Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs deleted file mode 100644 index 98181b8667..0000000000 --- a/Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Basically smoke and foam reactions. -/// -public sealed partial class ChemCleanBloodstream : EventEntityEffect -{ - [DataField] - public float CleanseRate = 3.0f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs b/Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs deleted file mode 100644 index 83b2aa96e5..0000000000 --- a/Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Eye.Blinding.Systems; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Heal or apply eye damage -/// -public sealed partial class ChemHealEyeDamage : EntityEffect -{ - /// - /// How much eye damage to add. - /// - [DataField] - public int Amount = -1; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-cure-eye-damage", ("chance", Probability), ("deltasign", MathF.Sign(Amount))); - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - if (reagentArgs.Scale != 1f) // huh? - return; - - args.EntityManager.EntitySysManager.GetEntitySystem().AdjustEyeDamage(args.TargetEntity, Amount); - } -} diff --git a/Content.Shared/EntityEffects/Effects/ChemVomit.cs b/Content.Shared/EntityEffects/Effects/ChemVomit.cs deleted file mode 100644 index 1cd6b2552c..0000000000 --- a/Content.Shared/EntityEffects/Effects/ChemVomit.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Forces you to vomit. -/// -public sealed partial class ChemVomit : EventEntityEffect -{ - /// How many units of thirst to add each time we vomit - [DataField] - public float ThirstAmount = -8f; - /// How many units of hunger to add each time we vomit - [DataField] - public float HungerAmount = -8f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs b/Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs deleted file mode 100644 index 33173b1737..0000000000 --- a/Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.Effects; - -[DataDefinition] -public sealed partial class CreateEntityReactionEffect : EventEntityEffect -{ - /// - /// What entity to create. - /// - [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Entity = default!; - - /// - /// How many entities to create per unit reaction. - /// - [DataField] - public uint Number = 1; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-create-entity-reaction-effect", - ("chance", Probability), - ("entname", IoCManager.Resolve().Index(Entity).Name), - ("amount", Number)); -} diff --git a/Content.Shared/EntityEffects/Effects/CreateGas.cs b/Content.Shared/EntityEffects/Effects/CreateGas.cs deleted file mode 100644 index 75d554cdb3..0000000000 --- a/Content.Shared/EntityEffects/Effects/CreateGas.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Shared.Atmos; -using Content.Shared.Atmos.EntitySystems; -using Content.Shared.Database; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class CreateGas : EventEntityEffect -{ - [DataField(required: true)] - public Gas Gas = default!; - - /// - /// For each unit consumed, how many moles of gas should be created? - /// - [DataField] - public float Multiplier = 3f; - - public override bool ShouldLog => true; - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - var atmos = entSys.GetEntitySystem(); - var gasProto = atmos.GetGas(Gas); - - return Loc.GetString("reagent-effect-guidebook-create-gas", - ("chance", Probability), - ("moles", Multiplier), - ("gas", gasProto.Name)); - } - - public override LogImpact LogImpact => LogImpact.High; -} diff --git a/Content.Shared/EntityEffects/Effects/CureZombieInfection.cs b/Content.Shared/EntityEffects/Effects/CureZombieInfection.cs deleted file mode 100644 index dd2d21854c..0000000000 --- a/Content.Shared/EntityEffects/Effects/CureZombieInfection.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class CureZombieInfection : EventEntityEffect -{ - [DataField] - public bool Innoculate; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - if(Innoculate) - return Loc.GetString("reagent-effect-guidebook-innoculate-zombie-infection", ("chance", Probability)); - - return Loc.GetString("reagent-effect-guidebook-cure-zombie-infection", ("chance", Probability)); - } -} - diff --git a/Content.Shared/EntityEffects/Effects/Drunk.cs b/Content.Shared/EntityEffects/Effects/Drunk.cs deleted file mode 100644 index aa15df8f3d..0000000000 --- a/Content.Shared/EntityEffects/Effects/Drunk.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Shared.Drunk; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class Drunk : EntityEffect -{ - /// - /// BoozePower is how long each metabolism cycle will make the drunk effect last for. - /// - [DataField] - public TimeSpan BoozePower = TimeSpan.FromSeconds(3f); - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-drunk", ("chance", Probability)); - - public override void Effect(EntityEffectBaseArgs args) - { - var boozePower = BoozePower; - - if (args is EntityEffectReagentArgs reagentArgs) - boozePower *= reagentArgs.Scale.Float(); - - var drunkSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - drunkSys.TryApplyDrunkenness(args.TargetEntity, boozePower); - } -} diff --git a/Content.Shared/EntityEffects/Effects/Electrocute.cs b/Content.Shared/EntityEffects/Effects/Electrocute.cs deleted file mode 100644 index 32e0ff1172..0000000000 --- a/Content.Shared/EntityEffects/Effects/Electrocute.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Shared.Electrocution; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class Electrocute : EntityEffect -{ - [DataField] public int ElectrocuteTime = 2; - - [DataField] public int ElectrocuteDamageScale = 5; - - /// - /// true - refresh electrocute time, false - accumulate electrocute time - /// - [DataField] public bool Refresh = true; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime)); - - public override bool ShouldLog => true; - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - reagentArgs.EntityManager.System().TryDoElectrocution(reagentArgs.TargetEntity, null, - Math.Max((reagentArgs.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true); - - if (reagentArgs.Reagent != null) - reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity); - } else - { - args.EntityManager.System().TryDoElectrocution(args.TargetEntity, null, - Math.Max(ElectrocuteDamageScale, 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true); - } - } -} diff --git a/Content.Shared/EntityEffects/Effects/Emote.cs b/Content.Shared/EntityEffects/Effects/Emote.cs deleted file mode 100644 index 494dc502f5..0000000000 --- a/Content.Shared/EntityEffects/Effects/Emote.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Shared.Chat.Prototypes; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits unless specially forced. -/// -public sealed partial class Emote : EventEntityEffect -{ - /// - /// The emote the entity will preform. - /// - [DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string EmoteId; - - /// - /// If the emote should be recorded in chat. - /// - [DataField] - public bool ShowInChat; - - /// - /// If the forced emote will be listed in the guidebook. - /// - [DataField] - public bool ShowInGuidebook; - - /// - /// If true, the entity will preform the emote even if they normally can't. - /// - [DataField] - public bool Force = false; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - if (!ShowInGuidebook) - return null; // JUSTIFICATION: Emoting is mostly flavor, so same reason popup messages are not in here. - - var emotePrototype = prototype.Index(EmoteId); - return Loc.GetString("reagent-effect-guidebook-emote", ("chance", Probability), ("emote", Loc.GetString(emotePrototype.Name))); - } -} diff --git a/Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs b/Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs new file mode 100644 index 0000000000..1fcfc703ce --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs @@ -0,0 +1,40 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +public sealed partial class Emote : EntityEffectBase +{ + /// + /// The emote the entity will preform. + /// + [DataField("emote", required: true)] + public ProtoId EmoteId; + + /// + /// If the emote should be recorded in chat. + /// + [DataField] + public bool ShowInChat; + + /// + /// If the forced emote will be listed in the guidebook. + /// + [DataField] + public bool ShowInGuidebook; + + /// + /// If true, the entity will preform the emote even if they normally can't. + /// + [DataField] + public bool Force; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + if (!ShowInGuidebook || !prototype.Resolve(EmoteId, out var emote)) + return null; // JUSTIFICATION: Emoting is mostly flavor, so same reason popup messages are not in here. + + return Loc.GetString("entity-effect-guidebook-emote", ("chance", Probability), ("emote", Loc.GetString(emote.Name))); + } +} diff --git a/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs b/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs deleted file mode 100644 index 0f9eacc58d..0000000000 --- a/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -[DataDefinition] -public sealed partial class EmpReactionEffect : EventEntityEffect -{ - /// - /// Impulse range per unit of quantity - /// - [DataField("rangePerUnit")] - public float EmpRangePerUnit = 0.5f; - - /// - /// Maximum impulse range - /// - [DataField("maxRange")] - public float EmpMaxRange = 10; - - /// - /// How much energy will be drain from sources - /// - [DataField] - public float EnergyConsumption = 12500; - - /// - /// Amount of time entities will be disabled - /// - [DataField("duration")] - public TimeSpan DisableDuration = TimeSpan.FromSeconds(15); - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs new file mode 100644 index 0000000000..3f5d5b3b57 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.EntitySpawning; + +/// +/// A type of for effects that spawn entities by prototype. +/// +/// The entity effect inheriting this BaseEffect +/// +public abstract partial class BaseSpawnEntityEntityEffect : EntityEffectBase where T : BaseSpawnEntityEntityEffect +{ + /// + /// Amount of entities we're spawning + /// + [DataField] + public int Number = 1; + + /// + /// Prototype of the entity we're spawning + /// + [DataField (required: true)] + public EntProtoId Entity; + + /// + /// Whether this spawning is predicted. Set false to not predict the spawn. + /// Entities with animations or that have random elements when spawned should set this to false. + /// + [DataField] + public bool Predicted = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-spawn-entity", + ("chance", Probability), + ("entname", IoCManager.Resolve().Index(Entity).Name), + ("amount", Number)); +} diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs new file mode 100644 index 0000000000..9887ecdc60 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs @@ -0,0 +1,37 @@ +using Robust.Shared.Network; + +namespace Content.Shared.EntityEffects.Effects.EntitySpawning; + +/// +/// Spawns a number of entities of a given prototype at the coordinates of this entity. +/// Amount is modified by scale. +/// +/// +public sealed partial class SpawnEntityEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly INetManager _net = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var quantity = args.Effect.Number * (int)Math.Floor(args.Scale); + var proto = args.Effect.Entity; + + if (args.Effect.Predicted) + { + for (var i = 0; i < quantity; i++) + { + PredictedSpawnNextToOrDrop(proto, entity, entity.Comp); + } + } + else if (_net.IsServer) + { + for (var i = 0; i < quantity; i++) + { + SpawnNextToOrDrop(proto, entity, entity.Comp); + } + } + } +} + +/// +public sealed partial class SpawnEntity : BaseSpawnEntityEntityEffect; diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs new file mode 100644 index 0000000000..6909d503df --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs @@ -0,0 +1,51 @@ +using Robust.Shared.Containers; +using Robust.Shared.Network; + +namespace Content.Shared.EntityEffects.Effects.EntitySpawning; + +/// +/// Spawns a given number of entities of a given prototype in a specified container owned by this entity. +/// Returns if the prototype cannot spawn in the specified container. +/// Amount is modified by scale. +/// +/// +public sealed partial class SpawnEntityInContainerEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly INetManager _net = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var quantity = args.Effect.Number * (int)Math.Floor(args.Scale); + var proto = args.Effect.Entity; + var container = args.Effect.ContainerName; + + if (args.Effect.Predicted) + { + for (var i = 0; i < quantity; i++) + { + // Stop trying to spawn if it fails + if (!PredictedTrySpawnInContainer(proto, entity, container, out _, entity.Comp)) + return; + } + } + else if (_net.IsServer) + { + for (var i = 0; i < quantity; i++) + { + // Stop trying to spawn if it fails + if (!TrySpawnInContainer(proto, entity, container, out _, entity.Comp)) + return; + } + } + } +} + +/// +public sealed partial class SpawnEntityInContainer : BaseSpawnEntityEntityEffect +{ + /// + /// Name of the container we're trying to spawn into. + /// + [DataField(required: true)] + public string ContainerName; +} diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs new file mode 100644 index 0000000000..e5b1e119c1 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs @@ -0,0 +1,49 @@ +using Robust.Shared.Containers; +using Robust.Shared.Network; + +namespace Content.Shared.EntityEffects.Effects.EntitySpawning; + +/// +/// Spawns a given number of entities of a given prototype in a specified container owned by this entity. +/// Acts like if it cannot spawn the prototype in the specified container. +/// Amount is modified by scale. +/// +/// +public sealed partial class SpawnEntityInContainerOrDropEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly INetManager _net = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var quantity = args.Effect.Number * (int)Math.Floor(args.Scale); + var proto = args.Effect.Entity; + var container = args.Effect.ContainerName; + + var xform = Transform(entity); + + if (args.Effect.Predicted) + { + for (var i = 0; i < quantity; i++) + { + PredictedSpawnInContainerOrDrop(proto, entity, container, xform, entity.Comp); + } + } + else if (_net.IsServer) + { + for (var i = 0; i < quantity; i++) + { + SpawnInContainerOrDrop(proto, entity, container, xform, entity.Comp); + } + } + } +} + +/// +public sealed partial class SpawnEntityInContainerOrDrop : BaseSpawnEntityEntityEffect +{ + /// + /// Name of the container we're trying to spawn into. + /// + [DataField(required: true)] + public string ContainerName; +} diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs new file mode 100644 index 0000000000..7514dae4e9 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs @@ -0,0 +1,35 @@ +using Content.Shared.Inventory; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.EntitySpawning; + +/// +/// Spawns an entity of a given prototype in a specified inventory slot owned by this entity. +/// Fails if it cannot spawn the entity in the given slot. +/// +/// +public sealed partial class SpawnEntityInInventoryEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly InventorySystem _inventory = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _inventory.SpawnItemInSlot(entity, args.Effect.Slot, args.Effect.Entity); + } +} + +/// +public sealed partial class SpawnEntityInInventory : EntityEffectBase +{ + /// + /// Name of the slot we're spawning the item into. + /// + [DataField(required: true)] + public string Slot = string.Empty; // Rider is drunk and keeps yelling at me to fill this out or make required: true but, it is required true so it's just being an asshole. + + /// + /// Prototype ID of item to spawn. + /// + [DataField(required: true)] + public EntProtoId Entity; +} diff --git a/Content.Shared/EntityEffects/Effects/EvenHealthChange.cs b/Content.Shared/EntityEffects/Effects/EvenHealthChange.cs deleted file mode 100644 index 968d559939..0000000000 --- a/Content.Shared/EntityEffects/Effects/EvenHealthChange.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Content.Shared.Damage; -using Content.Shared.Damage.Prototypes; -using Content.Shared.EntityEffects; -using Content.Shared.FixedPoint; -using Content.Shared.Localizations; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Version of that distributes the healing to groups -/// -public sealed partial class EvenHealthChange : EntityEffect -{ - /// - /// Damage to heal, collected into entire damage groups. - /// - [DataField(required: true)] - public Dictionary, FixedPoint2> Damage = new(); - - /// - /// Should this effect scale the damage by the amount of chemical in the solution? - /// Useful for touch reactions, like styptic powder or acid. - /// Only usable if the EntityEffectBaseArgs is an EntityEffectReagentArgs. - /// - [DataField] - public bool ScaleByQuantity; - - /// - /// Should this effect ignore damage modifiers? - /// - [DataField] - public bool IgnoreResistances = true; - - protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - var damages = new List(); - var heals = false; - var deals = false; - - var damagableSystem = entSys.GetEntitySystem(); - var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier; - var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier; - - foreach (var (group, amount) in Damage) - { - var groupProto = prototype.Index(group); - - var sign = FixedPoint2.Sign(amount); - var mod = 1f; - - if (sign < 0) - { - heals = true; - mod = universalReagentHealModifier; - } - else if (sign > 0) - { - deals = true; - mod = universalReagentDamageModifier; - } - - damages.Add( - Loc.GetString("health-change-display", - ("kind", groupProto.LocalizedName), - ("amount", MathF.Abs(amount.Float() * mod)), - ("deltasign", sign) - )); - } - - var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none"); - return Loc.GetString("reagent-effect-guidebook-even-health-change", - ("chance", Probability), - ("changes", ContentLocalizationManager.FormatList(damages)), - ("healsordeals", healsordeals)); - } - - public override void Effect(EntityEffectBaseArgs args) - { - if (!args.EntityManager.TryGetComponent(args.TargetEntity, out var damageable)) - return; - - var protoMan = IoCManager.Resolve(); - - var scale = FixedPoint2.New(1); - - if (args is EntityEffectReagentArgs reagentArgs) - { - scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale; - } - - var damagableSystem = args.EntityManager.System(); - var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier; - var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier; - - var dspec = new DamageSpecifier(); - - foreach (var (group, amount) in Damage) - { - var groupProto = protoMan.Index(group); - var groupDamage = new Dictionary(); - foreach (var damageId in groupProto.DamageTypes) - { - var damageAmount = damageable.Damage.DamageDict.GetValueOrDefault(damageId); - if (damageAmount != FixedPoint2.Zero) - groupDamage.Add(damageId, damageAmount); - } - - var sum = groupDamage.Values.Sum(); - foreach (var (damageId, damageAmount) in groupDamage) - { - var existing = dspec.DamageDict.GetOrNew(damageId); - dspec.DamageDict[damageId] = existing + damageAmount / sum * amount; - } - } - - if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1) - { - foreach (var (type, val) in dspec.DamageDict) - { - if (val < 0f) - { - dspec.DamageDict[type] = val * universalReagentHealModifier; - } - if (val > 0f) - { - dspec.DamageDict[type] = val * universalReagentDamageModifier; - } - } - } - - damagableSystem.TryChangeDamage( - args.TargetEntity, - dspec * scale, - IgnoreResistances, - interruptsDoAfters: false); - } -} diff --git a/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs new file mode 100644 index 0000000000..33b2041b37 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs @@ -0,0 +1,114 @@ +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Localizations; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// Evenly adjust the damage types in a damage group by up to a specified total on this entity. +/// Total adjustment is modified by scale. +/// +/// +public sealed partial class EvenHealthChangeEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var damageSpec = new DamageSpecifier(); + + foreach (var (group, amount) in args.Effect.Damage) + { + var groupProto = _proto.Index(group); + var groupDamage = new Dictionary(); + foreach (var damageId in groupProto.DamageTypes) + { + var damageAmount = entity.Comp.Damage.DamageDict.GetValueOrDefault(damageId); + if (damageAmount != FixedPoint2.Zero) + groupDamage.Add(damageId, damageAmount); + } + + var sum = groupDamage.Values.Sum(); + foreach (var (damageId, damageAmount) in groupDamage) + { + var existing = damageSpec.DamageDict.GetOrNew(damageId); + damageSpec.DamageDict[damageId] = existing + damageAmount / sum * amount; + } + } + + damageSpec *= args.Scale; + + _damageable.TryChangeDamage( + entity, + damageSpec, + args.Effect.IgnoreResistances, + interruptsDoAfters: false, + damageable: entity.Comp); + } +} + +/// +public sealed partial class EvenHealthChange : EntityEffectBase +{ + /// + /// Damage to heal, collected into entire damage groups. + /// + [DataField(required: true)] + public Dictionary, FixedPoint2> Damage = new(); + + /// + /// Should this effect ignore damage modifiers? + /// + [DataField] + public bool IgnoreResistances = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + var damages = new List(); + var heals = false; + var deals = false; + + var damagableSystem = entSys.GetEntitySystem(); + var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier; + var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier; + + foreach (var (group, amount) in Damage) + { + var groupProto = prototype.Index(group); + + var sign = FixedPoint2.Sign(amount); + float mod; + + switch (sign) + { + case < 0: + heals = true; + mod = universalReagentHealModifier; + break; + case > 0: + deals = true; + mod = universalReagentDamageModifier; + break; + default: + continue; // Don't need to show damage types of 0... + } + + damages.Add( + Loc.GetString("health-change-display", + ("kind", groupProto.LocalizedName), + ("amount", MathF.Abs(amount.Float() * mod)), + ("deltasign", sign) + )); + } + + var healsordeals = heals ? deals ? "both" : "heals" : deals ? "deals" : "none"; + return Loc.GetString("entity-effect-guidebook-even-health-change", + ("chance", Probability), + ("changes", ContentLocalizationManager.FormatList(damages)), + ("healsordeals", healsordeals)); + } +} diff --git a/Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs b/Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs deleted file mode 100644 index 11e776ac90..0000000000 --- a/Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Shared.Atmos; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects -{ - public sealed partial class ExtinguishReaction : EntityEffect - { - /// - /// Amount of firestacks reduced. - /// - [DataField] - public float FireStacksAdjustment = -1.5f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-extinguish-reaction", ("chance", Probability)); - - public override void Effect(EntityEffectBaseArgs args) - { - var ev = new ExtinguishEvent - { - FireStacksAdjustment = FireStacksAdjustment, - }; - - if (args is EntityEffectReagentArgs reagentArgs) - { - ev.FireStacksAdjustment *= (float)reagentArgs.Quantity; - } - - args.EntityManager.EventBus.RaiseLocalEvent(args.TargetEntity, ref ev); - } - } -} diff --git a/Content.Shared/EntityEffects/Effects/FlammableReaction.cs b/Content.Shared/EntityEffects/Effects/FlammableReaction.cs deleted file mode 100644 index 9f7d504f4e..0000000000 --- a/Content.Shared/EntityEffects/Effects/FlammableReaction.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared.Database; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class FlammableReaction : EventEntityEffect -{ - [DataField] - public float Multiplier = 0.05f; - - // The fire stack multiplier if fire stacks already exist on target, only works if 0 or greater - [DataField] - public float MultiplierOnExisting = -1f; - - public override bool ShouldLog => true; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-flammable-reaction", ("chance", Probability)); - - public override LogImpact LogImpact => LogImpact.Medium; -} diff --git a/Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs b/Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs deleted file mode 100644 index c238e94010..0000000000 --- a/Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -[DataDefinition] -public sealed partial class FlashReactionEffect : EventEntityEffect -{ - /// - /// Flash range per unit of reagent. - /// - [DataField] - public float RangePerUnit = 0.2f; - - /// - /// Maximum flash range. - /// - [DataField] - public float MaxRange = 10f; - - /// - /// How much to entities are slowed down. - /// - [DataField] - public float SlowTo = 0.5f; - - /// - /// The time entities will be flashed. - /// The default is chosen to be better than the hand flash so it is worth using it for grenades etc. - /// - [DataField] - public TimeSpan Duration = TimeSpan.FromSeconds(4); - - /// - /// The prototype ID used for the visual effect. - /// - [DataField] - public EntProtoId? FlashEffectPrototype = "ReactionFlash"; - - /// - /// The sound the flash creates. - /// - [DataField] - public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg"); - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-flash-reaction-effect", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/Glow.cs b/Content.Shared/EntityEffects/Effects/Glow.cs deleted file mode 100644 index 394d406700..0000000000 --- a/Content.Shared/EntityEffects/Effects/Glow.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Content.Shared.EntityEffects; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Makes a mob glow. -/// -public sealed partial class Glow : EntityEffect -{ - [DataField] - public float Radius = 2f; - - [DataField] - public Color Color = Color.Black; - - private static readonly List Colors = new() - { - Color.White, - Color.Red, - Color.Yellow, - Color.Green, - Color.Blue, - Color.Purple, - Color.Pink - }; - - public override void Effect(EntityEffectBaseArgs args) - { - if (Color == Color.Black) - { - var random = IoCManager.Resolve(); - Color = random.Pick(Colors); - } - - var lightSystem = args.EntityManager.System(); - var light = lightSystem.EnsureLight(args.TargetEntity); - lightSystem.SetRadius(args.TargetEntity, Radius, light); - lightSystem.SetColor(args.TargetEntity, Color, light); - lightSystem.SetCastShadows(args.TargetEntity, false, light); // this is expensive, and botanists make lots of plants - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return "TODO"; - } -} diff --git a/Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs new file mode 100644 index 0000000000..05e4fe29ca --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs @@ -0,0 +1,55 @@ +using Robust.Shared.Network; +using Robust.Shared.Random; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// Causes this entity to glow. +/// +/// +public sealed partial class GlowEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedPointLightSystem _lightSystem = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var color = args.Effect.Color; + + if (color == Color.Black) + { + // TODO: When we get proper predicted RNG remove this check... + if (_net.IsClient) + return; + + color = _random.Pick(Colors); + } + + var light = _lightSystem.EnsureLight(entity); + _lightSystem.SetRadius(entity, args.Effect.Radius, light); + _lightSystem.SetColor(entity, color, light); + _lightSystem.SetCastShadows(entity, false, light); // this is expensive, and botanists make lots of plants + } + + public static readonly List Colors = new() + { + Color.White, + Color.Red, + Color.Yellow, + Color.Green, + Color.Blue, + Color.Purple, + Color.Pink + }; +} + +/// +public sealed partial class Glow : EntityEffectBase +{ + [DataField] + public float Radius = 2f; + + [DataField] + public Color Color = Color.Black; +} diff --git a/Content.Shared/EntityEffects/Effects/HealthChange.cs b/Content.Shared/EntityEffects/Effects/HealthChange.cs deleted file mode 100644 index 17c24f6b5a..0000000000 --- a/Content.Shared/EntityEffects/Effects/HealthChange.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Content.Shared.Damage; -using Content.Shared.Damage.Prototypes; -using Content.Shared.EntityEffects; -using Content.Shared.FixedPoint; -using Content.Shared.Localizations; -using Robust.Shared.Prototypes; -using System.Linq; -using System.Text.Json.Serialization; - -namespace Content.Shared.EntityEffects.Effects -{ - /// - /// Default metabolism used for medicine reagents. - /// - public sealed partial class HealthChange : EntityEffect - { - /// - /// Damage to apply every cycle. Damage Ignores resistances. - /// - [DataField(required: true)] - [JsonPropertyName("damage")] - public DamageSpecifier Damage = default!; - - /// - /// Should this effect scale the damage by the amount of chemical in the solution? - /// Useful for touch reactions, like styptic powder or acid. - /// Only usable if the EntityEffectBaseArgs is an EntityEffectReagentArgs. - /// - [DataField] - [JsonPropertyName("scaleByQuantity")] - public bool ScaleByQuantity; - - [DataField] - [JsonPropertyName("ignoreResistances")] - public bool IgnoreResistances = true; - - protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - var damages = new List(); - var heals = false; - var deals = false; - - var damageSpec = new DamageSpecifier(Damage); - - var universalReagentDamageModifier = entSys.GetEntitySystem().UniversalReagentDamageModifier; - var universalReagentHealModifier = entSys.GetEntitySystem().UniversalReagentHealModifier; - - if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1) - { - foreach (var (type, val) in damageSpec.DamageDict) - { - if (val < 0f) - { - damageSpec.DamageDict[type] = val * universalReagentHealModifier; - } - if (val > 0f) - { - damageSpec.DamageDict[type] = val * universalReagentDamageModifier; - } - } - } - - damageSpec = entSys.GetEntitySystem().ApplyUniversalAllModifiers(damageSpec); - - foreach (var (kind, amount) in damageSpec.DamageDict) - { - var sign = FixedPoint2.Sign(amount); - - if (sign < 0) - heals = true; - if (sign > 0) - deals = true; - - damages.Add( - Loc.GetString("health-change-display", - ("kind", prototype.Index(kind).LocalizedName), - ("amount", MathF.Abs(amount.Float())), - ("deltasign", sign) - )); - } - - var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none"); - - return Loc.GetString("reagent-effect-guidebook-health-change", - ("chance", Probability), - ("changes", ContentLocalizationManager.FormatList(damages)), - ("healsordeals", healsordeals)); - } - - public override void Effect(EntityEffectBaseArgs args) - { - var scale = FixedPoint2.New(1); - var damageSpec = new DamageSpecifier(Damage); - - if (args is EntityEffectReagentArgs reagentArgs) - { - scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale; - } - - var universalReagentDamageModifier = args.EntityManager.System().UniversalReagentDamageModifier; - var universalReagentHealModifier = args.EntityManager.System().UniversalReagentHealModifier; - - if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1) - { - foreach (var (type, val) in damageSpec.DamageDict) - { - if (val < 0f) - { - damageSpec.DamageDict[type] = val * universalReagentHealModifier; - } - if (val > 0f) - { - damageSpec.DamageDict[type] = val * universalReagentDamageModifier; - } - } - } - - args.EntityManager.System() - .TryChangeDamage( - args.TargetEntity, - damageSpec * scale, - IgnoreResistances, - interruptsDoAfters: false); - } - } -} diff --git a/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs new file mode 100644 index 0000000000..89948cd2e4 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs @@ -0,0 +1,91 @@ +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Localizations; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// Adjust the damages on this entity by specified amounts. +/// Amounts are modified by scale. +/// +/// +public sealed partial class HealthChangeEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var damageSpec = new DamageSpecifier(args.Effect.Damage); + + damageSpec *= args.Scale; + + _damageable.TryChangeDamage( + entity, + damageSpec, + args.Effect.IgnoreResistances, + interruptsDoAfters: false); + } +} + +/// +public sealed partial class HealthChange : EntityEffectBase +{ + /// + /// Damage to apply every cycle. Damage Ignores resistances. + /// + [DataField(required: true)] + public DamageSpecifier Damage = default!; + + [DataField] + public bool IgnoreResistances = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + var damages = new List(); + var heals = false; + var deals = false; + + var damageSpec = new DamageSpecifier(Damage); + + var universalReagentDamageModifier = entSys.GetEntitySystem().UniversalReagentDamageModifier; + var universalReagentHealModifier = entSys.GetEntitySystem().UniversalReagentHealModifier; + + damageSpec = entSys.GetEntitySystem().ApplyUniversalAllModifiers(damageSpec); + + foreach (var (kind, amount) in damageSpec.DamageDict) + { + var sign = FixedPoint2.Sign(amount); + float mod; + + switch (sign) + { + case < 0: + heals = true; + mod = universalReagentHealModifier; + break; + case > 0: + deals = true; + mod = universalReagentDamageModifier; + break; + default: + continue; // Don't need to show damage types of 0... + } + + damages.Add( + Loc.GetString("health-change-display", + ("kind", prototype.Index(kind).LocalizedName), + ("amount", MathF.Abs(amount.Float() * mod)), + ("deltasign", sign) + )); + } + + var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none"); + + return Loc.GetString("entity-effect-guidebook-health-change", + ("chance", Probability), + ("changes", ContentLocalizationManager.FormatList(damages)), + ("healsordeals", healsordeals)); + } +} diff --git a/Content.Shared/EntityEffects/Effects/Ignite.cs b/Content.Shared/EntityEffects/Effects/Ignite.cs deleted file mode 100644 index 707ecc3208..0000000000 --- a/Content.Shared/EntityEffects/Effects/Ignite.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Database; -using Content.Shared.EntityEffects; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Ignites a mob. -/// -public sealed partial class Ignite : EventEntityEffect -{ - public override bool ShouldLog => true; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-ignite", ("chance", Probability)); - - public override LogImpact LogImpact => LogImpact.Medium; -} diff --git a/Content.Shared/EntityEffects/Effects/MakeSentient.cs b/Content.Shared/EntityEffects/Effects/MakeSentient.cs deleted file mode 100644 index 9c70eb452b..0000000000 --- a/Content.Shared/EntityEffects/Effects/MakeSentient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Content.Shared.Mind.Components; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class MakeSentient : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs b/Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs new file mode 100644 index 0000000000..4beb21454c --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +public sealed partial class MakeSentient : EntityEffectBase +{ + /// + /// Description for the ghost role created by this effect. + /// + [DataField] + public LocId RoleDescription = "ghost-role-information-cognizine-description"; + + /// + /// Whether we give the target the ability to speak coherently. + /// + [DataField] + public bool AllowSpeech = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-make-sentient", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs b/Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs deleted file mode 100644 index 9f15652095..0000000000 --- a/Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class ModifyBleedAmount : EventEntityEffect -{ - [DataField] - public bool Scaled = false; - - [DataField] - public float Amount = -1.0f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability), - ("deltasign", MathF.Sign(Amount))); -} diff --git a/Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs b/Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs deleted file mode 100644 index 06c026f128..0000000000 --- a/Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.FixedPoint; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class ModifyBloodLevel : EventEntityEffect -{ - [DataField] - public bool Scaled = false; - - [DataField] - public FixedPoint2 Amount = 1.0f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability), - ("deltasign", MathF.Sign(Amount.Float()))); -} diff --git a/Content.Shared/EntityEffects/Effects/ModifyLungGas.cs b/Content.Shared/EntityEffects/Effects/ModifyLungGas.cs deleted file mode 100644 index 45dc8c84c6..0000000000 --- a/Content.Shared/EntityEffects/Effects/ModifyLungGas.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Content.Shared.Atmos; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class ModifyLungGas : EventEntityEffect -{ - [DataField("ratios", required: true)] - public Dictionary Ratios = default!; - - // JUSTIFICATION: This is internal magic that players never directly interact with. - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => null; -} diff --git a/Content.Shared/EntityEffects/Effects/MovespeedModifier.cs b/Content.Shared/EntityEffects/Effects/MovespeedModifier.cs deleted file mode 100644 index 5e72746e32..0000000000 --- a/Content.Shared/EntityEffects/Effects/MovespeedModifier.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Content.Shared.Chemistry.Components; -using Content.Shared.Movement.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target, -/// adding one if not there and to change the movespeed -/// -public sealed partial class MovespeedModifier : EntityEffect -{ - /// - /// How much the entities' walk speed is multiplied by. - /// - [DataField] - public float WalkSpeedModifier { get; set; } = 1; - - /// - /// How much the entities' run speed is multiplied by. - /// - [DataField] - public float SprintSpeedModifier { get; set; } = 1; - - /// - /// How long the modifier applies (in seconds). - /// Is scaled by reagent amount if used with an EntityEffectReagentArgs. - /// - [DataField] - public float StatusLifetime = 2f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return Loc.GetString("reagent-effect-guidebook-movespeed-modifier", - ("chance", Probability), - ("walkspeed", WalkSpeedModifier), - ("time", StatusLifetime)); - } - - /// - /// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there. - /// - public override void Effect(EntityEffectBaseArgs args) - { - var status = args.EntityManager.EnsureComponent(args.TargetEntity); - - // Only refresh movement if we need to. - var modified = !status.WalkSpeedModifier.Equals(WalkSpeedModifier) || - !status.SprintSpeedModifier.Equals(SprintSpeedModifier); - - status.WalkSpeedModifier = WalkSpeedModifier; - status.SprintSpeedModifier = SprintSpeedModifier; - - // only going to scale application time - var statusLifetime = StatusLifetime; - - if (args is EntityEffectReagentArgs reagentArgs) - { - statusLifetime *= reagentArgs.Scale.Float(); - } - - IncreaseTimer(status, statusLifetime, args.EntityManager, args.TargetEntity); - - if (modified) - args.EntityManager.System().RefreshMovementSpeedModifiers(args.TargetEntity); - } - private void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time, IEntityManager entityManager, EntityUid uid) - { - var gameTiming = IoCManager.Resolve(); - - var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds); - - status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time); - - entityManager.Dirty(uid, status); - } -} diff --git a/Content.Shared/EntityEffects/Effects/Oxygenate.cs b/Content.Shared/EntityEffects/Effects/Oxygenate.cs deleted file mode 100644 index e990a3fec6..0000000000 --- a/Content.Shared/EntityEffects/Effects/Oxygenate.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class Oxygenate : EventEntityEffect -{ - [DataField] - public float Factor = 1f; - - // JUSTIFICATION: This is internal magic that players never directly interact with. - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => null; -} diff --git a/Content.Shared/EntityEffects/Effects/Paralyze.cs b/Content.Shared/EntityEffects/Effects/Paralyze.cs deleted file mode 100644 index 2a2270016b..0000000000 --- a/Content.Shared/EntityEffects/Effects/Paralyze.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Shared.Stunnable; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class Paralyze : EntityEffect -{ - [DataField] public double ParalyzeTime = 2; - - /// - /// true - refresh paralyze time, false - accumulate paralyze time - /// - [DataField] public bool Refresh = true; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString( - "reagent-effect-guidebook-paralyze", - ("chance", Probability), - ("time", ParalyzeTime) - ); - - public override void Effect(EntityEffectBaseArgs args) - { - var paralyzeTime = ParalyzeTime; - - if (args is EntityEffectReagentArgs reagentArgs) - { - paralyzeTime *= (double)reagentArgs.Scale; - } - - var stunSystem = args.EntityManager.System(); - _ = Refresh - ? stunSystem.TryUpdateParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime)) - : stunSystem.TryAddParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime)); - } -} - diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs deleted file mode 100644 index 2c8046452b..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Shared.EntityEffects; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using System.Diagnostics.CodeAnalysis; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -[ImplicitDataDefinitionForInheritors] -public abstract partial class PlantAdjustAttribute : EventEntityEffect where T : PlantAdjustAttribute -{ - [DataField] - public float Amount { get; protected set; } = 1; - - /// - /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions. - /// - [DataField] - public abstract string GuidebookAttributeName { get; set; } - - /// - /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number. - /// - [DataField] - public virtual bool GuidebookIsAttributePositive { get; protected set; } = true; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - string color; - if (GuidebookIsAttributePositive ^ Amount < 0.0) - { - color = "green"; - } - else - { - color = "red"; - } - return Loc.GetString("reagent-effect-guidebook-plant-attribute", ("attribute", Loc.GetString(GuidebookAttributeName)), ("amount", Amount.ToString("0.00")), ("colorName", color), ("chance", Probability)); - } -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs deleted file mode 100644 index c1e71894e5..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantAdjustHealth : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-health"; -} - diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs deleted file mode 100644 index 6610adf708..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level"; -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs deleted file mode 100644 index 91be222807..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod"; -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs deleted file mode 100644 index db01d5d060..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition"; -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs deleted file mode 100644 index 971f05f32d..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs +++ /dev/null @@ -1,12 +0,0 @@ -// using Content.Server.Botany.Systems; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -/// -/// Handles increase or decrease of plant potency. -/// - -public sealed partial class PlantAdjustPotency : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency"; -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs deleted file mode 100644 index 610d02231b..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantAdjustWater : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-water"; -} - diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs deleted file mode 100644 index 36b8f57d08..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantAffectGrowth : PlantAdjustAttribute -{ - public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth"; -} - diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs deleted file mode 100644 index 66cfb66d4b..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantChangeStat : EventEntityEffect -{ - [DataField] - public string TargetValue; - - [DataField] - public float MinValue; - - [DataField] - public float MaxValue; - - [DataField] - public int Steps; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - throw new NotImplementedException(); - } -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs deleted file mode 100644 index 0dabf1f22b..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantCryoxadone : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs deleted file mode 100644 index 9f16c35402..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -/// -/// Handles removal of seeds on a plant. -/// - -public sealed partial class PlantDestroySeeds : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => - Loc.GetString("reagent-effect-guidebook-plant-seeds-remove", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs deleted file mode 100644 index 9feccbf224..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantDiethylamine : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability)); -} - diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs deleted file mode 100644 index 9dc5140063..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class PlantPhalanximine : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs deleted file mode 100644 index 51ce353dbb..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -/// -/// Handles restoral of seeds on a plant. -/// -public sealed partial class PlantRestoreSeeds : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => - Loc.GetString("reagent-effect-guidebook-plant-seeds-add", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs deleted file mode 100644 index 6ba37c9be0..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Shared.EntityEffects.Effects.PlantMetabolism; - -public sealed partial class RobustHarvest : EventEntityEffect -{ - [DataField] - public int PotencyLimit = 50; - - [DataField] - public int PotencyIncrease = 3; - - [DataField] - public int PotencySeedlessThreshold = 30; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-robust-harvest", ("seedlesstreshold", PotencySeedlessThreshold), ("limit", PotencyLimit), ("increase", PotencyIncrease), ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs b/Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs deleted file mode 100644 index 9a3408bb8e..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Random; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// changes the chemicals available in a plant's produce -/// -public sealed partial class PlantMutateChemicals : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return "TODO"; - } -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMutateGases.cs b/Content.Shared/EntityEffects/Effects/PlantMutateGases.cs deleted file mode 100644 index 5eb5b1d07c..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMutateGases.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using System.Linq; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// changes the gases that a plant or produce create. -/// -public sealed partial class PlantMutateExudeGasses : EventEntityEffect -{ - [DataField] - public float MinValue = 0.01f; - - [DataField] - public float MaxValue = 0.5f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return "TODO"; - } -} - -/// -/// changes the gases that a plant or produce consumes. -/// -public sealed partial class PlantMutateConsumeGasses : EventEntityEffect -{ - [DataField] - public float MinValue = 0.01f; - - [DataField] - public float MaxValue = 0.5f; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return "TODO"; - } -} diff --git a/Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs b/Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs deleted file mode 100644 index 84d6293185..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Upgrades a plant's harvest type. -/// -public sealed partial class PlantMutateHarvest : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return "TODO"; - } -} diff --git a/Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs b/Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs deleted file mode 100644 index e2acc4cb7c..0000000000 --- a/Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Changes a plant into one of the species its able to mutate into. -/// -public sealed partial class PlantSpeciesChange : EventEntityEffect -{ - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - return "TODO"; - } -} diff --git a/Content.Shared/EntityEffects/Effects/Polymorph.cs b/Content.Shared/EntityEffects/Effects/Polymorph.cs deleted file mode 100644 index 65711ff99a..0000000000 --- a/Content.Shared/EntityEffects/Effects/Polymorph.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Polymorph; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.EntityEffects.Effects; - -public sealed partial class Polymorph : EventEntityEffect -{ - /// - /// What polymorph prototype is used on effect - /// - [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string PolymorphPrototype { get; set; } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-make-polymorph", - ("chance", Probability), ("entityname", - prototype.Index(prototype.Index(PolymorphPrototype).Configuration.Entity).Name)); -} diff --git a/Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs b/Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs new file mode 100644 index 0000000000..c0d80dffaa --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs @@ -0,0 +1,19 @@ +using Content.Shared.Polymorph; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +public sealed partial class Polymorph : EntityEffectBase +{ + /// + /// What polymorph prototype is used on effect + /// + [DataField(required: true)] + public ProtoId Prototype; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-make-polymorph", + ("chance", Probability), + ("entityname", prototype.Index(prototype.Index(Prototype).Configuration.Entity).Name)); +} diff --git a/Content.Shared/EntityEffects/Effects/PopupMessage.cs b/Content.Shared/EntityEffects/Effects/PopupMessage.cs deleted file mode 100644 index a837f816e4..0000000000 --- a/Content.Shared/EntityEffects/Effects/PopupMessage.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Content.Shared.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Shared.EntityEffects.Effects -{ - public sealed partial class PopupMessage : EntityEffect - { - [DataField(required: true)] - public string[] Messages = default!; - - [DataField] - public PopupRecipients Type = PopupRecipients.Local; - - [DataField] - public PopupType VisualType = PopupType.Small; - - // JUSTIFICATION: This is purely cosmetic. - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => null; - - public override void Effect(EntityEffectBaseArgs args) - { - var popupSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - var random = IoCManager.Resolve(); - - var msg = random.Pick(Messages); - var msgArgs = new (string, object)[] - { - ("entity", args.TargetEntity), - }; - - if (args is EntityEffectReagentArgs reagentArgs) - { - msgArgs = new (string, object)[] - { - ("entity", reagentArgs.TargetEntity), - ("organ", reagentArgs.OrganEntity.GetValueOrDefault()), - }; - } - - if (Type == PopupRecipients.Local) - popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.TargetEntity, args.TargetEntity, VisualType); - else if (Type == PopupRecipients.Pvs) - popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.TargetEntity, VisualType); - } - } - - public enum PopupRecipients - { - Pvs, - Local - } -} diff --git a/Content.Shared/EntityEffects/Effects/ReduceRotting.cs b/Content.Shared/EntityEffects/Effects/ReduceRotting.cs deleted file mode 100644 index b5f2a7ae7e..0000000000 --- a/Content.Shared/EntityEffects/Effects/ReduceRotting.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; -using Content.Shared.Atmos.Rotting; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Reduces the rotting accumulator on the patient, making them revivable. -/// -public sealed partial class ReduceRotting : EntityEffect -{ - [DataField("seconds")] - public double RottingAmount = 10; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-reduce-rotting", - ("chance", Probability), - ("time", RottingAmount)); - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - if (reagentArgs.Scale != 1f) - return; - } - - var rottingSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - - rottingSys.ReduceAccumulator(args.TargetEntity, TimeSpan.FromSeconds(RottingAmount)); - } -} diff --git a/Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs b/Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs deleted file mode 100644 index 009cf914d5..0000000000 --- a/Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Reset narcolepsy timer -/// -public sealed partial class ResetNarcolepsy : EventEntityEffect -{ - /// - /// The # of seconds the effect resets the narcolepsy timer to - /// - [DataField("TimerReset")] - public TimeSpan TimerReset = TimeSpan.FromSeconds(600); - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-reset-narcolepsy", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs new file mode 100644 index 0000000000..973b139fa4 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Traits.Assorted; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// Resets the narcolepsy timer on a given entity. +/// The new duration of the timer is modified by scale. +/// +/// +public sealed partial class ResetNarcolepsyEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly NarcolepsySystem _narcolepsy = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var timer = args.Effect.TimerReset * args.Scale; + + _narcolepsy.AdjustNarcolepsyTimer(entity.AsNullable(), timer); + } +} + +/// +public sealed partial class ResetNarcolepsy : EntityEffectBase +{ + /// + /// The time we set our narcolepsy timer to. + /// + [DataField("TimerReset")] + public TimeSpan TimerReset = TimeSpan.FromSeconds(600); + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-reset-narcolepsy", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/SatiateHunger.cs b/Content.Shared/EntityEffects/Effects/SatiateHunger.cs deleted file mode 100644 index 3e7af8833c..0000000000 --- a/Content.Shared/EntityEffects/Effects/SatiateHunger.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Nutrition.Components; -using Content.Shared.Nutrition.EntitySystems; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Attempts to find a HungerComponent on the target, -/// and to update it's hunger values. -/// -public sealed partial class SatiateHunger : EntityEffect -{ - private const float DefaultNutritionFactor = 3.0f; - - /// - /// How much hunger is satiated. - /// Is multiplied by quantity if used with EntityEffectReagentArgs. - /// - [DataField("factor")] public float NutritionFactor { get; set; } = DefaultNutritionFactor; - - //Remove reagent at set rate, satiate hunger if a HungerComponent can be found - public override void Effect(EntityEffectBaseArgs args) - { - var entman = args.EntityManager; - if (!entman.TryGetComponent(args.TargetEntity, out HungerComponent? hunger)) - return; - if (args is EntityEffectReagentArgs reagentArgs) - { - entman.System().ModifyHunger(reagentArgs.TargetEntity, NutritionFactor * (float) reagentArgs.Quantity, hunger); - } - else - { - entman.System().ModifyHunger(args.TargetEntity, NutritionFactor, hunger); - } - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", NutritionFactor / DefaultNutritionFactor)); -} diff --git a/Content.Shared/EntityEffects/Effects/SatiateThirst.cs b/Content.Shared/EntityEffects/Effects/SatiateThirst.cs deleted file mode 100644 index 21a055b528..0000000000 --- a/Content.Shared/EntityEffects/Effects/SatiateThirst.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Nutrition.Components; -using Content.Shared.Nutrition.EntitySystems; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target, -/// and to update it's thirst values. -/// -public sealed partial class SatiateThirst : EntityEffect -{ - private const float DefaultHydrationFactor = 3.0f; - - /// How much thirst is satiated each tick. Not currently tied to - /// rate or anything. - [DataField("factor")] - public float HydrationFactor { get; set; } = DefaultHydrationFactor; - - /// Satiate thirst if a ThirstComponent can be found - public override void Effect(EntityEffectBaseArgs args) - { - var uid = args.TargetEntity; - if (args.EntityManager.TryGetComponent(uid, out ThirstComponent? thirst)) - args.EntityManager.System().ModifyThirst(uid, thirst, HydrationFactor); - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", HydrationFactor / DefaultHydrationFactor)); -} diff --git a/Content.Shared/EntityEffects/Effects/Slipify.cs b/Content.Shared/EntityEffects/Effects/Slipify.cs deleted file mode 100644 index c152b8b010..0000000000 --- a/Content.Shared/EntityEffects/Effects/Slipify.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Shared.Physics; -using Content.Shared.Slippery; -using Content.Shared.StepTrigger.Components; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Makes a mob slippery. -/// -public sealed partial class Slipify : EntityEffect -{ - public override void Effect(EntityEffectBaseArgs args) - { - var fixtureSystem = args.EntityManager.System(); - var colWakeSystem = args.EntityManager.System(); - var slippery = args.EntityManager.EnsureComponent(args.TargetEntity); - args.EntityManager.Dirty(args.TargetEntity, slippery); - args.EntityManager.EnsureComponent(args.TargetEntity); - // Need a fixture with a slip layer in order to actually do the slipping - var fixtures = args.EntityManager.EnsureComponent(args.TargetEntity); - var body = args.EntityManager.EnsureComponent(args.TargetEntity); - var shape = fixtures.Fixtures["fix1"].Shape; - fixtureSystem.TryCreateFixture(args.TargetEntity, shape, "slips", 1, false, (int)CollisionGroup.SlipLayer, manager: fixtures, body: body); - // Need to disable collision wake so that mobs can collide with and slip on it - var collisionWake = args.EntityManager.EnsureComponent(args.TargetEntity); - colWakeSystem.SetEnabled(args.TargetEntity, false, collisionWake); - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - { - throw new NotImplementedException(); - } -} diff --git a/Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs new file mode 100644 index 0000000000..602e5e923a --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs @@ -0,0 +1,44 @@ +using System.Linq; +using Content.Shared.Physics; +using Content.Shared.Slippery; +using Content.Shared.StepTrigger.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Systems; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// This effect permanently creates a slippery fixture for this entity and then makes this entity slippery like soap. +/// +/// +public sealed partial class SlipifyEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly CollisionWakeSystem _collisionWake = default!; + [Dependency] private readonly FixtureSystem _fixture = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + EnsureComp(entity, out var slippery); + slippery.SlipData = args.Effect.Slippery; + + Dirty(entity, slippery); + + EnsureComp(entity); + + if (entity.Comp.Fixtures.FirstOrDefault(x => x.Value.Hard).Value.Shape is not { } shape) + return; + + _fixture.TryCreateFixture(entity, shape, "slips", 1, false, (int)CollisionGroup.SlipLayer, manager: entity.Comp); + + // Need to disable collision wake so that mobs can collide with and slip on it + EnsureComp(entity, out var collisionWake); + _collisionWake.SetEnabled(entity, false, collisionWake); + } +} + +/// +public sealed partial class Slipify : EntityEffectBase +{ + [DataField] + public SlipperyEffectEntry Slippery = new(); +} diff --git a/Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs new file mode 100644 index 0000000000..ef96723cff --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs @@ -0,0 +1,59 @@ +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Solution; + +// TODO: This should be removed and changed to an "AbsorbentSolutionComponent" +/// +/// Creates a reagent in a specified solution owned by this entity. +/// Quantity is modified by scale. +/// +/// +public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var solution = args.Effect.Solution; + var reagent = args.Effect.Reagent; + + if (!_solutionContainer.TryGetSolution((entity, entity), solution, out var solutionContainer)) + return; + + _solutionContainer.TryAddReagent(solutionContainer.Value, reagent, args.Scale * args.Effect.StrengthModifier); + } +} + +/// +public sealed partial class AddReagentToSolution : EntityEffectBase +{ + /// + /// Prototype of the reagent we're adding. + /// + [DataField(required: true)] + public ProtoId Reagent; + + /// + /// Solution we're looking for + /// + [DataField(required: true)] + public string? Solution = "reagents"; + + /// + /// A modifier for how much reagent we're creating. + /// + [DataField] + public float StrengthModifier = 1.0f; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return prototype.Resolve(Reagent, out ReagentPrototype? proto) + ? Loc.GetString("entity-effect-guidebook-add-to-solution-reaction", + ("chance", Probability), + ("reagent", proto.LocalizedName)) + : null; + } +} diff --git a/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs new file mode 100644 index 0000000000..334a63a95a --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs @@ -0,0 +1,52 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Solution; + +/// +/// Adjust a reagent in this solution by an amount modified by scale. +/// Quantity is modified by scale. +/// +/// +public sealed partial class AdjustReagentEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var quantity = args.Effect.Amount * args.Scale; + var reagent = args.Effect.Reagent; + + if (quantity > 0) + _solutionContainer.TryAddReagent(entity, reagent, quantity); + else + _solutionContainer.RemoveReagent(entity, reagent, -quantity); + } +} + +/// +public sealed partial class AdjustReagent : EntityEffectBase +{ + /// + /// The reagent ID to add or remove. + /// + [DataField(required: true)] + public ProtoId Reagent; + + [DataField(required: true)] + public FixedPoint2 Amount; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + return prototype.Resolve(Reagent, out ReagentPrototype? proto) + ? Loc.GetString("entity-effect-guidebook-adjust-reagent-reagent", + ("chance", Probability), + ("deltasign", MathF.Sign(Amount.Float())), + ("reagent", proto.LocalizedName), + ("amount", MathF.Abs(Amount.Float()))) + : null; + } +} diff --git a/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs new file mode 100644 index 0000000000..51259956d8 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs @@ -0,0 +1,52 @@ +using Content.Shared.Body.Prototypes; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Solution; + +/// +/// Adjust all reagents in this solution which are metabolized by a given metabolism group. +/// Quantity is modified by scale, quantity is per reagent and not a total. +/// +/// +public sealed partial class AdjustReagentsByGroupEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var quantity = args.Effect.Amount * args.Scale; + var group = args.Effect.Group; + var solution = entity.Comp.Solution; + + foreach (var quant in solution.Contents.ToArray()) + { + var proto = _proto.Index(quant.Reagent.Prototype); + if (proto.Metabolisms == null || !proto.Metabolisms.ContainsKey(group)) + continue; + + if (quantity > 0) + _solutionContainer.TryAddReagent(entity, proto.ID, quantity); + else + _solutionContainer.RemoveReagent(entity, proto.ID, -quantity); + } + } +} + +/// +public sealed partial class AdjustReagentsByGroup : EntityEffectBase +{ + + /// + /// The metabolism group being adjusted. All reagents in an affected solution with this group will be adjusted. + /// + [DataField(required: true)] + public ProtoId Group; + + [DataField(required: true)] + public FixedPoint2 Amount; +} diff --git a/Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs new file mode 100644 index 0000000000..5fbe948360 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs @@ -0,0 +1,39 @@ +using Content.Shared.Database; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Solution; + +/// +public sealed partial class AreaReactionEffect : EntityEffectBase +{ + /// + /// How many seconds will the effect stay, counting after fully spreading. + /// + [DataField("duration")] public float Duration = 10; + + /// + /// How big of a reaction scale we need for 1 smoke entity. + /// + [DataField] public float OverflowThreshold = 2.5f; + + /// + /// The entity prototype that is being spread over an area. + /// + [DataField(required: true)] + public EntProtoId PrototypeId; + + /// + /// Sound that will get played when this reaction effect occurs. + /// + [DataField(required: true)] public SoundSpecifier Sound = default!; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-area-reaction", + ("duration", Duration) + ); + + public override bool ShouldLog => true; + + public override LogImpact LogImpact => LogImpact.High; +} diff --git a/Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs new file mode 100644 index 0000000000..c0188fdfb4 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs @@ -0,0 +1,145 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.Solution; + +// TODO: Energy conservation!!! Once HeatContainers are merged nuke this and everything in SolutionContainerSystem to respect energy conservation! +/// +/// Sets the temperature of this solution to a fixed value. +/// +/// +public sealed class SetSolutionTemperatureEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + _solutionContainer.SetTemperature(entity, args.Effect.Temperature); + } +} + +/// +public sealed partial class SetSolutionTemperature : EntityEffectBase +{ + /// + /// The temperature to set the solution to. + /// + [DataField(required: true)] + public float Temperature; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-set-solution-temperature-effect", + ("chance", Probability), + ("temperature", Temperature)); +} + +/// +/// Adjusts the temperature of this solution by a given amount. +/// The temperature adjustment is modified by scale. +/// +/// +public sealed class AdjustSolutionTemperatureEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var solution = entity.Comp.Solution; + var temperature = Math.Clamp(solution.Temperature + args.Scale * args.Effect.Delta, args.Effect.MinTemp, args.Effect.MaxTemp); + + _solutionContainer.SetTemperature(entity, temperature); + } +} + +/// +public sealed partial class AdjustSolutionTemperature : EntityEffectBase +{ + /// + /// The change in temperature. + /// + [DataField(required: true)] + public float Delta; + + /// + /// The minimum temperature this effect can reach. + /// + [DataField] + public float MinTemp; + + /// + /// The maximum temperature this effect can reach. + /// + [DataField] + public float MaxTemp = float.PositiveInfinity; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-adjust-solution-temperature-effect", + ("chance", Probability), + ("deltasign", MathF.Sign(Delta)), + ("mintemp", MinTemp), + ("maxtemp", MaxTemp)); +} + +/// +/// Adjusts the thermal energy of this solution by a given amount. +/// The energy adjustment is modified by scale. +/// +/// +public sealed class AdjustSolutionThermalEnergyEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var solution = entity.Comp.Solution; + + var delta = args.Scale * args.Effect.Delta; + + // Don't adjust thermal energy if we're already at or above max temperature. + switch (delta) + { + case > 0: + if (solution.Temperature >= args.Effect.MaxTemp) + return; + break; + case < 0: + if (solution.Temperature <= args.Effect.MinTemp) + return; + break; + default: + return; + } + + _solutionContainer.AddThermalEnergyClamped(entity, delta, args.Effect.MinTemp, args.Effect.MaxTemp); + } +} + +/// +public sealed partial class AdjustSolutionThermalEnergy : EntityEffectBase +{ + /// + /// The change in thermal energy. + /// + [DataField(required: true)] + public float Delta; + + /// + /// The minimum temperature this effect can reach. + /// + [DataField] + public float MinTemp; + + /// + /// The maximum temperature this effect can reach. + /// + [DataField] + public float MaxTemp = float.PositiveInfinity; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-adjust-solution-temperature-effect", + ("chance", Probability), + ("deltasign", MathF.Sign(Delta)), + ("mintemp", MinTemp), + ("maxtemp", MaxTemp)); +} diff --git a/Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs b/Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs deleted file mode 100644 index 30ac6c3d77..0000000000 --- a/Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects; - -/// -/// Sets the temperature of the solution involved with the reaction to a new value. -/// -[DataDefinition] -public sealed partial class SetSolutionTemperatureEffect : EntityEffect -{ - /// - /// The temperature to set the solution to. - /// - [DataField("temperature", required: true)] private float _temperature; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-set-solution-temperature-effect", - ("chance", Probability), ("temperature", _temperature)); - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - var solution = reagentArgs.Source; - if (solution == null) - return; - - solution.Temperature = _temperature; - - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } -} - -/// -/// Adjusts the temperature of the solution involved in the reaction. -/// -[DataDefinition] -public sealed partial class AdjustSolutionTemperatureEffect : EntityEffect -{ - /// - /// The change in temperature. - /// - [DataField("delta", required: true)] private float _delta; - - /// - /// The minimum temperature this effect can reach. - /// - [DataField("minTemp")] private float _minTemp = 0.0f; - - /// - /// The maximum temperature this effect can reach. - /// - [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity; - - /// - /// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount. - /// - [DataField("scaled")] private bool _scaled; - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect", - ("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp)); - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - var solution = reagentArgs.Source; - if (solution == null || solution.Volume == 0) - return; - - var deltaT = _scaled ? _delta * (float) reagentArgs.Quantity : _delta; - solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp); - - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } -} - -/// -/// Adjusts the thermal energy of the solution involved in the reaction. -/// -public sealed partial class AdjustSolutionThermalEnergyEffect : EntityEffect -{ - /// - /// The change in energy. - /// - [DataField("delta", required: true)] private float _delta; - - /// - /// The minimum temperature this effect can reach. - /// - [DataField("minTemp")] private float _minTemp = 0.0f; - - /// - /// The maximum temperature this effect can reach. - /// - [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity; - - /// - /// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount. - /// - [DataField("scaled")] private bool _scaled; - - public override void Effect(EntityEffectBaseArgs args) - { - if (args is EntityEffectReagentArgs reagentArgs) - { - var solution = reagentArgs.Source; - if (solution == null || solution.Volume == 0) - return; - - if (_delta > 0 && solution.Temperature >= _maxTemp) - return; - if (_delta < 0 && solution.Temperature <= _minTemp) - return; - - var heatCap = solution.GetHeatCapacity(null); - var deltaT = _scaled - ? _delta / heatCap * (float) reagentArgs.Quantity - : _delta / heatCap; - - solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp); - - return; - } - - // TODO: Someone needs to figure out how to do this for non-reagent effects. - throw new NotImplementedException(); - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect", - ("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp)); -} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs new file mode 100644 index 0000000000..b028da1595 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs @@ -0,0 +1,35 @@ +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Entity effect that specifically deals with new status effects. +/// +/// The entity effect type, typically for status effects which need systems to pass arguments +public abstract partial class BaseStatusEntityEffect : EntityEffectBase where T : BaseStatusEntityEffect +{ + /// + /// How long the modifier applies (in seconds). + /// Is scaled by reagent amount if used with an EntityEffectReagentArgs. + /// + [DataField] + public TimeSpan? Time = TimeSpan.FromSeconds(2); + + /// + /// Should this effect add the status effect, remove time from it, or set its cooldown? + /// + [DataField] + public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Update; + + /// + /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence. + /// + [DataField] + public TimeSpan Delay; +} + +public enum StatusEffectMetabolismType +{ + Update, + Add, + Remove, + Set, +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs new file mode 100644 index 0000000000..553300be72 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Drunk; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Applies the drunk status effect to this entity. +/// The duration of the effect is equal to modified by scale. +/// +/// +public sealed partial class DrunkEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedDrunkSystem _drunk = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var boozePower = args.Effect.BoozePower * args.Scale; + + _drunk.TryApplyDrunkenness(entity, boozePower); + } +} + +/// +public sealed partial class Drunk : EntityEffectBase +{ + /// + /// BoozePower is how long each metabolism cycle will make the drunk effect last for. + /// + [DataField] + public TimeSpan BoozePower = TimeSpan.FromSeconds(3f); + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-drunk", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs new file mode 100644 index 0000000000..b5a208f2c7 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.Electrocution; +using Content.Shared.StatusEffect; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +// TODO: When Electrocution is moved to new Status, make this use StatusEffectsContainerComponent. +/// +/// Electrocutes this entity for a given amount of damage and time. +/// The shock damage applied by this effect is modified by scale. +/// +/// +public sealed partial class ElectrocuteEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedElectrocutionSystem _electrocution = default!; + + // TODO: When electrocution is new status, change this to new status + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var effect = args.Effect; + + _electrocution.TryDoElectrocution(entity, null, (int)(args.Scale * effect.ShockDamage), effect.ElectrocuteTime, effect.Refresh, ignoreInsulation: effect.BypassInsulation); + } +} + +/// +public sealed partial class Electrocute : EntityEffectBase +{ + /// + /// Time we electrocute this entity + /// + [DataField] public TimeSpan ElectrocuteTime = TimeSpan.FromSeconds(2); + + /// + /// Shock damage we apply to the entity. + /// + [DataField] public int ShockDamage = 5; + + /// + /// Do we refresh the duration? Or add more duration if it already exists. + /// + [DataField] public bool Refresh = true; + + /// + /// Should we by bypassing insulation? + /// + [DataField] public bool BypassInsulation = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("entity-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime.TotalSeconds)); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs deleted file mode 100644 index 652c1b90a1..0000000000 --- a/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Content.Shared.StatusEffect; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.StatusEffects; - -/// -/// Adds a generic status effect to the entity, -/// not worrying about things like how to affect the time it lasts for -/// or component fields or anything. Just adds a component to an entity -/// for a given time. Easy. -/// -/// -/// Can be used for things like adding accents or something. I don't know. Go wild. -/// -[Obsolete("Use ModifyStatusEffect with StatusEffectNewSystem instead")] -public sealed partial class GenericStatusEffect : EntityEffect -{ - [DataField(required: true)] - public string Key = default!; - - [DataField] - public string Component = String.Empty; - - [DataField] - public float Time = 2.0f; - - /// - /// true - refresh status effect time, false - accumulate status effect time - /// - [DataField] - public bool Refresh = true; - - /// - /// Should this effect add the status effect, remove time from it, or set its cooldown? - /// - [DataField] - public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add; - - public override void Effect(EntityEffectBaseArgs args) - { - var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - - var time = Time; - if (args is EntityEffectReagentArgs reagentArgs) - time *= reagentArgs.Scale.Float(); - - if (Type == StatusEffectMetabolismType.Add && Component != String.Empty) - { - statusSys.TryAddStatusEffect(args.TargetEntity, Key, TimeSpan.FromSeconds(time), Refresh, Component); - } - else if (Type == StatusEffectMetabolismType.Remove) - { - statusSys.TryRemoveTime(args.TargetEntity, Key, TimeSpan.FromSeconds(time)); - } - else if (Type == StatusEffectMetabolismType.Set) - { - statusSys.TrySetTime(args.TargetEntity, Key, TimeSpan.FromSeconds(time)); - } - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString( - "reagent-effect-guidebook-status-effect", - ("chance", Probability), - ("type", Type), - ("time", Time), - ("key", $"reagent-effect-status-effect-{Key}")); -} - -public enum StatusEffectMetabolismType -{ - Update, - Add, - Remove, - Set -} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs new file mode 100644 index 0000000000..2d870a433c --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs @@ -0,0 +1,64 @@ +using Content.Shared.StatusEffect; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Applies a Generic Status Effect to this entity, which is a timed Component. +/// The amount of time the Component is applied is modified by scale. +/// +/// +[Obsolete("Use ModifyStatusEffect instead")] +public sealed partial class GenericStatusEffectEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly StatusEffectsSystem _status = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var time = args.Effect.Time * args.Scale; + + switch (args.Effect.Type) + { + case StatusEffectMetabolismType.Update: + if (args.Effect.Component != String.Empty) + _status.TryAddStatusEffect(entity, args.Effect.Key, time, true, args.Effect.Component); + break; + case StatusEffectMetabolismType.Add: + if (args.Effect.Component != String.Empty) + _status.TryAddStatusEffect(entity, args.Effect.Key, time, false, args.Effect.Component); + break; + case StatusEffectMetabolismType.Remove: + _status.TryRemoveTime(entity, args.Effect.Key, time); + break; + case StatusEffectMetabolismType.Set: + _status.TrySetTime(entity, args.Effect.Key, time); + break; + } + } +} + +/// +public sealed partial class GenericStatusEffect : EntityEffectBase +{ + [DataField(required: true)] + public string Key = default!; + + [DataField] + public string Component = String.Empty; + + [DataField] + public TimeSpan Time = TimeSpan.FromSeconds(2f); + + /// + /// Should this effect add the status effect, remove time from it, or set its cooldown? + /// + [DataField] + public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Update; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString( + "entity-effect-guidebook-status-effect-old", + ("chance", Probability), + ("type", Type), + ("time", Time.TotalSeconds), + ("key", $"entity-effect-status-effect-{Key}")); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs deleted file mode 100644 index 891c791b07..0000000000 --- a/Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Jittering; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.StatusEffects; - -/// -/// Adds the jitter status effect to a mob. -/// This doesn't use generic status effects because it needs to -/// take in some parameters that JitterSystem needs. -/// -public sealed partial class Jitter : EntityEffect -{ - [DataField] - public float Amplitude = 10.0f; - - [DataField] - public float Frequency = 4.0f; - - [DataField] - public float Time = 2.0f; - - /// - /// true - refresh jitter time, false - accumulate jitter time - /// - [DataField] - public bool Refresh = true; - - public override void Effect(EntityEffectBaseArgs args) - { - var time = Time; - if (args is EntityEffectReagentArgs reagentArgs) - time *= reagentArgs.Scale.Float(); - - args.EntityManager.EntitySysManager.GetEntitySystem() - .DoJitter(args.TargetEntity, TimeSpan.FromSeconds(time), Refresh, Amplitude, Frequency); - } - - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => - Loc.GetString("reagent-effect-guidebook-jittering", ("chance", Probability)); -} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs new file mode 100644 index 0000000000..3b445da747 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs @@ -0,0 +1,45 @@ +using Content.Shared.Jittering; +using Content.Shared.StatusEffect; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +// TODO: When Jittering is moved to new Status, make this use StatusEffectsContainerComponent. +/// +/// Applies the Jittering Status Effect to this entity. +/// The amount of time the Jittering is applied is modified by scale. +/// +/// +public sealed partial class JitterEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedJitteringSystem _jittering = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var time = args.Effect.Time * args.Scale; + + _jittering.DoJitter(entity, TimeSpan.FromSeconds(time), args.Effect.Refresh, args.Effect.Amplitude, args.Effect.Frequency); + } +} + +/// +public sealed partial class Jitter : EntityEffectBase +{ + [DataField] + public float Amplitude = 10.0f; + + [DataField] + public float Frequency = 4.0f; + + [DataField] + public float Time = 2.0f; + + /// + /// true - refresh jitter time, false - accumulate jitter time + /// + [DataField] + public bool Refresh = true; + + public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Loc.GetString("entity-effect-guidebook-jittering", ("chance", Probability)); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs deleted file mode 100644 index 59ca5da0f7..0000000000 --- a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Content.Shared.Stunnable; -using JetBrains.Annotations; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.StatusEffects; - -/// -/// Changes the knockdown timer on an entity or causes knockdown. -/// -[UsedImplicitly] -public sealed partial class ModifyKnockdown : EntityEffect -{ - /// - /// Should we only affect those with crawler component? Note if this is false, it will paralyze non-crawler's instead. - /// - [DataField] - public bool Crawling; - - /// - /// Should we drop items when we fall? - /// - [DataField] - public bool Drop; - - /// - /// Time for which knockdown should be applied. Behaviour changes according to . - /// - [DataField] - public TimeSpan Time = TimeSpan.FromSeconds(0.5); - - /// - /// Should this effect add the status effect, remove time from it, or set its cooldown? - /// - [DataField] - public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add; - - /// - /// Should this effect add knockdown?, remove time from it?, or set its cooldown? - /// - [DataField] - public bool Refresh = true; - - /// - public override void Effect(EntityEffectBaseArgs args) - { - var stunSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - - var time = Time; - if (args is EntityEffectReagentArgs reagentArgs) - time *= reagentArgs.Scale.Float(); - - switch (Type) - { - case StatusEffectMetabolismType.Update: - if (Crawling) - { - stunSys.TryCrawling(args.TargetEntity, time, drop: Drop); - } - else - { - stunSys.TryKnockdown(args.TargetEntity, time, drop: Drop); - } - break; - case StatusEffectMetabolismType.Add: - if (Crawling) - { - stunSys.TryCrawling(args.TargetEntity, time, false, drop: Drop); - } - else - { - stunSys.TryKnockdown(args.TargetEntity, time, false, drop: Drop); - } - break; - case StatusEffectMetabolismType.Remove: - stunSys.AddKnockdownTime(args.TargetEntity, -time); - break; - case StatusEffectMetabolismType.Set: - if (Crawling) - { - stunSys.TryCrawling(args.TargetEntity, time, drop: Drop); - } - else - { - stunSys.TryKnockdown(args.TargetEntity, time, drop: Drop); - } - stunSys.SetKnockdownTime(args.TargetEntity, time); - break; - } - } - - /// - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) - => Loc.GetString( - "reagent-effect-guidebook-knockdown", - ("chance", Probability), - ("type", Type), - ("time", Time.TotalSeconds) - ); -} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs new file mode 100644 index 0000000000..5e01774ce4 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs @@ -0,0 +1,72 @@ +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Applies knockdown to this entity. +/// Duration is modified by scale. +/// +/// +public sealed partial class ModifyKnockdownEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly SharedStunSystem _stun = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var time = args.Effect.Time * args.Scale; + + switch (args.Effect.Type) + { + case StatusEffectMetabolismType.Update: + if (args.Effect.Crawling) + _stun.TryCrawling(entity.Owner, time, drop: args.Effect.Drop); + else + _stun.TryKnockdown(entity.Owner, time, drop: args.Effect.Drop); + break; + case StatusEffectMetabolismType.Add: + if (args.Effect.Crawling) + _stun.TryCrawling(entity.Owner, time, false, drop: args.Effect.Drop); + else + _stun.TryKnockdown(entity.Owner, time, false, drop: args.Effect.Drop); + break; + case StatusEffectMetabolismType.Remove: + _stun.AddKnockdownTime(entity.Owner, - time ?? TimeSpan.Zero); + break; + case StatusEffectMetabolismType.Set: + if (args.Effect.Crawling) + _stun.TryCrawling(entity.Owner, drop: args.Effect.Drop); + else + _stun.TryKnockdown(entity.Owner, time, drop: args.Effect.Drop); + _stun.SetKnockdownTime(entity.Owner, time ?? TimeSpan.Zero); + break; + } + } +} + +/// +public sealed partial class ModifyKnockdown : BaseStatusEntityEffect +{ + /// + /// Should we only affect those with crawler component? Note if this is false, it will paralyze non-crawler's instead. + /// + [DataField] + public bool Crawling; + + /// + /// Should we drop items when we fall? + /// + [DataField] + public bool Drop; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Time == null + ? null + : Loc.GetString( + "entity-effect-guidebook-knockdown", + ("chance", Probability), + ("type", Type), + ("time", Time.Value.TotalSeconds) + ); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs new file mode 100644 index 0000000000..318c8ad30b --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.StatusEffectNew; +using Content.Shared.StatusEffectNew.Components; +using Content.Shared.Stunnable; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Applies the paralysis status effect to this entity. +/// Duration is modified by scale. +/// +/// +public sealed partial class ModifyParalysisEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly StatusEffectsSystem _status = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var time = args.Effect.Time * args.Scale; + + switch (args.Effect.Type) + { + case StatusEffectMetabolismType.Update: + _stun.TryUpdateParalyzeDuration(entity, time); + break; + case StatusEffectMetabolismType.Add: + _stun.TryAddParalyzeDuration(entity, time); + break; + case StatusEffectMetabolismType.Remove: + _status.TryRemoveTime(entity, SharedStunSystem.StunId, time); + break; + case StatusEffectMetabolismType.Set: + _status.TrySetStatusEffectDuration(entity, SharedStunSystem.StunId, time); + break; + } + } +} + +/// +public sealed partial class ModifyParalysis : BaseStatusEntityEffect +{ + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Time == null + ? null // Not gonna make a whole new looc for something that shouldn't ever exist. + : Loc.GetString( + "entity-effect-guidebook-paralyze", + ("chance", Probability), + ("time", Time.Value.TotalSeconds) + ); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs deleted file mode 100644 index d7bf6482bd..0000000000 --- a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Content.Shared.StatusEffectNew; -using JetBrains.Annotations; -using Robust.Shared.Prototypes; - -namespace Content.Shared.EntityEffects.Effects.StatusEffects; - -/// -/// Changes status effects on entities: Adds, removes or sets time. -/// -[UsedImplicitly] -public sealed partial class ModifyStatusEffect : EntityEffect -{ - [DataField(required: true)] - public EntProtoId EffectProto; - - /// - /// Time for which status effect should be applied. Behaviour changes according to . - /// - [DataField] - public float Time = 2.0f; - - /// - /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence. - /// - [DataField] - public float Delay = 0f; - - /// - /// Should this effect add the status effect, remove time from it, or set its cooldown? - /// - [DataField] - public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add; - - /// - public override void Effect(EntityEffectBaseArgs args) - { - var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem(); - - var time = Time; - if (args is EntityEffectReagentArgs reagentArgs) - time *= reagentArgs.Scale.Float(); - - var duration = TimeSpan.FromSeconds(time); - switch (Type) - { - case StatusEffectMetabolismType.Update: - statusSys.TryUpdateStatusEffectDuration(args.TargetEntity, EffectProto, duration, Delay > 0 ? TimeSpan.FromSeconds(Delay) : null); - break; - case StatusEffectMetabolismType.Add: - statusSys.TryAddStatusEffectDuration(args.TargetEntity, EffectProto, duration, Delay > 0 ? TimeSpan.FromSeconds(Delay) : null); - break; - case StatusEffectMetabolismType.Remove: - statusSys.TryAddTime(args.TargetEntity, EffectProto, -duration); - break; - case StatusEffectMetabolismType.Set: - statusSys.TrySetStatusEffectDuration(args.TargetEntity, EffectProto, duration, TimeSpan.FromSeconds(Delay)); - break; - } - } - - /// - protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => - Delay > 0 - ? Loc.GetString( - "reagent-effect-guidebook-status-effect-delay", - ("chance", Probability), - ("type", Type), - ("time", Time), - ("key", prototype.Index(EffectProto).Name), - ("delay", Delay)) - : Loc.GetString( - "reagent-effect-guidebook-status-effect", - ("chance", Probability), - ("type", Type), - ("time", Time), - ("key", prototype.Index(EffectProto).Name) - ); -} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs new file mode 100644 index 0000000000..d7e4b634f3 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.StatusEffectNew; +using Content.Shared.StatusEffectNew.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Applies a given status effect to this entity. +/// Duration is modified by scale. +/// +/// +public sealed partial class ModifyStatusEffectEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly StatusEffectsSystem _status = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var time = args.Effect.Time * args.Scale; + var delay = args.Effect.Delay; + + switch (args.Effect.Type) + { + case StatusEffectMetabolismType.Update: + _status.TryUpdateStatusEffectDuration(entity, args.Effect.EffectProto, time, delay); + break; + case StatusEffectMetabolismType.Add: + if (time != null) + _status.TryAddStatusEffectDuration(entity, args.Effect.EffectProto, time.Value, delay); + else + _status.TryUpdateStatusEffectDuration(entity, args.Effect.EffectProto, time, delay); + break; + case StatusEffectMetabolismType.Remove: + _status.TryRemoveTime(entity, args.Effect.EffectProto, time); + break; + case StatusEffectMetabolismType.Set: + _status.TrySetStatusEffectDuration(entity, args.Effect.EffectProto, time, delay); + break; + } + } +} + +/// +public sealed partial class ModifyStatusEffect : BaseStatusEntityEffect +{ + /// + /// Prototype of the status effect we're modifying. + /// + [DataField(required: true)] + public EntProtoId EffectProto; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Time == null + ? Loc.GetString( + "entity-effect-guidebook-status-effect-indef", + ("chance", Probability), + ("type", Type), + ("key", prototype.Index(EffectProto).Name), + ("delay", Delay.TotalSeconds)) + : Loc.GetString( + "entity-effect-guidebook-status-effect", + ("chance", Probability), + ("type", Type), + ("time", Time.Value.TotalSeconds), + ("key", prototype.Index(EffectProto).Name), + ("delay", Delay.TotalSeconds)); +} diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs new file mode 100644 index 0000000000..ed9923f856 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs @@ -0,0 +1,92 @@ +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Systems; +using Content.Shared.StatusEffectNew; +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects.StatusEffects; + +/// +/// Applies a given movement speed modifier status effect to this entity. +/// Duration is modified by scale. +/// +/// +public sealed partial class MovementSpeedModifierEntityEffectSystem : EntityEffectSystem +{ + [Dependency] private readonly StatusEffectsSystem _status = default!; + [Dependency] private readonly MovementModStatusSystem _movementModStatus = default!; + + protected override void Effect(Entity entity, ref EntityEffectEvent args) + { + var proto = args.Effect.EffectProto; + var sprintMod = args.Effect.SprintSpeedModifier; + var walkMod = args.Effect.WalkSpeedModifier; + + switch (args.Effect.Type) + { + case StatusEffectMetabolismType.Update: + _movementModStatus.TryUpdateMovementSpeedModDuration( + entity, + proto, + args.Effect.Time * args.Scale, + sprintMod, + walkMod); + break; + case StatusEffectMetabolismType.Add: + if (args.Effect.Time != null) + { + _movementModStatus.TryAddMovementSpeedModDuration( + entity, + proto, + args.Effect.Time.Value * args.Scale, + sprintMod, + walkMod); + } + else + { + _movementModStatus.TryUpdateMovementSpeedModDuration( + entity, + proto, + args.Effect.Time * args.Scale, + sprintMod, + walkMod); + } + break; + case StatusEffectMetabolismType.Remove: + _status.TryRemoveTime(entity, args.Effect.EffectProto, args.Effect.Time * args.Scale); + break; + case StatusEffectMetabolismType.Set: + _status.TrySetStatusEffectDuration(entity, proto, args.Effect.Time * args.Scale); + break; + } + } +} + +/// +public sealed partial class MovementSpeedModifier : BaseStatusEntityEffect +{ + /// + /// How much the entities' walk speed is multiplied by. + /// + [DataField] + public float WalkSpeedModifier = 1f; + + /// + /// How much the entities' run speed is multiplied by. + /// + [DataField] + public float SprintSpeedModifier = 1f; + + /// + /// Movement speed modifier prototype we're adding. Adding in case we ever want more than one prototype that boosts speed. + /// + [DataField] + public EntProtoId EffectProto = MovementModStatusSystem.ReagentSpeed; + + public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => + Time == null + ? null // Not gonna make a whole new looc for something that shouldn't ever exist. + : Loc.GetString("entity-effect-guidebook-movespeed-modifier", + ("chance", Probability), + ("sprintspeed", SprintSpeedModifier), + ("time", Time.Value.TotalSeconds)); +} diff --git a/Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs new file mode 100644 index 0000000000..ea7c2dc8d4 --- /dev/null +++ b/Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.EntityEffects.Effects; + +/// +/// A brief summary of the effect. +/// +/// +public sealed partial class TemplateEntityEffectSystem : EntityEffectSystem +{ + protected override void Effect(Entity entity, ref EntityEffectEvent