/Content.*/Stunnable/ @Princess-Cheeseballs
/Content.*/Nutrition/ @Princess-Cheeseballs
+/Content.*/EntityEffects @Princess-Cheeseballs @sowelipililimute
# SKREEEE
/Content.*.Database/ @PJB3005 @DrSmugleaf
--- /dev/null
+using Content.Shared.Temperature.Systems;
+
+namespace Content.Client.Temperature.Systems;
+
+/// <summary>
+/// This exists so <see cref="SharedTemperatureSystem"/> runs on client/>
+/// </summary>
+public sealed class TemperatureSystem : SharedTemperatureSystem;
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;
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;
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;
/// <summary>
/// Handles metabolizing various reagents with given effects.
/// </summary>
- [RegisterComponent, Access(typeof(MetabolizerSystem))]
+ [RegisterComponent, AutoGenerateComponentPause, Access(typeof(MetabolizerSystem))]
public sealed partial class MetabolizerComponent : Component
{
/// <summary>
/// The next time that reagents will be metabolized.
/// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [DataField, AutoPausedField]
public TimeSpan NextUpdate;
/// <summary>
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;
using Robust.Shared.Random;
using Robust.Shared.Timing;
-namespace Content.Server.Body.Systems
+namespace Content.Server.Body.Systems;
+
+/// <inheritdoc/>
+public sealed class MetabolizerSystem : SharedMetabolizerSystem
{
- /// <inheritdoc/>
- 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<OrganComponent> _organQuery;
+ private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
+ private static readonly ProtoId<MetabolismGroupPrototype> 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!;
-
- private EntityQuery<OrganComponent> _organQuery;
- private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
+ base.Initialize();
- public override void Initialize()
- {
- base.Initialize();
+ _organQuery = GetEntityQuery<OrganComponent>();
+ _solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
- _organQuery = GetEntityQuery<OrganComponent>();
- _solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
+ SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
+ SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
+ SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
+ }
- SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
- SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<MetabolizerComponent, EntityUnpausedEvent>(OnUnpaused);
- SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
- }
+ private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
+ {
+ ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
+ }
- private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
+ private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
+ {
+ if (!entity.Comp.SolutionOnBody)
{
- ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
+ _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
}
-
- private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
+ else if (_organQuery.CompOrNull(entity)?.Body is { } body)
{
- ent.Comp.NextUpdate += args.PausedTime;
+ _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
}
+ }
- private void OnMetabolizerInit(Entity<MetabolizerComponent> 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<MetabolizerComponent> ent, ref ApplyMetabolicMultiplierEvent args)
+ {
+ ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
- private void OnApplyMetabolicMultiplier(Entity<MetabolizerComponent> ent, ref ApplyMetabolicMultiplierEvent args)
+ var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count<MetabolizerComponent>());
+ var query = EntityQueryEnumerator<MetabolizerComponent>();
+
+ while (query.MoveNext(out var uid, out var comp))
{
- ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
+ metabolizers.Add((uid, comp));
}
- public override void Update(float frameTime)
+ foreach (var (uid, metab) in metabolizers)
{
- base.Update(frameTime);
+ // Only update as frequently as it should
+ if (_gameTiming.CurTime < metab.NextUpdate)
+ continue;
- var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count<MetabolizerComponent>());
- var query = EntityQueryEnumerator<MetabolizerComponent>();
+ metab.NextUpdate += metab.AdjustedUpdateInterval;
+ TryMetabolize((uid, metab));
+ }
+ }
- while (query.MoveNext(out var uid, out var comp))
- {
- metabolizers.Add((uid, comp));
- }
+ private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> 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<SolutionComponent>? soln = default!;
+ EntityUid? solutionEntityUid = null;
- foreach (var (uid, metab) in metabolizers)
+ if (ent.Comp1.SolutionOnBody)
+ {
+ if (ent.Comp2?.Body is { } body)
{
- // Only update as frequently as it should
- if (_gameTiming.CurTime < metab.NextUpdate)
- continue;
+ if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
+ return;
- metab.NextUpdate += metab.AdjustedUpdateInterval;
- TryMetabolize((uid, metab));
+ _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
+ solutionEntityUid = body;
}
}
+ else
+ {
+ if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
+ return;
- private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
+ _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)
{
- _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
+ return;
+ }
- // First step is get the solution we actually care about
- var solutionName = ent.Comp1.SolutionName;
- Solution? solution = null;
- Entity<SolutionComponent>? soln = default!;
- EntityUid? solutionEntityUid = null;
+ // 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);
- if (ent.Comp1.SolutionOnBody)
+ int reagents = 0;
+ foreach (var (reagent, quantity) in list)
+ {
+ if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var proto))
+ continue;
+
+ var mostToRemove = FixedPoint2.Zero;
+ if (proto.Metabolisms is null)
{
- if (ent.Comp2?.Body is { } body)
+ if (ent.Comp1.RemoveEmpty)
{
- if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
- return;
-
- _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
- solutionEntityUid = body;
+ solution.RemoveReagent(reagent, FixedPoint2.New(1));
}
- }
- else
- {
- if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
- return;
- _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
- solutionEntityUid = ent;
+ continue;
}
- if (solutionEntityUid is null
- || soln is null
- || solution is null
- || solution.Contents.Count == 0)
- {
+ // we're done here entirely if this is true
+ if (reagents >= ent.Comp1.MaxReagentsProcessable)
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)
+ // 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 (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var proto))
+ if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
continue;
- var mostToRemove = FixedPoint2.Zero;
- if (proto.Metabolisms is null)
- {
- if (ent.Comp1.RemoveEmpty)
- {
- solution.RemoveReagent(reagent, FixedPoint2.New(1));
- }
+ var rate = entry.MetabolismRate * group.MetabolismRateModifier;
- continue;
- }
-
- // we're done here entirely if this is true
- if (reagents >= ent.Comp1.MaxReagentsProcessable)
- return;
+ // 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;
- // loop over all our groups and see which ones apply
- if (ent.Comp1.MetabolismGroups is null)
- continue;
+ // 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;
- foreach (var group in ent.Comp1.MetabolismGroups)
+ // 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<MobStateComponent>(solutionEntityUid.Value, out var state))
{
- if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
+ if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
continue;
+ }
- var rate = entry.MetabolismRate * group.MetabolismRateModifier;
+ var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
- // Remove $rate, as long as there's enough reagent there to actually remove that much
- mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
+ // do all effects, if conditions apply
+ foreach (var effect in entry.Effects)
+ {
+ if (scale < effect.MinScale)
+ continue;
- float scale = (float) mostToRemove / (float) rate;
+ // See if conditions apply
+ if (effect.Conditions != null && !CanMetabolizeEffect(actualEntity, ent, soln.Value, effect.Conditions))
+ continue;
- // 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<MobStateComponent>(solutionEntityUid.Value, out var state))
- {
- if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
- continue;
- }
+ ApplyEffect(effect);
- 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)
+ // TODO: We should have to do this with metabolism. ReagentEffect struct needs refactoring and so does metabolism!
+ void ApplyEffect(EntityEffect effect)
+ {
+ switch (effect)
{
- 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);
+ 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);
+ // 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;
- }
+ // We have processed a reagant, so count it towards the cap
+ reagents += 1;
}
+ }
- _solutionContainerSystem.UpdateChemicals(soln.Value);
+ _solutionContainerSystem.UpdateChemicals(soln.Value);
+ }
+
+ /// <summary>
+ /// 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!
+ /// </summary>
+ /// <param name="body">The body metabolizing the effects</param>
+ /// <param name="organ">The organ doing the metabolizing</param>
+ /// <param name="solution">The solution we are metabolizing from</param>
+ /// <param name="conditions">The conditions that need to be met to metabolize</param>
+ /// <returns>True if we can metabolize! False if we cannot!</returns>
+ public bool CanMetabolizeEffect(EntityUid body, EntityUid organ, Entity<SolutionComponent> 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;
}
}
+
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;
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;
{
[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<MetabolismGroupPrototype> GasId = new("Gas");
}
}
- // 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)
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;
}
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;
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;
[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.
+/// <remarks>
+/// 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.
+/// </remarks>
+// 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
public sealed partial class BotanySystem
{
+ [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
+
public void ProduceGrown(EntityUid uid, ProduceComponent produce)
{
if (!TryGetSeed(produce, out var seed))
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,
[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()
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);
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;
[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;
foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt))
{
var reagentProto = _prototype.Index<ReagentPrototype>(entry.Reagent.Prototype);
- reagentProto.ReactionPlant(uid, entry, solution, EntityManager, _random, _adminLogger);
+ _entityEffects.ApplyEffects(uid, reagentProto.PlantMetabolisms.ToArray());
}
}
{
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())));
}
}
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;
--- /dev/null
+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;
+
+/// <summary>
+/// Returns true if this entity is both able to breathe and is currently breathing.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class IsBreathingEntityConditionSystem : EntityConditionSystem<RespiratorComponent, BreathingCondition>
+{
+ [Dependency] private readonly RespiratorSystem _respirator = default!;
+ protected override void Condition(Entity<RespiratorComponent> entity, ref EntityConditionEvent<BreathingCondition> args)
+ {
+ args.Result = _respirator.IsBreathing(entity.AsNullable());
+ }
+}
--- /dev/null
+using System.Linq;
+using Content.Server.Body.Components;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions.Body;
+
+namespace Content.Server.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity has any of the listed metabolizer types.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class MetabolizerTypeEntityConditionSystem : EntityConditionSystem<MetabolizerComponent, MetabolizerTypeCondition>
+{
+ protected override void Condition(Entity<MetabolizerComponent> entity, ref EntityConditionEvent<MetabolizerTypeCondition> args)
+ {
+ if (entity.Comp.MetabolizerTypes == null)
+ return;
+
+ args.Result = entity.Comp.MetabolizerTypes.Overlaps(args.Condition.Type);
+ }
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// This effect adjusts a gas at the tile this entity is currently on.
+/// The amount changed is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class CreateGasEntityEffectSystem : EntityEffectSystem<TransformComponent, CreateGas>
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<CreateGas> args)
+ {
+ var tileMix = _atmosphere.GetContainingMixture(entity.AsNullable(), false, true);
+
+ tileMix?.AdjustMoles(args.Effect.Gas, args.Scale * args.Effect.Moles);
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// Adds a number of FireStacks modified by scale to this entity.
+/// The amount of FireStacks added is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class FlammableEntityEffectSystem : EntityEffectSystem<FlammableComponent, Flammable>
+{
+ [Dependency] private readonly FlammableSystem _flammable = default!;
+
+ protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Flammable> 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);
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// Sets this entity on fire.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class IngiteEntityEffectSystem : EntityEffectSystem<FlammableComponent, Ignite>
+{
+ [Dependency] private readonly FlammableSystem _flammable = default!;
+
+ protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Ignite> 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);
+ }
+}
+
--- /dev/null
+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;
+
+/// <summary>
+/// This effect adjusts a respirator's saturation value.
+/// The saturation adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class OxygenateEntityEffectsSystem : EntityEffectSystem<RespiratorComponent, Oxygenate>
+{
+ [Dependency] private readonly RespiratorSystem _respirator = default!;
+ protected override void Effect(Entity<RespiratorComponent> entity, ref EntityEffectEvent<Oxygenate> args)
+ {
+ _respirator.UpdateSaturation(entity, args.Scale * args.Effect.Factor, entity.Comp);
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustHealth>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustHealth> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.MutationLevel += args.Effect.Amount * entity.Comp.MutationMod;
+ _plantHolder.CheckHealth(entity, entity.Comp);
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustMutationLevel>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustMutationLevel> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.Health += args.Effect.Amount;
+ _plantHolder.CheckHealth(entity, entity.Comp);
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustMutationMod>
+{
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustMutationMod> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.MutationMod += args.Effect.Amount;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustNutrition>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustNutrition> args)
+ {
+ _plantHolder.AdjustNutrient(entity, args.Effect.Amount, entity);
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustPests>
+{
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPests> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.PestLevel += args.Effect.Amount;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustPotency>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPotency> 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);
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustToxins>
+{
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustToxins> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.Toxins += args.Effect.Amount;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustWater>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustWater> args)
+ {
+ _plantHolder.AdjustWater(entity, args.Effect.Amount, entity.Comp);
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAdjustWeeds>
+{
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustWeeds> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.WeedLevel += args.Effect.Amount;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantAffectGrowth>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAffectGrowth> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ _plantHolder.AffectGrowth(entity, (int)args.Effect.Amount, entity);
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// This system mutates an inputted stat for a PlantHolder, only works for floats, integers, and bools.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantChangeStat>
+{
+ // 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<PlantHolderComponent> entity, ref EntityEffectEvent<PlantChangeStat> 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;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantCryoxadone>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantCryoxadone> 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;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantDestroySeeds>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDestroySeeds> 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;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantDiethylamine>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDiethylamine> 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++;
+ }
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantPhalanximine>
+{
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantPhalanximine> args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+ return;
+
+ entity.Comp.Seed.Viable = true;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantRestoreSeeds>
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantRestoreSeeds> 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;
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, RobustHarvest>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<RobustHarvest> 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--;
+ }
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantMutateChemicals>
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateChemicals> 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;
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantMutateExudeGases>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateExudeGases> 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<Gas>().ToList());
+
+ if (!gasses.TryAdd(gas, amount))
+ {
+ gasses[gas] += amount;
+ }
+ }
+}
+
+public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateConsumeGases>
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateConsumeGases> 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<Gas>().ToList());
+
+ if (!gasses.TryAdd(gas, amount))
+ {
+ gasses[gas] += amount;
+ }
+ }
+}
+
--- /dev/null
+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<PlantHolderComponent, PlantMutateHarvest>
+{
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateHarvest> 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;
+ }
+ }
+}
--- /dev/null
+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<PlantHolderComponent, PlantMutateSpeciesChange>
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateSpeciesChange> 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);
+ }
+}
--- /dev/null
+using Content.Server.Chat.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
+
+namespace Content.Server.EntityEffects.Effects;
+
+/// <summary>
+/// Makes this entity emote.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Emote>
+{
+ [Dependency] private readonly ChatSystem _chat = default!;
+
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Emote> args)
+ {
+ if (args.Effect.ShowInChat)
+ _chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
+ else
+ _chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId);
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// Makes this entity sentient. Allows ghost to take it over if it's not already occupied.
+/// Optionally also allows this entity to speak.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class MakeSentientEntityEffectSystem : EntityEffectSystem<MetaDataComponent, MakeSentient>
+{
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<MakeSentient> 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<ReplacementAccentComponent>(entity);
+ // TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell.
+ RemComp<MonkeyAccentComponent>(entity);
+ }
+
+ // Stops from adding a ghost role to things like people who already have a mind
+ if (TryComp<MindContainerComponent>(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<GhostRoleComponent>(entity);
+ EnsureComp<GhostTakeoverAvailableComponent>(entity);
+
+ ghostRole.RoleName = entity.Comp.EntityName;
+ ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
+ }
+}
--- /dev/null
+using Content.Server.Polymorph.Components;
+using Content.Server.Polymorph.Systems;
+using Content.Shared.EntityEffects;
+
+namespace Content.Server.EntityEffects.Effects;
+
+/// <summary>
+/// Polymorphs this entity into another entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class PolymorphEntityEffectSystem : EntityEffectSystem<PolymorphableComponent, Shared.EntityEffects.Effects.Polymorph>
+{
+ [Dependency] private readonly PolymorphSystem _polymorph = default!;
+
+ protected override void Effect(Entity<PolymorphableComponent> entity, ref EntityEffectEvent<Shared.EntityEffects.Effects.Polymorph> args)
+ {
+ _polymorph.PolymorphEntity(entity, args.Effect.Prototype);
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// This effect creates smoke at this solution's position.
+/// The amount of smoke created is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AreaReactionEntityEffectsSystem : EntityEffectSystem<SolutionComponent, AreaReactionEffect>
+{
+ [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<SolutionComponent> entity, ref EntityEffectEvent<AreaReactionEffect> 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));
+ }
+}
--- /dev/null
+using Content.Server.Explosion.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Transform;
+
+namespace Content.Server.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates an explosion at this entity's position.
+/// Intensity is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ExplosionEntityEffectSystem : EntityEffectSystem<TransformComponent, ExplosionEffect>
+{
+ [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+ protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<ExplosionEffect> 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);
+ }
+}
+++ /dev/null
-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<WeightedRandomFillSolutionPrototype> 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<CheckEntityEffectConditionEvent<TemperatureCondition>>(OnCheckTemperature);
- SubscribeLocalEvent<CheckEntityEffectConditionEvent<Breathing>>(OnCheckBreathing);
- SubscribeLocalEvent<CheckEntityEffectConditionEvent<OrganType>>(OnCheckOrganType);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustHealth>>(OnExecutePlantAdjustHealth);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationLevel>>(OnExecutePlantAdjustMutationLevel);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationMod>>(OnExecutePlantAdjustMutationMod);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustNutrition>>(OnExecutePlantAdjustNutrition);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPests>>(OnExecutePlantAdjustPests);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPotency>>(OnExecutePlantAdjustPotency);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustToxins>>(OnExecutePlantAdjustToxins);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWater>>(OnExecutePlantAdjustWater);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWeeds>>(OnExecutePlantAdjustWeeds);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAffectGrowth>>(OnExecutePlantAffectGrowth);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantChangeStat>>(OnExecutePlantChangeStat);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantCryoxadone>>(OnExecutePlantCryoxadone);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDestroySeeds>>(OnExecutePlantDestroySeeds);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDiethylamine>>(OnExecutePlantDiethylamine);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantPhalanximine>>(OnExecutePlantPhalanximine);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantRestoreSeeds>>(OnExecutePlantRestoreSeeds);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<RobustHarvest>>(OnExecuteRobustHarvest);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<AdjustTemperature>>(OnExecuteAdjustTemperature);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<AreaReactionEffect>>(OnExecuteAreaReactionEffect);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<CauseZombieInfection>>(OnExecuteCauseZombieInfection);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemCleanBloodstream>>(OnExecuteChemCleanBloodstream);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemVomit>>(OnExecuteChemVomit);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateEntityReactionEffect>>(OnExecuteCreateEntityReactionEffect);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateGas>>(OnExecuteCreateGas);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<CureZombieInfection>>(OnExecuteCureZombieInfection);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<Emote>>(OnExecuteEmote);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<EmpReactionEffect>>(OnExecuteEmpReactionEffect);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ExplosionReactionEffect>>(OnExecuteExplosionReactionEffect);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<FlammableReaction>>(OnExecuteFlammableReaction);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<FlashReactionEffect>>(OnExecuteFlashReactionEffect);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<Ignite>>(OnExecuteIgnite);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<MakeSentient>>(OnExecuteMakeSentient);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBleedAmount>>(OnExecuteModifyBleedAmount);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBloodLevel>>(OnExecuteModifyBloodLevel);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyLungGas>>(OnExecuteModifyLungGas);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<Oxygenate>>(OnExecuteOxygenate);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateChemicals>>(OnExecutePlantMutateChemicals);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateConsumeGasses>>(OnExecutePlantMutateConsumeGasses);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateExudeGasses>>(OnExecutePlantMutateExudeGasses);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateHarvest>>(OnExecutePlantMutateHarvest);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantSpeciesChange>>(OnExecutePlantSpeciesChange);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<PolymorphEffect>>(OnExecutePolymorph);
- SubscribeLocalEvent<ExecuteEntityEffectEvent<ResetNarcolepsy>>(OnExecuteResetNarcolepsy);
- }
-
- private void OnCheckTemperature(ref CheckEntityEffectConditionEvent<TemperatureCondition> 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<Breathing> 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<OrganType> 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<MetabolizerComponent?> metabolizer)
- {
- metabolizer.Comp ??= EntityManager.GetComponentOrNull<MetabolizerComponent>(metabolizer.Owner);
- if (metabolizer.Comp != null
- && metabolizer.Comp.MetabolizerTypes != null
- && metabolizer.Comp.MetabolizerTypes.Contains(condition.Type))
- return condition.ShouldHave;
- return !condition.ShouldHave;
- }
-
- /// <summary>
- /// Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default.
- /// </summary>
- /// <param name="plantHolder">The entity holding the plant</param>
- /// <param name="plantHolderComponent">The plant holder component</param>
- /// <param name="entityManager">The entity manager</param>
- /// <param name="mustHaveAlivePlant">Whether to check if it has an alive plant or not</param>
- /// <returns></returns>
- 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<PlantAdjustHealth> 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<PlantAdjustMutationLevel> args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.MutationLevel += args.Effect.Amount * plantHolderComp.MutationMod;
- }
-
- private void OnExecutePlantAdjustMutationMod(ref ExecuteEntityEffectEvent<PlantAdjustMutationMod> args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.MutationMod += args.Effect.Amount;
- }
-
- private void OnExecutePlantAdjustNutrition(ref ExecuteEntityEffectEvent<PlantAdjustNutrition> 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<PlantAdjustPests> args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.PestLevel += args.Effect.Amount;
- }
-
- private void OnExecutePlantAdjustPotency(ref ExecuteEntityEffectEvent<PlantAdjustPotency> 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<PlantAdjustToxins> args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.Toxins += args.Effect.Amount;
- }
-
- private void OnExecutePlantAdjustWater(ref ExecuteEntityEffectEvent<PlantAdjustWater> 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<PlantAdjustWeeds> args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.WeedLevel += args.Effect.Amount;
- }
-
- private void OnExecutePlantAffectGrowth(ref ExecuteEntityEffectEvent<PlantAffectGrowth> 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<PlantChangeStat> 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<PlantCryoxadone> 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<PlantDestroySeeds> 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<PlantDiethylamine> 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<PlantPhalanximine> args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
- return;
-
- plantHolderComp.Seed!.Viable = true;
- }
-
- private void OnExecutePlantRestoreSeeds(ref ExecuteEntityEffectEvent<PlantRestoreSeeds> 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<RobustHarvest> 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<AdjustTemperature> 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<AreaReactionEffect> 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<TransformComponent>(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<CauseZombieInfection> args)
- {
- EnsureComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
- EnsureComp<PendingZombieComponent>(args.Args.TargetEntity);
- }
-
- private void OnExecuteChemCleanBloodstream(ref ExecuteEntityEffectEvent<ChemCleanBloodstream> 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<ChemVomit> 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<CreateEntityReactionEffect> args)
- {
- var transform = Comp<TransformComponent>(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<CreateGas> 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<CureZombieInfection> args)
- {
- if (HasComp<IncurableZombieComponent>(args.Args.TargetEntity))
- return;
-
- RemComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
- RemComp<PendingZombieComponent>(args.Args.TargetEntity);
-
- if (args.Effect.Innoculate)
- {
- EnsureComp<ZombieImmuneComponent>(args.Args.TargetEntity);
- }
- }
-
- private void OnExecuteEmote(ref ExecuteEntityEffectEvent<Emote> 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<EmpReactionEffect> args)
- {
- var transform = Comp<TransformComponent>(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<ExplosionReactionEffect> 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<FlammableReaction> 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<FlashReactionEffect> args)
- {
- var transform = Comp<TransformComponent>(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<PointLightComponent>(uid, out var pointLightComp))
- return;
-
- _pointLight.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp);
- }
-
- private void OnExecuteIgnite(ref ExecuteEntityEffectEvent<Ignite> 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<MakeSentient> 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<ReplacementAccentComponent>(uid);
- RemComp<MonkeyAccentComponent>(uid);
-
- // Stops from adding a ghost role to things like people who already have a mind
- if (TryComp<MindContainerComponent>(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<GhostRoleComponent>(uid);
- EnsureComp<GhostTakeoverAvailableComponent>(uid);
-
- var entityData = Comp<MetaDataComponent>(uid);
- ghostRole.RoleName = entityData.EntityName;
- ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
- }
-
- private void OnExecuteModifyBleedAmount(ref ExecuteEntityEffectEvent<ModifyBleedAmount> args)
- {
- if (TryComp<BloodstreamComponent>(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<ModifyBloodLevel> args)
- {
- if (TryComp<BloodstreamComponent>(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<ModifyLungGas> args)
- {
- LungComponent? lung;
- float amount = 1f;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- if (!TryComp<LungComponent>(reagentArgs.OrganEntity, out var organLung))
- return;
- lung = organLung;
- amount = reagentArgs.Quantity.Float();
- }
- else
- {
- if (!TryComp<LungComponent>(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<Oxygenate> args)
- {
- var multiplier = 1f;
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- multiplier = reagentArgs.Quantity.Float();
- }
-
- if (TryComp<RespiratorComponent>(args.Args.TargetEntity, out var resp))
- {
- _respirator.UpdateSaturation(args.Args.TargetEntity, multiplier * args.Effect.Factor, resp);
- }
- }
-
- private void OnExecutePlantMutateChemicals(ref ExecuteEntityEffectEvent<PlantMutateChemicals> args)
- {
- var plantholder = Comp<PlantHolderComponent>(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<RandomFillSolution>(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<PlantMutateConsumeGasses> args)
- {
- var plantholder = Comp<PlantHolderComponent>(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<Gas>().ToList());
- if (gasses.ContainsKey(gas))
- {
- gasses[gas] += amount;
- }
- else
- {
- gasses.Add(gas, amount);
- }
- }
-
- private void OnExecutePlantMutateExudeGasses(ref ExecuteEntityEffectEvent<PlantMutateExudeGasses> args)
- {
- var plantholder = Comp<PlantHolderComponent>(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<Gas>().ToList());
- if (gasses.ContainsKey(gas))
- {
- gasses[gas] += amount;
- }
- else
- {
- gasses.Add(gas, amount);
- }
- }
-
- private void OnExecutePlantMutateHarvest(ref ExecuteEntityEffectEvent<PlantMutateHarvest> args)
- {
- var plantholder = Comp<PlantHolderComponent>(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<PlantSpeciesChange> args)
- {
- var plantholder = Comp<PlantHolderComponent>(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<PolymorphEffect> args)
- {
- // Make it into a prototype
- EnsureComp<PolymorphableComponent>(args.Args.TargetEntity);
- _polymorph.PolymorphEntity(args.Args.TargetEntity, args.Effect.PolymorphPrototype);
- }
-
- private void OnExecuteResetNarcolepsy(ref ExecuteEntityEffectEvent<ResetNarcolepsy> args)
- {
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- if (reagentArgs.Scale != 1f)
- return;
-
- _narcolepsy.AdjustNarcolepsyTimer(args.Args.TargetEntity, args.Effect.TimerReset);
- }
-}
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;
{
if (reagentQuantity.Quantity == FixedPoint2.Zero)
continue;
- var reagentProto = _prototype.Index<ReagentPrototype>(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)
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;
Converters =
{
new UniversalJsonConverter<EntityEffect>(),
- new UniversalJsonConverter<EntityEffectCondition>(),
+ new UniversalJsonConverter<EntityCondition>(),
new UniversalJsonConverter<ReagentEffectsEntry>(),
new UniversalJsonConverter<DamageSpecifier>(),
new FixedPointJsonConverter()
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();
}
}
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
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;
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;
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;
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;
using Robust.Shared.Utility;
using Content.Shared.Atmos.Components;
using System.Linq;
+using Content.Shared.Temperature.Components;
namespace Content.Server.NPC.Systems;
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!;
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;
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<TemperatureComponent>(uid, out var temp))
/// <summary>
/// List of effects that should be applied.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [DataField]
public List<EntityEffect> Effects = default!;
}
-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()
{
private void OnTileStepTriggered(Entity<TileEntityEffectComponent> 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());
}
}
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;
using Robust.Shared.Prototypes;
using Content.Shared.NPC.Prototypes;
using Content.Shared.Roles;
+using Content.Shared.Temperature.Components;
namespace Content.Server.Zombies;
ChemicalReaction = 17,
/// <summary>
- /// Reagent effects related interactions.
+ /// EntityEffect related interactions.
/// </summary>
- ReagentEffect = 18,
+ EntityEffect = 18,
/// <summary>
/// Canister valve was opened or closed.
/// The name/key of the solution on this entity which these lungs act on.
/// </summary>
[DataField]
- public string SolutionName = LungSystem.LungSolutionName;
+ public string SolutionName = "Lung";
/// <summary>
/// The solution on this entity that these lungs act on.
+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;
[Dependency] private readonly SharedInternalsSystem _internals = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
- public static string LungSolutionName = "Lung";
-
public override void Initialize()
{
base.Initialize();
}
}
+ // 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))
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;
{
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;
}
// 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
UpdateChemicals(soln);
}
+ /// <summary>
+ /// Same as <see cref="AddThermalEnergy"/> but clamps the value between two temperature values.
+ /// </summary>
+ /// <param name="soln">Solution we're adjusting the energy of</param>
+ /// <param name="thermalEnergy">Thermal energy we're adding or removing</param>
+ /// <param name="min">Min desired temperature</param>
+ /// <param name="max">Max desired temperature</param>
+ public void AddThermalEnergyClamped(Entity<SolutionComponent> 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
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+ [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
/// <summary>
/// A cache of all reactions indexed by at most ONE of their required reactants.
private void OnReaction(Entity<SolutionComponent> 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
/// <summary>
/// Effects to be triggered when the reaction occurs.
/// </summary>
- [DataField("effects")] public List<EntityEffect> Effects = new();
+ [DataField("effects")] public EntityEffect[] Effects = [];
/// <summary>
/// How dangerous is this effect? Stuff like bicaridine should be low, while things like methamphetamine
public HashSet<string>? Reagents = null;
[DataField("effects", required: true)]
- public List<EntityEffect> Effects = default!;
+ public EntityEffect[] Effects = default!;
[DataField("groups", readOnly: true, serverOnly: true,
customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<ReactionMethod>, ReactiveGroupPrototype>))]
-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<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
- ReactionEntity(uid, method, proto, reagentQuantity, source);
- }
+ if (reagentQuantity.Quantity == FixedPoint2.Zero)
+ return;
- public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentPrototype proto,
- ReagentQuantity reagentQuantity, Solution? source)
- {
- if (!TryComp(uid, out ReactiveComponent? reactive))
+ // We throw if the reagent specified doesn't exist.
+ if (!_proto.Resolve<ReagentPrototype>(reagentQuantity.Reagent.Prototype, out var proto))
return;
- // custom event for bypassing reactivecomponent stuff
- var ev = new ReactionEntityEvent(method, proto, reagentQuantity, source);
+ 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
}
[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);
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;
[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<ReagentData>? data)
{
var removed = FixedPoint2.Zero;
return removed;
}
- public void ReactionPlant(EntityUid? plantHolder,
- ReagentQuantity amount,
- Solution solution,
- EntityManager entityManager,
- IRobustRandom random,
- ISharedAdminLogManager logger)
+ public IEnumerable<string> GuidebookReagentEffectsDescription(IPrototypeManager prototype, IEntitySystemManager entSys, IEnumerable<EntityEffect> 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;
-
- 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}");
- }
-
- plantMetabolizable.Effect(args);
- }
+ public string? GuidebookReagentEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys, EntityEffect effect, FixedPoint2? metabolism)
+ {
+ if (effect.EntityEffectGuidebookText(prototype, entSys) is not { } description)
+ return null;
+
+ var quantity = metabolism == null ? 0f : (double) (effect.MinScale * metabolism);
+
+ 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<string>()
+ )));
}
}
{
public string ReagentPrototype;
+ // TODO: Kill Metabolism groups!
public Dictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsGuideEntry>? GuideEntries;
public List<string>? PlantMetabolisms = null;
{
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<string>(proto.PlantMetabolisms
- .Select(x => x.GuidebookEffectDescription(prototype, entSys))
- .Where(x => x is not null)
- .Select(x => x!)
- .ToArray());
+ PlantMetabolisms =
+ new List<string>(proto.GuidebookReagentEffectsDescription(prototype, entSys, proto.PlantMetabolisms));
}
}
}
[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());
}
}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class BreathingCondition : EntityConditionBase<BreathingCondition>
+{
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+ Loc.GetString("reagent-effect-condition-guidebook-breathing", ("isBreathing", !Inverted));
+}
--- /dev/null
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <summary>
+/// Returns true if this entity's hunger is within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TotalHungerEntityConditionSystem : EntityConditionSystem<HungerComponent, HungerCondition>
+{
+ [Dependency] private readonly HungerSystem _hunger = default!;
+
+ protected override void Condition(Entity<HungerComponent> entity, ref EntityConditionEvent<HungerCondition> args)
+ {
+ var total = _hunger.GetHunger(entity.Comp);
+ args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class HungerCondition : EntityConditionBase<HungerCondition>
+{
+ [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));
+}
--- /dev/null
+using Content.Shared.Body.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <summary>
+/// Returns true if this entity is using internals. False if they are not or cannot use internals.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class InternalsOnEntityConditionSystem : EntityConditionSystem<InternalsComponent, InternalsCondition>
+{
+ protected override void Condition(Entity<InternalsComponent> entity, ref EntityConditionEvent<InternalsCondition> args)
+ {
+ args.Result = entity.Comp.GasTankEntity != null;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class InternalsCondition : EntityConditionBase<InternalsCondition>
+{
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+ Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", !Inverted));
+}
--- /dev/null
+using Content.Shared.Body.Prototypes;
+using Content.Shared.Localizations;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class MetabolizerTypeCondition : EntityConditionBase<MetabolizerTypeCondition>
+{
+ [DataField(required: true)]
+ public ProtoId<MetabolizerTypePrototype>[] Type = default!;
+
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+ {
+ var typeList = new List<string>();
+
+ 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));
+ }
+}
--- /dev/null
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <summary>
+/// Returns true if this entity's current mob state matches the condition's specified mob state.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class MobStateEntityConditionSystem : EntityConditionSystem<MobStateComponent, MobStateCondition>
+{
+ protected override void Condition(Entity<MobStateComponent> entity, ref EntityConditionEvent<MobStateCondition> args)
+ {
+ if (entity.Comp.CurrentState == args.Condition.Mobstate)
+ args.Result = true;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class MobStateCondition : EntityConditionBase<MobStateCondition>
+{
+ [DataField]
+ public MobState Mobstate = MobState.Alive;
+
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+ Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate));
+}
--- /dev/null
+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;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasJobEntityConditionSystem : EntityConditionSystem<MindContainerComponent, JobCondition>
+{
+ protected override void Condition(Entity<MindContainerComponent> entity, ref EntityConditionEvent<JobCondition> args)
+ {
+ // We need a mind in our mind container...
+ if (!TryComp<MindComponent>(entity.Comp.Mind, out var mind))
+ return;
+
+ foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
+ {
+ if (!HasComp<JobRoleComponent>(roleId))
+ continue;
+
+ if (!TryComp<MindRoleComponent>(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;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class JobCondition : EntityConditionBase<JobCondition>
+{
+ [DataField(required: true)] public List<ProtoId<JobPrototype>> 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)));
+ }
+}
--- /dev/null
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class ReagentThresholdEntityConditionSystem : EntityConditionSystem<SolutionComponent, ReagentCondition>
+{
+ protected override void Condition(Entity<SolutionComponent> entity, ref EntityConditionEvent<ReagentCondition> args)
+ {
+ var quant = entity.Comp.Solution.GetTotalPrototypeQuantity(args.Condition.Reagent);
+
+ args.Result = quant >= args.Condition.Min && quant <= args.Condition.Max;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class ReagentCondition : EntityConditionBase<ReagentCondition>
+{
+ [DataField]
+ public FixedPoint2 Min = FixedPoint2.Zero;
+
+ [DataField]
+ public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+ [DataField(required: true)]
+ public ProtoId<ReagentPrototype> 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()));
+ }
+}
--- /dev/null
+using Content.Shared.Localizations;
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Tags;
+
+/// <summary>
+/// Returns true if this entity has all the listed tags.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasAllTagsEntityConditionSystem : EntityConditionSystem<TagComponent, AllTagsCondition>
+{
+ [Dependency] private readonly TagSystem _tag = default!;
+
+ protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<AllTagsCondition> args)
+ {
+ args.Result = _tag.HasAllTags(entity.Comp, args.Condition.Tags);
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class AllTagsCondition : EntityConditionBase<AllTagsCondition>
+{
+ [DataField(required: true)]
+ public ProtoId<TagPrototype>[] Tags = [];
+
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+ {
+ var tagList = new List<string>();
+
+ 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));
+ }
+}
--- /dev/null
+using Content.Shared.Localizations;
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Tags;
+
+/// <summary>
+/// Returns true if this entity have any of the listed tags.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasAnyTagEntityConditionSystem : EntityConditionSystem<TagComponent, AnyTagCondition>
+{
+ [Dependency] private readonly TagSystem _tag = default!;
+
+ protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<AnyTagCondition> args)
+ {
+ args.Result = _tag.HasAnyTag(entity.Comp, args.Condition.Tags);
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class AnyTagCondition : EntityConditionBase<AnyTagCondition>
+{
+ [DataField(required: true)]
+ public ProtoId<TagPrototype>[] Tags = [];
+
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+ {
+ var tagList = new List<string>();
+
+ 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));
+ }
+}
--- /dev/null
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Tags;
+
+/// <summary>
+/// Returns true if this entity has the listed tag.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasTagEntityConditionSystem : EntityConditionSystem<TagComponent, TagCondition>
+{
+ [Dependency] private readonly TagSystem _tag = default!;
+
+ protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<TagCondition> args)
+ {
+ args.Result = _tag.HasTag(entity.Comp, args.Condition.Tag);
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TagCondition : EntityConditionBase<TagCondition>
+{
+ [DataField(required: true)]
+ public ProtoId<TagPrototype> Tag;
+
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+ Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Inverted));
+}
--- /dev/null
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Temperature.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity has an amount of reagent in it within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TemperatureEntityConditionSystem : EntityConditionSystem<TemperatureComponent, TemperatureCondition>
+{
+ protected override void Condition(Entity<TemperatureComponent> entity, ref EntityConditionEvent<TemperatureCondition> args)
+ {
+ if (entity.Comp.CurrentTemperature >= args.Condition.Min && entity.Comp.CurrentTemperature <= args.Condition.Max)
+ args.Result = true;
+ }
+}
+
+/// <summary>
+/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class SolutionTemperatureEntityConditionSystem : EntityConditionSystem<SolutionComponent, TemperatureCondition>
+{
+ protected override void Condition(Entity<SolutionComponent> entity, ref EntityConditionEvent<TemperatureCondition> args)
+ {
+ if (entity.Comp.Solution.Temperature >= args.Condition.Min && entity.Comp.Solution.Temperature <= args.Condition.Max)
+ args.Result = true;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TemperatureCondition : EntityConditionBase<TemperatureCondition>
+{
+ /// <summary>
+ /// Minimum allowed temperature
+ /// </summary>
+ [DataField]
+ public float Min = 0;
+
+ /// <summary>
+ /// Maximum allowed temperature
+ /// </summary>
+ [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));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+///<summary>
+/// A basic summary of this condition.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TemplateEntityConditionSystem : EntityConditionSystem<MetaDataComponent, TemplateCondition>
+{
+ protected override void Condition(Entity<MetaDataComponent> entity, ref EntityConditionEvent<TemplateCondition> args)
+ {
+ // Condition goes here.
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TemplateCondition : EntityConditionBase<TemplateCondition>
+{
+ public override string EntityConditionGuidebookText(IPrototypeManager prototype) => String.Empty;
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity can take damage and if its total damage is within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TotalDamageEntityConditionSystem : EntityConditionSystem<DamageableComponent, TotalDamageCondition>
+{
+ protected override void Condition(Entity<DamageableComponent> entity, ref EntityConditionEvent<TotalDamageCondition> args)
+ {
+ var total = entity.Comp.TotalDamage;
+ args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
+ }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TotalDamageCondition : EntityConditionBase<TotalDamageCondition>
+{
+ [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()));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions;
+
+/// <summary>
+/// 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.
+/// </summary>
+public sealed partial class SharedEntityConditionsSystem : EntitySystem, IEntityConditionRaiser
+{
+ /// <summary>
+ /// Checks a list of conditions to verify that they all return true.
+ /// </summary>
+ /// <param name="target">Target entity we're checking conditions on</param>
+ /// <param name="conditions">Conditions we're checking</param>
+ /// <returns>Returns true if all conditions return true, false if any fail</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Checks a list of conditions to see if any are true.
+ /// </summary>
+ /// <param name="target">Target entity we're checking conditions on</param>
+ /// <param name="conditions">Conditions we're checking</param>
+ /// <returns>Returns true if any conditions return true</returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Checks a single <see cref="EntityCondition"/> on an entity.
+ /// </summary>
+ /// <param name="target">Target entity we're checking conditions on</param>
+ /// <param name="condition">Condition we're checking</param>
+ /// <returns>Returns true if we meet the condition and false otherwise</returns>
+ public bool TryCondition(EntityUid target, EntityCondition condition)
+ {
+ return condition.Inverted != condition.RaiseEvent(target, this);
+ }
+
+ /// <summary>
+ /// Raises a condition to an entity. You should not be calling this unless you know what you're doing.
+ /// </summary>
+ public bool RaiseConditionEvent<T>(EntityUid target, T effect) where T : EntityConditionBase<T>
+ {
+ var effectEv = new EntityConditionEvent<T>(effect);
+ RaiseLocalEvent(target, ref effectEv);
+ return effectEv.Result;
+ }
+}
+
+/// <summary>
+/// This is a basic abstract entity effect containing all the data an entity effect needs to affect entities with effects...
+/// </summary>
+/// <typeparam name="T">The Component that is required for the effect</typeparam>
+/// <typeparam name="TCon">The Condition we're testing</typeparam>
+public abstract partial class EntityConditionSystem<T, TCon> : EntitySystem where T : Component where TCon : EntityConditionBase<TCon>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<T, EntityConditionEvent<TCon>>(Condition);
+ }
+ protected abstract void Condition(Entity<T> entity, ref EntityConditionEvent<TCon> args);
+}
+
+/// <summary>
+/// Used to raise an EntityCondition without losing the type of condition.
+/// </summary>
+public interface IEntityConditionRaiser
+{
+ bool RaiseConditionEvent<T>(EntityUid target, T effect) where T : EntityConditionBase<T>;
+}
+
+/// <summary>
+/// Used to store an <see cref="EntityCondition"/> so it can be raised without losing the type of the condition.
+/// </summary>
+/// <typeparam name="T">The Condition wer are raising.</typeparam>
+public abstract partial class EntityConditionBase<T> : EntityCondition where T : EntityConditionBase<T>
+{
+ 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);
+ }
+}
+
+/// <summary>
+/// A basic condition which can be checked for on an entity via events.
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class EntityCondition
+{
+ public abstract bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser);
+
+ /// <summary>
+ /// If true, invert the result. So false returns true and true returns false!
+ /// </summary>
+ [DataField]
+ public bool Inverted;
+
+ /// <summary>
+ /// A basic description of this condition, which displays in the guidebook.
+ /// </summary>
+ public abstract string EntityConditionGuidebookText(IPrototypeManager prototype);
+}
+
+/// <summary>
+/// An Event carrying an entity effect.
+/// </summary>
+/// <param name="Condition">The Condition we're checking</param>
+[ByRefEvent]
+public record struct EntityConditionEvent<T>(T Condition) where T : EntityConditionBase<T>
+{
+ /// <summary>
+ /// The result of our check, defaults to false if nothing handles it.
+ /// </summary>
+ [DataField]
+ public bool Result;
+
+ /// <summary>
+ /// The Condition being raised in this event
+ /// </summary>
+ public readonly T Condition = Condition;
+}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-/// Requires the target entity to be above or below a certain temperature.
-/// Used for things like cryoxadone and pyroxadone.
-/// </summary>
-public sealed partial class Temperature : EventEntityEffectCondition<Temperature>
-{
- [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));
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-/// Condition for if the entity is successfully breathing.
-/// </summary>
-public sealed partial class Breathing : EventEntityEffectCondition<Breathing>
-{
- /// <summary>
- /// If true, the entity must not have trouble breathing to pass.
- /// </summary>
- [DataField]
- public bool IsBreathing = true;
-
- public override string GuidebookExplanation(IPrototypeManager prototype)
- {
- return Loc.GetString("reagent-effect-condition-guidebook-breathing",
- ("isBreathing", IsBreathing));
- }
-}
+++ /dev/null
-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<TagPrototype>))]
- public string Tag = default!;
-
- [DataField]
- public bool Invert = false;
-
- public override bool Condition(EntityEffectBaseArgs args)
- {
- if (args.EntityManager.TryGetComponent<TagComponent>(args.TargetEntity, out var tag))
- return args.EntityManager.System<TagSystem>().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));
- }
-}
+++ /dev/null
-using Content.Shared.Body.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-/// Condition for if the entity is or isn't wearing internals.
-/// </summary>
-public sealed partial class Internals : EntityEffectCondition
-{
- /// <summary>
- /// To pass, the entity's internals must have this same state.
- /// </summary>
- [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));
- }
-}
+++ /dev/null
-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<ProtoId<JobPrototype>> Job;
-
- public override bool Condition(EntityEffectBaseArgs args)
- {
- args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
-
- if (mindContainer is null
- || !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
- return false;
-
- foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
- {
- if (!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
- continue;
-
- if (!args.EntityManager.TryGetComponent<MindRoleComponent>(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)));
- }
-}
+++ /dev/null
-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));
- }
-}
-
+++ /dev/null
-// 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;
-
-/// <summary>
-/// Requires that the metabolizing organ is or is not tagged with a certain MetabolizerType
-/// </summary>
-public sealed partial class OrganType : EventEntityEffectCondition<OrganType>
-{
- [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<MetabolizerTypePrototype>))]
- public string Type = default!;
-
- /// <summary>
- /// Does this condition pass when the organ has the type, or when it doesn't have the type?
- /// </summary>
- [DataField]
- public bool ShouldHave = true;
-
- public override string GuidebookExplanation(IPrototypeManager prototype)
- {
- return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
- ("name", prototype.Index<MetabolizerTypePrototype>(Type).LocalizedName),
- ("shouldhave", ShouldHave));
- }
-}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.FixedPoint;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-/// 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.
-/// </summary>
-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()));
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-/// Requires the solution to be above or below a certain temperature.
-/// Used for things like explosives.
-/// </summary>
-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));
- }
-}
+++ /dev/null
-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()));
- }
-}
+++ /dev/null
-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<HungerSystem>().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));
- }
-}
+++ /dev/null
-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<SharedSolutionContainerSystem>();
- 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));
- }
-}
+++ /dev/null
-using Content.Shared.Alert;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class AdjustAlert : EntityEffect
-{
- /// <summary>
- /// The specific Alert that will be adjusted
- /// </summary>
- [DataField(required: true)]
- public ProtoId<AlertPrototype> AlertType;
-
- /// <summary>
- /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
- /// </summary>
- [DataField]
- public bool Clear;
-
- /// <summary>
- /// Visually display cooldown progress over the alert icon.
- /// </summary>
- [DataField]
- public bool ShowCooldown;
-
- /// <summary>
- /// The length of the cooldown or delay before removing the alert (in seconds).
- /// </summary>
- [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<AlertsSystem>();
- if (!args.EntityManager.HasComponent<AlertsComponent>(args.TargetEntity))
- return;
-
- if (Clear && Time <= 0)
- {
- alertSys.ClearAlert(args.TargetEntity, AlertType);
- }
- else
- {
- var timing = IoCManager.Resolve<IGameTiming>();
- (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);
- }
-
- }
-}
--- /dev/null
+using Content.Shared.Alert;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Adjusts a given alert on this entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustAlertEntityEffectSysten : EntityEffectSystem<AlertsComponent, AdjustAlert>
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+
+ protected override void Effect(Entity<AlertsComponent> entity, ref EntityEffectEvent<AdjustAlert> 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);
+ }
+
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustAlert : EntityEffectBase<AdjustAlert>
+{
+ /// <summary>
+ /// The specific Alert that will be adjusted
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<AlertPrototype> AlertType;
+
+ /// <summary>
+ /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
+ /// </summary>
+ [DataField]
+ public bool Clear;
+
+ /// <summary>
+ /// Visually display cooldown progress over the alert icon.
+ /// </summary>
+ [DataField]
+ public bool ShowCooldown;
+
+ /// <summary>
+ /// The length of the cooldown or delay before removing the alert (in seconds).
+ /// </summary>
+ [DataField]
+ public TimeSpan Time;
+}
+++ /dev/null
-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
- {
- /// <summary>
- /// The reagent ID to remove. Only one of this and <see cref="Group"/> should be active.
- /// </summary>
- [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
- public string? Reagent = null;
- // TODO use ReagentId
-
- /// <summary>
- /// The metabolism group to remove, if the reagent satisfies any.
- /// Only one of this and <see cref="Reagent"/> should be active.
- /// </summary>
- [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<MetabolismGroupPrototype>))]
- 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<IPrototypeManager>();
- foreach (var quant in reagentArgs.Source.Contents.ToArray())
- {
- var proto = prototypeMan.Index<ReagentPrototype>(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();
- }
- }
-}
-
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class AdjustTemperature : EventEntityEffect<AdjustTemperature>
-{
- [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)));
-}
--- /dev/null
+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!!!
+/// <summary>
+/// Adjusts the temperature of this entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustTemperatureEntityEffectSystem : EntityEffectSystem<TemperatureComponent, AdjustTemperature>
+{
+ [Dependency] private readonly SharedTemperatureSystem _temperature = default!;
+ protected override void Effect(Entity<TemperatureComponent> entity, ref EntityEffectEvent<AdjustTemperature> args)
+ {
+ var amount = args.Effect.Amount * args.Scale;
+
+ _temperature.ChangeHeat(entity, amount, true, entity.Comp);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustTemperature : EntityEffectBase<AdjustTemperature>
+{
+ /// <summary>
+ /// Amount we're adjusting temperature by.
+ /// </summary>
+ [DataField]
+ public float Amount;
+}
+++ /dev/null
-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;
-
-/// <summary>
-/// Basically smoke and foam reactions.
-/// </summary>
-public sealed partial class AreaReactionEffect : EventEntityEffect<AreaReactionEffect>
-{
- /// <summary>
- /// How many seconds will the effect stay, counting after fully spreading.
- /// </summary>
- [DataField("duration")] public float Duration = 10;
-
- /// <summary>
- /// How many units of reaction for 1 smoke entity.
- /// </summary>
- [DataField] public FixedPoint2 OverflowThreshold = FixedPoint2.New(2.5);
-
- /// <summary>
- /// The entity prototype that will be spawned as the effect.
- /// </summary>
- [DataField("prototypeId", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string PrototypeId = default!;
-
- /// <summary>
- /// Sound that will get played when this reaction effect occurs.
- /// </summary>
- [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;
-}
+++ /dev/null
-using Content.Shared.Xenoarchaeology.Artifact.Components;
-using Content.Shared.Xenoarchaeology.Artifact;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Restores durability in active artefact nodes.
-/// </summary>
-public sealed partial class ArtifactDurabilityRestore : EntityEffect
-{
- /// <summary>
- /// Amount of durability that will be restored per effect interaction.
- /// </summary>
- [DataField]
- public int RestoredDurability = 1;
-
- public override void Effect(EntityEffectBaseArgs args)
- {
- var entMan = args.EntityManager;
- var xenoArtifactSys = entMan.System<SharedXenoArtifactSystem>();
-
- if (!entMan.TryGetComponent<XenoArtifactComponent>(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));
- }
-}
--- /dev/null
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Restores durability on this artifact
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ArtifactDurabilityRestoreEntityEffectsSystem : EntityEffectSystem<XenoArtifactComponent, ArtifactDurabilityRestore>
+{
+ [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!;
+
+ protected override void Effect(Entity<XenoArtifactComponent> entity, ref EntityEffectEvent<ArtifactDurabilityRestore> args)
+ {
+ var durability = args.Effect.RestoredDurability;
+
+ foreach (var node in _xenoArtifact.GetActiveNodes(entity))
+ {
+ _xenoArtifact.AdjustNodeDurability(node.Owner, durability);
+ }
+ }
+}
+
+/// <summary>
+/// Unlocks a node on this artifact. Only works this effect hasn't been applied before.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ArtifactUnlockEntityEffectSystem : EntityEffectSystem<XenoArtifactComponent, ArtifactUnlock>
+{
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!;
+
+ protected override void Effect(Entity<XenoArtifactComponent> entity, ref EntityEffectEvent<ArtifactUnlock> args)
+ {
+ if (EnsureComp<XenoArtifactUnlockingComponent>(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);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ArtifactDurabilityRestore : EntityEffectBase<ArtifactDurabilityRestore>
+{
+ /// <summary>
+ /// Amount of durability that will be restored per effect interaction.
+ /// </summary>
+ [DataField]
+ public int RestoredDurability = 1;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-artifact-durability-restore", ("restored", RestoredDurability));
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ArtifactUnlock : EntityEffectBase<ArtifactUnlock>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-artifact-unlock", ("chance", Probability));
+}
+++ /dev/null
-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;
-
-/// <summary>
-/// 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.
-/// </summary>
-public sealed partial class ArtifactUnlock : EntityEffect
-{
- public override void Effect(EntityEffectBaseArgs args)
- {
- var entMan = args.EntityManager;
- var xenoArtifactSys = entMan.System<SharedXenoArtifactSystem>();
- var popupSys = entMan.System<SharedPopupSystem>();
-
- if (!entMan.TryGetComponent<XenoArtifactComponent>(args.TargetEntity, out var xenoArtifact))
- return;
-
- if (!entMan.TryGetComponent<XenoArtifactUnlockingComponent>(args.TargetEntity, out var unlocking))
- {
- xenoArtifactSys.TriggerXenoArtifact((args.TargetEntity, xenoArtifact), null, force: true);
- unlocking = entMan.EnsureComponent<XenoArtifactUnlockingComponent>(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));
- }
-}
--- /dev/null
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CreateGas : EntityEffectBase<CreateGas>
+{
+ /// <summary>
+ /// The gas we're creating
+ /// </summary>
+ [DataField]
+ public Gas Gas;
+
+ /// <summary>
+ /// Amount of moles we're creating
+ /// </summary>
+ [DataField]
+ public float Moles = 3f;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ var atmos = entSys.GetEntitySystem<SharedAtmosphereSystem>();
+ var gasProto = atmos.GetGas(Gas);
+
+ return Loc.GetString("entity-effect-guidebook-create-gas",
+ ("chance", Probability),
+ ("moles", Moles),
+ ("gas", gasProto.Name));
+ }
+}
--- /dev/null
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// This raises an extinguish event on a given entity, reducing FireStacks.
+/// The amount of FireStacks reduced is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ExtinguishEntityEffectSystem : EntityEffectSystem<FlammableComponent, Extinguish>
+{
+ protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Extinguish> args)
+ {
+ var ev = new ExtinguishEvent
+ {
+ FireStacksAdjustment = args.Effect.FireStacksAdjustment * args.Scale,
+ };
+
+ RaiseLocalEvent(entity, ref ev);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Extinguish : EntityEffectBase<Extinguish>
+{
+ /// <summary>
+ /// Amount of FireStacks reduced.
+ /// </summary>
+ [DataField]
+ public float FireStacksAdjustment = -1.5f;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-extinguish-reaction", ("chance", Probability));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Flammable : EntityEffectBase<Flammable>
+{
+ /// <summary>
+ /// Fire stack multiplier applied on an entity,
+ /// unless that entity is already on fire and <see cref="MultiplierOnExisting"/> is not null.
+ /// </summary>
+ [DataField]
+ public float Multiplier = 0.05f;
+
+ /// <summary>
+ /// Fire stack multiplier applied if the entity is already on fire. Defaults to <see cref="Multiplier"/> if null.
+ /// </summary>
+ [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;
+}
--- /dev/null
+using Content.Shared.Database;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// See serverside system
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Ignite : EntityEffectBase<Ignite>
+{
+ 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;
+}
--- /dev/null
+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;
+
+/// <summary>
+/// Removes a given amount of chemicals from the bloodstream modified by scale.
+/// Optionally ignores a given chemical.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class CleanBloodstreamEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, CleanBloodstream>
+{
+ [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
+
+ protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<CleanBloodstream> args)
+ {
+ var scale = args.Scale * args.Effect.CleanseRate;
+
+ _bloodstream.FlushChemicals((entity, entity), args.Effect.Excluded, scale);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CleanBloodstream : EntityEffectBase<CleanBloodstream>
+{
+ /// <summary>
+ /// Amount of reagent we're cleaning out of our bloodstream.
+ /// </summary>
+ [DataField]
+ public FixedPoint2 CleanseRate = 3.0f;
+
+ /// <summary>
+ /// An optional chemical to ignore when doing removal.
+ /// </summary>
+ [DataField]
+ public ProtoId<ReagentPrototype>? Excluded;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-clean-bloodstream", ("chance", Probability));
+}
--- /dev/null
+using Content.Shared.Eye.Blinding.Systems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Modifies eye damage by a given amount, modified by scale, floored to an integer.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EyeDamageEntityEffectSystem : EntityEffectSystem<MetaDataComponent, EyeDamage>
+{
+ [Dependency] private readonly BlindableSystem _blindable = default!;
+
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<EyeDamage> args)
+ {
+ var amount = (int) Math.Floor(args.Effect.Amount * args.Scale);
+ _blindable.AdjustEyeDamage(entity.Owner, amount);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class EyeDamage : EntityEffectBase<EyeDamage>
+{
+ /// <summary>
+ /// The amount of eye damage we're adding or removing
+ /// </summary>
+ [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)));
+}
--- /dev/null
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Modifies bleed by a given amount multiplied by scale. This can increase or decrease bleed.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyBleedEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, ModifyBleed>
+{
+ [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
+
+ protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<ModifyBleed> args)
+ {
+ _bloodstream.TryModifyBleedAmount(entity.AsNullable(), args.Effect.Amount * args.Scale);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyBleed : EntityEffectBase<ModifyBleed>
+{
+ /// <summary>
+ /// Amount of bleed we're applying or removing if negative.
+ /// </summary>
+ [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)));
+}
--- /dev/null
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyBloodLevelEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, ModifyBloodLevel>
+{
+ [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
+
+ protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<ModifyBloodLevel> args)
+ {
+ _bloodstream.TryModifyBloodLevel(entity.AsNullable(), args.Effect.Amount * args.Scale);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyBloodLevel : EntityEffectBase<ModifyBloodLevel>
+{
+ /// <summary>
+ /// Amount of bleed we're applying or removing if negative.
+ /// </summary>
+ [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())));
+}
--- /dev/null
+using Content.Shared.Atmos;
+using Content.Shared.Body.Components;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyLungGasEntityEffectSystem : EntityEffectSystem<LungComponent, ModifyLungGas>
+{
+ // TODO: This shouldn't be an entity effect, gasses should just metabolize and make a byproduct by default...
+ protected override void Effect(Entity<LungComponent> entity, ref EntityEffectEvent<ModifyLungGas> 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);
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyLungGas : EntityEffectBase<ModifyLungGas>
+{
+ [DataField(required: true)]
+ public Dictionary<Gas, float> Ratios = default!;
+}
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Oxygenate : EntityEffectBase<Oxygenate>
+{
+ /// <summary>
+ /// Factor of oxygenation per metabolized quantity. Lungs metabolize at about 50u per tick so we need an equal multiplier to cancel that out!
+ /// </summary>
+ [DataField]
+ public float Factor = 1f;
+}
--- /dev/null
+using Content.Shared.Atmos.Rotting;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ReduceRottingEntityEffectSystem : EntityEffectSystem<PerishableComponent, ReduceRotting>
+{
+ [Dependency] private readonly SharedRottingSystem _rotting = default!;
+
+ protected override void Effect(Entity<PerishableComponent> entity, ref EntityEffectEvent<ReduceRotting> args)
+ {
+ var amount = args.Effect.Seconds * args.Scale;
+
+ _rotting.ReduceAccumulator(entity, amount);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ReduceRotting : EntityEffectBase<ReduceRotting>
+{
+ /// <summary>
+ /// Number of seconds removed from the rotting timer.
+ /// </summary>
+ [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));
+}
--- /dev/null
+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...
+/// <summary>
+/// Modifies the thirst level of a given entity, multiplied by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SatiateThirstEntityEffectsSystem : EntityEffectSystem<ThirstComponent, SatiateThirst>
+{
+ [Dependency] private readonly ThirstSystem _thirst = default!;
+ protected override void Effect(Entity<ThirstComponent> entity, ref EntityEffectEvent<SatiateThirst> args)
+ {
+ _thirst.ModifyThirst(entity, entity.Comp, args.Effect.Factor * args.Scale);
+ }
+}
+
+/// <summary>
+/// Modifies the hunger level of a given entity, multiplied by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SatiateHungerEntityEffectsSystem : EntityEffectSystem<HungerComponent, SatiateHunger>
+{
+ [Dependency] private readonly HungerSystem _hunger = default!;
+ protected override void Effect(Entity<HungerComponent> entity, ref EntityEffectEvent<SatiateHunger> args)
+ {
+ _hunger.ModifyHunger(entity, args.Effect.Factor * args.Scale, entity.Comp);
+ }
+}
+
+/// <summary>
+/// A type of <see cref="EntityEffectBase{T}"/> made for satiation effects.
+/// </summary>
+/// <typeparam name="T">The effect inheriting this BaseEffect</typeparam>
+/// <inheritdoc cref="EntityEffect"/>
+public abstract partial class Satiate<T> : EntityEffectBase<T> where T : EntityEffectBase<T>
+{
+ public const float AverageSatiation = 3f; // Magic number. Not sure how it was calculated since I didn't make it.
+
+ /// <summary>
+ /// Change in satiation.
+ /// </summary>
+ [DataField]
+ public float Factor = -1.5f;
+}
+
+/// <inheritdoc cref="Satiate{T}"/>
+public sealed partial class SatiateThirst : Satiate<SatiateThirst>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", Factor / AverageSatiation));
+}
+
+/// <inheritdoc cref="Satiate{T}"/>
+public sealed partial class SatiateHunger : Satiate<SatiateHunger>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", Factor / AverageSatiation));
+}
--- /dev/null
+using Content.Shared.Medical;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Makes an entity vomit and reduces hunger and thirst by a given amount, modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class VomitEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Vomit>
+{
+ [Dependency] private readonly VomitSystem _vomit = default!;
+
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Vomit> args)
+ {
+ _vomit.Vomit(entity.Owner, args.Effect.ThirstAmount * args.Scale, args.Effect.HungerAmount * args.Scale);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Vomit : EntityEffectBase<Vomit>
+{
+ /// <summary>
+ /// How much we adjust our thirst after vomiting.
+ /// </summary>
+ [DataField]
+ public float ThirstAmount = -8f;
+
+ /// <summary>
+ /// How much we adjust our hunger after vomiting.
+ /// </summary>
+ [DataField]
+ public float HungerAmount = -8f;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-vomit", ("chance", Probability));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// A type of <see cref="EntityEffectBase{T}"/> which modifies the attribute of a Seed in a PlantHolder.
+/// These are not modified by scale as botany has no concept of scale.
+/// </summary>
+/// <typeparam name="T">The effect inheriting this BaseEffect</typeparam>
+/// <inheritdoc cref="EntityEffect"/>
+public abstract partial class BasePlantAdjustAttribute<T> : EntityEffectBase<T> where T : BasePlantAdjustAttribute<T>
+{
+ /// <summary>
+ /// How much we're adjusting the given attribute by.
+ /// </summary>
+ [DataField]
+ public float Amount { get; protected set; } = 1;
+
+ /// <summary>
+ /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions.
+ /// </summary>
+ [DataField]
+ public abstract string GuidebookAttributeName { get; set; }
+
+ /// <summary>
+ /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number.
+ /// </summary>
+ [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));
+}
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustHealth : BasePlantAdjustAttribute<PlantAdjustHealth>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
+}
+
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationLevel : BasePlantAdjustAttribute<PlantAdjustMutationLevel>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
+}
+
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationMod : BasePlantAdjustAttribute<PlantAdjustMutationMod>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
+}
+
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustNutrition : BasePlantAdjustAttribute<PlantAdjustNutrition>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
+}
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
-public sealed partial class PlantAdjustPests : PlantAdjustAttribute<PlantAdjustPests>
+public sealed partial class PlantAdjustPests : BasePlantAdjustAttribute<PlantAdjustPests>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// Handles increase or decrease of plant potency.
+/// </summary>
+public sealed partial class PlantAdjustPotency : BasePlantAdjustAttribute<PlantAdjustPotency>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency";
+}
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
-public sealed partial class PlantAdjustToxins : PlantAdjustAttribute<PlantAdjustToxins>
+public sealed partial class PlantAdjustToxins : BasePlantAdjustAttribute<PlantAdjustToxins>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-toxins";
+
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
}
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustWater : BasePlantAdjustAttribute<PlantAdjustWater>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-water";
+}
+
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
-public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute<PlantAdjustWeeds>
+public sealed partial class PlantAdjustWeeds : BasePlantAdjustAttribute<PlantAdjustWeeds>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-weeds";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAffectGrowth : BasePlantAdjustAttribute<PlantAffectGrowth>
+{
+ public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth";
+}
+
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantChangeStat : EntityEffectBase<PlantChangeStat>
+{
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ [DataField (required: true)]
+ public string TargetValue = string.Empty;
+
+ [DataField]
+ public float MinValue;
+
+ [DataField]
+ public float MaxValue;
+
+ [DataField]
+ public int Steps;
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantCryoxadone : EntityEffectBase<PlantCryoxadone>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-plant-cryoxadone", ("chance", Probability));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// Handles removal of seeds on a plant.
+/// </summary>
+public sealed partial class PlantDestroySeeds : EntityEffectBase<PlantDestroySeeds>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-plant-seeds-remove", ("chance", Probability));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantDiethylamine : EntityEffectBase<PlantDiethylamine>
+{
+ /// <inheritdoc/>
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-plant-diethylamine", ("chance", Probability));
+}
+
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantPhalanximine : EntityEffectBase<PlantPhalanximine>
+{
+ /// <inheritdoc/>
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-plant-phalanximine", ("chance", Probability));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// Handles restoral of seeds on a plant.
+/// </summary>
+public sealed partial class PlantRestoreSeeds : EntityEffectBase<PlantRestoreSeeds>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-plant-seeds-add", ("chance", Probability));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class RobustHarvest : EntityEffectBase<RobustHarvest>
+{
+ [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));
+}
--- /dev/null
+using Content.Shared.Random;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateChemicals : EntityEffectBase<PlantMutateChemicals>
+{
+ /// <summary>
+ /// The Reagent list this mutation draws from.
+ /// </summary>
+ [DataField]
+ public ProtoId<WeightedRandomFillSolutionPrototype> RandomPickBotanyReagent = "RandomPickBotanyReagent";
+}
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateConsumeGases : EntityEffectBase<PlantMutateConsumeGases>
+{
+ [DataField]
+ public float MinValue = 0.01f;
+
+ [DataField]
+ public float MaxValue = 0.5f;
+}
+
+public sealed partial class PlantMutateExudeGases : EntityEffectBase<PlantMutateExudeGases>
+{
+ [DataField]
+ public float MinValue = 0.01f;
+
+ [DataField]
+ public float MaxValue = 0.5f;
+}
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateHarvest : EntityEffectBase<PlantMutateHarvest>;
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateSpeciesChange : EntityEffectBase<PlantMutateSpeciesChange>;
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class CauseZombieInfection : EventEntityEffect<CauseZombieInfection>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString("reagent-effect-guidebook-cause-zombie-infection", ("chance", Probability));
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Basically smoke and foam reactions.
-/// </summary>
-public sealed partial class ChemCleanBloodstream : EventEntityEffect<ChemCleanBloodstream>
-{
- [DataField]
- public float CleanseRate = 3.0f;
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
-}
+++ /dev/null
-using Content.Shared.Eye.Blinding.Systems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Heal or apply eye damage
-/// </summary>
-public sealed partial class ChemHealEyeDamage : EntityEffect
-{
- /// <summary>
- /// How much eye damage to add.
- /// </summary>
- [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<BlindableSystem>().AdjustEyeDamage(args.TargetEntity, Amount);
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Forces you to vomit.
-/// </summary>
-public sealed partial class ChemVomit : EventEntityEffect<ChemVomit>
-{
- /// 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));
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-[DataDefinition]
-public sealed partial class CreateEntityReactionEffect : EventEntityEffect<CreateEntityReactionEffect>
-{
- /// <summary>
- /// What entity to create.
- /// </summary>
- [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
- public string Entity = default!;
-
- /// <summary>
- /// How many entities to create per unit reaction.
- /// </summary>
- [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<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
- ("amount", Number));
-}
+++ /dev/null
-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<CreateGas>
-{
- [DataField(required: true)]
- public Gas Gas = default!;
-
- /// <summary>
- /// For each unit consumed, how many moles of gas should be created?
- /// </summary>
- [DataField]
- public float Multiplier = 3f;
-
- public override bool ShouldLog => true;
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- var atmos = entSys.GetEntitySystem<SharedAtmosphereSystem>();
- 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;
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class CureZombieInfection : EventEntityEffect<CureZombieInfection>
-{
- [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));
- }
-}
-
+++ /dev/null
-using Content.Shared.Drunk;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Drunk : EntityEffect
-{
- /// <summary>
- /// BoozePower is how long each metabolism cycle will make the drunk effect last for.
- /// </summary>
- [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<SharedDrunkSystem>();
- drunkSys.TryApplyDrunkenness(args.TargetEntity, boozePower);
- }
-}
+++ /dev/null
-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;
-
- /// <remarks>
- /// true - refresh electrocute time, false - accumulate electrocute time
- /// </remarks>
- [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<SharedElectrocutionSystem>().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<SharedElectrocutionSystem>().TryDoElectrocution(args.TargetEntity, null,
- Math.Max(ElectrocuteDamageScale, 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
- }
- }
-}
+++ /dev/null
-using Content.Shared.Chat.Prototypes;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits unless specially forced.
-/// </summary>
-public sealed partial class Emote : EventEntityEffect<Emote>
-{
- /// <summary>
- /// The emote the entity will preform.
- /// </summary>
- [DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
- public string EmoteId;
-
- /// <summary>
- /// If the emote should be recorded in chat.
- /// </summary>
- [DataField]
- public bool ShowInChat;
-
- /// <summary>
- /// If the forced emote will be listed in the guidebook.
- /// </summary>
- [DataField]
- public bool ShowInGuidebook;
-
- /// <summary>
- /// If true, the entity will preform the emote even if they normally can't.
- /// </summary>
- [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<EmotePrototype>(EmoteId);
- return Loc.GetString("reagent-effect-guidebook-emote", ("chance", Probability), ("emote", Loc.GetString(emotePrototype.Name)));
- }
-}
--- /dev/null
+using Content.Shared.Chat.Prototypes;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Emote : EntityEffectBase<Emote>
+{
+ /// <summary>
+ /// The emote the entity will preform.
+ /// </summary>
+ [DataField("emote", required: true)]
+ public ProtoId<EmotePrototype> EmoteId;
+
+ /// <summary>
+ /// If the emote should be recorded in chat.
+ /// </summary>
+ [DataField]
+ public bool ShowInChat;
+
+ /// <summary>
+ /// If the forced emote will be listed in the guidebook.
+ /// </summary>
+ [DataField]
+ public bool ShowInGuidebook;
+
+ /// <summary>
+ /// If true, the entity will preform the emote even if they normally can't.
+ /// </summary>
+ [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)));
+ }
+}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-[DataDefinition]
-public sealed partial class EmpReactionEffect : EventEntityEffect<EmpReactionEffect>
-{
- /// <summary>
- /// Impulse range per unit of quantity
- /// </summary>
- [DataField("rangePerUnit")]
- public float EmpRangePerUnit = 0.5f;
-
- /// <summary>
- /// Maximum impulse range
- /// </summary>
- [DataField("maxRange")]
- public float EmpMaxRange = 10;
-
- /// <summary>
- /// How much energy will be drain from sources
- /// </summary>
- [DataField]
- public float EnergyConsumption = 12500;
-
- /// <summary>
- /// Amount of time entities will be disabled
- /// </summary>
- [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));
-}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// A type of <see cref="EntityEffectBase{T}"/> for effects that spawn entities by prototype.
+/// </summary>
+/// <typeparam name="T">The entity effect inheriting this BaseEffect</typeparam>
+/// <inheritdoc cref="EntityEffect"/>
+public abstract partial class BaseSpawnEntityEntityEffect<T> : EntityEffectBase<T> where T : BaseSpawnEntityEntityEffect<T>
+{
+ /// <summary>
+ /// Amount of entities we're spawning
+ /// </summary>
+ [DataField]
+ public int Number = 1;
+
+ /// <summary>
+ /// Prototype of the entity we're spawning
+ /// </summary>
+ [DataField (required: true)]
+ public EntProtoId Entity;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [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<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
+ ("amount", Number));
+}
--- /dev/null
+using Robust.Shared.Network;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// Spawns a number of entities of a given prototype at the coordinates of this entity.
+/// Amount is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityEntityEffectSystem : EntityEffectSystem<TransformComponent, SpawnEntity>
+{
+ [Dependency] private readonly INetManager _net = default!;
+
+ protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<SpawnEntity> 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);
+ }
+ }
+ }
+}
+
+/// <inheritdoc cref="BaseSpawnEntityEntityEffect{T}"/>
+public sealed partial class SpawnEntity : BaseSpawnEntityEntityEffect<SpawnEntity>;
--- /dev/null
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityInContainerEntityEffectSystem : EntityEffectSystem<ContainerManagerComponent, SpawnEntityInContainer>
+{
+ [Dependency] private readonly INetManager _net = default!;
+
+ protected override void Effect(Entity<ContainerManagerComponent> entity, ref EntityEffectEvent<SpawnEntityInContainer> 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;
+ }
+ }
+ }
+}
+
+/// <inheritdoc cref="BaseSpawnEntityEntityEffect{T}"/>
+public sealed partial class SpawnEntityInContainer : BaseSpawnEntityEntityEffect<SpawnEntityInContainer>
+{
+ /// <summary>
+ /// Name of the container we're trying to spawn into.
+ /// </summary>
+ [DataField(required: true)]
+ public string ContainerName;
+}
--- /dev/null
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// Spawns a given number of entities of a given prototype in a specified container owned by this entity.
+/// Acts like <see cref="SpawnEntityEntityEffectSystem"/> if it cannot spawn the prototype in the specified container.
+/// Amount is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityInContainerOrDropEntityEffectSystem : EntityEffectSystem<ContainerManagerComponent, SpawnEntityInContainerOrDrop>
+{
+ [Dependency] private readonly INetManager _net = default!;
+
+ protected override void Effect(Entity<ContainerManagerComponent> entity, ref EntityEffectEvent<SpawnEntityInContainerOrDrop> 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);
+ }
+ }
+ }
+}
+
+/// <inheritdoc cref="BaseSpawnEntityEntityEffect{T}"/>
+public sealed partial class SpawnEntityInContainerOrDrop : BaseSpawnEntityEntityEffect<SpawnEntityInContainerOrDrop>
+{
+ /// <summary>
+ /// Name of the container we're trying to spawn into.
+ /// </summary>
+ [DataField(required: true)]
+ public string ContainerName;
+}
--- /dev/null
+using Content.Shared.Inventory;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityInInventoryEntityEffectSystem : EntityEffectSystem<InventoryComponent, SpawnEntityInInventory>
+{
+ [Dependency] private readonly InventorySystem _inventory = default!;
+
+ protected override void Effect(Entity<InventoryComponent> entity, ref EntityEffectEvent<SpawnEntityInInventory> args)
+ {
+ _inventory.SpawnItemInSlot(entity, args.Effect.Slot, args.Effect.Entity);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class SpawnEntityInInventory : EntityEffectBase<SpawnEntityInInventory>
+{
+ /// <summary>
+ /// Name of the slot we're spawning the item into.
+ /// </summary>
+ [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.
+
+ /// <summary>
+ /// Prototype ID of item to spawn.
+ /// </summary>
+ [DataField(required: true)]
+ public EntProtoId Entity;
+}
+++ /dev/null
-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;
-
-/// <summary>
-/// Version of <see cref="HealthChange"/> that distributes the healing to groups
-/// </summary>
-public sealed partial class EvenHealthChange : EntityEffect
-{
- /// <summary>
- /// Damage to heal, collected into entire damage groups.
- /// </summary>
- [DataField(required: true)]
- public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> Damage = new();
-
- /// <summary>
- /// 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.
- /// </summary>
- [DataField]
- public bool ScaleByQuantity;
-
- /// <summary>
- /// Should this effect ignore damage modifiers?
- /// </summary>
- [DataField]
- public bool IgnoreResistances = true;
-
- protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- var damages = new List<string>();
- var heals = false;
- var deals = false;
-
- var damagableSystem = entSys.GetEntitySystem<DamageableSystem>();
- 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<DamageableComponent>(args.TargetEntity, out var damageable))
- return;
-
- var protoMan = IoCManager.Resolve<IPrototypeManager>();
-
- var scale = FixedPoint2.New(1);
-
- if (args is EntityEffectReagentArgs reagentArgs)
- {
- scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale;
- }
-
- var damagableSystem = args.EntityManager.System<DamageableSystem>();
- 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<string, FixedPoint2>();
- 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);
- }
-}
--- /dev/null
+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;
+
+/// <summary>
+/// Evenly adjust the damage types in a damage group by up to a specified total on this entity.
+/// Total adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EvenHealthChangeEntityEffectSystem : EntityEffectSystem<DamageableComponent, EvenHealthChange>
+{
+ [Dependency] private readonly DamageableSystem _damageable = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ protected override void Effect(Entity<DamageableComponent> entity, ref EntityEffectEvent<EvenHealthChange> args)
+ {
+ var damageSpec = new DamageSpecifier();
+
+ foreach (var (group, amount) in args.Effect.Damage)
+ {
+ var groupProto = _proto.Index(group);
+ var groupDamage = new Dictionary<string, FixedPoint2>();
+ 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);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class EvenHealthChange : EntityEffectBase<EvenHealthChange>
+{
+ /// <summary>
+ /// Damage to heal, collected into entire damage groups.
+ /// </summary>
+ [DataField(required: true)]
+ public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> Damage = new();
+
+ /// <summary>
+ /// Should this effect ignore damage modifiers?
+ /// </summary>
+ [DataField]
+ public bool IgnoreResistances = true;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ var damages = new List<string>();
+ var heals = false;
+ var deals = false;
+
+ var damagableSystem = entSys.GetEntitySystem<DamageableSystem>();
+ 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));
+ }
+}
+++ /dev/null
-using Content.Shared.Atmos;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects
-{
- public sealed partial class ExtinguishReaction : EntityEffect
- {
- /// <summary>
- /// Amount of firestacks reduced.
- /// </summary>
- [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);
- }
- }
-}
+++ /dev/null
-using Content.Shared.Database;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class FlammableReaction : EventEntityEffect<FlammableReaction>
-{
- [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;
-}
+++ /dev/null
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-[DataDefinition]
-public sealed partial class FlashReactionEffect : EventEntityEffect<FlashReactionEffect>
-{
- /// <summary>
- /// Flash range per unit of reagent.
- /// </summary>
- [DataField]
- public float RangePerUnit = 0.2f;
-
- /// <summary>
- /// Maximum flash range.
- /// </summary>
- [DataField]
- public float MaxRange = 10f;
-
- /// <summary>
- /// How much to entities are slowed down.
- /// </summary>
- [DataField]
- public float SlowTo = 0.5f;
-
- /// <summary>
- /// 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.
- /// </summary>
- [DataField]
- public TimeSpan Duration = TimeSpan.FromSeconds(4);
-
- /// <summary>
- /// The prototype ID used for the visual effect.
- /// </summary>
- [DataField]
- public EntProtoId? FlashEffectPrototype = "ReactionFlash";
-
- /// <summary>
- /// The sound the flash creates.
- /// </summary>
- [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));
-}
+++ /dev/null
-using Content.Shared.EntityEffects;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Makes a mob glow.
-/// </summary>
-public sealed partial class Glow : EntityEffect
-{
- [DataField]
- public float Radius = 2f;
-
- [DataField]
- public Color Color = Color.Black;
-
- private static readonly List<Color> 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<IRobustRandom>();
- Color = random.Pick(Colors);
- }
-
- var lightSystem = args.EntityManager.System<SharedPointLightSystem>();
- 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";
- }
-}
--- /dev/null
+using Robust.Shared.Network;
+using Robust.Shared.Random;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Causes this entity to glow.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class GlowEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Glow>
+{
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedPointLightSystem _lightSystem = default!;
+
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Glow> 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<Color> Colors = new()
+ {
+ Color.White,
+ Color.Red,
+ Color.Yellow,
+ Color.Green,
+ Color.Blue,
+ Color.Purple,
+ Color.Pink
+ };
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Glow : EntityEffectBase<Glow>
+{
+ [DataField]
+ public float Radius = 2f;
+
+ [DataField]
+ public Color Color = Color.Black;
+}
+++ /dev/null
-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
-{
- /// <summary>
- /// Default metabolism used for medicine reagents.
- /// </summary>
- public sealed partial class HealthChange : EntityEffect
- {
- /// <summary>
- /// Damage to apply every cycle. Damage Ignores resistances.
- /// </summary>
- [DataField(required: true)]
- [JsonPropertyName("damage")]
- public DamageSpecifier Damage = default!;
-
- /// <summary>
- /// 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.
- /// </summary>
- [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<string>();
- var heals = false;
- var deals = false;
-
- var damageSpec = new DamageSpecifier(Damage);
-
- var universalReagentDamageModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentDamageModifier;
- var universalReagentHealModifier = entSys.GetEntitySystem<DamageableSystem>().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<DamageableSystem>().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<DamageTypePrototype>(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<DamageableSystem>().UniversalReagentDamageModifier;
- var universalReagentHealModifier = args.EntityManager.System<DamageableSystem>().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<DamageableSystem>()
- .TryChangeDamage(
- args.TargetEntity,
- damageSpec * scale,
- IgnoreResistances,
- interruptsDoAfters: false);
- }
- }
-}
--- /dev/null
+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;
+
+/// <summary>
+/// Adjust the damages on this entity by specified amounts.
+/// Amounts are modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class HealthChangeEntityEffectSystem : EntityEffectSystem<DamageableComponent, HealthChange>
+{
+ [Dependency] private readonly DamageableSystem _damageable = default!;
+
+ protected override void Effect(Entity<DamageableComponent> entity, ref EntityEffectEvent<HealthChange> args)
+ {
+ var damageSpec = new DamageSpecifier(args.Effect.Damage);
+
+ damageSpec *= args.Scale;
+
+ _damageable.TryChangeDamage(
+ entity,
+ damageSpec,
+ args.Effect.IgnoreResistances,
+ interruptsDoAfters: false);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class HealthChange : EntityEffectBase<HealthChange>
+{
+ /// <summary>
+ /// Damage to apply every cycle. Damage Ignores resistances.
+ /// </summary>
+ [DataField(required: true)]
+ public DamageSpecifier Damage = default!;
+
+ [DataField]
+ public bool IgnoreResistances = true;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ var damages = new List<string>();
+ var heals = false;
+ var deals = false;
+
+ var damageSpec = new DamageSpecifier(Damage);
+
+ var universalReagentDamageModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentDamageModifier;
+ var universalReagentHealModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentHealModifier;
+
+ damageSpec = entSys.GetEntitySystem<DamageableSystem>().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<DamageTypePrototype>(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));
+ }
+}
+++ /dev/null
-using Content.Shared.Database;
-using Content.Shared.EntityEffects;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Ignites a mob.
-/// </summary>
-public sealed partial class Ignite : EventEntityEffect<Ignite>
-{
- 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;
-}
+++ /dev/null
-using Content.Shared.Mind.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class MakeSentient : EventEntityEffect<MakeSentient>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability));
-}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class MakeSentient : EntityEffectBase<MakeSentient>
+{
+ /// <summary>
+ /// Description for the ghost role created by this effect.
+ /// </summary>
+ [DataField]
+ public LocId RoleDescription = "ghost-role-information-cognizine-description";
+
+ /// <summary>
+ /// Whether we give the target the ability to speak coherently.
+ /// </summary>
+ [DataField]
+ public bool AllowSpeech = true;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-make-sentient", ("chance", Probability));
+}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class ModifyBleedAmount : EventEntityEffect<ModifyBleedAmount>
-{
- [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)));
-}
+++ /dev/null
-using Content.Shared.FixedPoint;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class ModifyBloodLevel : EventEntityEffect<ModifyBloodLevel>
-{
- [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())));
-}
+++ /dev/null
-using Content.Shared.Atmos;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class ModifyLungGas : EventEntityEffect<ModifyLungGas>
-{
- [DataField("ratios", required: true)]
- public Dictionary<Gas, float> Ratios = default!;
-
- // JUSTIFICATION: This is internal magic that players never directly interact with.
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => null;
-}
+++ /dev/null
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Movement.Systems;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target,
-/// adding one if not there and to change the movespeed
-/// </summary>
-public sealed partial class MovespeedModifier : EntityEffect
-{
- /// <summary>
- /// How much the entities' walk speed is multiplied by.
- /// </summary>
- [DataField]
- public float WalkSpeedModifier { get; set; } = 1;
-
- /// <summary>
- /// How much the entities' run speed is multiplied by.
- /// </summary>
- [DataField]
- public float SprintSpeedModifier { get; set; } = 1;
-
- /// <summary>
- /// How long the modifier applies (in seconds).
- /// Is scaled by reagent amount if used with an EntityEffectReagentArgs.
- /// </summary>
- [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));
- }
-
- /// <summary>
- /// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there.
- /// </summary>
- public override void Effect(EntityEffectBaseArgs args)
- {
- var status = args.EntityManager.EnsureComponent<MovespeedModifierMetabolismComponent>(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<MovementSpeedModifierSystem>().RefreshMovementSpeedModifiers(args.TargetEntity);
- }
- private void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time, IEntityManager entityManager, EntityUid uid)
- {
- var gameTiming = IoCManager.Resolve<IGameTiming>();
-
- var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds);
-
- status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time);
-
- entityManager.Dirty(uid, status);
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Oxygenate : EventEntityEffect<Oxygenate>
-{
- [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;
-}
+++ /dev/null
-using Content.Shared.Stunnable;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Paralyze : EntityEffect
-{
- [DataField] public double ParalyzeTime = 2;
-
- /// <remarks>
- /// true - refresh paralyze time, false - accumulate paralyze time
- /// </remarks>
- [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<SharedStunSystem>();
- _ = Refresh
- ? stunSystem.TryUpdateParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime))
- : stunSystem.TryAddParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime));
- }
-}
-
+++ /dev/null
-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<T> : EventEntityEffect<T> where T : PlantAdjustAttribute<T>
-{
- [DataField]
- public float Amount { get; protected set; } = 1;
-
- /// <summary>
- /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions.
- /// </summary>
- [DataField]
- public abstract string GuidebookAttributeName { get; set; }
-
- /// <summary>
- /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number.
- /// </summary>
- [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));
- }
-}
+++ /dev/null
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustHealth : PlantAdjustAttribute<PlantAdjustHealth>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
-}
-
+++ /dev/null
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute<PlantAdjustMutationLevel>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
-}
+++ /dev/null
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute<PlantAdjustMutationMod>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
-}
+++ /dev/null
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute<PlantAdjustNutrition>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
-}
+++ /dev/null
-// using Content.Server.Botany.Systems;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-/// <summary>
-/// Handles increase or decrease of plant potency.
-/// </summary>
-
-public sealed partial class PlantAdjustPotency : PlantAdjustAttribute<PlantAdjustPotency>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency";
-}
+++ /dev/null
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustWater : PlantAdjustAttribute<PlantAdjustWater>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-water";
-}
-
+++ /dev/null
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAffectGrowth : PlantAdjustAttribute<PlantAffectGrowth>
-{
- public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth";
-}
-
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantChangeStat : EventEntityEffect<PlantChangeStat>
-{
- [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();
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantCryoxadone : EventEntityEffect<PlantCryoxadone>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability));
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-/// <summary>
-/// Handles removal of seeds on a plant.
-/// </summary>
-
-public sealed partial class PlantDestroySeeds : EventEntityEffect<PlantDestroySeeds>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
- Loc.GetString("reagent-effect-guidebook-plant-seeds-remove", ("chance", Probability));
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantDiethylamine : EventEntityEffect<PlantDiethylamine>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability));
-}
-
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantPhalanximine : EventEntityEffect<PlantPhalanximine>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability));
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-/// <summary>
-/// Handles restoral of seeds on a plant.
-/// </summary>
-public sealed partial class PlantRestoreSeeds : EventEntityEffect<PlantRestoreSeeds>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
- Loc.GetString("reagent-effect-guidebook-plant-seeds-add", ("chance", Probability));
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class RobustHarvest : EventEntityEffect<RobustHarvest>
-{
- [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));
-}
+++ /dev/null
-using Content.Shared.Random;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// changes the chemicals available in a plant's produce
-/// </summary>
-public sealed partial class PlantMutateChemicals : EventEntityEffect<PlantMutateChemicals>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- return "TODO";
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using System.Linq;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// changes the gases that a plant or produce create.
-/// </summary>
-public sealed partial class PlantMutateExudeGasses : EventEntityEffect<PlantMutateExudeGasses>
-{
- [DataField]
- public float MinValue = 0.01f;
-
- [DataField]
- public float MaxValue = 0.5f;
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- return "TODO";
- }
-}
-
-/// <summary>
-/// changes the gases that a plant or produce consumes.
-/// </summary>
-public sealed partial class PlantMutateConsumeGasses : EventEntityEffect<PlantMutateConsumeGasses>
-{
- [DataField]
- public float MinValue = 0.01f;
-
- [DataField]
- public float MaxValue = 0.5f;
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- return "TODO";
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Upgrades a plant's harvest type.
-/// </summary>
-public sealed partial class PlantMutateHarvest : EventEntityEffect<PlantMutateHarvest>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- return "TODO";
- }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Changes a plant into one of the species its able to mutate into.
-/// </summary>
-public sealed partial class PlantSpeciesChange : EventEntityEffect<PlantSpeciesChange>
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- return "TODO";
- }
-}
+++ /dev/null
-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<Polymorph>
-{
- /// <summary>
- /// What polymorph prototype is used on effect
- /// </summary>
- [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<PolymorphPrototype>))]
- 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<EntityPrototype>(prototype.Index<PolymorphPrototype>(PolymorphPrototype).Configuration.Entity).Name));
-}
--- /dev/null
+using Content.Shared.Polymorph;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Polymorph : EntityEffectBase<Polymorph>
+{
+ /// <summary>
+ /// What polymorph prototype is used on effect
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<PolymorphPrototype> Prototype;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-make-polymorph",
+ ("chance", Probability),
+ ("entityname", prototype.Index<EntityPrototype>(prototype.Index(Prototype).Configuration.Entity).Name));
+}
+++ /dev/null
-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<SharedPopupSystem>();
- var random = IoCManager.Resolve<IRobustRandom>();
-
- 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
- }
-}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-using Content.Shared.Atmos.Rotting;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Reduces the rotting accumulator on the patient, making them revivable.
-/// </summary>
-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<SharedRottingSystem>();
-
- rottingSys.ReduceAccumulator(args.TargetEntity, TimeSpan.FromSeconds(RottingAmount));
- }
-}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Reset narcolepsy timer
-/// </summary>
-public sealed partial class ResetNarcolepsy : EventEntityEffect<ResetNarcolepsy>
-{
- /// <summary>
- /// The # of seconds the effect resets the narcolepsy timer to
- /// </summary>
- [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));
-}
--- /dev/null
+using Content.Shared.Traits.Assorted;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Resets the narcolepsy timer on a given entity.
+/// The new duration of the timer is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ResetNarcolepsyEntityEffectSystem : EntityEffectSystem<NarcolepsyComponent, ResetNarcolepsy>
+{
+ [Dependency] private readonly NarcolepsySystem _narcolepsy = default!;
+
+ protected override void Effect(Entity<NarcolepsyComponent> entity, ref EntityEffectEvent<ResetNarcolepsy> args)
+ {
+ var timer = args.Effect.TimerReset * args.Scale;
+
+ _narcolepsy.AdjustNarcolepsyTimer(entity.AsNullable(), timer);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ResetNarcolepsy : EntityEffectBase<ResetNarcolepsy>
+{
+ /// <summary>
+ /// The time we set our narcolepsy timer to.
+ /// </summary>
+ [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));
+}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Attempts to find a HungerComponent on the target,
-/// and to update it's hunger values.
-/// </summary>
-public sealed partial class SatiateHunger : EntityEffect
-{
- private const float DefaultNutritionFactor = 3.0f;
-
- /// <summary>
- /// How much hunger is satiated.
- /// Is multiplied by quantity if used with EntityEffectReagentArgs.
- /// </summary>
- [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<HungerSystem>().ModifyHunger(reagentArgs.TargetEntity, NutritionFactor * (float) reagentArgs.Quantity, hunger);
- }
- else
- {
- entman.System<HungerSystem>().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));
-}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
-/// and to update it's thirst values.
-/// </summary>
-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<ThirstSystem>().ModifyThirst(uid, thirst, HydrationFactor);
- }
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString("reagent-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative", HydrationFactor / DefaultHydrationFactor));
-}
+++ /dev/null
-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;
-
-/// <summary>
-/// Makes a mob slippery.
-/// </summary>
-public sealed partial class Slipify : EntityEffect
-{
- public override void Effect(EntityEffectBaseArgs args)
- {
- var fixtureSystem = args.EntityManager.System<FixtureSystem>();
- var colWakeSystem = args.EntityManager.System<CollisionWakeSystem>();
- var slippery = args.EntityManager.EnsureComponent<SlipperyComponent>(args.TargetEntity);
- args.EntityManager.Dirty(args.TargetEntity, slippery);
- args.EntityManager.EnsureComponent<StepTriggerComponent>(args.TargetEntity);
- // Need a fixture with a slip layer in order to actually do the slipping
- var fixtures = args.EntityManager.EnsureComponent<FixturesComponent>(args.TargetEntity);
- var body = args.EntityManager.EnsureComponent<PhysicsComponent>(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<CollisionWakeComponent>(args.TargetEntity);
- colWakeSystem.SetEnabled(args.TargetEntity, false, collisionWake);
- }
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- throw new NotImplementedException();
- }
-}
--- /dev/null
+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;
+
+/// <summary>
+/// This effect permanently creates a slippery fixture for this entity and then makes this entity slippery like soap.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SlipifyEntityEffectSystem : EntityEffectSystem<FixturesComponent, Slipify>
+{
+ [Dependency] private readonly CollisionWakeSystem _collisionWake = default!;
+ [Dependency] private readonly FixtureSystem _fixture = default!;
+
+ protected override void Effect(Entity<FixturesComponent> entity, ref EntityEffectEvent<Slipify> args)
+ {
+ EnsureComp<SlipperyComponent>(entity, out var slippery);
+ slippery.SlipData = args.Effect.Slippery;
+
+ Dirty(entity, slippery);
+
+ EnsureComp<StepTriggerComponent>(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<CollisionWakeComponent>(entity, out var collisionWake);
+ _collisionWake.SetEnabled(entity, false, collisionWake);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Slipify : EntityEffectBase<Slipify>
+{
+ [DataField]
+ public SlipperyEffectEntry Slippery = new();
+}
--- /dev/null
+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"
+/// <summary>
+/// Creates a reagent in a specified solution owned by this entity.
+/// Quantity is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem<SolutionContainerManagerComponent, AddReagentToSolution>
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+ protected override void Effect(Entity<SolutionContainerManagerComponent> entity, ref EntityEffectEvent<AddReagentToSolution> 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);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AddReagentToSolution : EntityEffectBase<AddReagentToSolution>
+{
+ /// <summary>
+ /// Prototype of the reagent we're adding.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<ReagentPrototype> Reagent;
+
+ ///<summary>
+ /// Solution we're looking for
+ /// </summary>
+ [DataField(required: true)]
+ public string? Solution = "reagents";
+
+ ///<summary>
+ /// A modifier for how much reagent we're creating.
+ /// </summary>
+ [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;
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// Adjust a reagent in this solution by an amount modified by scale.
+/// Quantity is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustReagentEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustReagent>
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+ protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustReagent> 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);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustReagent : EntityEffectBase<AdjustReagent>
+{
+ /// <summary>
+ /// The reagent ID to add or remove.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<ReagentPrototype> 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;
+ }
+}
--- /dev/null
+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;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustReagentsByGroupEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustReagentsByGroup>
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+ protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustReagentsByGroup> 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<ReagentPrototype>(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);
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustReagentsByGroup : EntityEffectBase<AdjustReagentsByGroup>
+{
+
+ /// <summary>
+ /// The metabolism group being adjusted. All reagents in an affected solution with this group will be adjusted.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<MetabolismGroupPrototype> Group;
+
+ [DataField(required: true)]
+ public FixedPoint2 Amount;
+}
--- /dev/null
+using Content.Shared.Database;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Solution;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AreaReactionEffect : EntityEffectBase<AreaReactionEffect>
+{
+ /// <summary>
+ /// How many seconds will the effect stay, counting after fully spreading.
+ /// </summary>
+ [DataField("duration")] public float Duration = 10;
+
+ /// <summary>
+ /// How big of a reaction scale we need for 1 smoke entity.
+ /// </summary>
+ [DataField] public float OverflowThreshold = 2.5f;
+
+ /// <summary>
+ /// The entity prototype that is being spread over an area.
+ /// </summary>
+ [DataField(required: true)]
+ public EntProtoId PrototypeId;
+
+ /// <summary>
+ /// Sound that will get played when this reaction effect occurs.
+ /// </summary>
+ [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;
+}
--- /dev/null
+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!
+/// <summary>
+/// Sets the temperature of this solution to a fixed value.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class SetSolutionTemperatureEntityEffectSystem : EntityEffectSystem<SolutionComponent, SetSolutionTemperature>
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+ protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<SetSolutionTemperature> args)
+ {
+ _solutionContainer.SetTemperature(entity, args.Effect.Temperature);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class SetSolutionTemperature : EntityEffectBase<SetSolutionTemperature>
+{
+ /// <summary>
+ /// The temperature to set the solution to.
+ /// </summary>
+ [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));
+}
+
+/// <summary>
+/// Adjusts the temperature of this solution by a given amount.
+/// The temperature adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class AdjustSolutionTemperatureEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustSolutionTemperature>
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+ protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustSolutionTemperature> 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);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustSolutionTemperature : EntityEffectBase<AdjustSolutionTemperature>
+{
+ /// <summary>
+ /// The change in temperature.
+ /// </summary>
+ [DataField(required: true)]
+ public float Delta;
+
+ /// <summary>
+ /// The minimum temperature this effect can reach.
+ /// </summary>
+ [DataField]
+ public float MinTemp;
+
+ /// <summary>
+ /// The maximum temperature this effect can reach.
+ /// </summary>
+ [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));
+}
+
+/// <summary>
+/// Adjusts the thermal energy of this solution by a given amount.
+/// The energy adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class AdjustSolutionThermalEnergyEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustSolutionThermalEnergy>
+{
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+ protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustSolutionThermalEnergy> 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);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustSolutionThermalEnergy : EntityEffectBase<AdjustSolutionThermalEnergy>
+{
+ /// <summary>
+ /// The change in thermal energy.
+ /// </summary>
+ [DataField(required: true)]
+ public float Delta;
+
+ /// <summary>
+ /// The minimum temperature this effect can reach.
+ /// </summary>
+ [DataField]
+ public float MinTemp;
+
+ /// <summary>
+ /// The maximum temperature this effect can reach.
+ /// </summary>
+ [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));
+}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Sets the temperature of the solution involved with the reaction to a new value.
-/// </summary>
-[DataDefinition]
-public sealed partial class SetSolutionTemperatureEffect : EntityEffect
-{
- /// <summary>
- /// The temperature to set the solution to.
- /// </summary>
- [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();
- }
-}
-
-/// <summary>
-/// Adjusts the temperature of the solution involved in the reaction.
-/// </summary>
-[DataDefinition]
-public sealed partial class AdjustSolutionTemperatureEffect : EntityEffect
-{
- /// <summary>
- /// The change in temperature.
- /// </summary>
- [DataField("delta", required: true)] private float _delta;
-
- /// <summary>
- /// The minimum temperature this effect can reach.
- /// </summary>
- [DataField("minTemp")] private float _minTemp = 0.0f;
-
- /// <summary>
- /// The maximum temperature this effect can reach.
- /// </summary>
- [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity;
-
- /// <summary>
- /// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount.
- /// </summary>
- [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();
- }
-}
-
-/// <summary>
-/// Adjusts the thermal energy of the solution involved in the reaction.
-/// </summary>
-public sealed partial class AdjustSolutionThermalEnergyEffect : EntityEffect
-{
- /// <summary>
- /// The change in energy.
- /// </summary>
- [DataField("delta", required: true)] private float _delta;
-
- /// <summary>
- /// The minimum temperature this effect can reach.
- /// </summary>
- [DataField("minTemp")] private float _minTemp = 0.0f;
-
- /// <summary>
- /// The maximum temperature this effect can reach.
- /// </summary>
- [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity;
-
- /// <summary>
- /// If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount.
- /// </summary>
- [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));
-}
--- /dev/null
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Entity effect that specifically deals with new status effects.
+/// </summary>
+/// <typeparam name="T">The entity effect type, typically for status effects which need systems to pass arguments</typeparam>
+public abstract partial class BaseStatusEntityEffect<T> : EntityEffectBase<T> where T : BaseStatusEntityEffect<T>
+{
+ /// <summary>
+ /// How long the modifier applies (in seconds).
+ /// Is scaled by reagent amount if used with an EntityEffectReagentArgs.
+ /// </summary>
+ [DataField]
+ public TimeSpan? Time = TimeSpan.FromSeconds(2);
+
+ /// <summary>
+ /// Should this effect add the status effect, remove time from it, or set its cooldown?
+ /// </summary>
+ [DataField]
+ public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Update;
+
+ /// <summary>
+ /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence.
+ /// </summary>
+ [DataField]
+ public TimeSpan Delay;
+}
+
+public enum StatusEffectMetabolismType
+{
+ Update,
+ Add,
+ Remove,
+ Set,
+}
--- /dev/null
+using Content.Shared.Drunk;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies the drunk status effect to this entity.
+/// The duration of the effect is equal to <see cref="Drunk.BoozePower"/> modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class DrunkEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Drunk>
+{
+ [Dependency] private readonly SharedDrunkSystem _drunk = default!;
+
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Drunk> args)
+ {
+ var boozePower = args.Effect.BoozePower * args.Scale;
+
+ _drunk.TryApplyDrunkenness(entity, boozePower);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Drunk : EntityEffectBase<Drunk>
+{
+ /// <summary>
+ /// BoozePower is how long each metabolism cycle will make the drunk effect last for.
+ /// </summary>
+ [DataField]
+ public TimeSpan BoozePower = TimeSpan.FromSeconds(3f);
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-drunk", ("chance", Probability));
+}
--- /dev/null
+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.
+/// <summary>
+/// Electrocutes this entity for a given amount of damage and time.
+/// The shock damage applied by this effect is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ElectrocuteEntityEffectSystem : EntityEffectSystem<StatusEffectsComponent, Electrocute>
+{
+ [Dependency] private readonly SharedElectrocutionSystem _electrocution = default!;
+
+ // TODO: When electrocution is new status, change this to new status
+ protected override void Effect(Entity<StatusEffectsComponent> entity, ref EntityEffectEvent<Electrocute> args)
+ {
+ var effect = args.Effect;
+
+ _electrocution.TryDoElectrocution(entity, null, (int)(args.Scale * effect.ShockDamage), effect.ElectrocuteTime, effect.Refresh, ignoreInsulation: effect.BypassInsulation);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Electrocute : EntityEffectBase<Electrocute>
+{
+ /// <summary>
+ /// Time we electrocute this entity
+ /// </summary>
+ [DataField] public TimeSpan ElectrocuteTime = TimeSpan.FromSeconds(2);
+
+ /// <summary>
+ /// Shock damage we apply to the entity.
+ /// </summary>
+ [DataField] public int ShockDamage = 5;
+
+ /// <summary>
+ /// Do we refresh the duration? Or add more duration if it already exists.
+ /// </summary>
+ [DataField] public bool Refresh = true;
+
+ /// <summary>
+ /// Should we by bypassing insulation?
+ /// </summary>
+ [DataField] public bool BypassInsulation = true;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime.TotalSeconds));
+}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.StatusEffect;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-/// 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.
-/// </summary>
-/// <remarks>
-/// Can be used for things like adding accents or something. I don't know. Go wild.
-/// </remarks>
-[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;
-
- /// <remarks>
- /// true - refresh status effect time, false - accumulate status effect time
- /// </remarks>
- [DataField]
- public bool Refresh = true;
-
- /// <summary>
- /// Should this effect add the status effect, remove time from it, or set its cooldown?
- /// </summary>
- [DataField]
- public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
-
- public override void Effect(EntityEffectBaseArgs args)
- {
- var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
-
- 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
-}
--- /dev/null
+using Content.Shared.StatusEffect;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+[Obsolete("Use ModifyStatusEffect instead")]
+public sealed partial class GenericStatusEffectEntityEffectSystem : EntityEffectSystem<MetaDataComponent, GenericStatusEffect>
+{
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<GenericStatusEffect> 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;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class GenericStatusEffect : EntityEffectBase<GenericStatusEffect>
+{
+ [DataField(required: true)]
+ public string Key = default!;
+
+ [DataField]
+ public string Component = String.Empty;
+
+ [DataField]
+ public TimeSpan Time = TimeSpan.FromSeconds(2f);
+
+ /// <summary>
+ /// Should this effect add the status effect, remove time from it, or set its cooldown?
+ /// </summary>
+ [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}"));
+}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Jittering;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-/// 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.
-/// </summary>
-public sealed partial class Jitter : EntityEffect
-{
- [DataField]
- public float Amplitude = 10.0f;
-
- [DataField]
- public float Frequency = 4.0f;
-
- [DataField]
- public float Time = 2.0f;
-
- /// <remarks>
- /// true - refresh jitter time, false - accumulate jitter time
- /// </remarks>
- [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<SharedJitteringSystem>()
- .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));
-}
--- /dev/null
+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.
+/// <summary>
+/// Applies the Jittering Status Effect to this entity.
+/// The amount of time the Jittering is applied is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class JitterEntityEffectSystem : EntityEffectSystem<StatusEffectsComponent, Jitter>
+{
+ [Dependency] private readonly SharedJitteringSystem _jittering = default!;
+
+ protected override void Effect(Entity<StatusEffectsComponent> entity, ref EntityEffectEvent<Jitter> args)
+ {
+ var time = args.Effect.Time * args.Scale;
+
+ _jittering.DoJitter(entity, TimeSpan.FromSeconds(time), args.Effect.Refresh, args.Effect.Amplitude, args.Effect.Frequency);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Jitter : EntityEffectBase<Jitter>
+{
+ [DataField]
+ public float Amplitude = 10.0f;
+
+ [DataField]
+ public float Frequency = 4.0f;
+
+ [DataField]
+ public float Time = 2.0f;
+
+ /// <remarks>
+ /// true - refresh jitter time, false - accumulate jitter time
+ /// </remarks>
+ [DataField]
+ public bool Refresh = true;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+ Loc.GetString("entity-effect-guidebook-jittering", ("chance", Probability));
+}
+++ /dev/null
-using Content.Shared.Stunnable;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-/// Changes the knockdown timer on an entity or causes knockdown.
-/// </summary>
-[UsedImplicitly]
-public sealed partial class ModifyKnockdown : EntityEffect
-{
- /// <summary>
- /// Should we only affect those with crawler component? Note if this is false, it will paralyze non-crawler's instead.
- /// </summary>
- [DataField]
- public bool Crawling;
-
- /// <summary>
- /// Should we drop items when we fall?
- /// </summary>
- [DataField]
- public bool Drop;
-
- /// <summary>
- /// Time for which knockdown should be applied. Behaviour changes according to <see cref="StatusEffectMetabolismType"/>.
- /// </summary>
- [DataField]
- public TimeSpan Time = TimeSpan.FromSeconds(0.5);
-
- /// <summary>
- /// Should this effect add the status effect, remove time from it, or set its cooldown?
- /// </summary>
- [DataField]
- public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
-
- /// <summary>
- /// Should this effect add knockdown?, remove time from it?, or set its cooldown?
- /// </summary>
- [DataField]
- public bool Refresh = true;
-
- /// <inheritdoc />
- public override void Effect(EntityEffectBaseArgs args)
- {
- var stunSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedStunSystem>();
-
- 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;
- }
- }
-
- /// <inheritdoc />
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString(
- "reagent-effect-guidebook-knockdown",
- ("chance", Probability),
- ("type", Type),
- ("time", Time.TotalSeconds)
- );
-}
--- /dev/null
+using Content.Shared.Standing;
+using Content.Shared.Stunnable;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies knockdown to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyKnockdownEntityEffectSystem : EntityEffectSystem<StandingStateComponent, ModifyKnockdown>
+{
+ [Dependency] private readonly SharedStunSystem _stun = default!;
+
+ protected override void Effect(Entity<StandingStateComponent> entity, ref EntityEffectEvent<ModifyKnockdown> 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;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyKnockdown : BaseStatusEntityEffect<ModifyKnockdown>
+{
+ /// <summary>
+ /// Should we only affect those with crawler component? Note if this is false, it will paralyze non-crawler's instead.
+ /// </summary>
+ [DataField]
+ public bool Crawling;
+
+ /// <summary>
+ /// Should we drop items when we fall?
+ /// </summary>
+ [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)
+ );
+}
--- /dev/null
+using Content.Shared.StatusEffectNew;
+using Content.Shared.StatusEffectNew.Components;
+using Content.Shared.Stunnable;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies the paralysis status effect to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyParalysisEntityEffectSystem : EntityEffectSystem<StatusEffectContainerComponent, ModifyParalysis>
+{
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+ [Dependency] private readonly SharedStunSystem _stun = default!;
+
+ protected override void Effect(Entity<StatusEffectContainerComponent> entity, ref EntityEffectEvent<ModifyParalysis> 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;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyParalysis : BaseStatusEntityEffect<ModifyParalysis>
+{
+ 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)
+ );
+}
+++ /dev/null
-using Content.Shared.StatusEffectNew;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-/// Changes status effects on entities: Adds, removes or sets time.
-/// </summary>
-[UsedImplicitly]
-public sealed partial class ModifyStatusEffect : EntityEffect
-{
- [DataField(required: true)]
- public EntProtoId EffectProto;
-
- /// <summary>
- /// Time for which status effect should be applied. Behaviour changes according to <see cref="Refresh" />.
- /// </summary>
- [DataField]
- public float Time = 2.0f;
-
- /// <summary>
- /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence.
- /// </summary>
- [DataField]
- public float Delay = 0f;
-
- /// <summary>
- /// Should this effect add the status effect, remove time from it, or set its cooldown?
- /// </summary>
- [DataField]
- public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
-
- /// <inheritdoc />
- public override void Effect(EntityEffectBaseArgs args)
- {
- var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
-
- 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;
- }
- }
-
- /// <inheritdoc />
- 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)
- );
-}
--- /dev/null
+using Content.Shared.StatusEffectNew;
+using Content.Shared.StatusEffectNew.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies a given status effect to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyStatusEffectEntityEffectSystem : EntityEffectSystem<StatusEffectContainerComponent, ModifyStatusEffect>
+{
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+
+ protected override void Effect(Entity<StatusEffectContainerComponent> entity, ref EntityEffectEvent<ModifyStatusEffect> 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;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyStatusEffect : BaseStatusEntityEffect<ModifyStatusEffect>
+{
+ /// <summary>
+ /// Prototype of the status effect we're modifying.
+ /// </summary>
+ [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));
+}
--- /dev/null
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Content.Shared.StatusEffectNew;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies a given movement speed modifier status effect to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class MovementSpeedModifierEntityEffectSystem : EntityEffectSystem<MovementSpeedModifierComponent, MovementSpeedModifier>
+{
+ [Dependency] private readonly StatusEffectsSystem _status = default!;
+ [Dependency] private readonly MovementModStatusSystem _movementModStatus = default!;
+
+ protected override void Effect(Entity<MovementSpeedModifierComponent> entity, ref EntityEffectEvent<MovementSpeedModifier> 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;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class MovementSpeedModifier : BaseStatusEntityEffect<MovementSpeedModifier>
+{
+ /// <summary>
+ /// How much the entities' walk speed is multiplied by.
+ /// </summary>
+ [DataField]
+ public float WalkSpeedModifier = 1f;
+
+ /// <summary>
+ /// How much the entities' run speed is multiplied by.
+ /// </summary>
+ [DataField]
+ public float SprintSpeedModifier = 1f;
+
+ /// <summary>
+ /// Movement speed modifier prototype we're adding. Adding in case we ever want more than one prototype that boosts speed.
+ /// </summary>
+ [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));
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// A brief summary of the effect.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+public sealed partial class TemplateEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Template>
+{
+ protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Template> args)
+ {
+ // Effect goes here.
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Template : EntityEffectBase<Template>
+{
+ public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+}
--- /dev/null
+using Content.Shared.Database;
+using Content.Shared.Emp;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates an EMP at this entity's coordinates.
+/// Range is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EmpEntityEffectSystem : EntityEffectSystem<TransformComponent, Emp>
+{
+ [Dependency] private readonly SharedEmpSystem _emp = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+
+ protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<Emp> args)
+ {
+ var range = MathF.Min(args.Effect.RangeModifier * args.Scale, args.Effect.MaxRange);
+
+ _emp.EmpPulse(_xform.GetMapCoordinates(entity, xform: entity.Comp), range, args.Effect.EnergyConsumption, args.Effect.Duration);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Emp : EntityEffectBase<Emp>
+{
+ /// <summary>
+ /// Impulse range per unit of quantity
+ /// </summary>
+ [DataField]
+ public float RangeModifier = 0.5f;
+
+ /// <summary>
+ /// Maximum impulse range
+ /// </summary>
+ [DataField]
+ public float MaxRange = 10;
+
+ /// <summary>
+ /// How much energy will be drain from sources
+ /// </summary>
+ [DataField]
+ public float EnergyConsumption = 12500;
+
+ /// <summary>
+ /// Amount of time entities will be disabled
+ /// </summary>
+ [DataField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(15);
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-emp-reaction-effect", ("chance", Probability));
+
+ public override bool ShouldLog => true;
+
+ public override LogImpact LogImpact => LogImpact.Medium;
+}
-using Content.Shared.Database;
+using Content.Shared.Database;
using Content.Shared.Explosion;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using System.Text.Json.Serialization;
-namespace Content.Shared.EntityEffects.Effects;
+namespace Content.Shared.EntityEffects.Effects.Transform;
-[DataDefinition]
-public sealed partial class ExplosionReactionEffect : EventEntityEffect<ExplosionReactionEffect>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ExplosionEffect : EntityEffectBase<ExplosionEffect>
{
/// <summary>
/// The type of explosion. Determines damage types and tile break chance scaling.
/// </summary>
- [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
- public string ExplosionType = default!;
+ [DataField(required: true)]
+ public ProtoId<ExplosionPrototype> ExplosionType;
/// <summary>
/// The max intensity the explosion can have at a given tile. Places an upper limit of damage and tile break
[DataField]
public float TileBreakScale = 1f;
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
+
public override bool ShouldLog => true;
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString("reagent-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.High;
}
--- /dev/null
+using Content.Shared.Flash;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates a Flash at this entity's coordinates.
+/// Range is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class FlashEntityEffectSystem : EntityEffectSystem<TransformComponent, Flash>
+{
+ [Dependency] private readonly SharedFlashSystem _flash = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+ [Dependency] private readonly SharedPointLightSystem _pointLight = default!;
+
+ protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<Flash> args)
+ {
+ var range = MathF.Min(args.Scale * args.Effect.RangePerUnit, args.Effect.MaxRange);
+
+ _flash.FlashArea(
+ entity,
+ null,
+ range,
+ args.Effect.Duration,
+ slowTo: args.Effect.SlowTo,
+ sound: args.Effect.Sound);
+
+ if (args.Effect.FlashEffectPrototype == null)
+ return;
+
+ var uid = PredictedSpawnAtPosition(args.Effect.FlashEffectPrototype, _xform.GetMoverCoordinates(entity));
+
+ _pointLight.SetRadius(uid, MathF.Max(1.1f, range));
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Flash : EntityEffectBase<Flash>
+{
+ /// <summary>
+ /// Flash range per unit of reagent.
+ /// </summary>
+ [DataField]
+ public float RangePerUnit = 0.2f;
+
+ /// <summary>
+ /// Maximum flash range.
+ /// </summary>
+ [DataField]
+ public float MaxRange = 10f;
+
+ /// <summary>
+ /// How much to entities are slowed down.
+ /// </summary>
+ [DataField]
+ public float SlowTo = 0.5f;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [DataField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(4);
+
+ /// <summary>
+ /// The prototype ID used for the visual effect.
+ /// </summary>
+ [DataField]
+ public EntProtoId? FlashEffectPrototype = "ReactionFlash";
+
+ /// <summary>
+ /// The sound the flash creates.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg");
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-flash-reaction-effect", ("chance", Probability));
+}
--- /dev/null
+using Content.Shared.Popups;
+using Robust.Shared.Network;
+using Robust.Shared.Random;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates a text popup to appear at this entity's coordinates.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class PopupMessageEntityEffectSystem : EntityEffectSystem<TransformComponent, PopupMessage>
+{
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<PopupMessage> args)
+ {
+ // TODO: When we get proper random prediction remove this check.
+ if (_net.IsClient)
+ return;
+
+ var msg = _random.Pick(args.Effect.Messages);
+
+ switch (args.Effect.Type)
+ {
+ case PopupRecipients.Local:
+ _popup.PopupEntity(Loc.GetString(msg, ("entity", entity)), entity, entity, args.Effect.VisualType);
+ break;
+ case PopupRecipients.Pvs:
+ _popup.PopupEntity(Loc.GetString(msg, ("entity", entity)), entity, args.Effect.VisualType);
+ break;
+ }
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class PopupMessage : EntityEffectBase<PopupMessage>
+{
+ /// <summary>
+ /// Array of messages that can popup.
+ /// Only one is chosen when the effect is applied.
+ /// </summary>
+ [DataField(required: true)]
+ public string[] Messages = default!;
+
+ /// <summary>
+ /// Whether to just the entity we're affecting, or everyone around them.
+ /// </summary>
+ [DataField]
+ public PopupRecipients Type = PopupRecipients.Local;
+
+ /// <summary>
+ /// Size of the popup.
+ /// </summary>
+ [DataField]
+ public PopupType VisualType = PopupType.Small;
+}
+
+public enum PopupRecipients
+{
+ Pvs,
+ Local
+}
--- /dev/null
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Washes the cream pie off of this entity face.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+/// TODO: This can probably be made into a generic "CleanEntityEffect" which multiple components listen to...
+public sealed partial class WashCreamPieEntityEffectSystem : EntityEffectSystem<CreamPiedComponent, WashCreamPie>
+{
+ [Dependency] private readonly SharedCreamPieSystem _creamPie = default!;
+
+ protected override void Effect(Entity<CreamPiedComponent> entity, ref EntityEffectEvent<WashCreamPie> args)
+ {
+ _creamPie.SetCreamPied(entity, entity.Comp, false);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class WashCreamPie : EntityEffectBase<WashCreamPie>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
+}
+++ /dev/null
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class WashCreamPieReaction : EntityEffect
-{
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
- => Loc.GetString("reagent-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
-
- public override void Effect(EntityEffectBaseArgs args)
- {
- if (!args.EntityManager.TryGetComponent(args.TargetEntity, out CreamPiedComponent? creamPied)) return;
-
- args.EntityManager.System<SharedCreamPieSystem>().SetCreamPied(args.TargetEntity, creamPied, false);
- }
-}
+++ /dev/null
-using Content.Shared.Inventory;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// A reaction effect that spawns a PrototypeID in the entity's Slot, and attempts to consume the reagent if EntityEffectReagentArgs.
-/// Used to implement the water droplet effect for arachnids.
-/// </summary>
-public sealed partial class WearableReaction : EntityEffect
-{
- /// <summary>
- /// Minimum quantity of reagent required to trigger this effect.
- /// Only used with EntityEffectReagentArgs.
- /// </summary>
- [DataField]
- public float AmountThreshold = 1f;
-
- /// <summary>
- /// Slot to spawn the item into.
- /// </summary>
- [DataField(required: true)]
- public string Slot;
-
- /// <summary>
- /// Prototype ID of item to spawn.
- /// </summary>
- [DataField(required: true)]
- public string PrototypeID;
-
- protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
-
- public override void Effect(EntityEffectBaseArgs args)
- {
- // SpawnItemInSlot returns false if slot is already occupied
- if (args.EntityManager.System<InventorySystem>().SpawnItemInSlot(args.TargetEntity, Slot, PrototypeID))
- {
- if (args is EntityEffectReagentArgs reagentArgs)
- {
- if (reagentArgs.Reagent == null || reagentArgs.Quantity < AmountThreshold)
- return;
- reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, AmountThreshold);
- }
- }
- }
-}
--- /dev/null
+using Content.Shared.Mobs.Components;
+using Content.Shared.Zombies;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Causes the zombie infection on this entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+public sealed partial class CauseZombieInfectionEntityEffectsSystem : EntityEffectSystem<MobStateComponent, CauseZombieInfection>
+{
+ // MobState because you have to die to become a zombie...
+ protected override void Effect(Entity<MobStateComponent> entity, ref EntityEffectEvent<CauseZombieInfection> args)
+ {
+ if (HasComp<ZombieImmuneComponent>(entity) || HasComp<IncurableZombieComponent>(entity))
+ return;
+
+ EnsureComp<ZombifyOnDeathComponent>(entity);
+ EnsureComp<PendingZombieComponent>(entity);
+ }
+}
+
+/// <summary>
+/// Cures the Zombie infection on this entity and optionally inoculates them against future infection.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+public sealed partial class CureZombieInfectionEntityEffectsSystem : EntityEffectSystem<MobStateComponent, CureZombieInfection>
+{
+ // MobState because you have to die to become a zombie...
+ protected override void Effect(Entity<MobStateComponent> entity, ref EntityEffectEvent<CureZombieInfection> args)
+ {
+ if (HasComp<IncurableZombieComponent>(entity))
+ return;
+
+ RemComp<ZombifyOnDeathComponent>(entity);
+ RemComp<PendingZombieComponent>(entity);
+
+ if (args.Effect.Innoculate)
+ EnsureComp<ZombieImmuneComponent>(entity);
+ }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CauseZombieInfection : EntityEffectBase<CauseZombieInfection>
+{
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ => Loc.GetString("entity-effect-guidebook-cause-zombie-infection", ("chance", Probability));
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CureZombieInfection : EntityEffectBase<CureZombieInfection>
+{
+ /// <summary>
+ /// Do we also protect against future infections?
+ /// </summary>
+ [DataField]
+ public bool Innoculate;
+
+ public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ if (Innoculate)
+ return Loc.GetString("entity-effect-guidebook-innoculate-zombie-infection", ("chance", Probability));
+
+ return Loc.GetString("entity-effect-guidebook-cure-zombie-infection", ("chance", Probability));
+ }
+}
+++ /dev/null
-using System.Linq;
-using System.Text.Json.Serialization;
-using Content.Shared.Chemistry;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Database;
-using Content.Shared.FixedPoint;
-using Content.Shared.Localizations;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Toolshed.TypeParsers;
-
-namespace Content.Shared.EntityEffects;
-
-/// <summary>
-/// Entity effects describe behavior that occurs on different kinds of triggers, e.g. when a reagent is ingested and metabolized by some
-/// organ. They only trigger when all of <see cref="Conditions"/> are satisfied.
-/// </summary>
-[ImplicitDataDefinitionForInheritors]
-[MeansImplicitUse]
-public abstract partial class EntityEffect
-{
- private protected string _id => this.GetType().Name;
- /// <summary>
- /// The list of conditions required for the effect to activate. Not required.
- /// </summary>
- [DataField("conditions")]
- public EntityEffectCondition[]? Conditions;
-
- public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
-
- protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys);
-
- /// <summary>
- /// What's the chance, from 0 to 1, that this effect will occur?
- /// </summary>
- [DataField("probability")]
- public float Probability = 1.0f;
-
- public virtual LogImpact LogImpact { get; private set; } = LogImpact.Low;
-
- /// <summary>
- /// Should this entity effect log at all?
- /// </summary>
- public virtual bool ShouldLog { get; private set; } = false;
-
- public abstract void Effect(EntityEffectBaseArgs args);
-
- /// <summary>
- /// Produces a localized, bbcode'd guidebook description for this effect.
- /// </summary>
- /// <returns></returns>
- public string? GuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
- {
- var effect = ReagentEffectGuidebookText(prototype, entSys);
- if (effect is null)
- return null;
-
- return Loc.GetString(ReagentEffectFormat, ("effect", effect), ("chance", Probability),
- ("conditionCount", Conditions?.Length ?? 0),
- ("conditions",
- ContentLocalizationManager.FormatList(Conditions?.Select(x => x.GuidebookExplanation(prototype)).ToList() ??
- new List<string>())));
- }
-}
-
-public static class EntityEffectExt
-{
- public static bool ShouldApply(this EntityEffect effect, EntityEffectBaseArgs args,
- IRobustRandom? random = null)
- {
- if (random == null)
- random = IoCManager.Resolve<IRobustRandom>();
-
- if (effect.Probability < 1.0f && !random.Prob(effect.Probability))
- return false;
-
- if (effect.Conditions != null)
- {
- foreach (var cond in effect.Conditions)
- {
- if (!cond.Condition(args))
- return false;
- }
- }
-
- return true;
- }
-}
-
-[ByRefEvent]
-public struct ExecuteEntityEffectEvent<T> where T : EntityEffect
-{
- public T Effect;
- public EntityEffectBaseArgs Args;
-
- public ExecuteEntityEffectEvent(T effect, EntityEffectBaseArgs args)
- {
- Effect = effect;
- Args = args;
- }
-}
-
-/// <summary>
-/// EntityEffectBaseArgs only contains the target of an effect.
-/// If a trigger wants to include more info (e.g. the quantity of the chemical triggering the effect), it can be extended (see EntityEffectReagentArgs).
-/// </summary>
-public record class EntityEffectBaseArgs
-{
- public EntityUid TargetEntity;
-
- public IEntityManager EntityManager = default!;
-
- public EntityEffectBaseArgs(EntityUid targetEntity, IEntityManager entityManager)
- {
- TargetEntity = targetEntity;
- EntityManager = entityManager;
- }
-}
-
-public record class EntityEffectReagentArgs : EntityEffectBaseArgs
-{
- public EntityUid? OrganEntity;
-
- public Solution? Source;
-
- public FixedPoint2 Quantity;
-
- public ReagentPrototype? Reagent;
-
- public ReactionMethod? Method;
-
- public FixedPoint2 Scale;
-
- public EntityEffectReagentArgs(EntityUid targetEntity, IEntityManager entityManager, EntityUid? organEntity, Solution? source, FixedPoint2 quantity, ReagentPrototype? reagent, ReactionMethod? method, FixedPoint2 scale) : base(targetEntity, entityManager)
- {
- OrganEntity = organEntity;
- Source = source;
- Quantity = quantity;
- Reagent = reagent;
- Method = method;
- Scale = scale;
- }
-}
+++ /dev/null
-using System.Text.Json.Serialization;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects;
-
-[ImplicitDataDefinitionForInheritors]
-[MeansImplicitUse]
-public abstract partial class EntityEffectCondition
-{
- [JsonPropertyName("id")] private protected string _id => this.GetType().Name;
-
- public abstract bool Condition(EntityEffectBaseArgs args);
-
- /// <summary>
- /// Effect explanations are of the form "[chance to] [action] when [condition] and [condition]"
- /// </summary>
- /// <param name="prototype"></param>
- /// <returns></returns>
- public abstract string GuidebookExplanation(IPrototypeManager prototype);
-}
-
-[ByRefEvent]
-public struct CheckEntityEffectConditionEvent<T> where T : EntityEffectCondition
-{
- public T Condition;
- public EntityEffectBaseArgs Args;
- public bool Result;
-}
+++ /dev/null
-namespace Content.Shared.EntityEffects;
-
-public abstract partial class EventEntityEffect<T> : EntityEffect where T : EntityEffect
-{
- public override void Effect(EntityEffectBaseArgs args)
- {
- if (this is not T type)
- return;
- var ev = new ExecuteEntityEffectEvent<T>(type, args);
- args.EntityManager.EventBus.RaiseEvent(EventSource.Local, ref ev);
- }
-}
+++ /dev/null
-namespace Content.Shared.EntityEffects;
-
-public abstract partial class EventEntityEffectCondition<T> : EntityEffectCondition where T : EventEntityEffectCondition<T>
-{
- public override bool Condition(EntityEffectBaseArgs args)
- {
- if (this is not T type)
- return false;
-
- var evt = new CheckEntityEffectConditionEvent<T> { Condition = type, Args = args };
- args.EntityManager.EventBus.RaiseEvent(EventSource.Local, ref evt);
- return evt.Result;
- }
-}
--- /dev/null
+using System.Linq;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Database;
+using Content.Shared.EntityConditions;
+using Content.Shared.Localizations;
+using Content.Shared.Random.Helpers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.EntityEffects;
+
+/// <summary>
+/// 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.
+/// </summary>
+public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEffectRaiser
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
+ [Dependency] private readonly SharedEntityConditionsSystem _condition = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<ReactiveComponent, ReactionEntityEvent>(OnReactive);
+ }
+
+ private void OnReactive(Entity<ReactiveComponent> entity, ref ReactionEntityEvent args)
+ {
+ if (args.Reagent.ReactiveEffects == null || entity.Comp.ReactiveGroups == null)
+ return;
+
+ var scale = args.ReagentQuantity.Quantity.Float();
+
+ foreach (var (key, val) in args.Reagent.ReactiveEffects)
+ {
+ if (!val.Methods.Contains(args.Method))
+ continue;
+
+ if (!entity.Comp.ReactiveGroups.TryGetValue(key, out var group))
+ continue;
+
+ if (!group.Contains(args.Method))
+ continue;
+
+ ApplyEffects(entity, val.Effects, scale);
+ }
+
+ if (entity.Comp.Reactions == null)
+ return;
+
+ foreach (var entry in entity.Comp.Reactions)
+ {
+ if (!entry.Methods.Contains(args.Method))
+ continue;
+
+ if (entry.Reagents == null || entry.Reagents.Contains(args.Reagent.ID))
+ ApplyEffects(entity, entry.Effects, scale);
+ }
+ }
+
+ /// <summary>
+ /// Applies a list of entity effects to a target entity.
+ /// </summary>
+ /// <param name="target">Entity being targeted by the effects</param>
+ /// <param name="effects">Effects we're applying to the entity</param>
+ /// <param name="scale">Optional scale multiplier for the effects</param>
+ public void ApplyEffects(EntityUid target, EntityEffect[] effects, float scale = 1f)
+ {
+ // do all effects, if conditions apply
+ foreach (var effect in effects)
+ {
+ TryApplyEffect(target, effect, scale);
+ }
+ }
+
+ /// <summary>
+ /// Applies an entity effect to a target if all conditions pass.
+ /// </summary>
+ /// <param name="target">Target we're applying an effect to</param>
+ /// <param name="effect">Effect we're applying</param>
+ /// <param name="scale">Optional scale multiplier for the effect. Not all </param>
+ /// <returns>True if all conditions pass!</returns>
+ public bool TryApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+ {
+ if (scale < effect.MinScale)
+ return false;
+
+ // TODO: Replace with proper random prediciton when it exists.
+ if (effect.Probability <= 1f)
+ {
+ var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(target).Id, 0 });
+ var rand = new System.Random(seed);
+ if (!rand.Prob(effect.Probability))
+ return false;
+ }
+
+ // See if conditions apply
+ if (!_condition.TryConditions(target, effect.Conditions))
+ return false;
+
+ ApplyEffect(target, effect, scale);
+ return true;
+ }
+
+ /// <summary>
+ /// Applies an <see cref="EntityEffect"/> to a given target.
+ /// This doesn't check conditions so you should only call this if you know what you're doing!
+ /// </summary>
+ /// <param name="target">Target we're applying an effect to</param>
+ /// <param name="effect">Effect we're applying</param>
+ /// <param name="scale">Optional scale multiplier for the effect. Not all </param>
+ public void ApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+ {
+ // Clamp the scale if the effect doesn't allow scaling.
+ if (!effect.Scaling)
+ scale = Math.Min(scale, 1f);
+
+ if (effect.ShouldLog)
+ {
+ _adminLog.Add(
+ LogType.EntityEffect,
+ effect.LogImpact,
+ $"Entity effect {effect.GetType().Name:effect}"
+ + $" applied on entity {target:entity}"
+ + $" at {Transform(target).Coordinates:coordinates}"
+ + $" with a scale multiplier of {scale}"
+ );
+ }
+
+ effect.RaiseEvent(target, this, scale);
+ }
+
+ /// <summary>
+ /// Raises an effect to an entity. You should not be calling this unless you know what you're doing.
+ /// </summary>
+ public void RaiseEffectEvent<T>(EntityUid target, T effect, float scale) where T : EntityEffectBase<T>
+ {
+ var effectEv = new EntityEffectEvent<T>(effect, scale);
+ RaiseLocalEvent(target, ref effectEv);
+ }
+}
+
+/// <summary>
+/// This is a basic abstract entity effect containing all the data an entity effect needs to affect entities with effects...
+/// </summary>
+/// <typeparam name="T">The Component that is required for the effect</typeparam>
+/// <typeparam name="TEffect">The Entity Effect itself</typeparam>
+public abstract partial class EntityEffectSystem<T, TEffect> : EntitySystem where T : Component where TEffect : EntityEffectBase<TEffect>
+{
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<T, EntityEffectEvent<TEffect>>(Effect);
+ }
+
+ protected abstract void Effect(Entity<T> entity, ref EntityEffectEvent<TEffect> args);
+}
+
+/// <summary>
+/// Used to raise an EntityEffect without losing the type of effect.
+/// </summary>
+public interface IEntityEffectRaiser
+{
+ void RaiseEffectEvent<T>(EntityUid target, T effect, float scale) where T : EntityEffectBase<T>;
+}
+
+/// <summary>
+/// Used to store an <see cref="EntityEffect"/> so it can be raised without losing the type of the condition.
+/// </summary>
+/// <typeparam name="T">The Condition wer are raising.</typeparam>
+public abstract partial class EntityEffectBase<T> : EntityEffect where T : EntityEffectBase<T>
+{
+ public override void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale)
+ {
+ if (this is not T type)
+ return;
+
+ raiser.RaiseEffectEvent(target, type, scale);
+ }
+}
+
+/// <summary>
+/// A basic instantaneous effect which can be applied to an entity via events.
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class EntityEffect
+{
+ public abstract void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale);
+
+ [DataField]
+ public EntityCondition[]? Conditions;
+
+ /// <summary>
+ /// If our scale is less than this value, the effect fails.
+ /// </summary>
+ [DataField]
+ public virtual float MinScale { get; private set; }
+
+ /// <summary>
+ /// If true, then it allows the scale multiplier to go above 1.
+ /// </summary>
+ [DataField]
+ public virtual bool Scaling { get; private set; }
+
+ // TODO: This should be an entity condition but guidebook relies on it heavily for formatting...
+ /// <summary>
+ /// Probability of the effect occuring.
+ /// </summary>
+ [DataField]
+ public float Probability = 1.0f;
+
+ /// <summary>
+ /// The description of this entity effect that shows in guidebooks.
+ /// </summary>
+ public virtual string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+
+ /// <summary>
+ /// Whether this effect should be logged in admin logs.
+ /// </summary>
+ [ViewVariables]
+ public virtual bool ShouldLog => true;
+
+ /// <summary>
+ /// If this effect is logged, how important is the log?
+ /// </summary>
+ [ViewVariables]
+ public virtual LogImpact LogImpact => LogImpact.Low;
+}
+
+/// <summary>
+/// An Event carrying an entity effect.
+/// </summary>
+/// <param name="Effect">The Effect</param>
+/// <param name="Scale">A strength scalar for the effect, defaults to 1 and typically only goes under for incomplete reactions.</param>
+[ByRefEvent, Access(typeof(SharedEntityEffectsSystem))]
+public readonly record struct EntityEffectEvent<T>(T Effect, float Scale) where T : EntityEffectBase<T>
+{
+ /// <summary>
+ /// The Condition being raised in this event
+ /// </summary>
+ public readonly T Effect = Effect;
+
+ /// <summary>
+ /// The Scale modifier of this Effect.
+ /// </summary>
+ public readonly float Scale = Scale;
+}
/// </remarks>
public sealed class MovementModStatusSystem : EntitySystem
{
+ public static readonly EntProtoId ReagentSpeed = "ReagentSpeedStatusEffect";
public static readonly EntProtoId VomitingSlowdown = "VomitingSlowdownStatusEffect";
public static readonly EntProtoId TaserSlowdown = "TaserSlowdownStatusEffect";
public static readonly EntProtoId FlashSlowdown = "FlashSlowdownStatusEffect";
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
-using Content.Shared.EntityEffects.Effects;
+using Content.Shared.EntityEffects.Effects.Body;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
using Content.Shared.Nutrition.Components;
// ignores any effect conditions, just cares about how much it can hydrate
if (effect is SatiateHunger hunger)
{
- total += hunger.NutritionFactor * quantity.Quantity.Float();
+ total += hunger.Factor * quantity.Quantity.Float();
}
}
}
// ignores any effect conditions, just cares about how much it can hydrate
if (effect is SatiateThirst thirst)
{
- total += thirst.HydrationFactor * quantity.Quantity.Float();
+ total += thirst.Factor * quantity.Quantity.Float();
}
}
}
+using System.ComponentModel.Design;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.StatusEffectNew.Components;
using Robust.Shared.Prototypes;
+using YamlDotNet.Core.Tokens;
namespace Content.Shared.StatusEffectNew;
return false;
}
+ /// <summary>
+ /// A method which specifically removes time from a status effect, or removes the status effect if time is null.
+ /// </summary>
+ /// <param name="uid">The target entity on which the effect is applied.</param>
+ /// <param name="effectProto">The prototype ID of the status effect to modify.</param>
+ /// <param name="time">
+ /// The time adjustment to apply to the status effect. Positive values extend the duration,
+ /// while negative values reduce it.
+ /// </param>
+ /// <returns> True if duration was edited successfully, false otherwise.</returns>
+ public bool TryRemoveTime(EntityUid uid, EntProtoId effectProto, TimeSpan? time)
+ {
+ return time == null ? TryRemoveStatusEffect(uid, effectProto) : TryAddTime(uid, effectProto, time.Value);
+ }
+
/// <summary>
/// Attempts to set the remaining time for a status effect on an entity.
/// </summary>
private void OnRejuvenate(Entity<KnockedDownComponent> entity, ref RejuvenateEvent args)
{
- SetKnockdownNextUpdate((entity, entity), GameTiming.CurTime);
+ SetKnockdownNextUpdate(entity, GameTiming.CurTime);
if (entity.Comp.AutoStand)
RemComp<KnockedDownComponent>(entity);
if (!Resolve(entity, ref entity.Comp, false))
return;
- SetKnockdownNextUpdate(entity, GameTiming.CurTime + time);
+ SetKnockdownNextUpdate((entity, entity.Comp), GameTiming.CurTime + time);
}
/// <summary>
/// </summary>
/// <param name="entity">Entity whose timer we're updating</param>
/// <param name="time">The exact time we're setting the next update to.</param>
- private void SetKnockdownNextUpdate(Entity<KnockedDownComponent?> entity, TimeSpan time)
+ private void SetKnockdownNextUpdate(Entity<KnockedDownComponent> entity, TimeSpan time)
{
- if (!Resolve(entity, ref entity.Comp, false))
- return;
-
if (GameTiming.CurTime > time)
time = GameTiming.CurTime;
}
}
- public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration)
+ public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan? duration)
{
- if (!_status.TryAddStatusEffectDuration(uid, StunId, duration))
+ if (duration == null)
+ return TryUpdateParalyzeDuration(uid, duration);
+
+ if (!_status.TryAddStatusEffectDuration(uid, StunId, duration.Value))
return false;
// We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun.
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
-namespace Content.Server.Temperature.Components;
+namespace Content.Shared.Temperature.Components;
/// <summary>
/// Handles changing temperature,
using System.Linq;
+using Content.Shared.Atmos;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Temperature.Components;
+using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
namespace Content.Shared.Temperature.Systems;
/// <summary>
/// This handles predicting temperature based speedup.
/// </summary>
-public sealed class SharedTemperatureSystem : EntitySystem
+public abstract class SharedTemperatureSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
Dirty(uid, temp);
}
}
+
+ public virtual void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null)
+ {
+
+ }
+
+ 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;
+ }
}
using Robust.Shared.Prototypes;
-namespace Content.Server.Zombies;
+namespace Content.Shared.Zombies;
/// <summary>
/// This is used for a zombie that cannot be cured by any methods. Gives a succumb to zombie infection action.
/// </summary>
+/// <remarks> We don't network this component for anti-cheat purposes.</remarks>
[RegisterComponent]
public sealed partial class IncurableZombieComponent : Component
{
guidebook-reagent-effect-description =
- {$chance ->
+ {$quantity ->
+ [0] {""}
+ *[other] If there is at least {$quantity}u {$reagent},{" "}
+ }{$chance ->
[1] { $effect }
*[other] Has a { NATURALPERCENT($chance, 2) } chance to { $effect }
}{ $conditionCount ->
+++ /dev/null
-reagent-effect-status-effect-Stun = stunning
-reagent-effect-status-effect-KnockedDown = knockdown
-reagent-effect-status-effect-Jitter = jittering
-reagent-effect-status-effect-TemporaryBlindness = blindness
-reagent-effect-status-effect-SeeingRainbows = hallucinations
-reagent-effect-status-effect-Muted = inability to speak
-reagent-effect-status-effect-Stutter = stuttering
-reagent-effect-status-effect-ForcedSleep = unconsciousness
-reagent-effect-status-effect-Drunk = drunkenness
-reagent-effect-status-effect-PressureImmunity = pressure immunity
-reagent-effect-status-effect-Pacified = combat pacification
-reagent-effect-status-effect-RatvarianLanguage = ratvarian language patterns
-reagent-effect-status-effect-StaminaModifier = modified stamina
-reagent-effect-status-effect-RadiationProtection = radiation protection
-reagent-effect-status-effect-Drowsiness = drowsiness
-reagent-effect-status-effect-Adrenaline = adrenaline
*[other] satiate
}
-reagent-effect-guidebook-create-entity-reaction-effect =
+entity-effect-guidebook-spawn-entity =
{ $chance ->
[1] Creates
*[other] create
*[other] {$amount} {MAKEPLURAL($entname)}
}
-reagent-effect-guidebook-explosion-reaction-effect =
+entity-effect-guidebook-explosion =
{ $chance ->
[1] Causes
*[other] cause
} an explosion
-reagent-effect-guidebook-emp-reaction-effect =
+entity-effect-guidebook-emp =
{ $chance ->
[1] Causes
*[other] cause
} an electromagnetic pulse
-reagent-effect-guidebook-flash-reaction-effect =
+entity-effect-guidebook-flash =
{ $chance ->
[1] Causes
*[other] cause
} a blinding flash
-reagent-effect-guidebook-foam-area-reaction-effect =
+entity-effect-guidebook-foam-area =
{ $chance ->
[1] Creates
*[other] create
} large quantities of foam
-reagent-effect-guidebook-smoke-area-reaction-effect =
+entity-effect-guidebook-smoke-area =
{ $chance ->
[1] Creates
*[other] create
} large quantities of smoke
-reagent-effect-guidebook-satiate-thirst =
+entity-effect-guidebook-satiate-thirst =
{ $chance ->
[1] Satiates
*[other] satiate
*[other] thirst at {NATURALFIXED($relative, 3)}x the average rate
}
-reagent-effect-guidebook-satiate-hunger =
+entity-effect-guidebook-satiate-hunger =
{ $chance ->
[1] Satiates
*[other] satiate
*[other] hunger at {NATURALFIXED($relative, 3)}x the average rate
}
-reagent-effect-guidebook-health-change =
+entity-effect-guidebook-health-change =
{ $chance ->
[1] { $healsordeals ->
[heals] Heals
}
} { $changes }
-reagent-effect-guidebook-even-health-change =
+entity-effect-guidebook-even-health-change =
{ $chance ->
[1] { $healsordeals ->
[heals] Evenly heals
}
} { $changes }
-
-reagent-effect-guidebook-status-effect =
+entity-effect-guidebook-status-effect-old =
{ $type ->
[update]{ $chance ->
[1] Causes
- *[other] cause
- } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
+ *[other] cause
+ } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
[add] { $chance ->
[1] Causes
*[other] cause
} {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
- *[set] { $chance ->
+ [set] { $chance ->
[1] Causes
*[other] cause
- } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
- [remove]{ $chance ->
+ } {LOC($key)} for {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
+ *[remove]{ $chance ->
[1] Removes
*[other] remove
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
}
-reagent-effect-guidebook-status-effect-delay =
+entity-effect-guidebook-status-effect =
{ $type ->
+ [update]{ $chance ->
+ [1] Causes
+ *[other] cause
+ } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
[add] { $chance ->
[1] Causes
*[other] cause
} {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
- *[set] { $chance ->
+ [set] { $chance ->
[1] Causes
*[other] cause
} {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
- [remove]{ $chance ->
+ *[remove]{ $chance ->
[1] Removes
*[other] remove
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
- } after a {NATURALFIXED($delay, 3)} second delay
+ } { $delay ->
+ [0] immediately
+ *[other] after a {NATURALFIXED($delay, 3)} second delay
+ }
+
+entity-effect-guidebook-status-effect-indef =
+ { $type ->
+ [update]{ $chance ->
+ [1] Causes
+ *[other] cause
+ } permanent {LOC($key)}
+ [add] { $chance ->
+ [1] Causes
+ *[other] cause
+ } permanent {LOC($key)}
+ [set] { $chance ->
+ [1] Causes
+ *[other] cause
+ } permanent {LOC($key)}
+ *[remove]{ $chance ->
+ [1] Removes
+ *[other] remove
+ } {LOC($key)}
+ } { $delay ->
+ [0] immediately
+ *[other] after a {NATURALFIXED($delay, 3)} second delay
+ }
-reagent-effect-guidebook-knockdown =
+entity-effect-guidebook-knockdown =
{ $type ->
[update]{ $chance ->
[1] Causes
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of knockdown
}
-reagent-effect-guidebook-set-solution-temperature-effect =
+entity-effect-guidebook-set-solution-temperature-effect =
{ $chance ->
[1] Sets
*[other] set
} the solution temperature to exactly {NATURALFIXED($temperature, 2)}k
-reagent-effect-guidebook-adjust-solution-temperature-effect =
+entity-effect-guidebook-adjust-solution-temperature-effect =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] at least {NATURALFIXED($mintemp, 2)}k
}
-reagent-effect-guidebook-adjust-reagent-reagent =
+entity-effect-guidebook-adjust-reagent-reagent =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] from
} the solution
-reagent-effect-guidebook-adjust-reagent-group =
+entity-effect-guidebook-adjust-reagent-group =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] from
} the solution
-reagent-effect-guidebook-adjust-temperature =
+entity-effect-guidebook-adjust-temperature =
{ $chance ->
[1] { $deltasign ->
[1] Adds
*[-1] from
} the body it's in
-reagent-effect-guidebook-chem-cause-disease =
+entity-effect-guidebook-chem-cause-disease =
{ $chance ->
[1] Causes
*[other] cause
} the disease { $disease }
-reagent-effect-guidebook-chem-cause-random-disease =
+entity-effect-guidebook-chem-cause-random-disease =
{ $chance ->
[1] Causes
*[other] cause
} the diseases { $diseases }
-reagent-effect-guidebook-jittering =
+entity-effect-guidebook-jittering =
{ $chance ->
[1] Causes
*[other] cause
} jittering
-reagent-effect-guidebook-chem-clean-bloodstream =
+entity-effect-guidebook-clean-bloodstream =
{ $chance ->
[1] Cleanses
*[other] cleanse
} the bloodstream of other chemicals
-reagent-effect-guidebook-cure-disease =
+entity-effect-guidebook-cure-disease =
{ $chance ->
[1] Cures
*[other] cure
} diseases
-reagent-effect-guidebook-cure-eye-damage =
+entity-effect-guidebook-eye-damage =
{ $chance ->
[1] { $deltasign ->
[1] Deals
}
} eye damage
-reagent-effect-guidebook-chem-vomit =
+entity-effect-guidebook-vomit =
{ $chance ->
[1] Causes
*[other] cause
} vomiting
-reagent-effect-guidebook-create-gas =
+entity-effect-guidebook-create-gas =
{ $chance ->
[1] Creates
*[other] create
*[other] moles
} of { $gas }
-reagent-effect-guidebook-drunk =
+entity-effect-guidebook-drunk =
{ $chance ->
[1] Causes
*[other] cause
} drunkness
-reagent-effect-guidebook-electrocute =
+entity-effect-guidebook-electrocute =
{ $chance ->
[1] Electrocutes
*[other] electrocute
} the metabolizer for {NATURALFIXED($time, 3)} {MANY("second", $time)}
-reagent-effect-guidebook-emote =
+entity-effect-guidebook-emote =
{ $chance ->
[1] Will force
*[other] force
} the metabolizer to [bold][color=white]{$emote}[/color][/bold]
-reagent-effect-guidebook-extinguish-reaction =
+entity-effect-guidebook-extinguish-reaction =
{ $chance ->
[1] Extinguishes
*[other] extinguish
} fire
-reagent-effect-guidebook-flammable-reaction =
+entity-effect-guidebook-flammable-reaction =
{ $chance ->
[1] Increases
*[other] increase
} flammability
-reagent-effect-guidebook-ignite =
+entity-effect-guidebook-ignite =
{ $chance ->
[1] Ignites
*[other] ignite
} the metabolizer
-reagent-effect-guidebook-make-sentient =
+entity-effect-guidebook-make-sentient =
{ $chance ->
[1] Makes
*[other] make
} the metabolizer sentient
-reagent-effect-guidebook-make-polymorph =
+entity-effect-guidebook-make-polymorph =
{ $chance ->
[1] Polymorphs
*[other] polymorph
} the metabolizer into a { $entityname }
-reagent-effect-guidebook-modify-bleed-amount =
+entity-effect-guidebook-modify-bleed-amount =
{ $chance ->
[1] { $deltasign ->
[1] Induces
}
} bleeding
-reagent-effect-guidebook-modify-blood-level =
+entity-effect-guidebook-modify-blood-level =
{ $chance ->
[1] { $deltasign ->
[1] Increases
}
} blood level
-reagent-effect-guidebook-paralyze =
+entity-effect-guidebook-paralyze =
{ $chance ->
[1] Paralyzes
*[other] paralyze
} the metabolizer for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
-reagent-effect-guidebook-movespeed-modifier =
+entity-effect-guidebook-movespeed-modifier =
{ $chance ->
[1] Modifies
*[other] modify
- } movement speed by {NATURALFIXED($walkspeed, 3)}x for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
+ } movement speed by {NATURALFIXED($sprintspeed, 3)}x for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
-reagent-effect-guidebook-reset-narcolepsy =
+entity-effect-guidebook-reset-narcolepsy =
{ $chance ->
[1] Temporarily staves
*[other] temporarily stave
} off narcolepsy
-reagent-effect-guidebook-wash-cream-pie-reaction =
+entity-effect-guidebook-wash-cream-pie-reaction =
{ $chance ->
[1] Washes
*[other] wash
} off cream pie from one's face
-reagent-effect-guidebook-cure-zombie-infection =
+entity-effect-guidebook-cure-zombie-infection =
{ $chance ->
[1] Cures
*[other] cure
} an ongoing zombie infection
-reagent-effect-guidebook-cause-zombie-infection =
+entity-effect-guidebook-cause-zombie-infection =
{ $chance ->
[1] Gives
*[other] give
} an individual the zombie infection
-reagent-effect-guidebook-innoculate-zombie-infection =
+entity-effect-guidebook-innoculate-zombie-infection =
{ $chance ->
[1] Cures
*[other] cure
} an ongoing zombie infection, and provides immunity to future infections
-reagent-effect-guidebook-reduce-rotting =
+entity-effect-guidebook-reduce-rotting =
{ $chance ->
[1] Regenerates
*[other] regenerate
} {NATURALFIXED($time, 3)} {MANY("second", $time)} of rotting
-reagent-effect-guidebook-area-reaction =
+entity-effect-guidebook-area-reaction =
{ $chance ->
[1] Causes
*[other] cause
} a smoke or foam reaction for {NATURALFIXED($duration, 3)} {MANY("second", $duration)}
-reagent-effect-guidebook-add-to-solution-reaction =
+entity-effect-guidebook-add-to-solution-reaction =
{ $chance ->
[1] Causes
*[other] cause
- } chemicals applied to an object to be added to its internal solution container
+ } {$reagent} to be added to its internal solution container
-reagent-effect-guidebook-artifact-unlock =
+entity-effect-guidebook-artifact-unlock =
{ $chance ->
[1] Helps
*[other] help
} unlock an alien artifact.
-reagent-effect-guidebook-artifact-durability-restore =
+entity-effect-guidebook-artifact-durability-restore =
Restores {$restored} durability in active alien artifact nodes.
-reagent-effect-guidebook-plant-attribute =
+entity-effect-guidebook-plant-attribute =
{ $chance ->
[1] Adjusts
*[other] adjust
- } {$attribute} by [color={$colorName}]{$amount}[/color]
+ } {$attribute} by {$positive ->
+ [true] [color=red]{$amount}[/color]
+ *[false] [color=green]{$amount}[/color]
+ }
-reagent-effect-guidebook-plant-cryoxadone =
+entity-effect-guidebook-plant-cryoxadone =
{ $chance ->
[1] Ages back
*[other] age back
} the plant, depending on the plant's age and time to grow
-reagent-effect-guidebook-plant-phalanximine =
+entity-effect-guidebook-plant-phalanximine =
{ $chance ->
[1] Restores
*[other] restore
} viability to a plant rendered nonviable by a mutation
-reagent-effect-guidebook-plant-diethylamine =
+entity-effect-guidebook-plant-diethylamine =
{ $chance ->
[1] Increases
*[other] increase
} the plant's lifespan and/or base health with 10% chance for each
-reagent-effect-guidebook-plant-robust-harvest =
+entity-effect-guidebook-plant-robust-harvest =
{ $chance ->
[1] Increases
*[other] increase
} the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance
-reagent-effect-guidebook-plant-seeds-add =
+entity-effect-guidebook-plant-seeds-add =
{ $chance ->
[1] Restores the
*[other] restore the
} seeds of the plant
-reagent-effect-guidebook-plant-seeds-remove =
+entity-effect-guidebook-plant-seeds-remove =
{ $chance ->
[1] Removes the
*[other] remove the
--- /dev/null
+entity-effect-status-effect-Stun = stunning
+entity-effect-status-effect-KnockedDown = knockdown
+entity-effect-status-effect-Jitter = jittering
+entity-effect-status-effect-TemporaryBlindness = blindness
+entity-effect-status-effect-SeeingRainbows = hallucinations
+entity-effect-status-effect-Muted = inability to speak
+entity-effect-status-effect-Stutter = stuttering
+entity-effect-status-effect-ForcedSleep = unconsciousness
+entity-effect-status-effect-Drunk = drunkenness
+entity-effect-status-effect-PressureImmunity = pressure immunity
+entity-effect-status-effect-Pacified = combat pacification
+entity-effect-status-effect-RatvarianLanguage = ratvarian language patterns
+entity-effect-status-effect-StaminaModifier = modified stamina
+entity-effect-status-effect-RadiationProtection = radiation protection
+entity-effect-status-effect-Drowsiness = drowsiness
+entity-effect-status-effect-Adrenaline = adrenaline
- reagents: [ Water, SpaceCleaner ]
methods: [ Touch ]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
- type: Crawler
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
groups:
- Brute: -0.25
+ Brute: -0.15
- reagents: [ Blood ]
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
groups:
- Brute: -0.5
- Burn: -0.5
+ Brute: -0.25
+ Burn: -0.25
- reagents: [ RobustHarvest ]
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
groups:
- Brute: -2
- Burn: -2
+ Brute: -1
+ Burn: -1
- reagents: [ WeedKiller, PlantBGone ]
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
- Heat: 2
+ Heat: 1
- type: ReplacementAccent
accent: tomatoKiller
- type: Item
- reagents: [ Water, SpaceCleaner ]
methods: [ Touch ]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
- reagents: [ Water ]
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
- Heat: 0.15
+ Heat: 0.05 #Same as slime species
- !type:PopupMessage
type: Local
messages: [ "slime-hurt-by-water-popup" ]
methods: [Touch, Ingestion, Injection]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
Caustic: 1
- reagents: [Water]
methods: [Touch]
effects:
- - !type:WearableReaction
+ - !type:SpawnEntityInInventory
slot: head
- prototypeID: WaterDropletHat
+ entity: WaterDropletHat
- reagents: [Water, SpaceCleaner]
methods: [Touch]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
# Damage (Self)
- type: Bloodstream
bloodReagent: CopperBlood
- reagents: [Water, SpaceCleaner]
methods: [Touch]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
- type: StatusEffects
allowed:
- Electrocution
- reagents: [Water, SpaceCleaner]
methods: [Touch]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
- reagents: [ PlantBGone ]
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
- Blunt: 0.1
- Slash: 0.1
- Piercing: 0.15
+ Blunt: 0.05
+ Slash: 0.05
+ Piercing: 0.075
- !type:PopupMessage
type: Local
visualType: Large
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
- Poison: 0.25
+ Poison: 0.125
- !type:PopupMessage
type: Local
visualType: Large
- reagents: [ Water, SpaceCleaner ]
methods: [ Touch ]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
- reagents: [ Milk, MilkGoat, MilkSoy, MilkSpoiled ]
# add new types of milk to reagents as they appear, oat milk isn't on the list
# because turns out oat milk has 1/30th the amount of calcium in it compared to the rest
methods: [ Touch ]
effects: # TODO: when magic is around - make a milk transformation to a skeleton monster
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
groups:
- Burn: -2 # healing obviously up to discussion
- Brute: -1.5 # these groups are the only 2 possible ways to damage a skeleton
+ Burn: -1 # healing obviously up to discussion
+ Brute: -0.75 # these groups are the only 2 possible ways to damage a skeleton
- !type:PopupMessage
type: Local
visualType: Large
- reagents: [ Water, SpaceCleaner ]
methods: [ Touch ]
effects:
- - !type:WashCreamPieReaction
+ - !type:WashCreamPie
- reagents: [ Water ]
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
- Heat: 0.1
+ Heat: 0.05
- !type:PopupMessage
type: Local
visualType: Large
- reagents: [ Water ]
methods: [ Touch ]
effects:
- - !type:AddToSolutionReaction
+ - !type:AddReagentToSolution
+ reagent: Water
solution: plushie
- type: Fixtures
fixtures:
methods: [Touch]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
damage:
types:
Heat: 0.5
- reagents: [Water]
methods: [Touch, Ingestion, Injection]
effects:
- - !type:AddToSolutionReaction
+ - !type:AddReagentToSolution
+ reagent: Water
solution: cube
- type: Rehydratable
- type: CollisionWake
- reagents: [Blood]
methods: [Touch, Ingestion, Injection]
effects:
- - !type:AddToSolutionReaction
+ - !type:AddReagentToSolution
+ reagent: Blood
solution: cube
- type: Rehydratable
catalyst: Blood # blood is fuel
whitelist:
components:
- MobState
+ - MovementSpeedModifier
+ requireAll: true
blacklist:
tags:
- SlowImmune
- type: MovementModStatusEffect
+- type: entity
+ parent: MobStatusEffectBase
+ id: StatusEffectSpeed
+ abstract: true
+ name: speed
+ components:
+ - type: StatusEffect
+ whitelist:
+ components:
+ - MobState
+ - MovementSpeedModifier
+ requireAll: true
+ - type: MovementModStatusEffect
+
+- type: entity
+ parent: StatusEffectSpeed
+ id: ReagentSpeedStatusEffect
+ name: reagent speed
+
- type: entity
parent: StatusEffectSlowdown
id: VomitingSlowdownStatusEffect
- reagents: [Water]
methods: [Touch, Ingestion, Injection]
effects:
- - !type:AddToSolutionReaction
+ - !type:AddReagentToSolution
+ reagent: Water
solution: soil
- type: Appearance
- type: PlantHolderVisuals
- Catwalk
- type: TileEntityEffect
effects:
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 3.75
multiplierOnExisting: 0.75
- !type:Ignite
- Catwalk
- type: TileEntityEffect
effects:
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 3.75
multiplierOnExisting: 0.75
- !type:Ignite
- type: SolutionContainerManager
solutions:
pool:
- maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable.
+ maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable.
reagents:
- ReagentId: Water
Quantity: 9999999
- Catwalk
- type: TileEntityEffect
effects:
- - !type:ExtinguishReaction
+ - !type:Extinguish
- name: ChangeSpecies
baseOdds: 0.036
appliesToProduce: false
- effect: !type:PlantSpeciesChange
+ effect: !type:PlantMutateSpeciesChange
persists: false
- name: Unviable
baseOdds: 0.109
- name: ChangeExudeGasses
baseOdds: 0.0145
persists: false
- effect: !type:PlantMutateExudeGasses
+ effect: !type:PlantMutateExudeGases
- name: ChangeConsumeGasses
baseOdds: 0.0036
persists: false
- effect: !type:PlantMutateConsumeGasses
+ effect: !type:PlantMutateConsumeGases
- name: ChangeHarvest
baseOdds: 0.036
persists: false
- !type:ModifyStatusEffect
effectProto: StatusEffectSeeingRainbow
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Absinthe
min: 10
type: Add
time: 5
- absinthe-effect-feel-tulips
probability: 0.02
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Absinthe
min: 10
- type: reagent
effects:
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ethanol
min: 15
- - !type:OrganType
- type: Dwarf
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Dwarf ]
+ inverted: true
damage:
types:
Poison: 1
# dwarves take less toxin damage and heal a marginal amount of brute
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ethanol
min: 15
- - !type:OrganType
- type: Dwarf
+ - !type:MetabolizerTypeCondition
+ type: [ Dwarf ]
damage:
types:
Poison: 0.2
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ethanol
min: 15
- - !type:OrganType
- type: Dwarf
+ - !type:MetabolizerTypeCondition
+ type: [ Dwarf ]
damage:
groups:
Brute: -1
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.04
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Ethanol
min: 12
# dwarves immune to vomiting from alcohol
- - !type:OrganType
- type: Dwarf
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Dwarf ]
+ inverted: true
- !type:Drunk
boozePower: 2
metabolisms:
Drink:
effects:
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Mayojito
min: 5
probability: 0.5
- !type:HealthChange
boozePower: 10
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: BacchusBlessing
min: 6
- - !type:OrganType
- type: Dwarf
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Dwarf]
+ inverted: true
damage:
types:
Poison: 2 # TODO: Figure out poison amount. Ethanol does 1, this does 2 but also metabolises almost 3 to 4 times as fast as ethanol. This would be more Liver damage when that time arrives.
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: BacchusBlessing
min: 6
- - !type:OrganType
- type: Dwarf
+ - !type:MetabolizerTypeCondition
+ type: [Dwarf]
damage:
types:
Poison: 0.4 # TODO: Might increase this, even though it's just double of ethanol from 0.2 to 0.4
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.1 #TODO: Tweak vomit probability, maybe make this more violent and poisonous but the body aggressively purges it...
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: BacchusBlessing
min: 8
- - !type:OrganType
- type: Dwarf
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Dwarf]
+ inverted: true
Extinguish:
methods: [ Touch ]
effects:
- - !type:ExtinguishReaction
+ - !type:Extinguish
plantMetabolism:
- !type:PlantAdjustWater
amount: 1
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
tileReactions:
- !type:FlammableTileReaction
temperatureMultiplier: 1.35
Gas:
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
conditions:
- - !type:OrganType
- type: Slime
- shouldHave: true
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
damage:
types:
Heat: 3
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Slime]
+ inverted: true
damage:
types:
Poison: 1
Brute: -0.5
Burn: -0.5
# Helps you stop bleeding to an extent.
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -0.25
- !type:SatiateHunger #Numbers are balanced with this in mind + it helps limit how much healing you can get from food
# Lets plants benefit too
effects:
- !type:SatiateHunger
conditions:
- - !type:ReagentThreshold #Only satiates when eaten with nutriment
+ - !type:ReagentCondition #Only satiates when eaten with nutriment
reagent: Nutriment
min: 0.1
factor: 1
emote: Cough
showInGuidebook: true
conditions:
- - !type:Breathing
- - !type:Internals
- usingInternals: false
+ - !type:BreathingCondition
+ - !type:InternalsCondition
+ inverted: true
- type: reagent
id: Vinegar
- !type:AdjustReagent
reagent: Vitamin
amount: 0.1
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Vinegar
min: 6
- type: reagent
amount: 250 # thermal energy, not temp
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: CapsaicinOil
min: 5
damage:
types:
effects:
- !type:SatiateHunger
conditions:
- - !type:ReagentThreshold #Only satiates when eaten with nutriment
+ - !type:ReagentCondition #Only satiates when eaten with nutriment
reagent: Nutriment
min: 0.1
factor: 1
amount: -250 # thermal energy, not temp
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: FrostOil
min: 5
damage:
types:
- !type:SatiateThirst
factor: 1.0
conditions:
- - !type:OrganType
- type: Human
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Human ]
+ inverted: true
Food:
effects:
- !type:AdjustReagent
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Bloodsucker
+ - !type:MetabolizerTypeCondition
+ type: [ Bloodsucker ]
damage:
groups:
Brute: -3
damage:
types:
Poison: 4
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.25
- type: reagent
damage:
types:
Bloodloss: -3
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -1.5
# Just in case you REALLY want to water your plants
plantMetabolism:
effects:
- !type:SatiateHunger
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- type: reagent
id: Left4Zed
effects:
- !type:SatiateHunger
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- !type:HealthChange
damage:
types:
Poison: 1
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- type: reagent
id: PestKiller
types:
Poison: 3
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- type: reagent
id: PlantBGone
Slash: 1
Piercing: 1
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- type: reagent
id: RobustHarvest
Slash: -3
Piercing: -3
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Plant
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
+ - !type:ReagentCondition
+ reagent: RobustHarvest
min: 30
damage:
types:
- !type:Polymorph
prototype: TreeMorph
conditions:
- - !type:OrganType
- type: Plant
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
+ - !type:ReagentCondition
+ reagent: RobustHarvest
min: 80
- type: reagent
types:
Poison: 4
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [Plant]
- type: reagent
id: Ammonia
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Rat
- shouldHave: false
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [ Rat ]
+ inverted: true
+ - !type:ReagentCondition
reagent: Ammonia
min: 1
ignoreResistances: true
damage:
types:
Poison: 0.25
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.12
conditions:
- - !type:OrganType
- type: Rat
- shouldHave: false
- - !type:OrganType
- type: Vox
- shouldHave: false
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [ Rat, Vox ]
+ inverted: true
+ - !type:ReagentCondition
reagent: Ammonia
min: 0.8
- !type:PopupMessage
messages: [ "ammonia-smell" ]
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Ammonia
min: 0.25
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Rat
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [ Rat ]
+ - !type:ReagentCondition
reagent: Ammonia
min: 1
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
damage:
groups:
- Brute: -5
- Burn: -5
+ Brute: -2.5
+ Burn: -2.5
types:
Bloodloss: -5
- !type:Oxygenate # ammonia displaces nitrogen in vox blood
conditions:
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
factor: -4
damage:
types:
Poison: -1
- - !type:ChemCleanBloodstream
+ - !type:CleanBloodstream
+ excluded: Charcoal
cleanseRate: 3
- type: reagent
methods: [ Touch ]
effects:
- !type:ArtifactUnlock
- conditions:
- - !type:ReagentThreshold
- min: 5
+ minScale: 5
- type: reagent
parent: Artifexium
methods: [ Touch ]
effects:
- !type:ArtifactDurabilityRestore
- conditions:
- - !type:ReagentThreshold
- min: 5
+ minScale: 5
- type: reagent
id: Benzene
Heat: 1.5
Medicine:
effects:
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: SodiumHydroxide
min: 5
probability: 0.1
metabolisms:
Food:
effects:
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: SoapReagent
min: 6
probability: 0.20
Drink:
effects:
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: SoapReagent
min: 6
probability: 0.20
methods: [ Touch ]
effects:
# pva glue? no, antibiotic glue for sealing wounds
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -1.5
metabolisms:
Narcotic:
- !type:GenericStatusEffect
key: Muted
component: Muted
- type: Add
time: 5
- refresh: false
footstepSound:
collection: FootstepSlime
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Arachnid
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Arachnid ]
+ inverted: true
damage:
types:
Poison: 0.1
effects:
- !type:ModifyBloodLevel
conditions:
- - !type:OrganType
- type: Arachnid
- shouldHave: true
+ - !type:MetabolizerTypeCondition
+ type: [ Arachnid ]
amount: 0.4
- type: reagent
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Arachnid
- shouldHave: true
+ - !type:MetabolizerTypeCondition
+ type: [ Arachnid ]
damage:
types:
Poison: 0.1
effects:
- !type:ModifyBloodLevel
conditions:
- - !type:OrganType
- type: Arachnid
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Arachnid ]
+ inverted: true
amount: 0.4
- type: reagent
# Hail the madman logic, if it has CARP, means it helps against CARPs
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: CarpoToxin
min: 1
reagent: CarpoToxin
effects:
- !type:SatiateHunger
conditions:
- - !type:OrganType
- type: Moth
+ - !type:MetabolizerTypeCondition
+ type: [ Moth ]
- type: reagent
id: BuzzochloricBees
- "buzzochloricbees-effect-squeaky-clean"
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
max: 0
reagent: Histamine
- - !type:HasTag
- invert: true
+ - !type:TagCondition
+ inverted: true
tag: Bee
- !type:PopupMessage
type: Local
- "buzzochloricbees-effect-squeaky-clean"
probability: 0.05
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
min: 0.01
reagent: Histamine
- - !type:HasTag
- invert: true
+ - !type:TagCondition
+ inverted: true
tag: Bee
- !type:PopupMessage
type: Local
- "buzzochloricbees-effect-licoxide-buzzes"
probability: 0.05
conditions:
- - !type:HasTag
- invert: true
+ - !type:TagCondition
+ inverted: true
tag: Bee
- - !type:ReagentThreshold
+ - !type:ReagentCondition
min: 0.01
reagent: Licoxide
- !type:PopupMessage
- "buzzochloricbees-effect-fiber-soft"
probability: 0.05
conditions:
- - !type:HasTag
- invert: true
+ - !type:TagCondition
+ inverted: true
tag: Bee
- - !type:ReagentThreshold
+ - !type:ReagentCondition
min: 0.01
reagent: Fiber
- !type:HealthChange
Poison: 2
Piercing: 2
conditions:
- - !type:HasTag
- invert: true
+ - !type:TagCondition
+ inverted: true
tag: Bee
- type: reagent
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: false
damage:
types:
- Slash: 0.5
+ Slash: 0.25
- !type:Emote
emote: Scream
probability: 0.7
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: false
damage:
types:
Cold: 0.05
- !type:AdjustTemperature
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
min: 160.15
amount: -30000
Extinguish:
methods: [ Touch ]
effects:
- - !type:ExtinguishReaction # cold
+ - !type:Extinguish # cold
metabolisms:
Poison:
metabolismRate : 0.45
Heat: -3 # ghetto burn chem. i don't think anyone would use this intentionally but it's funny
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Fresium
max: 35
type: Local
probability: 0.05
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Fresium
max: 35
type: Local
probability: 0.2
- !type:AdjustTemperature
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
min: 160.15 # not quite enough for cryo, but can speed it up if you wanna take the risk
amount: -10000
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Fresium
max: 40 # slows when less than 40
walkSpeedModifier: 0.6
sprintSpeedModifier: 0.6
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Fresium
min: 40 # your legs stop working when above 40
walkSpeedModifier: 0.00
sprintSpeedModifier: 0.00
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Fresium
min: 40
type: Local
- !type:Polymorph
prototype: ArtifactLizard # Does the same thing as the original YML I made for this reagent.
conditions:
- - !type:OrganType
- type: Animal
- shouldHave: false
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [Animal]
+ inverted: true
+ - !type:ReagentCondition
+ reagent: JuiceThatMakesYouWeh
min: 50
- !type:AdjustReagent
reagent: JuiceThatMakesYouWeh
amount: -20
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: JuiceThatMakesYouWeh
min: 50
- type: reagent
- !type:Polymorph
prototype: ArtifactLizard
conditions:
- - !type:OrganType
- type: Animal
- shouldHave: false
- - !type:ReagentThreshold
+ - !type:MetabolizerTypeCondition
+ type: [ Animal ]
+ inverted: true
+ - !type:ReagentCondition
+ reagent: JuiceThatMakesYouHew
min: 50
- !type:AdjustReagent
reagent: JuiceThatMakesYouHew
amount: -20
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: JuiceThatMakesYouHew
min: 50
- type: reagent
effects:
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: CorgiJuice
min: 15
damage:
types:
- !type:Polymorph
prototype: SmartCorgiMorph
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: CorgiJuice
min: 50
- !type:AdjustReagent
reagent: CorgiJuice
amount: -20
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: CorgiJuice
min: 50
Poison:
effects:
- !type:Oxygenate
+ scaling: true
conditions:
- - !type:OrganType
- type: Human
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Animal
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Rat
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [ Human, Animal, Rat, Plant ]
# Convert Oxygen into CO2.
- !type:ModifyLungGas
+ scaling: true
conditions:
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
+ inverted: true
ratios:
CarbonDioxide: 1.0
Oxygen: -1.0
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Vox
- scaleByQuantity: true
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
+ scaling: true
ignoreResistances: true
damage:
types:
- Poison:
- 3
+ Poison: 1.5
- !type:AdjustAlert
alertType: Toxins
+ minScale: 1
conditions:
- - !type:ReagentThreshold
- min: 0.5
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
clear: true
time: 5
Gas:
effects:
- !type:Oxygenate
+ scaling: true
conditions:
- - !type:OrganType
- type: Human
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Animal
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Rat
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [ Human, Animal, Rat, Plant ]
# Convert Oxygen into CO2.
- !type:ModifyLungGas
+ scaling: true
conditions:
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
+ inverted: true
ratios:
CarbonDioxide: 1.0
Oxygen: -1.0
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Vox
- scaleByQuantity: true
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
+ scaling: true
ignoreResistances: true
damage:
types:
- Poison:
- 7
+ Poison: 0.7
- !type:AdjustAlert
alertType: Toxins
+ minScale: 1
conditions:
- - !type:ReagentThreshold
- min: 0.5
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
clear: true
time: 5
- !type:HealthChange
damage:
types:
- Poison: 3
+ Poison: 1.5
- !type:AdjustReagent
reagent: Inaprovaline
amount: -2.0
Gas:
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
damage:
types:
Poison:
- 1
+ 0.1
# We need a metabolism effect on reagent removal
- !type:AdjustAlert
alertType: Toxins
- conditions:
- - !type:ReagentThreshold
- min: 1.5
+ minScale: 3
clear: True
time: 5
reactiveEffects:
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
- type: reagent
id: Tritium
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.8
metabolisms:
Poison:
- !type:HealthChange
damage:
types:
- Radiation: 3
+ Radiation: 1.5
Gas:
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
damage:
types:
# We need a metabolism effect on reagent removal
- !type:AdjustAlert
alertType: Toxins
- conditions:
- - !type:ReagentThreshold
- min: 1.5
+ minScale: 3
clear: True
time: 5
Poison:
effects:
- !type:Oxygenate
+ scaling: true
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [ Plant ]
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Plant
- shouldHave: false
- - !type:OrganType
- type: Vox
- shouldHave: false
- scaleByQuantity: true
+ - !type:MetabolizerTypeCondition
+ type: [ Plant, Vox ]
+ inverted: true
+ scaling: true
ignoreResistances: true
damage:
types:
Poison:
- 0.8
+ 0.4
- !type:Oxygenate
+ scaling: true
conditions:
- - !type:OrganType
- type: Plant
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Plant ]
+ inverted: true
factor: -4
Gas:
effects:
- !type:Oxygenate
+ scaling: true
conditions:
- - !type:OrganType
- type: Plant
+ - !type:MetabolizerTypeCondition
+ type: [ Plant ]
- !type:HealthChange
+ minScale: 1
conditions:
- - !type:OrganType
- type: Plant
- shouldHave: false
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Plant, Vox ]
+ inverted: true
# Don't want people to get toxin damage from the gas they just
# exhaled, right?
- - !type:ReagentThreshold
- min: 0.5
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
damage:
types:
Poison:
0.8
- !type:Oxygenate # carbon dioxide displaces oxygen from the bloodstream, causing asphyxiation
+ scaling: true
conditions:
- - !type:OrganType
- type: Plant
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Plant ]
+ inverted: true
factor: -4
# We need a metabolism effect on reagent removal
#- !type:AdjustAlert
- !type:HealthChange
damage:
types:
- Cold: 1 # liquid nitrogen is cold
+ Cold: 0.5 # liquid nitrogen is cold
Gas:
effects:
- !type:Oxygenate
+ scaling: true
conditions:
- - !type:OrganType
- type: Vox
- - !type:Oxygenate
- conditions:
- - !type:OrganType
- type: Slime
- # Converts Nitrogen into CO2
+ - !type:MetabolizerTypeCondition
+ type: [ Vox, Slime ]
+ # Converts Nitrogen into Ammonia
- !type:ModifyLungGas
+ scaling: true
conditions:
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
ratios:
Ammonia: 1.0
Nitrogen: -1.0
- !type:ModifyLungGas
+ scaling: true
conditions:
- - !type:OrganType
- type: Slime
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
ratios:
NitrousOxide: 1.0
Nitrogen: -1.0
- !type:HealthChange
damage:
types:
- Poison: 2
+ Poison: 1
Gas:
effects:
- !type:Emote
+ minScale: 1
conditions:
- - !type:ReagentThreshold
- reagent: NitrousOxide
- min: 0.2
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
+ inverted: true
emote: Laugh
showInChat: true
probability: 0.1
- !type:Emote
+ minScale: 0.4
conditions:
- - !type:ReagentThreshold
- reagent: NitrousOxide
- min: 0.2
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
+ inverted: true
emote: Scream
showInChat: true
probability: 0.01
- !type:PopupMessage
+ minScale: 1
conditions:
- - !type:ReagentThreshold
- reagent: NitrousOxide
- min: 0.5
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
+ inverted: true
type: Local
visualType: Medium
messages: [ "effect-sleepy" ]
probability: 0.1
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
+ minScale: 2
conditions:
- - !type:ReagentThreshold
- reagent: NitrousOxide
- min: 1
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
+ inverted: true
walkSpeedModifier: 0.65
sprintSpeedModifier: 0.65
- !type:ModifyStatusEffect
+ minScale: 3.6
conditions:
- - !type:ReagentThreshold
- reagent: NitrousOxide
- min: 1.8
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
+ inverted: true
effectProto: StatusEffectForcedSleeping
time: 3
type: Update
- !type:HealthChange
+ minScale: 7
conditions:
- - !type:ReagentThreshold
- reagent: NitrousOxide
- min: 3.5
- - !type:OrganType
- type: Slime
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Slime ]
+ inverted: true
ignoreResistances: true
damage:
types:
Narcotic:
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
damage:
types:
- Cellular: 1
+ Cellular: 0.5
- !type:ModifyStatusEffect
effectProto: StatusEffectSeeingRainbow
type: Add
type: Local
messages: [ "frezon-lungs-cold" ]
probability: 0.1
- conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 0.5
+ minScale: 1
- !type:PopupMessage
type: Local
visualType: Medium
messages: [ "frezon-euphoric" ]
probability: 0.1
conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 1
+ minScale: 2
Gas:
effects:
- !type:HealthChange
- conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 0.5
- scaleByQuantity: true
+ minScale: 1
+ scaling: true
ignoreResistances: true
damage:
types:
- Cellular: 0.5
+ Cellular: 0.05
- !type:ModifyStatusEffect
- conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 1
+ minScale: 2
effectProto: StatusEffectSeeingRainbow
type: Add
time: 500
- !type:Drunk
boozePower: 500
- conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 1
+ minScale: 2
- !type:PopupMessage
type: Local
messages: [ "frezon-lungs-cold" ]
probability: 0.1
- conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 0.5
+ minScale: 2
- !type:PopupMessage
type: Local
visualType: Medium
messages: [ "frezon-euphoric" ]
probability: 0.1
- conditions:
- - !type:ReagentThreshold
- reagent: Frezon
- min: 1
+ minScale: 2
Poison: -1
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dylovene
min: 20
damage:
groups:
Brute: 2
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dylovene
min: 20
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dylovene
min: 20
type: Local
visualType: Medium
messages: [ "generic-reagent-effect-nauseous" ]
probability: 0.2
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dylovene
min: 20
probability: 0.02
- !type:Drunk
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dylovene
min: 15
plantMetabolism:
- !type:PlantAdjustToxins
Brute: -1.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Bicaridine
min: 15
damage:
types:
Asphyxiation: 0.5
Poison: 1.5
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Bicaridine
min: 30
probability: 0.02
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Bicaridine
min: 15
- !type:Drunk
effects:
- !type:HealthChange
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
# this is a little arbitrary but they gotta be pretty cold
max: 213.0
damage:
effects:
- !type:HealthChange
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
max: 213.0
damage:
types:
Cold: -1.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dermaline
min: 10
damage:
types:
Brute: 0.5
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dermaline
min: 10
- type: reagent
Bloodloss: -0.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Dexalin
min: 20
damage:
types:
Bloodloss: -3
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: HeartbreakerToxin
min: 1
reagent: HeartbreakerToxin
amount: -3
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: DexalinPlus
min: 25
damage:
types:
effects:
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Histamine
min: 45
reagent: Histamine
# they gotta be in crit first
- !type:MobStateCondition
mobstate: Critical
- - !type:ReagentThreshold
- min: 0
+ - !type:ReagentCondition
+ reagent: Epinephrine
max: 20
damage:
types:
Burn: -0.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Epinephrine
min: 20
damage:
types:
amount: -2
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: HeartbreakerToxin
min: 1
reagent: Epinephrine
- !type:AdjustReagent
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: HeartbreakerToxin
min: 1
reagent: Histamine
damage:
types:
Radiation: -1
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.02
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Hyronalin
min: 30
damage:
types:
Heat: 2
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Hyronalin
min: 30
- type: reagent
metabolisms:
Medicine:
effects:
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ipecac
min: 4
probability: 0.3
damage:
types:
Asphyxiation: -2
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -0.25
- type: reagent
- !type:SatiateThirst
factor: -10
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Kelotane
min: 30
- !type:PopupMessage
type: Local
- generic-reagent-effect-parched
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Kelotane
min: 25
- type: reagent
Cold: -4
- !type:AdjustTemperature
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
max: 293.15
amount: 100000 # thermal energy, not temperature!
- !type:AdjustTemperature
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
min: 293.15
amount: -10000
- !type:PopupMessage
Heat: 0.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Barozine
min: 30
damage:
types:
Poison: 3
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.15
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Barozine
min: 30
- !type:GenericStatusEffect
key: PressureImmunity
amount: 6
- !type:PlantPhalanximine
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Phalanximine
min: 4
metabolisms:
Medicine:
Caustic: 0.15
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Phalanximine
min: 11
damage:
types:
Radiation: 0.2
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Arithrazine
min: 1
damage:
Asphyxiation: -2.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: PolypyryliumOligomers
min: 30
damage:
types:
Asphyxiation: 3.5
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -0.25
- type: reagent
effects:
- !type:CureZombieInfection
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ambuzol
min: 10
- type: reagent
- !type:CureZombieInfection
innoculate: true
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: AmbuzolPlus
min: 5
- type: reagent
metabolisms:
Medicine:
effects:
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -0.5
- type: reagent
Poison: -4
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Amatoxin
min: 1
reagent: Amatoxin
effects:
# Medium-large quantities can hurt you instead,
# but still technically stop your bleeding.
- - !type:ModifyBleedAmount
+ - !type:ModifyBleed
amount: -1.5
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: TranexamicAcid
min: 15
damage:
types:
effects:
- !type:HealthChange
conditions:
- - !type:TotalDamage
+ - !type:TotalDamageCondition
max: 50
damage:
groups:
effects:
- !type:EvenHealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ultravasculine
min: 0
max: 20
damage:
Toxin: -6
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ultravasculine
min: 0
max: 20
damage:
Brute: 1.5
- !type:EvenHealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ultravasculine
min: 20
damage:
Toxin: -2
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ultravasculine
min: 20
damage:
groups:
Brute: 6
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Histamine
min: 1
reagent: Histamine
amount: -1
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Histamine
min: 1
reagent: Ultravasculine
metabolisms:
Medicine:
effects:
- - !type:ChemHealEyeDamage
+ - !type:EyeDamage
- type: reagent
id: Cognizine
effects:
- !type:MakeSentient
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Cognizine
min: 5
- type: reagent
type: Remove
- !type:ResetNarcolepsy
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ethyloxyephedrine
min: 10
- !type:PopupMessage
visualType: Medium
type: Remove
- !type:ResetNarcolepsy
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Diphenylmethylamine
min: 5
- !type:PopupMessage
visualType: Medium
Caustic: -1.25 # 5 per u
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Sigynate
min: 16
damage:
types:
Heat: 1 # 4 per u
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Sigynate
min: 20
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Sigynate
min: 20
type: Local
visualType: Medium
messages: [ "generic-reagent-effect-nauseous" ]
probability: 0.2
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Sigynate
min: 30
probability: 0.02
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Arithrazine
min: 1
probability: 0.1
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Arithrazine
min: 1
type: Local
Heat: 0.2 # 0.8 per u
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Lacerinol
min: 12
damage:
types:
Radiation: 0.05 # 0.2 per u, 3 for 15u
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Puncturase
min: 12
damage:
types:
factor: -1
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Bruizine
min: 10.5
damage:
types:
factor: 3
- !type:HealthChange
conditions:
- - !type:TotalDamage
+ - !type:TotalDamageCondition
max: 50
damage:
types:
Heat: -0.2
Shock: -0.2
Cold: -0.2
-
reactiveEffects:
Extinguish:
methods: [ Touch ]
effects:
- - !type:ExtinguishReaction
+ - !type:Extinguish
Acidic:
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: false
damage:
types:
# od causes massive bleeding
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Pyrazine
min: 20
damage:
types:
Slash: 0.5
Piercing: 0.5
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Pyrazine
min: 15
probability: 0.1
- !type:Emote
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Pyrazine
min: 20
emote: Scream
probability: 0.2
# od makes you freeze to death
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Insuzine
min: 12
damage:
types:
Cold: 1 # 4 per u
- !type:AdjustTemperature
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Insuzine
min: 12
amount: -30000
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Insuzine
min: 12
- type: reagent
seconds: 20
conditions:
#Patient must be dead and in a cryo tube (or something cold)
- - !type:Temperature
+ - !type:TemperatureCondition
max: 150.0
- !type:MobStateCondition
mobstate: Dead
effects:
- !type:HealthChange
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
max: 213.0
damage:
groups:
effects:
- !type:HealthChange
conditions:
- - !type:Temperature
+ - !type:TemperatureCondition
max: 213.0
damage:
types:
effects:
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Mannitol
min: 15
type: Local
visualType: Medium
effects:
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Psicodine
min: 30
damage:
types:
- !type:ModifyStatusEffect
effectProto: StatusEffectSeeingRainbow
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Psicodine
min: 30
type: Add
time: 8
component: RadiationProtection
time: 2
type: Add
- refresh: false
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: PotassiumIodide
min: 20
damage:
types:
Poison: 0.75
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Desoxyephedrine
min: 30
damage:
types:
Asphyxiation: 2
Narcotic:
effects:
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 1.35
sprintSpeedModifier: 1.35
- !type:GenericStatusEffect
type: Remove
- !type:ModifyStatusEffect
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Haloperidol
max: 0.01
effectProto: StatusEffectDrowsiness
effects:
- !type:ResetNarcolepsy
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Desoxyephedrine
min: 20
- type: reagent
metabolisms:
Narcotic:
effects:
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 1.25
sprintSpeedModifier: 1.25
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ephedrine
min: 20
damage:
types:
type: Remove
- !type:ModifyStatusEffect
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Haloperidol
max: 0.01
effectProto: StatusEffectDrowsiness
effects:
- !type:ResetNarcolepsy
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Ephedrine
min: 30
- type: reagent
Narcotic:
metabolismRate: 1.0
effects:
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 1.3
sprintSpeedModifier: 1.3
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Stimulants
min: 80 #please wait 3 minutes before using another stimpack
damage:
types:
Poison: 1
- !type:AdjustReagent
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: ChloralHydrate
min: 1
reagent: ChloralHydrate
key: StaminaModifier
component: StaminaModifier
time: 3
- type: Add
- !type:ModifyStatusEffect
effectProto: StatusEffectForcedSleeping
time: 3
type: Remove
- !type:ModifyStatusEffect
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Haloperidol
max: 0.01
effectProto: StatusEffectDrowsiness
factor: 1
- !type:HealthChange
conditions:
- - !type:TotalDamage
+ - !type:TotalDamageCondition
min: 70 # only heals when you're more dead than alive
damage: # heals at the same rate as tricordrazine, doesn't heal poison because if you OD'd I'm not giving you a safety net
groups:
metabolisms:
Narcotic:
effects:
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 0.65
sprintSpeedModifier: 0.65
- !type:HealthChange
effectProto: StatusEffectSeeingRainbow
time: 10
type: Add
- - !type:ChemVomit # Vomiting is a symptom of brain damage
+ - !type:Vomit # Vomiting is a symptom of brain damage
probability: 0.05
- !type:Drunk # Headaches and slurring are major symptoms of brain damage, this is close enough
boozePower: 5
metabolisms:
Narcotic:
effects: # It would be nice to have speech slurred or mumbly, but accents are a bit iffy atm. Same for distortion effects.
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 0.65
sprintSpeedModifier: 0.65
- !type:ModifyStatusEffect
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Nocturine
min: 8
effectProto: StatusEffectForcedSleeping
time: 6
delay: 5
- type: Update
- type: reagent
id: MuteToxin
- norepinephricacid-effect-darkness
- norepinephricacid-effect-blindness
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: NorepinephricAcid
min: 20
probability: 0.03
#If anyone wants to add a light dimming or grayscale effect when under 20u, be my guest
key: TemporaryBlindness
component: TemporaryBlindness
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: NorepinephricAcid
min: 20
- type: reagent
emote: Scream
probability: 0.08
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: TearGas
min: 4
- !type:Emote
emote: Cough
key: TemporaryBlindness
component: TemporaryBlindness
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: TearGas
min: 4
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 0.65
sprintSpeedModifier: 0.65
- statusLifetime: 1.5
+ time: 1.5
conditions: # because of the remainding after effect, threshold is given so the effects ends simultaniously
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: TearGas
min: 4
- type: reagent
showInChat: true
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Happiness
max: 20
- !type:Emote
emote: Whistle
showInChat: true
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Happiness
max: 20
- !type:Emote
emote: Crying
showInChat: true
probability: 0.1
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Happiness
min: 20
- !type:PopupMessage # we dont have sanity/mood so this will have to do
type: Local
- "psicodine-effect-at-peace"
probability: 0.2
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Happiness
max: 20
- !type:ModifyStatusEffect
effectProto: StatusEffectSeeingRainbow
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
plantMetabolism:
- !type:PlantAdjustWeeds
amount: -2
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.4
metabolisms:
Poison:
Heat: 2
Poison: 1
Caustic: 0.5 # based off napalm being an irritant
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.4
- type: reagent
types:
Heat: 3
Poison: 1
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.1
- !type:AdjustTemperature
amount: 6000
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.2
- !type:Ignite
Heat: 2
Poison: 1
Caustic: 0.5 # CLF3 is corrosive
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.2
- !type:AdjustTemperature
amount: 6000
Flammable:
methods: [ Touch ]
effects:
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.3
- !type:Ignite
- !type:Emote
- !type:SatiateThirst
factor: 1
conditions:
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [Vox]
Poison:
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Vox]
+ inverted: true
damage:
types:
Poison: 1
- - !type:FlammableReaction
+ - !type:Flammable
multiplier: 0.4
- type: reagent
emote: Yawn
showInChat: true
probability: 0.1
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 0.65
sprintSpeedModifier: 0.65
- !type:ModifyStatusEffect
effectProto: StatusEffectDrowsiness
time: 4
- type: Add
+ type: Update
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: ChloralHydrate
min: 20
damage:
damage:
types:
Poison: 2
- - !type:ChemVomit
+ - !type:Vomit
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: GastroToxin
min: 2
probability: 0.2
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: false
damage:
types:
- !type:SatiateThirst
factor: -1.5
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: true
damage:
types:
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: false
damage:
types:
methods: [ Touch ]
effects:
- !type:HealthChange
- scaleByQuantity: true
+ scaling: true
ignoreResistances: false
damage:
types:
# todo: cough, sneeze
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Histamine
min: 45
damage:
groups:
probability: 0.1
- !type:PopupMessage
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Histamine
min: 45
type: Local
visualType: Medium
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Animal # Applying damage to the mobs with lower metabolism capabilities
+ - !type:MetabolizerTypeCondition
+ type: [Animal] # Applying damage to the mobs with lower metabolism capabilities
damage:
types:
Poison: 0.4
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.04 #Scaled for time, not metabolismrate.
conditions:
- - !type:OrganType
- type: Animal
+ - !type:MetabolizerTypeCondition
+ type: [Animal]
- type: reagent
id: Amatoxin
effects:
- !type:CauseZombieInfection
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Romerol
min: 5
- type: reagent
effects:
- !type:PopupMessage
conditions:
- - !type:OrganType
- type: Animal
- shouldHave: false
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Animal, Vox ]
+ inverted: true
type: Local
visualType: MediumCaution
messages: [ "generic-reagent-effect-sick" ]
probability: 0.5
- - !type:ChemVomit
+ - !type:Vomit
probability: 0.1
conditions:
- - !type:OrganType
- type: Animal
- shouldHave: false
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Animal, Vox ]
+ inverted: true
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Animal
- shouldHave: false
- - !type:OrganType
- type: Vox
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [ Animal, Vox ]
+ inverted: true
damage:
types:
Poison: 1
- !type:AdjustReagent
conditions:
- - !type:OrganType
- type: Animal
+ - !type:MetabolizerTypeCondition
+ type: [ Animal ]
reagent: Protein
amount: 0.5
- !type:AdjustReagent
conditions:
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [ Vox ]
reagent: Protein
amount: 0.25
effects:
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Allicin
min: 1
- - !type:OrganType
- type: Animal
+ - !type:MetabolizerTypeCondition
+ type: [Animal]
damage:
types:
Poison: 0.06
- !type:GenericStatusEffect
key: Pacified
component: Pacified
- type: Add
time: 4
- type: reagent
probability: 0.2
- !type:HealthChange
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Honk
min: 1
- - !type:OrganType
- type: Animal
+ - !type:MetabolizerTypeCondition
+ type: [ Animal ]
damage:
types:
Poison: 0.06
effects:
- !type:Jitter
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
+ reagent: Vestine
min: 5
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
walkSpeedModifier: 0.8
sprintSpeedModifier: 0.8
- !type:HealthChange
effects:
- !type:HealthChange
conditions:
- - !type:Hunger
+ - !type:HungerCondition
max: 50
damage:
types:
effects:
- !type:HealthChange
conditions:
- - !type:OrganType
- type: Arachnid
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Arachnid]
+ inverted: true
damage:
types:
Poison: 1.6
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Mechanotoxin
min: 2
- - !type:OrganType
- type: Arachnid
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Arachnid]
+ inverted: true
walkSpeedModifier: 0.8
sprintSpeedModifier: 0.8
- - !type:MovespeedModifier
+ - !type:MovementSpeedModifier
conditions:
- - !type:ReagentThreshold
+ - !type:ReagentCondition
reagent: Mechanotoxin
min: 4
- - !type:OrganType
- type: Arachnid
- shouldHave: false
+ - !type:MetabolizerTypeCondition
+ type: [Arachnid]
+ inverted: true
walkSpeedModifier: 0.4
sprintSpeedModifier: 0.4
- !type:SatiateHunger
factor: 1
conditions:
- - !type:OrganType
- type: Vox
+ - !type:MetabolizerTypeCondition
+ type: [Vox]
Potassium:
amount: 1
effects:
- - !type:ExplosionReactionEffect
+ - !type:ExplosionEffect
explosionType: Default
intensityPerUnit: 0.25
maxTotalIntensity: 100
Aluminium:
amount: 1
effects:
- - !type:EmpReactionEffect
- rangePerUnit: 0.2
+ - !type:Emp
+ rangeModifier: 0.2
maxRange: 6
energyConsumption: 12500
duration: 15
Sulfur:
amount: 1
effects:
- - !type:FlashReactionEffect
+ - !type:Flash
- type: reaction
id: TableSalt
amount: 5
catalyst: true
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodCheese
- type: reaction
amount: 5
catalyst: true
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodChevre
- type: reaction
Water:
amount: 10
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodDough
- type: reaction
Egg:
amount: 6
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodDoughCornmeal
- type: reaction
Water:
amount: 10
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodDoughTortilla
- type: reaction
Water:
amount: 10
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodDoughCotton
- type: reaction
Milk:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodCakeBatter
- type: reaction
amount: 5
catalyst: true
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodButter
- type: reaction
TableSalt:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodDoughPie
# TG has a cake recipe that uses soy milk instead of eggs.
Sugar:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodCakeBatter
- type: reaction
amount: 5
catalyst: true
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodTofu
- type: reaction
Egg:
amount: 6
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodMeatMeatball
- type: reaction
Sugar:
amount: 2
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodSnackChocolateBar
# Condiments
Carbon:
amount: 10
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: FoodMeat
- type: reaction
SulfuricAcid:
amount: 2
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: SheetPlastic1
- type: reaction
Ethanol:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: ShardCrystalRandom
- type: reaction
Charcoal:
amount: 2
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: MaterialGunpowder
- type: reaction
FrostOil:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: IngotGold1
- type: reaction
FrostOil:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: IngotSilver1
- type: reaction
JuiceThatMakesYouHew:
amount: 1
effects:
- - !type:ExplosionReactionEffect
+ - !type:ExplosionEffect
explosionType: Radioactive
maxIntensity: 200
intensityPerUnit: 2
Fluorine:
amount: 3
effects:
- - !type:ExplosionReactionEffect
+ - !type:ExplosionEffect
explosionType: Default # 15 damage per intensity.
maxIntensity: 200
intensityPerUnit: 5
products:
Ethanol: 5
Hydrogen: 3
- Sulfur: 2
\ No newline at end of file
+ Sulfur: 2
TableSalt:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: Soap
- type: reaction
Plasma:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: SoapNT
- type: reaction
JuiceBerry:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: SoapDeluxe
- type: reaction
Blood:
amount: 10
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: SoapHomemade
- type: reaction
Stimulants:
amount: 5
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: SoapSyndie
- type: reaction
Amatoxin:
amount: 1
effects:
- - !type:CreateEntityReactionEffect
+ - !type:SpawnEntity
entity: SoapOmega