Entity effects ECS refactor (#40580)
* LOCKED THE FUCK IN * Forgot this little fella * Crying * All entity effects ported, needs cleanup still * Commit * HEHEHEHAW * Shelve for now * fixe * Big * First big chunk of changes * Big if true * Commit * IT BUILDS!!! * Fix LINTER fails * Cleanup * Scale working, cut down on some evil code * Delete old Entity Effects * Accidentally breaking shit by fixing bugs * Fix a bunch of effects not working * Fix reagent thresholds * Update damage * Wait don't change the gas metabolisms A * Cleanup * more fixes * Eh * Misc fixes and jank * Remove two things, add bullshit, change condition to inverted * Remove unused "Shared" system structure * Namespace fix * merge conflicts/cleanup * More fixes * Guidebook text begins * Shelve * Push * More shit to push * Fix * Fix merg conflicts * BLOOD FOR THE BLOOD GOD!!! * Mild cleanup and lists * Fix localization and comments * Shuffle localization around a bit. * All done? * Nearly everything * Is this the end? * Whoops forgot to remove that TODO * Get rid of some warnings for good measure... * It's done * Should make those virtual in case we want to override them tbqh... * Update Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Fix test fails real * Add to codeowners * Documentation to everything * Forgot to push whoops * Standardize Condition names * Fix up metabolism a little as a treat * review * add IsServer checks --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4d316ae553
commit
4059c29ebc
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
/Content.*/Stunnable/ @Princess-Cheeseballs
|
/Content.*/Stunnable/ @Princess-Cheeseballs
|
||||||
/Content.*/Nutrition/ @Princess-Cheeseballs
|
/Content.*/Nutrition/ @Princess-Cheeseballs
|
||||||
|
/Content.*/EntityEffects @Princess-Cheeseballs @sowelipililimute
|
||||||
|
|
||||||
# SKREEEE
|
# SKREEEE
|
||||||
/Content.*.Database/ @PJB3005 @DrSmugleaf
|
/Content.*.Database/ @PJB3005 @DrSmugleaf
|
||||||
|
|||||||
8
Content.Client/Temperature/Systems/TemperatureSystem.cs
Normal file
8
Content.Client/Temperature/Systems/TemperatureSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Shared.Temperature.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Temperature.Systems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This exists so <see cref="SharedTemperatureSystem"/> runs on client/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TemperatureSystem : SharedTemperatureSystem;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Stunnable;
|
using Content.Server.Stunnable;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Server.Temperature.Systems;
|
using Content.Server.Temperature.Systems;
|
||||||
using Content.Server.Damage.Components;
|
using Content.Server.Damage.Components;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
@@ -24,6 +23,7 @@ using Content.Shared.Toggleable;
|
|||||||
using Content.Shared.Weapons.Melee.Events;
|
using Content.Shared.Weapons.Melee.Events;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Hands;
|
using Content.Shared.Hands;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Robust.Server.Audio;
|
using Robust.Server.Audio;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Physics.Events;
|
using Robust.Shared.Physics.Events;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Atmos.Rotting;
|
using Content.Shared.Atmos.Rotting;
|
||||||
using Content.Shared.Body.Events;
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Robust.Server.Containers;
|
using Robust.Server.Containers;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ namespace Content.Server.Body.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles metabolizing various reagents with given effects.
|
/// Handles metabolizing various reagents with given effects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, Access(typeof(MetabolizerSystem))]
|
[RegisterComponent, AutoGenerateComponentPause, Access(typeof(MetabolizerSystem))]
|
||||||
public sealed partial class MetabolizerComponent : Component
|
public sealed partial class MetabolizerComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next time that reagents will be metabolized.
|
/// The next time that reagents will be metabolized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
[DataField, AutoPausedField]
|
||||||
public TimeSpan NextUpdate;
|
public TimeSpan NextUpdate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Body.Events;
|
using Content.Shared.Body.Events;
|
||||||
using Content.Shared.Body.Organ;
|
using Content.Shared.Body.Organ;
|
||||||
|
using Content.Shared.Body.Prototypes;
|
||||||
using Content.Shared.Body.Systems;
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.EntityConditions;
|
||||||
|
using Content.Shared.EntityConditions.Conditions;
|
||||||
|
using Content.Shared.EntityConditions.Conditions.Body;
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Body;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Solution;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
@@ -17,20 +21,22 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems
|
namespace Content.Server.Body.Systems;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class MetabolizerSystem : SharedMetabolizerSystem
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
|
||||||
public sealed class MetabolizerSystem : SharedMetabolizerSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||||
|
[Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!;
|
||||||
|
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
|
||||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
|
|
||||||
private EntityQuery<OrganComponent> _organQuery;
|
private EntityQuery<OrganComponent> _organQuery;
|
||||||
private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
|
private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
|
||||||
|
private static readonly ProtoId<MetabolismGroupPrototype> Gas = "Gas";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -41,7 +47,6 @@ namespace Content.Server.Body.Systems
|
|||||||
|
|
||||||
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
|
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
|
||||||
SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<MetabolizerComponent, EntityUnpausedEvent>(OnUnpaused);
|
|
||||||
SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +55,6 @@ namespace Content.Server.Body.Systems
|
|||||||
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
|
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
|
|
||||||
{
|
|
||||||
ent.Comp.NextUpdate += args.PausedTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
|
private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
|
||||||
{
|
{
|
||||||
if (!entity.Comp.SolutionOnBody)
|
if (!entity.Comp.SolutionOnBody)
|
||||||
@@ -164,6 +164,7 @@ namespace Content.Server.Body.Systems
|
|||||||
if (ent.Comp1.MetabolismGroups is null)
|
if (ent.Comp1.MetabolismGroups is null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// TODO: Kill MetabolismGroups!
|
||||||
foreach (var group in ent.Comp1.MetabolismGroups)
|
foreach (var group in ent.Comp1.MetabolismGroups)
|
||||||
{
|
{
|
||||||
if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
|
if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
|
||||||
@@ -174,7 +175,12 @@ namespace Content.Server.Body.Systems
|
|||||||
// Remove $rate, as long as there's enough reagent there to actually remove that much
|
// Remove $rate, as long as there's enough reagent there to actually remove that much
|
||||||
mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
|
mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
|
||||||
|
|
||||||
float scale = (float) mostToRemove / (float) rate;
|
var scale = (float) mostToRemove;
|
||||||
|
|
||||||
|
// TODO: This is a very stupid workaround to lungs heavily relying on scale = reagent quantity. Needs lung and metabolism refactors to remove.
|
||||||
|
// TODO: Lungs just need to have their scale be equal to the mols consumed, scale needs to be not hardcoded either and configurable per metabolizer...
|
||||||
|
if (group.Id != Gas)
|
||||||
|
scale /= (float) entry.MetabolismRate;
|
||||||
|
|
||||||
// if it's possible for them to be dead, and they are,
|
// if it's possible for them to be dead, and they are,
|
||||||
// then we shouldn't process any effects, but should probably
|
// then we shouldn't process any effects, but should probably
|
||||||
@@ -186,27 +192,36 @@ namespace Content.Server.Body.Systems
|
|||||||
}
|
}
|
||||||
|
|
||||||
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
|
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
|
||||||
var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale);
|
|
||||||
|
|
||||||
// do all effects, if conditions apply
|
// do all effects, if conditions apply
|
||||||
foreach (var effect in entry.Effects)
|
foreach (var effect in entry.Effects)
|
||||||
{
|
{
|
||||||
if (!effect.ShouldApply(args, _random))
|
if (scale < effect.MinScale)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (effect.ShouldLog)
|
// See if conditions apply
|
||||||
{
|
if (effect.Conditions != null && !CanMetabolizeEffect(actualEntity, ent, soln.Value, effect.Conditions))
|
||||||
_adminLogger.Add(
|
continue;
|
||||||
LogType.ReagentEffect,
|
|
||||||
effect.LogImpact,
|
ApplyEffect(effect);
|
||||||
$"Metabolism effect {effect.GetType().Name:effect}"
|
|
||||||
+ $" of reagent {proto.LocalizedName:reagent}"
|
|
||||||
+ $" applied on entity {actualEntity:entity}"
|
|
||||||
+ $" at {Transform(actualEntity).Coordinates:coordinates}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
effect.Effect(args);
|
// TODO: We should have to do this with metabolism. ReagentEffect struct needs refactoring and so does metabolism!
|
||||||
|
void ApplyEffect(EntityEffect effect)
|
||||||
|
{
|
||||||
|
switch (effect)
|
||||||
|
{
|
||||||
|
case ModifyLungGas:
|
||||||
|
_entityEffects.ApplyEffect(ent, effect, scale);
|
||||||
|
break;
|
||||||
|
case AdjustReagent:
|
||||||
|
_entityEffects.ApplyEffect(soln.Value, effect, scale);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_entityEffects.ApplyEffect(actualEntity, effect, scale);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,5 +237,42 @@ namespace Content.Server.Body.Systems
|
|||||||
|
|
||||||
_solutionContainerSystem.UpdateChemicals(soln.Value);
|
_solutionContainerSystem.UpdateChemicals(soln.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Public API to check if a certain metabolism effect can be applied to an entity.
|
||||||
|
/// TODO: With metabolism refactor make this logic smarter and unhardcode the old hardcoding entity effects used to have for metabolism!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="body">The body metabolizing the effects</param>
|
||||||
|
/// <param name="organ">The organ doing the metabolizing</param>
|
||||||
|
/// <param name="solution">The solution we are metabolizing from</param>
|
||||||
|
/// <param name="conditions">The conditions that need to be met to metabolize</param>
|
||||||
|
/// <returns>True if we can metabolize! False if we cannot!</returns>
|
||||||
|
public bool CanMetabolizeEffect(EntityUid body, EntityUid organ, Entity<SolutionComponent> solution, EntityCondition[] conditions)
|
||||||
|
{
|
||||||
|
foreach (var condition in conditions)
|
||||||
|
{
|
||||||
|
switch (condition)
|
||||||
|
{
|
||||||
|
// Need specific handling of specific conditions since Metabolism is funny like that.
|
||||||
|
// TODO: MetabolizerTypes should be handled well before this stage by metabolism itself.
|
||||||
|
case MetabolizerTypeCondition:
|
||||||
|
if (_entityConditions.TryCondition(organ, condition))
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
case ReagentCondition:
|
||||||
|
if (_entityConditions.TryCondition(solution, condition))
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (_entityConditions.TryCondition(body, condition))
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Content.Server.Administration.Logs;
|
|||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
using Content.Server.EntityEffects;
|
|
||||||
using Content.Shared.Body.Systems;
|
using Content.Shared.Body.Systems;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
@@ -14,9 +13,11 @@ using Content.Shared.Chemistry.EntitySystems;
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.EntityConditions;
|
||||||
|
using Content.Shared.EntityConditions.Conditions.Body;
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
using Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
using Content.Shared.EntityEffects.Effects;
|
using Content.Shared.EntityEffects.Effects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Body;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -29,16 +30,16 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||||
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosSys = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosSys = default!;
|
||||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
||||||
[Dependency] private readonly LungSystem _lungSystem = default!;
|
[Dependency] private readonly LungSystem _lungSystem = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
[Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!;
|
||||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
[Dependency] private readonly ChatSystem _chat = default!;
|
|
||||||
[Dependency] private readonly EntityEffectSystem _entityEffect = default!;
|
|
||||||
|
|
||||||
private static readonly ProtoId<MetabolismGroupPrototype> GasId = new("Gas");
|
private static readonly ProtoId<MetabolismGroupPrototype> GasId = new("Gas");
|
||||||
|
|
||||||
@@ -340,7 +341,6 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO generalize condition checks
|
|
||||||
// this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture
|
// this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture
|
||||||
// Applying actual reaction effects require a full ReagentEffectArgs struct.
|
// Applying actual reaction effects require a full ReagentEffectArgs struct.
|
||||||
bool CanMetabolize(EntityEffect effect)
|
bool CanMetabolize(EntityEffect effect)
|
||||||
@@ -348,9 +348,10 @@ public sealed class RespiratorSystem : EntitySystem
|
|||||||
if (effect.Conditions == null)
|
if (effect.Conditions == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// TODO: Use Metabolism Public API to do this instead, once that API has been built.
|
||||||
foreach (var cond in effect.Conditions)
|
foreach (var cond in effect.Conditions)
|
||||||
{
|
{
|
||||||
if (cond is OrganType organ && !_entityEffect.OrganCondition(organ, lung))
|
if (cond is MetabolizerTypeCondition organ && !_entityConditions.TryCondition(lung, organ))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Server.Temperature.Systems;
|
using Content.Server.Temperature.Systems;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server.Body.Systems;
|
namespace Content.Server.Body.Systems;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using Content.Server.Botany.Components;
|
using Content.Server.Botany.Components;
|
||||||
using Content.Server.Botany.Systems;
|
using Content.Server.Botany.Systems;
|
||||||
using Content.Server.EntityEffects;
|
using Content.Server.EntityEffects.Effects.Botany;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
using Content.Shared.Random;
|
using Content.Shared.Random;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -79,9 +80,13 @@ public partial struct SeedChemQuantity
|
|||||||
[DataField("Inherent")] public bool Inherent = true;
|
[DataField("Inherent")] public bool Inherent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component.
|
// TODO Make Botany ECS and give it a proper API. I removed the limited access of this class because it's egregious how many systems needed access to it due to a lack of an actual API.
|
||||||
|
/// <remarks>
|
||||||
|
/// SeedData is no longer restricted because the number of friends is absolutely unreasonable.
|
||||||
|
/// This entire data definition is unreasonable. I felt genuine fear looking at this, this is horrific. Send help.
|
||||||
|
/// </remarks>
|
||||||
|
// TODO: Hit Botany with hammers
|
||||||
[Virtual, DataDefinition]
|
[Virtual, DataDefinition]
|
||||||
[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffectSystem), typeof(MutationSystem))]
|
|
||||||
public partial class SeedData
|
public partial class SeedData
|
||||||
{
|
{
|
||||||
#region Tracking
|
#region Tracking
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ namespace Content.Server.Botany.Systems;
|
|||||||
|
|
||||||
public sealed partial class BotanySystem
|
public sealed partial class BotanySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
|
||||||
|
|
||||||
public void ProduceGrown(EntityUid uid, ProduceComponent produce)
|
public void ProduceGrown(EntityUid uid, ProduceComponent produce)
|
||||||
{
|
{
|
||||||
if (!TryGetSeed(produce, out var seed))
|
if (!TryGetSeed(produce, out var seed))
|
||||||
@@ -15,10 +17,7 @@ public sealed partial class BotanySystem
|
|||||||
foreach (var mutation in seed.Mutations)
|
foreach (var mutation in seed.Mutations)
|
||||||
{
|
{
|
||||||
if (mutation.AppliesToProduce)
|
if (mutation.AppliesToProduce)
|
||||||
{
|
_entityEffects.TryApplyEffect(uid, mutation.Effect);
|
||||||
var args = new EntityEffectBaseArgs(uid, EntityManager);
|
|
||||||
mutation.Effect.Effect(args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_solutionContainerSystem.EnsureSolution(uid,
|
if (!_solutionContainerSystem.EnsureSolution(uid,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public sealed class MutationSystem : EntitySystem
|
|||||||
|
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
|
||||||
private RandomPlantMutationListPrototype _randomMutations = default!;
|
private RandomPlantMutationListPrototype _randomMutations = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -32,10 +33,8 @@ public sealed class MutationSystem : EntitySystem
|
|||||||
if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f)))
|
if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f)))
|
||||||
{
|
{
|
||||||
if (mutation.AppliesToPlant)
|
if (mutation.AppliesToPlant)
|
||||||
{
|
_entityEffects.TryApplyEffect(plantHolder, mutation.Effect);
|
||||||
var args = new EntityEffectBaseArgs(plantHolder, EntityManager);
|
|
||||||
mutation.Effect.Effect(args);
|
|
||||||
}
|
|
||||||
// Stat adjustments do not persist by being an attached effect, they just change the stat.
|
// Stat adjustments do not persist by being an attached effect, they just change the stat.
|
||||||
if (mutation.Persists && !seed.Mutations.Any(m => m.Name == mutation.Name))
|
if (mutation.Persists && !seed.Mutations.Any(m => m.Name == mutation.Name))
|
||||||
seed.Mutations.Add(mutation);
|
seed.Mutations.Add(mutation);
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
using Content.Shared.Kitchen.Components;
|
using Content.Shared.Kitchen.Components;
|
||||||
using Content.Shared.Labels.Components;
|
using Content.Shared.Labels.Components;
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
|
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
|
||||||
|
|
||||||
public const float HydroponicsSpeedMultiplier = 1f;
|
public const float HydroponicsSpeedMultiplier = 1f;
|
||||||
public const float HydroponicsConsumptionMultiplier = 2f;
|
public const float HydroponicsConsumptionMultiplier = 2f;
|
||||||
@@ -887,7 +890,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
|||||||
foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt))
|
foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt))
|
||||||
{
|
{
|
||||||
var reagentProto = _prototype.Index<ReagentPrototype>(entry.Reagent.Prototype);
|
var reagentProto = _prototype.Index<ReagentPrototype>(entry.Reagent.Prototype);
|
||||||
reagentProto.ReactionPlant(uid, entry, solution, EntityManager, _random, _adminLogger);
|
_entityEffects.ApplyEffects(uid, reagentProto.PlantMetabolisms.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public sealed class DumpReagentGuideText : LocalizedEntityCommands
|
|||||||
{
|
{
|
||||||
foreach (var effect in entry.Effects)
|
foreach (var effect in entry.Effects)
|
||||||
{
|
{
|
||||||
shell.WriteLine(effect.GuidebookEffectDescription(_prototype, EntityManager.EntitySysManager) ??
|
shell.WriteLine(reagent.GuidebookReagentEffectDescription(_prototype, EntityManager.EntitySysManager, effect, entry.MetabolismRate) ??
|
||||||
Loc.GetString($"cmd-dumpreagentguidetext-skipped", ("effect", effect.GetType())));
|
Loc.GetString($"cmd-dumpreagentguidetext-skipped", ("effect", effect.GetType())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Content.Shared.Prying.Systems;
|
|||||||
using Content.Shared.Radio.EntitySystems;
|
using Content.Shared.Radio.EntitySystems;
|
||||||
using Content.Shared.Stacks;
|
using Content.Shared.Stacks;
|
||||||
using Content.Shared.Temperature;
|
using Content.Shared.Temperature;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Content.Shared.Tools.Systems;
|
using Content.Shared.Tools.Systems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.Body.Components;
|
||||||
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Shared.EntityConditions;
|
||||||
|
using Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityConditions.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity is both able to breathe and is currently breathing.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class IsBreathingEntityConditionSystem : EntityConditionSystem<RespiratorComponent, BreathingCondition>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly RespiratorSystem _respirator = default!;
|
||||||
|
protected override void Condition(Entity<RespiratorComponent> entity, ref EntityConditionEvent<BreathingCondition> args)
|
||||||
|
{
|
||||||
|
args.Result = _respirator.IsBreathing(entity.AsNullable());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Body.Components;
|
||||||
|
using Content.Shared.EntityConditions;
|
||||||
|
using Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityConditions.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity has any of the listed metabolizer types.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class MetabolizerTypeEntityConditionSystem : EntityConditionSystem<MetabolizerComponent, MetabolizerTypeCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<MetabolizerComponent> entity, ref EntityConditionEvent<MetabolizerTypeCondition> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.MetabolizerTypes == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.Result = entity.Comp.MetabolizerTypes.Overlaps(args.Condition.Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Atmos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This effect adjusts a gas at the tile this entity is currently on.
|
||||||
|
/// The amount changed is modified by scale.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class CreateGasEntityEffectSystem : EntityEffectSystem<TransformComponent, CreateGas>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<CreateGas> args)
|
||||||
|
{
|
||||||
|
var tileMix = _atmosphere.GetContainingMixture(entity.AsNullable(), false, true);
|
||||||
|
|
||||||
|
tileMix?.AdjustMoles(args.Effect.Gas, args.Scale * args.Effect.Moles);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Shared.Atmos.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Atmos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a number of FireStacks modified by scale to this entity.
|
||||||
|
/// The amount of FireStacks added is modified by scale.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class FlammableEntityEffectSystem : EntityEffectSystem<FlammableComponent, Flammable>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly FlammableSystem _flammable = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Flammable> args)
|
||||||
|
{
|
||||||
|
// The multiplier is determined by if the entity is already on fire, and if the multiplier for existing FireStacks has a value.
|
||||||
|
// If both of these are true, we use the MultiplierOnExisting value, otherwise we use the standard Multiplier.
|
||||||
|
var multiplier = entity.Comp.FireStacks == 0f || args.Effect.MultiplierOnExisting == null ? args.Effect.Multiplier : args.Effect.MultiplierOnExisting.Value;
|
||||||
|
|
||||||
|
_flammable.AdjustFireStacks(entity, args.Scale * multiplier, entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Server.Atmos.EntitySystems;
|
||||||
|
using Content.Shared.Atmos.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Atmos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets this entity on fire.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class IngiteEntityEffectSystem : EntityEffectSystem<FlammableComponent, Ignite>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly FlammableSystem _flammable = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Ignite> args)
|
||||||
|
{
|
||||||
|
// TODO: Proper BodySystem Metabolism Effect relay...
|
||||||
|
// TODO: If this fucks over downstream shitmed, I give you full approval to use whatever shitcode method you need to fix it. Metabolism is awful.
|
||||||
|
_flammable.Ignite(entity, entity, flammable: entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Server.Body.Components;
|
||||||
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Body;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Body;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This effect adjusts a respirator's saturation value.
|
||||||
|
/// The saturation adjustment is modified by scale.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class OxygenateEntityEffectsSystem : EntityEffectSystem<RespiratorComponent, Oxygenate>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly RespiratorSystem _respirator = default!;
|
||||||
|
protected override void Effect(Entity<RespiratorComponent> entity, ref EntityEffectEvent<Oxygenate> args)
|
||||||
|
{
|
||||||
|
_respirator.UpdateSaturation(entity, args.Scale * args.Effect.Factor, entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustHealthEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustHealth>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustHealth> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.MutationLevel += args.Effect.Amount * entity.Comp.MutationMod;
|
||||||
|
_plantHolder.CheckHealth(entity, entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustMutationLevelEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustMutationLevel>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustMutationLevel> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.Health += args.Effect.Amount;
|
||||||
|
_plantHolder.CheckHealth(entity, entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustMutationModEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustMutationMod>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustMutationMod> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.MutationMod += args.Effect.Amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustNutritionEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustNutrition>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustNutrition> args)
|
||||||
|
{
|
||||||
|
_plantHolder.AdjustNutrient(entity, args.Effect.Amount, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustPestsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustPests>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPests> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.PestLevel += args.Effect.Amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustPotencyEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustPotency>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPotency> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity.Comp);
|
||||||
|
entity.Comp.Seed.Potency = Math.Max(entity.Comp.Seed.Potency + args.Effect.Amount, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustToxinsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustToxins>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustToxins> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.Toxins += args.Effect.Amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustWaterEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustWater>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustWater> args)
|
||||||
|
{
|
||||||
|
_plantHolder.AdjustWater(entity, args.Effect.Amount, entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAdjustWeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustWeeds>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustWeeds> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.WeedLevel += args.Effect.Amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantAffectGrowthEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAffectGrowth>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAffectGrowth> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_plantHolder.AffectGrowth(entity, (int)args.Effect.Amount, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This system mutates an inputted stat for a PlantHolder, only works for floats, integers, and bools.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantChangeStat>
|
||||||
|
{
|
||||||
|
// TODO: This is awful. I do not have the strength to refactor this. I want it gone.
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantChangeStat> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var effect = args.Effect;
|
||||||
|
var member = entity.Comp.Seed.GetType().GetField(args.Effect.TargetValue);
|
||||||
|
|
||||||
|
if (member == null)
|
||||||
|
{
|
||||||
|
Log.Error($"{ effect.GetType().Name } Error: Member { args.Effect.TargetValue} not found on { entity.Comp.Seed.GetType().Name }. Did you misspell it?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentValObj = member.GetValue(entity.Comp.Seed);
|
||||||
|
if (currentValObj == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (member.FieldType == typeof(float))
|
||||||
|
{
|
||||||
|
var floatVal = (float)currentValObj;
|
||||||
|
MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
|
||||||
|
member.SetValue(entity.Comp.Seed, floatVal);
|
||||||
|
}
|
||||||
|
else if (member.FieldType == typeof(int))
|
||||||
|
{
|
||||||
|
var intVal = (int)currentValObj;
|
||||||
|
MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
|
||||||
|
member.SetValue(entity.Comp.Seed, intVal);
|
||||||
|
}
|
||||||
|
else if (member.FieldType == typeof(bool))
|
||||||
|
{
|
||||||
|
var boolVal = (bool)currentValObj;
|
||||||
|
boolVal = !boolVal;
|
||||||
|
member.SetValue(entity.Comp.Seed, boolVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutate reference 'val' between 'min' and 'max' by pretending the value
|
||||||
|
// is representable by a thermometer code with 'bits' number of bits and
|
||||||
|
// randomly flipping some of them.
|
||||||
|
private void MutateFloat(ref float val, float min, float max, int bits)
|
||||||
|
{
|
||||||
|
if (min == max)
|
||||||
|
{
|
||||||
|
val = min;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting number of bits that are high, between 0 and bits.
|
||||||
|
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
||||||
|
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
||||||
|
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
||||||
|
valInt = Math.Clamp(valInt, 0, bits);
|
||||||
|
|
||||||
|
// Probability that the bit flip increases n.
|
||||||
|
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
|
||||||
|
// In other words, it tends to go to the middle.
|
||||||
|
float probIncrease = 1 - (float)valInt / bits;
|
||||||
|
int valIntMutated;
|
||||||
|
if (_random.Prob(probIncrease))
|
||||||
|
{
|
||||||
|
valIntMutated = valInt + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valIntMutated = valInt - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set value based on mutated thermometer code.
|
||||||
|
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
|
||||||
|
val = valMutated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MutateInt(ref int val, int min, int max, int bits)
|
||||||
|
{
|
||||||
|
if (min == max)
|
||||||
|
{
|
||||||
|
val = min;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting number of bits that are high, between 0 and bits.
|
||||||
|
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
||||||
|
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
||||||
|
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
||||||
|
valInt = Math.Clamp(valInt, 0, bits);
|
||||||
|
|
||||||
|
// Probability that the bit flip increases n.
|
||||||
|
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
|
||||||
|
// In other words, it tends to go to the middle.
|
||||||
|
float probIncrease = 1 - (float)valInt / bits;
|
||||||
|
int valMutated;
|
||||||
|
if (_random.Prob(probIncrease))
|
||||||
|
{
|
||||||
|
valMutated = val + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valMutated = val - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
valMutated = Math.Clamp(valMutated, min, max);
|
||||||
|
val = valMutated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantCryoxadone>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantCryoxadone> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var deviation = 0;
|
||||||
|
var seed = entity.Comp.Seed;
|
||||||
|
if (seed == null)
|
||||||
|
return;
|
||||||
|
if (entity.Comp.Age > seed.Maturation)
|
||||||
|
deviation = (int) Math.Max(seed.Maturation - 1, entity.Comp.Age - _random.Next(7, 10));
|
||||||
|
else
|
||||||
|
deviation = (int) (seed.Maturation / seed.GrowthStages);
|
||||||
|
entity.Comp.Age -= deviation;
|
||||||
|
entity.Comp.LastProduce = entity.Comp.Age;
|
||||||
|
entity.Comp.SkipAging++;
|
||||||
|
entity.Comp.ForceUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantDestroySeeds>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDestroySeeds> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (entity.Comp.Seed.Seedless)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity.Comp);
|
||||||
|
_popup.PopupEntity(
|
||||||
|
Loc.GetString("botany-plant-seedsdestroyed"),
|
||||||
|
entity,
|
||||||
|
PopupType.SmallCaution
|
||||||
|
);
|
||||||
|
entity.Comp.Seed.Seedless = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantDiethylamineEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantDiethylamine>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDiethylamine> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_random.Prob(0.1f))
|
||||||
|
{
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity);
|
||||||
|
entity.Comp.Seed!.Lifespan++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_random.Prob(0.1f))
|
||||||
|
{
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity);
|
||||||
|
entity.Comp.Seed!.Endurance++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantPhalanximine>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantPhalanximine> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.Seed.Viable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantRestoreSeeds>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
[Dependency] private readonly PopupSystem _popup = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantRestoreSeeds> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!entity.Comp.Seed.Seedless)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity.Comp);
|
||||||
|
_popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), entity);
|
||||||
|
entity.Comp.Seed.Seedless = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Server.Botany.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This effect directly increases the potency of a PlantHolder's plant provided it exists and isn't dead.
|
||||||
|
/// Potency directly correlates to the size of the plant's produce.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, RobustHarvest>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<RobustHarvest> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Dead)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (entity.Comp.Seed.Potency < args.Effect.PotencyLimit)
|
||||||
|
{
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity.Comp);
|
||||||
|
entity.Comp.Seed.Potency = Math.Min(entity.Comp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
|
||||||
|
|
||||||
|
if (entity.Comp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
|
||||||
|
{
|
||||||
|
entity.Comp.Seed.Seedless = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entity.Comp.Seed.Yield > 1 && _random.Prob(0.1f))
|
||||||
|
{
|
||||||
|
// Too much of a good thing reduces yield
|
||||||
|
_plantHolder.EnsureUniqueSeed(entity, entity.Comp);
|
||||||
|
entity.Comp.Seed.Yield--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Server.Botany;
|
||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany;
|
||||||
|
|
||||||
|
public sealed partial class PlantMutateChemicalsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateChemicals>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateChemicals> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var chemicals = entity.Comp.Seed.Chemicals;
|
||||||
|
var randomChems = _proto.Index(args.Effect.RandomPickBotanyReagent).Fills;
|
||||||
|
|
||||||
|
// Add a random amount of a random chemical to this set of chemicals
|
||||||
|
var pick = _random.Pick(randomChems);
|
||||||
|
var chemicalId = _random.Pick(pick.Reagents);
|
||||||
|
var amount = _random.Next(1, (int)pick.Quantity);
|
||||||
|
var seedChemQuantity = new SeedChemQuantity();
|
||||||
|
if (chemicals.ContainsKey(chemicalId))
|
||||||
|
{
|
||||||
|
seedChemQuantity.Min = chemicals[chemicalId].Min;
|
||||||
|
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
seedChemQuantity.Min = 1;
|
||||||
|
seedChemQuantity.Max = 1 + amount;
|
||||||
|
seedChemQuantity.Inherent = false;
|
||||||
|
}
|
||||||
|
var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
|
||||||
|
seedChemQuantity.PotencyDivisor = potencyDivisor;
|
||||||
|
chemicals[chemicalId] = seedChemQuantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany;
|
||||||
|
|
||||||
|
public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateExudeGases>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateExudeGases> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var gasses = entity.Comp.Seed.ExudeGasses;
|
||||||
|
|
||||||
|
// Add a random amount of a random gas to this gas dictionary
|
||||||
|
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
|
||||||
|
var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
||||||
|
|
||||||
|
if (!gasses.TryAdd(gas, amount))
|
||||||
|
{
|
||||||
|
gasses[gas] += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateConsumeGases>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateConsumeGases> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var gasses = entity.Comp.Seed.ConsumeGasses;
|
||||||
|
|
||||||
|
// Add a random amount of a random gas to this gas dictionary
|
||||||
|
var amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
|
||||||
|
var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
||||||
|
|
||||||
|
if (!gasses.TryAdd(gas, amount))
|
||||||
|
{
|
||||||
|
gasses[gas] += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.Botany;
|
||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany;
|
||||||
|
|
||||||
|
public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateHarvest>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateHarvest> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (entity.Comp.Seed.HarvestRepeat)
|
||||||
|
{
|
||||||
|
case HarvestType.NoRepeat:
|
||||||
|
entity.Comp.Seed.HarvestRepeat = HarvestType.Repeat;
|
||||||
|
break;
|
||||||
|
case HarvestType.Repeat:
|
||||||
|
entity.Comp.Seed.HarvestRepeat = HarvestType.SelfHarvest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Server.Botany;
|
||||||
|
using Content.Server.Botany.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Botany;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Botany;
|
||||||
|
|
||||||
|
public sealed partial class PlantMutateSpeciesChangeEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateSpeciesChange>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateSpeciesChange> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Seed == null || entity.Comp.Seed.MutationPrototypes.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var targetProto = _random.Pick(entity.Comp.Seed.MutationPrototypes);
|
||||||
|
_proto.TryIndex(targetProto, out SeedPrototype? protoSeed);
|
||||||
|
|
||||||
|
if (protoSeed == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Seed prototype could not be found: {targetProto}!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Comp.Seed = entity.Comp.Seed.SpeciesChange(protoSeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes this entity emote.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Emote>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Emote> args)
|
||||||
|
{
|
||||||
|
if (args.Effect.ShowInChat)
|
||||||
|
_chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
|
||||||
|
else
|
||||||
|
_chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Server.Ghost.Roles.Components;
|
||||||
|
using Content.Server.Speech.Components;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects;
|
||||||
|
using Content.Shared.Mind.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes this entity sentient. Allows ghost to take it over if it's not already occupied.
|
||||||
|
/// Optionally also allows this entity to speak.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class MakeSentientEntityEffectSystem : EntityEffectSystem<MetaDataComponent, MakeSentient>
|
||||||
|
{
|
||||||
|
protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<MakeSentient> args)
|
||||||
|
{
|
||||||
|
// Let affected entities speak normally to make this effect different from, say, the "random sentience" event
|
||||||
|
// This also works on entities that already have a mind
|
||||||
|
// We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
|
||||||
|
if (args.Effect.AllowSpeech)
|
||||||
|
{
|
||||||
|
RemComp<ReplacementAccentComponent>(entity);
|
||||||
|
// TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell.
|
||||||
|
RemComp<MonkeyAccentComponent>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops from adding a ghost role to things like people who already have a mind
|
||||||
|
if (TryComp<MindContainerComponent>(entity, out var mindContainer) && mindContainer.HasMind)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Don't add a ghost role to things that already have ghost roles
|
||||||
|
if (TryComp(entity, out GhostRoleComponent? ghostRole))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ghostRole = AddComp<GhostRoleComponent>(entity);
|
||||||
|
EnsureComp<GhostTakeoverAvailableComponent>(entity);
|
||||||
|
|
||||||
|
ghostRole.RoleName = entity.Comp.EntityName;
|
||||||
|
ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.Polymorph.Components;
|
||||||
|
using Content.Server.Polymorph.Systems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Polymorphs this entity into another entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class PolymorphEntityEffectSystem : EntityEffectSystem<PolymorphableComponent, Shared.EntityEffects.Effects.Polymorph>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<PolymorphableComponent> entity, ref EntityEffectEvent<Shared.EntityEffects.Effects.Polymorph> args)
|
||||||
|
{
|
||||||
|
_polymorph.PolymorphEntity(entity, args.Effect.Prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Content.Server.Fluids.EntitySystems;
|
||||||
|
using Content.Server.Spreader;
|
||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Coordinates.Helpers;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Solution;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Solution;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This effect creates smoke at this solution's position.
|
||||||
|
/// The amount of smoke created is modified by scale.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class AreaReactionEntityEffectsSystem : EntityEffectSystem<SolutionComponent, AreaReactionEffect>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||||
|
[Dependency] private readonly SmokeSystem _smoke = default!;
|
||||||
|
[Dependency] private readonly SpreaderSystem _spreader = default!;
|
||||||
|
[Dependency] private readonly TurfSystem _turf = default!;
|
||||||
|
|
||||||
|
// TODO: A sane way to make Smoke without a solution.
|
||||||
|
protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AreaReactionEffect> args)
|
||||||
|
{
|
||||||
|
var xform = Transform(entity);
|
||||||
|
var mapCoords = _xform.GetMapCoordinates(entity);
|
||||||
|
var spreadAmount = (int) Math.Max(0, Math.Ceiling(args.Scale / args.Effect.OverflowThreshold));
|
||||||
|
var effect = args.Effect;
|
||||||
|
|
||||||
|
if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
|
||||||
|
!_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_spreader.RequiresFloorToSpread(effect.PrototypeId.ToString()) && _turf.IsSpace(tileRef))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var coords = _map.MapToGrid(gridUid, mapCoords);
|
||||||
|
var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid());
|
||||||
|
|
||||||
|
_smoke.StartSmoke(ent, entity.Comp.Solution, args.Effect.Duration, spreadAmount);
|
||||||
|
|
||||||
|
_audio.PlayPvs(args.Effect.Sound, entity, AudioParams.Default.WithVariation(0.25f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Server.Explosion.EntitySystems;
|
||||||
|
using Content.Shared.EntityEffects;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Transform;
|
||||||
|
|
||||||
|
namespace Content.Server.EntityEffects.Effects.Transform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an explosion at this entity's position.
|
||||||
|
/// Intensity is modified by scale.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class ExplosionEntityEffectSystem : EntityEffectSystem<TransformComponent, ExplosionEffect>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<ExplosionEffect> args)
|
||||||
|
{
|
||||||
|
var intensity = MathF.Min(args.Effect.IntensityPerUnit * args.Scale, args.Effect.MaxTotalIntensity);
|
||||||
|
|
||||||
|
_explosion.QueueExplosion(
|
||||||
|
entity,
|
||||||
|
args.Effect.ExplosionType,
|
||||||
|
intensity,
|
||||||
|
args.Effect.IntensitySlope,
|
||||||
|
args.Effect.MaxIntensity,
|
||||||
|
args.Effect.TileBreakScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,976 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Server.Body.Components;
|
|
||||||
using Content.Server.Body.Systems;
|
|
||||||
using Content.Server.Botany.Components;
|
|
||||||
using Content.Server.Botany.Systems;
|
|
||||||
using Content.Server.Botany;
|
|
||||||
using Content.Server.Chat.Systems;
|
|
||||||
using Content.Server.Emp;
|
|
||||||
using Content.Server.Explosion.EntitySystems;
|
|
||||||
using Content.Server.Fluids.EntitySystems;
|
|
||||||
using Content.Server.Ghost.Roles.Components;
|
|
||||||
using Content.Server.Polymorph.Components;
|
|
||||||
using Content.Server.Polymorph.Systems;
|
|
||||||
using Content.Server.Speech.Components;
|
|
||||||
using Content.Server.Spreader;
|
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Server.Temperature.Systems;
|
|
||||||
using Content.Server.Zombies;
|
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Shared.Atmos.Components;
|
|
||||||
using Content.Shared.Body.Components;
|
|
||||||
using Content.Shared.Coordinates.Helpers;
|
|
||||||
using Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
using Content.Shared.EntityEffects.Effects.PlantMetabolism;
|
|
||||||
using Content.Shared.EntityEffects.Effects;
|
|
||||||
using Content.Shared.EntityEffects;
|
|
||||||
using Content.Shared.Flash;
|
|
||||||
using Content.Shared.Maps;
|
|
||||||
using Content.Shared.Medical;
|
|
||||||
using Content.Shared.Mind.Components;
|
|
||||||
using Content.Shared.Popups;
|
|
||||||
using Content.Shared.Random;
|
|
||||||
using Content.Shared.Traits.Assorted;
|
|
||||||
using Content.Shared.Zombies;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
using TemperatureCondition = Content.Shared.EntityEffects.EffectConditions.Temperature; // disambiguate the namespace
|
|
||||||
using PolymorphEffect = Content.Shared.EntityEffects.Effects.Polymorph;
|
|
||||||
|
|
||||||
namespace Content.Server.EntityEffects;
|
|
||||||
|
|
||||||
public sealed class EntityEffectSystem : EntitySystem
|
|
||||||
{
|
|
||||||
private static readonly ProtoId<WeightedRandomFillSolutionPrototype> RandomPickBotanyReagent = "RandomPickBotanyReagent";
|
|
||||||
|
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
|
||||||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
|
||||||
[Dependency] private readonly ChatSystem _chat = default!;
|
|
||||||
[Dependency] private readonly EmpSystem _emp = default!;
|
|
||||||
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
|
||||||
[Dependency] private readonly FlammableSystem _flammable = default!;
|
|
||||||
[Dependency] private readonly SharedFlashSystem _flash = default!;
|
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
|
||||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
|
||||||
[Dependency] private readonly MutationSystem _mutation = default!;
|
|
||||||
[Dependency] private readonly NarcolepsySystem _narcolepsy = default!;
|
|
||||||
[Dependency] private readonly PlantHolderSystem _plantHolder = default!;
|
|
||||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
|
||||||
[Dependency] private readonly RespiratorSystem _respirator = default!;
|
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
||||||
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
|
||||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
|
||||||
[Dependency] private readonly SmokeSystem _smoke = default!;
|
|
||||||
[Dependency] private readonly SpreaderSystem _spreader = default!;
|
|
||||||
[Dependency] private readonly TemperatureSystem _temperature = default!;
|
|
||||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
|
||||||
[Dependency] private readonly VomitSystem _vomit = default!;
|
|
||||||
[Dependency] private readonly TurfSystem _turf = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<CheckEntityEffectConditionEvent<TemperatureCondition>>(OnCheckTemperature);
|
|
||||||
SubscribeLocalEvent<CheckEntityEffectConditionEvent<Breathing>>(OnCheckBreathing);
|
|
||||||
SubscribeLocalEvent<CheckEntityEffectConditionEvent<OrganType>>(OnCheckOrganType);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustHealth>>(OnExecutePlantAdjustHealth);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationLevel>>(OnExecutePlantAdjustMutationLevel);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationMod>>(OnExecutePlantAdjustMutationMod);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustNutrition>>(OnExecutePlantAdjustNutrition);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPests>>(OnExecutePlantAdjustPests);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPotency>>(OnExecutePlantAdjustPotency);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustToxins>>(OnExecutePlantAdjustToxins);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWater>>(OnExecutePlantAdjustWater);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWeeds>>(OnExecutePlantAdjustWeeds);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAffectGrowth>>(OnExecutePlantAffectGrowth);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantChangeStat>>(OnExecutePlantChangeStat);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantCryoxadone>>(OnExecutePlantCryoxadone);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDestroySeeds>>(OnExecutePlantDestroySeeds);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDiethylamine>>(OnExecutePlantDiethylamine);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantPhalanximine>>(OnExecutePlantPhalanximine);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantRestoreSeeds>>(OnExecutePlantRestoreSeeds);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<RobustHarvest>>(OnExecuteRobustHarvest);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<AdjustTemperature>>(OnExecuteAdjustTemperature);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<AreaReactionEffect>>(OnExecuteAreaReactionEffect);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<CauseZombieInfection>>(OnExecuteCauseZombieInfection);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemCleanBloodstream>>(OnExecuteChemCleanBloodstream);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemVomit>>(OnExecuteChemVomit);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateEntityReactionEffect>>(OnExecuteCreateEntityReactionEffect);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateGas>>(OnExecuteCreateGas);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<CureZombieInfection>>(OnExecuteCureZombieInfection);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<Emote>>(OnExecuteEmote);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<EmpReactionEffect>>(OnExecuteEmpReactionEffect);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ExplosionReactionEffect>>(OnExecuteExplosionReactionEffect);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<FlammableReaction>>(OnExecuteFlammableReaction);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<FlashReactionEffect>>(OnExecuteFlashReactionEffect);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<Ignite>>(OnExecuteIgnite);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<MakeSentient>>(OnExecuteMakeSentient);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBleedAmount>>(OnExecuteModifyBleedAmount);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBloodLevel>>(OnExecuteModifyBloodLevel);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyLungGas>>(OnExecuteModifyLungGas);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<Oxygenate>>(OnExecuteOxygenate);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateChemicals>>(OnExecutePlantMutateChemicals);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateConsumeGasses>>(OnExecutePlantMutateConsumeGasses);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateExudeGasses>>(OnExecutePlantMutateExudeGasses);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateHarvest>>(OnExecutePlantMutateHarvest);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantSpeciesChange>>(OnExecutePlantSpeciesChange);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<PolymorphEffect>>(OnExecutePolymorph);
|
|
||||||
SubscribeLocalEvent<ExecuteEntityEffectEvent<ResetNarcolepsy>>(OnExecuteResetNarcolepsy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCheckTemperature(ref CheckEntityEffectConditionEvent<TemperatureCondition> args)
|
|
||||||
{
|
|
||||||
args.Result = false;
|
|
||||||
if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
|
|
||||||
{
|
|
||||||
if (temp.CurrentTemperature >= args.Condition.Min && temp.CurrentTemperature <= args.Condition.Max)
|
|
||||||
args.Result = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCheckBreathing(ref CheckEntityEffectConditionEvent<Breathing> args)
|
|
||||||
{
|
|
||||||
if (!TryComp(args.Args.TargetEntity, out RespiratorComponent? respiratorComp))
|
|
||||||
{
|
|
||||||
args.Result = !args.Condition.IsBreathing;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var breathingState = _respirator.IsBreathing((args.Args.TargetEntity, respiratorComp));
|
|
||||||
args.Result = args.Condition.IsBreathing == breathingState;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCheckOrganType(ref CheckEntityEffectConditionEvent<OrganType> args)
|
|
||||||
{
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
if (reagentArgs.OrganEntity == null)
|
|
||||||
{
|
|
||||||
args.Result = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Result = OrganCondition(args.Condition, reagentArgs.OrganEntity.Value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Someone needs to figure out how to do this for non-reagent effects.
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OrganCondition(OrganType condition, Entity<MetabolizerComponent?> metabolizer)
|
|
||||||
{
|
|
||||||
metabolizer.Comp ??= EntityManager.GetComponentOrNull<MetabolizerComponent>(metabolizer.Owner);
|
|
||||||
if (metabolizer.Comp != null
|
|
||||||
&& metabolizer.Comp.MetabolizerTypes != null
|
|
||||||
&& metabolizer.Comp.MetabolizerTypes.Contains(condition.Type))
|
|
||||||
return condition.ShouldHave;
|
|
||||||
return !condition.ShouldHave;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="plantHolder">The entity holding the plant</param>
|
|
||||||
/// <param name="plantHolderComponent">The plant holder component</param>
|
|
||||||
/// <param name="entityManager">The entity manager</param>
|
|
||||||
/// <param name="mustHaveAlivePlant">Whether to check if it has an alive plant or not</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private bool CanMetabolizePlant(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent,
|
|
||||||
bool mustHaveAlivePlant = true, bool mustHaveMutableSeed = false)
|
|
||||||
{
|
|
||||||
plantHolderComponent = null;
|
|
||||||
|
|
||||||
if (!TryComp(plantHolder, out plantHolderComponent))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (mustHaveMutableSeed && (plantHolderComponent.Seed == null || plantHolderComponent.Seed.Immutable))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustHealth(ref ExecuteEntityEffectEvent<PlantAdjustHealth> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.Health += args.Effect.Amount;
|
|
||||||
_plantHolder.CheckHealth(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustMutationLevel(ref ExecuteEntityEffectEvent<PlantAdjustMutationLevel> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.MutationLevel += args.Effect.Amount * plantHolderComp.MutationMod;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustMutationMod(ref ExecuteEntityEffectEvent<PlantAdjustMutationMod> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.MutationMod += args.Effect.Amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustNutrition(ref ExecuteEntityEffectEvent<PlantAdjustNutrition> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_plantHolder.AdjustNutrient(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustPests(ref ExecuteEntityEffectEvent<PlantAdjustPests> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.PestLevel += args.Effect.Amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustPotency(ref ExecuteEntityEffectEvent<PlantAdjustPotency> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + args.Effect.Amount, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustToxins(ref ExecuteEntityEffectEvent<PlantAdjustToxins> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.Toxins += args.Effect.Amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustWater(ref ExecuteEntityEffectEvent<PlantAdjustWater> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_plantHolder.AdjustWater(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAdjustWeeds(ref ExecuteEntityEffectEvent<PlantAdjustWeeds> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.WeedLevel += args.Effect.Amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantAffectGrowth(ref ExecuteEntityEffectEvent<PlantAffectGrowth> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_plantHolder.AffectGrowth(args.Args.TargetEntity, (int) args.Effect.Amount, plantHolderComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutate reference 'val' between 'min' and 'max' by pretending the value
|
|
||||||
// is representable by a thermometer code with 'bits' number of bits and
|
|
||||||
// randomly flipping some of them.
|
|
||||||
private void MutateFloat(ref float val, float min, float max, int bits)
|
|
||||||
{
|
|
||||||
if (min == max)
|
|
||||||
{
|
|
||||||
val = min;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting number of bits that are high, between 0 and bits.
|
|
||||||
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
|
||||||
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
|
||||||
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
|
||||||
valInt = Math.Clamp(valInt, 0, bits);
|
|
||||||
|
|
||||||
// Probability that the bit flip increases n.
|
|
||||||
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
|
|
||||||
// In other words, it tends to go to the middle.
|
|
||||||
float probIncrease = 1 - (float)valInt / bits;
|
|
||||||
int valIntMutated;
|
|
||||||
if (_random.Prob(probIncrease))
|
|
||||||
{
|
|
||||||
valIntMutated = valInt + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
valIntMutated = valInt - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set value based on mutated thermometer code.
|
|
||||||
float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
|
|
||||||
val = valMutated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MutateInt(ref int val, int min, int max, int bits)
|
|
||||||
{
|
|
||||||
if (min == max)
|
|
||||||
{
|
|
||||||
val = min;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting number of bits that are high, between 0 and bits.
|
|
||||||
// In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
|
|
||||||
int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
|
|
||||||
// val may be outside the range of min/max due to starting prototype values, so clamp.
|
|
||||||
valInt = Math.Clamp(valInt, 0, bits);
|
|
||||||
|
|
||||||
// Probability that the bit flip increases n.
|
|
||||||
// The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
|
|
||||||
// In other words, it tends to go to the middle.
|
|
||||||
float probIncrease = 1 - (float)valInt / bits;
|
|
||||||
int valMutated;
|
|
||||||
if (_random.Prob(probIncrease))
|
|
||||||
{
|
|
||||||
valMutated = val + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
valMutated = val - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
valMutated = Math.Clamp(valMutated, min, max);
|
|
||||||
val = valMutated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantChangeStat(ref ExecuteEntityEffectEvent<PlantChangeStat> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var member = plantHolderComp.Seed.GetType().GetField(args.Effect.TargetValue);
|
|
||||||
|
|
||||||
if (member == null)
|
|
||||||
{
|
|
||||||
_mutation.Log.Error(args.Effect.GetType().Name + " Error: Member " + args.Effect.TargetValue + " not found on " + plantHolderComp.Seed.GetType().Name + ". Did you misspell it?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentValObj = member.GetValue(plantHolderComp.Seed);
|
|
||||||
if (currentValObj == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (member.FieldType == typeof(float))
|
|
||||||
{
|
|
||||||
var floatVal = (float)currentValObj;
|
|
||||||
MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
|
|
||||||
member.SetValue(plantHolderComp.Seed, floatVal);
|
|
||||||
}
|
|
||||||
else if (member.FieldType == typeof(int))
|
|
||||||
{
|
|
||||||
var intVal = (int)currentValObj;
|
|
||||||
MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
|
|
||||||
member.SetValue(plantHolderComp.Seed, intVal);
|
|
||||||
}
|
|
||||||
else if (member.FieldType == typeof(bool))
|
|
||||||
{
|
|
||||||
var boolVal = (bool)currentValObj;
|
|
||||||
boolVal = !boolVal;
|
|
||||||
member.SetValue(plantHolderComp.Seed, boolVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantCryoxadone(ref ExecuteEntityEffectEvent<PlantCryoxadone> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var deviation = 0;
|
|
||||||
var seed = plantHolderComp.Seed;
|
|
||||||
if (seed == null)
|
|
||||||
return;
|
|
||||||
if (plantHolderComp.Age > seed.Maturation)
|
|
||||||
deviation = (int) Math.Max(seed.Maturation - 1, plantHolderComp.Age - _random.Next(7, 10));
|
|
||||||
else
|
|
||||||
deviation = (int) (seed.Maturation / seed.GrowthStages);
|
|
||||||
plantHolderComp.Age -= deviation;
|
|
||||||
plantHolderComp.LastProduce = plantHolderComp.Age;
|
|
||||||
plantHolderComp.SkipAging++;
|
|
||||||
plantHolderComp.ForceUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantDestroySeeds(ref ExecuteEntityEffectEvent<PlantDestroySeeds> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed!.Seedless == false)
|
|
||||||
{
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
_popup.PopupEntity(
|
|
||||||
Loc.GetString("botany-plant-seedsdestroyed"),
|
|
||||||
args.Args.TargetEntity,
|
|
||||||
PopupType.SmallCaution
|
|
||||||
);
|
|
||||||
plantHolderComp.Seed.Seedless = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantDiethylamine(ref ExecuteEntityEffectEvent<PlantDiethylamine> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_random.Prob(0.1f))
|
|
||||||
{
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
plantHolderComp.Seed!.Lifespan++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_random.Prob(0.1f))
|
|
||||||
{
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
plantHolderComp.Seed!.Endurance++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantPhalanximine(ref ExecuteEntityEffectEvent<PlantPhalanximine> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
plantHolderComp.Seed!.Viable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantRestoreSeeds(ref ExecuteEntityEffectEvent<PlantRestoreSeeds> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed!.Seedless)
|
|
||||||
{
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
_popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.Args.TargetEntity);
|
|
||||||
plantHolderComp.Seed.Seedless = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteRobustHarvest(ref ExecuteEntityEffectEvent<RobustHarvest> args)
|
|
||||||
{
|
|
||||||
if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed.Potency < args.Effect.PotencyLimit)
|
|
||||||
{
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
|
|
||||||
|
|
||||||
if (plantHolderComp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
|
|
||||||
{
|
|
||||||
plantHolderComp.Seed.Seedless = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (plantHolderComp.Seed.Yield > 1 && _random.Prob(0.1f))
|
|
||||||
{
|
|
||||||
// Too much of a good thing reduces yield
|
|
||||||
_plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
|
|
||||||
plantHolderComp.Seed.Yield--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteAdjustTemperature(ref ExecuteEntityEffectEvent<AdjustTemperature> args)
|
|
||||||
{
|
|
||||||
if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
|
|
||||||
{
|
|
||||||
var amount = args.Effect.Amount;
|
|
||||||
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
amount *= reagentArgs.Scale.Float();
|
|
||||||
}
|
|
||||||
|
|
||||||
_temperature.ChangeHeat(args.Args.TargetEntity, amount, true, temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteAreaReactionEffect(ref ExecuteEntityEffectEvent<AreaReactionEffect> args)
|
|
||||||
{
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
if (reagentArgs.Source == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var spreadAmount = (int) Math.Max(0, Math.Ceiling((reagentArgs.Quantity / args.Effect.OverflowThreshold).Float()));
|
|
||||||
var splitSolution = reagentArgs.Source.SplitSolution(reagentArgs.Source.Volume);
|
|
||||||
var transform = Comp<TransformComponent>(reagentArgs.TargetEntity);
|
|
||||||
var mapCoords = _xform.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform);
|
|
||||||
|
|
||||||
if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
|
|
||||||
!_map.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_spreader.RequiresFloorToSpread(args.Effect.PrototypeId) && _turf.IsSpace(tileRef))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var coords = _map.MapToGrid(gridUid, mapCoords);
|
|
||||||
var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid());
|
|
||||||
|
|
||||||
_smoke.StartSmoke(ent, splitSolution, args.Effect.Duration, spreadAmount);
|
|
||||||
|
|
||||||
_audio.PlayPvs(args.Effect.Sound, reagentArgs.TargetEntity, AudioParams.Default.WithVariation(0.25f));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Someone needs to figure out how to do this for non-reagent effects.
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteCauseZombieInfection(ref ExecuteEntityEffectEvent<CauseZombieInfection> args)
|
|
||||||
{
|
|
||||||
EnsureComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
|
|
||||||
EnsureComp<PendingZombieComponent>(args.Args.TargetEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteChemCleanBloodstream(ref ExecuteEntityEffectEvent<ChemCleanBloodstream> args)
|
|
||||||
{
|
|
||||||
var cleanseRate = args.Effect.CleanseRate;
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
if (reagentArgs.Source == null || reagentArgs.Reagent == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
cleanseRate *= reagentArgs.Scale.Float();
|
|
||||||
_bloodstream.FlushChemicals(args.Args.TargetEntity, reagentArgs.Reagent, cleanseRate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_bloodstream.FlushChemicals(args.Args.TargetEntity, null, cleanseRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteChemVomit(ref ExecuteEntityEffectEvent<ChemVomit> args)
|
|
||||||
{
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
if (reagentArgs.Scale != 1f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_vomit.Vomit(args.Args.TargetEntity, args.Effect.ThirstAmount, args.Effect.HungerAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteCreateEntityReactionEffect(ref ExecuteEntityEffectEvent<CreateEntityReactionEffect> args)
|
|
||||||
{
|
|
||||||
var transform = Comp<TransformComponent>(args.Args.TargetEntity);
|
|
||||||
var quantity = (int)args.Effect.Number;
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
quantity *= reagentArgs.Quantity.Int();
|
|
||||||
|
|
||||||
for (var i = 0; i < quantity; i++)
|
|
||||||
{
|
|
||||||
var uid = Spawn(args.Effect.Entity, _xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform));
|
|
||||||
_xform.AttachToGridOrMap(uid);
|
|
||||||
|
|
||||||
// TODO figure out how to properly spawn inside of containers
|
|
||||||
// e.g. cheese:
|
|
||||||
// if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user.
|
|
||||||
// if reaction happens in a backpack, should insert cheese into backpack.
|
|
||||||
// --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players).
|
|
||||||
// if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that.
|
|
||||||
// presumably having cheese materialize inside of your blood would have "disadvantages".
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteCreateGas(ref ExecuteEntityEffectEvent<CreateGas> args)
|
|
||||||
{
|
|
||||||
var tileMix = _atmosphere.GetContainingMixture(args.Args.TargetEntity, false, true);
|
|
||||||
|
|
||||||
if (tileMix != null)
|
|
||||||
{
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
tileMix.AdjustMoles(args.Effect.Gas, reagentArgs.Quantity.Float() * args.Effect.Multiplier);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tileMix.AdjustMoles(args.Effect.Gas, args.Effect.Multiplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteCureZombieInfection(ref ExecuteEntityEffectEvent<CureZombieInfection> args)
|
|
||||||
{
|
|
||||||
if (HasComp<IncurableZombieComponent>(args.Args.TargetEntity))
|
|
||||||
return;
|
|
||||||
|
|
||||||
RemComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
|
|
||||||
RemComp<PendingZombieComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
if (args.Effect.Innoculate)
|
|
||||||
{
|
|
||||||
EnsureComp<ZombieImmuneComponent>(args.Args.TargetEntity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteEmote(ref ExecuteEntityEffectEvent<Emote> args)
|
|
||||||
{
|
|
||||||
if (args.Effect.EmoteId == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (args.Effect.ShowInChat)
|
|
||||||
_chat.TryEmoteWithChat(args.Args.TargetEntity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
|
|
||||||
else
|
|
||||||
_chat.TryEmoteWithoutChat(args.Args.TargetEntity, args.Effect.EmoteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteEmpReactionEffect(ref ExecuteEntityEffectEvent<EmpReactionEffect> args)
|
|
||||||
{
|
|
||||||
var transform = Comp<TransformComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
var range = args.Effect.EmpRangePerUnit;
|
|
||||||
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
range = MathF.Min((float) (reagentArgs.Quantity * args.Effect.EmpRangePerUnit), args.Effect.EmpMaxRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
_emp.EmpPulse(_xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform),
|
|
||||||
range,
|
|
||||||
args.Effect.EnergyConsumption,
|
|
||||||
args.Effect.DisableDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteExplosionReactionEffect(ref ExecuteEntityEffectEvent<ExplosionReactionEffect> args)
|
|
||||||
{
|
|
||||||
var intensity = args.Effect.IntensityPerUnit;
|
|
||||||
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
intensity = MathF.Min((float) reagentArgs.Quantity * args.Effect.IntensityPerUnit, args.Effect.MaxTotalIntensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
_explosion.QueueExplosion(
|
|
||||||
args.Args.TargetEntity,
|
|
||||||
args.Effect.ExplosionType,
|
|
||||||
intensity,
|
|
||||||
args.Effect.IntensitySlope,
|
|
||||||
args.Effect.MaxIntensity,
|
|
||||||
args.Effect.TileBreakScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteFlammableReaction(ref ExecuteEntityEffectEvent<FlammableReaction> args)
|
|
||||||
{
|
|
||||||
if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Sets the multiplier for FireStacks to MultiplierOnExisting is 0 or greater and target already has FireStacks
|
|
||||||
var multiplier = flammable.FireStacks != 0f && args.Effect.MultiplierOnExisting >= 0 ? args.Effect.MultiplierOnExisting : args.Effect.Multiplier;
|
|
||||||
var quantity = 1f;
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
quantity = reagentArgs.Quantity.Float();
|
|
||||||
_flammable.AdjustFireStacks(args.Args.TargetEntity, quantity * multiplier, flammable);
|
|
||||||
if (reagentArgs.Reagent != null)
|
|
||||||
reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_flammable.AdjustFireStacks(args.Args.TargetEntity, multiplier, flammable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteFlashReactionEffect(ref ExecuteEntityEffectEvent<FlashReactionEffect> args)
|
|
||||||
{
|
|
||||||
var transform = Comp<TransformComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
var range = 1f;
|
|
||||||
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
range = MathF.Min((float)(reagentArgs.Quantity * args.Effect.RangePerUnit), args.Effect.MaxRange);
|
|
||||||
|
|
||||||
_flash.FlashArea(
|
|
||||||
args.Args.TargetEntity,
|
|
||||||
null,
|
|
||||||
range,
|
|
||||||
args.Effect.Duration,
|
|
||||||
slowTo: args.Effect.SlowTo,
|
|
||||||
sound: args.Effect.Sound);
|
|
||||||
|
|
||||||
if (args.Effect.FlashEffectPrototype == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var uid = EntityManager.SpawnEntity(args.Effect.FlashEffectPrototype, _xform.GetMapCoordinates(transform));
|
|
||||||
_xform.AttachToGridOrMap(uid);
|
|
||||||
|
|
||||||
if (!TryComp<PointLightComponent>(uid, out var pointLightComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_pointLight.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteIgnite(ref ExecuteEntityEffectEvent<Ignite> args)
|
|
||||||
{
|
|
||||||
if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
_flammable.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_flammable.Ignite(args.Args.TargetEntity, args.Args.TargetEntity, flammable: flammable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteMakeSentient(ref ExecuteEntityEffectEvent<MakeSentient> args)
|
|
||||||
{
|
|
||||||
var uid = args.Args.TargetEntity;
|
|
||||||
|
|
||||||
// Let affected entities speak normally to make this effect different from, say, the "random sentience" event
|
|
||||||
// This also works on entities that already have a mind
|
|
||||||
// We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
|
|
||||||
RemComp<ReplacementAccentComponent>(uid);
|
|
||||||
RemComp<MonkeyAccentComponent>(uid);
|
|
||||||
|
|
||||||
// Stops from adding a ghost role to things like people who already have a mind
|
|
||||||
if (TryComp<MindContainerComponent>(uid, out var mindContainer) && mindContainer.HasMind)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't add a ghost role to things that already have ghost roles
|
|
||||||
if (TryComp(uid, out GhostRoleComponent? ghostRole))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ghostRole = AddComp<GhostRoleComponent>(uid);
|
|
||||||
EnsureComp<GhostTakeoverAvailableComponent>(uid);
|
|
||||||
|
|
||||||
var entityData = Comp<MetaDataComponent>(uid);
|
|
||||||
ghostRole.RoleName = entityData.EntityName;
|
|
||||||
ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteModifyBleedAmount(ref ExecuteEntityEffectEvent<ModifyBleedAmount> args)
|
|
||||||
{
|
|
||||||
if (TryComp<BloodstreamComponent>(args.Args.TargetEntity, out var blood))
|
|
||||||
{
|
|
||||||
var amt = args.Effect.Amount;
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs) {
|
|
||||||
if (args.Effect.Scaled)
|
|
||||||
amt *= reagentArgs.Quantity.Float();
|
|
||||||
amt *= reagentArgs.Scale.Float();
|
|
||||||
}
|
|
||||||
|
|
||||||
_bloodstream.TryModifyBleedAmount((args.Args.TargetEntity, blood), amt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteModifyBloodLevel(ref ExecuteEntityEffectEvent<ModifyBloodLevel> args)
|
|
||||||
{
|
|
||||||
if (TryComp<BloodstreamComponent>(args.Args.TargetEntity, out var blood))
|
|
||||||
{
|
|
||||||
var amt = args.Effect.Amount;
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
if (args.Effect.Scaled)
|
|
||||||
amt *= reagentArgs.Quantity;
|
|
||||||
amt *= reagentArgs.Scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bloodstream.TryModifyBloodLevel((args.Args.TargetEntity, blood), amt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteModifyLungGas(ref ExecuteEntityEffectEvent<ModifyLungGas> args)
|
|
||||||
{
|
|
||||||
LungComponent? lung;
|
|
||||||
float amount = 1f;
|
|
||||||
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
if (!TryComp<LungComponent>(reagentArgs.OrganEntity, out var organLung))
|
|
||||||
return;
|
|
||||||
lung = organLung;
|
|
||||||
amount = reagentArgs.Quantity.Float();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!TryComp<LungComponent>(args.Args.TargetEntity, out var organLung)) //Likely needs to be modified to ensure it works correctly
|
|
||||||
return;
|
|
||||||
lung = organLung;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lung != null)
|
|
||||||
{
|
|
||||||
foreach (var (gas, ratio) in args.Effect.Ratios)
|
|
||||||
{
|
|
||||||
var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier;
|
|
||||||
if (quantity < 0)
|
|
||||||
quantity = Math.Max(quantity, -lung.Air[(int) gas]);
|
|
||||||
lung.Air.AdjustMoles(gas, quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteOxygenate(ref ExecuteEntityEffectEvent<Oxygenate> args)
|
|
||||||
{
|
|
||||||
var multiplier = 1f;
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
multiplier = reagentArgs.Quantity.Float();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryComp<RespiratorComponent>(args.Args.TargetEntity, out var resp))
|
|
||||||
{
|
|
||||||
_respirator.UpdateSaturation(args.Args.TargetEntity, multiplier * args.Effect.Factor, resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantMutateChemicals(ref ExecuteEntityEffectEvent<PlantMutateChemicals> args)
|
|
||||||
{
|
|
||||||
var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
if (plantholder.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var chemicals = plantholder.Seed.Chemicals;
|
|
||||||
var randomChems = _protoManager.Index(RandomPickBotanyReagent).Fills;
|
|
||||||
|
|
||||||
// Add a random amount of a random chemical to this set of chemicals
|
|
||||||
if (randomChems != null)
|
|
||||||
{
|
|
||||||
var pick = _random.Pick<RandomFillSolution>(randomChems);
|
|
||||||
var chemicalId = _random.Pick(pick.Reagents);
|
|
||||||
var amount = _random.Next(1, (int)pick.Quantity);
|
|
||||||
var seedChemQuantity = new SeedChemQuantity();
|
|
||||||
if (chemicals.ContainsKey(chemicalId))
|
|
||||||
{
|
|
||||||
seedChemQuantity.Min = chemicals[chemicalId].Min;
|
|
||||||
seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
seedChemQuantity.Min = 1;
|
|
||||||
seedChemQuantity.Max = 1 + amount;
|
|
||||||
seedChemQuantity.Inherent = false;
|
|
||||||
}
|
|
||||||
var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
|
|
||||||
seedChemQuantity.PotencyDivisor = potencyDivisor;
|
|
||||||
chemicals[chemicalId] = seedChemQuantity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantMutateConsumeGasses(ref ExecuteEntityEffectEvent<PlantMutateConsumeGasses> args)
|
|
||||||
{
|
|
||||||
var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
if (plantholder.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var gasses = plantholder.Seed.ConsumeGasses;
|
|
||||||
|
|
||||||
// Add a random amount of a random gas to this gas dictionary
|
|
||||||
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
|
|
||||||
Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
|
||||||
if (gasses.ContainsKey(gas))
|
|
||||||
{
|
|
||||||
gasses[gas] += amount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gasses.Add(gas, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantMutateExudeGasses(ref ExecuteEntityEffectEvent<PlantMutateExudeGasses> args)
|
|
||||||
{
|
|
||||||
var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
if (plantholder.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var gasses = plantholder.Seed.ExudeGasses;
|
|
||||||
|
|
||||||
// Add a random amount of a random gas to this gas dictionary
|
|
||||||
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
|
|
||||||
Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
|
|
||||||
if (gasses.ContainsKey(gas))
|
|
||||||
{
|
|
||||||
gasses[gas] += amount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gasses.Add(gas, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantMutateHarvest(ref ExecuteEntityEffectEvent<PlantMutateHarvest> args)
|
|
||||||
{
|
|
||||||
var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
|
|
||||||
|
|
||||||
if (plantholder.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat)
|
|
||||||
plantholder.Seed.HarvestRepeat = HarvestType.Repeat;
|
|
||||||
else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat)
|
|
||||||
plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePlantSpeciesChange(ref ExecuteEntityEffectEvent<PlantSpeciesChange> args)
|
|
||||||
{
|
|
||||||
var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
|
|
||||||
if (plantholder.Seed == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (plantholder.Seed.MutationPrototypes.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var targetProto = _random.Pick(plantholder.Seed.MutationPrototypes);
|
|
||||||
if (!_protoManager.TryIndex(targetProto, out SeedPrototype? protoSeed))
|
|
||||||
{
|
|
||||||
Log.Error($"Seed prototype could not be found: {targetProto}!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecutePolymorph(ref ExecuteEntityEffectEvent<PolymorphEffect> args)
|
|
||||||
{
|
|
||||||
// Make it into a prototype
|
|
||||||
EnsureComp<PolymorphableComponent>(args.Args.TargetEntity);
|
|
||||||
_polymorph.PolymorphEntity(args.Args.TargetEntity, args.Effect.PolymorphPrototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnExecuteResetNarcolepsy(ref ExecuteEntityEffectEvent<ResetNarcolepsy> args)
|
|
||||||
{
|
|
||||||
if (args.Args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
if (reagentArgs.Scale != 1f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_narcolepsy.AdjustNarcolepsyTimer(args.Args.TargetEntity, args.Effect.TimerReset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Solution;
|
||||||
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
|
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
|
||||||
|
|
||||||
namespace Content.Server.Fluids.EntitySystems;
|
namespace Content.Server.Fluids.EntitySystems;
|
||||||
@@ -278,11 +278,10 @@ public sealed class SmokeSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
if (reagentQuantity.Quantity == FixedPoint2.Zero)
|
if (reagentQuantity.Quantity == FixedPoint2.Zero)
|
||||||
continue;
|
continue;
|
||||||
var reagentProto = _prototype.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
|
|
||||||
|
|
||||||
_reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentProto, reagentQuantity, transferSolution);
|
_reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentQuantity);
|
||||||
if (!blockIngestion)
|
if (!blockIngestion)
|
||||||
_reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentProto, reagentQuantity, transferSolution);
|
_reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentQuantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockIngestion)
|
if (blockIngestion)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Text.Json.Serialization;
|
|||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.EntityConditions;
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -42,7 +43,7 @@ public sealed class ChemistryJsonGenerator
|
|||||||
Converters =
|
Converters =
|
||||||
{
|
{
|
||||||
new UniversalJsonConverter<EntityEffect>(),
|
new UniversalJsonConverter<EntityEffect>(),
|
||||||
new UniversalJsonConverter<EntityEffectCondition>(),
|
new UniversalJsonConverter<EntityCondition>(),
|
||||||
new UniversalJsonConverter<ReagentEffectsEntry>(),
|
new UniversalJsonConverter<ReagentEffectsEntry>(),
|
||||||
new UniversalJsonConverter<DamageSpecifier>(),
|
new UniversalJsonConverter<DamageSpecifier>(),
|
||||||
new FixedPointJsonConverter()
|
new FixedPointJsonConverter()
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public sealed class ReactionEntry
|
|||||||
proto.Products
|
proto.Products
|
||||||
.Select(x => KeyValuePair.Create(x.Key, x.Value.Float()))
|
.Select(x => KeyValuePair.Create(x.Key, x.Value.Float()))
|
||||||
.ToDictionary(x => x.Key, x => x.Value);
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
Effects = proto.Effects;
|
Effects = proto.Effects.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ using Content.Shared.Stacks;
|
|||||||
using Content.Server.Construction.Components;
|
using Content.Server.Construction.Components;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Kitchen.EntitySystems
|
namespace Content.Server.Kitchen.EntitySystems
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ using Content.Server.Medical.Components;
|
|||||||
using Content.Server.NodeContainer.EntitySystems;
|
using Content.Server.NodeContainer.EntitySystems;
|
||||||
using Content.Server.NodeContainer.NodeGroups;
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
using Content.Server.NodeContainer.Nodes;
|
using Content.Server.NodeContainer.Nodes;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Medical.Cryogenics;
|
using Content.Shared.Medical.Cryogenics;
|
||||||
using Content.Shared.MedicalScanner;
|
using Content.Shared.MedicalScanner;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Content.Shared.UserInterface;
|
using Content.Shared.UserInterface;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Content.Server.Medical.Components;
|
using Content.Server.Medical.Components;
|
||||||
using Content.Server.PowerCell;
|
using Content.Server.PowerCell;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
@@ -13,6 +12,7 @@ using Content.Shared.Item.ItemToggle.Components;
|
|||||||
using Content.Shared.MedicalScanner;
|
using Content.Shared.MedicalScanner;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Popups;
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
using Content.Shared.Traits.Assorted;
|
using Content.Shared.Traits.Assorted;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Content.Server.NPC.Queries.Considerations;
|
|||||||
using Content.Server.NPC.Queries.Curves;
|
using Content.Server.NPC.Queries.Curves;
|
||||||
using Content.Server.NPC.Queries.Queries;
|
using Content.Server.NPC.Queries.Queries;
|
||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
@@ -30,6 +29,7 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using Content.Shared.Atmos.Components;
|
using Content.Shared.Atmos.Components;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
|
|
||||||
namespace Content.Server.NPC.Systems;
|
namespace Content.Server.NPC.Systems;
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ using Content.Shared.Database;
|
|||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.Rejuvenate;
|
using Content.Shared.Rejuvenate;
|
||||||
using Content.Shared.Temperature;
|
using Content.Shared.Temperature;
|
||||||
using Robust.Shared.Physics.Components;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Physics.Events;
|
|
||||||
using Content.Shared.Projectiles;
|
using Content.Shared.Projectiles;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
|
using Content.Shared.Temperature.Systems;
|
||||||
|
|
||||||
namespace Content.Server.Temperature.Systems;
|
namespace Content.Server.Temperature.Systems;
|
||||||
|
|
||||||
public sealed class TemperatureSystem : EntitySystem
|
public sealed class TemperatureSystem : SharedTemperatureSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||||
@@ -125,8 +125,7 @@ public sealed class TemperatureSystem : EntitySystem
|
|||||||
true);
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false,
|
public override void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null)
|
||||||
TemperatureComponent? temperature = null)
|
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref temperature, false))
|
if (!Resolve(uid, ref temperature, false))
|
||||||
return;
|
return;
|
||||||
@@ -161,16 +160,6 @@ public sealed class TemperatureSystem : EntitySystem
|
|||||||
ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
|
ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0)
|
|
||||||
{
|
|
||||||
return Atmospherics.MinimumHeatCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return comp.SpecificHeat * physics.FixturesMass;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnInit(EntityUid uid, InternalTemperatureComponent comp, MapInitEvent args)
|
private void OnInit(EntityUid uid, InternalTemperatureComponent comp, MapInitEvent args)
|
||||||
{
|
{
|
||||||
if (!TryComp<TemperatureComponent>(uid, out var temp))
|
if (!TryComp<TemperatureComponent>(uid, out var temp))
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ public sealed partial class TileEntityEffectComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of effects that should be applied.
|
/// List of effects that should be applied.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
[DataField]
|
||||||
public List<EntityEffect> Effects = default!;
|
public List<EntityEffect> Effects = default!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using Content.Server.Atmos.Components;
|
|
||||||
using Content.Server.Atmos.EntitySystems;
|
|
||||||
using Content.Shared.StepTrigger.Systems;
|
using Content.Shared.StepTrigger.Systems;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
|
|
||||||
namespace Content.Server.Tiles;
|
namespace Content.Server.Tiles;
|
||||||
|
|
||||||
public sealed class TileEntityEffectSystem : EntitySystem
|
public sealed class TileEntityEffectSystem : EntitySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -23,11 +21,7 @@ public sealed class TileEntityEffectSystem : EntitySystem
|
|||||||
private void OnTileStepTriggered(Entity<TileEntityEffectComponent> ent, ref StepTriggeredOffEvent args)
|
private void OnTileStepTriggered(Entity<TileEntityEffectComponent> ent, ref StepTriggeredOffEvent args)
|
||||||
{
|
{
|
||||||
var otherUid = args.Tripper;
|
var otherUid = args.Tripper;
|
||||||
var effectArgs = new EntityEffectBaseArgs(otherUid, EntityManager);
|
|
||||||
|
|
||||||
foreach (var effect in ent.Comp.Effects)
|
_entityEffects.ApplyEffects(otherUid, ent.Comp.Effects.ToArray());
|
||||||
{
|
|
||||||
effect.Effect(effectArgs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using Content.Server.NPC.HTN;
|
|||||||
using Content.Server.NPC.Systems;
|
using Content.Server.NPC.Systems;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
using Content.Server.Temperature.Components;
|
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.CombatMode;
|
using Content.Shared.CombatMode;
|
||||||
@@ -44,6 +43,7 @@ using Robust.Shared.Player;
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Content.Shared.NPC.Prototypes;
|
using Content.Shared.NPC.Prototypes;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
|
|
||||||
namespace Content.Server.Zombies;
|
namespace Content.Server.Zombies;
|
||||||
|
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ public enum LogType
|
|||||||
ChemicalReaction = 17,
|
ChemicalReaction = 17,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reagent effects related interactions.
|
/// EntityEffect related interactions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ReagentEffect = 18,
|
EntityEffect = 18,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Canister valve was opened or closed.
|
/// Canister valve was opened or closed.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public sealed partial class LungComponent : Component
|
|||||||
/// The name/key of the solution on this entity which these lungs act on.
|
/// The name/key of the solution on this entity which these lungs act on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public string SolutionName = LungSystem.LungSolutionName;
|
public string SolutionName = "Lung";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The solution on this entity that these lungs act on.
|
/// The solution on this entity that these lungs act on.
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Atmos.Components;
|
using Content.Shared.Atmos.Components;
|
||||||
using Content.Shared.Atmos.EntitySystems;
|
using Content.Shared.Atmos.EntitySystems;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.Chemistry.EntitySystems;
|
using Content.Shared.Body.Prototypes;
|
||||||
using Content.Shared.Atmos;
|
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Clothing;
|
using Content.Shared.Chemistry.EntitySystems;
|
||||||
using Content.Shared.Inventory.Events;
|
using Content.Shared.Inventory.Events;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using BreathToolComponent = Content.Shared.Atmos.Components.BreathToolComponent;
|
||||||
|
using InternalsComponent = Content.Shared.Body.Components.InternalsComponent;
|
||||||
|
|
||||||
namespace Content.Shared.Body.Systems;
|
namespace Content.Shared.Body.Systems;
|
||||||
|
|
||||||
@@ -15,8 +18,6 @@ public sealed class LungSystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedInternalsSystem _internals = default!;
|
[Dependency] private readonly SharedInternalsSystem _internals = default!;
|
||||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
|
|
||||||
public static string LungSolutionName = "Lung";
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -53,6 +54,7 @@ public sealed class LungSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: JUST METABOLIZE GASES DIRECTLY DON'T CONVERT TO REAGENTS!!! (Needs Metabolism refactor :B)
|
||||||
public void GasToReagent(EntityUid uid, LungComponent lung)
|
public void GasToReagent(EntityUid uid, LungComponent lung)
|
||||||
{
|
{
|
||||||
if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution))
|
if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution))
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ using Content.Shared.Chemistry.EntitySystems;
|
|||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.EntityEffects.Effects;
|
using Content.Shared.EntityEffects.Effects.Solution;
|
||||||
|
using Content.Shared.EntityEffects.Effects.Transform;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Fluids;
|
using Content.Shared.Fluids;
|
||||||
using Content.Shared.Forensics.Components;
|
using Content.Shared.Forensics.Components;
|
||||||
@@ -149,7 +150,9 @@ public abstract class SharedBloodstreamSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
switch (effect)
|
switch (effect)
|
||||||
{
|
{
|
||||||
case CreateEntityReactionEffect: // Prevent entities from spawning in the bloodstream
|
// TODO: Rather than this, ReactionAttempt should allow systems to remove effects from the list before the reaction.
|
||||||
|
// TODO: I think there's a PR up on the repo for this and if there isn't I'll make one -Princess
|
||||||
|
case EntityEffects.Effects.EntitySpawning.SpawnEntity: // Prevent entities from spawning in the bloodstream
|
||||||
case AreaReactionEffect: // No spontaneous smoke or foam leaking out of blood vessels.
|
case AreaReactionEffect: // No spontaneous smoke or foam leaking out of blood vessels.
|
||||||
args.Cancelled = true;
|
args.Cancelled = true;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -707,6 +707,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Thermal energy and temperature management.
|
// Thermal energy and temperature management.
|
||||||
|
// TODO: ENERGY CONSERVATION!!! Nuke this once we have HeatContainers and use methods which properly conserve energy and model heat transfer correctly!
|
||||||
|
|
||||||
#region Thermal Energy and Temperature
|
#region Thermal Energy and Temperature
|
||||||
|
|
||||||
@@ -763,6 +764,26 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
|
|||||||
UpdateChemicals(soln);
|
UpdateChemicals(soln);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Same as <see cref="AddThermalEnergy"/> but clamps the value between two temperature values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="soln">Solution we're adjusting the energy of</param>
|
||||||
|
/// <param name="thermalEnergy">Thermal energy we're adding or removing</param>
|
||||||
|
/// <param name="min">Min desired temperature</param>
|
||||||
|
/// <param name="max">Max desired temperature</param>
|
||||||
|
public void AddThermalEnergyClamped(Entity<SolutionComponent> soln, float thermalEnergy, float min, float max)
|
||||||
|
{
|
||||||
|
var solution = soln.Comp.Solution;
|
||||||
|
|
||||||
|
if (thermalEnergy == 0.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var heatCap = solution.GetHeatCapacity(PrototypeManager);
|
||||||
|
var deltaT = thermalEnergy / heatCap;
|
||||||
|
solution.Temperature = Math.Clamp(solution.Temperature + deltaT, min, max);
|
||||||
|
UpdateChemicals(soln);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion Thermal Energy and Temperature
|
#endregion Thermal Energy and Temperature
|
||||||
|
|
||||||
#region Event Handlers
|
#region Event Handlers
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace Content.Shared.Chemistry.Reaction
|
|||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
|
[Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A cache of all reactions indexed by at most ONE of their required reactants.
|
/// A cache of all reactions indexed by at most ONE of their required reactants.
|
||||||
@@ -205,27 +206,12 @@ namespace Content.Shared.Chemistry.Reaction
|
|||||||
|
|
||||||
private void OnReaction(Entity<SolutionComponent> soln, ReactionPrototype reaction, ReagentPrototype? reagent, FixedPoint2 unitReactions)
|
private void OnReaction(Entity<SolutionComponent> soln, ReactionPrototype reaction, ReagentPrototype? reagent, FixedPoint2 unitReactions)
|
||||||
{
|
{
|
||||||
var args = new EntityEffectReagentArgs(soln, EntityManager, null, soln.Comp.Solution, unitReactions, reagent, null, 1f);
|
|
||||||
|
|
||||||
var posFound = _transformSystem.TryGetMapOrGridCoordinates(soln, out var gridPos);
|
var posFound = _transformSystem.TryGetMapOrGridCoordinates(soln, out var gridPos);
|
||||||
|
|
||||||
_adminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
|
_adminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
|
||||||
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(soln):metabolizer} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found]")}");
|
$"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(soln):metabolizer} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found]")}");
|
||||||
|
|
||||||
foreach (var effect in reaction.Effects)
|
_entityEffects.ApplyEffects(soln, reaction.Effects, unitReactions.Float());
|
||||||
{
|
|
||||||
if (!effect.ShouldApply(args))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (effect.ShouldLog)
|
|
||||||
{
|
|
||||||
var entity = args.TargetEntity;
|
|
||||||
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
|
|
||||||
$"Reaction effect {effect.GetType().Name:effect} of reaction {reaction.ID:reaction} applied on entity {ToPrettyString(entity):entity} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
effect.Effect(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Someday, some brave soul will thread through an optional actor
|
// Someday, some brave soul will thread through an optional actor
|
||||||
// argument in from every call of OnReaction up, all just to pass
|
// argument in from every call of OnReaction up, all just to pass
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace Content.Shared.Chemistry.Reaction
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Effects to be triggered when the reaction occurs.
|
/// Effects to be triggered when the reaction occurs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("effects")] public List<EntityEffect> Effects = new();
|
[DataField("effects")] public EntityEffect[] Effects = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How dangerous is this effect? Stuff like bicaridine should be low, while things like methamphetamine
|
/// How dangerous is this effect? Stuff like bicaridine should be low, while things like methamphetamine
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public sealed partial class ReactiveReagentEffectEntry
|
|||||||
public HashSet<string>? Reagents = null;
|
public HashSet<string>? Reagents = null;
|
||||||
|
|
||||||
[DataField("effects", required: true)]
|
[DataField("effects", required: true)]
|
||||||
public List<EntityEffect> Effects = default!;
|
public EntityEffect[] Effects = default!;
|
||||||
|
|
||||||
[DataField("groups", readOnly: true, serverOnly: true,
|
[DataField("groups", readOnly: true, serverOnly: true,
|
||||||
customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<ReactionMethod>, ReactiveGroupPrototype>))]
|
customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<ReactionMethod>, ReactiveGroupPrototype>))]
|
||||||
|
|||||||
@@ -1,108 +1,35 @@
|
|||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Reaction;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.EntityEffects;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Shared.Chemistry;
|
namespace Content.Shared.Chemistry;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class ReactiveSystem : EntitySystem
|
public sealed class ReactiveSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
|
||||||
|
|
||||||
public void DoEntityReaction(EntityUid uid, Solution solution, ReactionMethod method)
|
public void DoEntityReaction(EntityUid uid, Solution solution, ReactionMethod method)
|
||||||
{
|
{
|
||||||
foreach (var reagent in solution.Contents.ToArray())
|
foreach (var reagent in solution.Contents.ToArray())
|
||||||
{
|
{
|
||||||
ReactionEntity(uid, method, reagent, solution);
|
ReactionEntity(uid, method, reagent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentQuantity reagentQuantity, Solution? source)
|
public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentQuantity reagentQuantity)
|
||||||
{
|
{
|
||||||
// We throw if the reagent specified doesn't exist.
|
if (reagentQuantity.Quantity == FixedPoint2.Zero)
|
||||||
var proto = _prototypeManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
|
|
||||||
ReactionEntity(uid, method, proto, reagentQuantity, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentPrototype proto,
|
|
||||||
ReagentQuantity reagentQuantity, Solution? source)
|
|
||||||
{
|
|
||||||
if (!TryComp(uid, out ReactiveComponent? reactive))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// custom event for bypassing reactivecomponent stuff
|
// We throw if the reagent specified doesn't exist.
|
||||||
var ev = new ReactionEntityEvent(method, proto, reagentQuantity, source);
|
if (!_proto.Resolve<ReagentPrototype>(reagentQuantity.Reagent.Prototype, out var proto))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ev = new ReactionEntityEvent(method, reagentQuantity, proto);
|
||||||
RaiseLocalEvent(uid, ref ev);
|
RaiseLocalEvent(uid, ref ev);
|
||||||
|
|
||||||
// If we have a source solution, use the reagent quantity we have left. Otherwise, use the reaction volume specified.
|
|
||||||
var args = new EntityEffectReagentArgs(uid, EntityManager, null, source, source?.GetReagentQuantity(reagentQuantity.Reagent) ?? reagentQuantity.Quantity, proto, method, 1f);
|
|
||||||
|
|
||||||
// First, check if the reagent wants to apply any effects.
|
|
||||||
if (proto.ReactiveEffects != null && reactive.ReactiveGroups != null)
|
|
||||||
{
|
|
||||||
foreach (var (key, val) in proto.ReactiveEffects)
|
|
||||||
{
|
|
||||||
if (!val.Methods.Contains(method))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!reactive.ReactiveGroups.ContainsKey(key))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!reactive.ReactiveGroups[key].Contains(method))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (var effect in val.Effects)
|
|
||||||
{
|
|
||||||
if (!effect.ShouldApply(args, _robustRandom))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (effect.ShouldLog)
|
|
||||||
{
|
|
||||||
var entity = args.TargetEntity;
|
|
||||||
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
|
|
||||||
$"Reactive effect {effect.GetType().Name:effect} of reagent {proto.ID:reagent} with method {method} applied on entity {ToPrettyString(entity):entity} at {Transform(entity).Coordinates:coordinates}");
|
|
||||||
}
|
|
||||||
|
|
||||||
effect.Effect(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, check if the prototype has any effects it can apply as well.
|
|
||||||
if (reactive.Reactions != null)
|
|
||||||
{
|
|
||||||
foreach (var entry in reactive.Reactions)
|
|
||||||
{
|
|
||||||
if (!entry.Methods.Contains(method))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (entry.Reagents != null && !entry.Reagents.Contains(proto.ID))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (var effect in entry.Effects)
|
|
||||||
{
|
|
||||||
if (!effect.ShouldApply(args, _robustRandom))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (effect.ShouldLog)
|
|
||||||
{
|
|
||||||
var entity = args.TargetEntity;
|
|
||||||
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
|
|
||||||
$"Reactive effect {effect.GetType().Name:effect} of {ToPrettyString(entity):entity} using reagent {proto.ID:reagent} with method {method} at {Transform(entity).Coordinates:coordinates}");
|
|
||||||
}
|
|
||||||
|
|
||||||
effect.Effect(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public enum ReactionMethod
|
public enum ReactionMethod
|
||||||
@@ -113,9 +40,4 @@ Ingestion,
|
|||||||
}
|
}
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct ReactionEntityEvent(
|
public readonly record struct ReactionEntityEvent(ReactionMethod Method, ReagentQuantity ReagentQuantity, ReagentPrototype Reagent);
|
||||||
ReactionMethod Method,
|
|
||||||
ReagentPrototype Reagent,
|
|
||||||
ReagentQuantity ReagentQuantity,
|
|
||||||
Solution? Source
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -2,21 +2,17 @@ using System.Collections.Frozen;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Body.Prototypes;
|
using Content.Shared.Body.Prototypes;
|
||||||
using Content.Shared.Chemistry.Components;
|
|
||||||
using Content.Shared.Chemistry.Reaction;
|
using Content.Shared.Chemistry.Reaction;
|
||||||
using Content.Shared.Contraband;
|
using Content.Shared.Contraband;
|
||||||
using Content.Shared.EntityEffects;
|
using Content.Shared.EntityEffects;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Localizations;
|
||||||
using Content.Shared.Nutrition;
|
using Content.Shared.Nutrition;
|
||||||
using Content.Shared.Prototypes;
|
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Slippery;
|
using Content.Shared.Slippery;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -190,6 +186,7 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
[DataField]
|
[DataField]
|
||||||
public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepPuddle");
|
public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepPuddle");
|
||||||
|
|
||||||
|
// TODO: Reaction tile doesn't work properly and destroys reagents way too quickly
|
||||||
public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
|
public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
|
||||||
{
|
{
|
||||||
var removed = FixedPoint2.Zero;
|
var removed = FixedPoint2.Zero;
|
||||||
@@ -211,33 +208,32 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReactionPlant(EntityUid? plantHolder,
|
public IEnumerable<string> GuidebookReagentEffectsDescription(IPrototypeManager prototype, IEntitySystemManager entSys, IEnumerable<EntityEffect> effects, FixedPoint2? metabolism = null)
|
||||||
ReagentQuantity amount,
|
|
||||||
Solution solution,
|
|
||||||
EntityManager entityManager,
|
|
||||||
IRobustRandom random,
|
|
||||||
ISharedAdminLogManager logger)
|
|
||||||
{
|
{
|
||||||
if (plantHolder == null)
|
return effects.Select(x => GuidebookReagentEffectDescription(prototype, entSys, x, metabolism))
|
||||||
return;
|
.Where(x => x is not null)
|
||||||
|
.Select(x => x!)
|
||||||
var args = new EntityEffectReagentArgs(plantHolder.Value, entityManager, null, solution, amount.Quantity, this, null, 1f);
|
.ToArray();
|
||||||
foreach (var plantMetabolizable in PlantMetabolisms)
|
|
||||||
{
|
|
||||||
if (!plantMetabolizable.ShouldApply(args, random))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (plantMetabolizable.ShouldLog)
|
|
||||||
{
|
|
||||||
var entity = args.TargetEntity;
|
|
||||||
logger.Add(
|
|
||||||
LogType.ReagentEffect,
|
|
||||||
plantMetabolizable.LogImpact,
|
|
||||||
$"Plant metabolism effect {plantMetabolizable.GetType().Name:effect} of reagent {ID} applied on entity {entity}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plantMetabolizable.Effect(args);
|
public string? GuidebookReagentEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys, EntityEffect effect, FixedPoint2? metabolism)
|
||||||
}
|
{
|
||||||
|
if (effect.EntityEffectGuidebookText(prototype, entSys) is not { } description)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var quantity = metabolism == null ? 0f : (double) (effect.MinScale * metabolism);
|
||||||
|
|
||||||
|
return Loc.GetString(
|
||||||
|
"guidebook-reagent-effect-description",
|
||||||
|
("reagent", LocalizedName),
|
||||||
|
("quantity", quantity),
|
||||||
|
("effect", description),
|
||||||
|
("chance", effect.Probability),
|
||||||
|
("conditionCount", effect.Conditions?.Length ?? 0),
|
||||||
|
("conditions",
|
||||||
|
ContentLocalizationManager.FormatList(
|
||||||
|
effect.Conditions?.Select(x => x.EntityConditionGuidebookText(prototype)).ToList() ?? new List<string>()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +242,7 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
{
|
{
|
||||||
public string ReagentPrototype;
|
public string ReagentPrototype;
|
||||||
|
|
||||||
|
// TODO: Kill Metabolism groups!
|
||||||
public Dictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsGuideEntry>? GuideEntries;
|
public Dictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsGuideEntry>? GuideEntries;
|
||||||
|
|
||||||
public List<string>? PlantMetabolisms = null;
|
public List<string>? PlantMetabolisms = null;
|
||||||
@@ -254,15 +251,12 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
{
|
{
|
||||||
ReagentPrototype = proto.ID;
|
ReagentPrototype = proto.ID;
|
||||||
GuideEntries = proto.Metabolisms?
|
GuideEntries = proto.Metabolisms?
|
||||||
.Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
|
.Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys, proto)))
|
||||||
.ToDictionary(x => x.Key, x => x.Item2);
|
.ToDictionary(x => x.Key, x => x.Item2);
|
||||||
if (proto.PlantMetabolisms.Count > 0)
|
if (proto.PlantMetabolisms.Count > 0)
|
||||||
{
|
{
|
||||||
PlantMetabolisms = new List<string>(proto.PlantMetabolisms
|
PlantMetabolisms =
|
||||||
.Select(x => x.GuidebookEffectDescription(prototype, entSys))
|
new List<string>(proto.GuidebookReagentEffectsDescription(prototype, entSys, proto.PlantMetabolisms));
|
||||||
.Where(x => x is not null)
|
|
||||||
.Select(x => x!)
|
|
||||||
.ToArray());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,14 +279,11 @@ namespace Content.Shared.Chemistry.Reagent
|
|||||||
[DataField("effects", required: true)]
|
[DataField("effects", required: true)]
|
||||||
public EntityEffect[] Effects = default!;
|
public EntityEffect[] Effects = default!;
|
||||||
|
|
||||||
public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
|
public string EntityEffectFormat => "guidebook-reagent-effect-description";
|
||||||
|
|
||||||
|
public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys, ReagentPrototype proto)
|
||||||
{
|
{
|
||||||
return new ReagentEffectsGuideEntry(MetabolismRate,
|
return new ReagentEffectsGuideEntry(MetabolismRate, proto.GuidebookReagentEffectsDescription(prototype, entSys, Effects, MetabolismRate).ToArray());
|
||||||
Effects
|
|
||||||
.Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
|
|
||||||
.Where(x => x is not null)
|
|
||||||
.Select(x => x!)
|
|
||||||
.ToArray());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class BreathingCondition : EntityConditionBase<BreathingCondition>
|
||||||
|
{
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-breathing", ("isBreathing", !Inverted));
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Content.Shared.Nutrition.Components;
|
||||||
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity's hunger is within a specified minimum and maximum.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class TotalHungerEntityConditionSystem : EntityConditionSystem<HungerComponent, HungerCondition>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly HungerSystem _hunger = default!;
|
||||||
|
|
||||||
|
protected override void Condition(Entity<HungerComponent> entity, ref EntityConditionEvent<HungerCondition> args)
|
||||||
|
{
|
||||||
|
var total = _hunger.GetHunger(entity.Comp);
|
||||||
|
args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class HungerCondition : EntityConditionBase<HungerCondition>
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public float Min;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public float Max = float.PositiveInfinity;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-total-hunger", ("max", float.IsPositiveInfinity(Max) ? int.MaxValue : Max), ("min", Min));
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Content.Shared.Body.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity is using internals. False if they are not or cannot use internals.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class InternalsOnEntityConditionSystem : EntityConditionSystem<InternalsComponent, InternalsCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<InternalsComponent> entity, ref EntityConditionEvent<InternalsCondition> args)
|
||||||
|
{
|
||||||
|
args.Result = entity.Comp.GasTankEntity != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class InternalsCondition : EntityConditionBase<InternalsCondition>
|
||||||
|
{
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", !Inverted));
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Content.Shared.Body.Prototypes;
|
||||||
|
using Content.Shared.Localizations;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class MetabolizerTypeCondition : EntityConditionBase<MetabolizerTypeCondition>
|
||||||
|
{
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<MetabolizerTypePrototype>[] Type = default!;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||||
|
{
|
||||||
|
var typeList = new List<string>();
|
||||||
|
|
||||||
|
foreach (var type in Type)
|
||||||
|
{
|
||||||
|
if (!prototype.Resolve(type, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
typeList.Add(proto.LocalizedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var names = ContentLocalizationManager.FormatListToOr(typeList);
|
||||||
|
|
||||||
|
return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
|
||||||
|
("name", names),
|
||||||
|
("shouldhave", !Inverted));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Shared.Mobs;
|
||||||
|
using Content.Shared.Mobs.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Body;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity's current mob state matches the condition's specified mob state.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class MobStateEntityConditionSystem : EntityConditionSystem<MobStateComponent, MobStateCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<MobStateComponent> entity, ref EntityConditionEvent<MobStateCondition> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.CurrentState == args.Condition.Mobstate)
|
||||||
|
args.Result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class MobStateCondition : EntityConditionBase<MobStateCondition>
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public MobState Mobstate = MobState.Alive;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate));
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Localizations;
|
||||||
|
using Content.Shared.Mind;
|
||||||
|
using Content.Shared.Mind.Components;
|
||||||
|
using Content.Shared.Roles;
|
||||||
|
using Content.Shared.Roles.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity has any of the specified jobs. False if the entity has no mind, none of the specified jobs, or is jobless.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class HasJobEntityConditionSystem : EntityConditionSystem<MindContainerComponent, JobCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<MindContainerComponent> entity, ref EntityConditionEvent<JobCondition> args)
|
||||||
|
{
|
||||||
|
// We need a mind in our mind container...
|
||||||
|
if (!TryComp<MindComponent>(entity.Comp.Mind, out var mind))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
|
||||||
|
{
|
||||||
|
if (!HasComp<JobRoleComponent>(roleId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!TryComp<MindRoleComponent>(roleId, out var mindRole))
|
||||||
|
{
|
||||||
|
Log.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mindRole.JobPrototype == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.Condition.Jobs.Contains(mindRole.JobPrototype.Value))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
args.Result = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class JobCondition : EntityConditionBase<JobCondition>
|
||||||
|
{
|
||||||
|
[DataField(required: true)] public List<ProtoId<JobPrototype>> Jobs = [];
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||||
|
{
|
||||||
|
var localizedNames = Jobs.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
|
||||||
|
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class ReagentThresholdEntityConditionSystem : EntityConditionSystem<SolutionComponent, ReagentCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<SolutionComponent> entity, ref EntityConditionEvent<ReagentCondition> args)
|
||||||
|
{
|
||||||
|
var quant = entity.Comp.Solution.GetTotalPrototypeQuantity(args.Condition.Reagent);
|
||||||
|
|
||||||
|
args.Result = quant >= args.Condition.Min && quant <= args.Condition.Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class ReagentCondition : EntityConditionBase<ReagentCondition>
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public FixedPoint2 Min = FixedPoint2.Zero;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public FixedPoint2 Max = FixedPoint2.MaxValue;
|
||||||
|
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<ReagentPrototype> Reagent;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||||
|
{
|
||||||
|
if (!prototype.Resolve(Reagent, out var reagentProto))
|
||||||
|
return String.Empty;
|
||||||
|
|
||||||
|
return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
|
||||||
|
("reagent", reagentProto.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")),
|
||||||
|
("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()),
|
||||||
|
("min", Min.Float()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Shared.Localizations;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Tags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity has all the listed tags.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class HasAllTagsEntityConditionSystem : EntityConditionSystem<TagComponent, AllTagsCondition>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
|
||||||
|
protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<AllTagsCondition> args)
|
||||||
|
{
|
||||||
|
args.Result = _tag.HasAllTags(entity.Comp, args.Condition.Tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class AllTagsCondition : EntityConditionBase<AllTagsCondition>
|
||||||
|
{
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<TagPrototype>[] Tags = [];
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||||
|
{
|
||||||
|
var tagList = new List<string>();
|
||||||
|
|
||||||
|
foreach (var type in Tags)
|
||||||
|
{
|
||||||
|
if (!prototype.Resolve(type, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tagList.Add(proto.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
var names = ContentLocalizationManager.FormatList(tagList);
|
||||||
|
|
||||||
|
return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", names), ("invert", Inverted));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Shared.Localizations;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Tags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity have any of the listed tags.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class HasAnyTagEntityConditionSystem : EntityConditionSystem<TagComponent, AnyTagCondition>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
|
||||||
|
protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<AnyTagCondition> args)
|
||||||
|
{
|
||||||
|
args.Result = _tag.HasAnyTag(entity.Comp, args.Condition.Tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class AnyTagCondition : EntityConditionBase<AnyTagCondition>
|
||||||
|
{
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<TagPrototype>[] Tags = [];
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||||
|
{
|
||||||
|
var tagList = new List<string>();
|
||||||
|
|
||||||
|
foreach (var type in Tags)
|
||||||
|
{
|
||||||
|
if (!prototype.Resolve(type, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tagList.Add(proto.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
var names = ContentLocalizationManager.FormatListToOr(tagList);
|
||||||
|
|
||||||
|
return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", names), ("invert", Inverted));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Shared.Tag;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions.Tags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity has the listed tag.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class HasTagEntityConditionSystem : EntityConditionSystem<TagComponent, TagCondition>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
|
||||||
|
protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<TagCondition> args)
|
||||||
|
{
|
||||||
|
args.Result = _tag.HasTag(entity.Comp, args.Condition.Tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class TagCondition : EntityConditionBase<TagCondition>
|
||||||
|
{
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<TagPrototype> Tag;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Inverted));
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using Content.Shared.Chemistry.Components;
|
||||||
|
using Content.Shared.Temperature.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity has an amount of reagent in it within a specified minimum and maximum.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class TemperatureEntityConditionSystem : EntityConditionSystem<TemperatureComponent, TemperatureCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<TemperatureComponent> entity, ref EntityConditionEvent<TemperatureCondition> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.CurrentTemperature >= args.Condition.Min && entity.Comp.CurrentTemperature <= args.Condition.Max)
|
||||||
|
args.Result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class SolutionTemperatureEntityConditionSystem : EntityConditionSystem<SolutionComponent, TemperatureCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<SolutionComponent> entity, ref EntityConditionEvent<TemperatureCondition> args)
|
||||||
|
{
|
||||||
|
if (entity.Comp.Solution.Temperature >= args.Condition.Min && entity.Comp.Solution.Temperature <= args.Condition.Max)
|
||||||
|
args.Result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class TemperatureCondition : EntityConditionBase<TemperatureCondition>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum allowed temperature
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Min = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum allowed temperature
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Max = float.PositiveInfinity;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-body-temperature",
|
||||||
|
("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
|
||||||
|
("min", Min));
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions;
|
||||||
|
///<summary>
|
||||||
|
/// A basic summary of this condition.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class TemplateEntityConditionSystem : EntityConditionSystem<MetaDataComponent, TemplateCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<MetaDataComponent> entity, ref EntityConditionEvent<TemplateCondition> args)
|
||||||
|
{
|
||||||
|
// Condition goes here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class TemplateCondition : EntityConditionBase<TemplateCondition>
|
||||||
|
{
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) => String.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions.Conditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if this entity can take damage and if its total damage is within a specified minimum and maximum.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
|
||||||
|
public sealed partial class TotalDamageEntityConditionSystem : EntityConditionSystem<DamageableComponent, TotalDamageCondition>
|
||||||
|
{
|
||||||
|
protected override void Condition(Entity<DamageableComponent> entity, ref EntityConditionEvent<TotalDamageCondition> args)
|
||||||
|
{
|
||||||
|
var total = entity.Comp.TotalDamage;
|
||||||
|
args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityCondition"/>
|
||||||
|
public sealed partial class TotalDamageCondition : EntityConditionBase<TotalDamageCondition>
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public FixedPoint2 Max = FixedPoint2.MaxValue;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public FixedPoint2 Min = FixedPoint2.Zero;
|
||||||
|
|
||||||
|
public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
|
||||||
|
Loc.GetString("reagent-effect-condition-guidebook-total-damage",
|
||||||
|
("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()),
|
||||||
|
("min", Min.Float()));
|
||||||
|
}
|
||||||
152
Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs
Normal file
152
Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityConditions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles entity effects.
|
||||||
|
/// Specifically it handles the receiving of events for causing entity effects, and provides
|
||||||
|
/// public API for other systems to take advantage of entity effects.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class SharedEntityConditionsSystem : EntitySystem, IEntityConditionRaiser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks a list of conditions to verify that they all return true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Target entity we're checking conditions on</param>
|
||||||
|
/// <param name="conditions">Conditions we're checking</param>
|
||||||
|
/// <returns>Returns true if all conditions return true, false if any fail</returns>
|
||||||
|
public bool TryConditions(EntityUid target, EntityCondition[]? conditions)
|
||||||
|
{
|
||||||
|
// If there's no conditions we can't fail any of them...
|
||||||
|
if (conditions == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (var condition in conditions)
|
||||||
|
{
|
||||||
|
if (!TryCondition(target, condition))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks a list of conditions to see if any are true.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Target entity we're checking conditions on</param>
|
||||||
|
/// <param name="conditions">Conditions we're checking</param>
|
||||||
|
/// <returns>Returns true if any conditions return true</returns>
|
||||||
|
public bool TryAnyCondition(EntityUid target, EntityCondition[]? conditions)
|
||||||
|
{
|
||||||
|
// If there's no conditions we can't meet any of them...
|
||||||
|
if (conditions == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var condition in conditions)
|
||||||
|
{
|
||||||
|
if (TryCondition(target, condition))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks a single <see cref="EntityCondition"/> on an entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">Target entity we're checking conditions on</param>
|
||||||
|
/// <param name="condition">Condition we're checking</param>
|
||||||
|
/// <returns>Returns true if we meet the condition and false otherwise</returns>
|
||||||
|
public bool TryCondition(EntityUid target, EntityCondition condition)
|
||||||
|
{
|
||||||
|
return condition.Inverted != condition.RaiseEvent(target, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises a condition to an entity. You should not be calling this unless you know what you're doing.
|
||||||
|
/// </summary>
|
||||||
|
public bool RaiseConditionEvent<T>(EntityUid target, T effect) where T : EntityConditionBase<T>
|
||||||
|
{
|
||||||
|
var effectEv = new EntityConditionEvent<T>(effect);
|
||||||
|
RaiseLocalEvent(target, ref effectEv);
|
||||||
|
return effectEv.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a basic abstract entity effect containing all the data an entity effect needs to affect entities with effects...
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The Component that is required for the effect</typeparam>
|
||||||
|
/// <typeparam name="TCon">The Condition we're testing</typeparam>
|
||||||
|
public abstract partial class EntityConditionSystem<T, TCon> : EntitySystem where T : Component where TCon : EntityConditionBase<TCon>
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<T, EntityConditionEvent<TCon>>(Condition);
|
||||||
|
}
|
||||||
|
protected abstract void Condition(Entity<T> entity, ref EntityConditionEvent<TCon> args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to raise an EntityCondition without losing the type of condition.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEntityConditionRaiser
|
||||||
|
{
|
||||||
|
bool RaiseConditionEvent<T>(EntityUid target, T effect) where T : EntityConditionBase<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to store an <see cref="EntityCondition"/> so it can be raised without losing the type of the condition.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The Condition wer are raising.</typeparam>
|
||||||
|
public abstract partial class EntityConditionBase<T> : EntityCondition where T : EntityConditionBase<T>
|
||||||
|
{
|
||||||
|
public override bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser)
|
||||||
|
{
|
||||||
|
if (this is not T type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the result of the event matches the result we're looking for then we pass.
|
||||||
|
return raiser.RaiseConditionEvent(target, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A basic condition which can be checked for on an entity via events.
|
||||||
|
/// </summary>
|
||||||
|
[ImplicitDataDefinitionForInheritors]
|
||||||
|
public abstract partial class EntityCondition
|
||||||
|
{
|
||||||
|
public abstract bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, invert the result. So false returns true and true returns false!
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool Inverted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A basic description of this condition, which displays in the guidebook.
|
||||||
|
/// </summary>
|
||||||
|
public abstract string EntityConditionGuidebookText(IPrototypeManager prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An Event carrying an entity effect.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Condition">The Condition we're checking</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public record struct EntityConditionEvent<T>(T Condition) where T : EntityConditionBase<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The result of our check, defaults to false if nothing handles it.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool Result;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Condition being raised in this event
|
||||||
|
/// </summary>
|
||||||
|
public readonly T Condition = Condition;
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Requires the target entity to be above or below a certain temperature.
|
|
||||||
/// Used for things like cryoxadone and pyroxadone.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class Temperature : EventEntityEffectCondition<Temperature>
|
|
||||||
{
|
|
||||||
[DataField]
|
|
||||||
public float Min = 0;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public float Max = float.PositiveInfinity;
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-body-temperature",
|
|
||||||
("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
|
|
||||||
("min", Min));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using Content.Shared.Tag;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
public sealed partial class HasTag : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]
|
|
||||||
public string Tag = default!;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public bool Invert = false;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args.EntityManager.TryGetComponent<TagComponent>(args.TargetEntity, out var tag))
|
|
||||||
return args.EntityManager.System<TagSystem>().HasTag(tag, Tag) ^ Invert;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
// this should somehow be made (much) nicer.
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Invert));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using Content.Shared.Body.Components;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Condition for if the entity is or isn't wearing internals.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class Internals : EntityEffectCondition
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// To pass, the entity's internals must have this same state.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public bool UsingInternals = true;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (!args.EntityManager.TryGetComponent(args.TargetEntity, out InternalsComponent? internalsComp))
|
|
||||||
return !UsingInternals; // They have no internals to wear.
|
|
||||||
|
|
||||||
var internalsState = internalsComp.GasTankEntity != null; // If gas tank is not null, they are wearing internals
|
|
||||||
return UsingInternals == internalsState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", UsingInternals));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Shared.Localizations;
|
|
||||||
using Content.Shared.Mind;
|
|
||||||
using Content.Shared.Mind.Components;
|
|
||||||
using Content.Shared.Roles;
|
|
||||||
using Content.Shared.Roles.Components;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
public sealed partial class JobCondition : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
|
|
||||||
|
|
||||||
if (mindContainer is null
|
|
||||||
|| !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
|
|
||||||
{
|
|
||||||
if (!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!args.EntityManager.TryGetComponent<MindRoleComponent>(roleId, out var mindRole))
|
|
||||||
{
|
|
||||||
Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mindRole.JobPrototype == null)
|
|
||||||
{
|
|
||||||
Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Job.Contains(mindRole.JobPrototype.Value))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using Content.Shared.Mobs;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
public sealed partial class MobStateCondition : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField]
|
|
||||||
public MobState Mobstate = MobState.Alive;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args.EntityManager.TryGetComponent(args.TargetEntity, out MobStateComponent? mobState))
|
|
||||||
{
|
|
||||||
if (mobState.CurrentState == Mobstate)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// using Content.Server.Body.Components;
|
|
||||||
using Content.Shared.Body.Prototypes;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
/// <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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used for implementing reagent effects that require a certain amount of reagent before it should be applied.
|
|
||||||
/// For instance, overdoses.
|
|
||||||
///
|
|
||||||
/// This can also trigger on -other- reagents, not just the one metabolizing. By default, it uses the
|
|
||||||
/// one being metabolized.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class ReagentThreshold : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField]
|
|
||||||
public FixedPoint2 Min = FixedPoint2.Zero;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public FixedPoint2 Max = FixedPoint2.MaxValue;
|
|
||||||
|
|
||||||
// TODO use ReagentId
|
|
||||||
[DataField]
|
|
||||||
public string? Reagent;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
var reagent = Reagent ?? reagentArgs.Reagent?.ID;
|
|
||||||
if (reagent == null)
|
|
||||||
return true; // No condition to apply.
|
|
||||||
|
|
||||||
var quant = FixedPoint2.Zero;
|
|
||||||
if (reagentArgs.Source != null)
|
|
||||||
quant = reagentArgs.Source.GetTotalPrototypeQuantity(reagent);
|
|
||||||
|
|
||||||
return quant >= Min && quant <= Max;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Someone needs to figure out how to do this for non-reagent effects.
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
ReagentPrototype? reagentProto = null;
|
|
||||||
if (Reagent is not null)
|
|
||||||
prototype.TryIndex(Reagent, out reagentProto);
|
|
||||||
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
|
|
||||||
("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")),
|
|
||||||
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
|
|
||||||
("min", Min.Float()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Requires the solution to be above or below a certain temperature.
|
|
||||||
/// Used for things like explosives.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class SolutionTemperature : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField]
|
|
||||||
public float Min = 0.0f;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public float Max = float.PositiveInfinity;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
return reagentArgs?.Source != null &&
|
|
||||||
reagentArgs.Source.Temperature >= Min &&
|
|
||||||
reagentArgs.Source.Temperature <= Max;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Someone needs to figure out how to do this for non-reagent effects.
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-solution-temperature",
|
|
||||||
("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
|
|
||||||
("min", Min));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Content.Shared.EntityEffects;
|
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
public sealed partial class TotalDamage : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField]
|
|
||||||
public FixedPoint2 Max = FixedPoint2.MaxValue;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public FixedPoint2 Min = FixedPoint2.Zero;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args.EntityManager.TryGetComponent(args.TargetEntity, out DamageableComponent? damage))
|
|
||||||
{
|
|
||||||
var total = damage.TotalDamage;
|
|
||||||
return total >= Min && total <= Max;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-total-damage",
|
|
||||||
("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
|
|
||||||
("min", Min.Float()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Content.Shared.EntityEffects;
|
|
||||||
using Content.Shared.Nutrition.Components;
|
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
|
||||||
|
|
||||||
public sealed partial class Hunger : EntityEffectCondition
|
|
||||||
{
|
|
||||||
[DataField]
|
|
||||||
public float Max = float.PositiveInfinity;
|
|
||||||
|
|
||||||
[DataField]
|
|
||||||
public float Min = 0;
|
|
||||||
|
|
||||||
public override bool Condition(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args.EntityManager.TryGetComponent(args.TargetEntity, out HungerComponent? hunger))
|
|
||||||
{
|
|
||||||
var total = args.EntityManager.System<HungerSystem>().GetHunger(hunger);
|
|
||||||
return total >= Min && total <= Max;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-condition-guidebook-total-hunger",
|
|
||||||
("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
|
|
||||||
("min", Min));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using Content.Shared.Chemistry.EntitySystems;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.Effects
|
|
||||||
{
|
|
||||||
public sealed partial class AddToSolutionReaction : EntityEffect
|
|
||||||
{
|
|
||||||
[DataField("solution")]
|
|
||||||
private string _solution = "reagents";
|
|
||||||
|
|
||||||
public override void Effect(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args is EntityEffectReagentArgs reagentArgs) {
|
|
||||||
if (reagentArgs.Reagent == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO see if this is correct
|
|
||||||
var solutionContainerSystem = reagentArgs.EntityManager.System<SharedSolutionContainerSystem>();
|
|
||||||
if (!solutionContainerSystem.TryGetSolution(reagentArgs.TargetEntity, _solution, out var solutionContainer))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (solutionContainerSystem.TryAddReagent(solutionContainer.Value, reagentArgs.Reagent.ID, reagentArgs.Quantity, out var accepted))
|
|
||||||
reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, accepted);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Someone needs to figure out how to do this for non-reagent effects.
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
|
|
||||||
Loc.GetString("reagent-effect-guidebook-add-to-solution-reaction", ("chance", Probability));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using Content.Shared.Alert;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.Effects;
|
|
||||||
|
|
||||||
public sealed partial class AdjustAlert : EntityEffect
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The specific Alert that will be adjusted
|
|
||||||
/// </summary>
|
|
||||||
[DataField(required: true)]
|
|
||||||
public ProtoId<AlertPrototype> AlertType;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public bool Clear;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Visually display cooldown progress over the alert icon.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public bool ShowCooldown;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The length of the cooldown or delay before removing the alert (in seconds).
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public float Time;
|
|
||||||
|
|
||||||
//JUSTIFICATION: This just changes some visuals, doesn't need to be documented.
|
|
||||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
|
|
||||||
|
|
||||||
public override void Effect(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem<AlertsSystem>();
|
|
||||||
if (!args.EntityManager.HasComponent<AlertsComponent>(args.TargetEntity))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Clear && Time <= 0)
|
|
||||||
{
|
|
||||||
alertSys.ClearAlert(args.TargetEntity, AlertType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var timing = IoCManager.Resolve<IGameTiming>();
|
|
||||||
(TimeSpan, TimeSpan)? cooldown = null;
|
|
||||||
|
|
||||||
if ((ShowCooldown || Clear) && Time > 0)
|
|
||||||
cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
|
|
||||||
|
|
||||||
alertSys.ShowAlert(args.TargetEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Content.Shared.Alert;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityEffects.Effects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adjusts a given alert on this entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class AdjustAlertEntityEffectSysten : EntityEffectSystem<AlertsComponent, AdjustAlert>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||||
|
|
||||||
|
protected override void Effect(Entity<AlertsComponent> entity, ref EntityEffectEvent<AdjustAlert> args)
|
||||||
|
{
|
||||||
|
var time = args.Effect.Time;
|
||||||
|
var clear = args.Effect.Clear;
|
||||||
|
var type = args.Effect.AlertType;
|
||||||
|
|
||||||
|
if (clear && time <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
_alerts.ClearAlert(entity.AsNullable(), type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(TimeSpan, TimeSpan)? cooldown = null;
|
||||||
|
|
||||||
|
if ((args.Effect.ShowCooldown || clear) && args.Effect.Time >= TimeSpan.Zero)
|
||||||
|
cooldown = (_timing.CurTime, _timing.CurTime + time);
|
||||||
|
|
||||||
|
_alerts.ShowAlert(entity.AsNullable(), type, cooldown: cooldown, autoRemove: clear, showCooldown: args.Effect.ShowCooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityEffect"/>
|
||||||
|
public sealed partial class AdjustAlert : EntityEffectBase<AdjustAlert>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The specific Alert that will be adjusted
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public ProtoId<AlertPrototype> AlertType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool Clear;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Visually display cooldown progress over the alert icon.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool ShowCooldown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length of the cooldown or delay before removing the alert (in seconds).
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan Time;
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
using Content.Shared.Body.Prototypes;
|
|
||||||
using Content.Shared.Chemistry.Reagent;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.Effects
|
|
||||||
{
|
|
||||||
public sealed partial class AdjustReagent : EntityEffect
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The reagent ID to remove. Only one of this and <see cref="Group"/> should be active.
|
|
||||||
/// </summary>
|
|
||||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
|
|
||||||
public string? Reagent = null;
|
|
||||||
// TODO use ReagentId
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The metabolism group to remove, if the reagent satisfies any.
|
|
||||||
/// Only one of this and <see cref="Reagent"/> should be active.
|
|
||||||
/// </summary>
|
|
||||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<MetabolismGroupPrototype>))]
|
|
||||||
public string? Group = null;
|
|
||||||
|
|
||||||
[DataField(required: true)]
|
|
||||||
public FixedPoint2 Amount = default!;
|
|
||||||
|
|
||||||
public override void Effect(EntityEffectBaseArgs args)
|
|
||||||
{
|
|
||||||
if (args is EntityEffectReagentArgs reagentArgs)
|
|
||||||
{
|
|
||||||
if (reagentArgs.Source == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var amount = Amount;
|
|
||||||
amount *= reagentArgs.Scale;
|
|
||||||
|
|
||||||
if (Reagent != null)
|
|
||||||
{
|
|
||||||
if (amount < 0 && reagentArgs.Source.ContainsPrototype(Reagent))
|
|
||||||
reagentArgs.Source.RemoveReagent(Reagent, -amount);
|
|
||||||
if (amount > 0)
|
|
||||||
reagentArgs.Source.AddReagent(Reagent, amount);
|
|
||||||
}
|
|
||||||
else if (Group != null)
|
|
||||||
{
|
|
||||||
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
|
|
||||||
foreach (var quant in reagentArgs.Source.Contents.ToArray())
|
|
||||||
{
|
|
||||||
var proto = prototypeMan.Index<ReagentPrototype>(quant.Reagent.Prototype);
|
|
||||||
if (proto.Metabolisms != null && proto.Metabolisms.ContainsKey(Group))
|
|
||||||
{
|
|
||||||
if (amount < 0)
|
|
||||||
reagentArgs.Source.RemoveReagent(quant.Reagent, amount);
|
|
||||||
if (amount > 0)
|
|
||||||
reagentArgs.Source.AddReagent(quant.Reagent, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Someone needs to figure out how to do this for non-reagent effects.
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
|
||||||
{
|
|
||||||
if (Reagent is not null && prototype.TryIndex(Reagent, out ReagentPrototype? reagentProto))
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-guidebook-adjust-reagent-reagent",
|
|
||||||
("chance", Probability),
|
|
||||||
("deltasign", MathF.Sign(Amount.Float())),
|
|
||||||
("reagent", reagentProto.LocalizedName),
|
|
||||||
("amount", MathF.Abs(Amount.Float())));
|
|
||||||
}
|
|
||||||
else if (Group is not null && prototype.TryIndex(Group, out MetabolismGroupPrototype? groupProto))
|
|
||||||
{
|
|
||||||
return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group",
|
|
||||||
("chance", Probability),
|
|
||||||
("deltasign", MathF.Sign(Amount.Float())),
|
|
||||||
("group", groupProto.LocalizedName),
|
|
||||||
("amount", MathF.Abs(Amount.Float())));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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)));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Content.Shared.Temperature.Components;
|
||||||
|
using Content.Shared.Temperature.Systems;
|
||||||
|
|
||||||
|
namespace Content.Shared.EntityEffects.Effects;
|
||||||
|
|
||||||
|
// TODO: When we get a proper temperature/energy struct combine this with the solution temperature effect!!!
|
||||||
|
/// <summary>
|
||||||
|
/// Adjusts the temperature of this entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
|
||||||
|
public sealed partial class AdjustTemperatureEntityEffectSystem : EntityEffectSystem<TemperatureComponent, AdjustTemperature>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedTemperatureSystem _temperature = default!;
|
||||||
|
protected override void Effect(Entity<TemperatureComponent> entity, ref EntityEffectEvent<AdjustTemperature> args)
|
||||||
|
{
|
||||||
|
var amount = args.Effect.Amount * args.Scale;
|
||||||
|
|
||||||
|
_temperature.ChangeHeat(entity, amount, true, entity.Comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="EntityEffect"/>
|
||||||
|
public sealed partial class AdjustTemperature : EntityEffectBase<AdjustTemperature>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Amount we're adjusting temperature by.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Amount;
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Shared.EntityEffects.Effects;
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user