Turn ReagentEffects into generic EntityEffects (#28168)

* Oh the possibilities

* Merge fixes

* Forgot to remote LavaSystem oops

* Changed EntityEffectArgs to EntityEffectBaseArgs and EntityEffectReagentArgs

* Throw exception for unimplemented effectargs

* Remove Json and overrideable datafields

* Fix test issues

* Actually fix the compiling issue

* Fix comments and remove EntityEffectArgs (no longer used, replaced with EntityEffectBaseArgs)
This commit is contained in:
SlamBamActionman
2024-06-30 05:43:43 +02:00
committed by GitHub
parent 250109f0b4
commit b9fa941ca6
127 changed files with 2199 additions and 1978 deletions

View File

@@ -1,6 +1,6 @@
using Content.Server.Body.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Chemistry.ReactionEffects;
using Content.Server.EntityEffects.Effects;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Popups;

View File

@@ -6,6 +6,7 @@ using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
@@ -193,8 +194,7 @@ namespace Content.Server.Body.Systems
}
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
var args = new ReagentEffectArgs(actualEntity, ent, solution, proto, mostToRemove,
EntityManager, null, scale);
var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale);
// do all effects, if conditions apply
foreach (var effect in entry.Effects)

View File

@@ -3,8 +3,8 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Chat.Systems;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Chemistry.ReagentEffectConditions;
using Content.Server.Chemistry.ReagentEffects;
using Content.Server.EntityEffects.EffectConditions;
using Content.Server.EntityEffects.Effects;
using Content.Shared.Alert;
using Content.Shared.Atmos;
using Content.Shared.Body.Components;
@@ -13,6 +13,7 @@ using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
@@ -261,7 +262,7 @@ public sealed class RespiratorSystem : EntitySystem
// TODO generalize condition checks
// this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture
// Applying actual reaction effects require a full ReagentEffectArgs struct.
bool CanMetabolize(ReagentEffect effect)
bool CanMetabolize(EntityEffect effect)
{
if (effect.Conditions == null)
return true;

View File

@@ -1,7 +1,7 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
@@ -80,7 +80,7 @@ public partial struct SeedChemQuantity
// TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component.
[Virtual, DataDefinition]
[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(ReagentEffect), typeof(MutationSystem))]
[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffect), typeof(MutationSystem))]
public partial class SeedData
{
#region Tracking

View File

@@ -1,7 +1,7 @@
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Forensics;

View File

@@ -1,85 +0,0 @@
using Content.Server.Fluids.EntitySystems;
using Content.Shared.Audio;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReactionEffects
{
/// <summary>
/// Basically smoke and foam reactions.
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed partial class AreaReactionEffect : ReagentEffect
{
/// <summary>
/// How many seconds will the effect stay, counting after fully spreading.
/// </summary>
[DataField("duration")] private 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>))]
private string _prototypeId = default!;
/// <summary>
/// Sound that will get played when this reaction effect occurs.
/// </summary>
[DataField("sound", required: true)] private 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;
public override void Effect(ReagentEffectArgs args)
{
if (args.Source == null)
return;
var spreadAmount = (int) Math.Max(0, Math.Ceiling((args.Quantity / OverflowThreshold).Float()));
var splitSolution = args.Source.SplitSolution(args.Source.Volume);
var transform = args.EntityManager.GetComponent<TransformComponent>(args.SolutionEntity);
var mapManager = IoCManager.Resolve<IMapManager>();
var mapSys = args.EntityManager.System<MapSystem>();
var sys = args.EntityManager.System<TransformSystem>();
var mapCoords = sys.GetMapCoordinates(args.SolutionEntity, xform: transform);
if (!mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
!mapSys.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef) ||
tileRef.Tile.IsSpace())
{
return;
}
var coords = mapSys.MapToGrid(gridUid, mapCoords);
var ent = args.EntityManager.SpawnEntity(_prototypeId, coords.SnapToGrid());
var smoke = args.EntityManager.System<SmokeSystem>();
smoke.StartSmoke(ent, splitSolution, _duration, spreadAmount);
var audio = args.EntityManager.System<SharedAudioSystem>();
audio.PlayPvs(_sound, args.SolutionEntity, AudioHelpers.WithVariation(0.125f));
}
}
}

View File

@@ -1,73 +0,0 @@
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Chemistry.Reagent;
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.Server.Chemistry.ReactionEffects
{
[DataDefinition]
public sealed partial class ExplosionReactionEffect : ReagentEffect
{
/// <summary>
/// The type of explosion. Determines damage types and tile break chance scaling.
/// </summary>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
[JsonIgnore]
public string ExplosionType = default!;
/// <summary>
/// The max intensity the explosion can have at a given tile. Places an upper limit of damage and tile break
/// chance.
/// </summary>
[DataField]
[JsonIgnore]
public float MaxIntensity = 5;
/// <summary>
/// How quickly intensity drops off as you move away from the epicenter
/// </summary>
[DataField]
[JsonIgnore]
public float IntensitySlope = 1;
/// <summary>
/// The maximum total intensity that this chemical reaction can achieve. Basically here to prevent people
/// from creating a nuke by collecting enough potassium and water.
/// </summary>
/// <remarks>
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles.
/// </remarks>
[DataField]
[JsonIgnore]
public float MaxTotalIntensity = 100;
/// <summary>
/// The intensity of the explosion per unit reaction.
/// </summary>
[DataField]
[JsonIgnore]
public float IntensityPerUnit = 1;
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;
public override void Effect(ReagentEffectArgs args)
{
var intensity = MathF.Min((float) args.Quantity * IntensityPerUnit, MaxTotalIntensity);
args.EntityManager.System<ExplosionSystem>()
.QueueExplosion(
args.SolutionEntity,
ExplosionType,
intensity,
IntensitySlope,
MaxIntensity);
}
}
}

View File

@@ -1,121 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReactionEffects
{
/// <summary>
/// Sets the temperature of the solution involved with the reaction to a new value.
/// </summary>
[DataDefinition]
public sealed partial class SetSolutionTemperatureEffect : ReagentEffect
{
/// <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(ReagentEffectArgs args)
{
var solution = args.Source;
if (solution == null)
return;
solution.Temperature = _temperature;
}
}
/// <summary>
/// Adjusts the temperature of the solution involved in the reaction.
/// </summary>
[DataDefinition]
public sealed partial class AdjustSolutionTemperatureEffect : ReagentEffect
{
/// <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(ReagentEffectArgs args)
{
var solution = args.Source;
if (solution == null || solution.Volume == 0)
return;
var deltaT = _scaled ? _delta * (float) args.Quantity : _delta;
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
}
}
/// <summary>
/// Adjusts the thermal energy of the solution involved in the reaction.
/// </summary>
public sealed partial class AdjustSolutionThermalEnergyEffect : ReagentEffect
{
/// <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(ReagentEffectArgs args)
{
var solution = args.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) args.Quantity
: _delta / heatCap;
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
}
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));
}
}

View File

@@ -1,36 +0,0 @@
using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
/// <summary>
/// Requires the solution entity to be above or below a certain temperature.
/// Used for things like cryoxadone and pyroxadone.
/// </summary>
public sealed partial class Temperature : ReagentEffectCondition
{
[DataField]
public float Min = 0;
[DataField]
public float Max = float.PositiveInfinity;
public override bool Condition(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp))
{
if (temp.CurrentTemperature > Min && temp.CurrentTemperature < Max)
return true;
}
return false;
}
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));
}
}
}

View File

@@ -1,49 +0,0 @@
using System.Linq;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Localizations;
using Robust.Shared.Prototypes;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.IoC;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
public sealed partial class JobCondition : ReagentEffectCondition
{
[DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
public override bool Condition(ReagentEffectArgs args)
{
args.EntityManager.TryGetComponent<MindContainerComponent>(args.SolutionEntity, out var mindContainer);
if (mindContainer != null && mindContainer.Mind != null)
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
{
foreach (var jobId in Job)
{
if (prototype.ID == jobId)
{
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)));
}
}
}

View File

@@ -1,30 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
public sealed partial class MobStateCondition : ReagentEffectCondition
{
[DataField]
public MobState Mobstate = MobState.Alive;
public override bool Condition(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, 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));
}
}
}

View File

@@ -1,48 +0,0 @@
using Content.Server.Body.Components;
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
/// <summary>
/// Requires that the metabolizing organ is or is not tagged with a certain MetabolizerType
/// </summary>
public sealed partial class OrganType : ReagentEffectCondition
{
[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 bool Condition(ReagentEffectArgs args)
{
if (args.OrganEntity == null)
return false;
return Condition(args.OrganEntity.Value, args.EntityManager);
}
public bool Condition(Entity<MetabolizerComponent?> metabolizer, IEntityManager entMan)
{
metabolizer.Comp ??= entMan.GetComponentOrNull<MetabolizerComponent>(metabolizer.Owner);
if (metabolizer.Comp != null
&& metabolizer.Comp.MetabolizerTypes != null
&& metabolizer.Comp.MetabolizerTypes.Contains(Type))
return ShouldHave;
return !ShouldHave;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
("name", prototype.Index<MetabolizerTypePrototype>(Type).LocalizedName),
("shouldhave", ShouldHave));
}
}
}

View File

@@ -1,51 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
/// <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 : ReagentEffectCondition
{
[DataField]
public FixedPoint2 Min = FixedPoint2.Zero;
[DataField]
public FixedPoint2 Max = FixedPoint2.MaxValue;
// TODO use ReagentId
[DataField]
public string? Reagent;
public override bool Condition(ReagentEffectArgs args)
{
var reagent = Reagent ?? args.Reagent?.ID;
if (reagent == null)
return true; // No condition to apply.
var quant = FixedPoint2.Zero;
if (args.Source != null)
quant = args.Source.GetTotalPrototypeQuantity(reagent);
return quant >= Min && quant <= Max;
}
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()));
}
}
}

View File

@@ -1,36 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
/// <summary>
/// Requires the solution to be above or below a certain temperature.
/// Used for things like explosives.
/// </summary>
public sealed partial class SolutionTemperature : ReagentEffectCondition
{
[DataField]
public float Min = 0.0f;
[DataField]
public float Max = float.PositiveInfinity;
public override bool Condition(ReagentEffectArgs args)
{
if (args.Source == null)
return false;
if (args.Source.Temperature < Min)
return false;
if (args.Source.Temperature > Max)
return false;
return true;
}
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));
}
}
}

View File

@@ -1,35 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
public sealed partial class TotalDamage : ReagentEffectCondition
{
[DataField]
public FixedPoint2 Max = FixedPoint2.MaxValue;
[DataField]
public FixedPoint2 Min = FixedPoint2.Zero;
public override bool Condition(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out DamageableComponent? damage))
{
var total = damage.TotalDamage;
if (total > Min && total < Max)
return true;
}
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()));
}
}
}

View File

@@ -1,35 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffectConditions
{
public sealed partial class Hunger : ReagentEffectCondition
{
[DataField]
public float Max = float.PositiveInfinity;
[DataField]
public float Min = 0;
public override bool Condition(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out HungerComponent? hunger))
{
var total = hunger.CurrentHunger;
if (total > Min && total < Max)
return true;
}
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));
}
}
}

View File

@@ -1,31 +0,0 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
[UsedImplicitly]
public sealed partial class AddToSolutionReaction : ReagentEffect
{
[DataField("solution")]
private string _solution = "reagents";
public override void Effect(ReagentEffectArgs args)
{
if (args.Reagent == null)
return;
// TODO see if this is correct
var solutionContainerSystem = args.EntityManager.System<SolutionContainerSystem>();
if (!solutionContainerSystem.TryGetSolution(args.SolutionEntity, _solution, out var solutionContainer))
return;
if (solutionContainerSystem.TryAddReagent(solutionContainer.Value, args.Reagent.ID, args.Quantity, out var accepted))
args.Source?.RemoveReagent(args.Reagent.ID, accepted);
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-add-to-solution-reaction", ("chance", Probability));
}
}

View File

@@ -1,33 +0,0 @@
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReactionEffects
{
/// <summary>
/// Basically smoke and foam reactions.
/// </summary>
[UsedImplicitly]
public sealed partial class ChemCleanBloodstream : ReagentEffect
{
[DataField]
public float CleanseRate = 3.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
{
if (args.Source == null || args.Reagent == null)
return;
var cleanseRate = CleanseRate;
cleanseRate *= args.Scale;
var bloodstreamSys = args.EntityManager.System<BloodstreamSystem>();
bloodstreamSys.FlushChemicals(args.SolutionEntity, args.Reagent.ID, cleanseRate);
}
}
}

View File

@@ -1,32 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Eye.Blinding;
using Content.Shared.Eye.Blinding.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Heal or apply eye damage
/// </summary>
[UsedImplicitly]
public sealed partial class ChemHealEyeDamage : ReagentEffect
{
/// <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(ReagentEffectArgs args)
{
if (args.Scale != 1f) // huh?
return;
args.EntityManager.EntitySysManager.GetEntitySystem<BlindableSystem>().AdjustEyeDamage(args.SolutionEntity, Amount);
}
}
}

View File

@@ -1,31 +0,0 @@
using Content.Server.Electrocution;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
public sealed partial class Electrocute : ReagentEffect
{
[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(ReagentEffectArgs args)
{
args.EntityManager.System<ElectrocutionSystem>().TryDoElectrocution(args.SolutionEntity, null,
Math.Max((args.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
if (args.Reagent != null)
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
[UsedImplicitly]
public sealed partial class ExtinguishReaction : ReagentEffect
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-extinguish-reaction", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable)) return;
var flammableSystem = args.EntityManager.System<FlammableSystem>();
flammableSystem.Extinguish(args.SolutionEntity, flammable);
flammableSystem.AdjustFireStacks(args.SolutionEntity, -1.5f * (float) args.Quantity, flammable);
}
}
}

View File

@@ -1,34 +0,0 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
[UsedImplicitly]
public sealed partial class FlammableReaction : ReagentEffect
{
[DataField]
public float Multiplier = 0.05f;
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;
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out FlammableComponent? flammable))
return;
args.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.SolutionEntity, args.Quantity.Float() * Multiplier, flammable);
if (args.Reagent != null)
args.Source?.RemoveReagent(args.Reagent.ID, args.Quantity);
}
}
}

View File

@@ -1,30 +0,0 @@
using Content.Server.Body.Components;
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
public sealed partial class ModifyLungGas : ReagentEffect
{
[DataField("ratios", required: true)]
private 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;
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
return;
foreach (var (gas, ratio) in _ratios)
{
var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier;
if (quantity < 0)
quantity = Math.Max(quantity, -lung.Air[(int)gas]);
lung.Air.AdjustMoles(gas, quantity);
}
}
}

View File

@@ -1,75 +0,0 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Movement.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <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 : ReagentEffect
{
/// <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) when metabolized.
/// </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(ReagentEffectArgs args)
{
var status = args.EntityManager.EnsureComponent<MovespeedModifierMetabolismComponent>(args.SolutionEntity);
// 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;
statusLifetime *= args.Scale;
IncreaseTimer(status, statusLifetime);
if (modified)
args.EntityManager.System<MovementSpeedModifierSystem>().RefreshMovementSpeedModifiers(args.SolutionEntity);
}
public void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time)
{
var gameTiming = IoCManager.Resolve<IGameTiming>();
var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds);
status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time);
status.Dirty();
}
}
}

View File

@@ -1,25 +0,0 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
public sealed partial class Oxygenate : ReagentEffect
{
[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;
public override void Effect(ReagentEffectArgs args)
{
if (args.EntityManager.TryGetComponent<RespiratorComponent>(args.SolutionEntity, out var resp))
{
var respSys = args.EntityManager.System<RespiratorSystem>();
respSys.UpdateSaturation(args.SolutionEntity, args.Quantity.Float() * Factor, resp);
}
}
}

View File

@@ -1,62 +0,0 @@
using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[ImplicitDataDefinitionForInheritors]
public abstract partial class PlantAdjustAttribute : ReagentEffect
{
[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;
/// <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>
public bool CanMetabolize(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent,
IEntityManager entityManager,
bool mustHaveAlivePlant = true)
{
plantHolderComponent = null;
if (!entityManager.TryGetComponent(plantHolder, out plantHolderComponent)
|| mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead))
return false;
return 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));
}
}
}

View File

@@ -1,22 +0,0 @@
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
public sealed partial class PlantAdjustHealth : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolderComp.Health += Amount;
plantHolder.CheckHealth(args.SolutionEntity, plantHolderComp);
}
}
}

View File

@@ -1,18 +0,0 @@
using Content.Shared.Chemistry.Reagent;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.MutationLevel += Amount * plantHolderComp.MutationMod;
}
}
}

View File

@@ -1,19 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.MutationMod += Amount;
}
}
}

View File

@@ -1,22 +0,0 @@
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.AdjustNutrient(args.SolutionEntity, Amount, plantHolderComp);
}
}
}

View File

@@ -1,20 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAdjustPests : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.PestLevel += Amount;
}
}
}

View File

@@ -1,20 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAdjustToxins : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-toxins";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.Toxins += Amount;
}
}
}

View File

@@ -1,22 +0,0 @@
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAdjustWater : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-water";
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.AdjustWater(args.SolutionEntity, Amount, plantHolderComp);
}
}
}

View File

@@ -1,20 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-weeds";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.WeedLevel += Amount;
}
}
}

View File

@@ -1,22 +0,0 @@
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
public sealed partial class PlantAffectGrowth : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth";
public override void Effect(ReagentEffectArgs args)
{
if (!CanMetabolize(args.SolutionEntity, out var plantHolderComp, args.EntityManager))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.AffectGrowth(args.SolutionEntity, (int) Amount, plantHolderComp);
}
}
}

View File

@@ -1,33 +0,0 @@
using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class PlantCryoxadone : ReagentEffect
{
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead)
return;
var deviation = 0;
var seed = plantHolderComp.Seed;
var random = IoCManager.Resolve<IRobustRandom>();
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.SkipAging++;
plantHolderComp.ForceUpdate = true;
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability));
}
}

View File

@@ -1,41 +0,0 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class PlantDiethylamine : ReagentEffect
{
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead ||
plantHolderComp.Seed.Immutable)
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
var random = IoCManager.Resolve<IRobustRandom>();
if (random.Prob(0.1f))
{
plantHolder.EnsureUniqueSeed(args.SolutionEntity, plantHolderComp);
plantHolderComp.Seed.Lifespan++;
}
if (random.Prob(0.1f))
{
plantHolder.EnsureUniqueSeed(args.SolutionEntity, plantHolderComp);
plantHolderComp.Seed.Endurance++;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability));
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.Botany.Components;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class PlantPhalanximine : ReagentEffect
{
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead ||
plantHolderComp.Seed.Immutable)
return;
plantHolderComp.Seed.Viable = true;
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability));
}
}

View File

@@ -1,54 +0,0 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects.PlantMetabolism
{
[UsedImplicitly]
[DataDefinition]
public sealed partial class RobustHarvest : ReagentEffect
{
[DataField]
public int PotencyLimit = 50;
[DataField]
public int PotencyIncrease = 3;
[DataField]
public int PotencySeedlessThreshold = 30;
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead ||
plantHolderComp.Seed.Immutable)
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
var random = IoCManager.Resolve<IRobustRandom>();
if (plantHolderComp.Seed.Potency < PotencyLimit)
{
plantHolder.EnsureUniqueSeed(args.SolutionEntity, plantHolderComp);
plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + PotencyIncrease, PotencyLimit);
if (plantHolderComp.Seed.Potency > 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.SolutionEntity, plantHolderComp);
plantHolderComp.Seed.Yield--;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-robust-harvest", ("seedlesstreshold", PotencySeedlessThreshold), ("limit", PotencyLimit), ("increase", PotencyIncrease), ("chance", Probability));
}
}

View File

@@ -1,32 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <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 : ReagentEffect
{
private const float DefaultHydrationFactor = 3.0f;
/// How much thirst is satiated each metabolism 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(ReagentEffectArgs args)
{
var uid = args.SolutionEntity;
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));
}
}

View File

@@ -1,76 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.StatusEffect;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.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>
[UsedImplicitly]
public sealed partial class GenericStatusEffect : ReagentEffect
{
[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(ReagentEffectArgs args)
{
var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
var time = Time;
time *= args.Scale;
if (Type == StatusEffectMetabolismType.Add && Component != String.Empty)
{
statusSys.TryAddStatusEffect(args.SolutionEntity, Key, TimeSpan.FromSeconds(time), Refresh, Component);
}
else if (Type == StatusEffectMetabolismType.Remove)
{
statusSys.TryRemoveTime(args.SolutionEntity, Key, TimeSpan.FromSeconds(time));
}
else if (Type == StatusEffectMetabolismType.Set)
{
statusSys.TrySetTime(args.SolutionEntity, 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
{
Add,
Remove,
Set
}
}

View File

@@ -1,41 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Jittering;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects.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 : ReagentEffect
{
[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(ReagentEffectArgs args)
{
var time = Time;
time *= args.Scale;
args.EntityManager.EntitySysManager.GetEntitySystem<SharedJitteringSystem>()
.DoJitter(args.SolutionEntity, TimeSpan.FromSeconds(time), Refresh, Amplitude, Frequency);
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-jittering", ("chance", Probability));
}
}

View File

@@ -1,22 +0,0 @@
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Nutrition.Components;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
{
[UsedImplicitly]
public sealed partial class WashCreamPieReaction : ReagentEffect
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out CreamPiedComponent? creamPied)) return;
args.EntityManager.System<CreamPieSystem>().SetCreamPied(args.SolutionEntity, creamPied, false);
}
}
}

View File

@@ -0,0 +1,35 @@
using Content.Server.Temperature.Components;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.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 : EntityEffectCondition
{
[DataField]
public float Min = 0;
[DataField]
public float Max = float.PositiveInfinity;
public override bool Condition(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent(args.TargetEntity, out TemperatureComponent? temp))
{
if (temp.CurrentTemperature > Min && temp.CurrentTemperature < Max)
return true;
}
return false;
}
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));
}
}

View File

@@ -1,13 +1,13 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.Tag;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffectConditions;
namespace Content.Server.EntityEffects.EffectConditions;
[UsedImplicitly]
public sealed partial class HasTag : ReagentEffectCondition
public sealed partial class HasTag : EntityEffectCondition
{
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]
public string Tag = default!;
@@ -15,9 +15,9 @@ public sealed partial class HasTag : ReagentEffectCondition
[DataField]
public bool Invert = false;
public override bool Condition(ReagentEffectArgs args)
public override bool Condition(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<TagComponent>(args.SolutionEntity, out var tag))
if (args.EntityManager.TryGetComponent<TagComponent>(args.TargetEntity, out var tag))
return args.EntityManager.System<TagSystem>().HasTag(tag, Tag) ^ Invert;
return false;

View File

@@ -0,0 +1,49 @@
using System.Linq;
using Content.Shared.EntityEffects;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Localizations;
using Robust.Shared.Prototypes;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.IoC;
namespace Content.Server.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 != null && mindContainer.Mind != null)
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
{
foreach (var jobId in Job)
{
if (prototype.ID == jobId)
{
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)));
}
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.EntityEffects;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.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));
}
}

View File

@@ -0,0 +1,53 @@
using Content.Server.Body.Components;
using Content.Shared.Body.Prototypes;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.EntityEffects.EffectConditions;
/// <summary>
/// Requires that the metabolizing organ is or is not tagged with a certain MetabolizerType
/// </summary>
public sealed partial class OrganType : EntityEffectCondition
{
[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 bool Condition(EntityEffectBaseArgs args)
{
if (args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.OrganEntity == null)
return false;
return Condition(reagentArgs.OrganEntity.Value, reagentArgs.EntityManager);
}
// TODO: Someone needs to figure out how to do this for non-reagent effects.
throw new NotImplementedException();
}
public bool Condition(Entity<MetabolizerComponent?> metabolizer, IEntityManager entMan)
{
metabolizer.Comp ??= entMan.GetComponentOrNull<MetabolizerComponent>(metabolizer.Owner);
if (metabolizer.Comp != null
&& metabolizer.Comp.MetabolizerTypes != null
&& metabolizer.Comp.MetabolizerTypes.Contains(Type))
return ShouldHave;
return !ShouldHave;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
("name", prototype.Index<MetabolizerTypePrototype>(Type).LocalizedName),
("shouldhave", ShouldHave));
}
}

View File

@@ -0,0 +1,57 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.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()));
}
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.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)
{
if (reagentArgs.Source == null)
return false;
if (reagentArgs.Source.Temperature < Min)
return false;
if (reagentArgs.Source.Temperature > Max)
return false;
return true;
}
// 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));
}
}

View File

@@ -0,0 +1,34 @@
using Content.Shared.EntityEffects;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.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;
if (total > Min && total < Max)
return true;
}
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()));
}
}

View File

@@ -0,0 +1,34 @@
using Content.Shared.EntityEffects;
using Content.Shared.Nutrition.Components;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.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 = hunger.CurrentHunger;
if (total > Min && total < Max)
return true;
}
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));
}
}

View File

@@ -1,15 +1,16 @@
using Content.Server.Xenoarchaeology.XenoArtifacts;
using Content.Server.Xenoarchaeology.XenoArtifacts;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
using Content.Shared.EntityEffects;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class ActivateArtifact : ReagentEffect
public sealed partial class ActivateArtifact : EntityEffect
{
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var artifact = args.EntityManager.EntitySysManager.GetEntitySystem<ArtifactSystem>();
artifact.TryActivateArtifact(args.SolutionEntity);
artifact.TryActivateArtifact(args.TargetEntity);
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>

View File

@@ -0,0 +1,38 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects
{
[UsedImplicitly]
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<SolutionContainerSystem>();
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));
}
}

View File

@@ -1,11 +1,11 @@
using Content.Shared.Alert;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Alert;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class AdjustAlert : ReagentEffect
public sealed partial class AdjustAlert : EntityEffect
{
/// <summary>
/// The specific Alert that will be adjusted
@@ -34,15 +34,15 @@ public sealed partial class AdjustAlert : ReagentEffect
//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(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem<AlertsSystem>();
if (!args.EntityManager.HasComponent<AlertsComponent>(args.SolutionEntity))
if (!args.EntityManager.HasComponent<AlertsComponent>(args.TargetEntity))
return;
if (Clear && Time <= 0)
{
alertSys.ClearAlert(args.SolutionEntity, AlertType);
alertSys.ClearAlert(args.TargetEntity, AlertType);
}
else
{
@@ -52,7 +52,7 @@ public sealed partial class AdjustAlert : ReagentEffect
if ((ShowCooldown || Clear) && Time > 0)
cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
alertSys.ShowAlert(args.SolutionEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
alertSys.ShowAlert(args.TargetEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
}
}

View File

@@ -1,14 +1,15 @@
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
[UsedImplicitly]
public sealed partial class AdjustReagent : ReagentEffect
public sealed partial class AdjustReagent : EntityEffect
{
/// <summary>
/// The reagent ID to remove. Only one of this and <see cref="Group"/> should be active.
@@ -27,36 +28,43 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField(required: true)]
public FixedPoint2 Amount = default!;
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.Source == null)
return;
var amount = Amount;
amount *= args.Scale;
if (Reagent != null)
if (args is EntityEffectReagentArgs reagentArgs)
{
if (amount < 0 && args.Source.ContainsPrototype(Reagent))
args.Source.RemoveReagent(Reagent, -amount);
if (amount > 0)
args.Source.AddReagent(Reagent, amount);
}
else if (Group != null)
{
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
foreach (var quant in args.Source.Contents.ToArray())
if (reagentArgs.Source == null)
return;
var amount = Amount;
amount *= reagentArgs.Scale;
if (Reagent != null)
{
var proto = prototypeMan.Index<ReagentPrototype>(quant.Reagent.Prototype);
if (proto.Metabolisms != null && proto.Metabolisms.ContainsKey(Group))
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())
{
if (amount < 0)
args.Source.RemoveReagent(quant.Reagent, amount);
if (amount > 0)
args.Source.AddReagent(quant.Reagent, amount);
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)

View File

@@ -1,11 +1,11 @@
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
public sealed partial class AdjustTemperature : ReagentEffect
public sealed partial class AdjustTemperature : EntityEffect
{
[DataField]
public float Amount;
@@ -16,16 +16,19 @@ namespace Content.Server.Chemistry.ReagentEffects
("deltasign", MathF.Sign(Amount)),
("amount", MathF.Abs(Amount)));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent(args.SolutionEntity, out TemperatureComponent? temp))
if (args.EntityManager.TryGetComponent(args.TargetEntity, out TemperatureComponent? temp))
{
var sys = args.EntityManager.EntitySysManager.GetEntitySystem<TemperatureSystem>();
var amount = Amount;
amount *= args.Scale;
if (args is EntityEffectReagentArgs reagentArgs)
{
amount *= reagentArgs.Scale.Float();
}
sys.ChangeHeat(args.SolutionEntity, amount, true, temp);
sys.ChangeHeat(args.TargetEntity, amount, true, temp);
}
}
}

View File

@@ -0,0 +1,91 @@
using Content.Server.Fluids.EntitySystems;
using Content.Shared.Audio;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Basically smoke and foam reactions.
/// </summary>
[UsedImplicitly]
[DataDefinition]
public sealed partial class AreaReactionEffect : EntityEffect
{
/// <summary>
/// How many seconds will the effect stay, counting after fully spreading.
/// </summary>
[DataField("duration")] private 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>))]
private string _prototypeId = default!;
/// <summary>
/// Sound that will get played when this reaction effect occurs.
/// </summary>
[DataField("sound", required: true)] private 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;
public override void Effect(EntityEffectBaseArgs args)
{
if (args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.Source == null)
return;
var spreadAmount = (int) Math.Max(0, Math.Ceiling((reagentArgs.Quantity / OverflowThreshold).Float()));
var splitSolution = reagentArgs.Source.SplitSolution(reagentArgs.Source.Volume);
var transform = reagentArgs.EntityManager.GetComponent<TransformComponent>(reagentArgs.TargetEntity);
var mapManager = IoCManager.Resolve<IMapManager>();
var mapSys = reagentArgs.EntityManager.System<MapSystem>();
var sys = reagentArgs.EntityManager.System<TransformSystem>();
var mapCoords = sys.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform);
if (!mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
!mapSys.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef) ||
tileRef.Tile.IsSpace())
{
return;
}
var coords = mapSys.MapToGrid(gridUid, mapCoords);
var ent = reagentArgs.EntityManager.SpawnEntity(_prototypeId, coords.SnapToGrid());
var smoke = reagentArgs.EntityManager.System<SmokeSystem>();
smoke.StartSmoke(ent, splitSolution, _duration, spreadAmount);
var audio = reagentArgs.EntityManager.System<SharedAudioSystem>();
audio.PlayPvs(_sound, reagentArgs.TargetEntity, AudioHelpers.WithVariation(0.125f));
return;
}
// TODO: Someone needs to figure out how to do this for non-reagent effects.
throw new NotImplementedException();
}
}

View File

@@ -1,22 +1,21 @@
using Content.Server.Zombies;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Configuration;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class CauseZombieInfection : ReagentEffect
public sealed partial class CauseZombieInfection : EntityEffect
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-cause-zombie-infection", ("chance", Probability));
// Adds the Zombie Infection Components
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
entityManager.EnsureComponent<ZombifyOnDeathComponent>(args.SolutionEntity);
entityManager.EnsureComponent<PendingZombieComponent>(args.SolutionEntity);
entityManager.EnsureComponent<ZombifyOnDeathComponent>(args.TargetEntity);
entityManager.EnsureComponent<PendingZombieComponent>(args.TargetEntity);
}
}

View File

@@ -0,0 +1,40 @@
using Content.Server.Body.Systems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Basically smoke and foam reactions.
/// </summary>
[UsedImplicitly]
public sealed partial class ChemCleanBloodstream : EntityEffect
{
[DataField]
public float CleanseRate = 3.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
public override void Effect(EntityEffectBaseArgs args)
{
var cleanseRate = CleanseRate;
var bloodstreamSys = args.EntityManager.System<BloodstreamSystem>();
if (args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.Source == null || reagentArgs.Reagent == null)
return;
cleanseRate *= reagentArgs.Scale.Float();
bloodstreamSys.FlushChemicals(args.TargetEntity, reagentArgs.Reagent.ID, cleanseRate);
}
else
{
bloodstreamSys.FlushChemicals(args.TargetEntity, "", cleanseRate);
}
}
}

View File

@@ -0,0 +1,31 @@
using Content.Shared.EntityEffects;
using Content.Shared.Eye.Blinding.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Heal or apply eye damage
/// </summary>
[UsedImplicitly]
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);
}
}

View File

@@ -1,15 +1,15 @@
using Content.Server.Medical;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
/// <summary>
/// Forces you to vomit.
/// </summary>
[UsedImplicitly]
public sealed partial class ChemVomit : ReagentEffect
public sealed partial class ChemVomit : EntityEffect
{
/// How many units of thirst to add each time we vomit
[DataField]
@@ -21,14 +21,15 @@ namespace Content.Server.Chemistry.ReagentEffects
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.Scale != 1f)
return;
if (args is EntityEffectReagentArgs reagentArgs)
if (reagentArgs.Scale != 1f)
return;
var vomitSys = args.EntityManager.EntitySysManager.GetEntitySystem<VomitSystem>();
vomitSys.Vomit(args.SolutionEntity, ThirstAmount, HungerAmount);
vomitSys.Vomit(args.TargetEntity, ThirstAmount, HungerAmount);
}
}
}

View File

@@ -1,12 +1,11 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReactionEffects;
namespace Content.Server.EntityEffects.Effects;
[DataDefinition]
public sealed partial class CreateEntityReactionEffect : ReagentEffect
public sealed partial class CreateEntityReactionEffect : EntityEffect
{
/// <summary>
/// What entity to create.
@@ -26,15 +25,17 @@ public sealed partial class CreateEntityReactionEffect : ReagentEffect
("entname", IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
("amount", Number));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var transform = args.EntityManager.GetComponent<TransformComponent>(args.SolutionEntity);
var transform = args.EntityManager.GetComponent<TransformComponent>(args.TargetEntity);
var transformSystem = args.EntityManager.System<SharedTransformSystem>();
var quantity = Number * args.Quantity.Int();
var quantity = (int)Number;
if (args is EntityEffectReagentArgs reagentArgs)
quantity *= reagentArgs.Quantity.Int();
for (var i = 0; i < quantity; i++)
{
var uid = args.EntityManager.SpawnEntity(Entity, transformSystem.GetMapCoordinates(args.SolutionEntity, xform: transform));
var uid = args.EntityManager.SpawnEntity(Entity, transformSystem.GetMapCoordinates(args.TargetEntity, xform: transform));
transformSystem.AttachToGridOrMap(uid);
// TODO figure out how to properly spawn inside of containers

View File

@@ -1,12 +1,12 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class CreateGas : ReagentEffect
public sealed partial class CreateGas : EntityEffect
{
[DataField(required: true)]
public Gas Gas = default!;
@@ -31,15 +31,22 @@ public sealed partial class CreateGas : ReagentEffect
public override LogImpact LogImpact => LogImpact.High;
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var atmosSys = args.EntityManager.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
var tileMix = atmosSys.GetContainingMixture(args.SolutionEntity, false, true);
var tileMix = atmosSys.GetContainingMixture(args.TargetEntity, false, true);
if (tileMix != null)
{
tileMix.AdjustMoles(Gas, args.Quantity.Float() * Multiplier);
if (args is EntityEffectReagentArgs reagentArgs)
{
tileMix.AdjustMoles(Gas, reagentArgs.Quantity.Float() * Multiplier);
}
else
{
tileMix.AdjustMoles(Gas, Multiplier);
}
}
}
}

View File

@@ -1,12 +1,11 @@
using Content.Server.Zombies;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Configuration;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class CureZombieInfection : ReagentEffect
public sealed partial class CureZombieInfection : EntityEffect
{
[DataField]
public bool Innoculate;
@@ -20,18 +19,18 @@ public sealed partial class CureZombieInfection : ReagentEffect
}
// Removes the Zombie Infection Components
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
if (entityManager.HasComponent<IncurableZombieComponent>(args.SolutionEntity))
if (entityManager.HasComponent<IncurableZombieComponent>(args.TargetEntity))
return;
entityManager.RemoveComponent<ZombifyOnDeathComponent>(args.SolutionEntity);
entityManager.RemoveComponent<PendingZombieComponent>(args.SolutionEntity);
entityManager.RemoveComponent<ZombifyOnDeathComponent>(args.TargetEntity);
entityManager.RemoveComponent<PendingZombieComponent>(args.TargetEntity);
if (Innoculate)
{
entityManager.EnsureComponent<ZombieImmuneComponent>(args.SolutionEntity);
entityManager.EnsureComponent<ZombieImmuneComponent>(args.TargetEntity);
}
}
}

View File

@@ -1,10 +1,10 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Drunk;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class Drunk : ReagentEffect
public sealed partial class Drunk : EntityEffect
{
/// <summary>
/// BoozePower is how long each metabolism cycle will make the drunk effect last for.
@@ -21,13 +21,15 @@ public sealed partial class Drunk : ReagentEffect
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-drunk", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var boozePower = BoozePower;
boozePower *= args.Scale;
if (args is EntityEffectReagentArgs reagentArgs) {
boozePower *= reagentArgs.Scale.Float();
}
var drunkSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedDrunkSystem>();
drunkSys.TryApplyDrunkenness(args.SolutionEntity, boozePower, SlurSpeech);
drunkSys.TryApplyDrunkenness(args.TargetEntity, boozePower, SlurSpeech);
}
}

View File

@@ -0,0 +1,38 @@
using Content.Server.Electrocution;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.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<ElectrocutionSystem>().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<ElectrocutionSystem>().TryDoElectrocution(args.TargetEntity, null,
Math.Max(ElectrocuteDamageScale, 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
}
}
}

View File

@@ -1,17 +1,17 @@
using Content.Server.Chat.Systems;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits of the specified emote unless forced.
/// </summary>
[UsedImplicitly]
public sealed partial class Emote : ReagentEffect
public sealed partial class Emote : EntityEffect
{
[DataField("emote", customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
public string? EmoteId;
@@ -26,16 +26,16 @@ public sealed partial class Emote : ReagentEffect
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (EmoteId == null)
return;
var chatSys = args.EntityManager.System<ChatSystem>();
if (ShowInChat)
chatSys.TryEmoteWithChat(args.SolutionEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force);
chatSys.TryEmoteWithChat(args.TargetEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force);
else
chatSys.TryEmoteWithoutChat(args.SolutionEntity, EmoteId);
chatSys.TryEmoteWithoutChat(args.TargetEntity, EmoteId);
}
}

View File

@@ -1,16 +1,17 @@
using Content.Server.Emp;
using Content.Shared.EntityEffects;
using Content.Shared.Chemistry.Reagent;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReactionEffects;
namespace Content.Server.EntityEffects.Effects;
[DataDefinition]
public sealed partial class EmpReactionEffect : ReagentEffect
public sealed partial class EmpReactionEffect : EntityEffect
{
/// <summary>
/// Impulse range per unit of reagent
/// Impulse range per unit of quantity
/// </summary>
[DataField("rangePerUnit")]
public float EmpRangePerUnit = 0.5f;
@@ -36,14 +37,20 @@ public sealed partial class EmpReactionEffect : ReagentEffect
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var tSys = args.EntityManager.System<TransformSystem>();
var transform = args.EntityManager.GetComponent<TransformComponent>(args.SolutionEntity);
var range = MathF.Min((float) (args.Quantity*EmpRangePerUnit), EmpMaxRange);
var transform = args.EntityManager.GetComponent<TransformComponent>(args.TargetEntity);
var range = EmpRangePerUnit;
if (args is EntityEffectReagentArgs reagentArgs)
{
range = MathF.Min((float) (reagentArgs.Quantity * EmpRangePerUnit), EmpMaxRange);
}
args.EntityManager.System<EmpSystem>()
.EmpPulse(tSys.GetMapCoordinates(args.SolutionEntity, xform: transform),
.EmpPulse(tSys.GetMapCoordinates(args.TargetEntity, xform: transform),
range,
EnergyConsumption,
DisableDuration);

View File

@@ -0,0 +1,77 @@
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Content.Shared.Explosion;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using System.Text.Json.Serialization;
namespace Content.Server.EntityEffects.Effects;
[DataDefinition]
public sealed partial class ExplosionReactionEffect : EntityEffect
{
/// <summary>
/// The type of explosion. Determines damage types and tile break chance scaling.
/// </summary>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
[JsonIgnore]
public string ExplosionType = default!;
/// <summary>
/// The max intensity the explosion can have at a given tile. Places an upper limit of damage and tile break
/// chance.
/// </summary>
[DataField]
[JsonIgnore]
public float MaxIntensity = 5;
/// <summary>
/// How quickly intensity drops off as you move away from the epicenter
/// </summary>
[DataField]
[JsonIgnore]
public float IntensitySlope = 1;
/// <summary>
/// The maximum total intensity that this chemical reaction can achieve. Basically here to prevent people
/// from creating a nuke by collecting enough potassium and water.
/// </summary>
/// <remarks>
/// A slope of 1 and MaxTotalIntensity of 100 corresponds to a radius of around 4.5 tiles.
/// </remarks>
[DataField]
[JsonIgnore]
public float MaxTotalIntensity = 100;
/// <summary>
/// The intensity of the explosion per unit reaction.
/// </summary>
[DataField]
[JsonIgnore]
public float IntensityPerUnit = 1;
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;
public override void Effect(EntityEffectBaseArgs args)
{
var intensity = IntensityPerUnit;
if (args is EntityEffectReagentArgs reagentArgs)
{
intensity = MathF.Min((float) reagentArgs.Quantity * IntensityPerUnit, MaxTotalIntensity);
}
args.EntityManager.System<ExplosionSystem>()
.QueueExplosion(
args.TargetEntity,
ExplosionType,
intensity,
IntensitySlope,
MaxIntensity);
}
}

View File

@@ -0,0 +1,36 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects
{
[UsedImplicitly]
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)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out FlammableComponent? flammable)) return;
var flammableSystem = args.EntityManager.System<FlammableSystem>();
flammableSystem.Extinguish(args.TargetEntity, flammable);
if (args is EntityEffectReagentArgs reagentArgs)
{
flammableSystem.AdjustFireStacks(reagentArgs.TargetEntity, FireStacksAdjustment * (float) reagentArgs.Quantity, flammable);
} else
{
flammableSystem.AdjustFireStacks(args.TargetEntity, FireStacksAdjustment, flammable);
}
}
}
}

View File

@@ -0,0 +1,45 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects
{
[UsedImplicitly]
public sealed partial class FlammableReaction : EntityEffect
{
[DataField]
public float Multiplier = 0.05f;
[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;
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out FlammableComponent? flammable))
return;
var multiplier = flammable.FireStacks == 0f ? Multiplier : MultiplierOnExisting;
var quantity = 1f;
if (args is EntityEffectReagentArgs reagentArgs)
{
quantity = reagentArgs.Quantity.Float();
reagentArgs.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.TargetEntity, quantity * multiplier, flammable);
if (reagentArgs.Reagent != null)
reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
} else
{
args.EntityManager.System<FlammableSystem>().AdjustFireStacks(args.TargetEntity, multiplier, flammable);
}
}
}
}

View File

@@ -1,6 +1,6 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Content.Shared.Localizations;
using JetBrains.Annotations;
@@ -8,16 +8,16 @@ using Robust.Shared.Prototypes;
using System.Linq;
using System.Text.Json.Serialization;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
/// <summary>
/// Default metabolism for medicine reagents.
/// Default metabolism used for medicine reagents.
/// </summary>
[UsedImplicitly]
public sealed partial class HealthChange : ReagentEffect
public sealed partial class HealthChange : EntityEffect
{
/// <summary>
/// Damage to apply every metabolism cycle. Damage Ignores resistances.
/// Damage to apply every cycle. Damage Ignores resistances.
/// </summary>
[DataField(required: true)]
[JsonPropertyName("damage")]
@@ -26,6 +26,7 @@ namespace Content.Server.Chemistry.ReagentEffects
/// <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")]
@@ -110,13 +111,17 @@ namespace Content.Server.Chemistry.ReagentEffects
("healsordeals", healsordeals));
}
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var scale = ScaleByQuantity ? args.Quantity : FixedPoint2.New(1);
scale *= args.Scale;
var scale = FixedPoint2.New(1);
if (args is EntityEffectReagentArgs reagentArgs)
{
scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale;
}
args.EntityManager.System<DamageableSystem>().TryChangeDamage(
args.SolutionEntity,
args.TargetEntity,
Damage * scale,
IgnoreResistances,
interruptsDoAfters: false);

View File

@@ -1,14 +1,14 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Ignites a mob.
/// </summary>
public sealed partial class Ignite : ReagentEffect
public sealed partial class Ignite : EntityEffect
{
public override bool ShouldLog => true;
@@ -17,9 +17,15 @@ public sealed partial class Ignite : ReagentEffect
public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var flamSys = args.EntityManager.System<FlammableSystem>();
flamSys.Ignite(args.SolutionEntity, args.OrganEntity ?? args.SolutionEntity);
if (args is EntityEffectReagentArgs reagentArgs)
{
flamSys.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity);
} else
{
flamSys.Ignite(args.TargetEntity, args.TargetEntity);
}
}
}

View File

@@ -1,20 +1,20 @@
using Content.Server.Ghost.Roles.Components;
using Content.Server.Speech.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.Mind.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class MakeSentient : ReagentEffect
public sealed partial class MakeSentient : EntityEffect
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
var uid = args.SolutionEntity;
var uid = 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

View File

@@ -1,11 +1,11 @@
using Content.Server.Body.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class ModifyBleedAmount : ReagentEffect
public sealed partial class ModifyBleedAmount : EntityEffect
{
[DataField]
public bool Scaled = false;
@@ -17,15 +17,19 @@ public sealed partial class ModifyBleedAmount : ReagentEffect
=> Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability),
("deltasign", MathF.Sign(Amount)));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.SolutionEntity, out var blood))
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.TargetEntity, out var blood))
{
var sys = args.EntityManager.System<BloodstreamSystem>();
var amt = Scaled ? Amount * args.Quantity.Float() : Amount;
amt *= args.Scale;
var amt = Amount;
if (args is EntityEffectReagentArgs reagentArgs) {
if (Scaled)
amt *= reagentArgs.Quantity.Float();
amt *= reagentArgs.Scale.Float();
}
sys.TryModifyBleedAmount(args.SolutionEntity, amt, blood);
sys.TryModifyBleedAmount(args.TargetEntity, amt, blood);
}
}
}

View File

@@ -1,12 +1,12 @@
using Content.Server.Body.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class ModifyBloodLevel : ReagentEffect
public sealed partial class ModifyBloodLevel : EntityEffect
{
[DataField]
public bool Scaled = false;
@@ -18,15 +18,20 @@ public sealed partial class ModifyBloodLevel : ReagentEffect
=> Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability),
("deltasign", MathF.Sign(Amount.Float())));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.SolutionEntity, out var blood))
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.TargetEntity, out var blood))
{
var sys = args.EntityManager.System<BloodstreamSystem>();
var amt = Scaled ? Amount * args.Quantity : Amount;
amt *= args.Scale;
var amt = Amount;
if (args is EntityEffectReagentArgs reagentArgs)
{
if (Scaled)
amt *= reagentArgs.Quantity;
amt *= reagentArgs.Scale;
}
sys.TryModifyBloodLevel(args.SolutionEntity, amt, blood);
sys.TryModifyBloodLevel(args.TargetEntity, amt, blood);
}
}
}

View File

@@ -0,0 +1,49 @@
using Content.Server.Body.Components;
using Content.Shared.Atmos;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Content.Shared.EntityEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class ModifyLungGas : EntityEffect
{
[DataField("ratios", required: true)]
private 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;
public override void Effect(EntityEffectBaseArgs args)
{
LungComponent? lung;
float amount = 1f;
if (args is EntityEffectReagentArgs reagentArgs)
{
if (!args.EntityManager.TryGetComponent<LungComponent>(reagentArgs.OrganEntity, out var organLung))
return;
lung = organLung;
amount = reagentArgs.Quantity.Float();
}
else
{
if (!args.EntityManager.TryGetComponent<LungComponent>(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 _ratios)
{
var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier;
if (quantity < 0)
quantity = Math.Max(quantity, -lung.Air[(int) gas]);
lung.Air.AdjustMoles(gas, quantity);
}
}
}
}

View File

@@ -0,0 +1,78 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.EntityEffects;
using Content.Shared.Movement.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.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);
if (modified)
args.EntityManager.System<MovementSpeedModifierSystem>().RefreshMovementSpeedModifiers(args.TargetEntity);
}
public void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time)
{
var gameTiming = IoCManager.Resolve<IGameTiming>();
var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds);
status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time);
status.Dirty();
}
}

View File

@@ -0,0 +1,32 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class Oxygenate : EntityEffect
{
[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;
public override void Effect(EntityEffectBaseArgs args)
{
var multiplier = 1f;
if (args is EntityEffectReagentArgs reagentArgs)
{
multiplier = reagentArgs.Quantity.Float();
}
if (args.EntityManager.TryGetComponent<RespiratorComponent>(args.TargetEntity, out var resp))
{
var respSys = args.EntityManager.System<RespiratorSystem>();
respSys.UpdateSaturation(args.TargetEntity, multiplier * Factor, resp);
}
}
}

View File

@@ -1,10 +1,10 @@
using Content.Server.Stunnable;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class Paralyze : ReagentEffect
public sealed partial class Paralyze : EntityEffect
{
[DataField] public double ParalyzeTime = 2;
@@ -18,12 +18,16 @@ public sealed partial class Paralyze : ReagentEffect
("chance", Probability),
("time", ParalyzeTime));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var paralyzeTime = ParalyzeTime;
paralyzeTime *= args.Scale;
args.EntityManager.System<StunSystem>().TryParalyze(args.SolutionEntity, TimeSpan.FromSeconds(paralyzeTime), Refresh);
if (args is EntityEffectReagentArgs reagentArgs)
{
paralyzeTime *= (double)reagentArgs.Scale;
}
args.EntityManager.System<StunSystem>().TryParalyze(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime), Refresh);
}
}

View File

@@ -0,0 +1,61 @@
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[ImplicitDataDefinitionForInheritors]
public abstract partial class PlantAdjustAttribute : EntityEffect
{
[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;
/// <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>
public bool CanMetabolize(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent,
IEntityManager entityManager,
bool mustHaveAlivePlant = true)
{
plantHolderComponent = null;
if (!entityManager.TryGetComponent(plantHolder, out plantHolderComponent)
|| mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead))
return false;
return 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));
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustHealth : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolderComp.Health += Amount;
plantHolder.CheckHealth(args.TargetEntity, plantHolderComp);
}
}

View File

@@ -0,0 +1,16 @@
using Content.Shared.EntityEffects;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.MutationLevel += Amount * plantHolderComp.MutationMod;
}
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.MutationMod += Amount;
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.AdjustNutrient(args.TargetEntity, Amount, plantHolderComp);
}
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAdjustPests : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.PestLevel += Amount;
}
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAdjustToxins : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-toxins";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.Toxins += Amount;
}
}

View File

@@ -0,0 +1,22 @@
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAdjustWater : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-water";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager, mustHaveAlivePlant: false))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.AdjustWater(args.TargetEntity, Amount, plantHolderComp);
}
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-weeds";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
plantHolderComp.WeedLevel += Amount;
}
}

View File

@@ -0,0 +1,22 @@
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
public sealed partial class PlantAffectGrowth : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.AffectGrowth(args.TargetEntity, (int) Amount, plantHolderComp);
}
}

View File

@@ -0,0 +1,32 @@
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
[DataDefinition]
public sealed partial class PlantCryoxadone : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead)
return;
var deviation = 0;
var seed = plantHolderComp.Seed;
var random = IoCManager.Resolve<IRobustRandom>();
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.SkipAging++;
plantHolderComp.ForceUpdate = true;
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability));
}

View File

@@ -0,0 +1,41 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
[DataDefinition]
public sealed partial class PlantDiethylamine : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead ||
plantHolderComp.Seed.Immutable)
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
var random = IoCManager.Resolve<IRobustRandom>();
if (random.Prob(0.1f))
{
plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Lifespan++;
}
if (random.Prob(0.1f))
{
plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Endurance++;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability));
}

View File

@@ -0,0 +1,23 @@
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
[DataDefinition]
public sealed partial class PlantPhalanximine : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead ||
plantHolderComp.Seed.Immutable)
return;
plantHolderComp.Seed.Viable = true;
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability));
}

View File

@@ -0,0 +1,53 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
[UsedImplicitly]
[DataDefinition]
public sealed partial class RobustHarvest : EntityEffect
{
[DataField]
public int PotencyLimit = 50;
[DataField]
public int PotencyIncrease = 3;
[DataField]
public int PotencySeedlessThreshold = 30;
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out PlantHolderComponent? plantHolderComp)
|| plantHolderComp.Seed == null || plantHolderComp.Dead ||
plantHolderComp.Seed.Immutable)
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
var random = IoCManager.Resolve<IRobustRandom>();
if (plantHolderComp.Seed.Potency < PotencyLimit)
{
plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + PotencyIncrease, PotencyLimit);
if (plantHolderComp.Seed.Potency > 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.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Yield--;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-robust-harvest", ("seedlesstreshold", PotencySeedlessThreshold), ("limit", PotencyLimit), ("increase", PotencyIncrease), ("chance", Probability));
}

View File

@@ -1,13 +1,14 @@
using Content.Server.Polymorph.Components;
using Content.Server.Polymorph.Systems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.Polymorph;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Content.Shared.EntityEffects;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class Polymorph : ReagentEffect
public sealed partial class Polymorph : EntityEffect
{
/// <summary>
/// What polymorph prototype is used on effect
@@ -20,10 +21,10 @@ public sealed partial class Polymorph : ReagentEffect
("chance", Probability), ("entityname",
prototype.Index<EntityPrototype>(prototype.Index<PolymorphPrototype>(PolymorphPrototype).Configuration.Entity).Name));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
var uid = args.SolutionEntity;
var uid = args.TargetEntity;
var polySystem = entityManager.System<PolymorphSystem>();
// Make it into a prototype

View File

@@ -1,12 +1,11 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.Popups;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
public sealed partial class PopupMessage : ReagentEffect
public sealed partial class PopupMessage : EntityEffect
{
[DataField(required: true)]
public string[] Messages = default!;
@@ -21,20 +20,30 @@ namespace Content.Server.Chemistry.ReagentEffects
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
public override void Effect(ReagentEffectArgs args)
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.SolutionEntity),
("organ", args.OrganEntity.GetValueOrDefault()),
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.SolutionEntity, args.SolutionEntity, VisualType);
popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.TargetEntity, args.TargetEntity, VisualType);
else if (Type == PopupRecipients.Pvs)
popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.SolutionEntity, VisualType);
popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.TargetEntity, VisualType);
}
}

View File

@@ -1,15 +1,16 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Content.Server.Atmos.Rotting;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
/// <summary>
/// Reduces the rotting accumulator on the patient, making them revivable.
/// </summary>
[UsedImplicitly]
public sealed partial class ReduceRotting : ReagentEffect
public sealed partial class ReduceRotting : EntityEffect
{
[DataField("seconds")]
public double RottingAmount = 10;
@@ -18,14 +19,17 @@ namespace Content.Server.Chemistry.ReagentEffects
=> Loc.GetString("reagent-effect-guidebook-reduce-rotting",
("chance", Probability),
("time", RottingAmount));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.Scale != 1f)
return;
if (args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.Scale != 1f)
return;
}
var rottingSys = args.EntityManager.EntitySysManager.GetEntitySystem<RottingSystem>();
rottingSys.ReduceAccumulator(args.SolutionEntity, TimeSpan.FromSeconds(RottingAmount));
rottingSys.ReduceAccumulator(args.TargetEntity, TimeSpan.FromSeconds(RottingAmount));
}
}
}

View File

@@ -1,15 +1,16 @@
using Content.Server.Traits.Assorted;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Reset narcolepsy timer
/// </summary>
[UsedImplicitly]
public sealed partial class ResetNarcolepsy : ReagentEffect
public sealed partial class ResetNarcolepsy : EntityEffect
{
/// <summary>
/// The # of seconds the effect resets the narcolepsy timer to
@@ -20,11 +21,12 @@ public sealed partial class ResetNarcolepsy : ReagentEffect
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-reset-narcolepsy", ("chance", Probability));
public override void Effect(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
if (args.Scale != 1f)
return;
if (args is EntityEffectReagentArgs reagentArgs)
if (reagentArgs.Scale != 1f)
return;
args.EntityManager.EntitySysManager.GetEntitySystem<NarcolepsySystem>().AdjustNarcolepsyTimer(args.SolutionEntity, TimerReset);
args.EntityManager.EntitySysManager.GetEntitySystem<NarcolepsySystem>().AdjustNarcolepsyTimer(args.TargetEntity, TimerReset);
}
}

View File

@@ -1,31 +1,40 @@
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.ReagentEffects
namespace Content.Server.EntityEffects.Effects
{
/// <summary>
/// Attempts to find a HungerComponent on the target,
/// and to update it's hunger values.
/// </summary>
public sealed partial class SatiateHunger : ReagentEffect
public sealed partial class SatiateHunger : EntityEffect
{
private const float DefaultNutritionFactor = 3.0f;
/// <summary>
/// How much hunger is satiated when 1u of the reagent is metabolized
/// 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(ReagentEffectArgs args)
public override void Effect(EntityEffectBaseArgs args)
{
var entman = args.EntityManager;
if (!entman.TryGetComponent(args.SolutionEntity, out HungerComponent? hunger))
if (!entman.TryGetComponent(args.TargetEntity, out HungerComponent? hunger))
return;
entman.System<HungerSystem>().ModifyHunger(args.SolutionEntity, NutritionFactor * (float) args.Quantity, hunger);
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)

Some files were not shown because too many files have changed in this diff Show More