From 4059c29ebc760b9200db768d313b5a41f511b915 Mon Sep 17 00:00:00 2001
From: Princess Cheeseballs
<66055347+Princess-Cheeseballs@users.noreply.github.com>
Date: Sun, 12 Oct 2025 14:23:42 -0700
Subject: [PATCH] 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>
---
.github/CODEOWNERS | 1 +
.../Temperature/Systems/TemperatureSystem.cs | 8 +
.../Atmos/EntitySystems/FlammableSystem.cs | 2 +-
Content.Server/Atmos/Rotting/RottingSystem.cs | 2 +-
.../Body/Components/MetabolizerComponent.cs | 4 +-
.../Body/Systems/MetabolizerSystem.cs | 450 ++++----
.../Body/Systems/RespiratorSystem.cs | 15 +-
.../Body/Systems/ThermalRegulatorSystem.cs | 2 +-
Content.Server/Botany/SeedPrototype.cs | 11 +-
.../Botany/Systems/BotanySystem.Produce.cs | 7 +-
.../Botany/Systems/MutationSystem.cs | 7 +-
.../Botany/Systems/PlantHolderSystem.cs | 5 +-
.../Commands/DumpReagentGuideText.cs | 2 +-
.../ConstructionSystem.Interactions.cs | 1 +
.../BreathingEntityConditionSystem.cs | 19 +
.../MetabolizerTypesEntityConditionSystem.cs | 21 +
.../Atmos/CreateGasEntityEffectSystem.cs | 22 +
.../Atmos/FlammableEntityEffectSystem.cs | 25 +
.../Effects/Atmos/IgniteEntityEffectSystem.cs | 23 +
.../Body/OxygenateEntityEffectsSystem.cs | 20 +
.../PlantAdjustHealthEntityEffectSystem.cs | 20 +
...ntAdjustMutationLevelEntityEffectSystem.cs | 20 +
...lantAdjustMutationModEntityEffectSystem.cs | 16 +
.../PlantAdjustNutritionEntityEffectSystem.cs | 16 +
.../PlantAdjustPestsEntityEffectSystem.cs | 16 +
.../PlantAdjustPotencyEntityEffectSystem.cs | 19 +
.../PlantAdjustToxinsEntityEffectSystem.cs | 16 +
.../PlantAdjustWaterEntityEffectSystem.cs | 16 +
.../PlantAdjustWeedsEntityEffectSystem.cs | 16 +
.../PlantAffectGrowthEntityEffectSystem.cs | 19 +
.../PlantChangeStatEntityEffectSystem.cs | 122 +++
.../PlantCryoxadoneEntityEffectSystem.cs | 30 +
.../PlantDestroySeedsEntityEffectSystem.cs | 31 +
.../PlantDiethylamineEntityEffectSystem.cs | 31 +
.../PlantPhalanximineEntityEffectSystem.cs | 16 +
.../PlantRestoreSeedsEntityEffectSystem.cs | 26 +
.../RobustHarvestEntityEffectSystem.cs | 41 +
.../PlantMutateChemicalsEntityEffectSystem.cs | 43 +
.../PlantMutateGasesEntityEffectSystem.cs | 53 +
.../PlantMutateHarvestEntityEffectSystem.cs | 25 +
...ntMutateSpeciesChangeEntityEffectSystem.cs | 31 +
.../Effects/EmoteEntityEffectSystem.cs | 22 +
.../Effects/MakeSentientEntityEffectSystem.cs | 42 +
.../Effects/PolymorphEntityEffectSystem.cs | 19 +
.../AreaReactionEntityEffectSystem.cs | 51 +
.../Transform/ExplosionEntityEffectSystem.cs | 28 +
.../EntityEffects/EntityEffectSystem.cs | 976 ------------------
.../Fluids/EntitySystems/SmokeSystem.cs | 7 +-
.../GuideGenerator/ChemistryJsonGenerator.cs | 3 +-
Content.Server/GuideGenerator/ReagentEntry.cs | 2 +-
.../Kitchen/EntitySystems/MicrowaveSystem.cs | 1 +
Content.Server/Medical/CryoPodSystem.cs | 2 +-
.../Medical/HealthAnalyzerSystem.cs | 2 +-
.../NPC/Systems/NPCUtilitySystem.cs | 2 +-
.../Temperature/Systems/TemperatureSystem.cs | 19 +-
.../Tiles/TileEntityEffectComponent.cs | 2 +-
.../Tiles/TileEntityEffectSystem.cs | 10 +-
.../Zombies/ZombieSystem.Transform.cs | 2 +-
Content.Shared.Database/LogType.cs | 4 +-
.../Body/Components/LungComponent.cs | 2 +-
Content.Shared/Body/Systems/LungSystem.cs | 12 +-
.../Body/Systems/SharedBloodstreamSystem.cs | 7 +-
.../SharedSolutionContainerSystem.cs | 21 +
.../Reaction/ChemicalReactionSystem.cs | 18 +-
.../Chemistry/Reaction/ReactionPrototype.cs | 2 +-
.../Chemistry/Reaction/ReactiveComponent.cs | 2 +-
Content.Shared/Chemistry/ReactiveSystem.cs | 100 +-
.../Chemistry/Reagent/ReagentPrototype.cs | 73 +-
.../Body/BreathingEntityCondition.cs | 10 +
.../Body/HungerEntityConditionSystem.cs | 33 +
.../Body/InternalsEntityConditionSystem.cs | 23 +
.../Body/MetabolizerTypeEntityCondition.cs | 31 +
.../Body/MobStateEntityConditionSystem.cs | 28 +
.../Conditions/JobEntityConditionSystem.cs | 59 ++
.../ReagentEntityConditionSystem.cs | 44 +
.../Tags/HasAllTagsEntityConditionSystem.cs | 43 +
.../Tags/HasAnyTagEntityConditionSystem.cs | 43 +
.../Tags/HasTagEntityConditionSystem.cs | 28 +
.../TemperatureEntityConditionSystem.cs | 52 +
.../TemplateEntityConditionSystem.cs | 20 +
.../TotalDamageEntityConditionSystem.cs | 33 +
.../SharedEntityConditionsSystem.cs | 152 +++
.../EffectConditions/BodyTemperature.cs | 23 -
.../EffectConditions/BreathingCondition.cs | 21 -
.../EffectConditions/HasTagCondition.cs | 28 -
.../EffectConditions/InternalsCondition.cs | 30 -
.../EffectConditions/JobCondition.cs | 52 -
.../EffectConditions/MobStateCondition.cs | 28 -
.../EffectConditions/OrganType.cs | 28 -
.../EffectConditions/ReagentThreshold.cs | 56 -
.../EffectConditions/SolutionTemperature.cs | 36 -
.../EffectConditions/TotalDamage.cs | 33 -
.../EffectConditions/TotalHunger.cs | 33 -
.../Effects/AddToSolutionReaction.cs | 35 -
.../EntityEffects/Effects/AdjustAlert.cs | 58 --
.../Effects/AdjustAlertEntityEffectSystem.cs | 65 ++
.../EntityEffects/Effects/AdjustReagent.cs | 90 --
.../Effects/AdjustTemperature.cs | 15 -
.../AdjustTemperatureEntityEffectsSystem.cs | 30 +
.../Effects/AreaReactionEffect.cs | 43 -
.../Effects/ArtifactDurabilityRestore.cs | 36 -
.../Effects/ArtifactEntityEffectsSystem.cs | 72 ++
.../EntityEffects/Effects/ArtifactUnlock.cs | 44 -
.../Effects/Atmos/CreateGasEntityEffect.cs | 35 +
.../Atmos/ExtinguishEntityEffectSystem.cs | 36 +
.../Effects/Atmos/FlammableEntityEffect.cs | 28 +
.../Effects/Atmos/IgniteEntityEffect.cs | 18 +
.../CleanBloodstreamEntityEffectSystem.cs | 43 +
.../Body/EyeDamageEntityEffectSystem.cs | 32 +
.../Body/ModifyBleedEntityEffectSystem.cs | 32 +
.../ModifyBloodLevelEntityEffectSystem.cs | 34 +
.../Body/ModifyLungGasEntityEffectSystem.cs | 33 +
.../Effects/Body/OxygenateEntityEffect.cs | 14 +
.../Body/ReduceRottingEntityEffectSystem.cs | 36 +
.../Effects/Body/SatiateEntityEffectSystem.cs | 63 ++
.../Effects/Body/VomitEntityEffectSystem.cs | 37 +
.../BasePlantAdjustAttributeEntityEffect.cs | 37 +
.../PlantAttributes/PlantAdjustHealth.cs | 7 +
.../PlantAdjustMutationLevel.cs | 7 +
.../PlantAttributes/PlantAdjustMutationMod.cs | 7 +
.../PlantAttributes/PlantAdjustNutrition.cs | 6 +
.../PlantAttributes}/PlantAdjustPests.cs | 4 +-
.../PlantAttributes/PlantAdjustPotency.cs | 9 +
.../PlantAttributes}/PlantAdjustToxins.cs | 5 +-
.../PlantAttributes/PlantAdjustWater.cs | 7 +
.../PlantAttributes}/PlantAdjustWeeds.cs | 4 +-
.../PlantAttributes/PlantAffectGrowth.cs | 7 +
.../Botany/PlantAttributes/PlantChangeStat.cs | 21 +
.../Botany/PlantAttributes/PlantCryoxadone.cs | 9 +
.../PlantAttributes/PlantDestroySeeds.cs | 12 +
.../PlantAttributes/PlantDiethylamine.cs | 11 +
.../PlantAttributes/PlantPhalanximine.cs | 10 +
.../PlantAttributes/PlantRestoreSeeds.cs | 12 +
.../Botany/PlantAttributes/RobustHarvest.cs | 22 +
.../PlantMutateChemicalsEntityEffect.cs | 16 +
.../Botany/PlantMutateGasesEntityEffect.cs | 22 +
.../Botany/PlantMutateHarvestEntityEffect.cs | 6 +
.../PlantMutateSpeciesChangeEntityEffect.cs | 6 +
.../Effects/CauseZombieInfection.cs | 9 -
.../Effects/ChemCleanBloodstream.cs | 15 -
.../Effects/ChemHealEyeDamage.cs | 28 -
.../EntityEffects/Effects/ChemVomit.cs | 19 -
.../Effects/CreateEntityReactionEffect.cs | 26 -
.../EntityEffects/Effects/CreateGas.cs | 32 -
.../Effects/CureZombieInfection.cs | 18 -
Content.Shared/EntityEffects/Effects/Drunk.cs | 27 -
.../EntityEffects/Effects/Electrocute.cs | 37 -
Content.Shared/EntityEffects/Effects/Emote.cs | 44 -
.../Effects/EmoteEntityEffect.cs | 40 +
.../Effects/EmpReactionEffect.cs | 34 -
.../BaseSpawnEntityEntityEffect.cs | 36 +
.../SpawnEntityEntityEffectSystem.cs | 37 +
...pawnEntityInContainerEntityEffectSystem.cs | 51 +
...tityInContainerOrDropEntityEffectSystem.cs | 49 +
...pawnEntityInInventoryEntityEffectSystem.cs | 35 +
.../EntityEffects/Effects/EvenHealthChange.cs | 139 ---
.../EvenHealthChangeEntityEffectSystem.cs | 114 ++
.../Effects/ExtinguishReaction.cs | 32 -
.../Effects/FlammableReaction.cs | 21 -
.../Effects/FlashReactionEffect.cs | 48 -
Content.Shared/EntityEffects/Effects/Glow.cs | 48 -
.../Effects/GlowEntityEffectSystem.cs | 55 +
.../EntityEffects/Effects/HealthChange.cs | 126 ---
.../Effects/HealthChangeEntityEffectSystem.cs | 91 ++
.../EntityEffects/Effects/Ignite.cs | 18 -
.../EntityEffects/Effects/MakeSentient.cs | 10 -
.../Effects/MakeSentientEntityEffect.cs | 22 +
.../Effects/ModifyBleedAmount.cs | 16 -
.../EntityEffects/Effects/ModifyBloodLevel.cs | 17 -
.../EntityEffects/Effects/ModifyLungGas.cs | 14 -
.../Effects/MovespeedModifier.cs | 78 --
.../EntityEffects/Effects/Oxygenate.cs | 13 -
.../EntityEffects/Effects/Paralyze.cs | 37 -
.../PlantMetabolism/PlantAdjustAttribute.cs | 39 -
.../PlantMetabolism/PlantAdjustHealth.cs | 7 -
.../PlantAdjustMutationLevel.cs | 6 -
.../PlantMetabolism/PlantAdjustMutationMod.cs | 6 -
.../PlantMetabolism/PlantAdjustNutrition.cs | 6 -
.../PlantMetabolism/PlantAdjustPotency.cs | 12 -
.../PlantMetabolism/PlantAdjustWater.cs | 7 -
.../PlantMetabolism/PlantAffectGrowth.cs | 7 -
.../PlantMetabolism/PlantChangeStat.cs | 24 -
.../PlantMetabolism/PlantCryoxadone.cs | 8 -
.../PlantMetabolism/PlantDestroySeeds.cs | 13 -
.../PlantMetabolism/PlantDiethylamine.cs | 9 -
.../PlantMetabolism/PlantPhalanximine.cs | 8 -
.../PlantMetabolism/PlantRestoreSeeds.cs | 12 -
.../Effects/PlantMetabolism/RobustHarvest.cs | 18 -
.../Effects/PlantMutateChemicals.cs | 16 -
.../EntityEffects/Effects/PlantMutateGases.cs | 39 -
.../Effects/PlantMutateHarvest.cs | 14 -
.../Effects/PlantSpeciesChange.cs | 14 -
.../EntityEffects/Effects/Polymorph.cs | 19 -
.../Effects/PolymorphEntityEffect.cs | 19 +
.../EntityEffects/Effects/PopupMessage.cs | 54 -
.../EntityEffects/Effects/ReduceRotting.cs | 32 -
.../EntityEffects/Effects/ResetNarcolepsy.cs | 19 -
.../ResetNarcolepsyEntityEffectSystem.cs | 34 +
.../EntityEffects/Effects/SatiateHunger.cs | 40 -
.../EntityEffects/Effects/SatiateThirst.cs | 31 -
.../EntityEffects/Effects/Slipify.cs | 37 -
.../Effects/SlipifyEntityEffectSystem.cs | 44 +
.../AddReagentToSolutionEntityEffectSystem.cs | 59 ++
.../AdjustReagentEntityEffectSystem.cs | 52 +
...AdjustReagentsByGroupEntityEffectSystem.cs | 52 +
.../Solution/AreaReactionEntityEffect.cs | 39 +
.../SolutionTemperatureEntityEffectsSystem.cs | 145 +++
.../Effects/SolutionTemperatureEffects.cs | 143 ---
.../BaseStatusEffectEntityEffect.cs | 35 +
.../StatusEffects/DrunkEntityEffectSystem.cs | 34 +
.../ElectrocuteEntityEffectSystem.cs | 51 +
.../StatusEffects/GenericStatusEffect.cs | 76 --
.../GenericStatusEffectEntityEffectSystem.cs | 64 ++
.../Effects/StatusEffects/Jitter.cs | 41 -
.../StatusEffects/JitterEntityEffectSystem.cs | 45 +
.../Effects/StatusEffects/ModifyKnockdown.cs | 99 --
.../ModifyKnockdownEntityEffectSystem.cs | 72 ++
.../ModifyParalysisEntityEffectSystem.cs | 51 +
.../StatusEffects/ModifyStatusEffect.cs | 78 --
.../ModifyStatusEffectEntityEffectSystem.cs | 66 ++
...MovementSpeedModifierEntityEffectSystem.cs | 92 ++
.../Effects/TemplateEntityEffectSystem.cs | 21 +
.../Transform/EmpEntityEffectSystem.cs | 58 ++
.../ExplosionEntityEffect.cs} | 19 +-
.../Transform/FlashEntityEffectSystem.cs | 81 ++
.../PopupMessageEntityEffectSystem.cs | 64 ++
.../Effects/WashCreamPieEntityEffectSystem.cs | 27 +
.../Effects/WashCreamPieReaction.cs | 19 -
.../EntityEffects/Effects/WearableReaction.cs | 47 -
.../Effects/ZombieEntityEffectsSystem.cs | 67 ++
Content.Shared/EntityEffects/EntityEffect.cs | 145 ---
.../EntityEffects/EntityEffectCondition.cs | 29 -
.../EntityEffects/EventEntityEffect.cs | 12 -
.../EventEntityEffectCondition.cs | 14 -
.../SharedEntityEffectsSystem.cs | 251 +++++
.../Systems/MovementModStatusSystem.cs | 1 +
.../EntitySystems/IngestionSystem.API.cs | 9 +-
.../StatusEffectNew/StatusEffectSystem.API.cs | 18 +
.../Stunnable/SharedStunSystem.Knockdown.cs | 9 +-
Content.Shared/Stunnable/SharedStunSystem.cs | 7 +-
.../Components/TemperatureComponent.cs | 2 +-
.../Systems/SharedTemperatureSystem.cs | 19 +-
.../Zombies/IncurableZombieComponent.cs | 3 +-
.../Locale/en-US/guidebook/chemistry/core.ftl | 5 +-
.../guidebook/chemistry/statuseffects.ftl | 16 -
.../conditions.ftl | 0
.../{chemistry => entity-effects}/effects.ftl | 166 +--
.../healthchange.ftl | 0
.../entity-effects/statuseffects.ftl | 16 +
.../Prototypes/Entities/Mobs/NPCs/animals.yml | 2 +-
.../Entities/Mobs/NPCs/miscellaneous.yml | 20 +-
.../Prototypes/Entities/Mobs/NPCs/slimes.yml | 6 +-
.../Prototypes/Entities/Mobs/NPCs/space.yml | 2 +-
.../Entities/Mobs/Species/arachnid.yml | 6 +-
.../Prototypes/Entities/Mobs/Species/base.yml | 2 +-
.../Entities/Mobs/Species/diona.yml | 14 +-
.../Entities/Mobs/Species/skeleton.yml | 8 +-
.../Entities/Mobs/Species/slime.yml | 6 +-
.../Entities/Objects/Fun/plushies.yml | 3 +-
.../Entities/Objects/Misc/kudzu.yml | 2 +-
.../Objects/Specific/rehydrateable.yml | 6 +-
.../Entities/StatusEffects/movement.yml | 21 +
.../Prototypes/Entities/Structures/soil.yml | 3 +-
Resources/Prototypes/Entities/Tiles/lava.yml | 2 +-
.../Entities/Tiles/liquid_plasma.yml | 2 +-
Resources/Prototypes/Entities/Tiles/water.yml | 4 +-
.../Hydroponics/randomMutations.yml | 6 +-
.../Reagents/Consumable/Drink/alcohol.yml | 70 +-
.../Reagents/Consumable/Drink/base_drink.yml | 4 +-
.../Reagents/Consumable/Drink/drinks.yml | 7 +-
.../Reagents/Consumable/Drink/soda.yml | 6 +-
.../Reagents/Consumable/Food/food.yml | 4 +-
.../Reagents/Consumable/Food/ingredients.yml | 19 +-
Resources/Prototypes/Reagents/biological.yml | 14 +-
Resources/Prototypes/Reagents/botany.yml | 81 +-
Resources/Prototypes/Reagents/chemicals.yml | 16 +-
Resources/Prototypes/Reagents/cleaning.yml | 14 +-
Resources/Prototypes/Reagents/elements.yml | 22 +-
Resources/Prototypes/Reagents/fun.yml | 93 +-
Resources/Prototypes/Reagents/gases.yml | 282 ++---
Resources/Prototypes/Reagents/medicine.yml | 224 ++--
Resources/Prototypes/Reagents/narcotics.yml | 72 +-
Resources/Prototypes/Reagents/pyrotechnic.yml | 26 +-
Resources/Prototypes/Reagents/toxins.yml | 127 ++-
.../Recipes/Reactions/chemicals.yml | 8 +-
.../Prototypes/Recipes/Reactions/food.yml | 26 +-
.../Prototypes/Recipes/Reactions/fun.yml | 14 +-
.../Recipes/Reactions/pyrotechnic.yml | 4 +-
.../Prototypes/Recipes/Reactions/soap.yml | 12 +-
289 files changed, 5635 insertions(+), 4918 deletions(-)
create mode 100644 Content.Client/Temperature/Systems/TemperatureSystem.cs
create mode 100644 Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs
create mode 100644 Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs
create mode 100644 Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs
delete mode 100644 Content.Server/EntityEffects/EntityEffectSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs
create mode 100644 Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/JobCondition.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/OrganType.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs
delete mode 100644 Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/AdjustAlert.cs
create mode 100644 Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/AdjustReagent.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/AdjustTemperature.cs
create mode 100644 Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs
create mode 100644 Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs
rename Content.Shared/EntityEffects/Effects/{PlantMetabolism => Botany/PlantAttributes}/PlantAdjustPests.cs (53%)
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs
rename Content.Shared/EntityEffects/Effects/{PlantMetabolism => Botany/PlantAttributes}/PlantAdjustToxins.cs (53%)
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs
rename Content.Shared/EntityEffects/Effects/{PlantMetabolism => Botany/PlantAttributes}/PlantAdjustWeeds.cs (53%)
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ChemVomit.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/CreateGas.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/CureZombieInfection.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Drunk.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Electrocute.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Emote.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/EvenHealthChange.cs
create mode 100644 Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/FlammableReaction.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Glow.cs
create mode 100644 Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/HealthChange.cs
create mode 100644 Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Ignite.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/MakeSentient.cs
create mode 100644 Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ModifyLungGas.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/MovespeedModifier.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Oxygenate.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Paralyze.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMutateGases.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Polymorph.cs
create mode 100644 Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/PopupMessage.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ReduceRotting.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs
create mode 100644 Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/SatiateHunger.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/SatiateThirst.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/Slipify.cs
create mode 100644 Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Transform/EmpEntityEffectSystem.cs
rename Content.Shared/EntityEffects/Effects/{ExplosionReactionEffect.cs => Transform/ExplosionEntityEffect.cs} (69%)
create mode 100644 Content.Shared/EntityEffects/Effects/Transform/FlashEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs
create mode 100644 Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/WashCreamPieReaction.cs
delete mode 100644 Content.Shared/EntityEffects/Effects/WearableReaction.cs
create mode 100644 Content.Shared/EntityEffects/Effects/ZombieEntityEffectsSystem.cs
delete mode 100644 Content.Shared/EntityEffects/EntityEffect.cs
delete mode 100644 Content.Shared/EntityEffects/EntityEffectCondition.cs
delete mode 100644 Content.Shared/EntityEffects/EventEntityEffect.cs
delete mode 100644 Content.Shared/EntityEffects/EventEntityEffectCondition.cs
create mode 100644 Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs
rename {Content.Server => Content.Shared}/Temperature/Components/TemperatureComponent.cs (98%)
rename {Content.Server => Content.Shared}/Zombies/IncurableZombieComponent.cs (77%)
delete mode 100644 Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl
rename Resources/Locale/en-US/guidebook/{chemistry => entity-effects}/conditions.ftl (100%)
rename Resources/Locale/en-US/guidebook/{chemistry => entity-effects}/effects.ftl (71%)
rename Resources/Locale/en-US/guidebook/{chemistry => entity-effects}/healthchange.ftl (100%)
create mode 100644 Resources/Locale/en-US/guidebook/entity-effects/statuseffects.ftl
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 14f591ec87..defe08ef23 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -28,6 +28,7 @@
/Content.*/Stunnable/ @Princess-Cheeseballs
/Content.*/Nutrition/ @Princess-Cheeseballs
+/Content.*/EntityEffects @Princess-Cheeseballs @sowelipililimute
# SKREEEE
/Content.*.Database/ @PJB3005 @DrSmugleaf
diff --git a/Content.Client/Temperature/Systems/TemperatureSystem.cs b/Content.Client/Temperature/Systems/TemperatureSystem.cs
new file mode 100644
index 0000000000..94a1e836e8
--- /dev/null
+++ b/Content.Client/Temperature/Systems/TemperatureSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Temperature.Systems;
+
+namespace Content.Client.Temperature.Systems;
+
+///
+/// This exists so runs on client/>
+///
+public sealed class TemperatureSystem : SharedTemperatureSystem;
diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
index 424f52ed58..81bd4e5c6c 100644
--- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
+++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs
@@ -1,7 +1,6 @@
using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
using Content.Server.Stunnable;
-using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Server.Damage.Components;
using Content.Shared.ActionBlocker;
@@ -24,6 +23,7 @@ using Content.Shared.Toggleable;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.FixedPoint;
using Content.Shared.Hands;
+using Content.Shared.Temperature.Components;
using Robust.Server.Audio;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
diff --git a/Content.Server/Atmos/Rotting/RottingSystem.cs b/Content.Server/Atmos/Rotting/RottingSystem.cs
index 6f14debc3d..57c1504b16 100644
--- a/Content.Server/Atmos/Rotting/RottingSystem.cs
+++ b/Content.Server/Atmos/Rotting/RottingSystem.cs
@@ -1,9 +1,9 @@
using Content.Server.Atmos.EntitySystems;
-using Content.Server.Temperature.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Rotting;
using Content.Shared.Body.Events;
using Content.Shared.Damage;
+using Content.Shared.Temperature.Components;
using Robust.Server.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
diff --git a/Content.Server/Body/Components/MetabolizerComponent.cs b/Content.Server/Body/Components/MetabolizerComponent.cs
index 5821b0d944..46d2fdd8e8 100644
--- a/Content.Server/Body/Components/MetabolizerComponent.cs
+++ b/Content.Server/Body/Components/MetabolizerComponent.cs
@@ -10,13 +10,13 @@ namespace Content.Server.Body.Components
///
/// Handles metabolizing various reagents with given effects.
///
- [RegisterComponent, Access(typeof(MetabolizerSystem))]
+ [RegisterComponent, AutoGenerateComponentPause, Access(typeof(MetabolizerSystem))]
public sealed partial class MetabolizerComponent : Component
{
///
/// The next time that reagents will be metabolized.
///
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [DataField, AutoPausedField]
public TimeSpan NextUpdate;
///
diff --git a/Content.Server/Body/Systems/MetabolizerSystem.cs b/Content.Server/Body/Systems/MetabolizerSystem.cs
index c59f87f576..6679bfea54 100644
--- a/Content.Server/Body/Systems/MetabolizerSystem.cs
+++ b/Content.Server/Body/Systems/MetabolizerSystem.cs
@@ -1,14 +1,18 @@
using Content.Server.Body.Components;
-using Content.Shared.Administration.Logs;
using Content.Shared.Body.Events;
using Content.Shared.Body.Organ;
+using Content.Shared.Body.Prototypes;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Database;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions;
+using Content.Shared.EntityConditions.Conditions.Body;
using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Body;
+using Content.Shared.EntityEffects.Effects.Solution;
using Content.Shared.FixedPoint;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
@@ -17,210 +21,258 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
-namespace Content.Server.Body.Systems
+namespace Content.Server.Body.Systems;
+
+///
+public sealed class MetabolizerSystem : SharedMetabolizerSystem
{
- ///
- public sealed class MetabolizerSystem : SharedMetabolizerSystem
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+ [Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!;
+ [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
+
+ private EntityQuery _organQuery;
+ private EntityQuery _solutionQuery;
+ private static readonly ProtoId Gas = "Gas";
+
+ public override void Initialize()
{
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
+ base.Initialize();
- private EntityQuery _organQuery;
- private EntityQuery _solutionQuery;
+ _organQuery = GetEntityQuery();
+ _solutionQuery = GetEntityQuery();
- public override void Initialize()
+ SubscribeLocalEvent(OnMetabolizerInit);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnApplyMetabolicMultiplier);
+ }
+
+ private void OnMapInit(Entity ent, ref MapInitEvent args)
+ {
+ ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
+ }
+
+ private void OnMetabolizerInit(Entity entity, ref ComponentInit args)
+ {
+ if (!entity.Comp.SolutionOnBody)
{
- base.Initialize();
-
- _organQuery = GetEntityQuery();
- _solutionQuery = GetEntityQuery();
-
- SubscribeLocalEvent(OnMetabolizerInit);
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnUnpaused);
- SubscribeLocalEvent(OnApplyMetabolicMultiplier);
+ _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
}
-
- private void OnMapInit(Entity ent, ref MapInitEvent args)
+ else if (_organQuery.CompOrNull(entity)?.Body is { } body)
{
- ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
- }
-
- private void OnUnpaused(Entity ent, ref EntityUnpausedEvent args)
- {
- ent.Comp.NextUpdate += args.PausedTime;
- }
-
- private void OnMetabolizerInit(Entity entity, ref ComponentInit args)
- {
- if (!entity.Comp.SolutionOnBody)
- {
- _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
- }
- else if (_organQuery.CompOrNull(entity)?.Body is { } body)
- {
- _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
- }
- }
-
- private void OnApplyMetabolicMultiplier(Entity ent, ref ApplyMetabolicMultiplierEvent args)
- {
- ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count());
- var query = EntityQueryEnumerator();
-
- while (query.MoveNext(out var uid, out var comp))
- {
- metabolizers.Add((uid, comp));
- }
-
- foreach (var (uid, metab) in metabolizers)
- {
- // Only update as frequently as it should
- if (_gameTiming.CurTime < metab.NextUpdate)
- continue;
-
- metab.NextUpdate += metab.AdjustedUpdateInterval;
- TryMetabolize((uid, metab));
- }
- }
-
- private void TryMetabolize(Entity ent)
- {
- _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
-
- // First step is get the solution we actually care about
- var solutionName = ent.Comp1.SolutionName;
- Solution? solution = null;
- Entity? soln = default!;
- EntityUid? solutionEntityUid = null;
-
- if (ent.Comp1.SolutionOnBody)
- {
- if (ent.Comp2?.Body is { } body)
- {
- if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
- return;
-
- _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
- solutionEntityUid = body;
- }
- }
- else
- {
- if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
- return;
-
- _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
- solutionEntityUid = ent;
- }
-
- if (solutionEntityUid is null
- || soln is null
- || solution is null
- || solution.Contents.Count == 0)
- {
- return;
- }
-
- // randomize the reagent list so we don't have any weird quirks
- // like alphabetical order or insertion order mattering for processing
- var list = solution.Contents.ToArray();
- _random.Shuffle(list);
-
- int reagents = 0;
- foreach (var (reagent, quantity) in list)
- {
- if (!_prototypeManager.TryIndex(reagent.Prototype, out var proto))
- continue;
-
- var mostToRemove = FixedPoint2.Zero;
- if (proto.Metabolisms is null)
- {
- if (ent.Comp1.RemoveEmpty)
- {
- solution.RemoveReagent(reagent, FixedPoint2.New(1));
- }
-
- continue;
- }
-
- // we're done here entirely if this is true
- if (reagents >= ent.Comp1.MaxReagentsProcessable)
- return;
-
-
- // loop over all our groups and see which ones apply
- if (ent.Comp1.MetabolismGroups is null)
- continue;
-
- foreach (var group in ent.Comp1.MetabolismGroups)
- {
- if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
- continue;
-
- var rate = entry.MetabolismRate * group.MetabolismRateModifier;
-
- // Remove $rate, as long as there's enough reagent there to actually remove that much
- mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
-
- float scale = (float) mostToRemove / (float) rate;
-
- // if it's possible for them to be dead, and they are,
- // then we shouldn't process any effects, but should probably
- // still remove reagents
- if (TryComp(solutionEntityUid.Value, out var state))
- {
- if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
- continue;
- }
-
- var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
- var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale);
-
- // do all effects, if conditions apply
- foreach (var effect in entry.Effects)
- {
- if (!effect.ShouldApply(args, _random))
- continue;
-
- if (effect.ShouldLog)
- {
- _adminLogger.Add(
- LogType.ReagentEffect,
- effect.LogImpact,
- $"Metabolism effect {effect.GetType().Name:effect}"
- + $" of reagent {proto.LocalizedName:reagent}"
- + $" applied on entity {actualEntity:entity}"
- + $" at {Transform(actualEntity).Coordinates:coordinates}"
- );
- }
-
- effect.Effect(args);
- }
- }
-
- // remove a certain amount of reagent
- if (mostToRemove > FixedPoint2.Zero)
- {
- solution.RemoveReagent(reagent, mostToRemove);
-
- // We have processed a reagant, so count it towards the cap
- reagents += 1;
- }
- }
-
- _solutionContainerSystem.UpdateChemicals(soln.Value);
+ _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
}
}
+
+ private void OnApplyMetabolicMultiplier(Entity ent, ref ApplyMetabolicMultiplierEvent args)
+ {
+ ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count());
+ var query = EntityQueryEnumerator();
+
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ metabolizers.Add((uid, comp));
+ }
+
+ foreach (var (uid, metab) in metabolizers)
+ {
+ // Only update as frequently as it should
+ if (_gameTiming.CurTime < metab.NextUpdate)
+ continue;
+
+ metab.NextUpdate += metab.AdjustedUpdateInterval;
+ TryMetabolize((uid, metab));
+ }
+ }
+
+ private void TryMetabolize(Entity ent)
+ {
+ _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
+
+ // First step is get the solution we actually care about
+ var solutionName = ent.Comp1.SolutionName;
+ Solution? solution = null;
+ Entity? soln = default!;
+ EntityUid? solutionEntityUid = null;
+
+ if (ent.Comp1.SolutionOnBody)
+ {
+ if (ent.Comp2?.Body is { } body)
+ {
+ if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
+ return;
+
+ _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
+ solutionEntityUid = body;
+ }
+ }
+ else
+ {
+ if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
+ return;
+
+ _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
+ solutionEntityUid = ent;
+ }
+
+ if (solutionEntityUid is null
+ || soln is null
+ || solution is null
+ || solution.Contents.Count == 0)
+ {
+ return;
+ }
+
+ // randomize the reagent list so we don't have any weird quirks
+ // like alphabetical order or insertion order mattering for processing
+ var list = solution.Contents.ToArray();
+ _random.Shuffle(list);
+
+ int reagents = 0;
+ foreach (var (reagent, quantity) in list)
+ {
+ if (!_prototypeManager.TryIndex(reagent.Prototype, out var proto))
+ continue;
+
+ var mostToRemove = FixedPoint2.Zero;
+ if (proto.Metabolisms is null)
+ {
+ if (ent.Comp1.RemoveEmpty)
+ {
+ solution.RemoveReagent(reagent, FixedPoint2.New(1));
+ }
+
+ continue;
+ }
+
+ // we're done here entirely if this is true
+ if (reagents >= ent.Comp1.MaxReagentsProcessable)
+ return;
+
+
+ // loop over all our groups and see which ones apply
+ if (ent.Comp1.MetabolismGroups is null)
+ continue;
+
+ // TODO: Kill MetabolismGroups!
+ foreach (var group in ent.Comp1.MetabolismGroups)
+ {
+ if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
+ continue;
+
+ var rate = entry.MetabolismRate * group.MetabolismRateModifier;
+
+ // Remove $rate, as long as there's enough reagent there to actually remove that much
+ mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
+
+ var scale = (float) mostToRemove;
+
+ // TODO: This is a very stupid workaround to lungs heavily relying on scale = reagent quantity. Needs lung and metabolism refactors to remove.
+ // TODO: Lungs just need to have their scale be equal to the mols consumed, scale needs to be not hardcoded either and configurable per metabolizer...
+ if (group.Id != Gas)
+ scale /= (float) entry.MetabolismRate;
+
+ // if it's possible for them to be dead, and they are,
+ // then we shouldn't process any effects, but should probably
+ // still remove reagents
+ if (TryComp(solutionEntityUid.Value, out var state))
+ {
+ if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
+ continue;
+ }
+
+ var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
+
+ // do all effects, if conditions apply
+ foreach (var effect in entry.Effects)
+ {
+ if (scale < effect.MinScale)
+ continue;
+
+ // See if conditions apply
+ if (effect.Conditions != null && !CanMetabolizeEffect(actualEntity, ent, soln.Value, effect.Conditions))
+ continue;
+
+ ApplyEffect(effect);
+
+ }
+
+ // TODO: We should have to do this with metabolism. ReagentEffect struct needs refactoring and so does metabolism!
+ void ApplyEffect(EntityEffect effect)
+ {
+ switch (effect)
+ {
+ case ModifyLungGas:
+ _entityEffects.ApplyEffect(ent, effect, scale);
+ break;
+ case AdjustReagent:
+ _entityEffects.ApplyEffect(soln.Value, effect, scale);
+ break;
+ default:
+ _entityEffects.ApplyEffect(actualEntity, effect, scale);
+ break;
+ }
+ }
+ }
+
+ // remove a certain amount of reagent
+ if (mostToRemove > FixedPoint2.Zero)
+ {
+ solution.RemoveReagent(reagent, mostToRemove);
+
+ // We have processed a reagant, so count it towards the cap
+ reagents += 1;
+ }
+ }
+
+ _solutionContainerSystem.UpdateChemicals(soln.Value);
+ }
+
+ ///
+ /// Public API to check if a certain metabolism effect can be applied to an entity.
+ /// TODO: With metabolism refactor make this logic smarter and unhardcode the old hardcoding entity effects used to have for metabolism!
+ ///
+ /// The body metabolizing the effects
+ /// The organ doing the metabolizing
+ /// The solution we are metabolizing from
+ /// The conditions that need to be met to metabolize
+ /// True if we can metabolize! False if we cannot!
+ public bool CanMetabolizeEffect(EntityUid body, EntityUid organ, Entity solution, EntityCondition[] conditions)
+ {
+ foreach (var condition in conditions)
+ {
+ switch (condition)
+ {
+ // Need specific handling of specific conditions since Metabolism is funny like that.
+ // TODO: MetabolizerTypes should be handled well before this stage by metabolism itself.
+ case MetabolizerTypeCondition:
+ if (_entityConditions.TryCondition(organ, condition))
+ continue;
+ break;
+ case ReagentCondition:
+ if (_entityConditions.TryCondition(solution, condition))
+ continue;
+ break;
+ default:
+ if (_entityConditions.TryCondition(body, condition))
+ continue;
+ break;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
}
+
diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs
index 63b04adc6a..2af7b24f26 100644
--- a/Content.Server/Body/Systems/RespiratorSystem.cs
+++ b/Content.Server/Body/Systems/RespiratorSystem.cs
@@ -2,7 +2,6 @@ using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Chat.Systems;
-using Content.Server.EntityEffects;
using Content.Shared.Body.Systems;
using Content.Shared.Alert;
using Content.Shared.Atmos;
@@ -14,9 +13,11 @@ using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.Database;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions.Body;
using Content.Shared.EntityEffects;
-using Content.Shared.EntityEffects.EffectConditions;
using Content.Shared.EntityEffects.Effects;
+using Content.Shared.EntityEffects.Effects.Body;
using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
@@ -29,16 +30,16 @@ public sealed class RespiratorSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosSys = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly DamageableSystem _damageableSys = default!;
[Dependency] private readonly LungSystem _lungSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
- [Dependency] private readonly IPrototypeManager _protoMan = default!;
+ [Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
- [Dependency] private readonly ChatSystem _chat = default!;
- [Dependency] private readonly EntityEffectSystem _entityEffect = default!;
private static readonly ProtoId GasId = new("Gas");
@@ -340,7 +341,6 @@ public sealed class RespiratorSystem : EntitySystem
}
}
- // TODO generalize condition checks
// this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture
// Applying actual reaction effects require a full ReagentEffectArgs struct.
bool CanMetabolize(EntityEffect effect)
@@ -348,9 +348,10 @@ public sealed class RespiratorSystem : EntitySystem
if (effect.Conditions == null)
return true;
+ // TODO: Use Metabolism Public API to do this instead, once that API has been built.
foreach (var cond in effect.Conditions)
{
- if (cond is OrganType organ && !_entityEffect.OrganCondition(organ, lung))
+ if (cond is MetabolizerTypeCondition organ && !_entityConditions.TryCondition(lung, organ))
return false;
}
diff --git a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs
index 3ba9e6af31..af7b17643e 100644
--- a/Content.Server/Body/Systems/ThermalRegulatorSystem.cs
+++ b/Content.Server/Body/Systems/ThermalRegulatorSystem.cs
@@ -1,7 +1,7 @@
using Content.Server.Body.Components;
-using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.ActionBlocker;
+using Content.Shared.Temperature.Components;
using Robust.Shared.Timing;
namespace Content.Server.Body.Systems;
diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs
index ee7ca4f584..253eea2df9 100644
--- a/Content.Server/Botany/SeedPrototype.cs
+++ b/Content.Server/Botany/SeedPrototype.cs
@@ -1,8 +1,9 @@
using Content.Server.Botany.Components;
using Content.Server.Botany.Systems;
-using Content.Server.EntityEffects;
+using Content.Server.EntityEffects.Effects.Botany;
using Content.Shared.Atmos;
using Content.Shared.Database;
+using Content.Shared.EntityEffects;
using Content.Shared.Random;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@@ -79,9 +80,13 @@ public partial struct SeedChemQuantity
[DataField("Inherent")] public bool Inherent = true;
}
-// TODO reduce the number of friends to a reasonable level. Requires ECS-ing things like plant holder component.
+// TODO Make Botany ECS and give it a proper API. I removed the limited access of this class because it's egregious how many systems needed access to it due to a lack of an actual API.
+///
+/// SeedData is no longer restricted because the number of friends is absolutely unreasonable.
+/// This entire data definition is unreasonable. I felt genuine fear looking at this, this is horrific. Send help.
+///
+// TODO: Hit Botany with hammers
[Virtual, DataDefinition]
-[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffectSystem), typeof(MutationSystem))]
public partial class SeedData
{
#region Tracking
diff --git a/Content.Server/Botany/Systems/BotanySystem.Produce.cs b/Content.Server/Botany/Systems/BotanySystem.Produce.cs
index f6f3f99c09..7d8f8652c7 100644
--- a/Content.Server/Botany/Systems/BotanySystem.Produce.cs
+++ b/Content.Server/Botany/Systems/BotanySystem.Produce.cs
@@ -7,6 +7,8 @@ namespace Content.Server.Botany.Systems;
public sealed partial class BotanySystem
{
+ [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
+
public void ProduceGrown(EntityUid uid, ProduceComponent produce)
{
if (!TryGetSeed(produce, out var seed))
@@ -15,10 +17,7 @@ public sealed partial class BotanySystem
foreach (var mutation in seed.Mutations)
{
if (mutation.AppliesToProduce)
- {
- var args = new EntityEffectBaseArgs(uid, EntityManager);
- mutation.Effect.Effect(args);
- }
+ _entityEffects.TryApplyEffect(uid, mutation.Effect);
}
if (!_solutionContainerSystem.EnsureSolution(uid,
diff --git a/Content.Server/Botany/Systems/MutationSystem.cs b/Content.Server/Botany/Systems/MutationSystem.cs
index ee35db48e3..834fd9e8ef 100644
--- a/Content.Server/Botany/Systems/MutationSystem.cs
+++ b/Content.Server/Botany/Systems/MutationSystem.cs
@@ -13,6 +13,7 @@ public sealed class MutationSystem : EntitySystem
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
private RandomPlantMutationListPrototype _randomMutations = default!;
public override void Initialize()
@@ -32,10 +33,8 @@ public sealed class MutationSystem : EntitySystem
if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f)))
{
if (mutation.AppliesToPlant)
- {
- var args = new EntityEffectBaseArgs(plantHolder, EntityManager);
- mutation.Effect.Effect(args);
- }
+ _entityEffects.TryApplyEffect(plantHolder, mutation.Effect);
+
// Stat adjustments do not persist by being an attached effect, they just change the stat.
if (mutation.Persists && !seed.Mutations.Any(m => m.Name == mutation.Name))
seed.Mutations.Add(mutation);
diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs
index caa796efe2..2554f95455 100644
--- a/Content.Server/Botany/Systems/PlantHolderSystem.cs
+++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs
@@ -24,8 +24,10 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Content.Shared.Administration.Logs;
+using Content.Shared.Chemistry.Reaction;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
+using Content.Shared.EntityEffects;
using Content.Shared.Kitchen.Components;
using Content.Shared.Labels.Components;
@@ -48,6 +50,7 @@ public sealed class PlantHolderSystem : EntitySystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
public const float HydroponicsSpeedMultiplier = 1f;
public const float HydroponicsConsumptionMultiplier = 2f;
@@ -887,7 +890,7 @@ public sealed class PlantHolderSystem : EntitySystem
foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt))
{
var reagentProto = _prototype.Index(entry.Reagent.Prototype);
- reagentProto.ReactionPlant(uid, entry, solution, EntityManager, _random, _adminLogger);
+ _entityEffects.ApplyEffects(uid, reagentProto.PlantMetabolisms.ToArray());
}
}
diff --git a/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs b/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs
index a70c2196ab..58c86f058a 100644
--- a/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs
+++ b/Content.Server/Chemistry/Commands/DumpReagentGuideText.cs
@@ -39,7 +39,7 @@ public sealed class DumpReagentGuideText : LocalizedEntityCommands
{
foreach (var effect in entry.Effects)
{
- shell.WriteLine(effect.GuidebookEffectDescription(_prototype, EntityManager.EntitySysManager) ??
+ shell.WriteLine(reagent.GuidebookReagentEffectDescription(_prototype, EntityManager.EntitySysManager, effect, entry.MetabolismRate) ??
Loc.GetString($"cmd-dumpreagentguidetext-skipped", ("effect", effect.GetType())));
}
}
diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs
index 3dd5a5b794..77a1a63e02 100644
--- a/Content.Server/Construction/ConstructionSystem.Interactions.cs
+++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs
@@ -13,6 +13,7 @@ using Content.Shared.Prying.Systems;
using Content.Shared.Radio.EntitySystems;
using Content.Shared.Stacks;
using Content.Shared.Temperature;
+using Content.Shared.Temperature.Components;
using Content.Shared.Tools.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Utility;
diff --git a/Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs b/Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs
new file mode 100644
index 0000000000..e7b8aaf22b
--- /dev/null
+++ b/Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs
@@ -0,0 +1,19 @@
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions.Body;
+
+namespace Content.Server.EntityConditions.Conditions;
+
+///
+/// Returns true if this entity is both able to breathe and is currently breathing.
+///
+///
+public sealed partial class IsBreathingEntityConditionSystem : EntityConditionSystem
+{
+ [Dependency] private readonly RespiratorSystem _respirator = default!;
+ protected override void Condition(Entity entity, ref EntityConditionEvent args)
+ {
+ args.Result = _respirator.IsBreathing(entity.AsNullable());
+ }
+}
diff --git a/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs b/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs
new file mode 100644
index 0000000000..3b4fb5292b
--- /dev/null
+++ b/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs
@@ -0,0 +1,21 @@
+using System.Linq;
+using Content.Server.Body.Components;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions.Body;
+
+namespace Content.Server.EntityConditions.Conditions;
+
+///
+/// Returns true if this entity has any of the listed metabolizer types.
+///
+///
+public sealed partial class MetabolizerTypeEntityConditionSystem : EntityConditionSystem
+{
+ protected override void Condition(Entity entity, ref EntityConditionEvent args)
+ {
+ if (entity.Comp.MetabolizerTypes == null)
+ return;
+
+ args.Result = entity.Comp.MetabolizerTypes.Overlaps(args.Condition.Type);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs
new file mode 100644
index 0000000000..033704ffcd
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs
@@ -0,0 +1,22 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+///
+/// This effect adjusts a gas at the tile this entity is currently on.
+/// The amount changed is modified by scale.
+///
+///
+public sealed partial class CreateGasEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ var tileMix = _atmosphere.GetContainingMixture(entity.AsNullable(), false, true);
+
+ tileMix?.AdjustMoles(args.Effect.Gas, args.Scale * args.Effect.Moles);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs
new file mode 100644
index 0000000000..65c818f143
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs
@@ -0,0 +1,25 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+///
+/// Adds a number of FireStacks modified by scale to this entity.
+/// The amount of FireStacks added is modified by scale.
+///
+///
+public sealed partial class FlammableEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly FlammableSystem _flammable = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ // The multiplier is determined by if the entity is already on fire, and if the multiplier for existing FireStacks has a value.
+ // If both of these are true, we use the MultiplierOnExisting value, otherwise we use the standard Multiplier.
+ var multiplier = entity.Comp.FireStacks == 0f || args.Effect.MultiplierOnExisting == null ? args.Effect.Multiplier : args.Effect.MultiplierOnExisting.Value;
+
+ _flammable.AdjustFireStacks(entity, args.Scale * multiplier, entity.Comp);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs
new file mode 100644
index 0000000000..de90656c66
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs
@@ -0,0 +1,23 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+///
+/// Sets this entity on fire.
+///
+///
+public sealed partial class IngiteEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly FlammableSystem _flammable = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ // TODO: Proper BodySystem Metabolism Effect relay...
+ // TODO: If this fucks over downstream shitmed, I give you full approval to use whatever shitcode method you need to fix it. Metabolism is awful.
+ _flammable.Ignite(entity, entity, flammable: entity.Comp);
+ }
+}
+
diff --git a/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs b/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs
new file mode 100644
index 0000000000..0cbf0b3864
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs
@@ -0,0 +1,20 @@
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Body;
+
+namespace Content.Server.EntityEffects.Effects.Body;
+
+///
+/// This effect adjusts a respirator's saturation value.
+/// The saturation adjustment is modified by scale.
+///
+///
+public sealed partial class OxygenateEntityEffectsSystem : EntityEffectSystem
+{
+ [Dependency] private readonly RespiratorSystem _respirator = default!;
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ _respirator.UpdateSaturation(entity, args.Scale * args.Effect.Factor, entity.Comp);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs
new file mode 100644
index 0000000000..64f61f5b11
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs
@@ -0,0 +1,20 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustHealthEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.MutationLevel += args.Effect.Amount * entity.Comp.MutationMod;
+ _plantHolder.CheckHealth(entity, entity.Comp);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs
new file mode 100644
index 0000000000..f35ff25b25
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs
@@ -0,0 +1,20 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationLevelEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.Health += args.Effect.Amount;
+ _plantHolder.CheckHealth(entity, entity.Comp);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs
new file mode 100644
index 0000000000..3163ee374c
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationModEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.MutationMod += args.Effect.Amount;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs
new file mode 100644
index 0000000000..56c016700d
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustNutritionEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ _plantHolder.AdjustNutrient(entity, args.Effect.Amount, entity);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs
new file mode 100644
index 0000000000..0495034b38
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustPestsEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.PestLevel += args.Effect.Amount;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
new file mode 100644
index 0000000000..ebe5c83181
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
@@ -0,0 +1,19 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustPotencyEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+ entity.Comp.Seed.Potency = Math.Max(entity.Comp.Seed.Potency + args.Effect.Amount, 1);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs
new file mode 100644
index 0000000000..31dc328977
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustToxinsEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.Toxins += args.Effect.Amount;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs
new file mode 100644
index 0000000000..706eeb2ffe
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustWaterEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ _plantHolder.AdjustWater(entity, args.Effect.Amount, entity.Comp);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs
new file mode 100644
index 0000000000..34aa51e4ff
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustWeedsEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ entity.Comp.WeedLevel += args.Effect.Amount;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
new file mode 100644
index 0000000000..b0faa6255e
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
@@ -0,0 +1,19 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAffectGrowthEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ _plantHolder.AffectGrowth(entity, (int)args.Effect.Amount, entity);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
new file mode 100644
index 0000000000..3d82f74b11
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
@@ -0,0 +1,122 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+///
+/// This system mutates an inputted stat for a PlantHolder, only works for floats, integers, and bools.
+///
+///
+public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSystem
+{
+ // TODO: This is awful. I do not have the strength to refactor this. I want it gone.
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ var effect = args.Effect;
+ var member = entity.Comp.Seed.GetType().GetField(args.Effect.TargetValue);
+
+ if (member == null)
+ {
+ Log.Error($"{ effect.GetType().Name } Error: Member { args.Effect.TargetValue} not found on { entity.Comp.Seed.GetType().Name }. Did you misspell it?");
+ return;
+ }
+
+ var currentValObj = member.GetValue(entity.Comp.Seed);
+ if (currentValObj == null)
+ return;
+
+ if (member.FieldType == typeof(float))
+ {
+ var floatVal = (float)currentValObj;
+ MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
+ member.SetValue(entity.Comp.Seed, floatVal);
+ }
+ else if (member.FieldType == typeof(int))
+ {
+ var intVal = (int)currentValObj;
+ MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
+ member.SetValue(entity.Comp.Seed, intVal);
+ }
+ else if (member.FieldType == typeof(bool))
+ {
+ var boolVal = (bool)currentValObj;
+ boolVal = !boolVal;
+ member.SetValue(entity.Comp.Seed, boolVal);
+ }
+ }
+
+ // Mutate reference 'val' between 'min' and 'max' by pretending the value
+ // is representable by a thermometer code with 'bits' number of bits and
+ // randomly flipping some of them.
+ private void MutateFloat(ref float val, float min, float max, int bits)
+ {
+ if (min == max)
+ {
+ val = min;
+ return;
+ }
+
+ // Starting number of bits that are high, between 0 and bits.
+ // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
+ int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
+ // val may be outside the range of min/max due to starting prototype values, so clamp.
+ valInt = Math.Clamp(valInt, 0, bits);
+
+ // Probability that the bit flip increases n.
+ // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
+ // In other words, it tends to go to the middle.
+ float probIncrease = 1 - (float)valInt / bits;
+ int valIntMutated;
+ if (_random.Prob(probIncrease))
+ {
+ valIntMutated = valInt + 1;
+ }
+ else
+ {
+ valIntMutated = valInt - 1;
+ }
+
+ // Set value based on mutated thermometer code.
+ float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
+ val = valMutated;
+ }
+
+ private void MutateInt(ref int val, int min, int max, int bits)
+ {
+ if (min == max)
+ {
+ val = min;
+ return;
+ }
+
+ // Starting number of bits that are high, between 0 and bits.
+ // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
+ int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
+ // val may be outside the range of min/max due to starting prototype values, so clamp.
+ valInt = Math.Clamp(valInt, 0, bits);
+
+ // Probability that the bit flip increases n.
+ // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
+ // In other words, it tends to go to the middle.
+ float probIncrease = 1 - (float)valInt / bits;
+ int valMutated;
+ if (_random.Prob(probIncrease))
+ {
+ valMutated = val + 1;
+ }
+ else
+ {
+ valMutated = val - 1;
+ }
+
+ valMutated = Math.Clamp(valMutated, min, max);
+ val = valMutated;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
new file mode 100644
index 0000000000..710bce24dd
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
@@ -0,0 +1,30 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ var deviation = 0;
+ var seed = entity.Comp.Seed;
+ if (seed == null)
+ return;
+ if (entity.Comp.Age > seed.Maturation)
+ deviation = (int) Math.Max(seed.Maturation - 1, entity.Comp.Age - _random.Next(7, 10));
+ else
+ deviation = (int) (seed.Maturation / seed.GrowthStages);
+ entity.Comp.Age -= deviation;
+ entity.Comp.LastProduce = entity.Comp.Age;
+ entity.Comp.SkipAging++;
+ entity.Comp.ForceUpdate = true;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
new file mode 100644
index 0000000000..1661c501be
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
@@ -0,0 +1,31 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Server.Popups;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Content.Shared.Popups;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+ return;
+
+ if (entity.Comp.Seed.Seedless)
+ return;
+
+ _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+ _popup.PopupEntity(
+ Loc.GetString("botany-plant-seedsdestroyed"),
+ entity,
+ PopupType.SmallCaution
+ );
+ entity.Comp.Seed.Seedless = true;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
new file mode 100644
index 0000000000..f6aebde465
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
@@ -0,0 +1,31 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantDiethylamineEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+ return;
+
+ if (_random.Prob(0.1f))
+ {
+ _plantHolder.EnsureUniqueSeed(entity, entity);
+ entity.Comp.Seed!.Lifespan++;
+ }
+
+ if (_random.Prob(0.1f))
+ {
+ _plantHolder.EnsureUniqueSeed(entity, entity);
+ entity.Comp.Seed!.Endurance++;
+ }
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
new file mode 100644
index 0000000000..8a073392e1
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+ return;
+
+ entity.Comp.Seed.Viable = true;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
new file mode 100644
index 0000000000..4d724be244
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
@@ -0,0 +1,26 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Server.Popups;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+ return;
+
+ if (!entity.Comp.Seed.Seedless)
+ return;
+
+ _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+ _popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), entity);
+ entity.Comp.Seed.Seedless = false;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
new file mode 100644
index 0000000000..68ea3319ef
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
@@ -0,0 +1,41 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+///
+/// This effect directly increases the potency of a PlantHolder's plant provided it exists and isn't dead.
+/// Potency directly correlates to the size of the plant's produce.
+///
+///
+public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Dead)
+ return;
+
+ if (entity.Comp.Seed.Potency < args.Effect.PotencyLimit)
+ {
+ _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+ entity.Comp.Seed.Potency = Math.Min(entity.Comp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
+
+ if (entity.Comp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
+ {
+ entity.Comp.Seed.Seedless = true;
+ }
+ }
+ else if (entity.Comp.Seed.Yield > 1 && _random.Prob(0.1f))
+ {
+ // Too much of a good thing reduces yield
+ _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+ entity.Comp.Seed.Yield--;
+ }
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs
new file mode 100644
index 0000000000..120ae6e881
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs
@@ -0,0 +1,43 @@
+using Content.Server.Botany;
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateChemicalsEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null)
+ return;
+
+ var chemicals = entity.Comp.Seed.Chemicals;
+ var randomChems = _proto.Index(args.Effect.RandomPickBotanyReagent).Fills;
+
+ // Add a random amount of a random chemical to this set of chemicals
+ var pick = _random.Pick(randomChems);
+ var chemicalId = _random.Pick(pick.Reagents);
+ var amount = _random.Next(1, (int)pick.Quantity);
+ var seedChemQuantity = new SeedChemQuantity();
+ if (chemicals.ContainsKey(chemicalId))
+ {
+ seedChemQuantity.Min = chemicals[chemicalId].Min;
+ seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
+ }
+ else
+ {
+ seedChemQuantity.Min = 1;
+ seedChemQuantity.Max = 1 + amount;
+ seedChemQuantity.Inherent = false;
+ }
+ var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
+ seedChemQuantity.PotencyDivisor = potencyDivisor;
+ chemicals[chemicalId] = seedChemQuantity;
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
new file mode 100644
index 0000000000..e2376ba186
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
@@ -0,0 +1,53 @@
+using System.Linq;
+using Content.Server.Botany.Components;
+using Content.Shared.Atmos;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null)
+ return;
+
+ var gasses = entity.Comp.Seed.ExudeGasses;
+
+ // Add a random amount of a random gas to this gas dictionary
+ float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
+ var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList());
+
+ if (!gasses.TryAdd(gas, amount))
+ {
+ gasses[gas] += amount;
+ }
+ }
+}
+
+public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null)
+ return;
+
+ var gasses = entity.Comp.Seed.ConsumeGasses;
+
+ // Add a random amount of a random gas to this gas dictionary
+ var amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
+ var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast().ToList());
+
+ if (!gasses.TryAdd(gas, amount))
+ {
+ gasses[gas] += amount;
+ }
+ }
+}
+
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
new file mode 100644
index 0000000000..95d7f97bbe
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
@@ -0,0 +1,25 @@
+using Content.Server.Botany;
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null)
+ return;
+
+ switch (entity.Comp.Seed.HarvestRepeat)
+ {
+ case HarvestType.NoRepeat:
+ entity.Comp.Seed.HarvestRepeat = HarvestType.Repeat;
+ break;
+ case HarvestType.Repeat:
+ entity.Comp.Seed.HarvestRepeat = HarvestType.SelfHarvest;
+ break;
+ }
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs
new file mode 100644
index 0000000000..c26e1e08cf
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs
@@ -0,0 +1,31 @@
+using Content.Server.Botany;
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateSpeciesChangeEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (entity.Comp.Seed == null || entity.Comp.Seed.MutationPrototypes.Count == 0)
+ return;
+
+ var targetProto = _random.Pick(entity.Comp.Seed.MutationPrototypes);
+ _proto.TryIndex(targetProto, out SeedPrototype? protoSeed);
+
+ if (protoSeed == null)
+ {
+ Log.Error($"Seed prototype could not be found: {targetProto}!");
+ return;
+ }
+
+ entity.Comp.Seed = entity.Comp.Seed.SpeciesChange(protoSeed);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs
new file mode 100644
index 0000000000..05ab857267
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs
@@ -0,0 +1,22 @@
+using Content.Server.Chat.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
+
+namespace Content.Server.EntityEffects.Effects;
+
+///
+/// Makes this entity emote.
+///
+///
+public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly ChatSystem _chat = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ if (args.Effect.ShowInChat)
+ _chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
+ else
+ _chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
new file mode 100644
index 0000000000..c623b25857
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
@@ -0,0 +1,42 @@
+using Content.Server.Ghost.Roles.Components;
+using Content.Server.Speech.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
+using Content.Shared.Mind.Components;
+
+namespace Content.Server.EntityEffects.Effects;
+
+///
+/// Makes this entity sentient. Allows ghost to take it over if it's not already occupied.
+/// Optionally also allows this entity to speak.
+///
+///
+public sealed partial class MakeSentientEntityEffectSystem : EntityEffectSystem
+{
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ // Let affected entities speak normally to make this effect different from, say, the "random sentience" event
+ // This also works on entities that already have a mind
+ // We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
+ if (args.Effect.AllowSpeech)
+ {
+ RemComp(entity);
+ // TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell.
+ RemComp(entity);
+ }
+
+ // Stops from adding a ghost role to things like people who already have a mind
+ if (TryComp(entity, out var mindContainer) && mindContainer.HasMind)
+ return;
+
+ // Don't add a ghost role to things that already have ghost roles
+ if (TryComp(entity, out GhostRoleComponent? ghostRole))
+ return;
+
+ ghostRole = AddComp(entity);
+ EnsureComp(entity);
+
+ ghostRole.RoleName = entity.Comp.EntityName;
+ ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs
new file mode 100644
index 0000000000..5f19bcc50b
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs
@@ -0,0 +1,19 @@
+using Content.Server.Polymorph.Components;
+using Content.Server.Polymorph.Systems;
+using Content.Shared.EntityEffects;
+
+namespace Content.Server.EntityEffects.Effects;
+
+///
+/// Polymorphs this entity into another entity.
+///
+///
+public sealed partial class PolymorphEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly PolymorphSystem _polymorph = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ _polymorph.PolymorphEntity(entity, args.Effect.Prototype);
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs
new file mode 100644
index 0000000000..e5ef488de8
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs
@@ -0,0 +1,51 @@
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Spreader;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Coordinates.Helpers;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Solution;
+using Content.Shared.Maps;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Map;
+
+namespace Content.Server.EntityEffects.Effects.Solution;
+
+///
+/// This effect creates smoke at this solution's position.
+/// The amount of smoke created is modified by scale.
+///
+///
+public sealed partial class AreaReactionEntityEffectsSystem : EntityEffectSystem
+{
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
+ [Dependency] private readonly SharedTransformSystem _xform = default!;
+ [Dependency] private readonly SmokeSystem _smoke = default!;
+ [Dependency] private readonly SpreaderSystem _spreader = default!;
+ [Dependency] private readonly TurfSystem _turf = default!;
+
+ // TODO: A sane way to make Smoke without a solution.
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ var xform = Transform(entity);
+ var mapCoords = _xform.GetMapCoordinates(entity);
+ var spreadAmount = (int) Math.Max(0, Math.Ceiling(args.Scale / args.Effect.OverflowThreshold));
+ var effect = args.Effect;
+
+ if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
+ !_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef))
+ return;
+
+ if (_spreader.RequiresFloorToSpread(effect.PrototypeId.ToString()) && _turf.IsSpace(tileRef))
+ return;
+
+ var coords = _map.MapToGrid(gridUid, mapCoords);
+ var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid());
+
+ _smoke.StartSmoke(ent, entity.Comp.Solution, args.Effect.Duration, spreadAmount);
+
+ _audio.PlayPvs(args.Effect.Sound, entity, AudioParams.Default.WithVariation(0.25f));
+ }
+}
diff --git a/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs
new file mode 100644
index 0000000000..55fc120051
--- /dev/null
+++ b/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs
@@ -0,0 +1,28 @@
+using Content.Server.Explosion.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Transform;
+
+namespace Content.Server.EntityEffects.Effects.Transform;
+
+///
+/// Creates an explosion at this entity's position.
+/// Intensity is modified by scale.
+///
+///
+public sealed partial class ExplosionEntityEffectSystem : EntityEffectSystem
+{
+ [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+ protected override void Effect(Entity entity, ref EntityEffectEvent args)
+ {
+ var intensity = MathF.Min(args.Effect.IntensityPerUnit * args.Scale, args.Effect.MaxTotalIntensity);
+
+ _explosion.QueueExplosion(
+ entity,
+ args.Effect.ExplosionType,
+ intensity,
+ args.Effect.IntensitySlope,
+ args.Effect.MaxIntensity,
+ args.Effect.TileBreakScale);
+ }
+}
diff --git a/Content.Server/EntityEffects/EntityEffectSystem.cs b/Content.Server/EntityEffects/EntityEffectSystem.cs
deleted file mode 100644
index 238ef4849d..0000000000
--- a/Content.Server/EntityEffects/EntityEffectSystem.cs
+++ /dev/null
@@ -1,976 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Body.Components;
-using Content.Server.Body.Systems;
-using Content.Server.Botany.Components;
-using Content.Server.Botany.Systems;
-using Content.Server.Botany;
-using Content.Server.Chat.Systems;
-using Content.Server.Emp;
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Ghost.Roles.Components;
-using Content.Server.Polymorph.Components;
-using Content.Server.Polymorph.Systems;
-using Content.Server.Speech.Components;
-using Content.Server.Spreader;
-using Content.Server.Temperature.Components;
-using Content.Server.Temperature.Systems;
-using Content.Server.Zombies;
-using Content.Shared.Atmos;
-using Content.Shared.Atmos.Components;
-using Content.Shared.Body.Components;
-using Content.Shared.Coordinates.Helpers;
-using Content.Shared.EntityEffects.EffectConditions;
-using Content.Shared.EntityEffects.Effects.PlantMetabolism;
-using Content.Shared.EntityEffects.Effects;
-using Content.Shared.EntityEffects;
-using Content.Shared.Flash;
-using Content.Shared.Maps;
-using Content.Shared.Medical;
-using Content.Shared.Mind.Components;
-using Content.Shared.Popups;
-using Content.Shared.Random;
-using Content.Shared.Traits.Assorted;
-using Content.Shared.Zombies;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-using TemperatureCondition = Content.Shared.EntityEffects.EffectConditions.Temperature; // disambiguate the namespace
-using PolymorphEffect = Content.Shared.EntityEffects.Effects.Polymorph;
-
-namespace Content.Server.EntityEffects;
-
-public sealed class EntityEffectSystem : EntitySystem
-{
- private static readonly ProtoId RandomPickBotanyReagent = "RandomPickBotanyReagent";
-
- [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
- [Dependency] private readonly BloodstreamSystem _bloodstream = default!;
- [Dependency] private readonly ChatSystem _chat = default!;
- [Dependency] private readonly EmpSystem _emp = default!;
- [Dependency] private readonly ExplosionSystem _explosion = default!;
- [Dependency] private readonly FlammableSystem _flammable = default!;
- [Dependency] private readonly SharedFlashSystem _flash = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly IPrototypeManager _protoManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly SharedMapSystem _map = default!;
- [Dependency] private readonly MutationSystem _mutation = default!;
- [Dependency] private readonly NarcolepsySystem _narcolepsy = default!;
- [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
- [Dependency] private readonly PolymorphSystem _polymorph = default!;
- [Dependency] private readonly RespiratorSystem _respirator = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedPointLightSystem _pointLight = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SmokeSystem _smoke = default!;
- [Dependency] private readonly SpreaderSystem _spreader = default!;
- [Dependency] private readonly TemperatureSystem _temperature = default!;
- [Dependency] private readonly SharedTransformSystem _xform = default!;
- [Dependency] private readonly VomitSystem _vomit = default!;
- [Dependency] private readonly TurfSystem _turf = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent>(OnCheckTemperature);
- SubscribeLocalEvent>(OnCheckBreathing);
- SubscribeLocalEvent>(OnCheckOrganType);
- SubscribeLocalEvent>(OnExecutePlantAdjustHealth);
- SubscribeLocalEvent>(OnExecutePlantAdjustMutationLevel);
- SubscribeLocalEvent>(OnExecutePlantAdjustMutationMod);
- SubscribeLocalEvent>(OnExecutePlantAdjustNutrition);
- SubscribeLocalEvent>(OnExecutePlantAdjustPests);
- SubscribeLocalEvent>(OnExecutePlantAdjustPotency);
- SubscribeLocalEvent>(OnExecutePlantAdjustToxins);
- SubscribeLocalEvent>(OnExecutePlantAdjustWater);
- SubscribeLocalEvent>(OnExecutePlantAdjustWeeds);
- SubscribeLocalEvent>(OnExecutePlantAffectGrowth);
- SubscribeLocalEvent>(OnExecutePlantChangeStat);
- SubscribeLocalEvent>(OnExecutePlantCryoxadone);
- SubscribeLocalEvent>(OnExecutePlantDestroySeeds);
- SubscribeLocalEvent>(OnExecutePlantDiethylamine);
- SubscribeLocalEvent>(OnExecutePlantPhalanximine);
- SubscribeLocalEvent>(OnExecutePlantRestoreSeeds);
- SubscribeLocalEvent>(OnExecuteRobustHarvest);
- SubscribeLocalEvent>(OnExecuteAdjustTemperature);
- SubscribeLocalEvent>(OnExecuteAreaReactionEffect);
- SubscribeLocalEvent>(OnExecuteCauseZombieInfection);
- SubscribeLocalEvent>(OnExecuteChemCleanBloodstream);
- SubscribeLocalEvent>(OnExecuteChemVomit);
- SubscribeLocalEvent>(OnExecuteCreateEntityReactionEffect);
- SubscribeLocalEvent>(OnExecuteCreateGas);
- SubscribeLocalEvent>(OnExecuteCureZombieInfection);
- SubscribeLocalEvent>(OnExecuteEmote);
- SubscribeLocalEvent>(OnExecuteEmpReactionEffect);
- SubscribeLocalEvent>(OnExecuteExplosionReactionEffect);
- SubscribeLocalEvent>(OnExecuteFlammableReaction);
- SubscribeLocalEvent>(OnExecuteFlashReactionEffect);
- SubscribeLocalEvent>(OnExecuteIgnite);
- SubscribeLocalEvent>(OnExecuteMakeSentient);
- SubscribeLocalEvent>(OnExecuteModifyBleedAmount);
- SubscribeLocalEvent>(OnExecuteModifyBloodLevel);
- SubscribeLocalEvent>(OnExecuteModifyLungGas);
- SubscribeLocalEvent>(OnExecuteOxygenate);
- SubscribeLocalEvent>(OnExecutePlantMutateChemicals);
- SubscribeLocalEvent>(OnExecutePlantMutateConsumeGasses);
- SubscribeLocalEvent>(OnExecutePlantMutateExudeGasses);
- SubscribeLocalEvent>(OnExecutePlantMutateHarvest);
- SubscribeLocalEvent>(OnExecutePlantSpeciesChange);
- SubscribeLocalEvent>(OnExecutePolymorph);
- SubscribeLocalEvent>(OnExecuteResetNarcolepsy);
- }
-
- private void OnCheckTemperature(ref CheckEntityEffectConditionEvent args)
- {
- args.Result = false;
- if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
- {
- if (temp.CurrentTemperature >= args.Condition.Min && temp.CurrentTemperature <= args.Condition.Max)
- args.Result = true;
- }
- }
-
- private void OnCheckBreathing(ref CheckEntityEffectConditionEvent args)
- {
- if (!TryComp(args.Args.TargetEntity, out RespiratorComponent? respiratorComp))
- {
- args.Result = !args.Condition.IsBreathing;
- return;
- }
-
- var breathingState = _respirator.IsBreathing((args.Args.TargetEntity, respiratorComp));
- args.Result = args.Condition.IsBreathing == breathingState;
- }
-
- private void OnCheckOrganType(ref CheckEntityEffectConditionEvent args)
- {
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- if (reagentArgs.OrganEntity == null)
- {
- args.Result = false;
- return;
- }
-
- args.Result = OrganCondition(args.Condition, reagentArgs.OrganEntity.Value);
- return;
- }
-
- // TODO: Someone needs to figure out how to do this for non-reagent effects.
- throw new NotImplementedException();
- }
-
- public bool OrganCondition(OrganType condition, Entity metabolizer)
- {
- metabolizer.Comp ??= EntityManager.GetComponentOrNull(metabolizer.Owner);
- if (metabolizer.Comp != null
- && metabolizer.Comp.MetabolizerTypes != null
- && metabolizer.Comp.MetabolizerTypes.Contains(condition.Type))
- return condition.ShouldHave;
- return !condition.ShouldHave;
- }
-
- ///
- /// Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default.
- ///
- /// The entity holding the plant
- /// The plant holder component
- /// The entity manager
- /// Whether to check if it has an alive plant or not
- ///
- private bool CanMetabolizePlant(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent,
- bool mustHaveAlivePlant = true, bool mustHaveMutableSeed = false)
- {
- plantHolderComponent = null;
-
- if (!TryComp(plantHolder, out plantHolderComponent))
- return false;
-
- if (mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead))
- return false;
-
- if (mustHaveMutableSeed && (plantHolderComponent.Seed == null || plantHolderComponent.Seed.Immutable))
- return false;
-
- return true;
- }
-
- private void OnExecutePlantAdjustHealth(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.Health += args.Effect.Amount;
- _plantHolder.CheckHealth(args.Args.TargetEntity, plantHolderComp);
- }
-
- private void OnExecutePlantAdjustMutationLevel(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.MutationLevel += args.Effect.Amount * plantHolderComp.MutationMod;
- }
-
- private void OnExecutePlantAdjustMutationMod(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.MutationMod += args.Effect.Amount;
- }
-
- private void OnExecutePlantAdjustNutrition(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
- return;
-
- _plantHolder.AdjustNutrient(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
- }
-
- private void OnExecutePlantAdjustPests(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.PestLevel += args.Effect.Amount;
- }
-
- private void OnExecutePlantAdjustPotency(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- if (plantHolderComp.Seed == null)
- return;
-
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + args.Effect.Amount, 1);
- }
-
- private void OnExecutePlantAdjustToxins(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.Toxins += args.Effect.Amount;
- }
-
- private void OnExecutePlantAdjustWater(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
- return;
-
- _plantHolder.AdjustWater(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
- }
-
- private void OnExecutePlantAdjustWeeds(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- plantHolderComp.WeedLevel += args.Effect.Amount;
- }
-
- private void OnExecutePlantAffectGrowth(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- _plantHolder.AffectGrowth(args.Args.TargetEntity, (int) args.Effect.Amount, plantHolderComp);
- }
-
- // Mutate reference 'val' between 'min' and 'max' by pretending the value
- // is representable by a thermometer code with 'bits' number of bits and
- // randomly flipping some of them.
- private void MutateFloat(ref float val, float min, float max, int bits)
- {
- if (min == max)
- {
- val = min;
- return;
- }
-
- // Starting number of bits that are high, between 0 and bits.
- // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
- int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
- // val may be outside the range of min/max due to starting prototype values, so clamp.
- valInt = Math.Clamp(valInt, 0, bits);
-
- // Probability that the bit flip increases n.
- // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
- // In other words, it tends to go to the middle.
- float probIncrease = 1 - (float)valInt / bits;
- int valIntMutated;
- if (_random.Prob(probIncrease))
- {
- valIntMutated = valInt + 1;
- }
- else
- {
- valIntMutated = valInt - 1;
- }
-
- // Set value based on mutated thermometer code.
- float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
- val = valMutated;
- }
-
- private void MutateInt(ref int val, int min, int max, int bits)
- {
- if (min == max)
- {
- val = min;
- return;
- }
-
- // Starting number of bits that are high, between 0 and bits.
- // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
- int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
- // val may be outside the range of min/max due to starting prototype values, so clamp.
- valInt = Math.Clamp(valInt, 0, bits);
-
- // Probability that the bit flip increases n.
- // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
- // In other words, it tends to go to the middle.
- float probIncrease = 1 - (float)valInt / bits;
- int valMutated;
- if (_random.Prob(probIncrease))
- {
- valMutated = val + 1;
- }
- else
- {
- valMutated = val - 1;
- }
-
- valMutated = Math.Clamp(valMutated, min, max);
- val = valMutated;
- }
-
- private void OnExecutePlantChangeStat(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- if (plantHolderComp.Seed == null)
- return;
-
- var member = plantHolderComp.Seed.GetType().GetField(args.Effect.TargetValue);
-
- if (member == null)
- {
- _mutation.Log.Error(args.Effect.GetType().Name + " Error: Member " + args.Effect.TargetValue + " not found on " + plantHolderComp.Seed.GetType().Name + ". Did you misspell it?");
- return;
- }
-
- var currentValObj = member.GetValue(plantHolderComp.Seed);
- if (currentValObj == null)
- return;
-
- if (member.FieldType == typeof(float))
- {
- var floatVal = (float)currentValObj;
- MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
- member.SetValue(plantHolderComp.Seed, floatVal);
- }
- else if (member.FieldType == typeof(int))
- {
- var intVal = (int)currentValObj;
- MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
- member.SetValue(plantHolderComp.Seed, intVal);
- }
- else if (member.FieldType == typeof(bool))
- {
- var boolVal = (bool)currentValObj;
- boolVal = !boolVal;
- member.SetValue(plantHolderComp.Seed, boolVal);
- }
- }
-
- private void OnExecutePlantCryoxadone(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- var deviation = 0;
- var seed = plantHolderComp.Seed;
- if (seed == null)
- return;
- if (plantHolderComp.Age > seed.Maturation)
- deviation = (int) Math.Max(seed.Maturation - 1, plantHolderComp.Age - _random.Next(7, 10));
- else
- deviation = (int) (seed.Maturation / seed.GrowthStages);
- plantHolderComp.Age -= deviation;
- plantHolderComp.LastProduce = plantHolderComp.Age;
- plantHolderComp.SkipAging++;
- plantHolderComp.ForceUpdate = true;
- }
-
- private void OnExecutePlantDestroySeeds(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
- return;
-
- if (plantHolderComp.Seed!.Seedless == false)
- {
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- _popup.PopupEntity(
- Loc.GetString("botany-plant-seedsdestroyed"),
- args.Args.TargetEntity,
- PopupType.SmallCaution
- );
- plantHolderComp.Seed.Seedless = true;
- }
- }
-
- private void OnExecutePlantDiethylamine(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
- return;
-
- if (_random.Prob(0.1f))
- {
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- plantHolderComp.Seed!.Lifespan++;
- }
-
- if (_random.Prob(0.1f))
- {
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- plantHolderComp.Seed!.Endurance++;
- }
- }
-
- private void OnExecutePlantPhalanximine(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
- return;
-
- plantHolderComp.Seed!.Viable = true;
- }
-
- private void OnExecutePlantRestoreSeeds(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
- return;
-
- if (plantHolderComp.Seed!.Seedless)
- {
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- _popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.Args.TargetEntity);
- plantHolderComp.Seed.Seedless = false;
- }
- }
-
- private void OnExecuteRobustHarvest(ref ExecuteEntityEffectEvent args)
- {
- if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
- return;
-
- if (plantHolderComp.Seed == null)
- return;
-
- if (plantHolderComp.Seed.Potency < args.Effect.PotencyLimit)
- {
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
-
- if (plantHolderComp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
- {
- plantHolderComp.Seed.Seedless = true;
- }
- }
- else if (plantHolderComp.Seed.Yield > 1 && _random.Prob(0.1f))
- {
- // Too much of a good thing reduces yield
- _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
- plantHolderComp.Seed.Yield--;
- }
- }
-
- private void OnExecuteAdjustTemperature(ref ExecuteEntityEffectEvent args)
- {
- if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
- {
- var amount = args.Effect.Amount;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- amount *= reagentArgs.Scale.Float();
- }
-
- _temperature.ChangeHeat(args.Args.TargetEntity, amount, true, temp);
- }
- }
-
- private void OnExecuteAreaReactionEffect(ref ExecuteEntityEffectEvent args)
- {
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- if (reagentArgs.Source == null)
- return;
-
- var spreadAmount = (int) Math.Max(0, Math.Ceiling((reagentArgs.Quantity / args.Effect.OverflowThreshold).Float()));
- var splitSolution = reagentArgs.Source.SplitSolution(reagentArgs.Source.Volume);
- var transform = Comp(reagentArgs.TargetEntity);
- var mapCoords = _xform.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform);
-
- if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
- !_map.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef))
- {
- return;
- }
-
- if (_spreader.RequiresFloorToSpread(args.Effect.PrototypeId) && _turf.IsSpace(tileRef))
- return;
-
- var coords = _map.MapToGrid(gridUid, mapCoords);
- var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid());
-
- _smoke.StartSmoke(ent, splitSolution, args.Effect.Duration, spreadAmount);
-
- _audio.PlayPvs(args.Effect.Sound, reagentArgs.TargetEntity, AudioParams.Default.WithVariation(0.25f));
- return;
- }
-
- // TODO: Someone needs to figure out how to do this for non-reagent effects.
- throw new NotImplementedException();
- }
-
- private void OnExecuteCauseZombieInfection(ref ExecuteEntityEffectEvent args)
- {
- EnsureComp(args.Args.TargetEntity);
- EnsureComp(args.Args.TargetEntity);
- }
-
- private void OnExecuteChemCleanBloodstream(ref ExecuteEntityEffectEvent args)
- {
- var cleanseRate = args.Effect.CleanseRate;
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- if (reagentArgs.Source == null || reagentArgs.Reagent == null)
- return;
-
- cleanseRate *= reagentArgs.Scale.Float();
- _bloodstream.FlushChemicals(args.Args.TargetEntity, reagentArgs.Reagent, cleanseRate);
- }
- else
- {
- _bloodstream.FlushChemicals(args.Args.TargetEntity, null, cleanseRate);
- }
- }
-
- private void OnExecuteChemVomit(ref ExecuteEntityEffectEvent args)
- {
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- if (reagentArgs.Scale != 1f)
- return;
-
- _vomit.Vomit(args.Args.TargetEntity, args.Effect.ThirstAmount, args.Effect.HungerAmount);
- }
-
- private void OnExecuteCreateEntityReactionEffect(ref ExecuteEntityEffectEvent args)
- {
- var transform = Comp(args.Args.TargetEntity);
- var quantity = (int)args.Effect.Number;
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- quantity *= reagentArgs.Quantity.Int();
-
- for (var i = 0; i < quantity; i++)
- {
- var uid = Spawn(args.Effect.Entity, _xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform));
- _xform.AttachToGridOrMap(uid);
-
- // TODO figure out how to properly spawn inside of containers
- // e.g. cheese:
- // if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user.
- // if reaction happens in a backpack, should insert cheese into backpack.
- // --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players).
- // if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that.
- // presumably having cheese materialize inside of your blood would have "disadvantages".
- }
- }
-
- private void OnExecuteCreateGas(ref ExecuteEntityEffectEvent args)
- {
- var tileMix = _atmosphere.GetContainingMixture(args.Args.TargetEntity, false, true);
-
- if (tileMix != null)
- {
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- tileMix.AdjustMoles(args.Effect.Gas, reagentArgs.Quantity.Float() * args.Effect.Multiplier);
- }
- else
- {
- tileMix.AdjustMoles(args.Effect.Gas, args.Effect.Multiplier);
- }
- }
- }
-
- private void OnExecuteCureZombieInfection(ref ExecuteEntityEffectEvent args)
- {
- if (HasComp(args.Args.TargetEntity))
- return;
-
- RemComp(args.Args.TargetEntity);
- RemComp(args.Args.TargetEntity);
-
- if (args.Effect.Innoculate)
- {
- EnsureComp(args.Args.TargetEntity);
- }
- }
-
- private void OnExecuteEmote(ref ExecuteEntityEffectEvent args)
- {
- if (args.Effect.EmoteId == null)
- return;
-
- if (args.Effect.ShowInChat)
- _chat.TryEmoteWithChat(args.Args.TargetEntity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
- else
- _chat.TryEmoteWithoutChat(args.Args.TargetEntity, args.Effect.EmoteId);
- }
-
- private void OnExecuteEmpReactionEffect(ref ExecuteEntityEffectEvent args)
- {
- var transform = Comp(args.Args.TargetEntity);
-
- var range = args.Effect.EmpRangePerUnit;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- range = MathF.Min((float) (reagentArgs.Quantity * args.Effect.EmpRangePerUnit), args.Effect.EmpMaxRange);
- }
-
- _emp.EmpPulse(_xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform),
- range,
- args.Effect.EnergyConsumption,
- args.Effect.DisableDuration);
- }
-
- private void OnExecuteExplosionReactionEffect(ref ExecuteEntityEffectEvent args)
- {
- var intensity = args.Effect.IntensityPerUnit;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- intensity = MathF.Min((float) reagentArgs.Quantity * args.Effect.IntensityPerUnit, args.Effect.MaxTotalIntensity);
- }
-
- _explosion.QueueExplosion(
- args.Args.TargetEntity,
- args.Effect.ExplosionType,
- intensity,
- args.Effect.IntensitySlope,
- args.Effect.MaxIntensity,
- args.Effect.TileBreakScale);
- }
-
- private void OnExecuteFlammableReaction(ref ExecuteEntityEffectEvent args)
- {
- if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
- return;
-
- // Sets the multiplier for FireStacks to MultiplierOnExisting is 0 or greater and target already has FireStacks
- var multiplier = flammable.FireStacks != 0f && args.Effect.MultiplierOnExisting >= 0 ? args.Effect.MultiplierOnExisting : args.Effect.Multiplier;
- var quantity = 1f;
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- quantity = reagentArgs.Quantity.Float();
- _flammable.AdjustFireStacks(args.Args.TargetEntity, quantity * multiplier, flammable);
- if (reagentArgs.Reagent != null)
- reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
- }
- else
- {
- _flammable.AdjustFireStacks(args.Args.TargetEntity, multiplier, flammable);
- }
- }
-
- private void OnExecuteFlashReactionEffect(ref ExecuteEntityEffectEvent args)
- {
- var transform = Comp(args.Args.TargetEntity);
-
- var range = 1f;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- range = MathF.Min((float)(reagentArgs.Quantity * args.Effect.RangePerUnit), args.Effect.MaxRange);
-
- _flash.FlashArea(
- args.Args.TargetEntity,
- null,
- range,
- args.Effect.Duration,
- slowTo: args.Effect.SlowTo,
- sound: args.Effect.Sound);
-
- if (args.Effect.FlashEffectPrototype == null)
- return;
-
- var uid = EntityManager.SpawnEntity(args.Effect.FlashEffectPrototype, _xform.GetMapCoordinates(transform));
- _xform.AttachToGridOrMap(uid);
-
- if (!TryComp(uid, out var pointLightComp))
- return;
-
- _pointLight.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp);
- }
-
- private void OnExecuteIgnite(ref ExecuteEntityEffectEvent args)
- {
- if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
- return;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- _flammable.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable);
- }
- else
- {
- _flammable.Ignite(args.Args.TargetEntity, args.Args.TargetEntity, flammable: flammable);
- }
- }
-
- private void OnExecuteMakeSentient(ref ExecuteEntityEffectEvent args)
- {
- var uid = args.Args.TargetEntity;
-
- // Let affected entities speak normally to make this effect different from, say, the "random sentience" event
- // This also works on entities that already have a mind
- // We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
- RemComp(uid);
- RemComp(uid);
-
- // Stops from adding a ghost role to things like people who already have a mind
- if (TryComp(uid, out var mindContainer) && mindContainer.HasMind)
- {
- return;
- }
-
- // Don't add a ghost role to things that already have ghost roles
- if (TryComp(uid, out GhostRoleComponent? ghostRole))
- {
- return;
- }
-
- ghostRole = AddComp(uid);
- EnsureComp(uid);
-
- var entityData = Comp(uid);
- ghostRole.RoleName = entityData.EntityName;
- ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
- }
-
- private void OnExecuteModifyBleedAmount(ref ExecuteEntityEffectEvent args)
- {
- if (TryComp(args.Args.TargetEntity, out var blood))
- {
- var amt = args.Effect.Amount;
- if (args.Args is EntityEffectReagentArgs reagentArgs) {
- if (args.Effect.Scaled)
- amt *= reagentArgs.Quantity.Float();
- amt *= reagentArgs.Scale.Float();
- }
-
- _bloodstream.TryModifyBleedAmount((args.Args.TargetEntity, blood), amt);
- }
- }
-
- private void OnExecuteModifyBloodLevel(ref ExecuteEntityEffectEvent args)
- {
- if (TryComp(args.Args.TargetEntity, out var blood))
- {
- var amt = args.Effect.Amount;
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- if (args.Effect.Scaled)
- amt *= reagentArgs.Quantity;
- amt *= reagentArgs.Scale;
- }
-
- _bloodstream.TryModifyBloodLevel((args.Args.TargetEntity, blood), amt);
- }
- }
-
- private void OnExecuteModifyLungGas(ref ExecuteEntityEffectEvent args)
- {
- LungComponent? lung;
- float amount = 1f;
-
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- if (!TryComp(reagentArgs.OrganEntity, out var organLung))
- return;
- lung = organLung;
- amount = reagentArgs.Quantity.Float();
- }
- else
- {
- if (!TryComp(args.Args.TargetEntity, out var organLung)) //Likely needs to be modified to ensure it works correctly
- return;
- lung = organLung;
- }
-
- if (lung != null)
- {
- foreach (var (gas, ratio) in args.Effect.Ratios)
- {
- var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier;
- if (quantity < 0)
- quantity = Math.Max(quantity, -lung.Air[(int) gas]);
- lung.Air.AdjustMoles(gas, quantity);
- }
- }
- }
-
- private void OnExecuteOxygenate(ref ExecuteEntityEffectEvent args)
- {
- var multiplier = 1f;
- if (args.Args is EntityEffectReagentArgs reagentArgs)
- {
- multiplier = reagentArgs.Quantity.Float();
- }
-
- if (TryComp