Move entity effects definitions to shared (#35614)

* Move entity effects to shared

* relocate spawning to server

* Generic version of EntityEffect for just raising event.

* genericise everything

* oops forgot to push you

* some condensation

* finish rebas

* unwhite the space

* oops forgot nuke

* bad rebase fix

* useless annotations begone

---------

Co-authored-by: EmoGarbage404 <retron404@gmail.com>
This commit is contained in:
pathetic meowmeow
2025-05-23 12:32:22 -04:00
committed by GitHub
parent 7ba81173bd
commit bf41de18aa
132 changed files with 1843 additions and 1961 deletions

View File

@@ -1,5 +1,5 @@
using Content.Server.Body.Components;
using Content.Server.EntityEffects.Effects;
using Content.Shared.EntityEffects.Effects;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Alert;

View File

@@ -2,8 +2,9 @@ using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Chat.Systems;
using Content.Server.EntityEffects.EffectConditions;
using Content.Server.EntityEffects.Effects;
using Content.Server.EntityEffects;
using Content.Shared.EntityEffects.EffectConditions;
using Content.Shared.EntityEffects.Effects;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Alert;
using Content.Shared.Atmos;
@@ -35,6 +36,7 @@ public sealed class RespiratorSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly EntityEffectSystem _entityEffect = default!;
private static readonly ProtoId<MetabolismGroupPrototype> GasId = new("Gas");
@@ -283,7 +285,7 @@ public sealed class RespiratorSystem : EntitySystem
foreach (var cond in effect.Conditions)
{
if (cond is OrganType organ && !organ.Condition(lung, EntityManager))
if (cond is OrganType organ && !_entityEffect.OrganCondition(organ, lung))
return false;
}

View File

@@ -10,6 +10,8 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Utility;
using Content.Server.EntityEffects;
namespace Content.Server.Botany;
[Prototype]
@@ -82,7 +84,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(EntityEffect), typeof(MutationSystem))]
[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffectSystem), typeof(MutationSystem))]
public partial class SeedData
{
#region Tracking

View File

@@ -1,33 +0,0 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
/// <summary>
/// Condition for if the entity is successfully breathing.
/// </summary>
public sealed partial class Breathing : EntityEffectCondition
{
/// <summary>
/// If true, the entity must not have trouble breathing to pass.
/// </summary>
[DataField]
public bool IsBreathing = true;
public override bool Condition(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out RespiratorComponent? respiratorComp))
return !IsBreathing; // They do not breathe.
var breathingState = args.EntityManager.System<RespiratorSystem>().IsBreathing((args.TargetEntity, respiratorComp));
return IsBreathing == breathingState;
}
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-breathing",
("isBreathing", IsBreathing));
}
}

View File

@@ -1,53 +0,0 @@
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

@@ -1,35 +0,0 @@
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects
{
public sealed partial class AdjustTemperature : EntityEffect
{
[DataField]
public float Amount;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-adjust-temperature",
("chance", Probability),
("deltasign", MathF.Sign(Amount)),
("amount", MathF.Abs(Amount)));
public override void Effect(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent(args.TargetEntity, out TemperatureComponent? temp))
{
var sys = args.EntityManager.EntitySysManager.GetEntitySystem<TemperatureSystem>();
var amount = Amount;
if (args is EntityEffectReagentArgs reagentArgs)
{
amount *= reagentArgs.Scale.Float();
}
sys.ChangeHeat(args.TargetEntity, amount, true, temp);
}
}
}
}

View File

@@ -1,95 +0,0 @@
using Content.Server.Fluids.EntitySystems;
using Content.Server.Spreader;
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 spreaderSys = args.EntityManager.System<SpreaderSystem>();
var sys = args.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))
{
return;
}
if (spreaderSys.RequiresFloorToSpread(_prototypeId) && 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, AudioParams.Default.WithVariation(0.25f));
return;
}
// TODO: Someone needs to figure out how to do this for non-reagent effects.
throw new NotImplementedException();
}
}

View File

@@ -1,21 +0,0 @@
using Content.Server.Zombies;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
namespace Content.Server.EntityEffects.Effects;
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(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
entityManager.EnsureComponent<ZombifyOnDeathComponent>(args.TargetEntity);
entityManager.EnsureComponent<PendingZombieComponent>(args.TargetEntity);
}
}

View File

@@ -1,40 +0,0 @@
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

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

View File

@@ -1,50 +0,0 @@
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.EntityEffects.Effects;
[DataDefinition]
public sealed partial class CreateEntityReactionEffect : EntityEffect
{
/// <summary>
/// What entity to create.
/// </summary>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Entity = default!;
/// <summary>
/// How many entities to create per unit reaction.
/// </summary>
[DataField]
public uint Number = 1;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-create-entity-reaction-effect",
("chance", Probability),
("entname", IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
("amount", Number));
public override void Effect(EntityEffectBaseArgs args)
{
var transform = args.EntityManager.GetComponent<TransformComponent>(args.TargetEntity);
var transformSystem = args.EntityManager.System<SharedTransformSystem>();
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.TargetEntity, xform: transform));
transformSystem.AttachToGridOrMap(uid);
// TODO figure out how to properly spawn inside of containers
// e.g. cheese:
// if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user.
// if reaction happens in a backpack, should insert cheese into backpack.
// --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players).
// if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that.
// presumably having cheese materialize inside of your blood would have "disadvantages".
}
}
}

View File

@@ -1,52 +0,0 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class CreateGas : EntityEffect
{
[DataField(required: true)]
public Gas Gas = default!;
/// <summary>
/// For each unit consumed, how many moles of gas should be created?
/// </summary>
[DataField]
public float Multiplier = 3f;
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var atmos = entSys.GetEntitySystem<AtmosphereSystem>();
var gasProto = atmos.GetGas(Gas);
return Loc.GetString("reagent-effect-guidebook-create-gas",
("chance", Probability),
("moles", Multiplier),
("gas", gasProto.Name));
}
public override LogImpact LogImpact => LogImpact.High;
public override void Effect(EntityEffectBaseArgs args)
{
var atmosSys = args.EntityManager.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
var tileMix = atmosSys.GetContainingMixture(args.TargetEntity, false, true);
if (tileMix != null)
{
if (args is EntityEffectReagentArgs reagentArgs)
{
tileMix.AdjustMoles(Gas, reagentArgs.Quantity.Float() * Multiplier);
}
else
{
tileMix.AdjustMoles(Gas, Multiplier);
}
}
}
}

View File

@@ -1,37 +0,0 @@
using Content.Server.Zombies;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class CureZombieInfection : EntityEffect
{
[DataField]
public bool Innoculate;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if(Innoculate)
return Loc.GetString("reagent-effect-guidebook-innoculate-zombie-infection", ("chance", Probability));
return Loc.GetString("reagent-effect-guidebook-cure-zombie-infection", ("chance", Probability));
}
// Removes the Zombie Infection Components
public override void Effect(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
if (entityManager.HasComponent<IncurableZombieComponent>(args.TargetEntity))
return;
entityManager.RemoveComponent<ZombifyOnDeathComponent>(args.TargetEntity);
entityManager.RemoveComponent<PendingZombieComponent>(args.TargetEntity);
if (Innoculate)
{
entityManager.EnsureComponent<ZombieImmuneComponent>(args.TargetEntity);
}
}
}

View File

@@ -1,58 +0,0 @@
using Content.Server.Emp;
using Content.Shared.EntityEffects;
using Content.Shared.Chemistry.Reagent;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
[DataDefinition]
public sealed partial class EmpReactionEffect : EntityEffect
{
/// <summary>
/// Impulse range per unit of quantity
/// </summary>
[DataField("rangePerUnit")]
public float EmpRangePerUnit = 0.5f;
/// <summary>
/// Maximum impulse range
/// </summary>
[DataField("maxRange")]
public float EmpMaxRange = 10;
/// <summary>
/// How much energy will be drain from sources
/// </summary>
[DataField]
public float EnergyConsumption = 12500;
/// <summary>
/// Amount of time entities will be disabled
/// </summary>
[DataField("duration")]
public float DisableDuration = 15;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability));
public override void Effect(EntityEffectBaseArgs args)
{
var tSys = args.EntityManager.System<TransformSystem>();
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.TargetEntity, xform: transform),
range,
EnergyConsumption,
DisableDuration);
}
}

View File

@@ -1,48 +0,0 @@
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;
// The fire stack multiplier if fire stacks already exist on target, only works if 0 or greater
[DataField]
public float MultiplierOnExisting = -1f;
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-flammable-reaction", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out FlammableComponent? flammable))
return;
// Sets the multiplier for FireStacks to MultiplierOnExisting is 0 or greater and target already has FireStacks
var multiplier = flammable.FireStacks != 0f && MultiplierOnExisting >= 0 ? MultiplierOnExisting : Multiplier;
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,82 +0,0 @@
using Content.Shared.EntityEffects;
using Content.Server.Flash;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
[DataDefinition]
public sealed partial class FlashReactionEffect : EntityEffect
{
/// <summary>
/// Flash range per unit of reagent.
/// </summary>
[DataField]
public float RangePerUnit = 0.2f;
/// <summary>
/// Maximum flash range.
/// </summary>
[DataField]
public float MaxRange = 10f;
/// <summary>
/// How much to entities are slowed down.
/// </summary>
[DataField]
public float SlowTo = 0.5f;
/// <summary>
/// The time entities will be flashed in seconds.
/// The default is chosen to be better than the hand flash so it is worth using it for grenades etc.
/// </summary>
[DataField]
public float Duration = 4f;
/// <summary>
/// The prototype ID used for the visual effect.
/// </summary>
[DataField]
public EntProtoId? FlashEffectPrototype = "ReactionFlash";
/// <summary>
/// The sound the flash creates.
/// </summary>
[DataField]
public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg");
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-flash-reaction-effect", ("chance", Probability));
public override void Effect(EntityEffectBaseArgs args)
{
var transform = args.EntityManager.GetComponent<TransformComponent>(args.TargetEntity);
var transformSystem = args.EntityManager.System<SharedTransformSystem>();
var range = 1f;
if (args is EntityEffectReagentArgs reagentArgs)
range = MathF.Min((float)(reagentArgs.Quantity * RangePerUnit), MaxRange);
args.EntityManager.System<FlashSystem>().FlashArea(
args.TargetEntity,
null,
range,
Duration * 1000,
slowTo: SlowTo,
sound: Sound);
if (FlashEffectPrototype == null)
return;
var uid = args.EntityManager.SpawnEntity(FlashEffectPrototype, transformSystem.GetMapCoordinates(transform));
transformSystem.AttachToGridOrMap(uid);
if (!args.EntityManager.TryGetComponent<PointLightComponent>(uid, out var pointLightComp))
return;
var pointLightSystem = args.EntityManager.System<SharedPointLightSystem>();
// PointLights with a radius lower than 1.1 are too small to be visible, so this is hardcoded
pointLightSystem.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp);
}
}

View File

@@ -1,36 +0,0 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Ignites a mob.
/// </summary>
public sealed partial class Ignite : EntityEffect
{
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-ignite", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.Medium;
public override void Effect(EntityEffectBaseArgs args)
{
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out FlammableComponent? flammable))
return;
var flamSys = args.EntityManager.System<FlammableSystem>();
if (args is EntityEffectReagentArgs reagentArgs)
{
flamSys.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable);
}
else
{
flamSys.Ignite(args.TargetEntity, args.TargetEntity, flammable: flammable);
}
}
}

View File

@@ -1,44 +0,0 @@
using Content.Server.Ghost.Roles.Components;
using Content.Server.Speech.Components;
using Content.Shared.EntityEffects;
using Content.Shared.Mind.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
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(EntityEffectBaseArgs args)
{
var entityManager = args.EntityManager;
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
// We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
entityManager.RemoveComponent<ReplacementAccentComponent>(uid);
entityManager.RemoveComponent<MonkeyAccentComponent>(uid);
// Stops from adding a ghost role to things like people who already have a mind
if (entityManager.TryGetComponent<MindContainerComponent>(uid, out var mindContainer) && mindContainer.HasMind)
{
return;
}
// Don't add a ghost role to things that already have ghost roles
if (entityManager.TryGetComponent(uid, out GhostRoleComponent? ghostRole))
{
return;
}
ghostRole = entityManager.AddComponent<GhostRoleComponent>(uid);
entityManager.EnsureComponent<GhostTakeoverAvailableComponent>(uid);
var entityData = entityManager.GetComponent<MetaDataComponent>(uid);
ghostRole.RoleName = entityData.EntityName;
ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
}
}

View File

@@ -1,35 +0,0 @@
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 ModifyBleedAmount : EntityEffect
{
[DataField]
public bool Scaled = false;
[DataField]
public float Amount = -1.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability),
("deltasign", MathF.Sign(Amount)));
public override void Effect(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.TargetEntity, out var blood))
{
var sys = args.EntityManager.System<BloodstreamSystem>();
var amt = Amount;
if (args is EntityEffectReagentArgs reagentArgs) {
if (Scaled)
amt *= reagentArgs.Quantity.Float();
amt *= reagentArgs.Scale.Float();
}
sys.TryModifyBleedAmount(args.TargetEntity, amt, blood);
}
}
}

View File

@@ -1,37 +0,0 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
public sealed partial class ModifyBloodLevel : EntityEffect
{
[DataField]
public bool Scaled = false;
[DataField]
public FixedPoint2 Amount = 1.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability),
("deltasign", MathF.Sign(Amount.Float())));
public override void Effect(EntityEffectBaseArgs args)
{
if (args.EntityManager.TryGetComponent<BloodstreamComponent>(args.TargetEntity, out var blood))
{
var sys = args.EntityManager.System<BloodstreamSystem>();
var amt = Amount;
if (args is EntityEffectReagentArgs reagentArgs)
{
if (Scaled)
amt *= reagentArgs.Quantity;
amt *= reagentArgs.Scale;
}
sys.TryModifyBloodLevel(args.TargetEntity, amt, blood);
}
}
}

View File

@@ -1,48 +0,0 @@
using Content.Server.Body.Components;
using Content.Shared.Atmos;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
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

@@ -1,32 +0,0 @@
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,142 +0,0 @@
using Content.Server.Botany;
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]
public sealed partial class PlantChangeStat : EntityEffect
{
[DataField]
public string TargetValue;
[DataField]
public float MinValue;
[DataField]
public float MaxValue;
[DataField]
public int Steps;
public override void Effect(EntityEffectBaseArgs args)
{
var plantHolder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
if (plantHolder == null || plantHolder.Seed == null)
return;
var member = plantHolder.Seed.GetType().GetField(TargetValue);
var mutationSys = args.EntityManager.System<MutationSystem>();
if (member == null)
{
mutationSys.Log.Error(this.GetType().Name + " Error: Member " + TargetValue + " not found on " + plantHolder.GetType().Name + ". Did you misspell it?");
return;
}
var currentValObj = member.GetValue(plantHolder.Seed);
if (currentValObj == null)
return;
if (member.FieldType == typeof(float))
{
var floatVal = (float)currentValObj;
MutateFloat(ref floatVal, MinValue, MaxValue, Steps);
member.SetValue(plantHolder.Seed, floatVal);
}
else if (member.FieldType == typeof(int))
{
var intVal = (int)currentValObj;
MutateInt(ref intVal, (int)MinValue, (int)MaxValue, Steps);
member.SetValue(plantHolder.Seed, intVal);
}
else if (member.FieldType == typeof(bool))
{
var boolVal = (bool)currentValObj;
boolVal = !boolVal;
member.SetValue(plantHolder.Seed, boolVal);
}
}
// Mutate reference 'val' between 'min' and 'max' by pretending the value
// is representable by a thermometer code with 'bits' number of bits and
// randomly flipping some of them.
private void MutateFloat(ref float val, float min, float max, int bits)
{
if (min == max)
{
val = min;
return;
}
// Starting number of bits that are high, between 0 and bits.
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
// In other words, it tends to go to the middle.
float probIncrease = 1 - (float)valInt / bits;
int valIntMutated;
if (Random(probIncrease))
{
valIntMutated = valInt + 1;
}
else
{
valIntMutated = valInt - 1;
}
// Set value based on mutated thermometer code.
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
val = valMutated;
}
private void MutateInt(ref int val, int min, int max, int bits)
{
if (min == max)
{
val = min;
return;
}
// Starting number of bits that are high, between 0 and bits.
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
// In other words, it tends to go to the middle.
float probIncrease = 1 - (float)valInt / bits;
int valMutated;
if (Random(probIncrease))
{
valMutated = val + 1;
}
else
{
valMutated = val - 1;
}
valMutated = Math.Clamp(valMutated, min, max);
val = valMutated;
}
private bool Random(float odds)
{
var random = IoCManager.Resolve<IRobustRandom>();
return random.Prob(odds);
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
throw new NotImplementedException();
}
}

View File

@@ -1,21 +0,0 @@
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

@@ -1,16 +0,0 @@
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

@@ -1,19 +0,0 @@
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

@@ -1,21 +0,0 @@
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

@@ -1,20 +0,0 @@
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

@@ -1,28 +0,0 @@
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
/// <summary>
/// Handles increase or decrease of plant potency.
/// </summary>
public sealed partial class PlantAdjustPotency : PlantAdjustAttribute
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency";
public override void Effect(EntityEffectBaseArgs args)
{
if (!CanMetabolize(args.TargetEntity, out var plantHolderComp, args.EntityManager))
return;
if (plantHolderComp.Seed == null)
return;
var plantHolder = args.EntityManager.System<PlantHolderSystem>();
plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + Amount, 1);
}
}

View File

@@ -1,20 +0,0 @@
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

@@ -1,22 +0,0 @@
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

@@ -1,19 +0,0 @@
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

@@ -1,22 +0,0 @@
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

@@ -1,33 +0,0 @@
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.LastProduce = plantHolderComp.Age;
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,42 +0,0 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using Content.Shared.Popups;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
/// <summary>
/// Handles removal of seeds on a plant.
/// </summary>
public sealed partial class PlantDestroySeeds : 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 popupSystem = args.EntityManager.System<SharedPopupSystem>();
if (plantHolderComp.Seed.Seedless == false)
{
plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp);
popupSystem.PopupEntity(
Loc.GetString("botany-plant-seedsdestroyed"),
args.TargetEntity,
PopupType.SmallCaution
);
plantHolderComp.Seed.Seedless = true;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-plant-seeds-remove", ("chance", Probability));
}

View File

@@ -1,41 +0,0 @@
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

@@ -1,23 +0,0 @@
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

@@ -1,38 +0,0 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Shared.EntityEffects;
using Content.Shared.Popups;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects.PlantMetabolism;
/// <summary>
/// Handles restoral of seeds on a plant.
/// </summary>
public sealed partial class PlantRestoreSeeds : 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 popupSystem = args.EntityManager.System<SharedPopupSystem>();
if (plantHolderComp.Seed.Seedless)
{
plantHolder.EnsureUniqueSeed(args.TargetEntity, plantHolderComp);
popupSystem.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.TargetEntity);
plantHolderComp.Seed.Seedless = false;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
Loc.GetString("reagent-effect-guidebook-plant-seeds-add", ("chance", Probability));
}

View File

@@ -1,53 +0,0 @@
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,55 +0,0 @@
using Content.Server.Botany;
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using Content.Shared.Random;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// changes the chemicals available in a plant's produce
/// </summary>
public sealed partial class PlantMutateChemicals : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
if (plantholder.Seed == null)
return;
var random = IoCManager.Resolve<IRobustRandom>();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var chemicals = plantholder.Seed.Chemicals;
var randomChems = prototypeManager.Index<WeightedRandomFillSolutionPrototype>("RandomPickBotanyReagent").Fills;
// Add a random amount of a random chemical to this set of chemicals
if (randomChems != null)
{
var pick = random.Pick<RandomFillSolution>(randomChems);
var chemicalId = random.Pick(pick.Reagents);
var amount = random.Next(1, (int)pick.Quantity);
var seedChemQuantity = new SeedChemQuantity();
if (chemicals.ContainsKey(chemicalId))
{
seedChemQuantity.Min = chemicals[chemicalId].Min;
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
}
else
{
seedChemQuantity.Min = 1;
seedChemQuantity.Max = 1 + amount;
seedChemQuantity.Inherent = false;
}
var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
seedChemQuantity.PotencyDivisor = potencyDivisor;
chemicals[chemicalId] = seedChemQuantity;
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return "TODO";
}
}

View File

@@ -1,87 +0,0 @@
using Content.Server.Botany.Components;
using Content.Shared.Atmos;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Linq;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// changes the gases that a plant or produce create.
/// </summary>
public sealed partial class PlantMutateExudeGasses : EntityEffect
{
[DataField]
public float MinValue = 0.01f;
[DataField]
public float MaxValue = 0.5f;
public override void Effect(EntityEffectBaseArgs args)
{
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
if (plantholder.Seed == null)
return;
var random = IoCManager.Resolve<IRobustRandom>();
var gasses = plantholder.Seed.ExudeGasses;
// Add a random amount of a random gas to this gas dictionary
float amount = random.NextFloat(MinValue, MaxValue);
Gas gas = random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
if (gasses.ContainsKey(gas))
{
gasses[gas] += amount;
}
else
{
gasses.Add(gas, amount);
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return "TODO";
}
}
/// <summary>
/// changes the gases that a plant or produce consumes.
/// </summary>
public sealed partial class PlantMutateConsumeGasses : EntityEffect
{
[DataField]
public float MinValue = 0.01f;
[DataField]
public float MaxValue = 0.5f;
public override void Effect(EntityEffectBaseArgs args)
{
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
if (plantholder.Seed == null)
return;
var random = IoCManager.Resolve<IRobustRandom>();
var gasses = plantholder.Seed.ConsumeGasses;
// Add a random amount of a random gas to this gas dictionary
float amount = random.NextFloat(MinValue, MaxValue);
Gas gas = random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
if (gasses.ContainsKey(gas))
{
gasses[gas] += amount;
}
else
{
gasses.Add(gas, amount);
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return "TODO";
}
}

View File

@@ -1,30 +0,0 @@
using Content.Server.Botany;
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Upgrades a plant's harvest type.
/// </summary>
public sealed partial class PlantMutateHarvest : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
if (plantholder.Seed == null)
return;
if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat)
plantholder.Seed.HarvestRepeat = HarvestType.Repeat;
else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat)
plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest;
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return "TODO";
}
}

View File

@@ -1,43 +0,0 @@
using Content.Server.Botany;
using Content.Server.Botany.Components;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Serilog;
namespace Content.Server.EntityEffects.Effects;
/// <summary>
/// Changes a plant into one of the species its able to mutate into.
/// </summary>
public sealed partial class PlantSpeciesChange : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var plantholder = args.EntityManager.GetComponent<PlantHolderComponent>(args.TargetEntity);
if (plantholder.Seed == null)
return;
if (plantholder.Seed.MutationPrototypes.Count == 0)
return;
var random = IoCManager.Resolve<IRobustRandom>();
var targetProto = random.Pick(plantholder.Seed.MutationPrototypes);
prototypeManager.TryIndex(targetProto, out SeedPrototype? protoSeed);
if (protoSeed == null)
{
Log.Error($"Seed prototype could not be found: {targetProto}!");
return;
}
plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed);
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
return "TODO";
}
}

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
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.EntityEffects.Effects
{
/// <summary>
/// Attempts to find a HungerComponent on the target,
/// and to update it's hunger values.
/// </summary>
public sealed partial class SatiateHunger : EntityEffect
{
private const float DefaultNutritionFactor = 3.0f;
/// <summary>
/// How much hunger is satiated.
/// Is multiplied by quantity if used with EntityEffectReagentArgs.
/// </summary>
[DataField("factor")] public float NutritionFactor { get; set; } = DefaultNutritionFactor;
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
public override void Effect(EntityEffectBaseArgs args)
{
var entman = args.EntityManager;
if (!entman.TryGetComponent(args.TargetEntity, out HungerComponent? hunger))
return;
if (args is EntityEffectReagentArgs reagentArgs)
{
entman.System<HungerSystem>().ModifyHunger(reagentArgs.TargetEntity, NutritionFactor * (float) reagentArgs.Quantity, hunger);
}
else
{
entman.System<HungerSystem>().ModifyHunger(args.TargetEntity, NutritionFactor, hunger);
}
}
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", NutritionFactor / DefaultNutritionFactor));
}
}

View File

@@ -0,0 +1,975 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
using Content.Server.Botany;
using Content.Server.Chat.Systems;
using Content.Server.Emp;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Flash;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Medical;
using Content.Server.Polymorph.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Speech.Components;
using Content.Server.Spreader;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Server.Traits.Assorted;
using Content.Server.Zombies;
using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.EntityEffects.EffectConditions;
using Content.Shared.EntityEffects.Effects.PlantMetabolism;
using Content.Shared.EntityEffects.Effects.StatusEffects;
using Content.Shared.EntityEffects.Effects;
using Content.Shared.EntityEffects;
using Content.Shared.Maps;
using Content.Shared.Mind.Components;
using Content.Shared.Popups;
using Content.Shared.Random;
using Content.Shared.Zombies;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using TemperatureCondition = Content.Shared.EntityEffects.EffectConditions.Temperature; // disambiguate the namespace
using PolymorphEffect = Content.Shared.EntityEffects.Effects.Polymorph;
namespace Content.Server.EntityEffects;
public sealed class EntityEffectSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly EmpSystem _emp = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly FlashSystem _flash = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly MutationSystem _mutation = default!;
[Dependency] private readonly NarcolepsySystem _narcolepsy = default!;
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
[Dependency] private readonly PolymorphSystem _polymorph = default!;
[Dependency] private readonly RespiratorSystem _respirator = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SmokeSystem _smoke = default!;
[Dependency] private readonly SpreaderSystem _spreader = default!;
[Dependency] private readonly TemperatureSystem _temperature = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
[Dependency] private readonly VomitSystem _vomit = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CheckEntityEffectConditionEvent<TemperatureCondition>>(OnCheckTemperature);
SubscribeLocalEvent<CheckEntityEffectConditionEvent<Breathing>>(OnCheckBreathing);
SubscribeLocalEvent<CheckEntityEffectConditionEvent<OrganType>>(OnCheckOrganType);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustHealth>>(OnExecutePlantAdjustHealth);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationLevel>>(OnExecutePlantAdjustMutationLevel);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationMod>>(OnExecutePlantAdjustMutationMod);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPests>>(OnExecutePlantAdjustPests);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPotency>>(OnExecutePlantAdjustPotency);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustToxins>>(OnExecutePlantAdjustToxins);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWater>>(OnExecutePlantAdjustWater);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWeeds>>(OnExecutePlantAdjustWeeds);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAffectGrowth>>(OnExecutePlantAffectGrowth);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantChangeStat>>(OnExecutePlantChangeStat);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantCryoxadone>>(OnExecutePlantCryoxadone);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDestroySeeds>>(OnExecutePlantDestroySeeds);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDiethylamine>>(OnExecutePlantDiethylamine);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantPhalanximine>>(OnExecutePlantPhalanximine);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantRestoreSeeds>>(OnExecutePlantRestoreSeeds);
SubscribeLocalEvent<ExecuteEntityEffectEvent<AdjustTemperature>>(OnExecuteAdjustTemperature);
SubscribeLocalEvent<ExecuteEntityEffectEvent<AreaReactionEffect>>(OnExecuteAreaReactionEffect);
SubscribeLocalEvent<ExecuteEntityEffectEvent<CauseZombieInfection>>(OnExecuteCauseZombieInfection);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemCleanBloodstream>>(OnExecuteChemCleanBloodstream);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemVomit>>(OnExecuteChemVomit);
SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateEntityReactionEffect>>(OnExecuteCreateEntityReactionEffect);
SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateGas>>(OnExecuteCreateGas);
SubscribeLocalEvent<ExecuteEntityEffectEvent<CureZombieInfection>>(OnExecuteCureZombieInfection);
SubscribeLocalEvent<ExecuteEntityEffectEvent<Emote>>(OnExecuteEmote);
SubscribeLocalEvent<ExecuteEntityEffectEvent<EmpReactionEffect>>(OnExecuteEmpReactionEffect);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ExplosionReactionEffect>>(OnExecuteExplosionReactionEffect);
SubscribeLocalEvent<ExecuteEntityEffectEvent<FlammableReaction>>(OnExecuteFlammableReaction);
SubscribeLocalEvent<ExecuteEntityEffectEvent<FlashReactionEffect>>(OnExecuteFlashReactionEffect);
SubscribeLocalEvent<ExecuteEntityEffectEvent<Ignite>>(OnExecuteIgnite);
SubscribeLocalEvent<ExecuteEntityEffectEvent<MakeSentient>>(OnExecuteMakeSentient);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBleedAmount>>(OnExecuteModifyBleedAmount);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBloodLevel>>(OnExecuteModifyBloodLevel);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyLungGas>>(OnExecuteModifyLungGas);
SubscribeLocalEvent<ExecuteEntityEffectEvent<Oxygenate>>(OnExecuteOxygenate);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateChemicals>>(OnExecutePlantMutateChemicals);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateConsumeGasses>>(OnExecutePlantMutateConsumeGasses);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateExudeGasses>>(OnExecutePlantMutateExudeGasses);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateHarvest>>(OnExecutePlantMutateHarvest);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantSpeciesChange>>(OnExecutePlantSpeciesChange);
SubscribeLocalEvent<ExecuteEntityEffectEvent<PolymorphEffect>>(OnExecutePolymorph);
SubscribeLocalEvent<ExecuteEntityEffectEvent<ResetNarcolepsy>>(OnExecuteResetNarcolepsy);
}
private void OnCheckTemperature(ref CheckEntityEffectConditionEvent<TemperatureCondition> args)
{
args.Result = false;
if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
{
if (temp.CurrentTemperature > args.Condition.Min && temp.CurrentTemperature < args.Condition.Max)
args.Result = true;
}
}
private void OnCheckBreathing(ref CheckEntityEffectConditionEvent<Breathing> args)
{
if (!TryComp(args.Args.TargetEntity, out RespiratorComponent? respiratorComp))
{
args.Result = !args.Condition.IsBreathing;
return;
}
var breathingState = _respirator.IsBreathing((args.Args.TargetEntity, respiratorComp));
args.Result = args.Condition.IsBreathing == breathingState;
}
private void OnCheckOrganType(ref CheckEntityEffectConditionEvent<OrganType> args)
{
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.OrganEntity == null)
{
args.Result = false;
return;
}
args.Result = OrganCondition(args.Condition, reagentArgs.OrganEntity.Value);
return;
}
// TODO: Someone needs to figure out how to do this for non-reagent effects.
throw new NotImplementedException();
}
public bool OrganCondition(OrganType condition, Entity<MetabolizerComponent?> metabolizer)
{
metabolizer.Comp ??= EntityManager.GetComponentOrNull<MetabolizerComponent>(metabolizer.Owner);
if (metabolizer.Comp != null
&& metabolizer.Comp.MetabolizerTypes != null
&& metabolizer.Comp.MetabolizerTypes.Contains(condition.Type))
return condition.ShouldHave;
return !condition.ShouldHave;
}
/// <summary>
/// Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default.
/// </summary>
/// <param name="plantHolder">The entity holding the plant</param>
/// <param name="plantHolderComponent">The plant holder component</param>
/// <param name="entityManager">The entity manager</param>
/// <param name="mustHaveAlivePlant">Whether to check if it has an alive plant or not</param>
/// <returns></returns>
private bool CanMetabolizePlant(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent,
bool mustHaveAlivePlant = true, bool mustHaveMutableSeed = false)
{
plantHolderComponent = null;
if (!TryComp(plantHolder, out plantHolderComponent))
return false;
if (mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead))
return false;
if (mustHaveMutableSeed && (plantHolderComponent.Seed == null || plantHolderComponent.Seed.Immutable))
return false;
return true;
}
private void OnExecutePlantAdjustHealth(ref ExecuteEntityEffectEvent<PlantAdjustHealth> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
plantHolderComp.Health += args.Effect.Amount;
_plantHolder.CheckHealth(args.Args.TargetEntity, plantHolderComp);
}
private void OnExecutePlantAdjustMutationLevel(ref ExecuteEntityEffectEvent<PlantAdjustMutationLevel> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
plantHolderComp.MutationLevel += args.Effect.Amount * plantHolderComp.MutationMod;
}
private void OnExecutePlantAdjustMutationMod(ref ExecuteEntityEffectEvent<PlantAdjustMutationMod> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
plantHolderComp.MutationMod += args.Effect.Amount;
}
private void OnExecutePlantAdjustNutrition(ref ExecuteEntityEffectEvent<PlantAdjustNutrition> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
return;
_plantHolder.AdjustNutrient(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
}
private void OnExecutePlantAdjustPests(ref ExecuteEntityEffectEvent<PlantAdjustPests> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
plantHolderComp.PestLevel += args.Effect.Amount;
}
private void OnExecutePlantAdjustPotency(ref ExecuteEntityEffectEvent<PlantAdjustPotency> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
if (plantHolderComp.Seed == null)
return;
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + args.Effect.Amount, 1);
}
private void OnExecutePlantAdjustToxins(ref ExecuteEntityEffectEvent<PlantAdjustToxins> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
plantHolderComp.Toxins += args.Effect.Amount;
}
private void OnExecutePlantAdjustWater(ref ExecuteEntityEffectEvent<PlantAdjustWater> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
return;
_plantHolder.AdjustWater(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
}
private void OnExecutePlantAdjustWeeds(ref ExecuteEntityEffectEvent<PlantAdjustWeeds> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
plantHolderComp.WeedLevel += args.Effect.Amount;
}
private void OnExecutePlantAffectGrowth(ref ExecuteEntityEffectEvent<PlantAffectGrowth> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
_plantHolder.AffectGrowth(args.Args.TargetEntity, (int) args.Effect.Amount, plantHolderComp);
}
// Mutate reference 'val' between 'min' and 'max' by pretending the value
// is representable by a thermometer code with 'bits' number of bits and
// randomly flipping some of them.
private void MutateFloat(ref float val, float min, float max, int bits)
{
if (min == max)
{
val = min;
return;
}
// Starting number of bits that are high, between 0 and bits.
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
// In other words, it tends to go to the middle.
float probIncrease = 1 - (float)valInt / bits;
int valIntMutated;
if (_random.Prob(probIncrease))
{
valIntMutated = valInt + 1;
}
else
{
valIntMutated = valInt - 1;
}
// Set value based on mutated thermometer code.
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
val = valMutated;
}
private void MutateInt(ref int val, int min, int max, int bits)
{
if (min == max)
{
val = min;
return;
}
// Starting number of bits that are high, between 0 and bits.
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
// val may be outside the range of min/max due to starting prototype values, so clamp.
valInt = Math.Clamp(valInt, 0, bits);
// Probability that the bit flip increases n.
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
// In other words, it tends to go to the middle.
float probIncrease = 1 - (float)valInt / bits;
int valMutated;
if (_random.Prob(probIncrease))
{
valMutated = val + 1;
}
else
{
valMutated = val - 1;
}
valMutated = Math.Clamp(valMutated, min, max);
val = valMutated;
}
private void OnExecutePlantChangeStat(ref ExecuteEntityEffectEvent<PlantChangeStat> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
if (plantHolderComp.Seed == null)
return;
var member = plantHolderComp.Seed.GetType().GetField(args.Effect.TargetValue);
if (member == null)
{
_mutation.Log.Error(args.Effect.GetType().Name + " Error: Member " + args.Effect.TargetValue + " not found on " + plantHolderComp.Seed.GetType().Name + ". Did you misspell it?");
return;
}
var currentValObj = member.GetValue(plantHolderComp.Seed);
if (currentValObj == null)
return;
if (member.FieldType == typeof(float))
{
var floatVal = (float)currentValObj;
MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
member.SetValue(plantHolderComp.Seed, floatVal);
}
else if (member.FieldType == typeof(int))
{
var intVal = (int)currentValObj;
MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
member.SetValue(plantHolderComp.Seed, intVal);
}
else if (member.FieldType == typeof(bool))
{
var boolVal = (bool)currentValObj;
boolVal = !boolVal;
member.SetValue(plantHolderComp.Seed, boolVal);
}
}
private void OnExecutePlantCryoxadone(ref ExecuteEntityEffectEvent<PlantCryoxadone> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
var deviation = 0;
var seed = plantHolderComp.Seed;
if (seed == null)
return;
if (plantHolderComp.Age > seed.Maturation)
deviation = (int) Math.Max(seed.Maturation - 1, plantHolderComp.Age - _random.Next(7, 10));
else
deviation = (int) (seed.Maturation / seed.GrowthStages);
plantHolderComp.Age -= deviation;
plantHolderComp.LastProduce = plantHolderComp.Age;
plantHolderComp.SkipAging++;
plantHolderComp.ForceUpdate = true;
}
private void OnExecutePlantDestroySeeds(ref ExecuteEntityEffectEvent<PlantDestroySeeds> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
return;
if (plantHolderComp.Seed!.Seedless == false)
{
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
_popup.PopupEntity(
Loc.GetString("botany-plant-seedsdestroyed"),
args.Args.TargetEntity,
PopupType.SmallCaution
);
plantHolderComp.Seed.Seedless = true;
}
}
private void OnExecutePlantDiethylamine(ref ExecuteEntityEffectEvent<PlantDiethylamine> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
return;
if (_random.Prob(0.1f))
{
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
plantHolderComp.Seed!.Lifespan++;
}
if (_random.Prob(0.1f))
{
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
plantHolderComp.Seed!.Endurance++;
}
}
private void OnExecutePlantPhalanximine(ref ExecuteEntityEffectEvent<PlantPhalanximine> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
return;
plantHolderComp.Seed!.Viable = true;
}
private void OnExecutePlantRestoreSeeds(ref ExecuteEntityEffectEvent<PlantRestoreSeeds> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
return;
if (plantHolderComp.Seed!.Seedless)
{
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
_popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.Args.TargetEntity);
plantHolderComp.Seed.Seedless = false;
}
}
private void OnExecuteRobustHarvest(ref ExecuteEntityEffectEvent<RobustHarvest> args)
{
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
return;
if (plantHolderComp.Seed == null)
return;
if (plantHolderComp.Seed.Potency < args.Effect.PotencyLimit)
{
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
if (plantHolderComp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
{
plantHolderComp.Seed.Seedless = true;
}
}
else if (plantHolderComp.Seed.Yield > 1 && _random.Prob(0.1f))
{
// Too much of a good thing reduces yield
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
plantHolderComp.Seed.Yield--;
}
}
private void OnExecuteAdjustTemperature(ref ExecuteEntityEffectEvent<AdjustTemperature> args)
{
if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
{
var amount = args.Effect.Amount;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
amount *= reagentArgs.Scale.Float();
}
_temperature.ChangeHeat(args.Args.TargetEntity, amount, true, temp);
}
}
private void OnExecuteAreaReactionEffect(ref ExecuteEntityEffectEvent<AreaReactionEffect> args)
{
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.Source == null)
return;
var spreadAmount = (int) Math.Max(0, Math.Ceiling((reagentArgs.Quantity / args.Effect.OverflowThreshold).Float()));
var splitSolution = reagentArgs.Source.SplitSolution(reagentArgs.Source.Volume);
var transform = EntityManager.GetComponent<TransformComponent>(reagentArgs.TargetEntity);
var mapCoords = _xform.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform);
if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
!_map.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef))
{
return;
}
if (_spreader.RequiresFloorToSpread(args.Effect.PrototypeId) && tileRef.Tile.IsSpace())
return;
var coords = _map.MapToGrid(gridUid, mapCoords);
var ent = EntityManager.SpawnEntity(args.Effect.PrototypeId, coords.SnapToGrid());
_smoke.StartSmoke(ent, splitSolution, args.Effect.Duration, spreadAmount);
_audio.PlayPvs(args.Effect.Sound, reagentArgs.TargetEntity, AudioParams.Default.WithVariation(0.25f));
return;
}
// TODO: Someone needs to figure out how to do this for non-reagent effects.
throw new NotImplementedException();
}
private void OnExecuteCauseZombieInfection(ref ExecuteEntityEffectEvent<CauseZombieInfection> args)
{
EnsureComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
EnsureComp<PendingZombieComponent>(args.Args.TargetEntity);
}
private void OnExecuteChemCleanBloodstream(ref ExecuteEntityEffectEvent<ChemCleanBloodstream> args)
{
var cleanseRate = args.Effect.CleanseRate;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
if (reagentArgs.Source == null || reagentArgs.Reagent == null)
return;
cleanseRate *= reagentArgs.Scale.Float();
_bloodstream.FlushChemicals(args.Args.TargetEntity, reagentArgs.Reagent.ID, cleanseRate);
}
else
{
_bloodstream.FlushChemicals(args.Args.TargetEntity, "", cleanseRate);
}
}
private void OnExecuteChemVomit(ref ExecuteEntityEffectEvent<ChemVomit> args)
{
if (args.Args is EntityEffectReagentArgs reagentArgs)
if (reagentArgs.Scale != 1f)
return;
_vomit.Vomit(args.Args.TargetEntity, args.Effect.ThirstAmount, args.Effect.HungerAmount);
}
private void OnExecuteCreateEntityReactionEffect(ref ExecuteEntityEffectEvent<CreateEntityReactionEffect> args)
{
var transform = Comp<TransformComponent>(args.Args.TargetEntity);
var quantity = (int)args.Effect.Number;
if (args.Args is EntityEffectReagentArgs reagentArgs)
quantity *= reagentArgs.Quantity.Int();
for (var i = 0; i < quantity; i++)
{
var uid = Spawn(args.Effect.Entity, _xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform));
_xform.AttachToGridOrMap(uid);
// TODO figure out how to properly spawn inside of containers
// e.g. cheese:
// if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user.
// if reaction happens in a backpack, should insert cheese into backpack.
// --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players).
// if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that.
// presumably having cheese materialize inside of your blood would have "disadvantages".
}
}
private void OnExecuteCreateGas(ref ExecuteEntityEffectEvent<CreateGas> args)
{
var tileMix = _atmosphere.GetContainingMixture(args.Args.TargetEntity, false, true);
if (tileMix != null)
{
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
tileMix.AdjustMoles(args.Effect.Gas, reagentArgs.Quantity.Float() * args.Effect.Multiplier);
}
else
{
tileMix.AdjustMoles(args.Effect.Gas, args.Effect.Multiplier);
}
}
}
private void OnExecuteCureZombieInfection(ref ExecuteEntityEffectEvent<CureZombieInfection> args)
{
if (HasComp<IncurableZombieComponent>(args.Args.TargetEntity))
return;
RemComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
RemComp<PendingZombieComponent>(args.Args.TargetEntity);
if (args.Effect.Innoculate)
{
EnsureComp<ZombieImmuneComponent>(args.Args.TargetEntity);
}
}
private void OnExecuteEmote(ref ExecuteEntityEffectEvent<Emote> args)
{
if (args.Effect.EmoteId == null)
return;
if (args.Effect.ShowInChat)
_chat.TryEmoteWithChat(args.Args.TargetEntity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
else
_chat.TryEmoteWithoutChat(args.Args.TargetEntity, args.Effect.EmoteId);
}
private void OnExecuteEmpReactionEffect(ref ExecuteEntityEffectEvent<EmpReactionEffect> args)
{
var transform = EntityManager.GetComponent<TransformComponent>(args.Args.TargetEntity);
var range = args.Effect.EmpRangePerUnit;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
range = MathF.Min((float) (reagentArgs.Quantity * args.Effect.EmpRangePerUnit), args.Effect.EmpMaxRange);
}
_emp.EmpPulse(_xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform),
range,
args.Effect.EnergyConsumption,
args.Effect.DisableDuration);
}
private void OnExecuteExplosionReactionEffect(ref ExecuteEntityEffectEvent<ExplosionReactionEffect> args)
{
var intensity = args.Effect.IntensityPerUnit;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
intensity = MathF.Min((float) reagentArgs.Quantity * args.Effect.IntensityPerUnit, args.Effect.MaxTotalIntensity);
}
_explosion.QueueExplosion(
args.Args.TargetEntity,
args.Effect.ExplosionType,
intensity,
args.Effect.IntensitySlope,
args.Effect.MaxIntensity,
args.Effect.TileBreakScale);
}
private void OnExecuteFlammableReaction(ref ExecuteEntityEffectEvent<FlammableReaction> args)
{
if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
return;
// Sets the multiplier for FireStacks to MultiplierOnExisting is 0 or greater and target already has FireStacks
var multiplier = flammable.FireStacks != 0f && args.Effect.MultiplierOnExisting >= 0 ? args.Effect.MultiplierOnExisting : args.Effect.Multiplier;
var quantity = 1f;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
quantity = reagentArgs.Quantity.Float();
_flammable.AdjustFireStacks(args.Args.TargetEntity, quantity * multiplier, flammable);
if (reagentArgs.Reagent != null)
reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
}
else
{
_flammable.AdjustFireStacks(args.Args.TargetEntity, multiplier, flammable);
}
}
private void OnExecuteFlashReactionEffect(ref ExecuteEntityEffectEvent<FlashReactionEffect> args)
{
var transform = EntityManager.GetComponent<TransformComponent>(args.Args.TargetEntity);
var range = 1f;
if (args.Args is EntityEffectReagentArgs reagentArgs)
range = MathF.Min((float)(reagentArgs.Quantity * args.Effect.RangePerUnit), args.Effect.MaxRange);
_flash.FlashArea(
args.Args.TargetEntity,
null,
range,
args.Effect.Duration * 1000,
slowTo: args.Effect.SlowTo,
sound: args.Effect.Sound);
if (args.Effect.FlashEffectPrototype == null)
return;
var uid = EntityManager.SpawnEntity(args.Effect.FlashEffectPrototype, _xform.GetMapCoordinates(transform));
_xform.AttachToGridOrMap(uid);
if (!TryComp<PointLightComponent>(uid, out var pointLightComp))
return;
_pointLight.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp);
}
private void OnExecuteIgnite(ref ExecuteEntityEffectEvent<Ignite> args)
{
if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
return;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
_flammable.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable);
}
else
{
_flammable.Ignite(args.Args.TargetEntity, args.Args.TargetEntity, flammable: flammable);
}
}
private void OnExecuteMakeSentient(ref ExecuteEntityEffectEvent<MakeSentient> args)
{
var uid = args.Args.TargetEntity;
// Let affected entities speak normally to make this effect different from, say, the "random sentience" event
// This also works on entities that already have a mind
// We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
RemComp<ReplacementAccentComponent>(uid);
RemComp<MonkeyAccentComponent>(uid);
// Stops from adding a ghost role to things like people who already have a mind
if (TryComp<MindContainerComponent>(uid, out var mindContainer) && mindContainer.HasMind)
{
return;
}
// Don't add a ghost role to things that already have ghost roles
if (TryComp(uid, out GhostRoleComponent? ghostRole))
{
return;
}
ghostRole = AddComp<GhostRoleComponent>(uid);
EnsureComp<GhostTakeoverAvailableComponent>(uid);
var entityData = EntityManager.GetComponent<MetaDataComponent>(uid);
ghostRole.RoleName = entityData.EntityName;
ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
}
private void OnExecuteModifyBleedAmount(ref ExecuteEntityEffectEvent<ModifyBleedAmount> args)
{
if (TryComp<BloodstreamComponent>(args.Args.TargetEntity, out var blood))
{
var amt = args.Effect.Amount;
if (args.Args is EntityEffectReagentArgs reagentArgs) {
if (args.Effect.Scaled)
amt *= reagentArgs.Quantity.Float();
amt *= reagentArgs.Scale.Float();
}
_bloodstream.TryModifyBleedAmount(args.Args.TargetEntity, amt, blood);
}
}
private void OnExecuteModifyBloodLevel(ref ExecuteEntityEffectEvent<ModifyBloodLevel> args)
{
if (TryComp<BloodstreamComponent>(args.Args.TargetEntity, out var blood))
{
var amt = args.Effect.Amount;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
if (args.Effect.Scaled)
amt *= reagentArgs.Quantity;
amt *= reagentArgs.Scale;
}
_bloodstream.TryModifyBloodLevel(args.Args.TargetEntity, amt, blood);
}
}
private void OnExecuteModifyLungGas(ref ExecuteEntityEffectEvent<ModifyLungGas> args)
{
LungComponent? lung;
float amount = 1f;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
if (!TryComp<LungComponent>(reagentArgs.OrganEntity, out var organLung))
return;
lung = organLung;
amount = reagentArgs.Quantity.Float();
}
else
{
if (!TryComp<LungComponent>(args.Args.TargetEntity, out var organLung)) //Likely needs to be modified to ensure it works correctly
return;
lung = organLung;
}
if (lung != null)
{
foreach (var (gas, ratio) in args.Effect.Ratios)
{
var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier;
if (quantity < 0)
quantity = Math.Max(quantity, -lung.Air[(int) gas]);
lung.Air.AdjustMoles(gas, quantity);
}
}
}
private void OnExecuteOxygenate(ref ExecuteEntityEffectEvent<Oxygenate> args)
{
var multiplier = 1f;
if (args.Args is EntityEffectReagentArgs reagentArgs)
{
multiplier = reagentArgs.Quantity.Float();
}
if (TryComp<RespiratorComponent>(args.Args.TargetEntity, out var resp))
{
_respirator.UpdateSaturation(args.Args.TargetEntity, multiplier * args.Effect.Factor, resp);
}
}
private void OnExecutePlantMutateChemicals(ref ExecuteEntityEffectEvent<PlantMutateChemicals> args)
{
var plantholder = EntityManager.GetComponent<PlantHolderComponent>(args.Args.TargetEntity);
if (plantholder.Seed == null)
return;
var chemicals = plantholder.Seed.Chemicals;
var randomChems = _protoManager.Index<WeightedRandomFillSolutionPrototype>("RandomPickBotanyReagent").Fills;
// Add a random amount of a random chemical to this set of chemicals
if (randomChems != null)
{
var pick = _random.Pick<RandomFillSolution>(randomChems);
var chemicalId = _random.Pick(pick.Reagents);
var amount = _random.Next(1, (int)pick.Quantity);
var seedChemQuantity = new SeedChemQuantity();
if (chemicals.ContainsKey(chemicalId))
{
seedChemQuantity.Min = chemicals[chemicalId].Min;
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
}
else
{
seedChemQuantity.Min = 1;
seedChemQuantity.Max = 1 + amount;
seedChemQuantity.Inherent = false;
}
var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
seedChemQuantity.PotencyDivisor = potencyDivisor;
chemicals[chemicalId] = seedChemQuantity;
}
}
private void OnExecutePlantMutateConsumeGasses(ref ExecuteEntityEffectEvent<PlantMutateConsumeGasses> args)
{
var plantholder = EntityManager.GetComponent<PlantHolderComponent>(args.Args.TargetEntity);
if (plantholder.Seed == null)
return;
var gasses = plantholder.Seed.ExudeGasses;
// Add a random amount of a random gas to this gas dictionary
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
if (gasses.ContainsKey(gas))
{
gasses[gas] += amount;
}
else
{
gasses.Add(gas, amount);
}
}
private void OnExecutePlantMutateExudeGasses(ref ExecuteEntityEffectEvent<PlantMutateExudeGasses> args)
{
var plantholder = EntityManager.GetComponent<PlantHolderComponent>(args.Args.TargetEntity);
if (plantholder.Seed == null)
return;
var gasses = plantholder.Seed.ConsumeGasses;
// Add a random amount of a random gas to this gas dictionary
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
if (gasses.ContainsKey(gas))
{
gasses[gas] += amount;
}
else
{
gasses.Add(gas, amount);
}
}
private void OnExecutePlantMutateHarvest(ref ExecuteEntityEffectEvent<PlantMutateHarvest> args)
{
var plantholder = EntityManager.GetComponent<PlantHolderComponent>(args.Args.TargetEntity);
if (plantholder.Seed == null)
return;
if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat)
plantholder.Seed.HarvestRepeat = HarvestType.Repeat;
else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat)
plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest;
}
private void OnExecutePlantSpeciesChange(ref ExecuteEntityEffectEvent<PlantSpeciesChange> args)
{
var plantholder = EntityManager.GetComponent<PlantHolderComponent>(args.Args.TargetEntity);
if (plantholder.Seed == null)
return;
if (plantholder.Seed.MutationPrototypes.Count == 0)
return;
var targetProto = _random.Pick(plantholder.Seed.MutationPrototypes);
_protoManager.TryIndex(targetProto, out SeedPrototype? protoSeed);
if (protoSeed == null)
{
Log.Error($"Seed prototype could not be found: {targetProto}!");
return;
}
plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed);
}
private void OnExecutePolymorph(ref ExecuteEntityEffectEvent<PolymorphEffect> args)
{
// Make it into a prototype
EnsureComp<PolymorphableComponent>(args.Args.TargetEntity);
_polymorph.PolymorphEntity(args.Args.TargetEntity, args.Effect.PolymorphPrototype);
}
private void OnExecuteResetNarcolepsy(ref ExecuteEntityEffectEvent<ResetNarcolepsy> args)
{
if (args.Args is EntityEffectReagentArgs reagentArgs)
if (reagentArgs.Scale != 1f)
return;
_narcolepsy.AdjustNarcolepsyTimer(args.Args.TargetEntity, args.Effect.TimerReset);
}
}

View File

@@ -1,7 +1,7 @@
using Content.Server.Administration.Logs;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.EntityEffects.Effects;
using Content.Shared.EntityEffects.Effects;
using Content.Server.Spreader;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;

View File

@@ -1,6 +1,6 @@
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.EntityEffects.Effects;
using Content.Shared.EntityEffects.Effects;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Inventory;

View File

@@ -60,7 +60,7 @@ namespace Content.Shared.Chemistry.Reaction
/// <summary>
/// Effects to be triggered when the reaction occurs.
/// </summary>
[DataField("effects", serverOnly: true)] public List<EntityEffect> Effects = new();
[DataField("effects")] public List<EntityEffect> Effects = new();
/// <summary>
/// How dangerous is this effect? Stuff like bicaridine should be low, while things like methamphetamine

View File

@@ -145,16 +145,16 @@ namespace Content.Shared.Chemistry.Reagent
[DataField]
public bool WorksOnTheDead;
[DataField(serverOnly: true)]
[DataField]
public FrozenDictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsEntry>? Metabolisms;
[DataField(serverOnly: true)]
[DataField]
public Dictionary<ProtoId<ReactiveGroupPrototype>, ReactiveReagentEffectEntry>? ReactiveEffects;
[DataField(serverOnly: true)]
public List<ITileReaction> TileReactions = new(0);
[DataField("plantMetabolism", serverOnly: true)]
[DataField("plantMetabolism")]
public List<EntityEffect> PlantMetabolisms = new(0);
[DataField]

View File

@@ -1,30 +1,18 @@
using Content.Server.Temperature.Components;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
/// <summary>
/// Requires the target entity to be above or below a certain temperature.
/// Used for things like cryoxadone and pyroxadone.
/// </summary>
public sealed partial class Temperature : EntityEffectCondition
public sealed partial class Temperature : EventEntityEffectCondition<Temperature>
{
[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)
{

View File

@@ -0,0 +1,21 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.EffectConditions;
/// <summary>
/// Condition for if the entity is successfully breathing.
/// </summary>
public sealed partial class Breathing : EventEntityEffectCondition<Breathing>
{
/// <summary>
/// If true, the entity must not have trouble breathing to pass.
/// </summary>
[DataField]
public bool IsBreathing = true;
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-breathing",
("isBreathing", IsBreathing));
}
}

View File

@@ -1,12 +1,9 @@
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.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
[UsedImplicitly]
public sealed partial class HasTag : EntityEffectCondition
{
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]

View File

@@ -1,5 +1,4 @@
using System.Linq;
using Content.Shared.EntityEffects;
using Content.Shared.Localizations;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
@@ -7,7 +6,7 @@ using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
public sealed partial class JobCondition : EntityEffectCondition
{

View File

@@ -1,9 +1,8 @@
using Content.Shared.EntityEffects;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
public sealed partial class MobStateCondition : EntityEffectCondition
{

View File

@@ -0,0 +1,28 @@
// using Content.Server.Body.Components;
using Content.Shared.Body.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.EntityEffects.EffectConditions;
/// <summary>
/// Requires that the metabolizing organ is or is not tagged with a certain MetabolizerType
/// </summary>
public sealed partial class OrganType : EventEntityEffectCondition<OrganType>
{
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<MetabolizerTypePrototype>))]
public string Type = default!;
/// <summary>
/// Does this condition pass when the organ has the type, or when it doesn't have the type?
/// </summary>
[DataField]
public bool ShouldHave = true;
public override string GuidebookExplanation(IPrototypeManager prototype)
{
return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
("name", prototype.Index<MetabolizerTypePrototype>(Type).LocalizedName),
("shouldhave", ShouldHave));
}
}

View File

@@ -1,9 +1,8 @@
using Content.Shared.Chemistry.Reagent;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
/// <summary>
/// Used for implementing reagent effects that require a certain amount of reagent before it should be applied.

View File

@@ -1,7 +1,6 @@
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
/// <summary>
/// Requires the solution to be above or below a certain temperature.

View File

@@ -3,7 +3,7 @@ using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
public sealed partial class TotalDamage : EntityEffectCondition
{

View File

@@ -3,7 +3,7 @@ using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.EffectConditions;
namespace Content.Shared.EntityEffects.EffectConditions;
public sealed partial class Hunger : EntityEffectCondition
{

View File

@@ -1,11 +1,8 @@
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects
namespace Content.Shared.EntityEffects.Effects
{
[UsedImplicitly]
public sealed partial class AddToSolutionReaction : EntityEffect
{
[DataField("solution")]

View File

@@ -1,9 +1,8 @@
using Content.Shared.Alert;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class AdjustAlert : EntityEffect
{

View File

@@ -1,14 +1,11 @@
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.EntityEffects.Effects
namespace Content.Shared.EntityEffects.Effects
{
[UsedImplicitly]
public sealed partial class AdjustReagent : EntityEffect
{
/// <summary>
@@ -90,3 +87,4 @@ namespace Content.Server.EntityEffects.Effects
}
}
}

View File

@@ -0,0 +1,15 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class AdjustTemperature : EventEntityEffect<AdjustTemperature>
{
[DataField]
public float Amount;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-adjust-temperature",
("chance", Probability),
("deltasign", MathF.Sign(Amount)),
("amount", MathF.Abs(Amount)));
}

View File

@@ -0,0 +1,43 @@
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Basically smoke and foam reactions.
/// </summary>
public sealed partial class AreaReactionEffect : EventEntityEffect<AreaReactionEffect>
{
/// <summary>
/// How many seconds will the effect stay, counting after fully spreading.
/// </summary>
[DataField("duration")] public float Duration = 10;
/// <summary>
/// How many units of reaction for 1 smoke entity.
/// </summary>
[DataField] public FixedPoint2 OverflowThreshold = FixedPoint2.New(2.5);
/// <summary>
/// The entity prototype that will be spawned as the effect.
/// </summary>
[DataField("prototypeId", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string PrototypeId = default!;
/// <summary>
/// Sound that will get played when this reaction effect occurs.
/// </summary>
[DataField("sound", required: true)] public SoundSpecifier Sound = default!;
public override bool ShouldLog => true;
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-area-reaction",
("duration", Duration)
);
public override LogImpact LogImpact => LogImpact.High;
}

View File

@@ -1,25 +1,22 @@
using Content.Server.Popups;
using Content.Server.Xenoarchaeology.Artifact;
using Content.Shared.Xenoarchaeology.Artifact;
using Content.Shared.EntityEffects;
using Content.Shared.Popups;
using Content.Shared.Xenoarchaeology.Artifact.Components;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Sets an artifact into the unlocking state and marks the artifexium effect as true.
/// This is a very specific behavior intended for a specific chem.
/// </summary>
[UsedImplicitly]
public sealed partial class ArtifactUnlock : EntityEffect
{
public override void Effect(EntityEffectBaseArgs args)
{
var entMan = args.EntityManager;
var xenoArtifactSys = entMan.System<XenoArtifactSystem>();
var popupSys = entMan.System<PopupSystem>();
var xenoArtifactSys = entMan.System<SharedXenoArtifactSystem>();
var popupSys = entMan.System<SharedPopupSystem>();
if (!entMan.TryGetComponent<XenoArtifactComponent>(args.TargetEntity, out var xenoArtifact))
return;

View File

@@ -0,0 +1,9 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class CauseZombieInfection : EventEntityEffect<CauseZombieInfection>
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-cause-zombie-infection", ("chance", Probability));
}

View File

@@ -0,0 +1,15 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Basically smoke and foam reactions.
/// </summary>
public sealed partial class ChemCleanBloodstream : EventEntityEffect<ChemCleanBloodstream>
{
[DataField]
public float CleanseRate = 3.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
}

View File

@@ -1,14 +1,11 @@
using Content.Shared.EntityEffects;
using Content.Shared.Eye.Blinding.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Heal or apply eye damage
/// </summary>
[UsedImplicitly]
public sealed partial class ChemHealEyeDamage : EntityEffect
{
/// <summary>

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Forces you to vomit.
/// </summary>
public sealed partial class ChemVomit : EventEntityEffect<ChemVomit>
{
/// How many units of thirst to add each time we vomit
[DataField]
public float ThirstAmount = -8f;
/// How many units of hunger to add each time we vomit
[DataField]
public float HungerAmount = -8f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability));
}

View File

@@ -0,0 +1,26 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.EntityEffects.Effects;
[DataDefinition]
public sealed partial class CreateEntityReactionEffect : EventEntityEffect<CreateEntityReactionEffect>
{
/// <summary>
/// What entity to create.
/// </summary>
[DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Entity = default!;
/// <summary>
/// How many entities to create per unit reaction.
/// </summary>
[DataField]
public uint Number = 1;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-create-entity-reaction-effect",
("chance", Probability),
("entname", IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
("amount", Number));
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Database;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class CreateGas : EventEntityEffect<CreateGas>
{
[DataField(required: true)]
public Gas Gas = default!;
/// <summary>
/// For each unit consumed, how many moles of gas should be created?
/// </summary>
[DataField]
public float Multiplier = 3f;
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
var atmos = entSys.GetEntitySystem<SharedAtmosphereSystem>();
var gasProto = atmos.GetGas(Gas);
return Loc.GetString("reagent-effect-guidebook-create-gas",
("chance", Probability),
("moles", Multiplier),
("gas", gasProto.Name));
}
public override LogImpact LogImpact => LogImpact.High;
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class CureZombieInfection : EventEntityEffect<CureZombieInfection>
{
[DataField]
public bool Innoculate;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
{
if(Innoculate)
return Loc.GetString("reagent-effect-guidebook-innoculate-zombie-infection", ("chance", Probability));
return Loc.GetString("reagent-effect-guidebook-cure-zombie-infection", ("chance", Probability));
}
}

View File

@@ -1,8 +1,7 @@
using Content.Shared.Drunk;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class Drunk : EntityEffect
{

View File

@@ -1,8 +1,7 @@
using Content.Server.Electrocution;
using Content.Shared.EntityEffects;
using Content.Shared.Electrocution;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class Electrocute : EntityEffect
{
@@ -24,14 +23,14 @@ public sealed partial class Electrocute : EntityEffect
{
if (args is EntityEffectReagentArgs reagentArgs)
{
reagentArgs.EntityManager.System<ElectrocutionSystem>().TryDoElectrocution(reagentArgs.TargetEntity, null,
reagentArgs.EntityManager.System<SharedElectrocutionSystem>().TryDoElectrocution(reagentArgs.TargetEntity, null,
Math.Max((reagentArgs.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
if (reagentArgs.Reagent != null)
reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
} else
{
args.EntityManager.System<ElectrocutionSystem>().TryDoElectrocution(args.TargetEntity, null,
args.EntityManager.System<SharedElectrocutionSystem>().TryDoElectrocution(args.TargetEntity, null,
Math.Max(ElectrocuteDamageScale, 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
}
}

View File

@@ -1,17 +1,13 @@
using Content.Server.Chat.Systems;
using Content.Shared.Chat.Prototypes;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits unless specially forced.
/// </summary>
[UsedImplicitly]
public sealed partial class Emote : EntityEffect
public sealed partial class Emote : EventEntityEffect<Emote>
{
/// <summary>
/// The emote the entity will preform.
@@ -44,13 +40,4 @@ public sealed partial class Emote : EntityEffect
return Loc.GetString("reagent-effect-guidebook-emote", ("chance", Probability), ("emote", EmoteId));
}
public override void Effect(EntityEffectBaseArgs args)
{
var chatSys = args.EntityManager.System<ChatSystem>();
if (ShowInChat)
chatSys.TryEmoteWithChat(args.TargetEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force);
else
chatSys.TryEmoteWithoutChat(args.TargetEntity, EmoteId);
}
}

View File

@@ -0,0 +1,34 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
[DataDefinition]
public sealed partial class EmpReactionEffect : EventEntityEffect<EmpReactionEffect>
{
/// <summary>
/// Impulse range per unit of quantity
/// </summary>
[DataField("rangePerUnit")]
public float EmpRangePerUnit = 0.5f;
/// <summary>
/// Maximum impulse range
/// </summary>
[DataField("maxRange")]
public float EmpMaxRange = 10;
/// <summary>
/// How much energy will be drain from sources
/// </summary>
[DataField]
public float EnergyConsumption = 12500;
/// <summary>
/// Amount of time entities will be disabled
/// </summary>
[DataField("duration")]
public float DisableDuration = 15;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability));
}

View File

@@ -3,16 +3,14 @@ using Content.Shared.Damage.Prototypes;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Version of <see cref="HealthChange"/> that distributes the healing to groups
/// </summary>
[UsedImplicitly]
public sealed partial class EvenHealthChange : EntityEffect
{
/// <summary>

View File

@@ -1,15 +1,13 @@
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;
namespace Content.Shared.EntityEffects.Effects;
[DataDefinition]
public sealed partial class ExplosionReactionEffect : EntityEffect
public sealed partial class ExplosionReactionEffect : EventEntityEffect<ExplosionReactionEffect>
{
/// <summary>
/// The type of explosion. Determines damage types and tile break chance scaling.
@@ -58,23 +56,4 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
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,
TileBreakScale);
}
}

View File

@@ -1,11 +1,8 @@
using Content.Shared.Atmos;
using Content.Shared.EntityEffects;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects
namespace Content.Shared.EntityEffects.Effects
{
[UsedImplicitly]
public sealed partial class ExtinguishReaction : EntityEffect
{
/// <summary>

View File

@@ -0,0 +1,21 @@
using Content.Shared.Database;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class FlammableReaction : EventEntityEffect<FlammableReaction>
{
[DataField]
public float Multiplier = 0.05f;
// The fire stack multiplier if fire stacks already exist on target, only works if 0 or greater
[DataField]
public float MultiplierOnExisting = -1f;
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-flammable-reaction", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.Medium;
}

View File

@@ -0,0 +1,48 @@
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
[DataDefinition]
public sealed partial class FlashReactionEffect : EventEntityEffect<FlashReactionEffect>
{
/// <summary>
/// Flash range per unit of reagent.
/// </summary>
[DataField]
public float RangePerUnit = 0.2f;
/// <summary>
/// Maximum flash range.
/// </summary>
[DataField]
public float MaxRange = 10f;
/// <summary>
/// How much to entities are slowed down.
/// </summary>
[DataField]
public float SlowTo = 0.5f;
/// <summary>
/// The time entities will be flashed in seconds.
/// The default is chosen to be better than the hand flash so it is worth using it for grenades etc.
/// </summary>
[DataField]
public float Duration = 4f;
/// <summary>
/// The prototype ID used for the visual effect.
/// </summary>
[DataField]
public EntProtoId? FlashEffectPrototype = "ReactionFlash";
/// <summary>
/// The sound the flash creates.
/// </summary>
[DataField]
public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg");
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-flash-reaction-effect", ("chance", Probability));
}

View File

@@ -2,7 +2,7 @@ using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Makes a mob glow.

View File

@@ -3,17 +3,15 @@ using Content.Shared.Damage.Prototypes;
using Content.Shared.EntityEffects;
using Content.Shared.FixedPoint;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using System.Linq;
using System.Text.Json.Serialization;
namespace Content.Server.EntityEffects.Effects
namespace Content.Shared.EntityEffects.Effects
{
/// <summary>
/// Default metabolism used for medicine reagents.
/// </summary>
[UsedImplicitly]
public sealed partial class HealthChange : EntityEffect
{
/// <summary>

View File

@@ -0,0 +1,18 @@
using Content.Shared.Database;
using Content.Shared.EntityEffects;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Ignites a mob.
/// </summary>
public sealed partial class Ignite : EventEntityEffect<Ignite>
{
public override bool ShouldLog => true;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-ignite", ("chance", Probability));
public override LogImpact LogImpact => LogImpact.Medium;
}

View File

@@ -0,0 +1,10 @@
using Content.Shared.Mind.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class MakeSentient : EventEntityEffect<MakeSentient>
{
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability));
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class ModifyBleedAmount : EventEntityEffect<ModifyBleedAmount>
{
[DataField]
public bool Scaled = false;
[DataField]
public float Amount = -1.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability),
("deltasign", MathF.Sign(Amount)));
}

View File

@@ -0,0 +1,17 @@
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class ModifyBloodLevel : EventEntityEffect<ModifyBloodLevel>
{
[DataField]
public bool Scaled = false;
[DataField]
public FixedPoint2 Amount = 1.0f;
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability),
("deltasign", MathF.Sign(Amount.Float())));
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.Atmos;
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class ModifyLungGas : EventEntityEffect<ModifyLungGas>
{
[DataField("ratios", required: true)]
public Dictionary<Gas, float> Ratios = default!;
// JUSTIFICATION: This is internal magic that players never directly interact with.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
}

View File

@@ -1,10 +1,9 @@
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;
namespace Content.Shared.EntityEffects.Effects;
/// <summary>
/// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target,

View File

@@ -0,0 +1,13 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class Oxygenate : EventEntityEffect<Oxygenate>
{
[DataField]
public float Factor = 1f;
// JUSTIFICATION: This is internal magic that players never directly interact with.
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> null;
}

View File

@@ -1,8 +1,8 @@
using Content.Server.Stunnable;
using Content.Shared.EntityEffects;
using Content.Shared.Stunnable;
using Robust.Shared.Prototypes;
namespace Content.Server.EntityEffects.Effects;
namespace Content.Shared.EntityEffects.Effects;
public sealed partial class Paralyze : EntityEffect
{
@@ -27,7 +27,7 @@ public sealed partial class Paralyze : EntityEffect
paralyzeTime *= (double)reagentArgs.Scale;
}
args.EntityManager.System<StunSystem>().TryParalyze(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime), Refresh);
args.EntityManager.System<SharedStunSystem>().TryParalyze(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime), Refresh);
}
}

View File

@@ -1,13 +1,12 @@
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;
namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
[ImplicitDataDefinitionForInheritors]
public abstract partial class PlantAdjustAttribute : EntityEffect
public abstract partial class PlantAdjustAttribute<T> : EventEntityEffect<T> where T : PlantAdjustAttribute<T>
{
[DataField]
public float Amount { get; protected set; } = 1;
@@ -24,27 +23,6 @@ public abstract partial class PlantAdjustAttribute : EntityEffect
[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;

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustHealth : PlantAdjustAttribute<PlantAdjustHealth>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
}

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute<PlantAdjustMutationLevel>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
}

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute<PlantAdjustMutationMod>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
}

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute<PlantAdjustNutrition>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
}

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
public sealed partial class PlantAdjustPests : PlantAdjustAttribute<PlantAdjustPests>
{
public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests";
public override bool GuidebookIsAttributePositive { get; protected set; } = false;
}

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