]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Entity effects ECS refactor (#40580)
authorPrincess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Sun, 12 Oct 2025 21:23:42 +0000 (14:23 -0700)
committerGitHub <noreply@github.com>
Sun, 12 Oct 2025 21:23:42 +0000 (21:23 +0000)
* 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>
289 files changed:
.github/CODEOWNERS
Content.Client/Temperature/Systems/TemperatureSystem.cs [new file with mode: 0644]
Content.Server/Atmos/EntitySystems/FlammableSystem.cs
Content.Server/Atmos/Rotting/RottingSystem.cs
Content.Server/Body/Components/MetabolizerComponent.cs
Content.Server/Body/Systems/MetabolizerSystem.cs
Content.Server/Body/Systems/RespiratorSystem.cs
Content.Server/Body/Systems/ThermalRegulatorSystem.cs
Content.Server/Botany/SeedPrototype.cs
Content.Server/Botany/Systems/BotanySystem.Produce.cs
Content.Server/Botany/Systems/MutationSystem.cs
Content.Server/Botany/Systems/PlantHolderSystem.cs
Content.Server/Chemistry/Commands/DumpReagentGuideText.cs
Content.Server/Construction/ConstructionSystem.Interactions.cs
Content.Server/EntityConditions/Conditions/BreathingEntityConditionSystem.cs [new file with mode: 0644]
Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs [new file with mode: 0644]
Content.Server/EntityEffects/EntityEffectSystem.cs [deleted file]
Content.Server/Fluids/EntitySystems/SmokeSystem.cs
Content.Server/GuideGenerator/ChemistryJsonGenerator.cs
Content.Server/GuideGenerator/ReagentEntry.cs
Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs
Content.Server/Medical/CryoPodSystem.cs
Content.Server/Medical/HealthAnalyzerSystem.cs
Content.Server/NPC/Systems/NPCUtilitySystem.cs
Content.Server/Temperature/Systems/TemperatureSystem.cs
Content.Server/Tiles/TileEntityEffectComponent.cs
Content.Server/Tiles/TileEntityEffectSystem.cs
Content.Server/Zombies/ZombieSystem.Transform.cs
Content.Shared.Database/LogType.cs
Content.Shared/Body/Components/LungComponent.cs
Content.Shared/Body/Systems/LungSystem.cs
Content.Shared/Body/Systems/SharedBloodstreamSystem.cs
Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs
Content.Shared/Chemistry/Reaction/ChemicalReactionSystem.cs
Content.Shared/Chemistry/Reaction/ReactionPrototype.cs
Content.Shared/Chemistry/Reaction/ReactiveComponent.cs
Content.Shared/Chemistry/ReactiveSystem.cs
Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs [new file with mode: 0644]
Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/JobCondition.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/OrganType.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs [deleted file]
Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs [deleted file]
Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs [deleted file]
Content.Shared/EntityEffects/Effects/AdjustAlert.cs [deleted file]
Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/AdjustReagent.cs [deleted file]
Content.Shared/EntityEffects/Effects/AdjustTemperature.cs [deleted file]
Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs [deleted file]
Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs [deleted file]
Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs [deleted file]
Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPests.cs [moved from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs with 53% similarity]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxins.cs [moved from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs with 53% similarity]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeeds.cs [moved from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs with 53% similarity]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs [deleted file]
Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs [deleted file]
Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs [deleted file]
Content.Shared/EntityEffects/Effects/ChemVomit.cs [deleted file]
Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs [deleted file]
Content.Shared/EntityEffects/Effects/CreateGas.cs [deleted file]
Content.Shared/EntityEffects/Effects/CureZombieInfection.cs [deleted file]
Content.Shared/EntityEffects/Effects/Drunk.cs [deleted file]
Content.Shared/EntityEffects/Effects/Electrocute.cs [deleted file]
Content.Shared/EntityEffects/Effects/Emote.cs [deleted file]
Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs [deleted file]
Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/EvenHealthChange.cs [deleted file]
Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs [deleted file]
Content.Shared/EntityEffects/Effects/FlammableReaction.cs [deleted file]
Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs [deleted file]
Content.Shared/EntityEffects/Effects/Glow.cs [deleted file]
Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/HealthChange.cs [deleted file]
Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Ignite.cs [deleted file]
Content.Shared/EntityEffects/Effects/MakeSentient.cs [deleted file]
Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs [deleted file]
Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs [deleted file]
Content.Shared/EntityEffects/Effects/ModifyLungGas.cs [deleted file]
Content.Shared/EntityEffects/Effects/MovespeedModifier.cs [deleted file]
Content.Shared/EntityEffects/Effects/Oxygenate.cs [deleted file]
Content.Shared/EntityEffects/Effects/Paralyze.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMutateGases.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs [deleted file]
Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs [deleted file]
Content.Shared/EntityEffects/Effects/Polymorph.cs [deleted file]
Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/PopupMessage.cs [deleted file]
Content.Shared/EntityEffects/Effects/ReduceRotting.cs [deleted file]
Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs [deleted file]
Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/SatiateHunger.cs [deleted file]
Content.Shared/EntityEffects/Effects/SatiateThirst.cs [deleted file]
Content.Shared/EntityEffects/Effects/Slipify.cs [deleted file]
Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs [deleted file]
Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs [deleted file]
Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs [deleted file]
Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs [deleted file]
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs [deleted file]
Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Transform/EmpEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs [moved from Content.Shared/EntityEffects/Effects/ExplosionReactionEffect.cs with 69% similarity]
Content.Shared/EntityEffects/Effects/Transform/FlashEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/Effects/WashCreamPieReaction.cs [deleted file]
Content.Shared/EntityEffects/Effects/WearableReaction.cs [deleted file]
Content.Shared/EntityEffects/Effects/ZombieEntityEffectsSystem.cs [new file with mode: 0644]
Content.Shared/EntityEffects/EntityEffect.cs [deleted file]
Content.Shared/EntityEffects/EntityEffectCondition.cs [deleted file]
Content.Shared/EntityEffects/EventEntityEffect.cs [deleted file]
Content.Shared/EntityEffects/EventEntityEffectCondition.cs [deleted file]
Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs [new file with mode: 0644]
Content.Shared/Movement/Systems/MovementModStatusSystem.cs
Content.Shared/Nutrition/EntitySystems/IngestionSystem.API.cs
Content.Shared/StatusEffectNew/StatusEffectSystem.API.cs
Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs
Content.Shared/Stunnable/SharedStunSystem.cs
Content.Shared/Temperature/Components/TemperatureComponent.cs [moved from Content.Server/Temperature/Components/TemperatureComponent.cs with 98% similarity]
Content.Shared/Temperature/Systems/SharedTemperatureSystem.cs
Content.Shared/Zombies/IncurableZombieComponent.cs [moved from Content.Server/Zombies/IncurableZombieComponent.cs with 77% similarity]
Resources/Locale/en-US/guidebook/chemistry/core.ftl
Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl [deleted file]
Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl [moved from Resources/Locale/en-US/guidebook/chemistry/conditions.ftl with 100% similarity]
Resources/Locale/en-US/guidebook/entity-effects/effects.ftl [moved from Resources/Locale/en-US/guidebook/chemistry/effects.ftl with 71% similarity]
Resources/Locale/en-US/guidebook/entity-effects/healthchange.ftl [moved from Resources/Locale/en-US/guidebook/chemistry/healthchange.ftl with 100% similarity]
Resources/Locale/en-US/guidebook/entity-effects/statuseffects.ftl [new file with mode: 0644]
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml
Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml
Resources/Prototypes/Entities/Mobs/NPCs/space.yml
Resources/Prototypes/Entities/Mobs/Species/arachnid.yml
Resources/Prototypes/Entities/Mobs/Species/base.yml
Resources/Prototypes/Entities/Mobs/Species/diona.yml
Resources/Prototypes/Entities/Mobs/Species/skeleton.yml
Resources/Prototypes/Entities/Mobs/Species/slime.yml
Resources/Prototypes/Entities/Objects/Fun/plushies.yml
Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
Resources/Prototypes/Entities/Objects/Specific/rehydrateable.yml
Resources/Prototypes/Entities/StatusEffects/movement.yml
Resources/Prototypes/Entities/Structures/soil.yml
Resources/Prototypes/Entities/Tiles/lava.yml
Resources/Prototypes/Entities/Tiles/liquid_plasma.yml
Resources/Prototypes/Entities/Tiles/water.yml
Resources/Prototypes/Hydroponics/randomMutations.yml
Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml
Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml
Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
Resources/Prototypes/Reagents/Consumable/Drink/soda.yml
Resources/Prototypes/Reagents/Consumable/Food/food.yml
Resources/Prototypes/Reagents/Consumable/Food/ingredients.yml
Resources/Prototypes/Reagents/biological.yml
Resources/Prototypes/Reagents/botany.yml
Resources/Prototypes/Reagents/chemicals.yml
Resources/Prototypes/Reagents/cleaning.yml
Resources/Prototypes/Reagents/elements.yml
Resources/Prototypes/Reagents/fun.yml
Resources/Prototypes/Reagents/gases.yml
Resources/Prototypes/Reagents/medicine.yml
Resources/Prototypes/Reagents/narcotics.yml
Resources/Prototypes/Reagents/pyrotechnic.yml
Resources/Prototypes/Reagents/toxins.yml
Resources/Prototypes/Recipes/Reactions/chemicals.yml
Resources/Prototypes/Recipes/Reactions/food.yml
Resources/Prototypes/Recipes/Reactions/fun.yml
Resources/Prototypes/Recipes/Reactions/pyrotechnic.yml
Resources/Prototypes/Recipes/Reactions/soap.yml

index 14f591ec87db2aa50d2ea7731b9c39e363de88d8..defe08ef2313b18c3499165cb6c976dfc87ac9b2 100644 (file)
@@ -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 (file)
index 0000000..94a1e83
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.Temperature.Systems;
+
+namespace Content.Client.Temperature.Systems;
+
+/// <summary>
+/// This exists so <see cref="SharedTemperatureSystem"/> runs on client/>
+/// </summary>
+public sealed class TemperatureSystem : SharedTemperatureSystem;
index 424f52ed581a9c76e49d1ab0dc74332098a58fbe..81bd4e5c6c880b318d4c5cde7ea20ccc2ec717af 100644 (file)
@@ -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;
index 6f14debc3d00e9e499dabc44592c2f750d180c85..57c1504b168149bd74e996f2a1a5476f7123aeec 100644 (file)
@@ -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;
index 5821b0d9447d7cb01cbf6609b773514f21012521..46d2fdd8e80ddb1c7777da324a1b966ac75df16e 100644 (file)
@@ -10,13 +10,13 @@ namespace Content.Server.Body.Components
     /// <summary>
     ///     Handles metabolizing various reagents with given effects.
     /// </summary>
-    [RegisterComponent, Access(typeof(MetabolizerSystem))]
+    [RegisterComponent, AutoGenerateComponentPause, Access(typeof(MetabolizerSystem))]
     public sealed partial class MetabolizerComponent : Component
     {
         /// <summary>
         ///     The next time that reagents will be metabolized.
         /// </summary>
-        [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+        [DataField, AutoPausedField]
         public TimeSpan NextUpdate;
 
         /// <summary>
index c59f87f576d21d644c3817445d9a4f0d98a68dc7..6679bfea54daac0c0a22e4e29f9fd5cb17cbd2d0 100644 (file)
@@ -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;
+
+/// <inheritdoc/>
+public sealed class MetabolizerSystem : SharedMetabolizerSystem
 {
-    /// <inheritdoc/>
-    public sealed class MetabolizerSystem : SharedMetabolizerSystem
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+    [Dependency] private readonly SharedEntityConditionsSystem _entityConditions = default!;
+    [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
+
+    private EntityQuery<OrganComponent> _organQuery;
+    private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
+    private static readonly ProtoId<MetabolismGroupPrototype> Gas = "Gas";
+
+    public override void Initialize()
     {
-        [Dependency] private readonly IGameTiming _gameTiming = default!;
-        [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-        [Dependency] private readonly IRobustRandom _random = default!;
-        [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
-        [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
-        [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
-
-        private EntityQuery<OrganComponent> _organQuery;
-        private EntityQuery<SolutionContainerManagerComponent> _solutionQuery;
+        base.Initialize();
 
-        public override void Initialize()
-        {
-            base.Initialize();
+        _organQuery = GetEntityQuery<OrganComponent>();
+        _solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
 
-            _organQuery = GetEntityQuery<OrganComponent>();
-            _solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
+        SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
+        SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
+    }
 
-            SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
-            SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
-            SubscribeLocalEvent<MetabolizerComponent, EntityUnpausedEvent>(OnUnpaused);
-            SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
-        }
+    private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
+    {
+        ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
+    }
 
-        private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
+    private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
+    {
+        if (!entity.Comp.SolutionOnBody)
         {
-            ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
+            _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
         }
-
-        private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
+        else if (_organQuery.CompOrNull(entity)?.Body is { } body)
         {
-            ent.Comp.NextUpdate += args.PausedTime;
+            _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
         }
+    }
 
-        private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
-        {
-            if (!entity.Comp.SolutionOnBody)
-            {
-                _solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out _);
-            }
-            else if (_organQuery.CompOrNull(entity)?.Body is { } body)
-            {
-                _solutionContainerSystem.EnsureSolution(body, entity.Comp.SolutionName, out _);
-            }
-        }
+    private void OnApplyMetabolicMultiplier(Entity<MetabolizerComponent> ent, ref ApplyMetabolicMultiplierEvent args)
+    {
+        ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
 
-        private void OnApplyMetabolicMultiplier(Entity<MetabolizerComponent> ent, ref ApplyMetabolicMultiplierEvent args)
+        var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count<MetabolizerComponent>());
+        var query = EntityQueryEnumerator<MetabolizerComponent>();
+
+        while (query.MoveNext(out var uid, out var comp))
         {
-            ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
+            metabolizers.Add((uid, comp));
         }
 
-        public override void Update(float frameTime)
+        foreach (var (uid, metab) in metabolizers)
         {
-            base.Update(frameTime);
+            // Only update as frequently as it should
+            if (_gameTiming.CurTime < metab.NextUpdate)
+                continue;
 
-            var metabolizers = new ValueList<(EntityUid Uid, MetabolizerComponent Component)>(Count<MetabolizerComponent>());
-            var query = EntityQueryEnumerator<MetabolizerComponent>();
+            metab.NextUpdate += metab.AdjustedUpdateInterval;
+            TryMetabolize((uid, metab));
+        }
+    }
 
-            while (query.MoveNext(out var uid, out var comp))
-            {
-                metabolizers.Add((uid, comp));
-            }
+    private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
+    {
+        _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
+
+        // First step is get the solution we actually care about
+        var solutionName = ent.Comp1.SolutionName;
+        Solution? solution = null;
+        Entity<SolutionComponent>? soln = default!;
+        EntityUid? solutionEntityUid = null;
 
-            foreach (var (uid, metab) in metabolizers)
+        if (ent.Comp1.SolutionOnBody)
+        {
+            if (ent.Comp2?.Body is { } body)
             {
-                // Only update as frequently as it should
-                if (_gameTiming.CurTime < metab.NextUpdate)
-                    continue;
+                if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
+                    return;
 
-                metab.NextUpdate += metab.AdjustedUpdateInterval;
-                TryMetabolize((uid, metab));
+                _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
+                solutionEntityUid = body;
             }
         }
+        else
+        {
+            if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
+                return;
 
-        private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
+            _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
+            solutionEntityUid = ent;
+        }
+
+        if (solutionEntityUid is null
+            || soln is null
+            || solution is null
+            || solution.Contents.Count == 0)
         {
-            _organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
+            return;
+        }
 
-            // First step is get the solution we actually care about
-            var solutionName = ent.Comp1.SolutionName;
-            Solution? solution = null;
-            Entity<SolutionComponent>? soln = default!;
-            EntityUid? solutionEntityUid = null;
+        // randomize the reagent list so we don't have any weird quirks
+        // like alphabetical order or insertion order mattering for processing
+        var list = solution.Contents.ToArray();
+        _random.Shuffle(list);
 
-            if (ent.Comp1.SolutionOnBody)
+        int reagents = 0;
+        foreach (var (reagent, quantity) in list)
+        {
+            if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var proto))
+                continue;
+
+            var mostToRemove = FixedPoint2.Zero;
+            if (proto.Metabolisms is null)
             {
-                if (ent.Comp2?.Body is { } body)
+                if (ent.Comp1.RemoveEmpty)
                 {
-                    if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
-                        return;
-
-                    _solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
-                    solutionEntityUid = body;
+                    solution.RemoveReagent(reagent, FixedPoint2.New(1));
                 }
-            }
-            else
-            {
-                if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
-                    return;
 
-                _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
-                solutionEntityUid = ent;
+                continue;
             }
 
-            if (solutionEntityUid is null
-                || soln is null
-                || solution is null
-                || solution.Contents.Count == 0)
-            {
+            // we're done here entirely if this is true
+            if (reagents >= ent.Comp1.MaxReagentsProcessable)
                 return;
-            }
 
-            // randomize the reagent list so we don't have any weird quirks
-            // like alphabetical order or insertion order mattering for processing
-            var list = solution.Contents.ToArray();
-            _random.Shuffle(list);
 
-            int reagents = 0;
-            foreach (var (reagent, quantity) in list)
+            // loop over all our groups and see which ones apply
+            if (ent.Comp1.MetabolismGroups is null)
+                continue;
+
+            // TODO: Kill MetabolismGroups!
+            foreach (var group in ent.Comp1.MetabolismGroups)
             {
-                if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var proto))
+                if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
                     continue;
 
-                var mostToRemove = FixedPoint2.Zero;
-                if (proto.Metabolisms is null)
-                {
-                    if (ent.Comp1.RemoveEmpty)
-                    {
-                        solution.RemoveReagent(reagent, FixedPoint2.New(1));
-                    }
+                var rate = entry.MetabolismRate * group.MetabolismRateModifier;
 
-                    continue;
-                }
-
-                // we're done here entirely if this is true
-                if (reagents >= ent.Comp1.MaxReagentsProcessable)
-                    return;
+                // Remove $rate, as long as there's enough reagent there to actually remove that much
+                mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
 
+                var scale = (float) mostToRemove;
 
-                // loop over all our groups and see which ones apply
-                if (ent.Comp1.MetabolismGroups is null)
-                    continue;
+                // TODO: This is a very stupid workaround to lungs heavily relying on scale = reagent quantity. Needs lung and metabolism refactors to remove.
+                // TODO: Lungs just need to have their scale be equal to the mols consumed, scale needs to be not hardcoded either and configurable per metabolizer...
+                if (group.Id != Gas)
+                    scale /= (float) entry.MetabolismRate;
 
-                foreach (var group in ent.Comp1.MetabolismGroups)
+                // if it's possible for them to be dead, and they are,
+                // then we shouldn't process any effects, but should probably
+                // still remove reagents
+                if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
                 {
-                    if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
+                    if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
                         continue;
+                }
 
-                    var rate = entry.MetabolismRate * group.MetabolismRateModifier;
+                var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
 
-                    // Remove $rate, as long as there's enough reagent there to actually remove that much
-                    mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
+                // do all effects, if conditions apply
+                foreach (var effect in entry.Effects)
+                {
+                    if (scale < effect.MinScale)
+                        continue;
 
-                    float scale = (float) mostToRemove / (float) rate;
+                    // See if conditions apply
+                    if (effect.Conditions != null && !CanMetabolizeEffect(actualEntity, ent, soln.Value, effect.Conditions))
+                        continue;
 
-                    // if it's possible for them to be dead, and they are,
-                    // then we shouldn't process any effects, but should probably
-                    // still remove reagents
-                    if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
-                    {
-                        if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
-                            continue;
-                    }
+                    ApplyEffect(effect);
 
-                    var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
-                    var args = new EntityEffectReagentArgs(actualEntity, EntityManager, ent, solution, mostToRemove, proto, null, scale);
+                }
 
-                    // do all effects, if conditions apply
-                    foreach (var effect in entry.Effects)
+                // TODO: We should have to do this with metabolism. ReagentEffect struct needs refactoring and so does metabolism!
+                void ApplyEffect(EntityEffect effect)
+                {
+                    switch (effect)
                     {
-                        if (!effect.ShouldApply(args, _random))
-                            continue;
-
-                        if (effect.ShouldLog)
-                        {
-                            _adminLogger.Add(
-                                LogType.ReagentEffect,
-                                effect.LogImpact,
-                                $"Metabolism effect {effect.GetType().Name:effect}"
-                                + $" of reagent {proto.LocalizedName:reagent}"
-                                + $" applied on entity {actualEntity:entity}"
-                                + $" at {Transform(actualEntity).Coordinates:coordinates}"
-                            );
-                        }
-
-                        effect.Effect(args);
+                        case ModifyLungGas:
+                            _entityEffects.ApplyEffect(ent, effect, scale);
+                            break;
+                        case AdjustReagent:
+                            _entityEffects.ApplyEffect(soln.Value, effect, scale);
+                            break;
+                        default:
+                            _entityEffects.ApplyEffect(actualEntity, effect, scale);
+                            break;
                     }
                 }
+            }
 
-                // remove a certain amount of reagent
-                if (mostToRemove > FixedPoint2.Zero)
-                {
-                    solution.RemoveReagent(reagent, mostToRemove);
+            // remove a certain amount of reagent
+            if (mostToRemove > FixedPoint2.Zero)
+            {
+                solution.RemoveReagent(reagent, mostToRemove);
 
-                    // We have processed a reagant, so count it towards the cap
-                    reagents += 1;
-                }
+                // We have processed a reagant, so count it towards the cap
+                reagents += 1;
             }
+        }
 
-            _solutionContainerSystem.UpdateChemicals(soln.Value);
+        _solutionContainerSystem.UpdateChemicals(soln.Value);
+    }
+
+    /// <summary>
+    /// Public API to check if a certain metabolism effect can be applied to an entity.
+    /// TODO: With metabolism refactor make this logic smarter and unhardcode the old hardcoding entity effects used to have for metabolism!
+    /// </summary>
+    /// <param name="body">The body metabolizing the effects</param>
+    /// <param name="organ">The organ doing the metabolizing</param>
+    /// <param name="solution">The solution we are metabolizing from</param>
+    /// <param name="conditions">The conditions that need to be met to metabolize</param>
+    /// <returns>True if we can metabolize! False if we cannot!</returns>
+    public bool CanMetabolizeEffect(EntityUid body, EntityUid organ, Entity<SolutionComponent> solution, EntityCondition[] conditions)
+    {
+        foreach (var condition in conditions)
+        {
+            switch (condition)
+            {
+                // Need specific handling of specific conditions since Metabolism is funny like that.
+                // TODO: MetabolizerTypes should be handled well before this stage by metabolism itself.
+                case MetabolizerTypeCondition:
+                    if (_entityConditions.TryCondition(organ, condition))
+                        continue;
+                    break;
+                case ReagentCondition:
+                    if (_entityConditions.TryCondition(solution, condition))
+                        continue;
+                    break;
+                default:
+                    if (_entityConditions.TryCondition(body, condition))
+                        continue;
+                    break;
+            }
+
+            return false;
         }
+
+        return true;
     }
 }
+
index 63b04adc6a06b4e07c21329cac9be9ad627368d9..2af7b24f265a1f19f8eecc7a9c1cd4c33669e962 100644 (file)
@@ -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<MetabolismGroupPrototype> GasId = new("Gas");
 
@@ -340,7 +341,6 @@ public sealed class RespiratorSystem : EntitySystem
             }
         }
 
-        // TODO generalize condition checks
         // this is pretty janky, but I just want to bodge a method that checks if an entity can breathe a gas mixture
         // 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;
             }
 
index 3ba9e6af31802cbb10d17cf118b12f03610a1dd5..af7b17643e95dc6b108de4a84f9cb829b30ffbe5 100644 (file)
@@ -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;
index ee7ca4f584616b8fb0ce000504e767981336698d..253eea2df9ff1a1a92c694ec7d3acef41d8a54ea 100644 (file)
@@ -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.
+/// <remarks>
+/// SeedData is no longer restricted because the number of friends is absolutely unreasonable.
+/// This entire data definition is unreasonable. I felt genuine fear looking at this, this is horrific. Send help.
+/// </remarks>
+// TODO: Hit Botany with hammers
 [Virtual, DataDefinition]
-[Access(typeof(BotanySystem), typeof(PlantHolderSystem), typeof(SeedExtractorSystem), typeof(PlantHolderComponent), typeof(EntityEffectSystem), typeof(MutationSystem))]
 public partial class SeedData
 {
     #region Tracking
index f6f3f99c09e74ed8928ec4c410f22bd3ef1552ec..7d8f8652c728dd74c1201914ea1668006fe43a57 100644 (file)
@@ -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,
index ee35db48e3b14ff924f25d7739ae896346d8bcb4..834fd9e8efba0f68cfc0df05d80b9dc2bd6338d5 100644 (file)
@@ -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);
index caa796efe2c1f23e950a1ff9f2d2fbf1b50f21ce..2554f95455efed4c911ddd7e168948d79ff2f99d 100644 (file)
@@ -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<ReagentPrototype>(entry.Reagent.Prototype);
-                reagentProto.ReactionPlant(uid, entry, solution, EntityManager, _random, _adminLogger);
+                _entityEffects.ApplyEffects(uid, reagentProto.PlantMetabolisms.ToArray());
             }
         }
 
index a70c2196abadb7845cec712472af2ee3dc77e494..58c86f058a7418033d4ef7fb6ae6156b8fe3fbfa 100644 (file)
@@ -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())));
             }
         }
index 3dd5a5b794bc678d13ddd7d12a38342ba8bfd767..77a1a63e02aab36bda1d0c4606bfe5d077ad9a0a 100644 (file)
@@ -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 (file)
index 0000000..e7b8aaf
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions.Body;
+
+namespace Content.Server.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity is both able to breathe and is currently breathing.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class IsBreathingEntityConditionSystem : EntityConditionSystem<RespiratorComponent, BreathingCondition>
+{
+    [Dependency] private readonly RespiratorSystem _respirator = default!;
+    protected override void Condition(Entity<RespiratorComponent> entity, ref EntityConditionEvent<BreathingCondition> args)
+    {
+        args.Result = _respirator.IsBreathing(entity.AsNullable());
+    }
+}
diff --git a/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs b/Content.Server/EntityConditions/Conditions/MetabolizerTypesEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..3b4fb52
--- /dev/null
@@ -0,0 +1,21 @@
+using System.Linq;
+using Content.Server.Body.Components;
+using Content.Shared.EntityConditions;
+using Content.Shared.EntityConditions.Conditions.Body;
+
+namespace Content.Server.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity has any of the listed metabolizer types.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class MetabolizerTypeEntityConditionSystem : EntityConditionSystem<MetabolizerComponent, MetabolizerTypeCondition>
+{
+    protected override void Condition(Entity<MetabolizerComponent> entity, ref EntityConditionEvent<MetabolizerTypeCondition> args)
+    {
+        if (entity.Comp.MetabolizerTypes == null)
+            return;
+
+        args.Result = entity.Comp.MetabolizerTypes.Overlaps(args.Condition.Type);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/CreateGasEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..033704f
--- /dev/null
@@ -0,0 +1,22 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// This effect adjusts a gas at the tile this entity is currently on.
+/// The amount changed is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class CreateGasEntityEffectSystem : EntityEffectSystem<TransformComponent, CreateGas>
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+
+    protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<CreateGas> args)
+    {
+        var tileMix = _atmosphere.GetContainingMixture(entity.AsNullable(), false, true);
+
+        tileMix?.AdjustMoles(args.Effect.Gas, args.Scale * args.Effect.Moles);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/FlammableEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..65c818f
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// Adds a number of FireStacks modified by scale to this entity.
+/// The amount of FireStacks added is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class FlammableEntityEffectSystem : EntityEffectSystem<FlammableComponent, Flammable>
+{
+    [Dependency] private readonly FlammableSystem _flammable = default!;
+
+    protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Flammable> args)
+    {
+        // The multiplier is determined by if the entity is already on fire, and if the multiplier for existing FireStacks has a value.
+        // If both of these are true, we use the MultiplierOnExisting value, otherwise we use the standard Multiplier.
+        var multiplier = entity.Comp.FireStacks == 0f || args.Effect.MultiplierOnExisting == null ? args.Effect.Multiplier : args.Effect.MultiplierOnExisting.Value;
+
+        _flammable.AdjustFireStacks(entity, args.Scale * multiplier, entity.Comp);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Atmos/IgniteEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..de90656
--- /dev/null
@@ -0,0 +1,23 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Atmos.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Atmos;
+
+namespace Content.Server.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// Sets this entity on fire.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class IngiteEntityEffectSystem : EntityEffectSystem<FlammableComponent, Ignite>
+{
+    [Dependency] private readonly FlammableSystem _flammable = default!;
+
+    protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Ignite> args)
+    {
+        // TODO: Proper BodySystem Metabolism Effect relay...
+        // TODO: If this fucks over downstream shitmed, I give you full approval to use whatever shitcode method you need to fix it. Metabolism is awful.
+        _flammable.Ignite(entity, entity, flammable: entity.Comp);
+    }
+}
+
diff --git a/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs b/Content.Server/EntityEffects/Effects/Body/OxygenateEntityEffectsSystem.cs
new file mode 100644 (file)
index 0000000..0cbf0b3
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Server.Body.Components;
+using Content.Server.Body.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Body;
+
+namespace Content.Server.EntityEffects.Effects.Body;
+
+/// <summary>
+/// This effect adjusts a respirator's saturation value.
+/// The saturation adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class OxygenateEntityEffectsSystem : EntityEffectSystem<RespiratorComponent, Oxygenate>
+{
+    [Dependency] private readonly RespiratorSystem _respirator = default!;
+    protected override void Effect(Entity<RespiratorComponent> entity, ref EntityEffectEvent<Oxygenate> args)
+    {
+        _respirator.UpdateSaturation(entity, args.Scale * args.Effect.Factor, entity.Comp);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealthEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..64f61f5
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustHealthEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustHealth>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustHealth> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        entity.Comp.MutationLevel += args.Effect.Amount * entity.Comp.MutationMod;
+        _plantHolder.CheckHealth(entity, entity.Comp);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevelEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..f35ff25
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationLevelEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustMutationLevel>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustMutationLevel> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        entity.Comp.Health += args.Effect.Amount;
+        _plantHolder.CheckHealth(entity, entity.Comp);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationModEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..3163ee3
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationModEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustMutationMod>
+{
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustMutationMod> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        entity.Comp.MutationMod += args.Effect.Amount;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutritionEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..56c0167
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustNutritionEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustNutrition>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustNutrition> args)
+    {
+        _plantHolder.AdjustNutrient(entity, args.Effect.Amount, entity);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPestsEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..0495034
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustPestsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustPests>
+{
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPests> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        entity.Comp.PestLevel += args.Effect.Amount;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotencyEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..ebe5c83
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustPotencyEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustPotency>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustPotency> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+        entity.Comp.Seed.Potency = Math.Max(entity.Comp.Seed.Potency + args.Effect.Amount, 1);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxinsEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..31dc328
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustToxinsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustToxins>
+{
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustToxins> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        entity.Comp.Toxins += args.Effect.Amount;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWaterEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..706eeb2
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustWaterEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustWater>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustWater> args)
+    {
+        _plantHolder.AdjustWater(entity, args.Effect.Amount, entity.Comp);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeedsEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..34aa51e
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustWeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAdjustWeeds>
+{
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAdjustWeeds> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        entity.Comp.WeedLevel += args.Effect.Amount;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowthEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..b0faa62
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAffectGrowthEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantAffectGrowth>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantAffectGrowth> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        _plantHolder.AffectGrowth(entity, (int)args.Effect.Amount, entity);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStatEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..3d82f74
--- /dev/null
@@ -0,0 +1,122 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// This system mutates an inputted stat for a PlantHolder, only works for floats, integers, and bools.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class PlantChangeStatEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantChangeStat>
+{
+    // TODO: This is awful. I do not have the strength to refactor this. I want it gone.
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantChangeStat> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        var effect = args.Effect;
+        var member = entity.Comp.Seed.GetType().GetField(args.Effect.TargetValue);
+
+        if (member == null)
+        {
+            Log.Error($"{ effect.GetType().Name } Error: Member { args.Effect.TargetValue} not found on { entity.Comp.Seed.GetType().Name }. Did you misspell it?");
+            return;
+        }
+
+        var currentValObj = member.GetValue(entity.Comp.Seed);
+        if (currentValObj == null)
+            return;
+
+        if (member.FieldType == typeof(float))
+        {
+            var floatVal = (float)currentValObj;
+            MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
+            member.SetValue(entity.Comp.Seed, floatVal);
+        }
+        else if (member.FieldType == typeof(int))
+        {
+            var intVal = (int)currentValObj;
+            MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
+            member.SetValue(entity.Comp.Seed, intVal);
+        }
+        else if (member.FieldType == typeof(bool))
+        {
+            var boolVal = (bool)currentValObj;
+            boolVal = !boolVal;
+            member.SetValue(entity.Comp.Seed, boolVal);
+        }
+    }
+
+    // Mutate reference 'val' between 'min' and 'max' by pretending the value
+    // is representable by a thermometer code with 'bits' number of bits and
+    // randomly flipping some of them.
+    private void MutateFloat(ref float val, float min, float max, int bits)
+    {
+        if (min == max)
+        {
+            val = min;
+            return;
+        }
+
+        // Starting number of bits that are high, between 0 and bits.
+        // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
+        int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
+        // val may be outside the range of min/max due to starting prototype values, so clamp.
+        valInt = Math.Clamp(valInt, 0, bits);
+
+        // Probability that the bit flip increases n.
+        // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
+        // In other words, it tends to go to the middle.
+        float probIncrease = 1 - (float)valInt / bits;
+        int valIntMutated;
+        if (_random.Prob(probIncrease))
+        {
+            valIntMutated = valInt + 1;
+        }
+        else
+        {
+            valIntMutated = valInt - 1;
+        }
+
+        // Set value based on mutated thermometer code.
+        float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
+        val = valMutated;
+    }
+
+    private void MutateInt(ref int val, int min, int max, int bits)
+    {
+        if (min == max)
+        {
+            val = min;
+            return;
+        }
+
+        // Starting number of bits that are high, between 0 and bits.
+        // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
+        int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
+        // val may be outside the range of min/max due to starting prototype values, so clamp.
+        valInt = Math.Clamp(valInt, 0, bits);
+
+        // Probability that the bit flip increases n.
+        // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
+        // In other words, it tends to go to the middle.
+        float probIncrease = 1 - (float)valInt / bits;
+        int valMutated;
+        if (_random.Prob(probIncrease))
+        {
+            valMutated = val + 1;
+        }
+        else
+        {
+            valMutated = val - 1;
+        }
+
+        valMutated = Math.Clamp(valMutated, min, max);
+        val = valMutated;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadoneEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..710bce2
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantCryoxadoneEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantCryoxadone>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantCryoxadone> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        var deviation = 0;
+        var seed = entity.Comp.Seed;
+        if (seed == null)
+            return;
+        if (entity.Comp.Age > seed.Maturation)
+            deviation = (int) Math.Max(seed.Maturation - 1, entity.Comp.Age - _random.Next(7, 10));
+        else
+            deviation = (int) (seed.Maturation / seed.GrowthStages);
+        entity.Comp.Age -= deviation;
+        entity.Comp.LastProduce = entity.Comp.Age;
+        entity.Comp.SkipAging++;
+        entity.Comp.ForceUpdate = true;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeedsEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..1661c50
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Server.Popups;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Content.Shared.Popups;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantDestroySeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantDestroySeeds>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDestroySeeds> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+            return;
+
+        if (entity.Comp.Seed.Seedless)
+            return;
+
+        _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+        _popup.PopupEntity(
+            Loc.GetString("botany-plant-seedsdestroyed"),
+            entity,
+            PopupType.SmallCaution
+        );
+        entity.Comp.Seed.Seedless = true;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamineEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..f6aebde
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantDiethylamineEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantDiethylamine>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantDiethylamine> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+            return;
+
+        if (_random.Prob(0.1f))
+        {
+            _plantHolder.EnsureUniqueSeed(entity, entity);
+            entity.Comp.Seed!.Lifespan++;
+        }
+
+        if (_random.Prob(0.1f))
+        {
+            _plantHolder.EnsureUniqueSeed(entity, entity);
+            entity.Comp.Seed!.Endurance++;
+        }
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximineEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..8a07339
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantPhalanximineEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantPhalanximine>
+{
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantPhalanximine> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+            return;
+
+        entity.Comp.Seed.Viable = true;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeedsEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..4d724be
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Server.Popups;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantRestoreSeedsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantRestoreSeeds>
+{
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantRestoreSeeds> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead || entity.Comp.Seed.Immutable)
+            return;
+
+        if (!entity.Comp.Seed.Seedless)
+            return;
+
+        _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+        _popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), entity);
+        entity.Comp.Seed.Seedless = false;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvestEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..68ea331
--- /dev/null
@@ -0,0 +1,41 @@
+using Content.Server.Botany.Components;
+using Content.Server.Botany.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// This effect directly increases the potency of a PlantHolder's plant provided it exists and isn't dead.
+/// Potency directly correlates to the size of the plant's produce.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class RobustHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, RobustHarvest>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<RobustHarvest> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Dead)
+            return;
+
+        if (entity.Comp.Seed.Potency < args.Effect.PotencyLimit)
+        {
+            _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+            entity.Comp.Seed.Potency = Math.Min(entity.Comp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
+
+            if (entity.Comp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
+            {
+                entity.Comp.Seed.Seedless = true;
+            }
+        }
+        else if (entity.Comp.Seed.Yield > 1 && _random.Prob(0.1f))
+        {
+            // Too much of a good thing reduces yield
+            _plantHolder.EnsureUniqueSeed(entity, entity.Comp);
+            entity.Comp.Seed.Yield--;
+        }
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..120ae6e
--- /dev/null
@@ -0,0 +1,43 @@
+using Content.Server.Botany;
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateChemicalsEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateChemicals>
+{
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateChemicals> args)
+    {
+        if (entity.Comp.Seed == null)
+            return;
+
+        var chemicals = entity.Comp.Seed.Chemicals;
+        var randomChems = _proto.Index(args.Effect.RandomPickBotanyReagent).Fills;
+
+        // Add a random amount of a random chemical to this set of chemicals
+        var pick = _random.Pick(randomChems);
+        var chemicalId = _random.Pick(pick.Reagents);
+        var amount = _random.Next(1, (int)pick.Quantity);
+        var seedChemQuantity = new SeedChemQuantity();
+        if (chemicals.ContainsKey(chemicalId))
+        {
+            seedChemQuantity.Min = chemicals[chemicalId].Min;
+            seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
+        }
+        else
+        {
+            seedChemQuantity.Min = 1;
+            seedChemQuantity.Max = 1 + amount;
+            seedChemQuantity.Inherent = false;
+        }
+        var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
+        seedChemQuantity.PotencyDivisor = potencyDivisor;
+        chemicals[chemicalId] = seedChemQuantity;
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..e2376ba
--- /dev/null
@@ -0,0 +1,53 @@
+using System.Linq;
+using Content.Server.Botany.Components;
+using Content.Shared.Atmos;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateExudeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateExudeGases>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateExudeGases> args)
+    {
+        if (entity.Comp.Seed == null)
+            return;
+
+        var gasses = entity.Comp.Seed.ExudeGasses;
+
+        // Add a random amount of a random gas to this gas dictionary
+        float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
+        var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
+
+        if (!gasses.TryAdd(gas, amount))
+        {
+            gasses[gas] += amount;
+        }
+    }
+}
+
+public sealed partial class PlantMutateConsumeGasesEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateConsumeGases>
+{
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateConsumeGases> args)
+    {
+        if (entity.Comp.Seed == null)
+            return;
+
+        var gasses = entity.Comp.Seed.ConsumeGasses;
+
+        // Add a random amount of a random gas to this gas dictionary
+        var amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
+        var gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
+
+        if (!gasses.TryAdd(gas, amount))
+        {
+            gasses[gas] += amount;
+        }
+    }
+}
+
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..95d7f97
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Server.Botany;
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateHarvestEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateHarvest>
+{
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateHarvest> args)
+    {
+        if (entity.Comp.Seed == null)
+            return;
+
+        switch (entity.Comp.Seed.HarvestRepeat)
+        {
+            case HarvestType.NoRepeat:
+                entity.Comp.Seed.HarvestRepeat = HarvestType.Repeat;
+                break;
+            case HarvestType.Repeat:
+                entity.Comp.Seed.HarvestRepeat = HarvestType.SelfHarvest;
+                break;
+        }
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..c26e1e0
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Server.Botany;
+using Content.Server.Botany.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Botany;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.EntityEffects.Effects.Botany;
+
+public sealed partial class PlantMutateSpeciesChangeEntityEffectSystem : EntityEffectSystem<PlantHolderComponent, PlantMutateSpeciesChange>
+{
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    protected override void Effect(Entity<PlantHolderComponent> entity, ref EntityEffectEvent<PlantMutateSpeciesChange> args)
+    {
+        if (entity.Comp.Seed == null || entity.Comp.Seed.MutationPrototypes.Count == 0)
+            return;
+
+        var targetProto = _random.Pick(entity.Comp.Seed.MutationPrototypes);
+        _proto.TryIndex(targetProto, out SeedPrototype? protoSeed);
+
+        if (protoSeed == null)
+        {
+            Log.Error($"Seed prototype could not be found: {targetProto}!");
+            return;
+        }
+
+        entity.Comp.Seed = entity.Comp.Seed.SpeciesChange(protoSeed);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/EmoteEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..05ab857
--- /dev/null
@@ -0,0 +1,22 @@
+using Content.Server.Chat.Systems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
+
+namespace Content.Server.EntityEffects.Effects;
+
+/// <summary>
+/// Makes this entity emote.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EmoteEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Emote>
+{
+    [Dependency] private readonly ChatSystem _chat = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Emote> args)
+    {
+        if (args.Effect.ShowInChat)
+            _chat.TryEmoteWithChat(entity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
+        else
+            _chat.TryEmoteWithoutChat(entity, args.Effect.EmoteId);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/MakeSentientEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..c623b25
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Server.Ghost.Roles.Components;
+using Content.Server.Speech.Components;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects;
+using Content.Shared.Mind.Components;
+
+namespace Content.Server.EntityEffects.Effects;
+
+/// <summary>
+/// Makes this entity sentient. Allows ghost to take it over if it's not already occupied.
+/// Optionally also allows this entity to speak.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class MakeSentientEntityEffectSystem : EntityEffectSystem<MetaDataComponent, MakeSentient>
+{
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<MakeSentient> args)
+    {
+        // Let affected entities speak normally to make this effect different from, say, the "random sentience" event
+        // This also works on entities that already have a mind
+        // We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
+        if (args.Effect.AllowSpeech)
+        {
+            RemComp<ReplacementAccentComponent>(entity);
+            // TODO: Make MonkeyAccent a replacement accent and remove MonkeyAccent code-smell.
+            RemComp<MonkeyAccentComponent>(entity);
+        }
+
+        // Stops from adding a ghost role to things like people who already have a mind
+        if (TryComp<MindContainerComponent>(entity, out var mindContainer) && mindContainer.HasMind)
+            return;
+
+        // Don't add a ghost role to things that already have ghost roles
+        if (TryComp(entity, out GhostRoleComponent? ghostRole))
+            return;
+
+        ghostRole = AddComp<GhostRoleComponent>(entity);
+        EnsureComp<GhostTakeoverAvailableComponent>(entity);
+
+        ghostRole.RoleName = entity.Comp.EntityName;
+        ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/PolymorphEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..5f19bcc
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Server.Polymorph.Components;
+using Content.Server.Polymorph.Systems;
+using Content.Shared.EntityEffects;
+
+namespace Content.Server.EntityEffects.Effects;
+
+/// <summary>
+/// Polymorphs this entity into another entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class PolymorphEntityEffectSystem : EntityEffectSystem<PolymorphableComponent, Shared.EntityEffects.Effects.Polymorph>
+{
+    [Dependency] private readonly PolymorphSystem _polymorph = default!;
+
+    protected override void Effect(Entity<PolymorphableComponent> entity, ref EntityEffectEvent<Shared.EntityEffects.Effects.Polymorph> args)
+    {
+        _polymorph.PolymorphEntity(entity, args.Effect.Prototype);
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Solution/AreaReactionEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..e5ef488
--- /dev/null
@@ -0,0 +1,51 @@
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Spreader;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Coordinates.Helpers;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Solution;
+using Content.Shared.Maps;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Map;
+
+namespace Content.Server.EntityEffects.Effects.Solution;
+
+/// <summary>
+/// This effect creates smoke at this solution's position.
+/// The amount of smoke created is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AreaReactionEntityEffectsSystem : EntityEffectSystem<SolutionComponent, AreaReactionEffect>
+{
+    [Dependency] private readonly IMapManager _mapManager = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedMapSystem _map = default!;
+    [Dependency] private readonly SharedTransformSystem _xform = default!;
+    [Dependency] private readonly SmokeSystem _smoke = default!;
+    [Dependency] private readonly SpreaderSystem _spreader = default!;
+    [Dependency] private readonly TurfSystem _turf = default!;
+
+    // TODO: A sane way to make Smoke without a solution.
+    protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AreaReactionEffect> args)
+    {
+        var xform = Transform(entity);
+        var mapCoords = _xform.GetMapCoordinates(entity);
+        var spreadAmount = (int) Math.Max(0, Math.Ceiling(args.Scale / args.Effect.OverflowThreshold));
+        var effect = args.Effect;
+
+        if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
+            !_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef))
+            return;
+
+        if (_spreader.RequiresFloorToSpread(effect.PrototypeId.ToString()) && _turf.IsSpace(tileRef))
+            return;
+
+        var coords = _map.MapToGrid(gridUid, mapCoords);
+        var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid());
+
+        _smoke.StartSmoke(ent, entity.Comp.Solution, args.Effect.Duration, spreadAmount);
+
+        _audio.PlayPvs(args.Effect.Sound, entity, AudioParams.Default.WithVariation(0.25f));
+    }
+}
diff --git a/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs b/Content.Server/EntityEffects/Effects/Transform/ExplosionEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..55fc120
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Server.Explosion.EntitySystems;
+using Content.Shared.EntityEffects;
+using Content.Shared.EntityEffects.Effects.Transform;
+
+namespace Content.Server.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates an explosion at this entity's position.
+/// Intensity is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ExplosionEntityEffectSystem : EntityEffectSystem<TransformComponent, ExplosionEffect>
+{
+    [Dependency] private readonly ExplosionSystem _explosion = default!;
+
+    protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<ExplosionEffect> args)
+    {
+        var intensity = MathF.Min(args.Effect.IntensityPerUnit * args.Scale, args.Effect.MaxTotalIntensity);
+
+        _explosion.QueueExplosion(
+            entity,
+            args.Effect.ExplosionType,
+            intensity,
+            args.Effect.IntensitySlope,
+            args.Effect.MaxIntensity,
+            args.Effect.TileBreakScale);
+    }
+}
diff --git a/Content.Server/EntityEffects/EntityEffectSystem.cs b/Content.Server/EntityEffects/EntityEffectSystem.cs
deleted file mode 100644 (file)
index 238ef48..0000000
+++ /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<WeightedRandomFillSolutionPrototype> RandomPickBotanyReagent = "RandomPickBotanyReagent";
-
-    [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
-    [Dependency] private readonly BloodstreamSystem _bloodstream = default!;
-    [Dependency] private readonly ChatSystem _chat = default!;
-    [Dependency] private readonly EmpSystem _emp = default!;
-    [Dependency] private readonly ExplosionSystem _explosion = default!;
-    [Dependency] private readonly FlammableSystem _flammable = default!;
-    [Dependency] private readonly SharedFlashSystem _flash = default!;
-    [Dependency] private readonly IMapManager _mapManager = default!;
-    [Dependency] private readonly IPrototypeManager _protoManager = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly SharedMapSystem _map = default!;
-    [Dependency] private readonly MutationSystem _mutation = default!;
-    [Dependency] private readonly NarcolepsySystem _narcolepsy = default!;
-    [Dependency] private readonly PlantHolderSystem _plantHolder = default!;
-    [Dependency] private readonly PolymorphSystem _polymorph = default!;
-    [Dependency] private readonly RespiratorSystem _respirator = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-    [Dependency] private readonly SharedPointLightSystem _pointLight = default!;
-    [Dependency] private readonly SharedPopupSystem _popup = default!;
-    [Dependency] private readonly SmokeSystem _smoke = default!;
-    [Dependency] private readonly SpreaderSystem _spreader = default!;
-    [Dependency] private readonly TemperatureSystem _temperature = default!;
-    [Dependency] private readonly SharedTransformSystem _xform = default!;
-    [Dependency] private readonly VomitSystem _vomit = default!;
-    [Dependency] private readonly TurfSystem _turf = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<CheckEntityEffectConditionEvent<TemperatureCondition>>(OnCheckTemperature);
-        SubscribeLocalEvent<CheckEntityEffectConditionEvent<Breathing>>(OnCheckBreathing);
-        SubscribeLocalEvent<CheckEntityEffectConditionEvent<OrganType>>(OnCheckOrganType);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustHealth>>(OnExecutePlantAdjustHealth);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationLevel>>(OnExecutePlantAdjustMutationLevel);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustMutationMod>>(OnExecutePlantAdjustMutationMod);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustNutrition>>(OnExecutePlantAdjustNutrition);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPests>>(OnExecutePlantAdjustPests);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustPotency>>(OnExecutePlantAdjustPotency);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustToxins>>(OnExecutePlantAdjustToxins);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWater>>(OnExecutePlantAdjustWater);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAdjustWeeds>>(OnExecutePlantAdjustWeeds);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantAffectGrowth>>(OnExecutePlantAffectGrowth);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantChangeStat>>(OnExecutePlantChangeStat);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantCryoxadone>>(OnExecutePlantCryoxadone);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDestroySeeds>>(OnExecutePlantDestroySeeds);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantDiethylamine>>(OnExecutePlantDiethylamine);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantPhalanximine>>(OnExecutePlantPhalanximine);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantRestoreSeeds>>(OnExecutePlantRestoreSeeds);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<RobustHarvest>>(OnExecuteRobustHarvest);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<AdjustTemperature>>(OnExecuteAdjustTemperature);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<AreaReactionEffect>>(OnExecuteAreaReactionEffect);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<CauseZombieInfection>>(OnExecuteCauseZombieInfection);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemCleanBloodstream>>(OnExecuteChemCleanBloodstream);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ChemVomit>>(OnExecuteChemVomit);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateEntityReactionEffect>>(OnExecuteCreateEntityReactionEffect);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<CreateGas>>(OnExecuteCreateGas);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<CureZombieInfection>>(OnExecuteCureZombieInfection);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<Emote>>(OnExecuteEmote);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<EmpReactionEffect>>(OnExecuteEmpReactionEffect);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ExplosionReactionEffect>>(OnExecuteExplosionReactionEffect);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<FlammableReaction>>(OnExecuteFlammableReaction);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<FlashReactionEffect>>(OnExecuteFlashReactionEffect);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<Ignite>>(OnExecuteIgnite);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<MakeSentient>>(OnExecuteMakeSentient);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBleedAmount>>(OnExecuteModifyBleedAmount);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyBloodLevel>>(OnExecuteModifyBloodLevel);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ModifyLungGas>>(OnExecuteModifyLungGas);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<Oxygenate>>(OnExecuteOxygenate);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateChemicals>>(OnExecutePlantMutateChemicals);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateConsumeGasses>>(OnExecutePlantMutateConsumeGasses);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateExudeGasses>>(OnExecutePlantMutateExudeGasses);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantMutateHarvest>>(OnExecutePlantMutateHarvest);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PlantSpeciesChange>>(OnExecutePlantSpeciesChange);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<PolymorphEffect>>(OnExecutePolymorph);
-        SubscribeLocalEvent<ExecuteEntityEffectEvent<ResetNarcolepsy>>(OnExecuteResetNarcolepsy);
-    }
-
-    private void OnCheckTemperature(ref CheckEntityEffectConditionEvent<TemperatureCondition> args)
-    {
-        args.Result = false;
-        if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
-        {
-            if (temp.CurrentTemperature >= args.Condition.Min && temp.CurrentTemperature <= args.Condition.Max)
-                args.Result = true;
-        }
-    }
-
-    private void OnCheckBreathing(ref CheckEntityEffectConditionEvent<Breathing> args)
-    {
-        if (!TryComp(args.Args.TargetEntity, out RespiratorComponent? respiratorComp))
-        {
-            args.Result = !args.Condition.IsBreathing;
-            return;
-        }
-
-        var breathingState = _respirator.IsBreathing((args.Args.TargetEntity, respiratorComp));
-        args.Result = args.Condition.IsBreathing == breathingState;
-    }
-
-    private void OnCheckOrganType(ref CheckEntityEffectConditionEvent<OrganType> args)
-    {
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            if (reagentArgs.OrganEntity == null)
-            {
-                args.Result = false;
-                return;
-            }
-
-            args.Result = OrganCondition(args.Condition, reagentArgs.OrganEntity.Value);
-            return;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-
-    public bool OrganCondition(OrganType condition, Entity<MetabolizerComponent?> metabolizer)
-    {
-        metabolizer.Comp ??= EntityManager.GetComponentOrNull<MetabolizerComponent>(metabolizer.Owner);
-        if (metabolizer.Comp != null
-            && metabolizer.Comp.MetabolizerTypes != null
-            && metabolizer.Comp.MetabolizerTypes.Contains(condition.Type))
-            return condition.ShouldHave;
-        return !condition.ShouldHave;
-    }
-
-    /// <summary>
-    ///     Checks if the plant holder can metabolize the reagent or not. Checks if it has an alive plant by default.
-    /// </summary>
-    /// <param name="plantHolder">The entity holding the plant</param>
-    /// <param name="plantHolderComponent">The plant holder component</param>
-    /// <param name="entityManager">The entity manager</param>
-    /// <param name="mustHaveAlivePlant">Whether to check if it has an alive plant or not</param>
-    /// <returns></returns>
-    private bool CanMetabolizePlant(EntityUid plantHolder, [NotNullWhen(true)] out PlantHolderComponent? plantHolderComponent,
-        bool mustHaveAlivePlant = true, bool mustHaveMutableSeed = false)
-    {
-        plantHolderComponent = null;
-
-        if (!TryComp(plantHolder, out plantHolderComponent))
-            return false;
-
-        if (mustHaveAlivePlant && (plantHolderComponent.Seed == null || plantHolderComponent.Dead))
-            return false;
-
-        if (mustHaveMutableSeed && (plantHolderComponent.Seed == null || plantHolderComponent.Seed.Immutable))
-            return false;
-
-        return true;
-    }
-
-    private void OnExecutePlantAdjustHealth(ref ExecuteEntityEffectEvent<PlantAdjustHealth> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        plantHolderComp.Health += args.Effect.Amount;
-        _plantHolder.CheckHealth(args.Args.TargetEntity, plantHolderComp);
-    }
-
-    private void OnExecutePlantAdjustMutationLevel(ref ExecuteEntityEffectEvent<PlantAdjustMutationLevel> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        plantHolderComp.MutationLevel += args.Effect.Amount * plantHolderComp.MutationMod;
-    }
-
-    private void OnExecutePlantAdjustMutationMod(ref ExecuteEntityEffectEvent<PlantAdjustMutationMod> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        plantHolderComp.MutationMod += args.Effect.Amount;
-    }
-
-    private void OnExecutePlantAdjustNutrition(ref ExecuteEntityEffectEvent<PlantAdjustNutrition> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
-            return;
-
-        _plantHolder.AdjustNutrient(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
-    }
-
-    private void OnExecutePlantAdjustPests(ref ExecuteEntityEffectEvent<PlantAdjustPests> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        plantHolderComp.PestLevel += args.Effect.Amount;
-    }
-
-    private void OnExecutePlantAdjustPotency(ref ExecuteEntityEffectEvent<PlantAdjustPotency> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        if (plantHolderComp.Seed == null)
-            return;
-
-        _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-        plantHolderComp.Seed.Potency = Math.Max(plantHolderComp.Seed.Potency + args.Effect.Amount, 1);
-    }
-
-    private void OnExecutePlantAdjustToxins(ref ExecuteEntityEffectEvent<PlantAdjustToxins> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        plantHolderComp.Toxins += args.Effect.Amount;
-    }
-
-    private void OnExecutePlantAdjustWater(ref ExecuteEntityEffectEvent<PlantAdjustWater> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveAlivePlant: false))
-            return;
-
-        _plantHolder.AdjustWater(args.Args.TargetEntity, args.Effect.Amount, plantHolderComp);
-    }
-
-    private void OnExecutePlantAdjustWeeds(ref ExecuteEntityEffectEvent<PlantAdjustWeeds> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        plantHolderComp.WeedLevel += args.Effect.Amount;
-    }
-
-    private void OnExecutePlantAffectGrowth(ref ExecuteEntityEffectEvent<PlantAffectGrowth> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        _plantHolder.AffectGrowth(args.Args.TargetEntity, (int) args.Effect.Amount, plantHolderComp);
-    }
-
-    // Mutate reference 'val' between 'min' and 'max' by pretending the value
-    // is representable by a thermometer code with 'bits' number of bits and
-    // randomly flipping some of them.
-    private void MutateFloat(ref float val, float min, float max, int bits)
-    {
-        if (min == max)
-        {
-            val = min;
-            return;
-        }
-
-        // Starting number of bits that are high, between 0 and bits.
-        // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
-        int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
-        // val may be outside the range of min/max due to starting prototype values, so clamp.
-        valInt = Math.Clamp(valInt, 0, bits);
-
-        // Probability that the bit flip increases n.
-        // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasive it it.
-        // In other words, it tends to go to the middle.
-        float probIncrease = 1 - (float)valInt / bits;
-        int valIntMutated;
-        if (_random.Prob(probIncrease))
-        {
-            valIntMutated = valInt + 1;
-        }
-        else
-        {
-            valIntMutated = valInt - 1;
-        }
-
-        // Set value based on mutated thermometer code.
-        float valMutated = Math.Clamp((float)valIntMutated / bits * (max - min) + min, min, max);
-        val = valMutated;
-    }
-
-    private void MutateInt(ref int val, int min, int max, int bits)
-    {
-        if (min == max)
-        {
-            val = min;
-            return;
-        }
-
-        // Starting number of bits that are high, between 0 and bits.
-        // In other words, it's val mapped linearly from range [min, max] to range [0, bits], and then rounded.
-        int valInt = (int)MathF.Round((val - min) / (max - min) * bits);
-        // val may be outside the range of min/max due to starting prototype values, so clamp.
-        valInt = Math.Clamp(valInt, 0, bits);
-
-        // Probability that the bit flip increases n.
-        // The higher the current value is, the lower the probability of increasing value is, and the higher the probability of decreasing it.
-        // In other words, it tends to go to the middle.
-        float probIncrease = 1 - (float)valInt / bits;
-        int valMutated;
-        if (_random.Prob(probIncrease))
-        {
-            valMutated = val + 1;
-        }
-        else
-        {
-            valMutated = val - 1;
-        }
-
-        valMutated = Math.Clamp(valMutated, min, max);
-        val = valMutated;
-    }
-
-    private void OnExecutePlantChangeStat(ref ExecuteEntityEffectEvent<PlantChangeStat> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        if (plantHolderComp.Seed == null)
-            return;
-
-        var member = plantHolderComp.Seed.GetType().GetField(args.Effect.TargetValue);
-
-        if (member == null)
-        {
-            _mutation.Log.Error(args.Effect.GetType().Name + " Error: Member " + args.Effect.TargetValue + " not found on " + plantHolderComp.Seed.GetType().Name + ". Did you misspell it?");
-            return;
-        }
-
-        var currentValObj = member.GetValue(plantHolderComp.Seed);
-        if (currentValObj == null)
-            return;
-
-        if (member.FieldType == typeof(float))
-        {
-            var floatVal = (float)currentValObj;
-            MutateFloat(ref floatVal, args.Effect.MinValue, args.Effect.MaxValue, args.Effect.Steps);
-            member.SetValue(plantHolderComp.Seed, floatVal);
-        }
-        else if (member.FieldType == typeof(int))
-        {
-            var intVal = (int)currentValObj;
-            MutateInt(ref intVal, (int)args.Effect.MinValue, (int)args.Effect.MaxValue, args.Effect.Steps);
-            member.SetValue(plantHolderComp.Seed, intVal);
-        }
-        else if (member.FieldType == typeof(bool))
-        {
-            var boolVal = (bool)currentValObj;
-            boolVal = !boolVal;
-            member.SetValue(plantHolderComp.Seed, boolVal);
-        }
-    }
-
-    private void OnExecutePlantCryoxadone(ref ExecuteEntityEffectEvent<PlantCryoxadone> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        var deviation = 0;
-        var seed = plantHolderComp.Seed;
-        if (seed == null)
-            return;
-        if (plantHolderComp.Age > seed.Maturation)
-            deviation = (int) Math.Max(seed.Maturation - 1, plantHolderComp.Age - _random.Next(7, 10));
-        else
-            deviation = (int) (seed.Maturation / seed.GrowthStages);
-        plantHolderComp.Age -= deviation;
-        plantHolderComp.LastProduce = plantHolderComp.Age;
-        plantHolderComp.SkipAging++;
-        plantHolderComp.ForceUpdate = true;
-    }
-
-    private void OnExecutePlantDestroySeeds(ref ExecuteEntityEffectEvent<PlantDestroySeeds> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
-            return;
-
-        if (plantHolderComp.Seed!.Seedless == false)
-        {
-            _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-            _popup.PopupEntity(
-                Loc.GetString("botany-plant-seedsdestroyed"),
-                args.Args.TargetEntity,
-                PopupType.SmallCaution
-            );
-            plantHolderComp.Seed.Seedless = true;
-        }
-    }
-
-    private void OnExecutePlantDiethylamine(ref ExecuteEntityEffectEvent<PlantDiethylamine> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
-            return;
-
-        if (_random.Prob(0.1f))
-        {
-            _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-            plantHolderComp.Seed!.Lifespan++;
-        }
-
-        if (_random.Prob(0.1f))
-        {
-            _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-            plantHolderComp.Seed!.Endurance++;
-        }
-    }
-
-    private void OnExecutePlantPhalanximine(ref ExecuteEntityEffectEvent<PlantPhalanximine> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
-            return;
-
-        plantHolderComp.Seed!.Viable = true;
-    }
-
-    private void OnExecutePlantRestoreSeeds(ref ExecuteEntityEffectEvent<PlantRestoreSeeds> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp, mustHaveMutableSeed: true))
-            return;
-
-        if (plantHolderComp.Seed!.Seedless)
-        {
-            _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-            _popup.PopupEntity(Loc.GetString("botany-plant-seedsrestored"), args.Args.TargetEntity);
-            plantHolderComp.Seed.Seedless = false;
-        }
-    }
-
-    private void OnExecuteRobustHarvest(ref ExecuteEntityEffectEvent<RobustHarvest> args)
-    {
-        if (!CanMetabolizePlant(args.Args.TargetEntity, out var plantHolderComp))
-            return;
-
-        if (plantHolderComp.Seed == null)
-            return;
-
-        if (plantHolderComp.Seed.Potency < args.Effect.PotencyLimit)
-        {
-            _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-            plantHolderComp.Seed.Potency = Math.Min(plantHolderComp.Seed.Potency + args.Effect.PotencyIncrease, args.Effect.PotencyLimit);
-
-            if (plantHolderComp.Seed.Potency > args.Effect.PotencySeedlessThreshold)
-            {
-                plantHolderComp.Seed.Seedless = true;
-            }
-        }
-        else if (plantHolderComp.Seed.Yield > 1 && _random.Prob(0.1f))
-        {
-            // Too much of a good thing reduces yield
-            _plantHolder.EnsureUniqueSeed(args.Args.TargetEntity, plantHolderComp);
-            plantHolderComp.Seed.Yield--;
-        }
-    }
-
-    private void OnExecuteAdjustTemperature(ref ExecuteEntityEffectEvent<AdjustTemperature> args)
-    {
-        if (TryComp(args.Args.TargetEntity, out TemperatureComponent? temp))
-        {
-            var amount = args.Effect.Amount;
-
-            if (args.Args is EntityEffectReagentArgs reagentArgs)
-            {
-                amount *= reagentArgs.Scale.Float();
-            }
-
-            _temperature.ChangeHeat(args.Args.TargetEntity, amount, true, temp);
-        }
-    }
-
-    private void OnExecuteAreaReactionEffect(ref ExecuteEntityEffectEvent<AreaReactionEffect> args)
-    {
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            if (reagentArgs.Source == null)
-                return;
-
-            var spreadAmount = (int) Math.Max(0, Math.Ceiling((reagentArgs.Quantity / args.Effect.OverflowThreshold).Float()));
-            var splitSolution = reagentArgs.Source.SplitSolution(reagentArgs.Source.Volume);
-            var transform = Comp<TransformComponent>(reagentArgs.TargetEntity);
-            var mapCoords = _xform.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform);
-
-            if (!_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
-                !_map.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef))
-            {
-                return;
-            }
-
-            if (_spreader.RequiresFloorToSpread(args.Effect.PrototypeId) && _turf.IsSpace(tileRef))
-                return;
-
-            var coords = _map.MapToGrid(gridUid, mapCoords);
-            var ent = Spawn(args.Effect.PrototypeId, coords.SnapToGrid());
-
-            _smoke.StartSmoke(ent, splitSolution, args.Effect.Duration, spreadAmount);
-
-            _audio.PlayPvs(args.Effect.Sound, reagentArgs.TargetEntity, AudioParams.Default.WithVariation(0.25f));
-            return;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-
-    private void OnExecuteCauseZombieInfection(ref ExecuteEntityEffectEvent<CauseZombieInfection> args)
-    {
-        EnsureComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
-        EnsureComp<PendingZombieComponent>(args.Args.TargetEntity);
-    }
-
-    private void OnExecuteChemCleanBloodstream(ref ExecuteEntityEffectEvent<ChemCleanBloodstream> args)
-    {
-        var cleanseRate = args.Effect.CleanseRate;
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            if (reagentArgs.Source == null || reagentArgs.Reagent == null)
-                return;
-
-            cleanseRate *= reagentArgs.Scale.Float();
-            _bloodstream.FlushChemicals(args.Args.TargetEntity, reagentArgs.Reagent, cleanseRate);
-        }
-        else
-        {
-            _bloodstream.FlushChemicals(args.Args.TargetEntity, null, cleanseRate);
-        }
-    }
-
-    private void OnExecuteChemVomit(ref ExecuteEntityEffectEvent<ChemVomit> args)
-    {
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-            if (reagentArgs.Scale != 1f)
-                return;
-
-        _vomit.Vomit(args.Args.TargetEntity, args.Effect.ThirstAmount, args.Effect.HungerAmount);
-    }
-
-    private void OnExecuteCreateEntityReactionEffect(ref ExecuteEntityEffectEvent<CreateEntityReactionEffect> args)
-    {
-        var transform = Comp<TransformComponent>(args.Args.TargetEntity);
-        var quantity = (int)args.Effect.Number;
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-            quantity *= reagentArgs.Quantity.Int();
-
-        for (var i = 0; i < quantity; i++)
-        {
-            var uid = Spawn(args.Effect.Entity, _xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform));
-            _xform.AttachToGridOrMap(uid);
-
-            // TODO figure out how to properly spawn inside of containers
-            // e.g. cheese:
-            // if the user is holding a bowl milk & enzyme, should drop to floor, not attached to the user.
-            // if reaction happens in a backpack, should insert cheese into backpack.
-            // --> if it doesn't fit, iterate through parent storage until it attaches to the grid (again, DON'T attach to players).
-            // if the reaction happens INSIDE a stomach? the bloodstream? I have no idea how to handle that.
-            // presumably having cheese materialize inside of your blood would have "disadvantages".
-        }
-    }
-
-    private void OnExecuteCreateGas(ref ExecuteEntityEffectEvent<CreateGas> args)
-    {
-        var tileMix = _atmosphere.GetContainingMixture(args.Args.TargetEntity, false, true);
-
-        if (tileMix != null)
-        {
-            if (args.Args is EntityEffectReagentArgs reagentArgs)
-            {
-                tileMix.AdjustMoles(args.Effect.Gas, reagentArgs.Quantity.Float() * args.Effect.Multiplier);
-            }
-            else
-            {
-                tileMix.AdjustMoles(args.Effect.Gas, args.Effect.Multiplier);
-            }
-        }
-    }
-
-    private void OnExecuteCureZombieInfection(ref ExecuteEntityEffectEvent<CureZombieInfection> args)
-    {
-        if (HasComp<IncurableZombieComponent>(args.Args.TargetEntity))
-            return;
-
-        RemComp<ZombifyOnDeathComponent>(args.Args.TargetEntity);
-        RemComp<PendingZombieComponent>(args.Args.TargetEntity);
-
-        if (args.Effect.Innoculate)
-        {
-            EnsureComp<ZombieImmuneComponent>(args.Args.TargetEntity);
-        }
-    }
-
-    private void OnExecuteEmote(ref ExecuteEntityEffectEvent<Emote> args)
-    {
-        if (args.Effect.EmoteId == null)
-            return;
-
-        if (args.Effect.ShowInChat)
-            _chat.TryEmoteWithChat(args.Args.TargetEntity, args.Effect.EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: args.Effect.Force);
-        else
-            _chat.TryEmoteWithoutChat(args.Args.TargetEntity, args.Effect.EmoteId);
-    }
-
-    private void OnExecuteEmpReactionEffect(ref ExecuteEntityEffectEvent<EmpReactionEffect> args)
-    {
-        var transform = Comp<TransformComponent>(args.Args.TargetEntity);
-
-        var range = args.Effect.EmpRangePerUnit;
-
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            range = MathF.Min((float) (reagentArgs.Quantity * args.Effect.EmpRangePerUnit), args.Effect.EmpMaxRange);
-        }
-
-        _emp.EmpPulse(_xform.GetMapCoordinates(args.Args.TargetEntity, xform: transform),
-            range,
-            args.Effect.EnergyConsumption,
-            args.Effect.DisableDuration);
-    }
-
-    private void OnExecuteExplosionReactionEffect(ref ExecuteEntityEffectEvent<ExplosionReactionEffect> args)
-    {
-        var intensity = args.Effect.IntensityPerUnit;
-
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            intensity = MathF.Min((float) reagentArgs.Quantity * args.Effect.IntensityPerUnit, args.Effect.MaxTotalIntensity);
-        }
-
-        _explosion.QueueExplosion(
-            args.Args.TargetEntity,
-            args.Effect.ExplosionType,
-            intensity,
-            args.Effect.IntensitySlope,
-            args.Effect.MaxIntensity,
-            args.Effect.TileBreakScale);
-    }
-
-    private void OnExecuteFlammableReaction(ref ExecuteEntityEffectEvent<FlammableReaction> args)
-    {
-        if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
-            return;
-
-        // Sets the multiplier for FireStacks to MultiplierOnExisting is 0 or greater and target already has FireStacks
-        var multiplier = flammable.FireStacks != 0f && args.Effect.MultiplierOnExisting >= 0 ? args.Effect.MultiplierOnExisting : args.Effect.Multiplier;
-        var quantity = 1f;
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            quantity = reagentArgs.Quantity.Float();
-            _flammable.AdjustFireStacks(args.Args.TargetEntity, quantity * multiplier, flammable);
-            if (reagentArgs.Reagent != null)
-                reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
-        }
-        else
-        {
-            _flammable.AdjustFireStacks(args.Args.TargetEntity, multiplier, flammable);
-        }
-    }
-
-    private void OnExecuteFlashReactionEffect(ref ExecuteEntityEffectEvent<FlashReactionEffect> args)
-    {
-        var transform = Comp<TransformComponent>(args.Args.TargetEntity);
-
-        var range = 1f;
-
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-            range = MathF.Min((float)(reagentArgs.Quantity * args.Effect.RangePerUnit), args.Effect.MaxRange);
-
-        _flash.FlashArea(
-            args.Args.TargetEntity,
-            null,
-            range,
-            args.Effect.Duration,
-            slowTo: args.Effect.SlowTo,
-            sound: args.Effect.Sound);
-
-        if (args.Effect.FlashEffectPrototype == null)
-            return;
-
-        var uid = EntityManager.SpawnEntity(args.Effect.FlashEffectPrototype, _xform.GetMapCoordinates(transform));
-        _xform.AttachToGridOrMap(uid);
-
-        if (!TryComp<PointLightComponent>(uid, out var pointLightComp))
-            return;
-
-        _pointLight.SetRadius(uid, MathF.Max(1.1f, range), pointLightComp);
-    }
-
-    private void OnExecuteIgnite(ref ExecuteEntityEffectEvent<Ignite> args)
-    {
-        if (!TryComp(args.Args.TargetEntity, out FlammableComponent? flammable))
-            return;
-
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            _flammable.Ignite(reagentArgs.TargetEntity, reagentArgs.OrganEntity ?? reagentArgs.TargetEntity, flammable: flammable);
-        }
-        else
-        {
-            _flammable.Ignite(args.Args.TargetEntity, args.Args.TargetEntity, flammable: flammable);
-        }
-    }
-
-    private void OnExecuteMakeSentient(ref ExecuteEntityEffectEvent<MakeSentient> args)
-    {
-        var uid = args.Args.TargetEntity;
-
-        // Let affected entities speak normally to make this effect different from, say, the "random sentience" event
-        // This also works on entities that already have a mind
-        // We call this before the mind check to allow things like player-controlled mice to be able to benefit from the effect
-        RemComp<ReplacementAccentComponent>(uid);
-        RemComp<MonkeyAccentComponent>(uid);
-
-        // Stops from adding a ghost role to things like people who already have a mind
-        if (TryComp<MindContainerComponent>(uid, out var mindContainer) && mindContainer.HasMind)
-        {
-            return;
-        }
-
-        // Don't add a ghost role to things that already have ghost roles
-        if (TryComp(uid, out GhostRoleComponent? ghostRole))
-        {
-            return;
-        }
-
-        ghostRole = AddComp<GhostRoleComponent>(uid);
-        EnsureComp<GhostTakeoverAvailableComponent>(uid);
-
-        var entityData = Comp<MetaDataComponent>(uid);
-        ghostRole.RoleName = entityData.EntityName;
-        ghostRole.RoleDescription = Loc.GetString("ghost-role-information-cognizine-description");
-    }
-
-    private void OnExecuteModifyBleedAmount(ref ExecuteEntityEffectEvent<ModifyBleedAmount> args)
-    {
-        if (TryComp<BloodstreamComponent>(args.Args.TargetEntity, out var blood))
-        {
-            var amt = args.Effect.Amount;
-            if (args.Args is EntityEffectReagentArgs reagentArgs) {
-                if (args.Effect.Scaled)
-                    amt *= reagentArgs.Quantity.Float();
-                amt *= reagentArgs.Scale.Float();
-            }
-
-            _bloodstream.TryModifyBleedAmount((args.Args.TargetEntity, blood), amt);
-        }
-    }
-
-    private void OnExecuteModifyBloodLevel(ref ExecuteEntityEffectEvent<ModifyBloodLevel> args)
-    {
-        if (TryComp<BloodstreamComponent>(args.Args.TargetEntity, out var blood))
-        {
-            var amt = args.Effect.Amount;
-            if (args.Args is EntityEffectReagentArgs reagentArgs)
-            {
-                if (args.Effect.Scaled)
-                    amt *= reagentArgs.Quantity;
-                amt *= reagentArgs.Scale;
-            }
-
-            _bloodstream.TryModifyBloodLevel((args.Args.TargetEntity, blood), amt);
-        }
-    }
-
-    private void OnExecuteModifyLungGas(ref ExecuteEntityEffectEvent<ModifyLungGas> args)
-    {
-        LungComponent? lung;
-        float amount = 1f;
-
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            if (!TryComp<LungComponent>(reagentArgs.OrganEntity, out var organLung))
-                return;
-            lung = organLung;
-            amount = reagentArgs.Quantity.Float();
-        }
-        else
-        {
-            if (!TryComp<LungComponent>(args.Args.TargetEntity, out var organLung)) //Likely needs to be modified to ensure it works correctly
-                return;
-            lung = organLung;
-        }
-
-        if (lung != null)
-        {
-            foreach (var (gas, ratio) in args.Effect.Ratios)
-            {
-                var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier;
-                if (quantity < 0)
-                    quantity = Math.Max(quantity, -lung.Air[(int) gas]);
-                lung.Air.AdjustMoles(gas, quantity);
-            }
-        }
-    }
-
-    private void OnExecuteOxygenate(ref ExecuteEntityEffectEvent<Oxygenate> args)
-    {
-        var multiplier = 1f;
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-        {
-            multiplier = reagentArgs.Quantity.Float();
-        }
-
-        if (TryComp<RespiratorComponent>(args.Args.TargetEntity, out var resp))
-        {
-            _respirator.UpdateSaturation(args.Args.TargetEntity, multiplier * args.Effect.Factor, resp);
-        }
-    }
-
-    private void OnExecutePlantMutateChemicals(ref ExecuteEntityEffectEvent<PlantMutateChemicals> args)
-    {
-        var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
-
-        if (plantholder.Seed == null)
-            return;
-
-        var chemicals = plantholder.Seed.Chemicals;
-        var randomChems = _protoManager.Index(RandomPickBotanyReagent).Fills;
-
-        // Add a random amount of a random chemical to this set of chemicals
-        if (randomChems != null)
-        {
-            var pick = _random.Pick<RandomFillSolution>(randomChems);
-            var chemicalId = _random.Pick(pick.Reagents);
-            var amount = _random.Next(1, (int)pick.Quantity);
-            var seedChemQuantity = new SeedChemQuantity();
-            if (chemicals.ContainsKey(chemicalId))
-            {
-                seedChemQuantity.Min = chemicals[chemicalId].Min;
-                seedChemQuantity.Max = chemicals[chemicalId].Max + amount;
-            }
-            else
-            {
-                seedChemQuantity.Min = 1;
-                seedChemQuantity.Max = 1 + amount;
-                seedChemQuantity.Inherent = false;
-            }
-            var potencyDivisor = (int)Math.Ceiling(100.0f / seedChemQuantity.Max);
-            seedChemQuantity.PotencyDivisor = potencyDivisor;
-            chemicals[chemicalId] = seedChemQuantity;
-        }
-    }
-
-    private void OnExecutePlantMutateConsumeGasses(ref ExecuteEntityEffectEvent<PlantMutateConsumeGasses> args)
-    {
-        var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
-
-        if (plantholder.Seed == null)
-            return;
-
-        var gasses = plantholder.Seed.ConsumeGasses;
-
-        // Add a random amount of a random gas to this gas dictionary
-        float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
-        Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
-        if (gasses.ContainsKey(gas))
-        {
-            gasses[gas] += amount;
-        }
-        else
-        {
-            gasses.Add(gas, amount);
-        }
-    }
-
-    private void OnExecutePlantMutateExudeGasses(ref ExecuteEntityEffectEvent<PlantMutateExudeGasses> args)
-    {
-        var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
-
-        if (plantholder.Seed == null)
-            return;
-
-        var gasses = plantholder.Seed.ExudeGasses;
-
-        // Add a random amount of a random gas to this gas dictionary
-        float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
-        Gas gas = _random.Pick(Enum.GetValues(typeof(Gas)).Cast<Gas>().ToList());
-        if (gasses.ContainsKey(gas))
-        {
-            gasses[gas] += amount;
-        }
-        else
-        {
-            gasses.Add(gas, amount);
-        }
-    }
-
-    private void OnExecutePlantMutateHarvest(ref ExecuteEntityEffectEvent<PlantMutateHarvest> args)
-    {
-        var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
-
-        if (plantholder.Seed == null)
-            return;
-
-        if (plantholder.Seed.HarvestRepeat == HarvestType.NoRepeat)
-            plantholder.Seed.HarvestRepeat = HarvestType.Repeat;
-        else if (plantholder.Seed.HarvestRepeat == HarvestType.Repeat)
-            plantholder.Seed.HarvestRepeat = HarvestType.SelfHarvest;
-    }
-
-    private void OnExecutePlantSpeciesChange(ref ExecuteEntityEffectEvent<PlantSpeciesChange> args)
-    {
-        var plantholder = Comp<PlantHolderComponent>(args.Args.TargetEntity);
-        if (plantholder.Seed == null)
-            return;
-
-        if (plantholder.Seed.MutationPrototypes.Count == 0)
-            return;
-
-        var targetProto = _random.Pick(plantholder.Seed.MutationPrototypes);
-        if (!_protoManager.TryIndex(targetProto, out SeedPrototype? protoSeed))
-        {
-            Log.Error($"Seed prototype could not be found: {targetProto}!");
-            return;
-        }
-
-        plantholder.Seed = plantholder.Seed.SpeciesChange(protoSeed);
-    }
-
-    private void OnExecutePolymorph(ref ExecuteEntityEffectEvent<PolymorphEffect> args)
-    {
-        // Make it into a prototype
-        EnsureComp<PolymorphableComponent>(args.Args.TargetEntity);
-        _polymorph.PolymorphEntity(args.Args.TargetEntity, args.Effect.PolymorphPrototype);
-    }
-
-    private void OnExecuteResetNarcolepsy(ref ExecuteEntityEffectEvent<ResetNarcolepsy> args)
-    {
-        if (args.Args is EntityEffectReagentArgs reagentArgs)
-            if (reagentArgs.Scale != 1f)
-                return;
-
-        _narcolepsy.AdjustNarcolepsyTimer(args.Args.TargetEntity, args.Effect.TimerReset);
-    }
-}
index 13695caff14f58e8a2acba6c5e89c88bc167b45d..7c9d02c5619ecc7a00c169fb4dfcc8a9ad3f0451 100644 (file)
@@ -21,7 +21,7 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 using System.Linq;
-
+using Content.Shared.EntityEffects.Effects.Solution;
 using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
 
 namespace Content.Server.Fluids.EntitySystems;
@@ -278,11 +278,10 @@ public sealed class SmokeSystem : EntitySystem
         {
             if (reagentQuantity.Quantity == FixedPoint2.Zero)
                 continue;
-            var reagentProto = _prototype.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
 
-            _reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentProto, reagentQuantity, transferSolution);
+            _reactive.ReactionEntity(entity, ReactionMethod.Touch, reagentQuantity);
             if (!blockIngestion)
-                _reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentProto, reagentQuantity, transferSolution);
+                _reactive.ReactionEntity(entity, ReactionMethod.Ingestion, reagentQuantity);
         }
 
         if (blockIngestion)
index 8fd088408c0f2e10c334d31474384c130537a144..930368255caaa22659ae5c63ad97084ba0e21a8f 100644 (file)
@@ -5,6 +5,7 @@ using System.Text.Json.Serialization;
 using Content.Shared.Chemistry.Reaction;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Damage;
+using Content.Shared.EntityConditions;
 using Content.Shared.EntityEffects;
 using Content.Shared.FixedPoint;
 using Robust.Shared.Prototypes;
@@ -42,7 +43,7 @@ public sealed class ChemistryJsonGenerator
             Converters =
             {
                 new UniversalJsonConverter<EntityEffect>(),
-                new UniversalJsonConverter<EntityEffectCondition>(),
+                new UniversalJsonConverter<EntityCondition>(),
                 new UniversalJsonConverter<ReagentEffectsEntry>(),
                 new UniversalJsonConverter<DamageSpecifier>(),
                 new FixedPointJsonConverter()
index 8b597ad61bc8c1b17d8146443c24ffb16e18fe32..59a212dbd2778f5915521d0da34c36f380f23fea 100644 (file)
@@ -76,7 +76,7 @@ public sealed class ReactionEntry
             proto.Products
                 .Select(x => KeyValuePair.Create(x.Key, x.Value.Float()))
                 .ToDictionary(x => x.Key, x => x.Value);
-        Effects = proto.Effects;
+        Effects = proto.Effects.ToList();
     }
 }
 
index c2d2614a0ac81305b24e468a553f7a433f26212f..1430f53cddbe1bef4b69f8786cc34e3d2c4b858a 100644 (file)
@@ -41,6 +41,7 @@ using Content.Shared.Stacks;
 using Content.Server.Construction.Components;
 using Content.Shared.Chat;
 using Content.Shared.Damage;
+using Content.Shared.Temperature.Components;
 using Robust.Shared.Utility;
 
 namespace Content.Server.Kitchen.EntitySystems
index 20dc1149180c1316ab1359d9d8b6aea4cef84738..8dab21902d936e0667a62c7a198669e2eafdaf8f 100644 (file)
@@ -5,12 +5,12 @@ using Content.Server.Medical.Components;
 using Content.Server.NodeContainer.EntitySystems;
 using Content.Server.NodeContainer.NodeGroups;
 using Content.Server.NodeContainer.Nodes;
-using Content.Server.Temperature.Components;
 using Content.Shared.Atmos;
 using Content.Shared.Body.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Medical.Cryogenics;
 using Content.Shared.MedicalScanner;
+using Content.Shared.Temperature.Components;
 using Content.Shared.UserInterface;
 using Robust.Shared.Containers;
 
index 11e4ed4fcf70acf8f8e288edb002afaf1c8a3a24..657ac3e636bc2340f18abe12f9f07b887d401ae1 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Server.Medical.Components;
 using Content.Server.PowerCell;
-using Content.Server.Temperature.Components;
 using Content.Shared.Body.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Damage;
@@ -13,6 +12,7 @@ using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.MedicalScanner;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Popups;
+using Content.Shared.Temperature.Components;
 using Content.Shared.Traits.Assorted;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio.Systems;
index 81f9415121b884add6a927a061637c3d7afcc636..e8fe9e4c39d503b5c6ac05b0ad120bd70a63e7e6 100644 (file)
@@ -5,7 +5,6 @@ using Content.Server.NPC.Queries.Considerations;
 using Content.Server.NPC.Queries.Curves;
 using Content.Server.NPC.Queries.Queries;
 using Content.Server.Nutrition.Components;
-using Content.Server.Temperature.Components;
 using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Damage;
 using Content.Shared.Examine;
@@ -30,6 +29,7 @@ using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 using Content.Shared.Atmos.Components;
 using System.Linq;
+using Content.Shared.Temperature.Components;
 
 namespace Content.Server.NPC.Systems;
 
index 6290e8231adca55240b03865524bc0eca2707f3c..f6a7536994defdab2cdef52b47d3a9d18d590d89 100644 (file)
@@ -10,14 +10,14 @@ using Content.Shared.Database;
 using Content.Shared.Inventory;
 using Content.Shared.Rejuvenate;
 using Content.Shared.Temperature;
-using Robust.Shared.Physics.Components;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Physics.Events;
 using Content.Shared.Projectiles;
+using Content.Shared.Temperature.Components;
+using Content.Shared.Temperature.Systems;
 
 namespace Content.Server.Temperature.Systems;
 
-public sealed class TemperatureSystem : EntitySystem
+public sealed class TemperatureSystem : SharedTemperatureSystem
 {
     [Dependency] private readonly AlertsSystem _alerts = default!;
     [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
@@ -125,8 +125,7 @@ public sealed class TemperatureSystem : EntitySystem
             true);
     }
 
-    public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false,
-        TemperatureComponent? temperature = null)
+    public override void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null)
     {
         if (!Resolve(uid, ref temperature, false))
             return;
@@ -161,16 +160,6 @@ public sealed class TemperatureSystem : EntitySystem
         ChangeHeat(uid, heat * temperature.AtmosTemperatureTransferEfficiency, temperature: temperature);
     }
 
-    public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null)
-    {
-        if (!Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0)
-        {
-            return Atmospherics.MinimumHeatCapacity;
-        }
-
-        return comp.SpecificHeat * physics.FixturesMass;
-    }
-
     private void OnInit(EntityUid uid, InternalTemperatureComponent comp, MapInitEvent args)
     {
         if (!TryComp<TemperatureComponent>(uid, out var temp))
index 4201af47f9e2895f4e80492979950e4d71d08454..70f11bb0605e4aed6afa541fb17058545f7ceced 100644 (file)
@@ -14,6 +14,6 @@ public sealed partial class TileEntityEffectComponent : Component
     /// <summary>
     /// List of effects that should be applied.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField]
+    [DataField]
     public List<EntityEffect> Effects = default!;
 }
index 4d866cb25480f1a36cedd62e2bd9c91c77f7526c..bd4aa789c2fef255851e27fbb143fda0ad287fcb 100644 (file)
@@ -1,13 +1,11 @@
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
 using Content.Shared.StepTrigger.Systems;
-using Content.Shared.Chemistry.Reagent;
 using Content.Shared.EntityEffects;
 
 namespace Content.Server.Tiles;
 
 public sealed class TileEntityEffectSystem : EntitySystem
 {
+    [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
 
     public override void Initialize()
     {
@@ -23,11 +21,7 @@ public sealed class TileEntityEffectSystem : EntitySystem
     private void OnTileStepTriggered(Entity<TileEntityEffectComponent> ent, ref StepTriggeredOffEvent args)
     {
         var otherUid = args.Tripper;
-        var effectArgs = new EntityEffectBaseArgs(otherUid, EntityManager);
 
-        foreach (var effect in ent.Comp.Effects)
-        {
-            effect.Effect(effectArgs);
-        }
+        _entityEffects.ApplyEffects(otherUid, ent.Comp.Effects.ToArray());
     }
 }
index 7cdcec78c2aff93fa68cf9971a28d204253ee2d5..720fec490aebfc4001c5a89c62d323058dd2cbd6 100644 (file)
@@ -13,7 +13,6 @@ using Content.Server.NPC.HTN;
 using Content.Server.NPC.Systems;
 using Content.Server.StationEvents.Components;
 using Content.Server.Speech.Components;
-using Content.Server.Temperature.Components;
 using Content.Shared.Body.Components;
 using Content.Shared.Chat;
 using Content.Shared.CombatMode;
@@ -44,6 +43,7 @@ using Robust.Shared.Player;
 using Robust.Shared.Prototypes;
 using Content.Shared.NPC.Prototypes;
 using Content.Shared.Roles;
+using Content.Shared.Temperature.Components;
 
 namespace Content.Server.Zombies;
 
index 58a41a5f7a47c11a373c6fe1c66974cd357d2c65..d905cc03aede83b1962713e17c0dbb9d4818fd1e 100644 (file)
@@ -81,9 +81,9 @@ public enum LogType
     ChemicalReaction = 17,
 
     /// <summary>
-    /// Reagent effects related interactions.
+    /// EntityEffect related interactions.
     /// </summary>
-    ReagentEffect = 18,
+    EntityEffect = 18,
 
     /// <summary>
     /// Canister valve was opened or closed.
index dd31de7722d152743dd2b4ab26a2d7eed9721f6c..ab1869f8b121a45b4b753a6b8805a60702bd7762 100644 (file)
@@ -22,7 +22,7 @@ public sealed partial class LungComponent : Component
     /// The name/key of the solution on this entity which these lungs act on.
     /// </summary>
     [DataField]
-    public string SolutionName = LungSystem.LungSolutionName;
+    public string SolutionName = "Lung";
 
     /// <summary>
     /// The solution on this entity that these lungs act on.
index 5f4c1ee4ef3e20a5bf1c81b64d36d19f9d503ff2..a097a7752afab8816756c9682bbbcb690d1ccf62 100644 (file)
@@ -1,11 +1,14 @@
+using Content.Shared.Atmos;
 using Content.Shared.Atmos.Components;
 using Content.Shared.Atmos.EntitySystems;
 using Content.Shared.Body.Components;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Shared.Atmos;
+using Content.Shared.Body.Prototypes;
 using Content.Shared.Chemistry.Components;
-using Content.Shared.Clothing;
+using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Inventory.Events;
+using Robust.Shared.Prototypes;
+using BreathToolComponent = Content.Shared.Atmos.Components.BreathToolComponent;
+using InternalsComponent = Content.Shared.Body.Components.InternalsComponent;
 
 namespace Content.Shared.Body.Systems;
 
@@ -15,8 +18,6 @@ public sealed class LungSystem : EntitySystem
     [Dependency] private readonly SharedInternalsSystem _internals = default!;
     [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
 
-    public static string LungSolutionName = "Lung";
-
     public override void Initialize()
     {
         base.Initialize();
@@ -53,6 +54,7 @@ public sealed class LungSystem : EntitySystem
         }
     }
 
+    // TODO: JUST METABOLIZE GASES DIRECTLY DON'T CONVERT TO REAGENTS!!! (Needs Metabolism refactor :B)
     public void GasToReagent(EntityUid uid, LungComponent lung)
     {
         if (!_solutionContainerSystem.ResolveSolution(uid, lung.SolutionName, ref lung.Solution, out var solution))
index 3b46d3913c397674784b6b673a431f5e11b70b2d..ee85fe65b715893f7c9c9cf00104e1a6cae23a09 100644 (file)
@@ -6,7 +6,8 @@ using Content.Shared.Chemistry.EntitySystems;
 using Content.Shared.Chemistry.Reaction;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Damage;
-using Content.Shared.EntityEffects.Effects;
+using Content.Shared.EntityEffects.Effects.Solution;
+using Content.Shared.EntityEffects.Effects.Transform;
 using Content.Shared.FixedPoint;
 using Content.Shared.Fluids;
 using Content.Shared.Forensics.Components;
@@ -149,7 +150,9 @@ public abstract class SharedBloodstreamSystem : EntitySystem
         {
             switch (effect)
             {
-                case CreateEntityReactionEffect: // Prevent entities from spawning in the bloodstream
+                // TODO: Rather than this, ReactionAttempt should allow systems to remove effects from the list before the reaction.
+                // TODO: I think there's a PR up on the repo for this and if there isn't I'll make one -Princess
+                case EntityEffects.Effects.EntitySpawning.SpawnEntity: // Prevent entities from spawning in the bloodstream
                 case AreaReactionEffect: // No spontaneous smoke or foam leaking out of blood vessels.
                     args.Cancelled = true;
                     return;
index 54210cf195f4585964b129cd7c72dde45d99cb10..671a30dec416beec4cb62f6ab842d6b64d9dabf6 100644 (file)
@@ -707,6 +707,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
     }
 
     // Thermal energy and temperature management.
+    // TODO: ENERGY CONSERVATION!!! Nuke this once we have HeatContainers and use methods which properly conserve energy and model heat transfer correctly!
 
     #region Thermal Energy and Temperature
 
@@ -763,6 +764,26 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
         UpdateChemicals(soln);
     }
 
+    /// <summary>
+    /// Same as <see cref="AddThermalEnergy"/> but clamps the value between two temperature values.
+    /// </summary>
+    /// <param name="soln">Solution we're adjusting the energy of</param>
+    /// <param name="thermalEnergy">Thermal energy we're adding or removing</param>
+    /// <param name="min">Min desired temperature</param>
+    /// <param name="max">Max desired temperature</param>
+    public void AddThermalEnergyClamped(Entity<SolutionComponent> soln, float thermalEnergy, float min, float max)
+    {
+        var solution = soln.Comp.Solution;
+
+        if (thermalEnergy == 0.0f)
+            return;
+
+        var heatCap = solution.GetHeatCapacity(PrototypeManager);
+        var deltaT = thermalEnergy / heatCap;
+        solution.Temperature = Math.Clamp(solution.Temperature + deltaT, min, max);
+        UpdateChemicals(soln);
+    }
+
     #endregion Thermal Energy and Temperature
 
     #region Event Handlers
index 351a51ecc1607cc5d39b5017a853218ad7f8dd95..a995ef90f40a70ffb85ab56cc42d17b345d5ff64 100644 (file)
@@ -31,6 +31,7 @@ namespace Content.Shared.Chemistry.Reaction
         [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+        [Dependency] private readonly SharedEntityEffectsSystem _entityEffects = default!;
 
         /// <summary>
         /// A cache of all reactions indexed by at most ONE of their required reactants.
@@ -205,27 +206,12 @@ namespace Content.Shared.Chemistry.Reaction
 
         private void OnReaction(Entity<SolutionComponent> soln, ReactionPrototype reaction, ReagentPrototype? reagent, FixedPoint2 unitReactions)
         {
-            var args = new EntityEffectReagentArgs(soln, EntityManager, null, soln.Comp.Solution, unitReactions, reagent, null, 1f);
-
             var posFound = _transformSystem.TryGetMapOrGridCoordinates(soln, out var gridPos);
 
             _adminLogger.Add(LogType.ChemicalReaction, reaction.Impact,
                 $"Chemical reaction {reaction.ID:reaction} occurred with strength {unitReactions:strength} on entity {ToPrettyString(soln):metabolizer} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found]")}");
 
-            foreach (var effect in reaction.Effects)
-            {
-                if (!effect.ShouldApply(args))
-                    continue;
-
-                if (effect.ShouldLog)
-                {
-                    var entity = args.TargetEntity;
-                    _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
-                        $"Reaction effect {effect.GetType().Name:effect} of reaction {reaction.ID:reaction} applied on entity {ToPrettyString(entity):entity} at Pos:{(posFound ? $"{gridPos:coordinates}" : "[Grid or Map not Found")}");
-                }
-
-                effect.Effect(args);
-            }
+            _entityEffects.ApplyEffects(soln, reaction.Effects, unitReactions.Float());
 
             // Someday, some brave soul will thread through an optional actor
             // argument in from every call of OnReaction up, all just to pass
index 4bbb972572372677280fb3ee79fff3b8357be587..c9a24ec55021e06119090e79bd54b85085bd7468 100644 (file)
@@ -60,7 +60,7 @@ namespace Content.Shared.Chemistry.Reaction
         /// <summary>
         /// Effects to be triggered when the reaction occurs.
         /// </summary>
-        [DataField("effects")] public List<EntityEffect> Effects = new();
+        [DataField("effects")] public EntityEffect[] Effects = [];
 
         /// <summary>
         /// How dangerous is this effect? Stuff like bicaridine should be low, while things like methamphetamine
index cabdee93c1a30af799d54f7d38685d1d3d8dee8d..89fcca900eba4ec397f313d5a3b4bee943ccdfdd 100644 (file)
@@ -34,7 +34,7 @@ public sealed partial class ReactiveReagentEffectEntry
     public HashSet<string>? Reagents = null;
 
     [DataField("effects", required: true)]
-    public List<EntityEffect> Effects = default!;
+    public EntityEffect[] Effects = default!;
 
     [DataField("groups", readOnly: true, serverOnly: true,
         customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<ReactionMethod>, ReactiveGroupPrototype>))]
index 6306537324efc154e76a0c9cd5f454845c383942..2ffb848f8a717b0c38a1f4655183d55d0ba7186b 100644 (file)
-using Content.Shared.Administration.Logs;
 using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.Reaction;
 using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Database;
-using Content.Shared.EntityEffects;
+using Content.Shared.FixedPoint;
 using JetBrains.Annotations;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
 
 namespace Content.Shared.Chemistry;
 
 [UsedImplicitly]
 public sealed class ReactiveSystem : EntitySystem
 {
-    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-    [Dependency] private readonly IRobustRandom _robustRandom = default!;
-    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
 
     public void DoEntityReaction(EntityUid uid, Solution solution, ReactionMethod method)
     {
         foreach (var reagent in solution.Contents.ToArray())
         {
-            ReactionEntity(uid, method, reagent, solution);
+            ReactionEntity(uid, method, reagent);
         }
     }
 
-    public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentQuantity reagentQuantity, Solution? source)
+    public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentQuantity reagentQuantity)
     {
-        // We throw if the reagent specified doesn't exist.
-        var proto = _prototypeManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
-        ReactionEntity(uid, method, proto, reagentQuantity, source);
-    }
+        if (reagentQuantity.Quantity == FixedPoint2.Zero)
+            return;
 
-    public void ReactionEntity(EntityUid uid, ReactionMethod method, ReagentPrototype proto,
-        ReagentQuantity reagentQuantity, Solution? source)
-    {
-        if (!TryComp(uid, out ReactiveComponent? reactive))
+        // We throw if the reagent specified doesn't exist.
+        if (!_proto.Resolve<ReagentPrototype>(reagentQuantity.Reagent.Prototype, out var proto))
             return;
 
-        // custom event for bypassing reactivecomponent stuff
-        var ev = new ReactionEntityEvent(method, proto, reagentQuantity, source);
+        var ev = new ReactionEntityEvent(method, reagentQuantity, proto);
         RaiseLocalEvent(uid, ref ev);
-
-        // If we have a source solution, use the reagent quantity we have left. Otherwise, use the reaction volume specified.
-        var args = new EntityEffectReagentArgs(uid, EntityManager, null, source, source?.GetReagentQuantity(reagentQuantity.Reagent) ?? reagentQuantity.Quantity, proto, method, 1f);
-
-        // First, check if the reagent wants to apply any effects.
-        if (proto.ReactiveEffects != null && reactive.ReactiveGroups != null)
-        {
-            foreach (var (key, val) in proto.ReactiveEffects)
-            {
-                if (!val.Methods.Contains(method))
-                    continue;
-
-                if (!reactive.ReactiveGroups.ContainsKey(key))
-                    continue;
-
-                if (!reactive.ReactiveGroups[key].Contains(method))
-                    continue;
-
-                foreach (var effect in val.Effects)
-                {
-                    if (!effect.ShouldApply(args, _robustRandom))
-                        continue;
-
-                    if (effect.ShouldLog)
-                    {
-                        var entity = args.TargetEntity;
-                        _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
-                            $"Reactive effect {effect.GetType().Name:effect} of reagent {proto.ID:reagent} with method {method} applied on entity {ToPrettyString(entity):entity} at {Transform(entity).Coordinates:coordinates}");
-                    }
-
-                    effect.Effect(args);
-                }
-            }
-        }
-
-        // Then, check if the prototype has any effects it can apply as well.
-        if (reactive.Reactions != null)
-        {
-            foreach (var entry in reactive.Reactions)
-            {
-                if (!entry.Methods.Contains(method))
-                    continue;
-
-                if (entry.Reagents != null && !entry.Reagents.Contains(proto.ID))
-                    continue;
-
-                foreach (var effect in entry.Effects)
-                {
-                    if (!effect.ShouldApply(args, _robustRandom))
-                        continue;
-
-                    if (effect.ShouldLog)
-                    {
-                        var entity = args.TargetEntity;
-                        _adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
-                            $"Reactive effect {effect.GetType().Name:effect} of {ToPrettyString(entity):entity} using reagent {proto.ID:reagent} with method {method} at {Transform(entity).Coordinates:coordinates}");
-                    }
-
-                    effect.Effect(args);
-                }
-            }
-        }
     }
 }
 public enum ReactionMethod
@@ -113,9 +40,4 @@ Ingestion,
 }
 
 [ByRefEvent]
-public readonly record struct ReactionEntityEvent(
-    ReactionMethod Method,
-    ReagentPrototype Reagent,
-    ReagentQuantity ReagentQuantity,
-    Solution? Source
-);
+public readonly record struct ReactionEntityEvent(ReactionMethod Method, ReagentQuantity ReagentQuantity, ReagentPrototype Reagent);
index b999d8df61594b2a2180b46b874f360bb61d81e1..3b16b577cbc6c2e05bb1a278756ee1456f02d801 100644 (file)
@@ -2,21 +2,17 @@ using System.Collections.Frozen;
 using System.Linq;
 using Content.Shared.FixedPoint;
 using System.Text.Json.Serialization;
-using Content.Shared.Administration.Logs;
 using Content.Shared.Body.Prototypes;
-using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Reaction;
 using Content.Shared.Contraband;
 using Content.Shared.EntityEffects;
-using Content.Shared.Database;
+using Content.Shared.Localizations;
 using Content.Shared.Nutrition;
-using Content.Shared.Prototypes;
 using Content.Shared.Roles;
 using Content.Shared.Slippery;
 using Robust.Shared.Audio;
 using Robust.Shared.Map;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
 using Robust.Shared.Utility;
@@ -190,6 +186,7 @@ namespace Content.Shared.Chemistry.Reagent
         [DataField]
         public SoundSpecifier FootstepSound = new SoundCollectionSpecifier("FootstepPuddle");
 
+        // TODO: Reaction tile doesn't work properly and destroys reagents way too quickly
         public FixedPoint2 ReactionTile(TileRef tile, FixedPoint2 reactVolume, IEntityManager entityManager, List<ReagentData>? data)
         {
             var removed = FixedPoint2.Zero;
@@ -211,33 +208,32 @@ namespace Content.Shared.Chemistry.Reagent
             return removed;
         }
 
-        public void ReactionPlant(EntityUid? plantHolder,
-            ReagentQuantity amount,
-            Solution solution,
-            EntityManager entityManager,
-            IRobustRandom random,
-            ISharedAdminLogManager logger)
+        public IEnumerable<string> GuidebookReagentEffectsDescription(IPrototypeManager prototype, IEntitySystemManager entSys, IEnumerable<EntityEffect> effects, FixedPoint2? metabolism = null)
         {
-            if (plantHolder == null)
-                return;
+            return effects.Select(x => GuidebookReagentEffectDescription(prototype, entSys, x, metabolism))
+                .Where(x => x is not null)
+                .Select(x => x!)
+                .ToArray();
+        }
 
-            var args = new EntityEffectReagentArgs(plantHolder.Value, entityManager, null, solution, amount.Quantity, this, null, 1f);
-            foreach (var plantMetabolizable in PlantMetabolisms)
-            {
-                if (!plantMetabolizable.ShouldApply(args, random))
-                    continue;
-
-                if (plantMetabolizable.ShouldLog)
-                {
-                    var entity = args.TargetEntity;
-                    logger.Add(
-                        LogType.ReagentEffect,
-                        plantMetabolizable.LogImpact,
-                        $"Plant metabolism effect {plantMetabolizable.GetType().Name:effect} of reagent {ID} applied on entity {entity}");
-                }
-
-                plantMetabolizable.Effect(args);
-            }
+        public string? GuidebookReagentEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys, EntityEffect effect, FixedPoint2? metabolism)
+        {
+            if (effect.EntityEffectGuidebookText(prototype, entSys) is not { } description)
+                return null;
+
+            var quantity = metabolism == null ? 0f : (double) (effect.MinScale * metabolism);
+
+            return Loc.GetString(
+                "guidebook-reagent-effect-description",
+                ("reagent", LocalizedName),
+                ("quantity", quantity),
+                ("effect", description),
+                ("chance", effect.Probability),
+                ("conditionCount", effect.Conditions?.Length ?? 0),
+                ("conditions",
+                    ContentLocalizationManager.FormatList(
+                        effect.Conditions?.Select(x => x.EntityConditionGuidebookText(prototype)).ToList() ?? new List<string>()
+                    )));
         }
     }
 
@@ -246,6 +242,7 @@ namespace Content.Shared.Chemistry.Reagent
     {
         public string ReagentPrototype;
 
+        // TODO: Kill Metabolism groups!
         public Dictionary<ProtoId<MetabolismGroupPrototype>, ReagentEffectsGuideEntry>? GuideEntries;
 
         public List<string>? PlantMetabolisms = null;
@@ -254,15 +251,12 @@ namespace Content.Shared.Chemistry.Reagent
         {
             ReagentPrototype = proto.ID;
             GuideEntries = proto.Metabolisms?
-                .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys)))
+                .Select(x => (x.Key, x.Value.MakeGuideEntry(prototype, entSys, proto)))
                 .ToDictionary(x => x.Key, x => x.Item2);
             if (proto.PlantMetabolisms.Count > 0)
             {
-                PlantMetabolisms = new List<string>(proto.PlantMetabolisms
-                    .Select(x => x.GuidebookEffectDescription(prototype, entSys))
-                    .Where(x => x is not null)
-                    .Select(x => x!)
-                    .ToArray());
+                PlantMetabolisms =
+                    new List<string>(proto.GuidebookReagentEffectsDescription(prototype, entSys, proto.PlantMetabolisms));
             }
         }
     }
@@ -285,14 +279,11 @@ namespace Content.Shared.Chemistry.Reagent
         [DataField("effects", required: true)]
         public EntityEffect[] Effects = default!;
 
-        public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys)
+        public string EntityEffectFormat => "guidebook-reagent-effect-description";
+
+        public ReagentEffectsGuideEntry MakeGuideEntry(IPrototypeManager prototype, IEntitySystemManager entSys, ReagentPrototype proto)
         {
-            return new ReagentEffectsGuideEntry(MetabolismRate,
-                Effects
-                    .Select(x => x.GuidebookEffectDescription(prototype, entSys)) // hate.
-                    .Where(x => x is not null)
-                    .Select(x => x!)
-                    .ToArray());
+            return new ReagentEffectsGuideEntry(MetabolismRate, proto.GuidebookReagentEffectsDescription(prototype, entSys, Effects, MetabolismRate).ToArray());
         }
     }
 
diff --git a/Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs b/Content.Shared/EntityConditions/Conditions/Body/BreathingEntityCondition.cs
new file mode 100644 (file)
index 0000000..d19d326
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class BreathingCondition : EntityConditionBase<BreathingCondition>
+{
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-breathing", ("isBreathing", !Inverted));
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Body/HungerEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..f9867b2
--- /dev/null
@@ -0,0 +1,33 @@
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <summary>
+/// Returns true if this entity's hunger is within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TotalHungerEntityConditionSystem : EntityConditionSystem<HungerComponent, HungerCondition>
+{
+    [Dependency] private readonly HungerSystem _hunger = default!;
+
+    protected override void Condition(Entity<HungerComponent> entity, ref EntityConditionEvent<HungerCondition> args)
+    {
+        var total = _hunger.GetHunger(entity.Comp);
+        args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class HungerCondition : EntityConditionBase<HungerCondition>
+{
+    [DataField]
+    public float Min;
+
+    [DataField]
+    public float Max = float.PositiveInfinity;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-total-hunger", ("max", float.IsPositiveInfinity(Max) ? int.MaxValue : Max), ("min", Min));
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Body/InternalsEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..e87c54a
--- /dev/null
@@ -0,0 +1,23 @@
+using Content.Shared.Body.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <summary>
+/// Returns true if this entity is using internals. False if they are not or cannot use internals.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class InternalsOnEntityConditionSystem : EntityConditionSystem<InternalsComponent, InternalsCondition>
+{
+    protected override void Condition(Entity<InternalsComponent> entity, ref EntityConditionEvent<InternalsCondition> args)
+    {
+        args.Result = entity.Comp.GasTankEntity != null;
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class InternalsCondition : EntityConditionBase<InternalsCondition>
+{
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", !Inverted));
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs b/Content.Shared/EntityConditions/Conditions/Body/MetabolizerTypeEntityCondition.cs
new file mode 100644 (file)
index 0000000..2196d27
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Shared.Body.Prototypes;
+using Content.Shared.Localizations;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class MetabolizerTypeCondition : EntityConditionBase<MetabolizerTypeCondition>
+{
+    [DataField(required: true)]
+    public ProtoId<MetabolizerTypePrototype>[] Type = default!;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+    {
+        var typeList = new List<string>();
+
+        foreach (var type in Type)
+        {
+            if (!prototype.Resolve(type, out var proto))
+                continue;
+
+            typeList.Add(proto.LocalizedName);
+        }
+
+        var names = ContentLocalizationManager.FormatListToOr(typeList);
+
+        return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
+            ("name", names),
+            ("shouldhave", !Inverted));
+    }
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Body/MobStateEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..d00481d
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Body;
+
+/// <summary>
+/// Returns true if this entity's current mob state matches the condition's specified mob state.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class MobStateEntityConditionSystem : EntityConditionSystem<MobStateComponent, MobStateCondition>
+{
+    protected override void Condition(Entity<MobStateComponent> entity, ref EntityConditionEvent<MobStateCondition> args)
+    {
+        if (entity.Comp.CurrentState == args.Condition.Mobstate)
+            args.Result = true;
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class MobStateCondition : EntityConditionBase<MobStateCondition>
+{
+    [DataField]
+    public MobState Mobstate = MobState.Alive;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate));
+}
diff --git a/Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/JobEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..e07989d
--- /dev/null
@@ -0,0 +1,59 @@
+using System.Linq;
+using Content.Shared.Localizations;
+using Content.Shared.Mind;
+using Content.Shared.Mind.Components;
+using Content.Shared.Roles;
+using Content.Shared.Roles.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity has any of the specified jobs. False if the entity has no mind, none of the specified jobs, or is jobless.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasJobEntityConditionSystem : EntityConditionSystem<MindContainerComponent, JobCondition>
+{
+    protected override void Condition(Entity<MindContainerComponent> entity, ref EntityConditionEvent<JobCondition> args)
+    {
+        // We need a mind in our mind container...
+        if (!TryComp<MindComponent>(entity.Comp.Mind, out var mind))
+            return;
+
+        foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
+        {
+            if (!HasComp<JobRoleComponent>(roleId))
+                continue;
+
+            if (!TryComp<MindRoleComponent>(roleId, out var mindRole))
+            {
+                Log.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}");
+                continue;
+            }
+
+            if (mindRole.JobPrototype == null)
+            {
+                Log.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}");
+                continue;
+            }
+
+            if (!args.Condition.Jobs.Contains(mindRole.JobPrototype.Value))
+                continue;
+
+            args.Result = true;
+            return;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class JobCondition : EntityConditionBase<JobCondition>
+{
+    [DataField(required: true)] public List<ProtoId<JobPrototype>> Jobs = [];
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+    {
+        var localizedNames = Jobs.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
+        return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
+    }
+}
diff --git a/Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/ReagentEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..c49c9a3
--- /dev/null
@@ -0,0 +1,44 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class ReagentThresholdEntityConditionSystem : EntityConditionSystem<SolutionComponent, ReagentCondition>
+{
+    protected override void Condition(Entity<SolutionComponent> entity, ref EntityConditionEvent<ReagentCondition> args)
+    {
+        var quant = entity.Comp.Solution.GetTotalPrototypeQuantity(args.Condition.Reagent);
+
+        args.Result = quant >= args.Condition.Min && quant <= args.Condition.Max;
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class ReagentCondition : EntityConditionBase<ReagentCondition>
+{
+    [DataField]
+    public FixedPoint2 Min = FixedPoint2.Zero;
+
+    [DataField]
+    public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+    [DataField(required: true)]
+    public ProtoId<ReagentPrototype> Reagent;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+    {
+        if (!prototype.Resolve(Reagent, out var reagentProto))
+            return String.Empty;
+
+        return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
+            ("reagent", reagentProto.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")),
+            ("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()),
+            ("min", Min.Float()));
+    }
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Tags/HasAllTagsEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..99d7206
--- /dev/null
@@ -0,0 +1,43 @@
+using Content.Shared.Localizations;
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Tags;
+
+/// <summary>
+/// Returns true if this entity has all the listed tags.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasAllTagsEntityConditionSystem : EntityConditionSystem<TagComponent, AllTagsCondition>
+{
+    [Dependency] private readonly TagSystem _tag = default!;
+
+    protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<AllTagsCondition> args)
+    {
+        args.Result = _tag.HasAllTags(entity.Comp, args.Condition.Tags);
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class AllTagsCondition : EntityConditionBase<AllTagsCondition>
+{
+    [DataField(required: true)]
+    public ProtoId<TagPrototype>[] Tags = [];
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+    {
+        var tagList = new List<string>();
+
+        foreach (var type in Tags)
+        {
+            if (!prototype.Resolve(type, out var proto))
+                continue;
+
+            tagList.Add(proto.ID);
+        }
+
+        var names = ContentLocalizationManager.FormatList(tagList);
+
+        return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", names), ("invert", Inverted));
+    }
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Tags/HasAnyTagEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..3513353
--- /dev/null
@@ -0,0 +1,43 @@
+using Content.Shared.Localizations;
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Tags;
+
+/// <summary>
+/// Returns true if this entity have any of the listed tags.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasAnyTagEntityConditionSystem : EntityConditionSystem<TagComponent, AnyTagCondition>
+{
+    [Dependency] private readonly TagSystem _tag = default!;
+
+    protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<AnyTagCondition> args)
+    {
+        args.Result = _tag.HasAnyTag(entity.Comp, args.Condition.Tags);
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class AnyTagCondition : EntityConditionBase<AnyTagCondition>
+{
+    [DataField(required: true)]
+    public ProtoId<TagPrototype>[] Tags = [];
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype)
+    {
+        var tagList = new List<string>();
+
+        foreach (var type in Tags)
+        {
+            if (!prototype.Resolve(type, out var proto))
+                continue;
+
+            tagList.Add(proto.ID);
+        }
+
+        var names = ContentLocalizationManager.FormatListToOr(tagList);
+
+        return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", names), ("invert", Inverted));
+    }
+}
diff --git a/Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/Tags/HasTagEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..9b67f38
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Shared.Tag;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions.Tags;
+
+/// <summary>
+/// Returns true if this entity has the listed tag.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class HasTagEntityConditionSystem : EntityConditionSystem<TagComponent, TagCondition>
+{
+    [Dependency] private readonly TagSystem _tag = default!;
+
+    protected override void Condition(Entity<TagComponent> entity, ref EntityConditionEvent<TagCondition> args)
+    {
+        args.Result = _tag.HasTag(entity.Comp, args.Condition.Tag);
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TagCondition : EntityConditionBase<TagCondition>
+{
+    [DataField(required: true)]
+    public ProtoId<TagPrototype> Tag;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Inverted));
+}
diff --git a/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TemperatureEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..6585f3b
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Temperature.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity has an amount of reagent in it within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TemperatureEntityConditionSystem : EntityConditionSystem<TemperatureComponent, TemperatureCondition>
+{
+    protected override void Condition(Entity<TemperatureComponent> entity, ref EntityConditionEvent<TemperatureCondition> args)
+    {
+        if (entity.Comp.CurrentTemperature >= args.Condition.Min && entity.Comp.CurrentTemperature <= args.Condition.Max)
+            args.Result = true;
+    }
+}
+
+/// <summary>
+/// Returns true if this solution entity has an amount of reagent in it within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class SolutionTemperatureEntityConditionSystem : EntityConditionSystem<SolutionComponent, TemperatureCondition>
+{
+    protected override void Condition(Entity<SolutionComponent> entity, ref EntityConditionEvent<TemperatureCondition> args)
+    {
+        if (entity.Comp.Solution.Temperature >= args.Condition.Min && entity.Comp.Solution.Temperature <= args.Condition.Max)
+            args.Result = true;
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TemperatureCondition : EntityConditionBase<TemperatureCondition>
+{
+    /// <summary>
+    /// Minimum allowed temperature
+    /// </summary>
+    [DataField]
+    public float Min = 0;
+
+    /// <summary>
+    /// Maximum allowed temperature
+    /// </summary>
+    [DataField]
+    public float Max = float.PositiveInfinity;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-body-temperature",
+            ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
+            ("min", Min));
+}
diff --git a/Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TemplateEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..fbb659b
--- /dev/null
@@ -0,0 +1,20 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+///<summary>
+/// A basic summary of this condition.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TemplateEntityConditionSystem : EntityConditionSystem<MetaDataComponent, TemplateCondition>
+{
+    protected override void Condition(Entity<MetaDataComponent> entity, ref EntityConditionEvent<TemplateCondition> args)
+    {
+        // Condition goes here.
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TemplateCondition : EntityConditionBase<TemplateCondition>
+{
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) => String.Empty;
+}
diff --git a/Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs b/Content.Shared/EntityConditions/Conditions/TotalDamageEntityConditionSystem.cs
new file mode 100644 (file)
index 0000000..e710e07
--- /dev/null
@@ -0,0 +1,33 @@
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions.Conditions;
+
+/// <summary>
+/// Returns true if this entity can take damage and if its total damage is within a specified minimum and maximum.
+/// </summary>
+/// <inheritdoc cref="EntityConditionSystem{T, TCondition}"/>
+public sealed partial class TotalDamageEntityConditionSystem : EntityConditionSystem<DamageableComponent, TotalDamageCondition>
+{
+    protected override void Condition(Entity<DamageableComponent> entity, ref EntityConditionEvent<TotalDamageCondition> args)
+    {
+        var total = entity.Comp.TotalDamage;
+        args.Result = total >= args.Condition.Min && total <= args.Condition.Max;
+    }
+}
+
+/// <inheritdoc cref="EntityCondition"/>
+public sealed partial class TotalDamageCondition : EntityConditionBase<TotalDamageCondition>
+{
+    [DataField]
+    public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+    [DataField]
+    public FixedPoint2 Min = FixedPoint2.Zero;
+
+    public override string EntityConditionGuidebookText(IPrototypeManager prototype) =>
+        Loc.GetString("reagent-effect-condition-guidebook-total-damage",
+            ("max", Max == FixedPoint2.MaxValue ? int.MaxValue : Max.Float()),
+            ("min", Min.Float()));
+}
diff --git a/Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs b/Content.Shared/EntityConditions/SharedEntityConditionsSystem.cs
new file mode 100644 (file)
index 0000000..090a422
--- /dev/null
@@ -0,0 +1,152 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityConditions;
+
+/// <summary>
+/// This handles entity effects.
+/// Specifically it handles the receiving of events for causing entity effects, and provides
+/// public API for other systems to take advantage of entity effects.
+/// </summary>
+public sealed partial class SharedEntityConditionsSystem : EntitySystem, IEntityConditionRaiser
+{
+    /// <summary>
+    /// Checks a list of conditions to verify that they all return true.
+    /// </summary>
+    /// <param name="target">Target entity we're checking conditions on</param>
+    /// <param name="conditions">Conditions we're checking</param>
+    /// <returns>Returns true if all conditions return true, false if any fail</returns>
+    public bool TryConditions(EntityUid target, EntityCondition[]? conditions)
+    {
+        // If there's no conditions we can't fail any of them...
+        if (conditions == null)
+            return true;
+
+        foreach (var condition in conditions)
+        {
+            if (!TryCondition(target, condition))
+                return false;
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Checks a list of conditions to see if any are true.
+    /// </summary>
+    /// <param name="target">Target entity we're checking conditions on</param>
+    /// <param name="conditions">Conditions we're checking</param>
+    /// <returns>Returns true if any conditions return true</returns>
+    public bool TryAnyCondition(EntityUid target, EntityCondition[]? conditions)
+    {
+        // If there's no conditions we can't meet any of them...
+        if (conditions == null)
+            return false;
+
+        foreach (var condition in conditions)
+        {
+            if (TryCondition(target, condition))
+                return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    /// Checks a single <see cref="EntityCondition"/> on an entity.
+    /// </summary>
+    /// <param name="target">Target entity we're checking conditions on</param>
+    /// <param name="condition">Condition we're checking</param>
+    /// <returns>Returns true if we meet the condition and false otherwise</returns>
+    public bool TryCondition(EntityUid target, EntityCondition condition)
+    {
+        return condition.Inverted != condition.RaiseEvent(target, this);
+    }
+
+    /// <summary>
+    /// Raises a condition to an entity. You should not be calling this unless you know what you're doing.
+    /// </summary>
+    public bool RaiseConditionEvent<T>(EntityUid target, T effect) where T : EntityConditionBase<T>
+    {
+        var effectEv = new EntityConditionEvent<T>(effect);
+        RaiseLocalEvent(target, ref effectEv);
+        return effectEv.Result;
+    }
+}
+
+/// <summary>
+/// This is a basic abstract entity effect containing all the data an entity effect needs to affect entities with effects...
+/// </summary>
+/// <typeparam name="T">The Component that is required for the effect</typeparam>
+/// <typeparam name="TCon">The Condition we're testing</typeparam>
+public abstract partial class EntityConditionSystem<T, TCon> : EntitySystem where T : Component where TCon : EntityConditionBase<TCon>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<T, EntityConditionEvent<TCon>>(Condition);
+    }
+    protected abstract void Condition(Entity<T> entity, ref EntityConditionEvent<TCon> args);
+}
+
+/// <summary>
+/// Used to raise an EntityCondition without losing the type of condition.
+/// </summary>
+public interface IEntityConditionRaiser
+{
+    bool RaiseConditionEvent<T>(EntityUid target, T effect) where T : EntityConditionBase<T>;
+}
+
+/// <summary>
+/// Used to store an <see cref="EntityCondition"/> so it can be raised without losing the type of the condition.
+/// </summary>
+/// <typeparam name="T">The Condition wer are raising.</typeparam>
+public abstract partial class EntityConditionBase<T> : EntityCondition where T : EntityConditionBase<T>
+{
+    public override bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser)
+    {
+        if (this is not T type)
+            return false;
+
+        // If the result of the event matches the result we're looking for then we pass.
+        return raiser.RaiseConditionEvent(target, type);
+    }
+}
+
+/// <summary>
+/// A basic condition which can be checked for on an entity via events.
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class EntityCondition
+{
+    public abstract bool RaiseEvent(EntityUid target, IEntityConditionRaiser raiser);
+
+    /// <summary>
+    /// If true, invert the result. So false returns true and true returns false!
+    /// </summary>
+    [DataField]
+    public bool Inverted;
+
+    /// <summary>
+    /// A basic description of this condition, which displays in the guidebook.
+    /// </summary>
+    public abstract string EntityConditionGuidebookText(IPrototypeManager prototype);
+}
+
+/// <summary>
+/// An Event carrying an entity effect.
+/// </summary>
+/// <param name="Condition">The Condition we're checking</param>
+[ByRefEvent]
+public record struct EntityConditionEvent<T>(T Condition) where T : EntityConditionBase<T>
+{
+    /// <summary>
+    /// The result of our check, defaults to false if nothing handles it.
+    /// </summary>
+    [DataField]
+    public bool Result;
+
+    /// <summary>
+    /// The Condition being raised in this event
+    /// </summary>
+    public readonly T Condition = Condition;
+}
diff --git a/Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs b/Content.Shared/EntityEffects/EffectConditions/BodyTemperature.cs
deleted file mode 100644 (file)
index 351e4ee..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-///     Requires the target entity to be above or below a certain temperature.
-///     Used for things like cryoxadone and pyroxadone.
-/// </summary>
-public sealed partial class Temperature : EventEntityEffectCondition<Temperature>
-{
-    [DataField]
-    public float Min = 0;
-
-    [DataField]
-    public float Max = float.PositiveInfinity;
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-body-temperature",
-            ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
-            ("min", Min));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs b/Content.Shared/EntityEffects/EffectConditions/BreathingCondition.cs
deleted file mode 100644 (file)
index 9de1bfd..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-///     Condition for if the entity is successfully breathing.
-/// </summary>
-public sealed partial class Breathing : EventEntityEffectCondition<Breathing>
-{
-    /// <summary>
-    ///     If true, the entity must not have trouble breathing to pass.
-    /// </summary>
-    [DataField]
-    public bool IsBreathing = true;
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-breathing",
-                            ("isBreathing", IsBreathing));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs b/Content.Shared/EntityEffects/EffectConditions/HasTagCondition.cs
deleted file mode 100644 (file)
index 379a248..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-using Content.Shared.Tag;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-public sealed partial class HasTag : EntityEffectCondition
-{
-    [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>))]
-    public string Tag = default!;
-
-    [DataField]
-    public bool Invert = false;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (args.EntityManager.TryGetComponent<TagComponent>(args.TargetEntity, out var tag))
-            return args.EntityManager.System<TagSystem>().HasTag(tag, Tag) ^ Invert;
-
-        return false;
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        // this should somehow be made (much) nicer.
-        return Loc.GetString("reagent-effect-condition-guidebook-has-tag", ("tag", Tag), ("invert", Invert));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs b/Content.Shared/EntityEffects/EffectConditions/InternalsCondition.cs
deleted file mode 100644 (file)
index cb30ef7..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-using Content.Shared.Body.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-///     Condition for if the entity is or isn't wearing internals.
-/// </summary>
-public sealed partial class Internals : EntityEffectCondition
-{
-    /// <summary>
-    ///     To pass, the entity's internals must have this same state.
-    /// </summary>
-    [DataField]
-    public bool UsingInternals = true;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (!args.EntityManager.TryGetComponent(args.TargetEntity, out InternalsComponent? internalsComp))
-            return !UsingInternals; // They have no internals to wear.
-
-        var internalsState = internalsComp.GasTankEntity != null; // If gas tank is not null, they are wearing internals
-        return UsingInternals == internalsState;
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-internals", ("usingInternals", UsingInternals));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs
deleted file mode 100644 (file)
index 96f3be6..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-using System.Linq;
-using Content.Shared.Localizations;
-using Content.Shared.Mind;
-using Content.Shared.Mind.Components;
-using Content.Shared.Roles;
-using Content.Shared.Roles.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-public sealed partial class JobCondition : EntityEffectCondition
-{
-    [DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
-
-        if (mindContainer is null
-            || !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
-            return false;
-
-        foreach (var roleId in mind.MindRoleContainer.ContainedEntities)
-        {
-            if (!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
-                continue;
-
-            if (!args.EntityManager.TryGetComponent<MindRoleComponent>(roleId, out var mindRole))
-            {
-                Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}");
-                continue;
-            }
-
-            if (mindRole.JobPrototype == null)
-            {
-                Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}");
-                continue;
-            }
-
-            if (Job.Contains(mindRole.JobPrototype.Value))
-                return true;
-        }
-
-        return false;
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
-        return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs b/Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs
deleted file mode 100644 (file)
index efe7246..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-public sealed partial class MobStateCondition : EntityEffectCondition
-{
-    [DataField]
-    public MobState Mobstate = MobState.Alive;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (args.EntityManager.TryGetComponent(args.TargetEntity, out MobStateComponent? mobState))
-        {
-            if (mobState.CurrentState == Mobstate)
-                return true;
-        }
-
-        return false;
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-mob-state-condition", ("state", Mobstate));
-    }
-}
-
diff --git a/Content.Shared/EntityEffects/EffectConditions/OrganType.cs b/Content.Shared/EntityEffects/EffectConditions/OrganType.cs
deleted file mode 100644 (file)
index f99eb5c..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-// using Content.Server.Body.Components;
-using Content.Shared.Body.Prototypes;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-///     Requires that the metabolizing organ is or is not tagged with a certain MetabolizerType
-/// </summary>
-public sealed partial class OrganType : EventEntityEffectCondition<OrganType>
-{
-    [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<MetabolizerTypePrototype>))]
-    public string Type = default!;
-
-    /// <summary>
-    ///     Does this condition pass when the organ has the type, or when it doesn't have the type?
-    /// </summary>
-    [DataField]
-    public bool ShouldHave = true;
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-organ-type",
-            ("name", prototype.Index<MetabolizerTypePrototype>(Type).LocalizedName),
-            ("shouldhave", ShouldHave));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs b/Content.Shared/EntityEffects/EffectConditions/ReagentThreshold.cs
deleted file mode 100644 (file)
index af71f20..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.FixedPoint;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-///     Used for implementing reagent effects that require a certain amount of reagent before it should be applied.
-///     For instance, overdoses.
-///
-///     This can also trigger on -other- reagents, not just the one metabolizing. By default, it uses the
-///     one being metabolized.
-/// </summary>
-public sealed partial class ReagentThreshold : EntityEffectCondition
-{
-    [DataField]
-    public FixedPoint2 Min = FixedPoint2.Zero;
-
-    [DataField]
-    public FixedPoint2 Max = FixedPoint2.MaxValue;
-
-    // TODO use ReagentId
-    [DataField]
-    public string? Reagent;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            var reagent = Reagent ?? reagentArgs.Reagent?.ID;
-            if (reagent == null)
-                return true; // No condition to apply.
-
-            var quant = FixedPoint2.Zero;
-            if (reagentArgs.Source != null)
-                quant = reagentArgs.Source.GetTotalPrototypeQuantity(reagent);
-
-            return quant >= Min && quant <= Max;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        ReagentPrototype? reagentProto = null;
-        if (Reagent is not null)
-            prototype.TryIndex(Reagent, out reagentProto);
-
-        return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold",
-            ("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")),
-            ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
-            ("min", Min.Float()));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs b/Content.Shared/EntityEffects/EffectConditions/SolutionTemperature.cs
deleted file mode 100644 (file)
index e2febd8..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-/// <summary>
-///     Requires the solution to be above or below a certain temperature.
-///     Used for things like explosives.
-/// </summary>
-public sealed partial class SolutionTemperature : EntityEffectCondition
-{
-    [DataField]
-    public float Min = 0.0f;
-
-    [DataField]
-    public float Max = float.PositiveInfinity;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            return reagentArgs?.Source != null &&
-                   reagentArgs.Source.Temperature >= Min &&
-                   reagentArgs.Source.Temperature <= Max;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-solution-temperature",
-            ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
-            ("min", Min));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs b/Content.Shared/EntityEffects/EffectConditions/TotalDamage.cs
deleted file mode 100644 (file)
index a4baeb6..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Shared.EntityEffects;
-using Content.Shared.Damage;
-using Content.Shared.FixedPoint;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-public sealed partial class TotalDamage : EntityEffectCondition
-{
-    [DataField]
-    public FixedPoint2 Max = FixedPoint2.MaxValue;
-
-    [DataField]
-    public FixedPoint2 Min = FixedPoint2.Zero;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (args.EntityManager.TryGetComponent(args.TargetEntity, out DamageableComponent? damage))
-        {
-            var total = damage.TotalDamage;
-            return total >= Min && total <= Max;
-        }
-
-        return false;
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-total-damage",
-            ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
-            ("min", Min.Float()));
-    }
-}
diff --git a/Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs b/Content.Shared/EntityEffects/EffectConditions/TotalHunger.cs
deleted file mode 100644 (file)
index cbeb334..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Shared.EntityEffects;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.EffectConditions;
-
-public sealed partial class Hunger : EntityEffectCondition
-{
-    [DataField]
-    public float Max = float.PositiveInfinity;
-
-    [DataField]
-    public float Min = 0;
-
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (args.EntityManager.TryGetComponent(args.TargetEntity, out HungerComponent? hunger))
-        {
-            var total = args.EntityManager.System<HungerSystem>().GetHunger(hunger);
-            return total >= Min && total <= Max;
-        }
-
-        return false;
-    }
-
-    public override string GuidebookExplanation(IPrototypeManager prototype)
-    {
-        return Loc.GetString("reagent-effect-condition-guidebook-total-hunger",
-            ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max),
-            ("min", Min));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs b/Content.Shared/EntityEffects/Effects/AddToSolutionReaction.cs
deleted file mode 100644 (file)
index 0f2d35d..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-using Content.Shared.Chemistry.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects
-{
-    public sealed partial class AddToSolutionReaction : EntityEffect
-    {
-        [DataField("solution")]
-        private string _solution = "reagents";
-
-        public override void Effect(EntityEffectBaseArgs args)
-        {
-            if (args is EntityEffectReagentArgs reagentArgs) {
-                if (reagentArgs.Reagent == null)
-                    return;
-
-                // TODO see if this is correct
-                var solutionContainerSystem = reagentArgs.EntityManager.System<SharedSolutionContainerSystem>();
-                if (!solutionContainerSystem.TryGetSolution(reagentArgs.TargetEntity, _solution, out var solutionContainer))
-                    return;
-
-                if (solutionContainerSystem.TryAddReagent(solutionContainer.Value, reagentArgs.Reagent.ID, reagentArgs.Quantity, out var accepted))
-                    reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, accepted);
-
-                return;
-            }
-
-            // TODO: Someone needs to figure out how to do this for non-reagent effects.
-            throw new NotImplementedException();
-        }
-
-        protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
-            Loc.GetString("reagent-effect-guidebook-add-to-solution-reaction", ("chance", Probability));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/AdjustAlert.cs b/Content.Shared/EntityEffects/Effects/AdjustAlert.cs
deleted file mode 100644 (file)
index 282de0a..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-using Content.Shared.Alert;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class AdjustAlert : EntityEffect
-{
-    /// <summary>
-    /// The specific Alert that will be adjusted
-    /// </summary>
-    [DataField(required: true)]
-    public ProtoId<AlertPrototype> AlertType;
-
-    /// <summary>
-    /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
-    /// </summary>
-    [DataField]
-    public bool Clear;
-
-    /// <summary>
-    /// Visually display cooldown progress over the alert icon.
-    /// </summary>
-    [DataField]
-    public bool ShowCooldown;
-
-    /// <summary>
-    /// The length of the cooldown or delay before removing the alert (in seconds).
-    /// </summary>
-    [DataField]
-    public float Time;
-
-    //JUSTIFICATION: This just changes some visuals, doesn't need to be documented.
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var alertSys = args.EntityManager.EntitySysManager.GetEntitySystem<AlertsSystem>();
-        if (!args.EntityManager.HasComponent<AlertsComponent>(args.TargetEntity))
-            return;
-
-        if (Clear && Time <= 0)
-        {
-            alertSys.ClearAlert(args.TargetEntity, AlertType);
-        }
-        else
-        {
-            var timing = IoCManager.Resolve<IGameTiming>();
-            (TimeSpan, TimeSpan)? cooldown = null;
-
-            if ((ShowCooldown || Clear) && Time > 0)
-                cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
-
-            alertSys.ShowAlert(args.TargetEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
-        }
-
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/AdjustAlertEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..633cde0
--- /dev/null
@@ -0,0 +1,65 @@
+using Content.Shared.Alert;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Adjusts a given alert on this entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustAlertEntityEffectSysten : EntityEffectSystem<AlertsComponent, AdjustAlert>
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly AlertsSystem _alerts = default!;
+
+    protected override void Effect(Entity<AlertsComponent> entity, ref EntityEffectEvent<AdjustAlert> args)
+    {
+        var time = args.Effect.Time;
+        var clear = args.Effect.Clear;
+        var type = args.Effect.AlertType;
+
+        if (clear && time <= TimeSpan.Zero)
+        {
+            _alerts.ClearAlert(entity.AsNullable(), type);
+        }
+        else
+        {
+            (TimeSpan, TimeSpan)? cooldown = null;
+
+            if ((args.Effect.ShowCooldown || clear) && args.Effect.Time >= TimeSpan.Zero)
+                cooldown = (_timing.CurTime, _timing.CurTime + time);
+
+            _alerts.ShowAlert(entity.AsNullable(), type, cooldown: cooldown, autoRemove: clear, showCooldown: args.Effect.ShowCooldown);
+        }
+
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustAlert : EntityEffectBase<AdjustAlert>
+{
+    /// <summary>
+    /// The specific Alert that will be adjusted
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<AlertPrototype> AlertType;
+
+    /// <summary>
+    /// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
+    /// </summary>
+    [DataField]
+    public bool Clear;
+
+    /// <summary>
+    /// Visually display cooldown progress over the alert icon.
+    /// </summary>
+    [DataField]
+    public bool ShowCooldown;
+
+    /// <summary>
+    /// The length of the cooldown or delay before removing the alert (in seconds).
+    /// </summary>
+    [DataField]
+    public TimeSpan Time;
+}
diff --git a/Content.Shared/EntityEffects/Effects/AdjustReagent.cs b/Content.Shared/EntityEffects/Effects/AdjustReagent.cs
deleted file mode 100644 (file)
index bb655b4..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-using Content.Shared.Body.Prototypes;
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.FixedPoint;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects
-{
-    public sealed partial class AdjustReagent : EntityEffect
-    {
-        /// <summary>
-        ///     The reagent ID to remove. Only one of this and <see cref="Group"/> should be active.
-        /// </summary>
-        [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<ReagentPrototype>))]
-        public string? Reagent = null;
-        // TODO use ReagentId
-
-        /// <summary>
-        ///     The metabolism group to remove, if the reagent satisfies any.
-        ///     Only one of this and <see cref="Reagent"/> should be active.
-        /// </summary>
-        [DataField(customTypeSerializer: typeof(PrototypeIdSerializer<MetabolismGroupPrototype>))]
-        public string? Group = null;
-
-        [DataField(required: true)]
-        public FixedPoint2 Amount = default!;
-
-        public override void Effect(EntityEffectBaseArgs args)
-        {
-            if (args is EntityEffectReagentArgs reagentArgs)
-            {
-                if (reagentArgs.Source == null)
-                    return;
-
-                var amount = Amount;
-                amount *= reagentArgs.Scale;
-
-                if (Reagent != null)
-                {
-                    if (amount < 0 && reagentArgs.Source.ContainsPrototype(Reagent))
-                        reagentArgs.Source.RemoveReagent(Reagent, -amount);
-                    if (amount > 0)
-                        reagentArgs.Source.AddReagent(Reagent, amount);
-                }
-                else if (Group != null)
-                {
-                    var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
-                    foreach (var quant in reagentArgs.Source.Contents.ToArray())
-                    {
-                        var proto = prototypeMan.Index<ReagentPrototype>(quant.Reagent.Prototype);
-                        if (proto.Metabolisms != null && proto.Metabolisms.ContainsKey(Group))
-                        {
-                            if (amount < 0)
-                                reagentArgs.Source.RemoveReagent(quant.Reagent, amount);
-                            if (amount > 0)
-                                reagentArgs.Source.AddReagent(quant.Reagent, amount);
-                        }
-                    }
-                }
-                return;
-            }
-
-            // TODO: Someone needs to figure out how to do this for non-reagent effects.
-            throw new NotImplementedException();
-        }
-
-        protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        {
-            if (Reagent is not null && prototype.TryIndex(Reagent, out ReagentPrototype? reagentProto))
-            {
-                return Loc.GetString("reagent-effect-guidebook-adjust-reagent-reagent",
-                    ("chance", Probability),
-                    ("deltasign", MathF.Sign(Amount.Float())),
-                    ("reagent", reagentProto.LocalizedName),
-                    ("amount", MathF.Abs(Amount.Float())));
-            }
-            else if (Group is not null && prototype.TryIndex(Group, out MetabolismGroupPrototype? groupProto))
-            {
-                return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group",
-                    ("chance", Probability),
-                    ("deltasign", MathF.Sign(Amount.Float())),
-                    ("group", groupProto.LocalizedName),
-                    ("amount", MathF.Abs(Amount.Float())));
-            }
-
-            throw new NotImplementedException();
-        }
-    }
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/AdjustTemperature.cs b/Content.Shared/EntityEffects/Effects/AdjustTemperature.cs
deleted file mode 100644 (file)
index 03dc226..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class AdjustTemperature : EventEntityEffect<AdjustTemperature>
-{
-    [DataField]
-    public float Amount;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-adjust-temperature",
-            ("chance", Probability),
-            ("deltasign", MathF.Sign(Amount)),
-            ("amount", MathF.Abs(Amount)));
-}
diff --git a/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/AdjustTemperatureEntityEffectsSystem.cs
new file mode 100644 (file)
index 0000000..adc465f
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Shared.Temperature.Components;
+using Content.Shared.Temperature.Systems;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+// TODO: When we get a proper temperature/energy struct combine this with the solution temperature effect!!!
+/// <summary>
+/// Adjusts the temperature of this entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustTemperatureEntityEffectSystem : EntityEffectSystem<TemperatureComponent, AdjustTemperature>
+{
+    [Dependency] private readonly SharedTemperatureSystem _temperature = default!;
+    protected override void Effect(Entity<TemperatureComponent> entity, ref EntityEffectEvent<AdjustTemperature> args)
+    {
+        var amount = args.Effect.Amount * args.Scale;
+
+        _temperature.ChangeHeat(entity, amount, true, entity.Comp);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustTemperature : EntityEffectBase<AdjustTemperature>
+{
+    /// <summary>
+    ///     Amount we're adjusting temperature by.
+    /// </summary>
+    [DataField]
+    public float Amount;
+}
diff --git a/Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs b/Content.Shared/EntityEffects/Effects/AreaReactionEffect.cs
deleted file mode 100644 (file)
index 45ed261..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-using Content.Shared.Database;
-using Content.Shared.FixedPoint;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Basically smoke and foam reactions.
-/// </summary>
-public sealed partial class AreaReactionEffect : EventEntityEffect<AreaReactionEffect>
-{
-    /// <summary>
-    /// How many seconds will the effect stay, counting after fully spreading.
-    /// </summary>
-    [DataField("duration")] public float Duration = 10;
-
-    /// <summary>
-    /// How many units of reaction for 1 smoke entity.
-    /// </summary>
-    [DataField] public FixedPoint2 OverflowThreshold = FixedPoint2.New(2.5);
-
-    /// <summary>
-    /// The entity prototype that will be spawned as the effect.
-    /// </summary>
-    [DataField("prototypeId", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string PrototypeId = default!;
-
-    /// <summary>
-    /// Sound that will get played when this reaction effect occurs.
-    /// </summary>
-    [DataField("sound", required: true)] public SoundSpecifier Sound = default!;
-
-    public override bool ShouldLog => true;
-
-    protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-            => Loc.GetString("reagent-effect-guidebook-area-reaction",
-                    ("duration", Duration)
-                );
-
-    public override LogImpact LogImpact => LogImpact.High;
-}
diff --git a/Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs b/Content.Shared/EntityEffects/Effects/ArtifactDurabilityRestore.cs
deleted file mode 100644 (file)
index 45ca740..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-using Content.Shared.Xenoarchaeology.Artifact.Components;
-using Content.Shared.Xenoarchaeology.Artifact;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Restores durability in active artefact nodes.
-/// </summary>
-public sealed partial class ArtifactDurabilityRestore : EntityEffect
-{
-    /// <summary>
-    ///     Amount of durability that will be restored per effect interaction.
-    /// </summary>
-    [DataField]
-    public int RestoredDurability = 1;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var entMan = args.EntityManager;
-        var xenoArtifactSys = entMan.System<SharedXenoArtifactSystem>();
-
-        if (!entMan.TryGetComponent<XenoArtifactComponent>(args.TargetEntity, out var xenoArtifact))
-            return;
-
-        foreach (var node in xenoArtifactSys.GetActiveNodes((args.TargetEntity, xenoArtifact)))
-        {
-            xenoArtifactSys.AdjustNodeDurability(node.Owner, RestoredDurability);
-        }
-    }
-
-    protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return Loc.GetString("reagent-effect-guidebook-artifact-durability-restore", ("restored", RestoredDurability));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/ArtifactEntityEffectsSystem.cs
new file mode 100644 (file)
index 0000000..563c053
--- /dev/null
@@ -0,0 +1,72 @@
+using Content.Shared.Popups;
+using Content.Shared.Xenoarchaeology.Artifact;
+using Content.Shared.Xenoarchaeology.Artifact.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Restores durability on this artifact
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ArtifactDurabilityRestoreEntityEffectsSystem : EntityEffectSystem<XenoArtifactComponent, ArtifactDurabilityRestore>
+{
+    [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!;
+
+    protected override void Effect(Entity<XenoArtifactComponent> entity, ref EntityEffectEvent<ArtifactDurabilityRestore> args)
+    {
+        var durability = args.Effect.RestoredDurability;
+
+        foreach (var node in _xenoArtifact.GetActiveNodes(entity))
+        {
+            _xenoArtifact.AdjustNodeDurability(node.Owner, durability);
+        }
+    }
+}
+
+/// <summary>
+/// Unlocks a node on this artifact. Only works this effect hasn't been applied before.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ArtifactUnlockEntityEffectSystem : EntityEffectSystem<XenoArtifactComponent, ArtifactUnlock>
+{
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedXenoArtifactSystem _xenoArtifact = default!;
+
+    protected override void Effect(Entity<XenoArtifactComponent> entity, ref EntityEffectEvent<ArtifactUnlock> args)
+    {
+        if (EnsureComp<XenoArtifactUnlockingComponent>(entity, out var unlocking))
+        {
+            if (unlocking.ArtifexiumApplied)
+                return;
+
+            _popup.PopupEntity(Loc.GetString("artifact-activation-artifexium"), entity, PopupType.Medium);
+        }
+        else
+        {
+            _xenoArtifact.TriggerXenoArtifact(entity, null, force: true);
+        }
+
+        _xenoArtifact.SetArtifexiumApplied((entity, unlocking), true);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ArtifactDurabilityRestore : EntityEffectBase<ArtifactDurabilityRestore>
+{
+    /// <summary>
+    ///     Amount of durability that will be restored per effect interaction.
+    /// </summary>
+    [DataField]
+    public int RestoredDurability = 1;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-artifact-durability-restore", ("restored", RestoredDurability));
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ArtifactUnlock : EntityEffectBase<ArtifactUnlock>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-artifact-unlock", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs b/Content.Shared/EntityEffects/Effects/ArtifactUnlock.cs
deleted file mode 100644 (file)
index 077e1eb..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Shared.Xenoarchaeology.Artifact;
-using Content.Shared.EntityEffects;
-using Content.Shared.Popups;
-using Content.Shared.Xenoarchaeology.Artifact.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Sets an artifact into the unlocking state and marks the artifexium effect as true.
-/// This is a very specific behavior intended for a specific chem.
-/// </summary>
-public sealed partial class ArtifactUnlock : EntityEffect
-{
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var entMan = args.EntityManager;
-        var xenoArtifactSys = entMan.System<SharedXenoArtifactSystem>();
-        var popupSys = entMan.System<SharedPopupSystem>();
-
-        if (!entMan.TryGetComponent<XenoArtifactComponent>(args.TargetEntity, out var xenoArtifact))
-            return;
-
-        if (!entMan.TryGetComponent<XenoArtifactUnlockingComponent>(args.TargetEntity, out var unlocking))
-        {
-            xenoArtifactSys.TriggerXenoArtifact((args.TargetEntity, xenoArtifact), null, force: true);
-            unlocking = entMan.EnsureComponent<XenoArtifactUnlockingComponent>(args.TargetEntity);
-        }
-        else if (!unlocking.ArtifexiumApplied)
-        {
-            popupSys.PopupEntity(Loc.GetString("artifact-activation-artifexium"), args.TargetEntity, PopupType.Medium);
-        }
-
-        if (unlocking.ArtifexiumApplied)
-            return;
-
-        xenoArtifactSys.SetArtifexiumApplied((args.TargetEntity, unlocking), true);
-    }
-
-    protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return Loc.GetString("reagent-effect-guidebook-artifact-unlock", ("chance", Probability));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/CreateGasEntityEffect.cs
new file mode 100644 (file)
index 0000000..aa5132e
--- /dev/null
@@ -0,0 +1,35 @@
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CreateGas : EntityEffectBase<CreateGas>
+{
+    /// <summary>
+    ///     The gas we're creating
+    /// </summary>
+    [DataField]
+    public Gas Gas;
+
+    /// <summary>
+    ///     Amount of moles we're creating
+    /// </summary>
+    [DataField]
+    public float Moles = 3f;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        var atmos = entSys.GetEntitySystem<SharedAtmosphereSystem>();
+        var gasProto = atmos.GetGas(Gas);
+
+        return Loc.GetString("entity-effect-guidebook-create-gas",
+            ("chance", Probability),
+            ("moles", Moles),
+            ("gas", gasProto.Name));
+    }
+}
diff --git a/Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Atmos/ExtinguishEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..b4b475e
--- /dev/null
@@ -0,0 +1,36 @@
+using Content.Shared.Atmos;
+using Content.Shared.Atmos.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// This raises an extinguish event on a given entity, reducing FireStacks.
+/// The amount of FireStacks reduced is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ExtinguishEntityEffectSystem : EntityEffectSystem<FlammableComponent, Extinguish>
+{
+    protected override void Effect(Entity<FlammableComponent> entity, ref EntityEffectEvent<Extinguish> args)
+    {
+        var ev = new ExtinguishEvent
+        {
+            FireStacksAdjustment = args.Effect.FireStacksAdjustment * args.Scale,
+        };
+
+        RaiseLocalEvent(entity, ref ev);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Extinguish : EntityEffectBase<Extinguish>
+{
+    /// <summary>
+    ///     Amount of FireStacks reduced.
+    /// </summary>
+    [DataField]
+    public float FireStacksAdjustment = -1.5f;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-extinguish-reaction", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/FlammableEntityEffect.cs
new file mode 100644 (file)
index 0000000..f08b609
--- /dev/null
@@ -0,0 +1,28 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Flammable : EntityEffectBase<Flammable>
+{
+    /// <summary>
+    /// Fire stack multiplier applied on an entity,
+    /// unless that entity is already on fire and <see cref="MultiplierOnExisting"/> is not null.
+    /// </summary>
+    [DataField]
+    public float Multiplier = 0.05f;
+
+    /// <summary>
+    /// Fire stack multiplier applied if the entity is already on fire. Defaults to <see cref="Multiplier"/> if null.
+    /// </summary>
+    [DataField]
+    public float? MultiplierOnExisting;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-flammable-reaction", ("chance", Probability));
+
+    public override bool ShouldLog => true;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Atmos/IgniteEntityEffect.cs
new file mode 100644 (file)
index 0000000..e10aaf3
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Database;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Atmos;
+
+/// <summary>
+/// See serverside system
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Ignite : EntityEffectBase<Ignite>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-ignite", ("chance", Probability));
+
+    public override bool ShouldLog => true;
+
+    public override LogImpact LogImpact => LogImpact.Medium;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/CleanBloodstreamEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..402a505
--- /dev/null
@@ -0,0 +1,43 @@
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Removes a given amount of chemicals from the bloodstream modified by scale.
+/// Optionally ignores a given chemical.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class CleanBloodstreamEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, CleanBloodstream>
+{
+    [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
+
+    protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<CleanBloodstream> args)
+    {
+        var scale = args.Scale * args.Effect.CleanseRate;
+
+        _bloodstream.FlushChemicals((entity, entity), args.Effect.Excluded, scale);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CleanBloodstream : EntityEffectBase<CleanBloodstream>
+{
+    /// <summary>
+    ///     Amount of reagent we're cleaning out of our bloodstream.
+    /// </summary>
+    [DataField]
+    public FixedPoint2 CleanseRate = 3.0f;
+
+    /// <summary>
+    ///     An optional chemical to ignore when doing removal.
+    /// </summary>
+    [DataField]
+    public ProtoId<ReagentPrototype>? Excluded;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-clean-bloodstream", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/EyeDamageEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..29fd994
--- /dev/null
@@ -0,0 +1,32 @@
+using Content.Shared.Eye.Blinding.Systems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Modifies eye damage by a given amount, modified by scale, floored to an integer.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EyeDamageEntityEffectSystem : EntityEffectSystem<MetaDataComponent, EyeDamage>
+{
+    [Dependency] private readonly BlindableSystem _blindable = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<EyeDamage> args)
+    {
+        var amount = (int) Math.Floor(args.Effect.Amount * args.Scale);
+        _blindable.AdjustEyeDamage(entity.Owner, amount);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class EyeDamage : EntityEffectBase<EyeDamage>
+{
+    /// <summary>
+    /// The amount of eye damage we're adding or removing
+    /// </summary>
+    [DataField]
+    public int Amount = -1;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-eye-damage", ("chance", Probability), ("deltasign", MathF.Sign(Amount)));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ModifyBleedEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..c684ffc
--- /dev/null
@@ -0,0 +1,32 @@
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Modifies bleed by a given amount multiplied by scale. This can increase or decrease bleed.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyBleedEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, ModifyBleed>
+{
+    [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
+
+    protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<ModifyBleed> args)
+    {
+        _bloodstream.TryModifyBleedAmount(entity.AsNullable(), args.Effect.Amount * args.Scale);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyBleed : EntityEffectBase<ModifyBleed>
+{
+    /// <summary>
+    /// Amount of bleed we're applying or removing if negative.
+    /// </summary>
+    [DataField]
+    public float Amount = -1.0f;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-modify-bleed-amount", ("chance", Probability), ("deltasign", MathF.Sign(Amount)));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ModifyBloodLevelEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..43098c9
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Body.Components;
+using Content.Shared.Body.Systems;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Modifies the amount of blood in this entity's bloodstream by a given amount multiplied by scale.
+/// This effect can increase or decrease blood level.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyBloodLevelEntityEffectSystem : EntityEffectSystem<BloodstreamComponent, ModifyBloodLevel>
+{
+    [Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
+
+    protected override void Effect(Entity<BloodstreamComponent> entity, ref EntityEffectEvent<ModifyBloodLevel> args)
+    {
+        _bloodstream.TryModifyBloodLevel(entity.AsNullable(), args.Effect.Amount * args.Scale);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyBloodLevel : EntityEffectBase<ModifyBloodLevel>
+{
+    /// <summary>
+    /// Amount of bleed we're applying or removing if negative.
+    /// </summary>
+    [DataField]
+    public FixedPoint2 Amount = 1.0f;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-modify-blood-level", ("chance", Probability), ("deltasign", MathF.Sign(Amount.Float())));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ModifyLungGasEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..29fa1de
--- /dev/null
@@ -0,0 +1,33 @@
+using Content.Shared.Atmos;
+using Content.Shared.Body.Components;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Adjust the amount of Moles stored in this set of lungs based on a given dictionary of gasses and ratios.
+/// The amount of gas adjusted is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyLungGasEntityEffectSystem : EntityEffectSystem<LungComponent, ModifyLungGas>
+{
+    // TODO: This shouldn't be an entity effect, gasses should just metabolize and make a byproduct by default...
+    protected override void Effect(Entity<LungComponent> entity, ref EntityEffectEvent<ModifyLungGas> args)
+    {
+        var amount = args.Scale;
+
+        foreach (var (gas, ratio) in args.Effect.Ratios)
+        {
+            var quantity = ratio * amount / Atmospherics.BreathMolesToReagentMultiplier;
+            if (quantity < 0)
+                quantity = Math.Max(quantity, -entity.Comp.Air[(int) gas]);
+            entity.Comp.Air.AdjustMoles(gas, quantity);
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyLungGas : EntityEffectBase<ModifyLungGas>
+{
+    [DataField(required: true)]
+    public Dictionary<Gas, float> Ratios = default!;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Body/OxygenateEntityEffect.cs
new file mode 100644 (file)
index 0000000..9790fce
--- /dev/null
@@ -0,0 +1,14 @@
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Oxygenate : EntityEffectBase<Oxygenate>
+{
+    /// <summary>
+    /// Factor of oxygenation per metabolized quantity. Lungs metabolize at about 50u per tick so we need an equal multiplier to cancel that out!
+    /// </summary>
+    [DataField]
+    public float Factor = 1f;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/ReduceRottingEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..4fb41bd
--- /dev/null
@@ -0,0 +1,36 @@
+using Content.Shared.Atmos.Rotting;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Reduces the rotting timer on an entity by a number of seconds, modified by scale.
+/// This cannot increase the amount of seconds a body has rotted.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ReduceRottingEntityEffectSystem : EntityEffectSystem<PerishableComponent, ReduceRotting>
+{
+    [Dependency] private readonly SharedRottingSystem _rotting = default!;
+
+    protected override void Effect(Entity<PerishableComponent> entity, ref EntityEffectEvent<ReduceRotting> args)
+    {
+        var amount = args.Effect.Seconds * args.Scale;
+
+        _rotting.ReduceAccumulator(entity, amount);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ReduceRotting : EntityEffectBase<ReduceRotting>
+{
+    /// <summary>
+    /// Number of seconds removed from the rotting timer.
+    /// </summary>
+    [DataField]
+    public TimeSpan Seconds = TimeSpan.FromSeconds(10);
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-reduce-rotting",
+            ("chance", Probability),
+            ("time", Seconds.TotalSeconds));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/SatiateEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..7960842
--- /dev/null
@@ -0,0 +1,63 @@
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+// TODO: These systems are in the same file since satiation should be one system instead of two. Combine these when that happens.
+// TODO: Arguably oxygen saturation should also be added here...
+/// <summary>
+/// Modifies the thirst level of a given entity, multiplied by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SatiateThirstEntityEffectsSystem : EntityEffectSystem<ThirstComponent, SatiateThirst>
+{
+    [Dependency] private readonly ThirstSystem _thirst = default!;
+    protected override void Effect(Entity<ThirstComponent> entity, ref EntityEffectEvent<SatiateThirst> args)
+    {
+        _thirst.ModifyThirst(entity, entity.Comp, args.Effect.Factor * args.Scale);
+    }
+}
+
+/// <summary>
+/// Modifies the hunger level of a given entity, multiplied by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SatiateHungerEntityEffectsSystem : EntityEffectSystem<HungerComponent, SatiateHunger>
+{
+    [Dependency] private readonly HungerSystem _hunger = default!;
+    protected override void Effect(Entity<HungerComponent> entity, ref EntityEffectEvent<SatiateHunger> args)
+    {
+        _hunger.ModifyHunger(entity, args.Effect.Factor * args.Scale, entity.Comp);
+    }
+}
+
+/// <summary>
+/// A type of <see cref="EntityEffectBase{T}"/> made for satiation effects.
+/// </summary>
+/// <typeparam name="T">The effect inheriting this BaseEffect</typeparam>
+/// <inheritdoc cref="EntityEffect"/>
+public abstract partial class Satiate<T> : EntityEffectBase<T> where T : EntityEffectBase<T>
+{
+    public const float AverageSatiation = 3f; // Magic number. Not sure how it was calculated since I didn't make it.
+
+    /// <summary>
+    ///     Change in satiation.
+    /// </summary>
+    [DataField]
+    public float Factor = -1.5f;
+}
+
+/// <inheritdoc cref="Satiate{T}"/>
+public sealed partial class SatiateThirst : Satiate<SatiateThirst>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative",  Factor / AverageSatiation));
+}
+
+/// <inheritdoc cref="Satiate{T}"/>
+public sealed partial class SatiateHunger : Satiate<SatiateHunger>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", Factor / AverageSatiation));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Body/VomitEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..0274705
--- /dev/null
@@ -0,0 +1,37 @@
+using Content.Shared.Medical;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Body;
+
+/// <summary>
+/// Makes an entity vomit and reduces hunger and thirst by a given amount, modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class VomitEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Vomit>
+{
+    [Dependency] private readonly VomitSystem _vomit = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Vomit> args)
+    {
+        _vomit.Vomit(entity.Owner, args.Effect.ThirstAmount * args.Scale, args.Effect.HungerAmount * args.Scale);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Vomit : EntityEffectBase<Vomit>
+{
+    /// <summary>
+    /// How much we adjust our thirst after vomiting.
+    /// </summary>
+    [DataField]
+    public float ThirstAmount = -8f;
+
+    /// <summary>
+    /// How much we adjust our hunger after vomiting.
+    /// </summary>
+    [DataField]
+    public float HungerAmount = -8f;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-vomit", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/BasePlantAdjustAttributeEntityEffect.cs
new file mode 100644 (file)
index 0000000..9b235e6
--- /dev/null
@@ -0,0 +1,37 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+/// A type of <see cref="EntityEffectBase{T}"/> which modifies the attribute of a Seed in a PlantHolder.
+/// These are not modified by scale as botany has no concept of scale.
+/// </summary>
+/// <typeparam name="T">The effect inheriting this BaseEffect</typeparam>
+/// <inheritdoc cref="EntityEffect"/>
+public abstract partial class BasePlantAdjustAttribute<T> : EntityEffectBase<T> where T : BasePlantAdjustAttribute<T>
+{
+    /// <summary>
+    /// How much we're adjusting the given attribute by.
+    /// </summary>
+    [DataField]
+    public float Amount { get; protected set; } = 1;
+
+    /// <summary>
+    /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions.
+    /// </summary>
+    [DataField]
+    public abstract string GuidebookAttributeName { get; set; }
+
+    /// <summary>
+    /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number.
+    /// </summary>
+    [DataField]
+    public virtual bool GuidebookIsAttributePositive { get; protected set; } = true;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-attribute",
+        ("attribute", Loc.GetString(GuidebookAttributeName)),
+        ("amount", Amount.ToString("0.00")),
+        ("positive", GuidebookIsAttributePositive),
+        ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustHealth.cs
new file mode 100644 (file)
index 0000000..069cb32
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustHealth : BasePlantAdjustAttribute<PlantAdjustHealth>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
+}
+
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationLevel.cs
new file mode 100644 (file)
index 0000000..32e419e
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationLevel : BasePlantAdjustAttribute<PlantAdjustMutationLevel>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
+}
+
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustMutationMod.cs
new file mode 100644 (file)
index 0000000..4ea695d
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustMutationMod : BasePlantAdjustAttribute<PlantAdjustMutationMod>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
+}
+
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustNutrition.cs
new file mode 100644 (file)
index 0000000..6f53076
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustNutrition : BasePlantAdjustAttribute<PlantAdjustNutrition>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
+}
similarity index 53%
rename from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs
rename to Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPests.cs
index 18c00550d534c781e103b14e985c480f53ead640..c1661ec89e629ca9e5ba447f15d2b22e0506c45e 100644 (file)
@@ -1,6 +1,6 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
 
-public sealed partial class PlantAdjustPests : PlantAdjustAttribute<PlantAdjustPests>
+public sealed partial class PlantAdjustPests : BasePlantAdjustAttribute<PlantAdjustPests>
 {
     public override string GuidebookAttributeName { get; set; } = "plant-attribute-pests";
     public override bool GuidebookIsAttributePositive { get; protected set; } = false;
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustPotency.cs
new file mode 100644 (file)
index 0000000..4f42adf
--- /dev/null
@@ -0,0 +1,9 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+///     Handles increase or decrease of plant potency.
+/// </summary>
+public sealed partial class PlantAdjustPotency : BasePlantAdjustAttribute<PlantAdjustPotency>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency";
+}
similarity index 53%
rename from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs
rename to Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustToxins.cs
index 9123b5847d60292d9c982432076cf140f240ac26..04eccb03ec4cbcc3edbd7a265697382f073cb38b 100644 (file)
@@ -1,8 +1,9 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
 
-public sealed partial class PlantAdjustToxins : PlantAdjustAttribute<PlantAdjustToxins>
+public sealed partial class PlantAdjustToxins : BasePlantAdjustAttribute<PlantAdjustToxins>
 {
     public override string GuidebookAttributeName { get; set; } = "plant-attribute-toxins";
+
     public override bool GuidebookIsAttributePositive { get; protected set; } = false;
 }
 
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWater.cs
new file mode 100644 (file)
index 0000000..1d6ef8e
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAdjustWater : BasePlantAdjustAttribute<PlantAdjustWater>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-water";
+}
+
similarity index 53%
rename from Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs
rename to Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAdjustWeeds.cs
index 70ff0747dd3119f1e245c835b66a4f9cb333f9cd..e6be25d8c90f7d0a87ad158e5474a1763a50226b 100644 (file)
@@ -1,6 +1,6 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
 
-public sealed partial class PlantAdjustWeeds : PlantAdjustAttribute<PlantAdjustWeeds>
+public sealed partial class PlantAdjustWeeds : BasePlantAdjustAttribute<PlantAdjustWeeds>
 {
     public override string GuidebookAttributeName { get; set; } = "plant-attribute-weeds";
     public override bool GuidebookIsAttributePositive { get; protected set; } = false;
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantAffectGrowth.cs
new file mode 100644 (file)
index 0000000..76466f5
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantAffectGrowth : BasePlantAdjustAttribute<PlantAffectGrowth>
+{
+    public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth";
+}
+
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantChangeStat.cs
new file mode 100644 (file)
index 0000000..dcea56b
--- /dev/null
@@ -0,0 +1,21 @@
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantChangeStat : EntityEffectBase<PlantChangeStat>
+{
+    /// <remarks>
+    /// This is the worst thing in the code base.
+    /// It's meant to be generic and expandable I guess? But it's looking for a specific datafield and then
+    /// sending it into an if else if else if statement that filters by object type and randomly flips bits.
+    /// </remarks>
+    [DataField (required: true)]
+    public string TargetValue = string.Empty;
+
+    [DataField]
+    public float MinValue;
+
+    [DataField]
+    public float MaxValue;
+
+    [DataField]
+    public int Steps;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantCryoxadone.cs
new file mode 100644 (file)
index 0000000..7dcea24
--- /dev/null
@@ -0,0 +1,9 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantCryoxadone : EntityEffectBase<PlantCryoxadone>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-cryoxadone", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDestroySeeds.cs
new file mode 100644 (file)
index 0000000..d45c036
--- /dev/null
@@ -0,0 +1,12 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+///     Handles removal of seeds on a plant.
+/// </summary>
+public sealed partial class PlantDestroySeeds : EntityEffectBase<PlantDestroySeeds>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-seeds-remove", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantDiethylamine.cs
new file mode 100644 (file)
index 0000000..4355a44
--- /dev/null
@@ -0,0 +1,11 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantDiethylamine : EntityEffectBase<PlantDiethylamine>
+{
+    /// <inheritdoc/>
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-diethylamine", ("chance", Probability));
+}
+
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantPhalanximine.cs
new file mode 100644 (file)
index 0000000..b56b990
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class PlantPhalanximine : EntityEffectBase<PlantPhalanximine>
+{
+    /// <inheritdoc/>
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-phalanximine", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/PlantRestoreSeeds.cs
new file mode 100644 (file)
index 0000000..63b06b0
--- /dev/null
@@ -0,0 +1,12 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+/// <summary>
+///     Handles restoral of seeds on a plant.
+/// </summary>
+public sealed partial class PlantRestoreSeeds : EntityEffectBase<PlantRestoreSeeds>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-seeds-add", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantAttributes/RobustHarvest.cs
new file mode 100644 (file)
index 0000000..77bde39
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany.PlantAttributes;
+
+public sealed partial class RobustHarvest : EntityEffectBase<RobustHarvest>
+{
+    [DataField]
+    public int PotencyLimit = 50;
+
+    [DataField]
+    public int PotencyIncrease = 3;
+
+    [DataField]
+    public int PotencySeedlessThreshold = 30;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-plant-robust-harvest",
+            ("seedlesstreshold", PotencySeedlessThreshold),
+            ("limit", PotencyLimit),
+            ("increase", PotencyIncrease),
+            ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateChemicalsEntityEffect.cs
new file mode 100644 (file)
index 0000000..6a3d13e
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Shared.Random;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateChemicals : EntityEffectBase<PlantMutateChemicals>
+{
+    /// <summary>
+    /// The Reagent list this mutation draws from.
+    /// </summary>
+    [DataField]
+    public ProtoId<WeightedRandomFillSolutionPrototype> RandomPickBotanyReagent = "RandomPickBotanyReagent";
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateGasesEntityEffect.cs
new file mode 100644 (file)
index 0000000..c617c05
--- /dev/null
@@ -0,0 +1,22 @@
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateConsumeGases : EntityEffectBase<PlantMutateConsumeGases>
+{
+    [DataField]
+    public float MinValue = 0.01f;
+
+    [DataField]
+    public float MaxValue = 0.5f;
+}
+
+public sealed partial class PlantMutateExudeGases : EntityEffectBase<PlantMutateExudeGases>
+{
+    [DataField]
+    public float MinValue = 0.01f;
+
+    [DataField]
+    public float MaxValue = 0.5f;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateHarvestEntityEffect.cs
new file mode 100644 (file)
index 0000000..3602453
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateHarvest : EntityEffectBase<PlantMutateHarvest>;
diff --git a/Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Botany/PlantMutateSpeciesChangeEntityEffect.cs
new file mode 100644 (file)
index 0000000..91e8947
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Content.Shared.EntityEffects.Effects.Botany;
+
+/// <summary>
+/// See serverside system.
+/// </summary>
+public sealed partial class PlantMutateSpeciesChange : EntityEffectBase<PlantMutateSpeciesChange>;
diff --git a/Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs b/Content.Shared/EntityEffects/Effects/CauseZombieInfection.cs
deleted file mode 100644 (file)
index 3f8c58b..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class CauseZombieInfection : EventEntityEffect<CauseZombieInfection>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-cause-zombie-infection", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs b/Content.Shared/EntityEffects/Effects/ChemCleanBloodstream.cs
deleted file mode 100644 (file)
index 98181b8..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Basically smoke and foam reactions.
-/// </summary>
-public sealed partial class ChemCleanBloodstream : EventEntityEffect<ChemCleanBloodstream>
-{
-    [DataField]
-    public float CleanseRate = 3.0f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-chem-clean-bloodstream", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs b/Content.Shared/EntityEffects/Effects/ChemHealEyeDamage.cs
deleted file mode 100644 (file)
index 83b2aa9..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-using Content.Shared.Eye.Blinding.Systems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Heal or apply eye damage
-/// </summary>
-public sealed partial class ChemHealEyeDamage : EntityEffect
-{
-    /// <summary>
-    /// How much eye damage to add.
-    /// </summary>
-    [DataField]
-    public int Amount = -1;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-cure-eye-damage", ("chance", Probability), ("deltasign", MathF.Sign(Amount)));
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-            if (reagentArgs.Scale != 1f) // huh?
-                return;
-
-        args.EntityManager.EntitySysManager.GetEntitySystem<BlindableSystem>().AdjustEyeDamage(args.TargetEntity, Amount);
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/ChemVomit.cs b/Content.Shared/EntityEffects/Effects/ChemVomit.cs
deleted file mode 100644 (file)
index 1cd6b25..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Forces you to vomit.
-/// </summary>
-public sealed partial class ChemVomit : EventEntityEffect<ChemVomit>
-{
-    /// How many units of thirst to add each time we vomit
-    [DataField]
-    public float ThirstAmount = -8f;
-    /// How many units of hunger to add each time we vomit
-    [DataField]
-    public float HungerAmount = -8f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-chem-vomit", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs b/Content.Shared/EntityEffects/Effects/CreateEntityReactionEffect.cs
deleted file mode 100644 (file)
index 33173b1..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-[DataDefinition]
-public sealed partial class CreateEntityReactionEffect : EventEntityEffect<CreateEntityReactionEffect>
-{
-    /// <summary>
-    ///     What entity to create.
-    /// </summary>
-    [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string Entity = default!;
-
-    /// <summary>
-    ///     How many entities to create per unit reaction.
-    /// </summary>
-    [DataField]
-    public uint Number = 1;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-create-entity-reaction-effect",
-            ("chance", Probability),
-            ("entname", IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
-            ("amount", Number));
-}
diff --git a/Content.Shared/EntityEffects/Effects/CreateGas.cs b/Content.Shared/EntityEffects/Effects/CreateGas.cs
deleted file mode 100644 (file)
index 75d554c..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using Content.Shared.Atmos;
-using Content.Shared.Atmos.EntitySystems;
-using Content.Shared.Database;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class CreateGas : EventEntityEffect<CreateGas>
-{
-    [DataField(required: true)]
-    public Gas Gas = default!;
-
-    /// <summary>
-    ///     For each unit consumed, how many moles of gas should be created?
-    /// </summary>
-    [DataField]
-    public float Multiplier = 3f;
-
-    public override bool ShouldLog => true;
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        var atmos = entSys.GetEntitySystem<SharedAtmosphereSystem>();
-        var gasProto = atmos.GetGas(Gas);
-
-        return Loc.GetString("reagent-effect-guidebook-create-gas",
-            ("chance", Probability),
-            ("moles", Multiplier),
-            ("gas", gasProto.Name));
-    }
-
-    public override LogImpact LogImpact => LogImpact.High;
-}
diff --git a/Content.Shared/EntityEffects/Effects/CureZombieInfection.cs b/Content.Shared/EntityEffects/Effects/CureZombieInfection.cs
deleted file mode 100644 (file)
index dd2d218..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class CureZombieInfection : EventEntityEffect<CureZombieInfection>
-{
-    [DataField]
-    public bool Innoculate;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        if(Innoculate)
-            return Loc.GetString("reagent-effect-guidebook-innoculate-zombie-infection", ("chance", Probability));
-
-        return Loc.GetString("reagent-effect-guidebook-cure-zombie-infection", ("chance", Probability));
-    }
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/Drunk.cs b/Content.Shared/EntityEffects/Effects/Drunk.cs
deleted file mode 100644 (file)
index aa15df8..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-using Content.Shared.Drunk;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Drunk : EntityEffect
-{
-    /// <summary>
-    ///     BoozePower is how long each metabolism cycle will make the drunk effect last for.
-    /// </summary>
-    [DataField]
-    public TimeSpan BoozePower = TimeSpan.FromSeconds(3f);
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-drunk", ("chance", Probability));
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var boozePower = BoozePower;
-
-        if (args is EntityEffectReagentArgs reagentArgs)
-            boozePower *= reagentArgs.Scale.Float();
-
-        var drunkSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedDrunkSystem>();
-        drunkSys.TryApplyDrunkenness(args.TargetEntity, boozePower);
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/Electrocute.cs b/Content.Shared/EntityEffects/Effects/Electrocute.cs
deleted file mode 100644 (file)
index 32e0ff1..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-using Content.Shared.Electrocution;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Electrocute : EntityEffect
-{
-    [DataField] public int ElectrocuteTime = 2;
-
-    [DataField] public int ElectrocuteDamageScale = 5;
-
-    /// <remarks>
-    ///     true - refresh electrocute time,  false - accumulate electrocute time
-    /// </remarks>
-    [DataField] public bool Refresh = true;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime));
-
-    public override bool ShouldLog => true;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            reagentArgs.EntityManager.System<SharedElectrocutionSystem>().TryDoElectrocution(reagentArgs.TargetEntity, null,
-                Math.Max((reagentArgs.Quantity * ElectrocuteDamageScale).Int(), 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
-
-            if (reagentArgs.Reagent != null)
-                reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, reagentArgs.Quantity);
-        } else
-        {
-            args.EntityManager.System<SharedElectrocutionSystem>().TryDoElectrocution(args.TargetEntity, null,
-                Math.Max(ElectrocuteDamageScale, 1), TimeSpan.FromSeconds(ElectrocuteTime), Refresh, ignoreInsulation: true);
-        }
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/Emote.cs b/Content.Shared/EntityEffects/Effects/Emote.cs
deleted file mode 100644 (file)
index 494dc50..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-using Content.Shared.Chat.Prototypes;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Tries to force someone to emote (scream, laugh, etc). Still respects whitelists/blacklists and other limits unless specially forced.
-/// </summary>
-public sealed partial class Emote : EventEntityEffect<Emote>
-{
-    /// <summary>
-    ///     The emote the entity will preform.
-    /// </summary>
-    [DataField("emote", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EmotePrototype>))]
-    public string EmoteId;
-
-    /// <summary>
-    ///     If the emote should be recorded in chat.
-    /// </summary>
-    [DataField]
-    public bool ShowInChat;
-
-    /// <summary>
-    ///     If the forced emote will be listed in the guidebook.
-    /// </summary>
-    [DataField]
-    public bool ShowInGuidebook;
-
-    /// <summary>
-    ///     If true, the entity will preform the emote even if they normally can't.
-    /// </summary>
-    [DataField]
-    public bool Force = false;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        if (!ShowInGuidebook)
-            return null; // JUSTIFICATION: Emoting is mostly flavor, so same reason popup messages are not in here.
-
-        var emotePrototype = prototype.Index<EmotePrototype>(EmoteId);
-        return Loc.GetString("reagent-effect-guidebook-emote", ("chance", Probability), ("emote", Loc.GetString(emotePrototype.Name)));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs b/Content.Shared/EntityEffects/Effects/EmoteEntityEffect.cs
new file mode 100644 (file)
index 0000000..1fcfc70
--- /dev/null
@@ -0,0 +1,40 @@
+using Content.Shared.Chat.Prototypes;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Emote : EntityEffectBase<Emote>
+{
+    /// <summary>
+    ///     The emote the entity will preform.
+    /// </summary>
+    [DataField("emote", required: true)]
+    public ProtoId<EmotePrototype> EmoteId;
+
+    /// <summary>
+    ///     If the emote should be recorded in chat.
+    /// </summary>
+    [DataField]
+    public bool ShowInChat;
+
+    /// <summary>
+    ///     If the forced emote will be listed in the guidebook.
+    /// </summary>
+    [DataField]
+    public bool ShowInGuidebook;
+
+    /// <summary>
+    ///     If true, the entity will preform the emote even if they normally can't.
+    /// </summary>
+    [DataField]
+    public bool Force;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        if (!ShowInGuidebook || !prototype.Resolve(EmoteId, out var emote))
+            return null; // JUSTIFICATION: Emoting is mostly flavor, so same reason popup messages are not in here.
+
+        return Loc.GetString("entity-effect-guidebook-emote", ("chance", Probability), ("emote", Loc.GetString(emote.Name)));
+    }
+}
diff --git a/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs b/Content.Shared/EntityEffects/Effects/EmpReactionEffect.cs
deleted file mode 100644 (file)
index 0f9eacc..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-[DataDefinition]
-public sealed partial class EmpReactionEffect : EventEntityEffect<EmpReactionEffect>
-{
-    /// <summary>
-    ///     Impulse range per unit of quantity
-    /// </summary>
-    [DataField("rangePerUnit")]
-    public float EmpRangePerUnit = 0.5f;
-
-    /// <summary>
-    ///     Maximum impulse range
-    /// </summary>
-    [DataField("maxRange")]
-    public float EmpMaxRange = 10;
-
-    /// <summary>
-    ///     How much energy will be drain from sources
-    /// </summary>
-    [DataField]
-    public float EnergyConsumption = 12500;
-
-    /// <summary>
-    ///     Amount of time entities will be disabled
-    /// </summary>
-    [DataField("duration")]
-    public TimeSpan DisableDuration = TimeSpan.FromSeconds(15);
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-            => Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/BaseSpawnEntityEntityEffect.cs
new file mode 100644 (file)
index 0000000..3f5d5b3
--- /dev/null
@@ -0,0 +1,36 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// A type of <see cref="EntityEffectBase{T}"/> for effects that spawn entities by prototype.
+/// </summary>
+/// <typeparam name="T">The entity effect inheriting this BaseEffect</typeparam>
+/// <inheritdoc cref="EntityEffect"/>
+public abstract partial class BaseSpawnEntityEntityEffect<T> : EntityEffectBase<T> where T : BaseSpawnEntityEntityEffect<T>
+{
+    /// <summary>
+    /// Amount of entities we're spawning
+    /// </summary>
+    [DataField]
+    public int Number = 1;
+
+    /// <summary>
+    /// Prototype of the entity we're spawning
+    /// </summary>
+    [DataField (required: true)]
+    public EntProtoId Entity;
+
+    /// <summary>
+    /// Whether this spawning is predicted. Set false to not predict the spawn.
+    /// Entities with animations or that have random elements when spawned should set this to false.
+    /// </summary>
+    [DataField]
+    public bool Predicted = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-spawn-entity",
+            ("chance", Probability),
+            ("entname", IoCManager.Resolve<IPrototypeManager>().Index<EntityPrototype>(Entity).Name),
+            ("amount", Number));
+}
diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..9887ecd
--- /dev/null
@@ -0,0 +1,37 @@
+using Robust.Shared.Network;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// Spawns a number of entities of a given prototype at the coordinates of this entity.
+/// Amount is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityEntityEffectSystem : EntityEffectSystem<TransformComponent, SpawnEntity>
+{
+    [Dependency] private readonly INetManager _net = default!;
+
+    protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<SpawnEntity> args)
+    {
+        var quantity = args.Effect.Number * (int)Math.Floor(args.Scale);
+        var proto = args.Effect.Entity;
+
+        if (args.Effect.Predicted)
+        {
+            for (var i = 0; i < quantity; i++)
+            {
+                PredictedSpawnNextToOrDrop(proto, entity, entity.Comp);
+            }
+        }
+        else if (_net.IsServer)
+        {
+            for (var i = 0; i < quantity; i++)
+            {
+                SpawnNextToOrDrop(proto, entity, entity.Comp);
+            }
+        }
+    }
+}
+
+/// <inheritdoc cref="BaseSpawnEntityEntityEffect{T}"/>
+public sealed partial class SpawnEntity : BaseSpawnEntityEntityEffect<SpawnEntity>;
diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..6909d50
--- /dev/null
@@ -0,0 +1,51 @@
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// Spawns a given number of entities of a given prototype in a specified container owned by this entity.
+/// Returns if the prototype cannot spawn in the specified container.
+/// Amount is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityInContainerEntityEffectSystem : EntityEffectSystem<ContainerManagerComponent, SpawnEntityInContainer>
+{
+    [Dependency] private readonly INetManager _net = default!;
+
+    protected override void Effect(Entity<ContainerManagerComponent> entity, ref EntityEffectEvent<SpawnEntityInContainer> args)
+    {
+        var quantity = args.Effect.Number * (int)Math.Floor(args.Scale);
+        var proto = args.Effect.Entity;
+        var container = args.Effect.ContainerName;
+
+        if (args.Effect.Predicted)
+        {
+            for (var i = 0; i < quantity; i++)
+            {
+                // Stop trying to spawn if it fails
+                if (!PredictedTrySpawnInContainer(proto, entity, container, out _, entity.Comp))
+                    return;
+            }
+        }
+        else if (_net.IsServer)
+        {
+            for (var i = 0; i < quantity; i++)
+            {
+                // Stop trying to spawn if it fails
+                if (!TrySpawnInContainer(proto, entity, container, out _, entity.Comp))
+                    return;
+            }
+        }
+    }
+}
+
+/// <inheritdoc cref="BaseSpawnEntityEntityEffect{T}"/>
+public sealed partial class SpawnEntityInContainer : BaseSpawnEntityEntityEffect<SpawnEntityInContainer>
+{
+    /// <summary>
+    /// Name of the container we're trying to spawn into.
+    /// </summary>
+    [DataField(required: true)]
+    public string ContainerName;
+}
diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInContainerOrDropEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..e5b1e11
--- /dev/null
@@ -0,0 +1,49 @@
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// Spawns a given number of entities of a given prototype in a specified container owned by this entity.
+/// Acts like <see cref="SpawnEntityEntityEffectSystem"/> if it cannot spawn the prototype in the specified container.
+/// Amount is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityInContainerOrDropEntityEffectSystem : EntityEffectSystem<ContainerManagerComponent, SpawnEntityInContainerOrDrop>
+{
+    [Dependency] private readonly INetManager _net = default!;
+
+    protected override void Effect(Entity<ContainerManagerComponent> entity, ref EntityEffectEvent<SpawnEntityInContainerOrDrop> args)
+    {
+        var quantity = args.Effect.Number * (int)Math.Floor(args.Scale);
+        var proto = args.Effect.Entity;
+        var container = args.Effect.ContainerName;
+
+        var xform = Transform(entity);
+
+        if (args.Effect.Predicted)
+        {
+            for (var i = 0; i < quantity; i++)
+            {
+                PredictedSpawnInContainerOrDrop(proto, entity, container, xform, entity.Comp);
+            }
+        }
+        else if (_net.IsServer)
+        {
+            for (var i = 0; i < quantity; i++)
+            {
+                SpawnInContainerOrDrop(proto, entity, container, xform, entity.Comp);
+            }
+        }
+    }
+}
+
+/// <inheritdoc cref="BaseSpawnEntityEntityEffect{T}"/>
+public sealed partial class SpawnEntityInContainerOrDrop : BaseSpawnEntityEntityEffect<SpawnEntityInContainerOrDrop>
+{
+    /// <summary>
+    /// Name of the container we're trying to spawn into.
+    /// </summary>
+    [DataField(required: true)]
+    public string ContainerName;
+}
diff --git a/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EntitySpawning/SpawnEntityInInventoryEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..7514dae
--- /dev/null
@@ -0,0 +1,35 @@
+using Content.Shared.Inventory;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.EntitySpawning;
+
+/// <summary>
+/// Spawns an entity of a given prototype in a specified inventory slot owned by this entity.
+/// Fails if it cannot spawn the entity in the given slot.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SpawnEntityInInventoryEntityEffectSystem : EntityEffectSystem<InventoryComponent, SpawnEntityInInventory>
+{
+    [Dependency] private readonly InventorySystem _inventory = default!;
+
+    protected override void Effect(Entity<InventoryComponent> entity, ref EntityEffectEvent<SpawnEntityInInventory> args)
+    {
+        _inventory.SpawnItemInSlot(entity, args.Effect.Slot, args.Effect.Entity);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class SpawnEntityInInventory : EntityEffectBase<SpawnEntityInInventory>
+{
+    /// <summary>
+    /// Name of the slot we're spawning the item into.
+    /// </summary>
+    [DataField(required: true)]
+    public string Slot = string.Empty; // Rider is drunk and keeps yelling at me to fill this out or make required: true but, it is required true so it's just being an asshole.
+
+    /// <summary>
+    /// Prototype ID of item to spawn.
+    /// </summary>
+    [DataField(required: true)]
+    public EntProtoId Entity;
+}
diff --git a/Content.Shared/EntityEffects/Effects/EvenHealthChange.cs b/Content.Shared/EntityEffects/Effects/EvenHealthChange.cs
deleted file mode 100644 (file)
index 968d559..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-using Content.Shared.Damage;
-using Content.Shared.Damage.Prototypes;
-using Content.Shared.EntityEffects;
-using Content.Shared.FixedPoint;
-using Content.Shared.Localizations;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Version of <see cref="HealthChange"/> that distributes the healing to groups
-/// </summary>
-public sealed partial class EvenHealthChange : EntityEffect
-{
-    /// <summary>
-    /// Damage to heal, collected into entire damage groups.
-    /// </summary>
-    [DataField(required: true)]
-    public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> Damage = new();
-
-    /// <summary>
-    /// Should this effect scale the damage by the amount of chemical in the solution?
-    /// Useful for touch reactions, like styptic powder or acid.
-    /// Only usable if the EntityEffectBaseArgs is an EntityEffectReagentArgs.
-    /// </summary>
-    [DataField]
-    public bool ScaleByQuantity;
-
-    /// <summary>
-    /// Should this effect ignore damage modifiers?
-    /// </summary>
-    [DataField]
-    public bool IgnoreResistances = true;
-
-    protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        var damages = new List<string>();
-        var heals = false;
-        var deals = false;
-
-        var damagableSystem = entSys.GetEntitySystem<DamageableSystem>();
-        var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier;
-        var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier;
-
-        foreach (var (group, amount) in Damage)
-        {
-            var groupProto = prototype.Index(group);
-
-            var sign = FixedPoint2.Sign(amount);
-            var mod = 1f;
-
-            if (sign < 0)
-            {
-                heals = true;
-                mod = universalReagentHealModifier;
-            }
-            else if (sign > 0)
-            {
-                deals = true;
-                mod = universalReagentDamageModifier;
-            }
-
-            damages.Add(
-                Loc.GetString("health-change-display",
-                    ("kind", groupProto.LocalizedName),
-                    ("amount", MathF.Abs(amount.Float() * mod)),
-                    ("deltasign", sign)
-                ));
-        }
-
-        var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none");
-        return Loc.GetString("reagent-effect-guidebook-even-health-change",
-            ("chance", Probability),
-            ("changes", ContentLocalizationManager.FormatList(damages)),
-            ("healsordeals", healsordeals));
-    }
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (!args.EntityManager.TryGetComponent<DamageableComponent>(args.TargetEntity, out var damageable))
-            return;
-
-        var protoMan = IoCManager.Resolve<IPrototypeManager>();
-
-        var scale = FixedPoint2.New(1);
-
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale;
-        }
-
-        var damagableSystem = args.EntityManager.System<DamageableSystem>();
-        var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier;
-        var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier;
-
-        var dspec = new DamageSpecifier();
-
-        foreach (var (group, amount) in Damage)
-        {
-            var groupProto = protoMan.Index(group);
-            var groupDamage = new Dictionary<string, FixedPoint2>();
-            foreach (var damageId in groupProto.DamageTypes)
-            {
-                var damageAmount = damageable.Damage.DamageDict.GetValueOrDefault(damageId);
-                if (damageAmount != FixedPoint2.Zero)
-                    groupDamage.Add(damageId, damageAmount);
-            }
-
-            var sum = groupDamage.Values.Sum();
-            foreach (var (damageId, damageAmount) in groupDamage)
-            {
-                var existing = dspec.DamageDict.GetOrNew(damageId);
-                dspec.DamageDict[damageId] = existing + damageAmount / sum * amount;
-            }
-        }
-
-        if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1)
-        {
-            foreach (var (type, val) in dspec.DamageDict)
-            {
-                if (val < 0f)
-                {
-                    dspec.DamageDict[type] = val * universalReagentHealModifier;
-                }
-                if (val > 0f)
-                {
-                    dspec.DamageDict[type] = val * universalReagentDamageModifier;
-                }
-            }
-        }
-
-        damagableSystem.TryChangeDamage(
-            args.TargetEntity,
-            dspec * scale,
-            IgnoreResistances,
-            interruptsDoAfters: false);
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/EvenHealthChangeEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..33b2041
--- /dev/null
@@ -0,0 +1,114 @@
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Content.Shared.Localizations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Evenly adjust the damage types in a damage group by up to a specified total on this entity.
+/// Total adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EvenHealthChangeEntityEffectSystem : EntityEffectSystem<DamageableComponent, EvenHealthChange>
+{
+    [Dependency] private readonly DamageableSystem _damageable = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+
+    protected override void Effect(Entity<DamageableComponent> entity, ref EntityEffectEvent<EvenHealthChange> args)
+    {
+        var damageSpec = new DamageSpecifier();
+
+        foreach (var (group, amount) in args.Effect.Damage)
+        {
+            var groupProto = _proto.Index(group);
+            var groupDamage = new Dictionary<string, FixedPoint2>();
+            foreach (var damageId in groupProto.DamageTypes)
+            {
+                var damageAmount = entity.Comp.Damage.DamageDict.GetValueOrDefault(damageId);
+                if (damageAmount != FixedPoint2.Zero)
+                    groupDamage.Add(damageId, damageAmount);
+            }
+
+            var sum = groupDamage.Values.Sum();
+            foreach (var (damageId, damageAmount) in groupDamage)
+            {
+                var existing = damageSpec.DamageDict.GetOrNew(damageId);
+                damageSpec.DamageDict[damageId] = existing + damageAmount / sum * amount;
+            }
+        }
+
+        damageSpec *= args.Scale;
+
+        _damageable.TryChangeDamage(
+            entity,
+            damageSpec,
+            args.Effect.IgnoreResistances,
+            interruptsDoAfters: false,
+            damageable: entity.Comp);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class EvenHealthChange : EntityEffectBase<EvenHealthChange>
+{
+    /// <summary>
+    /// Damage to heal, collected into entire damage groups.
+    /// </summary>
+    [DataField(required: true)]
+    public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> Damage = new();
+
+    /// <summary>
+    /// Should this effect ignore damage modifiers?
+    /// </summary>
+    [DataField]
+    public bool IgnoreResistances = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        var damages = new List<string>();
+        var heals = false;
+        var deals = false;
+
+        var damagableSystem = entSys.GetEntitySystem<DamageableSystem>();
+        var universalReagentDamageModifier = damagableSystem.UniversalReagentDamageModifier;
+        var universalReagentHealModifier = damagableSystem.UniversalReagentHealModifier;
+
+        foreach (var (group, amount) in Damage)
+        {
+            var groupProto = prototype.Index(group);
+
+            var sign = FixedPoint2.Sign(amount);
+            float mod;
+
+            switch (sign)
+            {
+                case < 0:
+                    heals = true;
+                    mod = universalReagentHealModifier;
+                    break;
+                case > 0:
+                    deals = true;
+                    mod = universalReagentDamageModifier;
+                    break;
+                default:
+                    continue; // Don't need to show damage types of 0...
+            }
+
+            damages.Add(
+                Loc.GetString("health-change-display",
+                    ("kind", groupProto.LocalizedName),
+                    ("amount", MathF.Abs(amount.Float() * mod)),
+                    ("deltasign", sign)
+                ));
+        }
+
+        var healsordeals = heals ? deals ? "both" : "heals" : deals ? "deals" : "none";
+        return Loc.GetString("entity-effect-guidebook-even-health-change",
+            ("chance", Probability),
+            ("changes", ContentLocalizationManager.FormatList(damages)),
+            ("healsordeals", healsordeals));
+    }
+}
diff --git a/Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs b/Content.Shared/EntityEffects/Effects/ExtinguishReaction.cs
deleted file mode 100644 (file)
index 11e776a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using Content.Shared.Atmos;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects
-{
-    public sealed partial class ExtinguishReaction : EntityEffect
-    {
-        /// <summary>
-        ///     Amount of firestacks reduced.
-        /// </summary>
-        [DataField]
-        public float FireStacksAdjustment = -1.5f;
-
-        protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-            => Loc.GetString("reagent-effect-guidebook-extinguish-reaction", ("chance", Probability));
-
-        public override void Effect(EntityEffectBaseArgs args)
-        {
-            var ev = new ExtinguishEvent
-            {
-                FireStacksAdjustment = FireStacksAdjustment,
-            };
-
-            if (args is EntityEffectReagentArgs reagentArgs)
-            {
-                ev.FireStacksAdjustment *= (float)reagentArgs.Quantity;
-            }
-
-            args.EntityManager.EventBus.RaiseLocalEvent(args.TargetEntity, ref ev);
-        }
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/FlammableReaction.cs b/Content.Shared/EntityEffects/Effects/FlammableReaction.cs
deleted file mode 100644 (file)
index 9f7d504..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Shared.Database;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class FlammableReaction : EventEntityEffect<FlammableReaction>
-{
-    [DataField]
-    public float Multiplier = 0.05f;
-
-    // The fire stack multiplier if fire stacks already exist on target, only works if 0 or greater
-    [DataField]
-    public float MultiplierOnExisting = -1f;
-
-    public override bool ShouldLog => true;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-flammable-reaction", ("chance", Probability));
-
-    public override LogImpact LogImpact => LogImpact.Medium;
-}
diff --git a/Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs b/Content.Shared/EntityEffects/Effects/FlashReactionEffect.cs
deleted file mode 100644 (file)
index c238e94..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-[DataDefinition]
-public sealed partial class FlashReactionEffect : EventEntityEffect<FlashReactionEffect>
-{
-    /// <summary>
-    ///     Flash range per unit of reagent.
-    /// </summary>
-    [DataField]
-    public float RangePerUnit = 0.2f;
-
-    /// <summary>
-    ///     Maximum flash range.
-    /// </summary>
-    [DataField]
-    public float MaxRange = 10f;
-
-    /// <summary>
-    ///     How much to entities are slowed down.
-    /// </summary>
-    [DataField]
-    public float SlowTo = 0.5f;
-
-    /// <summary>
-    ///     The time entities will be flashed.
-    ///     The default is chosen to be better than the hand flash so it is worth using it for grenades etc.
-    /// </summary>
-    [DataField]
-    public TimeSpan Duration = TimeSpan.FromSeconds(4);
-
-    /// <summary>
-    ///     The prototype ID used for the visual effect.
-    /// </summary>
-    [DataField]
-    public EntProtoId? FlashEffectPrototype = "ReactionFlash";
-
-    /// <summary>
-    ///     The sound the flash creates.
-    /// </summary>
-    [DataField]
-    public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg");
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-flash-reaction-effect", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/Glow.cs b/Content.Shared/EntityEffects/Effects/Glow.cs
deleted file mode 100644 (file)
index 394d406..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-using Content.Shared.EntityEffects;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Makes a mob glow.
-/// </summary>
-public sealed partial class Glow : EntityEffect
-{
-    [DataField]
-    public float Radius = 2f;
-
-    [DataField]
-    public Color Color = Color.Black;
-
-    private static readonly List<Color> Colors = new()
-    {
-        Color.White,
-        Color.Red,
-        Color.Yellow,
-        Color.Green,
-        Color.Blue,
-        Color.Purple,
-        Color.Pink
-    };
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (Color == Color.Black)
-        {
-            var random = IoCManager.Resolve<IRobustRandom>();
-            Color = random.Pick(Colors);
-        }
-
-        var lightSystem = args.EntityManager.System<SharedPointLightSystem>();
-        var light = lightSystem.EnsureLight(args.TargetEntity);
-        lightSystem.SetRadius(args.TargetEntity, Radius, light);
-        lightSystem.SetColor(args.TargetEntity, Color, light);
-        lightSystem.SetCastShadows(args.TargetEntity, false, light); // this is expensive, and botanists make lots of plants
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return "TODO";
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/GlowEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..05e4fe2
--- /dev/null
@@ -0,0 +1,55 @@
+using Robust.Shared.Network;
+using Robust.Shared.Random;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Causes this entity to glow.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class GlowEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Glow>
+{
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SharedPointLightSystem _lightSystem = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Glow> args)
+    {
+        var color = args.Effect.Color;
+
+        if (color == Color.Black)
+        {
+            // TODO: When we get proper predicted RNG remove this check...
+            if (_net.IsClient)
+                return;
+
+            color = _random.Pick(Colors);
+        }
+
+        var light = _lightSystem.EnsureLight(entity);
+        _lightSystem.SetRadius(entity, args.Effect.Radius, light);
+        _lightSystem.SetColor(entity, color, light);
+        _lightSystem.SetCastShadows(entity, false, light); // this is expensive, and botanists make lots of plants
+    }
+
+    public static readonly List<Color> Colors = new()
+    {
+        Color.White,
+        Color.Red,
+        Color.Yellow,
+        Color.Green,
+        Color.Blue,
+        Color.Purple,
+        Color.Pink
+    };
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Glow : EntityEffectBase<Glow>
+{
+    [DataField]
+    public float Radius = 2f;
+
+    [DataField]
+    public Color Color = Color.Black;
+}
diff --git a/Content.Shared/EntityEffects/Effects/HealthChange.cs b/Content.Shared/EntityEffects/Effects/HealthChange.cs
deleted file mode 100644 (file)
index 17c24f6..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-using Content.Shared.Damage;
-using Content.Shared.Damage.Prototypes;
-using Content.Shared.EntityEffects;
-using Content.Shared.FixedPoint;
-using Content.Shared.Localizations;
-using Robust.Shared.Prototypes;
-using System.Linq;
-using System.Text.Json.Serialization;
-
-namespace Content.Shared.EntityEffects.Effects
-{
-    /// <summary>
-    /// Default metabolism used for medicine reagents.
-    /// </summary>
-    public sealed partial class HealthChange : EntityEffect
-    {
-        /// <summary>
-        /// Damage to apply every cycle. Damage Ignores resistances.
-        /// </summary>
-        [DataField(required: true)]
-        [JsonPropertyName("damage")]
-        public DamageSpecifier Damage = default!;
-
-        /// <summary>
-        ///     Should this effect scale the damage by the amount of chemical in the solution?
-        ///     Useful for touch reactions, like styptic powder or acid.
-        ///     Only usable if the EntityEffectBaseArgs is an EntityEffectReagentArgs.
-        /// </summary>
-        [DataField]
-        [JsonPropertyName("scaleByQuantity")]
-        public bool ScaleByQuantity;
-
-        [DataField]
-        [JsonPropertyName("ignoreResistances")]
-        public bool IgnoreResistances = true;
-
-        protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        {
-            var damages = new List<string>();
-            var heals = false;
-            var deals = false;
-
-            var damageSpec = new DamageSpecifier(Damage);
-
-            var universalReagentDamageModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentDamageModifier;
-            var universalReagentHealModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentHealModifier;
-
-            if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1)
-            {
-                foreach (var (type, val) in damageSpec.DamageDict)
-                {
-                    if (val < 0f)
-                    {
-                        damageSpec.DamageDict[type] = val * universalReagentHealModifier;
-                    }
-                    if (val > 0f)
-                    {
-                        damageSpec.DamageDict[type] = val * universalReagentDamageModifier;
-                    }
-                }
-            }
-
-            damageSpec = entSys.GetEntitySystem<DamageableSystem>().ApplyUniversalAllModifiers(damageSpec);
-
-            foreach (var (kind, amount) in damageSpec.DamageDict)
-            {
-                var sign = FixedPoint2.Sign(amount);
-
-                if (sign < 0)
-                    heals = true;
-                if (sign > 0)
-                    deals = true;
-
-                damages.Add(
-                    Loc.GetString("health-change-display",
-                        ("kind", prototype.Index<DamageTypePrototype>(kind).LocalizedName),
-                        ("amount", MathF.Abs(amount.Float())),
-                        ("deltasign", sign)
-                    ));
-            }
-
-            var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none");
-
-            return Loc.GetString("reagent-effect-guidebook-health-change",
-                ("chance", Probability),
-                ("changes", ContentLocalizationManager.FormatList(damages)),
-                ("healsordeals", healsordeals));
-        }
-
-        public override void Effect(EntityEffectBaseArgs args)
-        {
-            var scale = FixedPoint2.New(1);
-            var damageSpec = new DamageSpecifier(Damage);
-
-            if (args is EntityEffectReagentArgs reagentArgs)
-            {
-                scale = ScaleByQuantity ? reagentArgs.Quantity * reagentArgs.Scale : reagentArgs.Scale;
-            }
-
-            var universalReagentDamageModifier = args.EntityManager.System<DamageableSystem>().UniversalReagentDamageModifier;
-            var universalReagentHealModifier = args.EntityManager.System<DamageableSystem>().UniversalReagentHealModifier;
-
-            if (universalReagentDamageModifier != 1 || universalReagentHealModifier != 1)
-            {
-                foreach (var (type, val) in damageSpec.DamageDict)
-                {
-                    if (val < 0f)
-                    {
-                        damageSpec.DamageDict[type] = val * universalReagentHealModifier;
-                    }
-                    if (val > 0f)
-                    {
-                        damageSpec.DamageDict[type] = val * universalReagentDamageModifier;
-                    }
-                }
-            }
-
-            args.EntityManager.System<DamageableSystem>()
-                .TryChangeDamage(
-                    args.TargetEntity,
-                    damageSpec * scale,
-                    IgnoreResistances,
-                    interruptsDoAfters: false);
-        }
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/HealthChangeEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..89948cd
--- /dev/null
@@ -0,0 +1,91 @@
+using Content.Shared.Damage;
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Content.Shared.Localizations;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Adjust the damages on this entity by specified amounts.
+/// Amounts are modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class HealthChangeEntityEffectSystem : EntityEffectSystem<DamageableComponent, HealthChange>
+{
+    [Dependency] private readonly DamageableSystem _damageable = default!;
+
+    protected override void Effect(Entity<DamageableComponent> entity, ref EntityEffectEvent<HealthChange> args)
+    {
+        var damageSpec = new DamageSpecifier(args.Effect.Damage);
+
+        damageSpec *= args.Scale;
+
+        _damageable.TryChangeDamage(
+                entity,
+                damageSpec,
+                args.Effect.IgnoreResistances,
+                interruptsDoAfters: false);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class HealthChange : EntityEffectBase<HealthChange>
+{
+    /// <summary>
+    /// Damage to apply every cycle. Damage Ignores resistances.
+    /// </summary>
+    [DataField(required: true)]
+    public DamageSpecifier Damage = default!;
+
+    [DataField]
+    public bool IgnoreResistances = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        {
+            var damages = new List<string>();
+            var heals = false;
+            var deals = false;
+
+            var damageSpec = new DamageSpecifier(Damage);
+
+            var universalReagentDamageModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentDamageModifier;
+            var universalReagentHealModifier = entSys.GetEntitySystem<DamageableSystem>().UniversalReagentHealModifier;
+
+            damageSpec = entSys.GetEntitySystem<DamageableSystem>().ApplyUniversalAllModifiers(damageSpec);
+
+            foreach (var (kind, amount) in damageSpec.DamageDict)
+            {
+                var sign = FixedPoint2.Sign(amount);
+                float mod;
+
+                switch (sign)
+                {
+                    case < 0:
+                        heals = true;
+                        mod = universalReagentHealModifier;
+                        break;
+                    case > 0:
+                        deals = true;
+                        mod = universalReagentDamageModifier;
+                        break;
+                    default:
+                        continue; // Don't need to show damage types of 0...
+                }
+
+                damages.Add(
+                    Loc.GetString("health-change-display",
+                        ("kind", prototype.Index<DamageTypePrototype>(kind).LocalizedName),
+                        ("amount", MathF.Abs(amount.Float() * mod)),
+                        ("deltasign", sign)
+                    ));
+            }
+
+            var healsordeals = heals ? (deals ? "both" : "heals") : (deals ? "deals" : "none");
+
+            return Loc.GetString("entity-effect-guidebook-health-change",
+                ("chance", Probability),
+                ("changes", ContentLocalizationManager.FormatList(damages)),
+                ("healsordeals", healsordeals));
+        }
+}
diff --git a/Content.Shared/EntityEffects/Effects/Ignite.cs b/Content.Shared/EntityEffects/Effects/Ignite.cs
deleted file mode 100644 (file)
index 707ecc3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Content.Shared.Database;
-using Content.Shared.EntityEffects;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Ignites a mob.
-/// </summary>
-public sealed partial class Ignite : EventEntityEffect<Ignite>
-{
-    public override bool ShouldLog => true;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-ignite", ("chance", Probability));
-
-    public override LogImpact LogImpact => LogImpact.Medium;
-}
diff --git a/Content.Shared/EntityEffects/Effects/MakeSentient.cs b/Content.Shared/EntityEffects/Effects/MakeSentient.cs
deleted file mode 100644 (file)
index 9c70eb4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-using Content.Shared.Mind.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class MakeSentient : EventEntityEffect<MakeSentient>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-make-sentient", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs b/Content.Shared/EntityEffects/Effects/MakeSentientEntityEffect.cs
new file mode 100644 (file)
index 0000000..4beb214
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class MakeSentient : EntityEffectBase<MakeSentient>
+{
+    /// <summary>
+    /// Description for the ghost role created by this effect.
+    /// </summary>
+    [DataField]
+    public LocId RoleDescription = "ghost-role-information-cognizine-description";
+
+    /// <summary>
+    /// Whether we give the target the ability to speak coherently.
+    /// </summary>
+    [DataField]
+    public bool AllowSpeech = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-make-sentient", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs b/Content.Shared/EntityEffects/Effects/ModifyBleedAmount.cs
deleted file mode 100644 (file)
index 9f15652..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class ModifyBleedAmount : EventEntityEffect<ModifyBleedAmount>
-{
-    [DataField]
-    public bool Scaled = false;
-
-    [DataField]
-    public float Amount = -1.0f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-modify-bleed-amount", ("chance", Probability),
-            ("deltasign", MathF.Sign(Amount)));
-}
diff --git a/Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs b/Content.Shared/EntityEffects/Effects/ModifyBloodLevel.cs
deleted file mode 100644 (file)
index 06c026f..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Shared.FixedPoint;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class ModifyBloodLevel : EventEntityEffect<ModifyBloodLevel>
-{
-    [DataField]
-    public bool Scaled = false;
-
-    [DataField]
-    public FixedPoint2 Amount = 1.0f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-modify-blood-level", ("chance", Probability),
-            ("deltasign", MathF.Sign(Amount.Float())));
-}
diff --git a/Content.Shared/EntityEffects/Effects/ModifyLungGas.cs b/Content.Shared/EntityEffects/Effects/ModifyLungGas.cs
deleted file mode 100644 (file)
index 45dc8c8..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.Atmos;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class ModifyLungGas : EventEntityEffect<ModifyLungGas>
-{
-    [DataField("ratios", required: true)]
-    public Dictionary<Gas, float> Ratios = default!;
-
-    // JUSTIFICATION: This is internal magic that players never directly interact with.
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => null;
-}
diff --git a/Content.Shared/EntityEffects/Effects/MovespeedModifier.cs b/Content.Shared/EntityEffects/Effects/MovespeedModifier.cs
deleted file mode 100644 (file)
index 5e72746..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Movement.Systems;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Default metabolism for stimulants and tranqs. Attempts to find a MovementSpeedModifier on the target,
-/// adding one if not there and to change the movespeed
-/// </summary>
-public sealed partial class MovespeedModifier : EntityEffect
-{
-    /// <summary>
-    /// How much the entities' walk speed is multiplied by.
-    /// </summary>
-    [DataField]
-    public float WalkSpeedModifier { get; set; } = 1;
-
-    /// <summary>
-    /// How much the entities' run speed is multiplied by.
-    /// </summary>
-    [DataField]
-    public float SprintSpeedModifier { get; set; } = 1;
-
-    /// <summary>
-    /// How long the modifier applies (in seconds).
-    /// Is scaled by reagent amount if used with an EntityEffectReagentArgs.
-    /// </summary>
-    [DataField]
-    public float StatusLifetime = 2f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return Loc.GetString("reagent-effect-guidebook-movespeed-modifier",
-            ("chance", Probability),
-            ("walkspeed", WalkSpeedModifier),
-            ("time", StatusLifetime));
-    }
-
-    /// <summary>
-    /// Remove reagent at set rate, changes the movespeed modifiers and adds a MovespeedModifierMetabolismComponent if not already there.
-    /// </summary>
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var status = args.EntityManager.EnsureComponent<MovespeedModifierMetabolismComponent>(args.TargetEntity);
-
-        // Only refresh movement if we need to.
-        var modified = !status.WalkSpeedModifier.Equals(WalkSpeedModifier) ||
-                       !status.SprintSpeedModifier.Equals(SprintSpeedModifier);
-
-        status.WalkSpeedModifier = WalkSpeedModifier;
-        status.SprintSpeedModifier = SprintSpeedModifier;
-
-        // only going to scale application time
-        var statusLifetime = StatusLifetime;
-
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            statusLifetime *= reagentArgs.Scale.Float();
-        }
-
-        IncreaseTimer(status, statusLifetime, args.EntityManager, args.TargetEntity);
-
-        if (modified)
-            args.EntityManager.System<MovementSpeedModifierSystem>().RefreshMovementSpeedModifiers(args.TargetEntity);
-    }
-    private void IncreaseTimer(MovespeedModifierMetabolismComponent status, float time, IEntityManager entityManager, EntityUid uid)
-    {
-        var gameTiming = IoCManager.Resolve<IGameTiming>();
-
-        var offsetTime = Math.Max(status.ModifierTimer.TotalSeconds, gameTiming.CurTime.TotalSeconds);
-
-        status.ModifierTimer = TimeSpan.FromSeconds(offsetTime + time);
-
-        entityManager.Dirty(uid, status);
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/Oxygenate.cs b/Content.Shared/EntityEffects/Effects/Oxygenate.cs
deleted file mode 100644 (file)
index e990a3f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Oxygenate : EventEntityEffect<Oxygenate>
-{
-    [DataField]
-    public float Factor = 1f;
-
-    // JUSTIFICATION: This is internal magic that players never directly interact with.
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => null;
-}
diff --git a/Content.Shared/EntityEffects/Effects/Paralyze.cs b/Content.Shared/EntityEffects/Effects/Paralyze.cs
deleted file mode 100644 (file)
index 2a22700..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-using Content.Shared.Stunnable;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Paralyze : EntityEffect
-{
-    [DataField] public double ParalyzeTime = 2;
-
-    /// <remarks>
-    ///     true - refresh paralyze time,  false - accumulate paralyze time
-    /// </remarks>
-    [DataField] public bool Refresh = true;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString(
-            "reagent-effect-guidebook-paralyze",
-            ("chance", Probability),
-            ("time", ParalyzeTime)
-        );
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var paralyzeTime = ParalyzeTime;
-
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            paralyzeTime *= (double)reagentArgs.Scale;
-        }
-
-        var stunSystem = args.EntityManager.System<SharedStunSystem>();
-        _ = Refresh
-            ? stunSystem.TryUpdateParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime))
-            : stunSystem.TryAddParalyzeDuration(args.TargetEntity, TimeSpan.FromSeconds(paralyzeTime));
-    }
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustAttribute.cs
deleted file mode 100644 (file)
index 2c80464..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-using Content.Shared.EntityEffects;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using System.Diagnostics.CodeAnalysis;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-[ImplicitDataDefinitionForInheritors]
-public abstract partial class PlantAdjustAttribute<T> : EventEntityEffect<T> where T : PlantAdjustAttribute<T>
-{
-    [DataField]
-    public float Amount { get; protected set; } = 1;
-
-    /// <summary>
-    /// Localisation key for the name of the adjusted attribute. Used for guidebook descriptions.
-    /// </summary>
-    [DataField]
-    public abstract string GuidebookAttributeName { get; set; }
-
-    /// <summary>
-    /// Whether the attribute in question is a good thing. Used for guidebook descriptions to determine the color of the number.
-    /// </summary>
-    [DataField]
-    public virtual bool GuidebookIsAttributePositive { get; protected set; } = true;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        string color;
-        if (GuidebookIsAttributePositive ^ Amount < 0.0)
-        {
-            color = "green";
-        }
-        else
-        {
-            color = "red";
-        }
-        return Loc.GetString("reagent-effect-guidebook-plant-attribute", ("attribute", Loc.GetString(GuidebookAttributeName)), ("amount", Amount.ToString("0.00")), ("colorName", color), ("chance", Probability));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustHealth.cs
deleted file mode 100644 (file)
index c1e7189..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustHealth : PlantAdjustAttribute<PlantAdjustHealth>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-health";
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationLevel.cs
deleted file mode 100644 (file)
index 6610adf..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustMutationLevel : PlantAdjustAttribute<PlantAdjustMutationLevel>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-level";
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustMutationMod.cs
deleted file mode 100644 (file)
index 91be222..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustMutationMod : PlantAdjustAttribute<PlantAdjustMutationMod>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-mutation-mod";
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustNutrition.cs
deleted file mode 100644 (file)
index db01d5d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustNutrition : PlantAdjustAttribute<PlantAdjustNutrition>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-nutrition";
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustPotency.cs
deleted file mode 100644 (file)
index 971f05f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-// using Content.Server.Botany.Systems;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-/// <summary>
-///     Handles increase or decrease of plant potency.
-/// </summary>
-
-public sealed partial class PlantAdjustPotency : PlantAdjustAttribute<PlantAdjustPotency>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-potency";
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAdjustWater.cs
deleted file mode 100644 (file)
index 610d022..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAdjustWater : PlantAdjustAttribute<PlantAdjustWater>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-water";
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantAffectGrowth.cs
deleted file mode 100644 (file)
index 36b8f57..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantAffectGrowth : PlantAdjustAttribute<PlantAffectGrowth>
-{
-    public override string GuidebookAttributeName { get; set; } = "plant-attribute-growth";
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantChangeStat.cs
deleted file mode 100644 (file)
index 66cfb66..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantChangeStat : EventEntityEffect<PlantChangeStat>
-{
-    [DataField]
-    public string TargetValue;
-
-    [DataField]
-    public float MinValue;
-
-    [DataField]
-    public float MaxValue;
-
-    [DataField]
-    public int Steps;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        throw new NotImplementedException();
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantCryoxadone.cs
deleted file mode 100644 (file)
index 0dabf1f..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantCryoxadone : EventEntityEffect<PlantCryoxadone>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-cryoxadone", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDestroySeeds.cs
deleted file mode 100644 (file)
index 9f16c35..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-/// <summary>
-///     Handles removal of seeds on a plant.
-/// </summary>
-
-public sealed partial class PlantDestroySeeds : EventEntityEffect<PlantDestroySeeds>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
-        Loc.GetString("reagent-effect-guidebook-plant-seeds-remove", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantDiethylamine.cs
deleted file mode 100644 (file)
index 9feccbf..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantDiethylamine : EventEntityEffect<PlantDiethylamine>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-diethylamine", ("chance", Probability));
-}
-
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantPhalanximine.cs
deleted file mode 100644 (file)
index 9dc5140..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class PlantPhalanximine : EventEntityEffect<PlantPhalanximine>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-phalanximine", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/PlantRestoreSeeds.cs
deleted file mode 100644 (file)
index 51ce353..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-/// <summary>
-///     Handles restoral of seeds on a plant.
-/// </summary>
-public sealed partial class PlantRestoreSeeds : EventEntityEffect<PlantRestoreSeeds>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
-        Loc.GetString("reagent-effect-guidebook-plant-seeds-add", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs b/Content.Shared/EntityEffects/Effects/PlantMetabolism/RobustHarvest.cs
deleted file mode 100644 (file)
index 6ba37c9..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects.PlantMetabolism;
-
-public sealed partial class RobustHarvest : EventEntityEffect<RobustHarvest>
-{
-    [DataField]
-    public int PotencyLimit = 50;
-
-    [DataField]
-    public int PotencyIncrease = 3;
-
-    [DataField]
-    public int PotencySeedlessThreshold = 30;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-plant-robust-harvest", ("seedlesstreshold", PotencySeedlessThreshold), ("limit", PotencyLimit), ("increase", PotencyIncrease), ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs b/Content.Shared/EntityEffects/Effects/PlantMutateChemicals.cs
deleted file mode 100644 (file)
index 9a3408b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Shared.Random;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     changes the chemicals available in a plant's produce
-/// </summary>
-public sealed partial class PlantMutateChemicals : EventEntityEffect<PlantMutateChemicals>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return "TODO";
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMutateGases.cs b/Content.Shared/EntityEffects/Effects/PlantMutateGases.cs
deleted file mode 100644 (file)
index 5eb5b1d..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using System.Linq;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     changes the gases that a plant or produce create.
-/// </summary>
-public sealed partial class PlantMutateExudeGasses : EventEntityEffect<PlantMutateExudeGasses>
-{
-    [DataField]
-    public float MinValue = 0.01f;
-
-    [DataField]
-    public float MaxValue = 0.5f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return "TODO";
-    }
-}
-
-/// <summary>
-///     changes the gases that a plant or produce consumes.
-/// </summary>
-public sealed partial class PlantMutateConsumeGasses : EventEntityEffect<PlantMutateConsumeGasses>
-{
-    [DataField]
-    public float MinValue = 0.01f;
-
-    [DataField]
-    public float MaxValue = 0.5f;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return "TODO";
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs b/Content.Shared/EntityEffects/Effects/PlantMutateHarvest.cs
deleted file mode 100644 (file)
index 84d6293..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Upgrades a plant's harvest type.
-/// </summary>
-public sealed partial class PlantMutateHarvest : EventEntityEffect<PlantMutateHarvest>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return "TODO";
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs b/Content.Shared/EntityEffects/Effects/PlantSpeciesChange.cs
deleted file mode 100644 (file)
index e2acc4c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Changes a plant into one of the species its able to mutate into.
-/// </summary>
-public sealed partial class PlantSpeciesChange : EventEntityEffect<PlantSpeciesChange>
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        return "TODO";
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/Polymorph.cs b/Content.Shared/EntityEffects/Effects/Polymorph.cs
deleted file mode 100644 (file)
index 65711ff..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-using Content.Shared.Polymorph;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class Polymorph : EventEntityEffect<Polymorph>
-{
-    /// <summary>
-    ///     What polymorph prototype is used on effect
-    /// </summary>
-    [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<PolymorphPrototype>))]
-    public string PolymorphPrototype { get; set; }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    => Loc.GetString("reagent-effect-guidebook-make-polymorph",
-            ("chance", Probability), ("entityname",
-                prototype.Index<EntityPrototype>(prototype.Index<PolymorphPrototype>(PolymorphPrototype).Configuration.Entity).Name));
-}
diff --git a/Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs b/Content.Shared/EntityEffects/Effects/PolymorphEntityEffect.cs
new file mode 100644 (file)
index 0000000..c0d80df
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Shared.Polymorph;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Polymorph : EntityEffectBase<Polymorph>
+{
+    /// <summary>
+    ///     What polymorph prototype is used on effect
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<PolymorphPrototype> Prototype;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-make-polymorph",
+            ("chance", Probability),
+            ("entityname", prototype.Index<EntityPrototype>(prototype.Index(Prototype).Configuration.Entity).Name));
+}
diff --git a/Content.Shared/EntityEffects/Effects/PopupMessage.cs b/Content.Shared/EntityEffects/Effects/PopupMessage.cs
deleted file mode 100644 (file)
index a837f81..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-using Content.Shared.Popups;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-
-namespace Content.Shared.EntityEffects.Effects
-{
-    public sealed partial class PopupMessage : EntityEffect
-    {
-        [DataField(required: true)]
-        public string[] Messages = default!;
-
-        [DataField]
-        public PopupRecipients Type = PopupRecipients.Local;
-
-        [DataField]
-        public PopupType VisualType = PopupType.Small;
-
-        // JUSTIFICATION: This is purely cosmetic.
-        protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-            => null;
-
-        public override void Effect(EntityEffectBaseArgs args)
-        {
-            var popupSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedPopupSystem>();
-            var random = IoCManager.Resolve<IRobustRandom>();
-
-            var msg = random.Pick(Messages);
-            var msgArgs = new (string, object)[]
-            {
-                ("entity", args.TargetEntity),
-            };
-
-            if (args is EntityEffectReagentArgs reagentArgs)
-            {
-                msgArgs = new (string, object)[]
-                {
-                    ("entity", reagentArgs.TargetEntity),
-                    ("organ", reagentArgs.OrganEntity.GetValueOrDefault()),
-                };
-            }
-
-            if (Type == PopupRecipients.Local)
-                popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.TargetEntity, args.TargetEntity, VisualType);
-            else if (Type == PopupRecipients.Pvs)
-                popupSys.PopupEntity(Loc.GetString(msg, msgArgs), args.TargetEntity, VisualType);
-        }
-    }
-
-    public enum PopupRecipients
-    {
-        Pvs,
-        Local
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/ReduceRotting.cs b/Content.Shared/EntityEffects/Effects/ReduceRotting.cs
deleted file mode 100644 (file)
index b5f2a7a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-using Content.Shared.Atmos.Rotting;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Reduces the rotting accumulator on the patient, making them revivable.
-/// </summary>
-public sealed partial class ReduceRotting : EntityEffect
-{
-    [DataField("seconds")]
-    public double RottingAmount = 10;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-reduce-rotting",
-            ("chance", Probability),
-            ("time", RottingAmount));
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            if (reagentArgs.Scale != 1f)
-                return;
-        }
-
-        var rottingSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedRottingSystem>();
-
-        rottingSys.ReduceAccumulator(args.TargetEntity, TimeSpan.FromSeconds(RottingAmount));
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs b/Content.Shared/EntityEffects/Effects/ResetNarcolepsy.cs
deleted file mode 100644 (file)
index 009cf91..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Reset narcolepsy timer
-/// </summary>
-public sealed partial class ResetNarcolepsy : EventEntityEffect<ResetNarcolepsy>
-{
-    /// <summary>
-    /// The # of seconds the effect resets the narcolepsy timer to
-    /// </summary>
-    [DataField("TimerReset")]
-    public TimeSpan TimerReset = TimeSpan.FromSeconds(600);
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-reset-narcolepsy", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/ResetNarcolepsyEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..973b139
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Traits.Assorted;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Resets the narcolepsy timer on a given entity.
+/// The new duration of the timer is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ResetNarcolepsyEntityEffectSystem : EntityEffectSystem<NarcolepsyComponent, ResetNarcolepsy>
+{
+    [Dependency] private readonly NarcolepsySystem _narcolepsy = default!;
+
+    protected override void Effect(Entity<NarcolepsyComponent> entity, ref EntityEffectEvent<ResetNarcolepsy> args)
+    {
+        var timer = args.Effect.TimerReset * args.Scale;
+
+        _narcolepsy.AdjustNarcolepsyTimer(entity.AsNullable(), timer);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ResetNarcolepsy : EntityEffectBase<ResetNarcolepsy>
+{
+    /// <summary>
+    /// The time we set our narcolepsy timer to.
+    /// </summary>
+    [DataField("TimerReset")]
+    public TimeSpan TimerReset = TimeSpan.FromSeconds(600);
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-reset-narcolepsy", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/SatiateHunger.cs b/Content.Shared/EntityEffects/Effects/SatiateHunger.cs
deleted file mode 100644 (file)
index 3e7af88..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Attempts to find a HungerComponent on the target,
-/// and to update it's hunger values.
-/// </summary>
-public sealed partial class SatiateHunger : EntityEffect
-{
-    private const float DefaultNutritionFactor = 3.0f;
-
-    /// <summary>
-    ///     How much hunger is satiated.
-    ///     Is multiplied by quantity if used with EntityEffectReagentArgs.
-    /// </summary>
-    [DataField("factor")] public float NutritionFactor { get; set; } = DefaultNutritionFactor;
-
-    //Remove reagent at set rate, satiate hunger if a HungerComponent can be found
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var entman = args.EntityManager;
-        if (!entman.TryGetComponent(args.TargetEntity, out HungerComponent? hunger))
-            return;
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            entman.System<HungerSystem>().ModifyHunger(reagentArgs.TargetEntity, NutritionFactor * (float) reagentArgs.Quantity, hunger);
-        }
-        else
-        {
-            entman.System<HungerSystem>().ModifyHunger(args.TargetEntity, NutritionFactor, hunger);
-        }
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-satiate-hunger", ("chance", Probability), ("relative", NutritionFactor / DefaultNutritionFactor));
-}
diff --git a/Content.Shared/EntityEffects/Effects/SatiateThirst.cs b/Content.Shared/EntityEffects/Effects/SatiateThirst.cs
deleted file mode 100644 (file)
index 21a055b..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
-/// and to update it's thirst values.
-/// </summary>
-public sealed partial class SatiateThirst : EntityEffect
-{
-    private const float DefaultHydrationFactor = 3.0f;
-
-    /// How much thirst is satiated each tick. Not currently tied to
-    /// rate or anything.
-    [DataField("factor")]
-    public float HydrationFactor { get; set; } = DefaultHydrationFactor;
-
-    /// Satiate thirst if a ThirstComponent can be found
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var uid = args.TargetEntity;
-        if (args.EntityManager.TryGetComponent(uid, out ThirstComponent? thirst))
-            args.EntityManager.System<ThirstSystem>().ModifyThirst(uid, thirst, HydrationFactor);
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-satiate-thirst", ("chance", Probability), ("relative",  HydrationFactor / DefaultHydrationFactor));
-}
diff --git a/Content.Shared/EntityEffects/Effects/Slipify.cs b/Content.Shared/EntityEffects/Effects/Slipify.cs
deleted file mode 100644 (file)
index c152b8b..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-using Content.Shared.Physics;
-using Content.Shared.Slippery;
-using Content.Shared.StepTrigger.Components;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Makes a mob slippery.
-/// </summary>
-public sealed partial class Slipify : EntityEffect
-{
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var fixtureSystem = args.EntityManager.System<FixtureSystem>();
-        var colWakeSystem = args.EntityManager.System<CollisionWakeSystem>();
-        var slippery = args.EntityManager.EnsureComponent<SlipperyComponent>(args.TargetEntity);
-        args.EntityManager.Dirty(args.TargetEntity, slippery);
-        args.EntityManager.EnsureComponent<StepTriggerComponent>(args.TargetEntity);
-        // Need a fixture with a slip layer in order to actually do the slipping
-        var fixtures = args.EntityManager.EnsureComponent<FixturesComponent>(args.TargetEntity);
-        var body = args.EntityManager.EnsureComponent<PhysicsComponent>(args.TargetEntity);
-        var shape = fixtures.Fixtures["fix1"].Shape;
-        fixtureSystem.TryCreateFixture(args.TargetEntity, shape, "slips", 1, false, (int)CollisionGroup.SlipLayer, manager: fixtures, body: body);
-        // Need to disable collision wake so that mobs can collide with and slip on it
-        var collisionWake = args.EntityManager.EnsureComponent<CollisionWakeComponent>(args.TargetEntity);
-        colWakeSystem.SetEnabled(args.TargetEntity, false, collisionWake);
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        throw new NotImplementedException();
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/SlipifyEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..602e5e9
--- /dev/null
@@ -0,0 +1,44 @@
+using System.Linq;
+using Content.Shared.Physics;
+using Content.Shared.Slippery;
+using Content.Shared.StepTrigger.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Systems;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// This effect permanently creates a slippery fixture for this entity and then makes this entity slippery like soap.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class SlipifyEntityEffectSystem : EntityEffectSystem<FixturesComponent, Slipify>
+{
+    [Dependency] private readonly CollisionWakeSystem _collisionWake = default!;
+    [Dependency] private readonly FixtureSystem _fixture = default!;
+
+    protected override void Effect(Entity<FixturesComponent> entity, ref EntityEffectEvent<Slipify> args)
+    {
+        EnsureComp<SlipperyComponent>(entity, out var slippery);
+        slippery.SlipData = args.Effect.Slippery;
+
+        Dirty(entity, slippery);
+
+        EnsureComp<StepTriggerComponent>(entity);
+
+        if (entity.Comp.Fixtures.FirstOrDefault(x => x.Value.Hard).Value.Shape is not { } shape)
+            return;
+
+        _fixture.TryCreateFixture(entity, shape, "slips", 1, false, (int)CollisionGroup.SlipLayer, manager: entity.Comp);
+
+        // Need to disable collision wake so that mobs can collide with and slip on it
+        EnsureComp<CollisionWakeComponent>(entity, out var collisionWake);
+        _collisionWake.SetEnabled(entity, false, collisionWake);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Slipify : EntityEffectBase<Slipify>
+{
+    [DataField]
+    public SlipperyEffectEntry Slippery = new();
+}
diff --git a/Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/AddReagentToSolutionEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..ef96723
--- /dev/null
@@ -0,0 +1,59 @@
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Chemistry.Reagent;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Solution;
+
+// TODO: This should be removed and changed to an "AbsorbentSolutionComponent"
+/// <summary>
+/// Creates a reagent in a specified solution owned by this entity.
+/// Quantity is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem<SolutionContainerManagerComponent, AddReagentToSolution>
+{
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+    protected override void Effect(Entity<SolutionContainerManagerComponent> entity, ref EntityEffectEvent<AddReagentToSolution> args)
+    {
+        var solution = args.Effect.Solution;
+        var reagent = args.Effect.Reagent;
+
+        if (!_solutionContainer.TryGetSolution((entity, entity), solution, out var solutionContainer))
+            return;
+
+        _solutionContainer.TryAddReagent(solutionContainer.Value, reagent, args.Scale * args.Effect.StrengthModifier);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AddReagentToSolution : EntityEffectBase<AddReagentToSolution>
+{
+    /// <summary>
+    ///     Prototype of the reagent we're adding.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<ReagentPrototype> Reagent;
+
+    ///<summary>
+    ///     Solution we're looking for
+    /// </summary>
+    [DataField(required: true)]
+    public string? Solution = "reagents";
+
+    ///<summary>
+    ///     A modifier for how much reagent we're creating.
+    /// </summary>
+    [DataField]
+    public float StrengthModifier = 1.0f;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        return prototype.Resolve(Reagent, out ReagentPrototype? proto)
+            ? Loc.GetString("entity-effect-guidebook-add-to-solution-reaction",
+                ("chance", Probability),
+                ("reagent", proto.LocalizedName))
+            : null;
+    }
+}
diff --git a/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..334a63a
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Solution;
+
+/// <summary>
+/// Adjust a reagent in this solution by an amount modified by scale.
+/// Quantity is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustReagentEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustReagent>
+{
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+    protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustReagent> args)
+    {
+        var quantity = args.Effect.Amount * args.Scale;
+        var reagent = args.Effect.Reagent;
+
+        if (quantity > 0)
+            _solutionContainer.TryAddReagent(entity, reagent, quantity);
+        else
+            _solutionContainer.RemoveReagent(entity, reagent, -quantity);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustReagent : EntityEffectBase<AdjustReagent>
+{
+    /// <summary>
+    ///     The reagent ID to add or remove.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<ReagentPrototype> Reagent;
+
+    [DataField(required: true)]
+    public FixedPoint2 Amount;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        return prototype.Resolve(Reagent, out ReagentPrototype? proto)
+            ? Loc.GetString("entity-effect-guidebook-adjust-reagent-reagent",
+                ("chance", Probability),
+                ("deltasign", MathF.Sign(Amount.Float())),
+                ("reagent", proto.LocalizedName),
+                ("amount", MathF.Abs(Amount.Float())))
+            : null;
+    }
+}
diff --git a/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/AdjustReagentsByGroupEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..5125995
--- /dev/null
@@ -0,0 +1,52 @@
+using Content.Shared.Body.Prototypes;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Solution;
+
+/// <summary>
+/// Adjust all reagents in this solution which are metabolized by a given metabolism group.
+/// Quantity is modified by scale, quantity is per reagent and not a total.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class AdjustReagentsByGroupEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustReagentsByGroup>
+{
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+    protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustReagentsByGroup> args)
+    {
+        var quantity = args.Effect.Amount * args.Scale;
+        var group = args.Effect.Group;
+        var solution = entity.Comp.Solution;
+
+        foreach (var quant in solution.Contents.ToArray())
+        {
+            var proto = _proto.Index<ReagentPrototype>(quant.Reagent.Prototype);
+            if (proto.Metabolisms == null || !proto.Metabolisms.ContainsKey(group))
+                continue;
+
+            if (quantity > 0)
+                _solutionContainer.TryAddReagent(entity, proto.ID, quantity);
+            else
+                _solutionContainer.RemoveReagent(entity, proto.ID, -quantity);
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustReagentsByGroup : EntityEffectBase<AdjustReagentsByGroup>
+{
+
+    /// <summary>
+    ///     The metabolism group being adjusted. All reagents in an affected solution with this group will be adjusted.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<MetabolismGroupPrototype> Group;
+
+    [DataField(required: true)]
+    public FixedPoint2 Amount;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs b/Content.Shared/EntityEffects/Effects/Solution/AreaReactionEntityEffect.cs
new file mode 100644 (file)
index 0000000..5fbe948
--- /dev/null
@@ -0,0 +1,39 @@
+using Content.Shared.Database;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Solution;
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AreaReactionEffect : EntityEffectBase<AreaReactionEffect>
+{
+    /// <summary>
+    /// How many seconds will the effect stay, counting after fully spreading.
+    /// </summary>
+    [DataField("duration")] public float Duration = 10;
+
+    /// <summary>
+    /// How big of a reaction scale we need for 1 smoke entity.
+    /// </summary>
+    [DataField] public float OverflowThreshold = 2.5f;
+
+    /// <summary>
+    /// The entity prototype that is being spread over an area.
+    /// </summary>
+    [DataField(required: true)]
+    public EntProtoId PrototypeId;
+
+    /// <summary>
+    /// Sound that will get played when this reaction effect occurs.
+    /// </summary>
+    [DataField(required: true)] public SoundSpecifier Sound = default!;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-area-reaction",
+            ("duration", Duration)
+        );
+
+    public override bool ShouldLog => true;
+
+    public override LogImpact LogImpact => LogImpact.High;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/Solution/SolutionTemperatureEntityEffectsSystem.cs
new file mode 100644 (file)
index 0000000..c0188fd
--- /dev/null
@@ -0,0 +1,145 @@
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Solution;
+
+// TODO: Energy conservation!!! Once HeatContainers are merged nuke this and everything in SolutionContainerSystem to respect energy conservation!
+/// <summary>
+/// Sets the temperature of this solution to a fixed value.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class SetSolutionTemperatureEntityEffectSystem : EntityEffectSystem<SolutionComponent, SetSolutionTemperature>
+{
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+    protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<SetSolutionTemperature> args)
+    {
+        _solutionContainer.SetTemperature(entity, args.Effect.Temperature);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class SetSolutionTemperature : EntityEffectBase<SetSolutionTemperature>
+{
+    /// <summary>
+    ///     The temperature to set the solution to.
+    /// </summary>
+    [DataField(required: true)]
+    public float Temperature;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-set-solution-temperature-effect",
+            ("chance", Probability),
+            ("temperature", Temperature));
+}
+
+/// <summary>
+/// Adjusts the temperature of this solution by a given amount.
+/// The temperature adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class AdjustSolutionTemperatureEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustSolutionTemperature>
+{
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+    protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustSolutionTemperature> args)
+    {
+        var solution = entity.Comp.Solution;
+        var temperature = Math.Clamp(solution.Temperature + args.Scale * args.Effect.Delta, args.Effect.MinTemp, args.Effect.MaxTemp);
+
+        _solutionContainer.SetTemperature(entity, temperature);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustSolutionTemperature : EntityEffectBase<AdjustSolutionTemperature>
+{
+    /// <summary>
+    ///     The change in temperature.
+    /// </summary>
+    [DataField(required: true)]
+    public float Delta;
+
+    /// <summary>
+    ///     The minimum temperature this effect can reach.
+    /// </summary>
+    [DataField]
+    public float MinTemp;
+
+    /// <summary>
+    ///     The maximum temperature this effect can reach.
+    /// </summary>
+    [DataField]
+    public float MaxTemp = float.PositiveInfinity;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-adjust-solution-temperature-effect",
+            ("chance", Probability),
+            ("deltasign", MathF.Sign(Delta)),
+            ("mintemp", MinTemp),
+            ("maxtemp", MaxTemp));
+}
+
+/// <summary>
+/// Adjusts the thermal energy of this solution by a given amount.
+/// The energy adjustment is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed class AdjustSolutionThermalEnergyEntityEffectSystem : EntityEffectSystem<SolutionComponent, AdjustSolutionThermalEnergy>
+{
+    [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
+
+    protected override void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AdjustSolutionThermalEnergy> args)
+    {
+        var solution = entity.Comp.Solution;
+
+        var delta = args.Scale * args.Effect.Delta;
+
+        // Don't adjust thermal energy if we're already at or above max temperature.
+        switch (delta)
+        {
+            case > 0:
+                if (solution.Temperature >= args.Effect.MaxTemp)
+                    return;
+                break;
+            case < 0:
+                if (solution.Temperature <= args.Effect.MinTemp)
+                    return;
+                break;
+            default:
+                return;
+        }
+
+        _solutionContainer.AddThermalEnergyClamped(entity, delta, args.Effect.MinTemp, args.Effect.MaxTemp);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class AdjustSolutionThermalEnergy : EntityEffectBase<AdjustSolutionThermalEnergy>
+{
+    /// <summary>
+    ///     The change in thermal energy.
+    /// </summary>
+    [DataField(required: true)]
+    public float Delta;
+
+    /// <summary>
+    ///     The minimum temperature this effect can reach.
+    /// </summary>
+    [DataField]
+    public float MinTemp;
+
+    /// <summary>
+    ///     The maximum temperature this effect can reach.
+    /// </summary>
+    [DataField]
+    public float MaxTemp = float.PositiveInfinity;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-adjust-solution-temperature-effect",
+            ("chance", Probability),
+            ("deltasign", MathF.Sign(Delta)),
+            ("mintemp", MinTemp),
+            ("maxtemp", MaxTemp));
+}
diff --git a/Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs b/Content.Shared/EntityEffects/Effects/SolutionTemperatureEffects.cs
deleted file mode 100644 (file)
index 30ac6c3..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-///     Sets the temperature of the solution involved with the reaction to a new value.
-/// </summary>
-[DataDefinition]
-public sealed partial class SetSolutionTemperatureEffect : EntityEffect
-{
-    /// <summary>
-    ///     The temperature to set the solution to.
-    /// </summary>
-    [DataField("temperature", required: true)] private float _temperature;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-set-solution-temperature-effect",
-            ("chance", Probability), ("temperature", _temperature));
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            var solution = reagentArgs.Source;
-            if (solution == null)
-                return;
-
-            solution.Temperature = _temperature;
-
-            return;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-}
-
-/// <summary>
-///     Adjusts the temperature of the solution involved in the reaction.
-/// </summary>
-[DataDefinition]
-public sealed partial class AdjustSolutionTemperatureEffect : EntityEffect
-{
-    /// <summary>
-    ///     The change in temperature.
-    /// </summary>
-    [DataField("delta", required: true)] private float _delta;
-
-    /// <summary>
-    ///     The minimum temperature this effect can reach.
-    /// </summary>
-    [DataField("minTemp")] private float _minTemp = 0.0f;
-
-    /// <summary>
-    ///     The maximum temperature this effect can reach.
-    /// </summary>
-    [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity;
-
-    /// <summary>
-    ///     If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount.
-    /// </summary>
-    [DataField("scaled")] private bool _scaled;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect",
-            ("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp));
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            var solution = reagentArgs.Source;
-            if (solution == null || solution.Volume == 0)
-                return;
-
-            var deltaT = _scaled ? _delta * (float) reagentArgs.Quantity : _delta;
-            solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
-
-            return;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-}
-
-/// <summary>
-///     Adjusts the thermal energy of the solution involved in the reaction.
-/// </summary>
-public sealed partial class AdjustSolutionThermalEnergyEffect : EntityEffect
-{
-    /// <summary>
-    ///     The change in energy.
-    /// </summary>
-    [DataField("delta", required: true)] private float _delta;
-
-    /// <summary>
-    ///     The minimum temperature this effect can reach.
-    /// </summary>
-    [DataField("minTemp")] private float _minTemp = 0.0f;
-
-    /// <summary>
-    ///     The maximum temperature this effect can reach.
-    /// </summary>
-    [DataField("maxTemp")] private float _maxTemp = float.PositiveInfinity;
-
-    /// <summary>
-    ///     If true, then scale ranges by intensity. If not, the ranges are the same regardless of reactant amount.
-    /// </summary>
-    [DataField("scaled")] private bool _scaled;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (args is EntityEffectReagentArgs reagentArgs)
-        {
-            var solution = reagentArgs.Source;
-            if (solution == null || solution.Volume == 0)
-                return;
-
-            if (_delta > 0 && solution.Temperature >= _maxTemp)
-                return;
-            if (_delta < 0 && solution.Temperature <= _minTemp)
-                return;
-
-            var heatCap = solution.GetHeatCapacity(null);
-            var deltaT = _scaled
-                ? _delta / heatCap * (float) reagentArgs.Quantity
-                : _delta / heatCap;
-
-            solution.Temperature = Math.Clamp(solution.Temperature + deltaT, _minTemp, _maxTemp);
-
-            return;
-        }
-
-        // TODO: Someone needs to figure out how to do this for non-reagent effects.
-        throw new NotImplementedException();
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-adjust-solution-temperature-effect",
-            ("chance", Probability), ("deltasign", MathF.Sign(_delta)), ("mintemp", _minTemp), ("maxtemp", _maxTemp));
-}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/BaseStatusEffectEntityEffect.cs
new file mode 100644 (file)
index 0000000..b028da1
--- /dev/null
@@ -0,0 +1,35 @@
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Entity effect that specifically deals with new status effects.
+/// </summary>
+/// <typeparam name="T">The entity effect type, typically for status effects which need systems to pass arguments</typeparam>
+public abstract partial class BaseStatusEntityEffect<T> : EntityEffectBase<T> where T : BaseStatusEntityEffect<T>
+{
+    /// <summary>
+    /// How long the modifier applies (in seconds).
+    /// Is scaled by reagent amount if used with an EntityEffectReagentArgs.
+    /// </summary>
+    [DataField]
+    public TimeSpan? Time = TimeSpan.FromSeconds(2);
+
+    /// <summary>
+    /// Should this effect add the status effect, remove time from it, or set its cooldown?
+    /// </summary>
+    [DataField]
+    public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Update;
+
+    /// <summary>
+    /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence.
+    /// </summary>
+    [DataField]
+    public TimeSpan Delay;
+}
+
+public enum StatusEffectMetabolismType
+{
+    Update,
+    Add,
+    Remove,
+    Set,
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/DrunkEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..553300b
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Drunk;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies the drunk status effect to this entity.
+/// The duration of the effect is equal to <see cref="Drunk.BoozePower"/> modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class DrunkEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Drunk>
+{
+    [Dependency] private readonly SharedDrunkSystem _drunk = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Drunk> args)
+    {
+        var boozePower = args.Effect.BoozePower * args.Scale;
+
+        _drunk.TryApplyDrunkenness(entity, boozePower);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Drunk : EntityEffectBase<Drunk>
+{
+    /// <summary>
+    ///     BoozePower is how long each metabolism cycle will make the drunk effect last for.
+    /// </summary>
+    [DataField]
+    public TimeSpan BoozePower = TimeSpan.FromSeconds(3f);
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-drunk", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ElectrocuteEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..b5a208f
--- /dev/null
@@ -0,0 +1,51 @@
+using Content.Shared.Electrocution;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+// TODO: When Electrocution is moved to new Status, make this use StatusEffectsContainerComponent.
+/// <summary>
+/// Electrocutes this entity for a given amount of damage and time.
+/// The shock damage applied by this effect is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ElectrocuteEntityEffectSystem : EntityEffectSystem<StatusEffectsComponent, Electrocute>
+{
+    [Dependency] private readonly SharedElectrocutionSystem _electrocution = default!;
+
+    // TODO: When electrocution is new status, change this to new status
+    protected override void Effect(Entity<StatusEffectsComponent> entity, ref EntityEffectEvent<Electrocute> args)
+    {
+        var effect = args.Effect;
+
+        _electrocution.TryDoElectrocution(entity, null, (int)(args.Scale * effect.ShockDamage), effect.ElectrocuteTime, effect.Refresh, ignoreInsulation: effect.BypassInsulation);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Electrocute : EntityEffectBase<Electrocute>
+{
+    /// <summary>
+    /// Time we electrocute this entity
+    /// </summary>
+    [DataField] public TimeSpan ElectrocuteTime = TimeSpan.FromSeconds(2);
+
+    /// <summary>
+    /// Shock damage we apply to the entity.
+    /// </summary>
+    [DataField] public int ShockDamage = 5;
+
+    /// <summary>
+    /// Do we refresh the duration? Or add more duration if it already exists.
+    /// </summary>
+    [DataField] public bool Refresh = true;
+
+    /// <summary>
+    /// Should we by bypassing insulation?
+    /// </summary>
+    [DataField] public bool BypassInsulation = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-electrocute", ("chance", Probability), ("time", ElectrocuteTime.TotalSeconds));
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs
deleted file mode 100644 (file)
index 652c1b9..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.StatusEffect;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-///     Adds a generic status effect to the entity,
-///     not worrying about things like how to affect the time it lasts for
-///     or component fields or anything. Just adds a component to an entity
-///     for a given time. Easy.
-/// </summary>
-/// <remarks>
-///     Can be used for things like adding accents or something. I don't know. Go wild.
-/// </remarks>
-[Obsolete("Use ModifyStatusEffect with StatusEffectNewSystem instead")]
-public sealed partial class GenericStatusEffect : EntityEffect
-{
-    [DataField(required: true)]
-    public string Key = default!;
-
-    [DataField]
-    public string Component = String.Empty;
-
-    [DataField]
-    public float Time = 2.0f;
-
-    /// <remarks>
-    ///     true - refresh status effect time,  false - accumulate status effect time
-    /// </remarks>
-    [DataField]
-    public bool Refresh = true;
-
-    /// <summary>
-    ///     Should this effect add the status effect, remove time from it, or set its cooldown?
-    /// </summary>
-    [DataField]
-    public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
-
-        var time = Time;
-        if (args is EntityEffectReagentArgs reagentArgs)
-            time *= reagentArgs.Scale.Float();
-
-        if (Type == StatusEffectMetabolismType.Add && Component != String.Empty)
-        {
-            statusSys.TryAddStatusEffect(args.TargetEntity, Key, TimeSpan.FromSeconds(time), Refresh, Component);
-        }
-        else if (Type == StatusEffectMetabolismType.Remove)
-        {
-            statusSys.TryRemoveTime(args.TargetEntity, Key, TimeSpan.FromSeconds(time));
-        }
-        else if (Type == StatusEffectMetabolismType.Set)
-        {
-            statusSys.TrySetTime(args.TargetEntity, Key, TimeSpan.FromSeconds(time));
-        }
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString(
-        "reagent-effect-guidebook-status-effect",
-        ("chance", Probability),
-        ("type", Type),
-        ("time", Time),
-        ("key", $"reagent-effect-status-effect-{Key}"));
-}
-
-public enum StatusEffectMetabolismType
-{
-    Update,
-    Add,
-    Remove,
-    Set
-}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffectEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..2d870a4
--- /dev/null
@@ -0,0 +1,64 @@
+using Content.Shared.StatusEffect;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies a Generic Status Effect to this entity, which is a timed Component.
+/// The amount of time the Component is applied is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+[Obsolete("Use ModifyStatusEffect instead")]
+public sealed partial class GenericStatusEffectEntityEffectSystem : EntityEffectSystem<MetaDataComponent, GenericStatusEffect>
+{
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
+
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<GenericStatusEffect> args)
+    {
+        var time = args.Effect.Time * args.Scale;
+
+        switch (args.Effect.Type)
+        {
+            case StatusEffectMetabolismType.Update:
+                if (args.Effect.Component != String.Empty)
+                    _status.TryAddStatusEffect(entity, args.Effect.Key, time, true, args.Effect.Component);
+                break;
+            case StatusEffectMetabolismType.Add:
+                if (args.Effect.Component != String.Empty)
+                    _status.TryAddStatusEffect(entity, args.Effect.Key, time, false, args.Effect.Component);
+                break;
+            case StatusEffectMetabolismType.Remove:
+                _status.TryRemoveTime(entity, args.Effect.Key, time);
+                break;
+            case StatusEffectMetabolismType.Set:
+                _status.TrySetTime(entity, args.Effect.Key, time);
+                break;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class GenericStatusEffect : EntityEffectBase<GenericStatusEffect>
+{
+    [DataField(required: true)]
+    public string Key = default!;
+
+    [DataField]
+    public string Component = String.Empty;
+
+    [DataField]
+    public TimeSpan Time = TimeSpan.FromSeconds(2f);
+
+    /// <summary>
+    ///     Should this effect add the status effect, remove time from it, or set its cooldown?
+    /// </summary>
+    [DataField]
+    public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Update;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString(
+        "entity-effect-guidebook-status-effect-old",
+        ("chance", Probability),
+        ("type", Type),
+        ("time", Time.TotalSeconds),
+        ("key", $"entity-effect-status-effect-{Key}"));
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/Jitter.cs
deleted file mode 100644 (file)
index 891c791..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Jittering;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-///     Adds the jitter status effect to a mob.
-///     This doesn't use generic status effects because it needs to
-///     take in some parameters that JitterSystem needs.
-/// </summary>
-public sealed partial class Jitter : EntityEffect
-{
-    [DataField]
-    public float Amplitude = 10.0f;
-
-    [DataField]
-    public float Frequency = 4.0f;
-
-    [DataField]
-    public float Time = 2.0f;
-
-    /// <remarks>
-    ///     true - refresh jitter time,  false - accumulate jitter time
-    /// </remarks>
-    [DataField]
-    public bool Refresh = true;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var time = Time;
-        if (args is EntityEffectReagentArgs reagentArgs)
-            time *= reagentArgs.Scale.Float();
-
-        args.EntityManager.EntitySysManager.GetEntitySystem<SharedJitteringSystem>()
-            .DoJitter(args.TargetEntity, TimeSpan.FromSeconds(time), Refresh, Amplitude, Frequency);
-    }
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
-        Loc.GetString("reagent-effect-guidebook-jittering", ("chance", Probability));
-}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/JitterEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..3b445da
--- /dev/null
@@ -0,0 +1,45 @@
+using Content.Shared.Jittering;
+using Content.Shared.StatusEffect;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+// TODO: When Jittering is moved to new Status, make this use StatusEffectsContainerComponent.
+/// <summary>
+/// Applies the Jittering Status Effect to this entity.
+/// The amount of time the Jittering is applied is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class JitterEntityEffectSystem : EntityEffectSystem<StatusEffectsComponent, Jitter>
+{
+    [Dependency] private readonly SharedJitteringSystem _jittering = default!;
+
+    protected override void Effect(Entity<StatusEffectsComponent> entity, ref EntityEffectEvent<Jitter> args)
+    {
+        var time = args.Effect.Time * args.Scale;
+
+        _jittering.DoJitter(entity, TimeSpan.FromSeconds(time), args.Effect.Refresh, args.Effect.Amplitude, args.Effect.Frequency);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Jitter : EntityEffectBase<Jitter>
+{
+    [DataField]
+    public float Amplitude = 10.0f;
+
+    [DataField]
+    public float Frequency = 4.0f;
+
+    [DataField]
+    public float Time = 2.0f;
+
+    /// <remarks>
+    ///     true - refresh jitter time,  false - accumulate jitter time
+    /// </remarks>
+    [DataField]
+    public bool Refresh = true;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Loc.GetString("entity-effect-guidebook-jittering", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdown.cs
deleted file mode 100644 (file)
index 59ca5da..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-using Content.Shared.Stunnable;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-/// Changes the knockdown timer on an entity or causes knockdown.
-/// </summary>
-[UsedImplicitly]
-public sealed partial class ModifyKnockdown : EntityEffect
-{
-    /// <summary>
-    /// Should we only affect those with crawler component? Note if this is false, it will paralyze non-crawler's instead.
-    /// </summary>
-    [DataField]
-    public bool Crawling;
-
-    /// <summary>
-    /// Should we drop items when we fall?
-    /// </summary>
-    [DataField]
-    public bool Drop;
-
-    /// <summary>
-    /// Time for which knockdown should be applied. Behaviour changes according to <see cref="StatusEffectMetabolismType"/>.
-    /// </summary>
-    [DataField]
-    public TimeSpan Time = TimeSpan.FromSeconds(0.5);
-
-    /// <summary>
-    /// Should this effect add the status effect, remove time from it, or set its cooldown?
-    /// </summary>
-    [DataField]
-    public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
-
-    /// <summary>
-    /// Should this effect add knockdown?, remove time from it?, or set its cooldown?
-    /// </summary>
-    [DataField]
-    public bool Refresh = true;
-
-    /// <inheritdoc />
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var stunSys = args.EntityManager.EntitySysManager.GetEntitySystem<SharedStunSystem>();
-
-        var time = Time;
-        if (args is EntityEffectReagentArgs reagentArgs)
-            time *= reagentArgs.Scale.Float();
-
-        switch (Type)
-        {
-            case StatusEffectMetabolismType.Update:
-                if (Crawling)
-                {
-                    stunSys.TryCrawling(args.TargetEntity, time, drop: Drop);
-                }
-                else
-                {
-                    stunSys.TryKnockdown(args.TargetEntity, time, drop: Drop);
-                }
-                break;
-            case StatusEffectMetabolismType.Add:
-                if (Crawling)
-                {
-                    stunSys.TryCrawling(args.TargetEntity, time, false, drop: Drop);
-                }
-                else
-                {
-                    stunSys.TryKnockdown(args.TargetEntity, time, false, drop: Drop);
-                }
-                break;
-            case StatusEffectMetabolismType.Remove:
-                    stunSys.AddKnockdownTime(args.TargetEntity, -time);
-                break;
-            case StatusEffectMetabolismType.Set:
-                if (Crawling)
-                {
-                    stunSys.TryCrawling(args.TargetEntity, time, drop: Drop);
-                }
-                else
-                {
-                    stunSys.TryKnockdown(args.TargetEntity, time, drop: Drop);
-                }
-                stunSys.SetKnockdownTime(args.TargetEntity, time);
-                break;
-        }
-    }
-
-    /// <inheritdoc />
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString(
-            "reagent-effect-guidebook-knockdown",
-            ("chance", Probability),
-            ("type", Type),
-            ("time", Time.TotalSeconds)
-        );
-}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyKnockdownEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..5e01774
--- /dev/null
@@ -0,0 +1,72 @@
+using Content.Shared.Standing;
+using Content.Shared.Stunnable;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies knockdown to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyKnockdownEntityEffectSystem : EntityEffectSystem<StandingStateComponent, ModifyKnockdown>
+{
+    [Dependency] private readonly SharedStunSystem _stun = default!;
+
+    protected override void Effect(Entity<StandingStateComponent> entity, ref EntityEffectEvent<ModifyKnockdown> args)
+    {
+        var time = args.Effect.Time * args.Scale;
+
+        switch (args.Effect.Type)
+        {
+            case StatusEffectMetabolismType.Update:
+                if (args.Effect.Crawling)
+                    _stun.TryCrawling(entity.Owner, time, drop: args.Effect.Drop);
+                else
+                    _stun.TryKnockdown(entity.Owner, time, drop: args.Effect.Drop);
+                break;
+            case StatusEffectMetabolismType.Add:
+                if (args.Effect.Crawling)
+                    _stun.TryCrawling(entity.Owner, time, false, drop: args.Effect.Drop);
+                else
+                    _stun.TryKnockdown(entity.Owner, time, false, drop: args.Effect.Drop);
+                break;
+            case StatusEffectMetabolismType.Remove:
+                _stun.AddKnockdownTime(entity.Owner, - time ?? TimeSpan.Zero);
+                break;
+            case StatusEffectMetabolismType.Set:
+                if (args.Effect.Crawling)
+                    _stun.TryCrawling(entity.Owner, drop: args.Effect.Drop);
+                else
+                    _stun.TryKnockdown(entity.Owner, time, drop: args.Effect.Drop);
+                _stun.SetKnockdownTime(entity.Owner, time ?? TimeSpan.Zero);
+                break;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyKnockdown : BaseStatusEntityEffect<ModifyKnockdown>
+{
+    /// <summary>
+    /// Should we only affect those with crawler component? Note if this is false, it will paralyze non-crawler's instead.
+    /// </summary>
+    [DataField]
+    public bool Crawling;
+
+    /// <summary>
+    /// Should we drop items when we fall?
+    /// </summary>
+    [DataField]
+    public bool Drop;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Time == null
+        ? null
+        : Loc.GetString(
+            "entity-effect-guidebook-knockdown",
+            ("chance", Probability),
+            ("type", Type),
+            ("time", Time.Value.TotalSeconds)
+        );
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyParalysisEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..318c8ad
--- /dev/null
@@ -0,0 +1,51 @@
+using Content.Shared.StatusEffectNew;
+using Content.Shared.StatusEffectNew.Components;
+using Content.Shared.Stunnable;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies the paralysis status effect to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyParalysisEntityEffectSystem : EntityEffectSystem<StatusEffectContainerComponent, ModifyParalysis>
+{
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
+    [Dependency] private readonly SharedStunSystem _stun = default!;
+
+    protected override void Effect(Entity<StatusEffectContainerComponent> entity, ref EntityEffectEvent<ModifyParalysis> args)
+    {
+        var time = args.Effect.Time * args.Scale;
+
+        switch (args.Effect.Type)
+        {
+            case StatusEffectMetabolismType.Update:
+                _stun.TryUpdateParalyzeDuration(entity, time);
+                break;
+            case StatusEffectMetabolismType.Add:
+                _stun.TryAddParalyzeDuration(entity, time);
+                break;
+            case StatusEffectMetabolismType.Remove:
+                _status.TryRemoveTime(entity, SharedStunSystem.StunId, time);
+                break;
+            case StatusEffectMetabolismType.Set:
+                _status.TrySetStatusEffectDuration(entity, SharedStunSystem.StunId, time);
+                break;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyParalysis : BaseStatusEntityEffect<ModifyParalysis>
+{
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Time == null
+            ? null // Not gonna make a whole new looc for something that shouldn't ever exist.
+            : Loc.GetString(
+            "entity-effect-guidebook-paralyze",
+            ("chance", Probability),
+            ("time", Time.Value.TotalSeconds)
+        );
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffect.cs
deleted file mode 100644 (file)
index d7bf648..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-using Content.Shared.StatusEffectNew;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects.StatusEffects;
-
-/// <summary>
-/// Changes status effects on entities: Adds, removes or sets time.
-/// </summary>
-[UsedImplicitly]
-public sealed partial class ModifyStatusEffect : EntityEffect
-{
-    [DataField(required: true)]
-    public EntProtoId EffectProto;
-
-    /// <summary>
-    /// Time for which status effect should be applied. Behaviour changes according to <see cref="Refresh" />.
-    /// </summary>
-    [DataField]
-    public float Time = 2.0f;
-
-    /// <summary>
-    /// Delay before the effect starts. If another effect is added with a shorter delay, it takes precedence.
-    /// </summary>
-    [DataField]
-    public float Delay = 0f;
-
-    /// <summary>
-    /// Should this effect add the status effect, remove time from it, or set its cooldown?
-    /// </summary>
-    [DataField]
-    public StatusEffectMetabolismType Type = StatusEffectMetabolismType.Add;
-
-    /// <inheritdoc />
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        var statusSys = args.EntityManager.EntitySysManager.GetEntitySystem<StatusEffectsSystem>();
-
-        var time = Time;
-        if (args is EntityEffectReagentArgs reagentArgs)
-            time *= reagentArgs.Scale.Float();
-
-        var duration = TimeSpan.FromSeconds(time);
-        switch (Type)
-        {
-            case StatusEffectMetabolismType.Update:
-                statusSys.TryUpdateStatusEffectDuration(args.TargetEntity, EffectProto, duration, Delay > 0 ? TimeSpan.FromSeconds(Delay) : null);
-                break;
-            case StatusEffectMetabolismType.Add:
-                statusSys.TryAddStatusEffectDuration(args.TargetEntity, EffectProto, duration, Delay > 0 ? TimeSpan.FromSeconds(Delay) : null);
-                break;
-            case StatusEffectMetabolismType.Remove:
-                statusSys.TryAddTime(args.TargetEntity, EffectProto, -duration);
-                break;
-            case StatusEffectMetabolismType.Set:
-                statusSys.TrySetStatusEffectDuration(args.TargetEntity, EffectProto, duration, TimeSpan.FromSeconds(Delay));
-                break;
-        }
-    }
-
-    /// <inheritdoc />
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
-        Delay > 0
-        ? Loc.GetString(
-            "reagent-effect-guidebook-status-effect-delay",
-            ("chance", Probability),
-            ("type", Type),
-            ("time", Time),
-            ("key", prototype.Index(EffectProto).Name),
-            ("delay", Delay))
-        : Loc.GetString(
-            "reagent-effect-guidebook-status-effect",
-            ("chance", Probability),
-            ("type", Type),
-            ("time", Time),
-            ("key", prototype.Index(EffectProto).Name)
-        );
-}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/ModifyStatusEffectEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..d7e4b63
--- /dev/null
@@ -0,0 +1,66 @@
+using Content.Shared.StatusEffectNew;
+using Content.Shared.StatusEffectNew.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies a given status effect to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class ModifyStatusEffectEntityEffectSystem : EntityEffectSystem<StatusEffectContainerComponent, ModifyStatusEffect>
+{
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
+
+    protected override void Effect(Entity<StatusEffectContainerComponent> entity, ref EntityEffectEvent<ModifyStatusEffect> args)
+    {
+        var time = args.Effect.Time * args.Scale;
+        var delay = args.Effect.Delay;
+
+        switch (args.Effect.Type)
+        {
+            case StatusEffectMetabolismType.Update:
+                _status.TryUpdateStatusEffectDuration(entity, args.Effect.EffectProto, time, delay);
+                break;
+            case StatusEffectMetabolismType.Add:
+                if (time != null)
+                    _status.TryAddStatusEffectDuration(entity, args.Effect.EffectProto, time.Value, delay);
+                else
+                    _status.TryUpdateStatusEffectDuration(entity, args.Effect.EffectProto, time, delay);
+                break;
+            case StatusEffectMetabolismType.Remove:
+                _status.TryRemoveTime(entity, args.Effect.EffectProto, time);
+                break;
+            case StatusEffectMetabolismType.Set:
+                _status.TrySetStatusEffectDuration(entity, args.Effect.EffectProto, time, delay);
+                break;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ModifyStatusEffect : BaseStatusEntityEffect<ModifyStatusEffect>
+{
+    /// <summary>
+    /// Prototype of the status effect we're modifying.
+    /// </summary>
+    [DataField(required: true)]
+    public EntProtoId EffectProto;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+        Time == null
+            ? Loc.GetString(
+                "entity-effect-guidebook-status-effect-indef",
+                ("chance", Probability),
+                ("type", Type),
+                ("key", prototype.Index(EffectProto).Name),
+                ("delay", Delay.TotalSeconds))
+            : Loc.GetString(
+                "entity-effect-guidebook-status-effect",
+                ("chance", Probability),
+                ("type", Type),
+                ("time", Time.Value.TotalSeconds),
+                ("key", prototype.Index(EffectProto).Name),
+                ("delay", Delay.TotalSeconds));
+}
diff --git a/Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/StatusEffects/MovementSpeedModifierEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..ed9923f
--- /dev/null
@@ -0,0 +1,92 @@
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Content.Shared.StatusEffectNew;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.StatusEffects;
+
+/// <summary>
+/// Applies a given movement speed modifier status effect to this entity.
+/// Duration is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class MovementSpeedModifierEntityEffectSystem : EntityEffectSystem<MovementSpeedModifierComponent, MovementSpeedModifier>
+{
+    [Dependency] private readonly StatusEffectsSystem _status = default!;
+    [Dependency] private readonly MovementModStatusSystem _movementModStatus = default!;
+
+    protected override void Effect(Entity<MovementSpeedModifierComponent> entity, ref EntityEffectEvent<MovementSpeedModifier> args)
+    {
+        var proto = args.Effect.EffectProto;
+        var sprintMod = args.Effect.SprintSpeedModifier;
+        var walkMod = args.Effect.WalkSpeedModifier;
+
+        switch (args.Effect.Type)
+        {
+            case StatusEffectMetabolismType.Update:
+                _movementModStatus.TryUpdateMovementSpeedModDuration(
+                    entity,
+                    proto,
+                    args.Effect.Time * args.Scale,
+                    sprintMod,
+                    walkMod);
+                break;
+            case StatusEffectMetabolismType.Add:
+                if (args.Effect.Time != null)
+                {
+                    _movementModStatus.TryAddMovementSpeedModDuration(
+                        entity,
+                        proto,
+                        args.Effect.Time.Value * args.Scale,
+                        sprintMod,
+                        walkMod);
+                }
+                else
+                {
+                    _movementModStatus.TryUpdateMovementSpeedModDuration(
+                        entity,
+                        proto,
+                        args.Effect.Time * args.Scale,
+                        sprintMod,
+                        walkMod);
+                }
+                break;
+            case StatusEffectMetabolismType.Remove:
+                _status.TryRemoveTime(entity, args.Effect.EffectProto, args.Effect.Time * args.Scale);
+                break;
+            case StatusEffectMetabolismType.Set:
+                _status.TrySetStatusEffectDuration(entity, proto, args.Effect.Time * args.Scale);
+                break;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class MovementSpeedModifier : BaseStatusEntityEffect<MovementSpeedModifier>
+{
+    /// <summary>
+    /// How much the entities' walk speed is multiplied by.
+    /// </summary>
+    [DataField]
+    public float WalkSpeedModifier = 1f;
+
+    /// <summary>
+    /// How much the entities' run speed is multiplied by.
+    /// </summary>
+    [DataField]
+    public float SprintSpeedModifier = 1f;
+
+    /// <summary>
+    /// Movement speed modifier prototype we're adding. Adding in case we ever want more than one prototype that boosts speed.
+    /// </summary>
+    [DataField]
+    public EntProtoId EffectProto = MovementModStatusSystem.ReagentSpeed;
+
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
+    Time == null
+        ? null // Not gonna make a whole new looc for something that shouldn't ever exist.
+        : Loc.GetString("entity-effect-guidebook-movespeed-modifier",
+            ("chance", Probability),
+            ("sprintspeed", SprintSpeedModifier),
+            ("time", Time.Value.TotalSeconds));
+}
diff --git a/Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/TemplateEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..ea7c2dc
--- /dev/null
@@ -0,0 +1,21 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// A brief summary of the effect.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+public sealed partial class TemplateEntityEffectSystem : EntityEffectSystem<MetaDataComponent, Template>
+{
+    protected override void Effect(Entity<MetaDataComponent> entity, ref EntityEffectEvent<Template> args)
+    {
+        // Effect goes here.
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Template : EntityEffectBase<Template>
+{
+    public override string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+}
diff --git a/Content.Shared/EntityEffects/Effects/Transform/EmpEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Transform/EmpEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..c4768dc
--- /dev/null
@@ -0,0 +1,58 @@
+using Content.Shared.Database;
+using Content.Shared.Emp;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates an EMP at this entity's coordinates.
+/// Range is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class EmpEntityEffectSystem : EntityEffectSystem<TransformComponent, Emp>
+{
+    [Dependency] private readonly SharedEmpSystem _emp = default!;
+    [Dependency] private readonly SharedTransformSystem _xform = default!;
+
+    protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<Emp> args)
+    {
+        var range = MathF.Min(args.Effect.RangeModifier * args.Scale, args.Effect.MaxRange);
+
+        _emp.EmpPulse(_xform.GetMapCoordinates(entity, xform: entity.Comp), range, args.Effect.EnergyConsumption, args.Effect.Duration);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Emp : EntityEffectBase<Emp>
+{
+    /// <summary>
+    ///     Impulse range per unit of quantity
+    /// </summary>
+    [DataField]
+    public float RangeModifier = 0.5f;
+
+    /// <summary>
+    ///     Maximum impulse range
+    /// </summary>
+    [DataField]
+    public float MaxRange = 10;
+
+    /// <summary>
+    ///     How much energy will be drain from sources
+    /// </summary>
+    [DataField]
+    public float EnergyConsumption = 12500;
+
+    /// <summary>
+    ///     Amount of time entities will be disabled
+    /// </summary>
+    [DataField]
+    public TimeSpan Duration = TimeSpan.FromSeconds(15);
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-emp-reaction-effect", ("chance", Probability));
+
+    public override bool ShouldLog => true;
+
+    public override LogImpact LogImpact => LogImpact.Medium;
+}
similarity index 69%
rename from Content.Shared/EntityEffects/Effects/ExplosionReactionEffect.cs
rename to Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
index bc9af1509255bce7e79aa5773538b7a41df5be80..762ef24141a174deacb63bbf973bd5972e2ce786 100644 (file)
@@ -1,19 +1,17 @@
-using Content.Shared.Database;
+using Content.Shared.Database;
 using Content.Shared.Explosion;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using System.Text.Json.Serialization;
 
-namespace Content.Shared.EntityEffects.Effects;
+namespace Content.Shared.EntityEffects.Effects.Transform;
 
-[DataDefinition]
-public sealed partial class ExplosionReactionEffect : EventEntityEffect<ExplosionReactionEffect>
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class ExplosionEffect : EntityEffectBase<ExplosionEffect>
 {
     /// <summary>
     ///     The type of explosion. Determines damage types and tile break chance scaling.
     /// </summary>
-    [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ExplosionPrototype>))]
-    public string ExplosionType = default!;
+    [DataField(required: true)]
+    public ProtoId<ExplosionPrototype> ExplosionType;
 
     /// <summary>
     ///     The max intensity the explosion can have at a given tile. Places an upper limit of damage and tile break
@@ -51,9 +49,10 @@ public sealed partial class ExplosionReactionEffect : EventEntityEffect<Explosio
     [DataField]
     public float TileBreakScale = 1f;
 
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
+
     public override bool ShouldLog => true;
 
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-explosion-reaction-effect", ("chance", Probability));
     public override LogImpact LogImpact => LogImpact.High;
 }
diff --git a/Content.Shared/EntityEffects/Effects/Transform/FlashEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Transform/FlashEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..df788e9
--- /dev/null
@@ -0,0 +1,81 @@
+using Content.Shared.Flash;
+using Robust.Shared.Audio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates a Flash at this entity's coordinates.
+/// Range is modified by scale.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class FlashEntityEffectSystem : EntityEffectSystem<TransformComponent, Flash>
+{
+    [Dependency] private readonly SharedFlashSystem _flash = default!;
+    [Dependency] private readonly SharedTransformSystem _xform = default!;
+    [Dependency] private readonly SharedPointLightSystem _pointLight = default!;
+
+    protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<Flash> args)
+    {
+        var range = MathF.Min(args.Scale * args.Effect.RangePerUnit, args.Effect.MaxRange);
+
+        _flash.FlashArea(
+            entity,
+            null,
+            range,
+            args.Effect.Duration,
+            slowTo: args.Effect.SlowTo,
+            sound: args.Effect.Sound);
+
+        if (args.Effect.FlashEffectPrototype == null)
+            return;
+
+        var uid = PredictedSpawnAtPosition(args.Effect.FlashEffectPrototype, _xform.GetMoverCoordinates(entity));
+
+        _pointLight.SetRadius(uid, MathF.Max(1.1f, range));
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class Flash : EntityEffectBase<Flash>
+{
+    /// <summary>
+    ///     Flash range per unit of reagent.
+    /// </summary>
+    [DataField]
+    public float RangePerUnit = 0.2f;
+
+    /// <summary>
+    ///     Maximum flash range.
+    /// </summary>
+    [DataField]
+    public float MaxRange = 10f;
+
+    /// <summary>
+    ///     How much to entities are slowed down.
+    /// </summary>
+    [DataField]
+    public float SlowTo = 0.5f;
+
+    /// <summary>
+    ///     The time entities will be flashed.
+    ///     The default is chosen to be better than the hand flash so it is worth using it for grenades etc.
+    /// </summary>
+    [DataField]
+    public TimeSpan Duration = TimeSpan.FromSeconds(4);
+
+    /// <summary>
+    ///     The prototype ID used for the visual effect.
+    /// </summary>
+    [DataField]
+    public EntProtoId? FlashEffectPrototype = "ReactionFlash";
+
+    /// <summary>
+    ///     The sound the flash creates.
+    /// </summary>
+    [DataField]
+    public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg");
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-flash-reaction-effect", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/Transform/PopupMessageEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..53d9acd
--- /dev/null
@@ -0,0 +1,64 @@
+using Content.Shared.Popups;
+using Robust.Shared.Network;
+using Robust.Shared.Random;
+
+namespace Content.Shared.EntityEffects.Effects.Transform;
+
+/// <summary>
+/// Creates a text popup to appear at this entity's coordinates.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
+public sealed partial class PopupMessageEntityEffectSystem : EntityEffectSystem<TransformComponent, PopupMessage>
+{
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+    protected override void Effect(Entity<TransformComponent> entity, ref EntityEffectEvent<PopupMessage> args)
+    {
+        // TODO: When we get proper random prediction remove this check.
+        if (_net.IsClient)
+            return;
+
+        var msg = _random.Pick(args.Effect.Messages);
+
+        switch (args.Effect.Type)
+        {
+            case PopupRecipients.Local:
+                _popup.PopupEntity(Loc.GetString(msg, ("entity", entity)), entity, entity, args.Effect.VisualType);
+                break;
+            case PopupRecipients.Pvs:
+                _popup.PopupEntity(Loc.GetString(msg, ("entity", entity)), entity, args.Effect.VisualType);
+                break;
+        }
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class PopupMessage : EntityEffectBase<PopupMessage>
+{
+    /// <summary>
+    /// Array of messages that can popup.
+    /// Only one is chosen when the effect is applied.
+    /// </summary>
+    [DataField(required: true)]
+    public string[] Messages = default!;
+
+    /// <summary>
+    /// Whether to just the entity we're affecting, or everyone around them.
+    /// </summary>
+    [DataField]
+    public PopupRecipients Type = PopupRecipients.Local;
+
+    /// <summary>
+    /// Size of the popup.
+    /// </summary>
+    [DataField]
+    public PopupType VisualType = PopupType.Small;
+}
+
+public enum PopupRecipients
+{
+    Pvs,
+    Local
+}
diff --git a/Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs b/Content.Shared/EntityEffects/Effects/WashCreamPieEntityEffectSystem.cs
new file mode 100644 (file)
index 0000000..c549121
--- /dev/null
@@ -0,0 +1,27 @@
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Washes the cream pie off of this entity face.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+/// TODO: This can probably be made into a generic "CleanEntityEffect" which multiple components listen to...
+public sealed partial class WashCreamPieEntityEffectSystem : EntityEffectSystem<CreamPiedComponent, WashCreamPie>
+{
+    [Dependency] private readonly SharedCreamPieSystem _creamPie = default!;
+
+    protected override void Effect(Entity<CreamPiedComponent> entity, ref EntityEffectEvent<WashCreamPie> args)
+    {
+        _creamPie.SetCreamPied(entity, entity.Comp, false);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class WashCreamPie : EntityEffectBase<WashCreamPie>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
+}
diff --git a/Content.Shared/EntityEffects/Effects/WashCreamPieReaction.cs b/Content.Shared/EntityEffects/Effects/WashCreamPieReaction.cs
deleted file mode 100644 (file)
index e6992a3..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-public sealed partial class WashCreamPieReaction : EntityEffect
-{
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
-        => Loc.GetString("reagent-effect-guidebook-wash-cream-pie-reaction", ("chance", Probability));
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (!args.EntityManager.TryGetComponent(args.TargetEntity, out CreamPiedComponent? creamPied)) return;
-
-        args.EntityManager.System<SharedCreamPieSystem>().SetCreamPied(args.TargetEntity, creamPied, false);
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/WearableReaction.cs b/Content.Shared/EntityEffects/Effects/WearableReaction.cs
deleted file mode 100644 (file)
index 08c28f9..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-using Content.Shared.Inventory;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects.Effects;
-
-/// <summary>
-/// A reaction effect that spawns a PrototypeID in the entity's Slot, and attempts to consume the reagent if EntityEffectReagentArgs.
-/// Used to implement the water droplet effect for arachnids.
-/// </summary>
-public sealed partial class WearableReaction : EntityEffect
-{
-    /// <summary>
-    /// Minimum quantity of reagent required to trigger this effect.
-    /// Only used with EntityEffectReagentArgs.
-    /// </summary>
-    [DataField]
-    public float AmountThreshold = 1f;
-
-    /// <summary>
-    /// Slot to spawn the item into.
-    /// </summary>
-    [DataField(required: true)]
-    public string Slot;
-
-    /// <summary>
-    /// Prototype ID of item to spawn.
-    /// </summary>
-    [DataField(required: true)]
-    public string PrototypeID;
-
-    protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
-
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        // SpawnItemInSlot returns false if slot is already occupied
-        if (args.EntityManager.System<InventorySystem>().SpawnItemInSlot(args.TargetEntity, Slot, PrototypeID))
-        {
-            if (args is EntityEffectReagentArgs reagentArgs)
-            {
-                if (reagentArgs.Reagent == null || reagentArgs.Quantity < AmountThreshold)
-                    return;
-                reagentArgs.Source?.RemoveReagent(reagentArgs.Reagent.ID, AmountThreshold);
-            }
-        }
-    }
-}
diff --git a/Content.Shared/EntityEffects/Effects/ZombieEntityEffectsSystem.cs b/Content.Shared/EntityEffects/Effects/ZombieEntityEffectsSystem.cs
new file mode 100644 (file)
index 0000000..ea0e6f0
--- /dev/null
@@ -0,0 +1,67 @@
+using Content.Shared.Mobs.Components;
+using Content.Shared.Zombies;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.EntityEffects.Effects;
+
+/// <summary>
+/// Causes the zombie infection on this entity.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+public sealed partial class CauseZombieInfectionEntityEffectsSystem : EntityEffectSystem<MobStateComponent, CauseZombieInfection>
+{
+    // MobState because you have to die to become a zombie...
+    protected override void Effect(Entity<MobStateComponent> entity, ref EntityEffectEvent<CauseZombieInfection> args)
+    {
+        if (HasComp<ZombieImmuneComponent>(entity) || HasComp<IncurableZombieComponent>(entity))
+            return;
+
+        EnsureComp<ZombifyOnDeathComponent>(entity);
+        EnsureComp<PendingZombieComponent>(entity);
+    }
+}
+
+/// <summary>
+/// Cures the Zombie infection on this entity and optionally inoculates them against future infection.
+/// </summary>
+/// <inheritdoc cref="EntityEffectSystem{T, TEffect}"/>
+public sealed partial class CureZombieInfectionEntityEffectsSystem : EntityEffectSystem<MobStateComponent, CureZombieInfection>
+{
+    // MobState because you have to die to become a zombie...
+    protected override void Effect(Entity<MobStateComponent> entity, ref EntityEffectEvent<CureZombieInfection> args)
+    {
+        if (HasComp<IncurableZombieComponent>(entity))
+            return;
+
+        RemComp<ZombifyOnDeathComponent>(entity);
+        RemComp<PendingZombieComponent>(entity);
+
+        if (args.Effect.Innoculate)
+            EnsureComp<ZombieImmuneComponent>(entity);
+    }
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CauseZombieInfection : EntityEffectBase<CauseZombieInfection>
+{
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+        => Loc.GetString("entity-effect-guidebook-cause-zombie-infection", ("chance", Probability));
+}
+
+/// <inheritdoc cref="EntityEffect"/>
+public sealed partial class CureZombieInfection : EntityEffectBase<CureZombieInfection>
+{
+    /// <summary>
+    /// Do we also protect against future infections?
+    /// </summary>
+    [DataField]
+    public bool Innoculate;
+
+    public override string EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+    {
+        if (Innoculate)
+            return Loc.GetString("entity-effect-guidebook-innoculate-zombie-infection", ("chance", Probability));
+
+        return Loc.GetString("entity-effect-guidebook-cure-zombie-infection", ("chance", Probability));
+    }
+}
diff --git a/Content.Shared/EntityEffects/EntityEffect.cs b/Content.Shared/EntityEffects/EntityEffect.cs
deleted file mode 100644 (file)
index 9987590..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-using System.Linq;
-using System.Text.Json.Serialization;
-using Content.Shared.Chemistry;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Database;
-using Content.Shared.FixedPoint;
-using Content.Shared.Localizations;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Content.Shared.Chemistry.Reagent;
-using Robust.Shared.Toolshed.TypeParsers;
-
-namespace Content.Shared.EntityEffects;
-
-/// <summary>
-///     Entity effects describe behavior that occurs on different kinds of triggers, e.g. when a reagent is ingested and metabolized by some
-///     organ. They only trigger when all of <see cref="Conditions"/> are satisfied.
-/// </summary>
-[ImplicitDataDefinitionForInheritors]
-[MeansImplicitUse]
-public abstract partial class EntityEffect
-{
-    private protected string _id => this.GetType().Name;
-    /// <summary>
-    ///     The list of conditions required for the effect to activate. Not required.
-    /// </summary>
-    [DataField("conditions")]
-    public EntityEffectCondition[]? Conditions;
-
-    public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
-
-    protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys);
-
-    /// <summary>
-    ///     What's the chance, from 0 to 1, that this effect will occur?
-    /// </summary>
-    [DataField("probability")]
-    public float Probability = 1.0f;
-
-    public virtual LogImpact LogImpact { get; private set; } = LogImpact.Low;
-
-    /// <summary>
-    ///     Should this entity effect log at all?
-    /// </summary>
-    public virtual bool ShouldLog { get; private set; } = false;
-
-    public abstract void Effect(EntityEffectBaseArgs args);
-
-    /// <summary>
-    /// Produces a localized, bbcode'd guidebook description for this effect.
-    /// </summary>
-    /// <returns></returns>
-    public string? GuidebookEffectDescription(IPrototypeManager prototype, IEntitySystemManager entSys)
-    {
-        var effect = ReagentEffectGuidebookText(prototype, entSys);
-        if (effect is null)
-            return null;
-
-        return Loc.GetString(ReagentEffectFormat, ("effect", effect), ("chance", Probability),
-            ("conditionCount", Conditions?.Length ?? 0),
-            ("conditions",
-                ContentLocalizationManager.FormatList(Conditions?.Select(x => x.GuidebookExplanation(prototype)).ToList() ??
-                                                        new List<string>())));
-    }
-}
-
-public static class EntityEffectExt
-{
-    public static bool ShouldApply(this EntityEffect effect, EntityEffectBaseArgs args,
-        IRobustRandom? random = null)
-    {
-        if (random == null)
-            random = IoCManager.Resolve<IRobustRandom>();
-
-        if (effect.Probability < 1.0f && !random.Prob(effect.Probability))
-            return false;
-
-        if (effect.Conditions != null)
-        {
-            foreach (var cond in effect.Conditions)
-            {
-                if (!cond.Condition(args))
-                    return false;
-            }
-        }
-
-        return true;
-    }
-}
-
-[ByRefEvent]
-public struct ExecuteEntityEffectEvent<T> where T : EntityEffect
-{
-    public T Effect;
-    public EntityEffectBaseArgs Args;
-
-    public ExecuteEntityEffectEvent(T effect, EntityEffectBaseArgs args)
-    {
-        Effect = effect;
-        Args = args;
-    }
-}
-
-/// <summary>
-///     EntityEffectBaseArgs only contains the target of an effect.
-///     If a trigger wants to include more info (e.g. the quantity of the chemical triggering the effect), it can be extended (see EntityEffectReagentArgs).
-/// </summary>
-public record class EntityEffectBaseArgs
-{
-    public EntityUid TargetEntity;
-
-    public IEntityManager EntityManager = default!;
-
-    public EntityEffectBaseArgs(EntityUid targetEntity, IEntityManager entityManager)
-    {
-        TargetEntity = targetEntity;
-        EntityManager = entityManager;
-    }
-}
-
-public record class EntityEffectReagentArgs : EntityEffectBaseArgs
-{
-    public EntityUid? OrganEntity;
-
-    public Solution? Source;
-
-    public FixedPoint2 Quantity;
-
-    public ReagentPrototype? Reagent;
-
-    public ReactionMethod? Method;
-
-    public FixedPoint2 Scale;
-
-    public EntityEffectReagentArgs(EntityUid targetEntity, IEntityManager entityManager, EntityUid? organEntity, Solution? source, FixedPoint2 quantity, ReagentPrototype? reagent, ReactionMethod? method, FixedPoint2 scale) : base(targetEntity, entityManager)
-    {
-        OrganEntity = organEntity;
-        Source = source;
-        Quantity = quantity;
-        Reagent = reagent;
-        Method = method;
-        Scale = scale;
-    }
-}
diff --git a/Content.Shared/EntityEffects/EntityEffectCondition.cs b/Content.Shared/EntityEffects/EntityEffectCondition.cs
deleted file mode 100644 (file)
index 4c27b1e..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-using System.Text.Json.Serialization;
-using JetBrains.Annotations;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.EntityEffects;
-
-[ImplicitDataDefinitionForInheritors]
-[MeansImplicitUse]
-public abstract partial class EntityEffectCondition
-{
-    [JsonPropertyName("id")] private protected string _id => this.GetType().Name;
-
-    public abstract bool Condition(EntityEffectBaseArgs args);
-
-    /// <summary>
-    /// Effect explanations are of the form "[chance to] [action] when [condition] and [condition]"
-    /// </summary>
-    /// <param name="prototype"></param>
-    /// <returns></returns>
-    public abstract string GuidebookExplanation(IPrototypeManager prototype);
-}
-
-[ByRefEvent]
-public struct CheckEntityEffectConditionEvent<T> where T : EntityEffectCondition
-{
-    public T Condition;
-    public EntityEffectBaseArgs Args;
-    public bool Result;
-}
diff --git a/Content.Shared/EntityEffects/EventEntityEffect.cs b/Content.Shared/EntityEffects/EventEntityEffect.cs
deleted file mode 100644 (file)
index 22cd035..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Content.Shared.EntityEffects;
-
-public abstract partial class EventEntityEffect<T> : EntityEffect where T : EntityEffect
-{
-    public override void Effect(EntityEffectBaseArgs args)
-    {
-        if (this is not T type)
-            return;
-        var ev = new ExecuteEntityEffectEvent<T>(type, args);
-        args.EntityManager.EventBus.RaiseEvent(EventSource.Local, ref ev);
-    }
-}
diff --git a/Content.Shared/EntityEffects/EventEntityEffectCondition.cs b/Content.Shared/EntityEffects/EventEntityEffectCondition.cs
deleted file mode 100644 (file)
index f36b177..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Content.Shared.EntityEffects;
-
-public abstract partial class EventEntityEffectCondition<T> : EntityEffectCondition where T : EventEntityEffectCondition<T>
-{
-    public override bool Condition(EntityEffectBaseArgs args)
-    {
-        if (this is not T type)
-            return false;
-
-        var evt = new CheckEntityEffectConditionEvent<T> { Condition = type, Args = args };
-        args.EntityManager.EventBus.RaiseEvent(EventSource.Local, ref evt);
-        return evt.Result;
-    }
-}
diff --git a/Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs b/Content.Shared/EntityEffects/SharedEntityEffectsSystem.cs
new file mode 100644 (file)
index 0000000..6245fc0
--- /dev/null
@@ -0,0 +1,251 @@
+using System.Linq;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Chemistry;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Database;
+using Content.Shared.EntityConditions;
+using Content.Shared.Localizations;
+using Content.Shared.Random.Helpers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.EntityEffects;
+
+/// <summary>
+/// This handles entity effects.
+/// Specifically it handles the receiving of events for causing entity effects, and provides
+/// public API for other systems to take advantage of entity effects.
+/// </summary>
+public sealed partial class SharedEntityEffectsSystem : EntitySystem, IEntityEffectRaiser
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
+    [Dependency] private readonly SharedEntityConditionsSystem _condition = default!;
+
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<ReactiveComponent, ReactionEntityEvent>(OnReactive);
+    }
+
+    private void OnReactive(Entity<ReactiveComponent> entity, ref ReactionEntityEvent args)
+    {
+        if (args.Reagent.ReactiveEffects == null || entity.Comp.ReactiveGroups == null)
+            return;
+
+        var scale = args.ReagentQuantity.Quantity.Float();
+
+        foreach (var (key, val) in args.Reagent.ReactiveEffects)
+        {
+            if (!val.Methods.Contains(args.Method))
+                continue;
+
+            if (!entity.Comp.ReactiveGroups.TryGetValue(key, out var group))
+                continue;
+
+            if (!group.Contains(args.Method))
+                continue;
+
+            ApplyEffects(entity, val.Effects, scale);
+        }
+
+        if (entity.Comp.Reactions == null)
+            return;
+
+        foreach (var entry in entity.Comp.Reactions)
+        {
+            if (!entry.Methods.Contains(args.Method))
+                continue;
+
+            if (entry.Reagents == null || entry.Reagents.Contains(args.Reagent.ID))
+                ApplyEffects(entity, entry.Effects, scale);
+        }
+    }
+
+    /// <summary>
+    /// Applies a list of entity effects to a target entity.
+    /// </summary>
+    /// <param name="target">Entity being targeted by the effects</param>
+    /// <param name="effects">Effects we're applying to the entity</param>
+    /// <param name="scale">Optional scale multiplier for the effects</param>
+    public void ApplyEffects(EntityUid target, EntityEffect[] effects, float scale = 1f)
+    {
+        // do all effects, if conditions apply
+        foreach (var effect in effects)
+        {
+            TryApplyEffect(target, effect, scale);
+        }
+    }
+
+    /// <summary>
+    /// Applies an entity effect to a target if all conditions pass.
+    /// </summary>
+    /// <param name="target">Target we're applying an effect to</param>
+    /// <param name="effect">Effect we're applying</param>
+    /// <param name="scale">Optional scale multiplier for the effect. Not all </param>
+    /// <returns>True if all conditions pass!</returns>
+    public bool TryApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+    {
+        if (scale < effect.MinScale)
+            return false;
+
+        // TODO: Replace with proper random prediciton when it exists.
+        if (effect.Probability <= 1f)
+        {
+            var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(target).Id, 0 });
+            var rand = new System.Random(seed);
+            if (!rand.Prob(effect.Probability))
+                return false;
+        }
+
+        // See if conditions apply
+        if (!_condition.TryConditions(target, effect.Conditions))
+            return false;
+
+        ApplyEffect(target, effect, scale);
+        return true;
+    }
+
+    /// <summary>
+    /// Applies an <see cref="EntityEffect"/> to a given target.
+    /// This doesn't check conditions so you should only call this if you know what you're doing!
+    /// </summary>
+    /// <param name="target">Target we're applying an effect to</param>
+    /// <param name="effect">Effect we're applying</param>
+    /// <param name="scale">Optional scale multiplier for the effect. Not all </param>
+    public void ApplyEffect(EntityUid target, EntityEffect effect, float scale = 1f)
+    {
+        // Clamp the scale if the effect doesn't allow scaling.
+        if (!effect.Scaling)
+            scale = Math.Min(scale, 1f);
+
+        if (effect.ShouldLog)
+        {
+            _adminLog.Add(
+                LogType.EntityEffect,
+                effect.LogImpact,
+                $"Entity effect {effect.GetType().Name:effect}"
+                + $" applied on entity {target:entity}"
+                + $" at {Transform(target).Coordinates:coordinates}"
+                + $" with a scale multiplier of {scale}"
+            );
+        }
+
+        effect.RaiseEvent(target, this, scale);
+    }
+
+    /// <summary>
+    /// Raises an effect to an entity. You should not be calling this unless you know what you're doing.
+    /// </summary>
+    public void RaiseEffectEvent<T>(EntityUid target, T effect, float scale) where T : EntityEffectBase<T>
+    {
+        var effectEv = new EntityEffectEvent<T>(effect, scale);
+        RaiseLocalEvent(target, ref effectEv);
+    }
+}
+
+/// <summary>
+/// This is a basic abstract entity effect containing all the data an entity effect needs to affect entities with effects...
+/// </summary>
+/// <typeparam name="T">The Component that is required for the effect</typeparam>
+/// <typeparam name="TEffect">The Entity Effect itself</typeparam>
+public abstract partial class EntityEffectSystem<T, TEffect> : EntitySystem where T : Component where TEffect : EntityEffectBase<TEffect>
+{
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<T, EntityEffectEvent<TEffect>>(Effect);
+    }
+
+    protected abstract void Effect(Entity<T> entity, ref EntityEffectEvent<TEffect> args);
+}
+
+/// <summary>
+/// Used to raise an EntityEffect without losing the type of effect.
+/// </summary>
+public interface IEntityEffectRaiser
+{
+    void RaiseEffectEvent<T>(EntityUid target, T effect, float scale) where T : EntityEffectBase<T>;
+}
+
+/// <summary>
+/// Used to store an <see cref="EntityEffect"/> so it can be raised without losing the type of the condition.
+/// </summary>
+/// <typeparam name="T">The Condition wer are raising.</typeparam>
+public abstract partial class EntityEffectBase<T> : EntityEffect where T : EntityEffectBase<T>
+{
+    public override void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale)
+    {
+        if (this is not T type)
+            return;
+
+        raiser.RaiseEffectEvent(target, type, scale);
+    }
+}
+
+/// <summary>
+/// A basic instantaneous effect which can be applied to an entity via events.
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+public abstract partial class EntityEffect
+{
+    public abstract void RaiseEvent(EntityUid target, IEntityEffectRaiser raiser, float scale);
+
+    [DataField]
+    public EntityCondition[]? Conditions;
+
+    /// <summary>
+    /// If our scale is less than this value, the effect fails.
+    /// </summary>
+    [DataField]
+    public virtual float MinScale { get; private set; }
+
+    /// <summary>
+    /// If true, then it allows the scale multiplier to go above 1.
+    /// </summary>
+    [DataField]
+    public virtual bool Scaling { get; private set; }
+
+    // TODO: This should be an entity condition but guidebook relies on it heavily for formatting...
+    /// <summary>
+    /// Probability of the effect occuring.
+    /// </summary>
+    [DataField]
+    public float Probability = 1.0f;
+
+    /// <summary>
+    /// The description of this entity effect that shows in guidebooks.
+    /// </summary>
+    public virtual string? EntityEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => null;
+
+    /// <summary>
+    /// Whether this effect should be logged in admin logs.
+    /// </summary>
+    [ViewVariables]
+    public virtual bool ShouldLog => true;
+
+    /// <summary>
+    /// If this effect is logged, how important is the log?
+    /// </summary>
+    [ViewVariables]
+    public virtual LogImpact LogImpact => LogImpact.Low;
+}
+
+/// <summary>
+/// An Event carrying an entity effect.
+/// </summary>
+/// <param name="Effect">The Effect</param>
+/// <param name="Scale">A strength scalar for the effect, defaults to 1 and typically only goes under for incomplete reactions.</param>
+[ByRefEvent, Access(typeof(SharedEntityEffectsSystem))]
+public readonly record struct EntityEffectEvent<T>(T Effect, float Scale) where T : EntityEffectBase<T>
+{
+    /// <summary>
+    /// The Condition being raised in this event
+    /// </summary>
+    public readonly T Effect = Effect;
+
+    /// <summary>
+    /// The Scale modifier of this Effect.
+    /// </summary>
+    public readonly float Scale = Scale;
+}
index 99ac22e3301757514a954b578ccd12cac31fdb92..a56ff9e683990ed912d0465bdaaa1da1ebcc57ad 100644 (file)
@@ -19,6 +19,7 @@ namespace Content.Shared.Movement.Systems;
 /// </remarks>
 public sealed class MovementModStatusSystem : EntitySystem
 {
+    public static readonly EntProtoId ReagentSpeed = "ReagentSpeedStatusEffect";
     public static readonly EntProtoId VomitingSlowdown = "VomitingSlowdownStatusEffect";
     public static readonly EntProtoId TaserSlowdown = "TaserSlowdownStatusEffect";
     public static readonly EntProtoId FlashSlowdown = "FlashSlowdownStatusEffect";
index 990c8105c52a5b209a1a7819541cef2d91671d06..086349454c8609439319d9c3c4e7221d5bcd056d 100644 (file)
@@ -1,9 +1,8 @@
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Chemistry.Components.SolutionManager;
 using Content.Shared.Chemistry.Reagent;
-using Content.Shared.EntityEffects.Effects;
+using Content.Shared.EntityEffects.Effects.Body;
 using Content.Shared.FixedPoint;
 using Content.Shared.Inventory;
 using Content.Shared.Nutrition.Components;
@@ -228,7 +227,7 @@ public sealed partial class IngestionSystem
                     // ignores any effect conditions, just cares about how much it can hydrate
                     if (effect is SatiateHunger hunger)
                     {
-                        total += hunger.NutritionFactor * quantity.Quantity.Float();
+                        total += hunger.Factor * quantity.Quantity.Float();
                     }
                 }
             }
@@ -279,7 +278,7 @@ public sealed partial class IngestionSystem
                     // ignores any effect conditions, just cares about how much it can hydrate
                     if (effect is SatiateThirst thirst)
                     {
-                        total += thirst.HydrationFactor * quantity.Quantity.Float();
+                        total += thirst.Factor * quantity.Quantity.Float();
                     }
                 }
             }
index 905ad98c6c534ff3986357899bd6898b8d4b9f83..0d153ecfaa24b30fa2fb8616329efb4b0fd72b1b 100644 (file)
@@ -1,6 +1,9 @@
+using System.ComponentModel.Design;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Content.Shared.StatusEffectNew.Components;
 using Robust.Shared.Prototypes;
+using YamlDotNet.Core.Tokens;
 
 namespace Content.Shared.StatusEffectNew;
 
@@ -291,6 +294,21 @@ public sealed partial class StatusEffectsSystem
         return false;
     }
 
+    /// <summary>
+    /// A method which specifically removes time from a status effect, or removes the status effect if time is null.
+    /// </summary>
+    /// <param name="uid">The target entity on which the effect is applied.</param>
+    /// <param name="effectProto">The prototype ID of the status effect to modify.</param>
+    /// <param name="time">
+    /// The time adjustment to apply to the status effect. Positive values extend the duration,
+    /// while negative values reduce it.
+    /// </param>
+    /// <returns> True if duration was edited successfully, false otherwise.</returns>
+    public bool TryRemoveTime(EntityUid uid, EntProtoId effectProto, TimeSpan? time)
+    {
+        return time == null ? TryRemoveStatusEffect(uid, effectProto) : TryAddTime(uid, effectProto, time.Value);
+    }
+
     /// <summary>
     /// Attempts to set the remaining time for a status effect on an entity.
     /// </summary>
index f484bd012e54f74e71386b76cd230866fa094938..ae8f97062399205eb7e03d5a7bf12d98e054aeef 100644 (file)
@@ -95,7 +95,7 @@ public abstract partial class SharedStunSystem
 
     private void OnRejuvenate(Entity<KnockedDownComponent> entity, ref RejuvenateEvent args)
     {
-        SetKnockdownNextUpdate((entity, entity), GameTiming.CurTime);
+        SetKnockdownNextUpdate(entity, GameTiming.CurTime);
 
         if (entity.Comp.AutoStand)
             RemComp<KnockedDownComponent>(entity);
@@ -166,7 +166,7 @@ public abstract partial class SharedStunSystem
         if (!Resolve(entity, ref entity.Comp, false))
             return;
 
-        SetKnockdownNextUpdate(entity, GameTiming.CurTime + time);
+        SetKnockdownNextUpdate((entity, entity.Comp), GameTiming.CurTime + time);
     }
 
     /// <summary>
@@ -227,11 +227,8 @@ public abstract partial class SharedStunSystem
     /// </summary>
     /// <param name="entity">Entity whose timer we're updating</param>
     /// <param name="time">The exact time we're setting the next update to.</param>
-    private void SetKnockdownNextUpdate(Entity<KnockedDownComponent?> entity, TimeSpan time)
+    private void SetKnockdownNextUpdate(Entity<KnockedDownComponent> entity, TimeSpan time)
     {
-        if (!Resolve(entity, ref entity.Comp, false))
-            return;
-
         if (GameTiming.CurTime > time)
             time = GameTiming.CurTime;
 
index dcf9ee4f60816fc7b8ba5da175fd841ad4cc240e..8bb5949c23098adf2c7a9ba4fc85c5e1e65b9097 100644 (file)
@@ -288,9 +288,12 @@ public abstract partial class SharedStunSystem : EntitySystem
         }
     }
 
-    public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan duration)
+    public bool TryAddParalyzeDuration(EntityUid uid, TimeSpan? duration)
     {
-        if (!_status.TryAddStatusEffectDuration(uid, StunId, duration))
+        if (duration == null)
+            return TryUpdateParalyzeDuration(uid, duration);
+
+        if (!_status.TryAddStatusEffectDuration(uid, StunId, duration.Value))
             return false;
 
         // We can't exit knockdown when we're stunned, so this prevents knockdown lasting longer than the stun.
similarity index 98%
rename from Content.Server/Temperature/Components/TemperatureComponent.cs
rename to Content.Shared/Temperature/Components/TemperatureComponent.cs
index ae2c373b1403c04d4c43d72e6188224a0cf0037b..b73d1f77d10e8d78276b9237c5fdb986bcbc4ed2 100644 (file)
@@ -4,7 +4,7 @@ using Content.Shared.Damage;
 using Content.Shared.FixedPoint;
 using Robust.Shared.Prototypes;
 
-namespace Content.Server.Temperature.Components;
+namespace Content.Shared.Temperature.Components;
 
 /// <summary>
 /// Handles changing temperature,
index efea2df5affa2ae501bc457c1ef8997fab8adbcf..9561c6dfe14607022609586ec3f0689588da0241 100644 (file)
@@ -1,7 +1,9 @@
 using System.Linq;
+using Content.Shared.Atmos;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Temperature.Components;
+using Robust.Shared.Physics.Components;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Temperature.Systems;
@@ -9,7 +11,7 @@ namespace Content.Shared.Temperature.Systems;
 /// <summary>
 /// This handles predicting temperature based speedup.
 /// </summary>
-public sealed class SharedTemperatureSystem : EntitySystem
+public abstract class SharedTemperatureSystem : EntitySystem
 {
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
@@ -77,4 +79,19 @@ public sealed class SharedTemperatureSystem : EntitySystem
             Dirty(uid, temp);
         }
     }
+
+    public virtual void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null)
+    {
+
+    }
+
+    public float GetHeatCapacity(EntityUid uid, TemperatureComponent? comp = null, PhysicsComponent? physics = null)
+    {
+        if (!Resolve(uid, ref comp) || !Resolve(uid, ref physics, false) || physics.FixturesMass <= 0)
+        {
+            return Atmospherics.MinimumHeatCapacity;
+        }
+
+        return comp.SpecificHeat * physics.FixturesMass;
+    }
 }
similarity index 77%
rename from Content.Server/Zombies/IncurableZombieComponent.cs
rename to Content.Shared/Zombies/IncurableZombieComponent.cs
index 375b814b51da219136e08aabdb1ca4608b738604..2d5495029180012faa2b041267be87320feadbbc 100644 (file)
@@ -1,10 +1,11 @@
 using Robust.Shared.Prototypes;
 
-namespace Content.Server.Zombies;
+namespace Content.Shared.Zombies;
 
 /// <summary>
 /// This is used for a zombie that cannot be cured by any methods. Gives a succumb to zombie infection action.
 /// </summary>
+/// <remarks> We don't network this component for anti-cheat purposes.</remarks>
 [RegisterComponent]
 public sealed partial class IncurableZombieComponent : Component
 {
index 2ffb0b2be23189c361a273e7edc7071634f2b16d..bcb3250db6f8569d1b9ec364e533a515b5fcecbe 100644 (file)
@@ -1,5 +1,8 @@
 guidebook-reagent-effect-description =
-    {$chance ->
+    {$quantity ->
+        [0] {""}
+        *[other] If there is at least {$quantity}u {$reagent},{" "}
+    }{$chance ->
         [1] { $effect }
         *[other] Has a { NATURALPERCENT($chance, 2) } chance to { $effect }
     }{ $conditionCount ->
diff --git a/Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl b/Resources/Locale/en-US/guidebook/chemistry/statuseffects.ftl
deleted file mode 100644 (file)
index 3379304..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-reagent-effect-status-effect-Stun = stunning
-reagent-effect-status-effect-KnockedDown = knockdown
-reagent-effect-status-effect-Jitter = jittering
-reagent-effect-status-effect-TemporaryBlindness = blindness
-reagent-effect-status-effect-SeeingRainbows = hallucinations
-reagent-effect-status-effect-Muted = inability to speak
-reagent-effect-status-effect-Stutter = stuttering
-reagent-effect-status-effect-ForcedSleep = unconsciousness
-reagent-effect-status-effect-Drunk = drunkenness
-reagent-effect-status-effect-PressureImmunity = pressure immunity
-reagent-effect-status-effect-Pacified = combat pacification
-reagent-effect-status-effect-RatvarianLanguage = ratvarian language patterns
-reagent-effect-status-effect-StaminaModifier = modified stamina
-reagent-effect-status-effect-RadiationProtection = radiation protection
-reagent-effect-status-effect-Drowsiness = drowsiness
-reagent-effect-status-effect-Adrenaline = adrenaline
similarity index 71%
rename from Resources/Locale/en-US/guidebook/chemistry/effects.ftl
rename to Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
index 07c908c0357b971657e64ed729f7b9cb79386a21..0de71133caec5c3a33b7ed8fecc1da26a5c83512 100644 (file)
@@ -16,7 +16,7 @@
         *[other] satiate
     }
 
-reagent-effect-guidebook-create-entity-reaction-effect =
+entity-effect-guidebook-spawn-entity =
     { $chance ->
         [1] Creates
         *[other] create
@@ -25,37 +25,37 @@ reagent-effect-guidebook-create-entity-reaction-effect =
         *[other] {$amount} {MAKEPLURAL($entname)}
     }
 
-reagent-effect-guidebook-explosion-reaction-effect =
+entity-effect-guidebook-explosion =
     { $chance ->
         [1] Causes
         *[other] cause
     } an explosion
 
-reagent-effect-guidebook-emp-reaction-effect =
+entity-effect-guidebook-emp =
     { $chance ->
         [1] Causes
         *[other] cause
     } an electromagnetic pulse
 
-reagent-effect-guidebook-flash-reaction-effect =
+entity-effect-guidebook-flash =
     { $chance ->
         [1] Causes
         *[other] cause
     } a blinding flash
 
-reagent-effect-guidebook-foam-area-reaction-effect =
+entity-effect-guidebook-foam-area =
     { $chance ->
         [1] Creates
         *[other] create
     } large quantities of foam
 
-reagent-effect-guidebook-smoke-area-reaction-effect =
+entity-effect-guidebook-smoke-area =
     { $chance ->
         [1] Creates
         *[other] create
     } large quantities of smoke
 
-reagent-effect-guidebook-satiate-thirst =
+entity-effect-guidebook-satiate-thirst =
     { $chance ->
         [1] Satiates
         *[other] satiate
@@ -64,7 +64,7 @@ reagent-effect-guidebook-satiate-thirst =
         *[other] thirst at {NATURALFIXED($relative, 3)}x the average rate
     }
 
-reagent-effect-guidebook-satiate-hunger =
+entity-effect-guidebook-satiate-hunger =
     { $chance ->
         [1] Satiates
         *[other] satiate
@@ -73,7 +73,7 @@ reagent-effect-guidebook-satiate-hunger =
         *[other] hunger at {NATURALFIXED($relative, 3)}x the average rate
     }
 
-reagent-effect-guidebook-health-change =
+entity-effect-guidebook-health-change =
     { $chance ->
         [1] { $healsordeals ->
                 [heals] Heals
@@ -87,7 +87,7 @@ reagent-effect-guidebook-health-change =
                  }
     } { $changes }
 
-reagent-effect-guidebook-even-health-change =
+entity-effect-guidebook-even-health-change =
     { $chance ->
         [1] { $healsordeals ->
             [heals] Evenly heals
@@ -101,44 +101,73 @@ reagent-effect-guidebook-even-health-change =
         }
     } { $changes }
 
-
-reagent-effect-guidebook-status-effect =
+entity-effect-guidebook-status-effect-old =
     { $type ->
         [update]{ $chance ->
                     [1] Causes
-                    *[other] cause
-                } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
+                     *[other] cause
+                 } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
         [add]   { $chance ->
                     [1] Causes
                     *[other] cause
                 } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
-        *[set]  { $chance ->
+        [set]  { $chance ->
                     [1] Causes
                     *[other] cause
-                } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
-        [remove]{ $chance ->
+                } {LOC($key)} for {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
+        *[remove]{ $chance ->
                     [1] Removes
                     *[other] remove
                 } {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
     }
 
-reagent-effect-guidebook-status-effect-delay =
+entity-effect-guidebook-status-effect =
     { $type ->
+        [update]{ $chance ->
+                    [1] Causes
+                    *[other] cause
+                 } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
         [add]   { $chance ->
                     [1] Causes
                     *[other] cause
                 } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} with accumulation
-        *[set]  { $chance ->
+        [set]  { $chance ->
                     [1] Causes
                     *[other] cause
                 } {LOC($key)} for at least {NATURALFIXED($time, 3)} {MANY("second", $time)} without accumulation
-        [remove]{ $chance ->
+        *[remove]{ $chance ->
                     [1] Removes
                     *[other] remove
                 } {NATURALFIXED($time, 3)} {MANY("second", $time)} of {LOC($key)}
-    } after a {NATURALFIXED($delay, 3)} second delay
+    } { $delay ->
+        [0] immediately
+        *[other] after a {NATURALFIXED($delay, 3)} second delay
+    }
+
+entity-effect-guidebook-status-effect-indef =
+    { $type ->
+        [update]{ $chance ->
+                    [1] Causes
+                    *[other] cause
+                 } permanent {LOC($key)}
+        [add]   { $chance ->
+                    [1] Causes
+                    *[other] cause
+                } permanent {LOC($key)}
+        [set]  { $chance ->
+                    [1] Causes
+                    *[other] cause
+                } permanent {LOC($key)}
+        *[remove]{ $chance ->
+                    [1] Removes
+                    *[other] remove
+                } {LOC($key)}
+    } { $delay ->
+        [0] immediately
+        *[other] after a {NATURALFIXED($delay, 3)} second delay
+    }
 
-reagent-effect-guidebook-knockdown =
+entity-effect-guidebook-knockdown =
     { $type ->
         [update]{ $chance ->
                     [1] Causes
@@ -158,13 +187,13 @@ reagent-effect-guidebook-knockdown =
                 } {NATURALFIXED($time, 3)} {MANY("second", $time)} of knockdown
     }
 
-reagent-effect-guidebook-set-solution-temperature-effect =
+entity-effect-guidebook-set-solution-temperature-effect =
     { $chance ->
         [1] Sets
         *[other] set
     } the solution temperature to exactly {NATURALFIXED($temperature, 2)}k
 
-reagent-effect-guidebook-adjust-solution-temperature-effect =
+entity-effect-guidebook-adjust-solution-temperature-effect =
     { $chance ->
         [1] { $deltasign ->
                 [1] Adds
@@ -180,7 +209,7 @@ reagent-effect-guidebook-adjust-solution-temperature-effect =
                 *[-1] at least {NATURALFIXED($mintemp, 2)}k
             }
 
-reagent-effect-guidebook-adjust-reagent-reagent =
+entity-effect-guidebook-adjust-reagent-reagent =
     { $chance ->
         [1] { $deltasign ->
                 [1] Adds
@@ -196,7 +225,7 @@ reagent-effect-guidebook-adjust-reagent-reagent =
         *[-1] from
     } the solution
 
-reagent-effect-guidebook-adjust-reagent-group =
+entity-effect-guidebook-adjust-reagent-group =
     { $chance ->
         [1] { $deltasign ->
                 [1] Adds
@@ -212,7 +241,7 @@ reagent-effect-guidebook-adjust-reagent-group =
             *[-1] from
         } the solution
 
-reagent-effect-guidebook-adjust-temperature =
+entity-effect-guidebook-adjust-temperature =
     { $chance ->
         [1] { $deltasign ->
                 [1] Adds
@@ -228,37 +257,37 @@ reagent-effect-guidebook-adjust-temperature =
             *[-1] from
         } the body it's in
 
-reagent-effect-guidebook-chem-cause-disease =
+entity-effect-guidebook-chem-cause-disease =
     { $chance ->
         [1] Causes
         *[other] cause
     } the disease { $disease }
 
-reagent-effect-guidebook-chem-cause-random-disease =
+entity-effect-guidebook-chem-cause-random-disease =
     { $chance ->
         [1] Causes
         *[other] cause
     } the diseases { $diseases }
 
-reagent-effect-guidebook-jittering =
+entity-effect-guidebook-jittering =
     { $chance ->
         [1] Causes
         *[other] cause
     } jittering
 
-reagent-effect-guidebook-chem-clean-bloodstream =
+entity-effect-guidebook-clean-bloodstream =
     { $chance ->
         [1] Cleanses
         *[other] cleanse
     } the bloodstream of other chemicals
 
-reagent-effect-guidebook-cure-disease =
+entity-effect-guidebook-cure-disease =
     { $chance ->
         [1] Cures
         *[other] cure
     } diseases
 
-reagent-effect-guidebook-cure-eye-damage =
+entity-effect-guidebook-eye-damage =
     { $chance ->
         [1] { $deltasign ->
                 [1] Deals
@@ -271,13 +300,13 @@ reagent-effect-guidebook-cure-eye-damage =
             }
     } eye damage
 
-reagent-effect-guidebook-chem-vomit =
+entity-effect-guidebook-vomit =
     { $chance ->
         [1] Causes
         *[other] cause
     } vomiting
 
-reagent-effect-guidebook-create-gas =
+entity-effect-guidebook-create-gas =
     { $chance ->
         [1] Creates
         *[other] create
@@ -286,55 +315,55 @@ reagent-effect-guidebook-create-gas =
         *[other] moles
     } of { $gas }
 
-reagent-effect-guidebook-drunk =
+entity-effect-guidebook-drunk =
     { $chance ->
         [1] Causes
         *[other] cause
     } drunkness
 
-reagent-effect-guidebook-electrocute =
+entity-effect-guidebook-electrocute =
     { $chance ->
         [1] Electrocutes
         *[other] electrocute
     } the metabolizer for {NATURALFIXED($time, 3)} {MANY("second", $time)}
 
-reagent-effect-guidebook-emote =
+entity-effect-guidebook-emote =
     { $chance ->
         [1] Will force
         *[other] force
     } the metabolizer to [bold][color=white]{$emote}[/color][/bold]
 
-reagent-effect-guidebook-extinguish-reaction =
+entity-effect-guidebook-extinguish-reaction =
     { $chance ->
         [1] Extinguishes
         *[other] extinguish
     } fire
 
-reagent-effect-guidebook-flammable-reaction =
+entity-effect-guidebook-flammable-reaction =
     { $chance ->
         [1] Increases
         *[other] increase
     } flammability
 
-reagent-effect-guidebook-ignite =
+entity-effect-guidebook-ignite =
     { $chance ->
         [1] Ignites
         *[other] ignite
     } the metabolizer
 
-reagent-effect-guidebook-make-sentient =
+entity-effect-guidebook-make-sentient =
     { $chance ->
         [1] Makes
         *[other] make
     } the metabolizer sentient
 
-reagent-effect-guidebook-make-polymorph =
+entity-effect-guidebook-make-polymorph =
     { $chance ->
         [1] Polymorphs
         *[other] polymorph
     } the metabolizer into a { $entityname }
 
-reagent-effect-guidebook-modify-bleed-amount =
+entity-effect-guidebook-modify-bleed-amount =
     { $chance ->
         [1] { $deltasign ->
                 [1] Induces
@@ -346,7 +375,7 @@ reagent-effect-guidebook-modify-bleed-amount =
                  }
     } bleeding
 
-reagent-effect-guidebook-modify-blood-level =
+entity-effect-guidebook-modify-blood-level =
     { $chance ->
         [1] { $deltasign ->
                 [1] Increases
@@ -358,112 +387,115 @@ reagent-effect-guidebook-modify-blood-level =
                  }
     } blood level
 
-reagent-effect-guidebook-paralyze =
+entity-effect-guidebook-paralyze =
     { $chance ->
         [1] Paralyzes
         *[other] paralyze
     } the metabolizer for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
 
-reagent-effect-guidebook-movespeed-modifier =
+entity-effect-guidebook-movespeed-modifier =
     { $chance ->
         [1] Modifies
         *[other] modify
-    } movement speed by {NATURALFIXED($walkspeed, 3)}x for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
+    } movement speed by {NATURALFIXED($sprintspeed, 3)}x for at least {NATURALFIXED($time, 3)} {MANY("second", $time)}
 
-reagent-effect-guidebook-reset-narcolepsy =
+entity-effect-guidebook-reset-narcolepsy =
     { $chance ->
         [1] Temporarily staves
         *[other] temporarily stave
     } off narcolepsy
 
-reagent-effect-guidebook-wash-cream-pie-reaction =
+entity-effect-guidebook-wash-cream-pie-reaction =
     { $chance ->
         [1] Washes
         *[other] wash
     } off cream pie from one's face
 
-reagent-effect-guidebook-cure-zombie-infection =
+entity-effect-guidebook-cure-zombie-infection =
     { $chance ->
         [1] Cures
         *[other] cure
     } an ongoing zombie infection
 
-reagent-effect-guidebook-cause-zombie-infection =
+entity-effect-guidebook-cause-zombie-infection =
     { $chance ->
         [1] Gives
         *[other] give
     } an individual the zombie infection
 
-reagent-effect-guidebook-innoculate-zombie-infection =
+entity-effect-guidebook-innoculate-zombie-infection =
     { $chance ->
         [1] Cures
         *[other] cure
     } an ongoing zombie infection, and provides immunity to future infections
 
-reagent-effect-guidebook-reduce-rotting =
+entity-effect-guidebook-reduce-rotting =
     { $chance ->
         [1] Regenerates
         *[other] regenerate
     } {NATURALFIXED($time, 3)} {MANY("second", $time)} of rotting
 
-reagent-effect-guidebook-area-reaction =
+entity-effect-guidebook-area-reaction =
     { $chance ->
         [1] Causes
         *[other] cause
     } a smoke or foam reaction for {NATURALFIXED($duration, 3)} {MANY("second", $duration)}
 
-reagent-effect-guidebook-add-to-solution-reaction =
+entity-effect-guidebook-add-to-solution-reaction =
     { $chance ->
         [1] Causes
         *[other] cause
-    } chemicals applied to an object to be added to its internal solution container
+    } {$reagent} to be added to its internal solution container
 
-reagent-effect-guidebook-artifact-unlock =
+entity-effect-guidebook-artifact-unlock =
     { $chance ->
         [1] Helps
         *[other] help
         } unlock an alien artifact.
 
-reagent-effect-guidebook-artifact-durability-restore =
+entity-effect-guidebook-artifact-durability-restore =
     Restores {$restored} durability in active alien artifact nodes.
 
-reagent-effect-guidebook-plant-attribute =
+entity-effect-guidebook-plant-attribute =
     { $chance ->
         [1] Adjusts
         *[other] adjust
-    } {$attribute} by [color={$colorName}]{$amount}[/color]
+    } {$attribute} by {$positive ->
+    [true] [color=red]{$amount}[/color]
+    *[false] [color=green]{$amount}[/color]
+    }
 
-reagent-effect-guidebook-plant-cryoxadone =
+entity-effect-guidebook-plant-cryoxadone =
     { $chance ->
         [1] Ages back
         *[other] age back
     } the plant, depending on the plant's age and time to grow
 
-reagent-effect-guidebook-plant-phalanximine =
+entity-effect-guidebook-plant-phalanximine =
     { $chance ->
         [1] Restores
         *[other] restore
     } viability to a plant rendered nonviable by a mutation
 
-reagent-effect-guidebook-plant-diethylamine =
+entity-effect-guidebook-plant-diethylamine =
     { $chance ->
         [1] Increases
         *[other] increase
     } the plant's lifespan and/or base health with 10% chance for each
 
-reagent-effect-guidebook-plant-robust-harvest =
+entity-effect-guidebook-plant-robust-harvest =
     { $chance ->
         [1] Increases
         *[other] increase
     } the plant's potency by {$increase} up to a maximum of {$limit}. Causes the plant to lose its seeds once the potency reaches {$seedlesstreshold}. Trying to add potency over {$limit} may cause decrease in yield at a 10% chance
 
-reagent-effect-guidebook-plant-seeds-add =
+entity-effect-guidebook-plant-seeds-add =
     { $chance ->
         [1] Restores the
         *[other] restore the
     } seeds of the plant
 
-reagent-effect-guidebook-plant-seeds-remove =
+entity-effect-guidebook-plant-seeds-remove =
     { $chance ->
         [1] Removes the
         *[other] remove the
diff --git a/Resources/Locale/en-US/guidebook/entity-effects/statuseffects.ftl b/Resources/Locale/en-US/guidebook/entity-effects/statuseffects.ftl
new file mode 100644 (file)
index 0000000..435041d
--- /dev/null
@@ -0,0 +1,16 @@
+entity-effect-status-effect-Stun = stunning
+entity-effect-status-effect-KnockedDown = knockdown
+entity-effect-status-effect-Jitter = jittering
+entity-effect-status-effect-TemporaryBlindness = blindness
+entity-effect-status-effect-SeeingRainbows = hallucinations
+entity-effect-status-effect-Muted = inability to speak
+entity-effect-status-effect-Stutter = stuttering
+entity-effect-status-effect-ForcedSleep = unconsciousness
+entity-effect-status-effect-Drunk = drunkenness
+entity-effect-status-effect-PressureImmunity = pressure immunity
+entity-effect-status-effect-Pacified = combat pacification
+entity-effect-status-effect-RatvarianLanguage = ratvarian language patterns
+entity-effect-status-effect-StaminaModifier = modified stamina
+entity-effect-status-effect-RadiationProtection = radiation protection
+entity-effect-status-effect-Drowsiness = drowsiness
+entity-effect-status-effect-Adrenaline = adrenaline
index c812140812fe33a6a86fffdf47959006dd23651d..ebe8134d1b4e7d3d377777aefdf902bace429c13 100644 (file)
     - reagents: [ Water, SpaceCleaner ]
       methods: [ Touch ]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
   - type: Crawler
 
 
index e5f900b20950e509339b7332d8cc812e2b712816..dd3fd5d4308e6c94e963f488aad88e0ab581a8ae 100644 (file)
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           groups:
-            Brute: -0.25
+            Brute: -0.15
     - reagents: [ Blood ]
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           groups:
-            Brute: -0.5
-            Burn: -0.5
+            Brute: -0.25
+            Burn: -0.25
     - reagents: [ RobustHarvest ]
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           groups:
-            Brute: -2
-            Burn: -2
+            Brute: -1
+            Burn: -1
     - reagents: [ WeedKiller, PlantBGone ]
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           types:
-            Heat: 2
+            Heat: 1
   - type: ReplacementAccent
     accent: tomatoKiller
   - type: Item
index 0eef52266bce9139d97a6e45b16419ff35dde005..a239ede7b06c2ac224d335c702b794c92d7f7539 100644 (file)
     - reagents: [ Water, SpaceCleaner ]
       methods: [ Touch ]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
     - reagents: [ Water ]
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           types:
-            Heat: 0.15
+            Heat: 0.05 #Same as slime species
       - !type:PopupMessage
         type: Local
         messages: [ "slime-hurt-by-water-popup" ]
index 039cac335885416184ac30d79a93c5e227ca91c9..785c8939edd40f8efb541d5997801615f1536169 100644 (file)
       methods: [Touch, Ingestion, Injection]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           types:
             Caustic: 1
index 92d118cb2642289c729f9f7e58180a9e4bb4b45c..0624e9670a4a66db83128135517180e865645ab9 100644 (file)
     - reagents: [Water]
       methods: [Touch]
       effects:
-      - !type:WearableReaction
+      - !type:SpawnEntityInInventory
         slot: head
-        prototypeID: WaterDropletHat
+        entity: WaterDropletHat
     - reagents: [Water, SpaceCleaner]
       methods: [Touch]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
   # Damage (Self)
   - type: Bloodstream
     bloodReagent: CopperBlood
index d4fecee2ca50873fb43ef15494e469922c2fccfd..d2a8b21a0b1afc741a8403ef504c76fcc1ca4a3e 100644 (file)
     - reagents: [Water, SpaceCleaner]
       methods: [Touch]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
   - type: StatusEffects
     allowed:
     - Electrocution
index c2939347a82c10382e850d0496eff66a11cd3c59..fa992ded0baca7ad746a22cdb4f417a6751bcc2c 100644 (file)
     - reagents: [Water, SpaceCleaner]
       methods: [Touch]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
     - reagents: [ PlantBGone ]
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           types:
-            Blunt: 0.1
-            Slash: 0.1
-            Piercing: 0.15
+            Blunt: 0.05
+            Slash: 0.05
+            Piercing: 0.075
       - !type:PopupMessage
         type: Local
         visualType: Large
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           types:
-            Poison: 0.25
+            Poison: 0.125
       - !type:PopupMessage
         type: Local
         visualType: Large
index d2232938628a9c9e02990363c95765f7e6b23eb8..ca6301b0a4c78915e0e530bb0306c947368be0f0 100644 (file)
@@ -73,7 +73,7 @@
     - reagents: [ Water, SpaceCleaner ]
       methods: [ Touch ]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
     - reagents: [ Milk, MilkGoat, MilkSoy, MilkSpoiled ]
       # add new types of milk to reagents as they appear, oat milk isn't on the list
       # because turns out oat milk has 1/30th the amount of calcium in it compared to the rest
       methods: [ Touch ]
       effects: # TODO: when magic is around - make a milk transformation to a skeleton monster
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           groups:
-            Burn: -2 # healing obviously up to discussion
-            Brute: -1.5 # these groups are the only 2 possible ways to damage a skeleton
+            Burn: -1 # healing obviously up to discussion
+            Brute: -0.75 # these groups are the only 2 possible ways to damage a skeleton
       - !type:PopupMessage
         type: Local
         visualType: Large
index 56acf52fb7178c738aec23d5eae6e9ad2703d28f..9526b490e3243d4305a584afd9073037148d68a7 100644 (file)
     - reagents: [ Water, SpaceCleaner ]
       methods: [ Touch ]
       effects:
-      - !type:WashCreamPieReaction
+      - !type:WashCreamPie
     - reagents: [ Water ]
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         damage:
           types:
-            Heat: 0.1
+            Heat: 0.05
       - !type:PopupMessage
         type: Local
         visualType: Large
index 48670291e6b77f5cd7e21446ddf40868c3b29880..2e9f231e908e1a8663b28c9e7c75b3379da8596f 100644 (file)
     - reagents: [ Water ]
       methods: [ Touch ]
       effects:
-      - !type:AddToSolutionReaction
+      - !type:AddReagentToSolution
+        reagent: Water
         solution: plushie
   - type: Fixtures
     fixtures:
index 1afe45e5eff0be1ad916d7a1a9f70a3df2f1dc17..65d507daa50cf33714de800d8e5c26f926074cce 100644 (file)
@@ -75,7 +75,7 @@
         methods: [Touch]
         effects:
         - !type:HealthChange
-          scaleByQuantity: true
+          scaling: true
           damage:
             types:
               Heat: 0.5
index dd2c73e005727fc8f15a4bc153bd40322cb46975..c9a8c2c89b884add7e49bbbff853c1e08647fbe4 100644 (file)
@@ -16,7 +16,8 @@
     - reagents: [Water]
       methods: [Touch, Ingestion, Injection]
       effects:
-      - !type:AddToSolutionReaction
+      - !type:AddReagentToSolution
+        reagent: Water
         solution: cube
   - type: Rehydratable
   - type: CollisionWake
     - reagents: [Blood]
       methods: [Touch, Ingestion, Injection]
       effects:
-      - !type:AddToSolutionReaction
+      - !type:AddReagentToSolution
+        reagent: Blood
         solution: cube
   - type: Rehydratable
     catalyst: Blood # blood is fuel
index 71142af434c9dca6f05838dfa0dc3c1322c107bb..07d4faed9c19db3825cb2eaf0113fccc6a45826c 100644 (file)
@@ -8,11 +8,32 @@
     whitelist:
       components:
       - MobState
+      - MovementSpeedModifier
+      requireAll: true
     blacklist:
       tags:
       - SlowImmune
   - type: MovementModStatusEffect
 
+- type: entity
+  parent: MobStatusEffectBase
+  id: StatusEffectSpeed
+  abstract: true
+  name: speed
+  components:
+  - type: StatusEffect
+    whitelist:
+      components:
+      - MobState
+      - MovementSpeedModifier
+      requireAll: true
+  - type: MovementModStatusEffect
+
+- type: entity
+  parent: StatusEffectSpeed
+  id: ReagentSpeedStatusEffect
+  name: reagent speed
+
 - type: entity
   parent: StatusEffectSlowdown
   id: VomitingSlowdownStatusEffect
index 725ab0c6ffc6be36e4392a1a2fade67d617f94cf..276b053157b4526a6d2b6e622bd95850c6ac9e3e 100644 (file)
@@ -52,7 +52,8 @@
     - reagents: [Water]
       methods: [Touch, Ingestion, Injection]
       effects:
-      - !type:AddToSolutionReaction
+      - !type:AddReagentToSolution
+        reagent: Water
         solution: soil
   - type: Appearance
   - type: PlantHolderVisuals
index 10a09116ade4f9dfbc357fbd90fc0418773f0f12..84907318de04198a7ce894b3d113fdb420567544 100644 (file)
@@ -17,7 +17,7 @@
         - Catwalk
   - type: TileEntityEffect
     effects:
-    - !type:FlammableReaction
+    - !type:Flammable
       multiplier: 3.75
       multiplierOnExisting: 0.75
     - !type:Ignite
index ade23b6f711f55f0538535ff2d6795dd406ca7d3..8d1881375bb914b4484802080d35c39ab1e2d9ac 100644 (file)
@@ -17,7 +17,7 @@
       - Catwalk
   - type: TileEntityEffect
     effects:
-    - !type:FlammableReaction
+    - !type:Flammable
       multiplier: 3.75
       multiplierOnExisting: 0.75
     - !type:Ignite
index c488df231b23eaba080d30df508136800c193922..a4478b5e583c30f5793a0e46d36e52a8b5372d48 100644 (file)
@@ -20,7 +20,7 @@
   - type: SolutionContainerManager
     solutions:
       pool:
-        maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable. 
+        maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable.
         reagents:
         - ReagentId: Water
           Quantity: 9999999
@@ -62,4 +62,4 @@
         - Catwalk
   - type: TileEntityEffect
     effects:
-    - !type:ExtinguishReaction
+    - !type:Extinguish
index 1120c38f3199a7322b3fe22035fef0cd7a96936f..2d45601da9bf60bd31a066f69ee4f5fca62c7a48 100644 (file)
@@ -21,7 +21,7 @@
     - name: ChangeSpecies
       baseOdds: 0.036
       appliesToProduce: false
-      effect: !type:PlantSpeciesChange
+      effect: !type:PlantMutateSpeciesChange
       persists: false
     - name: Unviable
       baseOdds: 0.109
     - name: ChangeExudeGasses
       baseOdds: 0.0145
       persists: false
-      effect: !type:PlantMutateExudeGasses
+      effect: !type:PlantMutateExudeGases
     - name: ChangeConsumeGasses
       baseOdds: 0.0036
       persists: false
-      effect: !type:PlantMutateConsumeGasses
+      effect: !type:PlantMutateConsumeGases
     - name: ChangeHarvest
       baseOdds: 0.036
       persists: false
index 9bd5bb195fa063c2c2a33c9b4c32aac362058b98..f294b5438b18fb2aeba47b5449aeab1a191a8c06 100644 (file)
@@ -30,7 +30,8 @@
       - !type:ModifyStatusEffect
         effectProto: StatusEffectSeeingRainbow
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Absinthe
           min: 10
         type: Add
         time: 5
@@ -41,7 +42,8 @@
         - absinthe-effect-feel-tulips
         probability: 0.02
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Absinthe
           min: 10
 
 - type: reagent
       effects:
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ethanol
           min: 15
-        - !type:OrganType
-          type: Dwarf
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Dwarf ]
+          inverted: true
         damage:
           types:
             Poison: 1
       # dwarves take less toxin damage and heal a marginal amount of brute
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ethanol
           min: 15
-        - !type:OrganType
-          type: Dwarf
+        - !type:MetabolizerTypeCondition
+          type: [ Dwarf ]
         damage:
           types:
             Poison: 0.2
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ethanol
           min: 15
-        - !type:OrganType
-          type: Dwarf
+        - !type:MetabolizerTypeCondition
+          type: [ Dwarf ]
         damage:
           groups:
             Brute: -1
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.04
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Ethanol
           min: 12
         # dwarves immune to vomiting from alcohol
-        - !type:OrganType
-          type: Dwarf
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Dwarf ]
+          inverted: true
       - !type:Drunk
         boozePower: 2
 
   metabolisms:
     Drink:
       effects:
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Mayojito
           min: 5
         probability: 0.5
       - !type:HealthChange
         boozePower: 10
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: BacchusBlessing
           min: 6
-        - !type:OrganType
-          type: Dwarf
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Dwarf]
+          inverted: true
         damage:
           types:
             Poison: 2 # TODO: Figure out poison amount. Ethanol does 1, this does 2 but also metabolises almost 3 to 4 times as fast as ethanol. This would be more Liver damage when that time arrives.
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: BacchusBlessing
           min: 6
-        - !type:OrganType
-          type: Dwarf
+        - !type:MetabolizerTypeCondition
+          type: [Dwarf]
         damage:
           types:
             Poison: 0.4 # TODO: Might increase this, even though it's just double of ethanol from 0.2 to 0.4
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.1 #TODO: Tweak vomit probability, maybe make this more violent and poisonous but the body aggressively purges it...
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: BacchusBlessing
           min: 8
-        - !type:OrganType
-          type: Dwarf
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Dwarf]
+          inverted: true
index 6c050a6ca699d907e21da452243cd601d9b21d3d..1311042949da3ea261caf19316e88f95e9429fc3 100644 (file)
@@ -13,7 +13,7 @@
     Extinguish:
       methods: [ Touch ]
       effects:
-      - !type:ExtinguishReaction
+      - !type:Extinguish
   plantMetabolism:
   - !type:PlantAdjustWater
     amount: 1
@@ -60,7 +60,7 @@
     Flammable:
       methods: [ Touch ]
       effects:
-      - !type:FlammableReaction
+      - !type:Flammable
   tileReactions:
   - !type:FlammableTileReaction
     temperatureMultiplier: 1.35
index 464aa93dbbb2ef4a9c00f79eb61dc11f0f08aaa7..6fac8606d9f828cd4d3e15741a1996d981fc599f 100644 (file)
     Gas:
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         conditions:
-        - !type:OrganType
-          type: Slime
-          shouldHave: true
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
         damage:
           types:
             Heat: 3
index 5ac667ec181580410bffd4d75eadac102e1dc900..67c0bf1615699928505455852fe10b0ec20d5bdc 100644 (file)
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Slime]
+          inverted: true
         damage:
           types:
             Poison: 1
index c9625c663cab2d055b970d75357b368380355bb9..45c826eff994afa1bfa8f1b310e62ead0f12e71d 100644 (file)
@@ -36,7 +36,7 @@
             Brute: -0.5
             Burn: -0.5
       # Helps you stop bleeding to an extent.
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -0.25
       - !type:SatiateHunger #Numbers are balanced with this in mind + it helps limit how much healing you can get from food
   # Lets plants benefit too
@@ -82,7 +82,7 @@
       effects:
       - !type:SatiateHunger
         conditions:
-        - !type:ReagentThreshold #Only satiates when eaten with nutriment
+        - !type:ReagentCondition #Only satiates when eaten with nutriment
           reagent: Nutriment
           min: 0.1
         factor: 1
index 6e4114cecf39c4f2700dac37b43a796919a03a02..8813b5bb66dc1c85370d26c434501721fc2b12b5 100644 (file)
         emote: Cough
         showInGuidebook: true
         conditions:
-        - !type:Breathing
-        - !type:Internals
-          usingInternals: false
+        - !type:BreathingCondition
+        - !type:InternalsCondition
+          inverted: true
 
 - type: reagent
   id: Vinegar
       - !type:AdjustReagent
         reagent: Vitamin
         amount: 0.1
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.1
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: Vinegar
             min: 6
 
 - type: reagent
           amount: 250 # thermal energy, not temp
         - !type:HealthChange
           conditions:
-            - !type:ReagentThreshold
+            - !type:ReagentCondition
+              reagent: CapsaicinOil
               min: 5
           damage:
             types:
       effects:
       - !type:SatiateHunger
         conditions:
-        - !type:ReagentThreshold #Only satiates when eaten with nutriment
+        - !type:ReagentCondition #Only satiates when eaten with nutriment
           reagent: Nutriment
           min: 0.1
         factor: 1
         amount: -250 # thermal energy, not temp
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: FrostOil
           min: 5
         damage:
           types:
index ca46881a83358279d715f3cb13c52020defa48da..dd76a34ee281bc229a285bcb071c8159db104cc1 100644 (file)
@@ -20,9 +20,9 @@
       - !type:SatiateThirst
         factor: 1.0
         conditions:
-          - !type:OrganType
-            type: Human
-            shouldHave: false
+          - !type:MetabolizerTypeCondition
+            type: [ Human ]
+            inverted: true
     Food:
       effects:
         - !type:AdjustReagent
@@ -32,8 +32,8 @@
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Bloodsucker
+        - !type:MetabolizerTypeCondition
+          type: [ Bloodsucker ]
         damage:
           groups:
             Brute: -3
         damage:
           types:
             Poison: 4
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.25
 
 - type: reagent
         damage:
           types:
             Bloodloss: -3
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -1.5
   # Just in case you REALLY want to water your plants
   plantMetabolism:
index e96d5be30c71282c7b49dff21c5b2fd6ea748948..eb5ceba470345b283a999a5e3ffa80cdeae80095 100644 (file)
@@ -14,8 +14,8 @@
       effects:
       - !type:SatiateHunger
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
 
 - type: reagent
   id: Left4Zed
       effects:
       - !type:SatiateHunger
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
       - !type:HealthChange
         damage:
           types:
             Poison: 1
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
 
 - type: reagent
   id: PestKiller
@@ -69,8 +69,8 @@
           types:
             Poison: 3
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
 
 - type: reagent
   id: PlantBGone
             Slash: 1
             Piercing: 1
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
 
 - type: reagent
   id: RobustHarvest
             Slash: -3
             Piercing: -3
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Plant
-        - !type:ReagentThreshold
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
+        - !type:ReagentCondition
+          reagent: RobustHarvest
           min: 30
         damage:
           types:
       - !type:Polymorph
         prototype: TreeMorph
         conditions:
-          - !type:OrganType
-            type: Plant
-          - !type:ReagentThreshold
+          - !type:MetabolizerTypeCondition
+            type: [Plant]
+          - !type:ReagentCondition
+            reagent: RobustHarvest
             min: 80
 
 - type: reagent
           types:
             Poison: 4
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [Plant]
 
 - type: reagent
   id: Ammonia
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Rat
-          shouldHave: false
-        - !type:ReagentThreshold
+        - !type:MetabolizerTypeCondition
+          type: [ Rat ]
+          inverted: true
+        - !type:ReagentCondition
           reagent: Ammonia
           min: 1
         ignoreResistances: true
         damage:
           types:
             Poison: 0.25
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.12
         conditions:
-        - !type:OrganType
-          type: Rat
-          shouldHave: false
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
-        - !type:ReagentThreshold
+        - !type:MetabolizerTypeCondition
+          type: [ Rat, Vox ]
+          inverted: true
+        - !type:ReagentCondition
           reagent: Ammonia
           min: 0.8
       - !type:PopupMessage
         messages: [ "ammonia-smell" ]
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Ammonia
           min: 0.25
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Rat
-        - !type:ReagentThreshold
+        - !type:MetabolizerTypeCondition
+          type: [ Rat ]
+        - !type:ReagentCondition
           reagent: Ammonia
           min: 1
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         damage:
           groups:
-            Brute: -5
-            Burn: -5
+            Brute: -2.5
+            Burn: -2.5
           types:
             Bloodloss: -5
       - !type:Oxygenate # ammonia displaces nitrogen in vox blood
         conditions:
-        - !type:OrganType
-          type: Vox
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
         factor: -4
 
 
index 81f4e421bfb43fec3044b44b868f8826a60559c1..7c0f1c8f09c3dbe5fc48258da66d2a09c4011b8f 100644 (file)
@@ -41,7 +41,8 @@
         damage:
           types:
             Poison: -1
-      - !type:ChemCleanBloodstream
+      - !type:CleanBloodstream
+        excluded: Charcoal
         cleanseRate: 3
 
 - type: reagent
@@ -94,9 +95,7 @@
       methods: [ Touch ]
       effects:
       - !type:ArtifactUnlock
-        conditions:
-        - !type:ReagentThreshold
-          min: 5
+        minScale: 5
 
 - type: reagent
   parent: Artifexium
       methods: [ Touch ]
       effects:
       - !type:ArtifactDurabilityRestore
-        conditions:
-        - !type:ReagentThreshold
-          min: 5
+        minScale: 5
 
 - type: reagent
   id: Benzene
             Heat: 1.5
     Medicine:
       effects:
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: SodiumHydroxide
           min: 5
         probability: 0.1
 
index 2ebd213fec544aa44bdb6459c890b649f15f2f2b..c5fe0e4da9746addaadcbadf5ed6ec809df6e2ab 100644 (file)
   metabolisms:
     Food:
       effects:
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: SoapReagent
           min: 6
         probability: 0.20
     Drink:
       effects:
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: SoapReagent
           min: 6
         probability: 0.20
 
       methods: [ Touch ]
       effects:
       # pva glue? no, antibiotic glue for sealing wounds
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -1.5
   metabolisms:
     Narcotic:
       - !type:GenericStatusEffect
         key: Muted
         component: Muted
-        type: Add
         time: 5
-        refresh: false
   footstepSound:
     collection: FootstepSlime
index 8d317102c1bc8648269f9c39805d8208977ca3cd..27e27be503564b3c8c3fce37095bc89c281a32da 100644 (file)
@@ -63,9 +63,9 @@
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Arachnid ]
+          inverted: true
         damage:
           types:
             Poison: 0.1
@@ -73,9 +73,8 @@
       effects:
       - !type:ModifyBloodLevel
         conditions:
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: true
+        - !type:MetabolizerTypeCondition
+          type: [ Arachnid ]
         amount: 0.4
 
 - type: reagent
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: true
+        - !type:MetabolizerTypeCondition
+          type: [ Arachnid ]
         damage:
           types:
             Poison: 0.1
       effects:
       - !type:ModifyBloodLevel
         conditions:
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Arachnid ]
+          inverted: true
         amount: 0.4
 
 - type: reagent
index 2ed57f73b06d2437e56479011c8207b53cdb197f..0ad3e9af61bd23514cdccb1a9ff5c1e9a0dd4a65 100644 (file)
@@ -21,7 +21,7 @@
       # Hail the madman logic, if it has CARP, means it helps against CARPs
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: CarpoToxin
           min: 1
         reagent: CarpoToxin
@@ -39,8 +39,8 @@
       effects:
       - !type:SatiateHunger
         conditions:
-        - !type:OrganType
-          type: Moth
+        - !type:MetabolizerTypeCondition
+          type: [ Moth ]
 
 - type: reagent
   id: BuzzochloricBees
         - "buzzochloricbees-effect-squeaky-clean"
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           max: 0
           reagent: Histamine
-        - !type:HasTag
-          invert: true
+        - !type:TagCondition
+          inverted: true
           tag: Bee
       - !type:PopupMessage
         type: Local
           - "buzzochloricbees-effect-squeaky-clean"
         probability: 0.05
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           min: 0.01
           reagent: Histamine
-        - !type:HasTag
-          invert: true
+        - !type:TagCondition
+          inverted: true
           tag: Bee
       - !type:PopupMessage
         type: Local
           - "buzzochloricbees-effect-licoxide-buzzes"
         probability: 0.05
         conditions:
-        - !type:HasTag
-          invert: true
+        - !type:TagCondition
+          inverted: true
           tag: Bee
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           min: 0.01
           reagent: Licoxide
       - !type:PopupMessage
           - "buzzochloricbees-effect-fiber-soft"
         probability: 0.05
         conditions:
-        - !type:HasTag
-          invert: true
+        - !type:TagCondition
+          inverted: true
           tag: Bee
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           min: 0.01
           reagent: Fiber
       - !type:HealthChange
             Poison: 2
             Piercing: 2
         conditions:
-        - !type:HasTag
-          invert: true
+        - !type:TagCondition
+          inverted: true
           tag: Bee
 
 - type: reagent
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: false
         damage:
           types:
-            Slash: 0.5
+            Slash: 0.25
       - !type:Emote
         emote: Scream
         probability: 0.7
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: false
         damage:
           types:
             Cold: 0.05
       - !type:AdjustTemperature
         conditions:
-        - !type:Temperature
+        - !type:TemperatureCondition
           min: 160.15
         amount: -30000
     Extinguish:
       methods: [ Touch ]
       effects:
-      - !type:ExtinguishReaction # cold
+      - !type:Extinguish # cold
   metabolisms:
     Poison:
       metabolismRate : 0.45
              Heat: -3 # ghetto burn chem. i don't think anyone would use this intentionally but it's funny
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Fresium
           max: 35
         type: Local
         probability: 0.05
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Fresium
           max: 35
         type: Local
         probability: 0.2
       - !type:AdjustTemperature
         conditions:
-        - !type:Temperature
+        - !type:TemperatureCondition
           min: 160.15 # not quite enough for cryo, but can speed it up if you wanna take the risk
         amount: -10000
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Fresium
           max: 40 # slows when less than 40
         walkSpeedModifier: 0.6
         sprintSpeedModifier: 0.6
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Fresium
           min: 40 # your legs stop working when above 40
         walkSpeedModifier: 0.00
         sprintSpeedModifier: 0.00
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Fresium
           min: 40
         type: Local
       - !type:Polymorph
         prototype: ArtifactLizard # Does the same thing as the original YML I made for this reagent.
         conditions:
-        - !type:OrganType
-          type: Animal
-          shouldHave: false
-        - !type:ReagentThreshold
+        - !type:MetabolizerTypeCondition
+          type: [Animal]
+          inverted: true
+        - !type:ReagentCondition
+          reagent: JuiceThatMakesYouWeh
           min: 50
       - !type:AdjustReagent
         reagent: JuiceThatMakesYouWeh
         amount: -20
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: JuiceThatMakesYouWeh
           min: 50
 
 - type: reagent
       - !type:Polymorph
         prototype: ArtifactLizard
         conditions:
-        - !type:OrganType
-          type: Animal
-          shouldHave: false
-        - !type:ReagentThreshold
+        - !type:MetabolizerTypeCondition
+          type: [ Animal ]
+          inverted: true
+        - !type:ReagentCondition
+          reagent: JuiceThatMakesYouHew
           min: 50
       - !type:AdjustReagent
         reagent: JuiceThatMakesYouHew
         amount: -20
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: JuiceThatMakesYouHew
           min: 50
 
 - type: reagent
       effects:
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: CorgiJuice
           min: 15
         damage:
           types:
       - !type:Polymorph
         prototype: SmartCorgiMorph
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: CorgiJuice
           min: 50
       - !type:AdjustReagent
         reagent: CorgiJuice
         amount: -20
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: CorgiJuice
           min: 50
index 3d65931f19f3cfa5e2a41f722c843a3cb6c53756..6a0b2578776ced762cdbc480e6ef7228d4ed3fd7 100644 (file)
     Poison:
       effects:
       - !type:Oxygenate
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Human
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Animal
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Rat
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [ Human, Animal, Rat, Plant ]
       # Convert Oxygen into CO2.
       - !type:ModifyLungGas
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
+          inverted: true
         ratios:
           CarbonDioxide: 1.0
           Oxygen: -1.0
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Vox
-        scaleByQuantity: true
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
+        scaling: true
         ignoreResistances: true
         damage:
           types:
-            Poison:
-              3
+            Poison: 1.5
       - !type:AdjustAlert
         alertType: Toxins
+        minScale: 1
         conditions:
-          - !type:ReagentThreshold
-            min: 0.5
-          - !type:OrganType
-            type: Vox
+          - !type:MetabolizerTypeCondition
+            type: [ Vox ]
         clear: true
         time: 5
     Gas:
       effects:
       - !type:Oxygenate
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Human
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Animal
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Rat
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [ Human, Animal, Rat, Plant ]
       # Convert Oxygen into CO2.
       - !type:ModifyLungGas
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
+          inverted: true
         ratios:
           CarbonDioxide: 1.0
           Oxygen: -1.0
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Vox
-        scaleByQuantity: true
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
+        scaling: true
         ignoreResistances: true
         damage:
           types:
-            Poison:
-              7
+            Poison: 0.7
       - !type:AdjustAlert
         alertType: Toxins
+        minScale: 1
         conditions:
-          - !type:ReagentThreshold
-            min: 0.5
-          - !type:OrganType
-            type: Vox
+          - !type:MetabolizerTypeCondition
+            type: [ Vox ]
         clear: true
         time: 5
 
       - !type:HealthChange
         damage:
           types:
-            Poison: 3
+            Poison: 1.5
       - !type:AdjustReagent
         reagent: Inaprovaline
         amount: -2.0
     Gas:
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         damage:
           types:
             Poison:
-              1
+              0.1
       # We need a metabolism effect on reagent removal
       - !type:AdjustAlert
         alertType: Toxins
-        conditions:
-          - !type:ReagentThreshold
-            min: 1.5
+        minScale: 3
         clear: True
         time: 5
   reactiveEffects:
     Flammable:
       methods: [ Touch ]
       effects:
-      - !type:FlammableReaction
+      - !type:Flammable
 
 - type: reagent
   id: Tritium
     Flammable:
       methods: [ Touch ]
       effects:
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.8
   metabolisms:
     Poison:
       - !type:HealthChange
         damage:
           types:
-            Radiation: 3
+            Radiation: 1.5
     Gas:
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         damage:
           types:
       # We need a metabolism effect on reagent removal
       - !type:AdjustAlert
         alertType: Toxins
-        conditions:
-          - !type:ReagentThreshold
-            min: 1.5
+        minScale: 3
         clear: True
         time: 5
 
     Poison:
       effects:
       - !type:Oxygenate
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [ Plant ]
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Plant
-          shouldHave: false
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
-        scaleByQuantity: true
+        - !type:MetabolizerTypeCondition
+          type: [ Plant, Vox ]
+          inverted: true
+        scaling: true
         ignoreResistances: true
         damage:
           types:
             Poison:
-              0.8
+              0.4
       - !type:Oxygenate
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Plant
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Plant ]
+          inverted: true
         factor: -4
     Gas:
       effects:
       - !type:Oxygenate
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Plant
+        - !type:MetabolizerTypeCondition
+          type: [ Plant ]
       - !type:HealthChange
+        minScale: 1
         conditions:
-        - !type:OrganType
-          type: Plant
-          shouldHave: false
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Plant, Vox ]
+          inverted: true
         # Don't want people to get toxin damage from the gas they just
         # exhaled, right?
-        - !type:ReagentThreshold
-          min: 0.5
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         damage:
           types:
             Poison:
               0.8
       - !type:Oxygenate # carbon dioxide displaces oxygen from the bloodstream, causing asphyxiation
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Plant
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Plant ]
+          inverted: true
         factor: -4
       # We need a metabolism effect on reagent removal
       #- !type:AdjustAlert
       - !type:HealthChange
         damage:
           types:
-            Cold: 1 # liquid nitrogen is cold
+            Cold: 0.5 # liquid nitrogen is cold
     Gas:
       effects:
       - !type:Oxygenate
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Vox
-      - !type:Oxygenate
-        conditions:
-        - !type:OrganType
-          type: Slime
-      # Converts Nitrogen into CO2
+        - !type:MetabolizerTypeCondition
+          type: [ Vox, Slime ]
+      # Converts Nitrogen into Ammonia
       - !type:ModifyLungGas
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Vox
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
         ratios:
           Ammonia: 1.0
           Nitrogen: -1.0
       - !type:ModifyLungGas
+        scaling: true
         conditions:
-        - !type:OrganType
-          type: Slime
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
         ratios:
           NitrousOxide: 1.0
           Nitrogen: -1.0
       - !type:HealthChange
         damage:
           types:
-            Poison: 2
+            Poison: 1
     Gas:
       effects:
       - !type:Emote
+        minScale: 1
         conditions:
-        - !type:ReagentThreshold
-          reagent: NitrousOxide
-          min: 0.2
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
+          inverted: true
         emote: Laugh
         showInChat: true
         probability: 0.1
       - !type:Emote
+        minScale: 0.4
         conditions:
-        - !type:ReagentThreshold
-          reagent: NitrousOxide
-          min: 0.2
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
+          inverted: true
         emote: Scream
         showInChat: true
         probability: 0.01
       - !type:PopupMessage
+        minScale: 1
         conditions:
-        - !type:ReagentThreshold
-          reagent: NitrousOxide
-          min: 0.5
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
+          inverted: true
         type: Local
         visualType: Medium
         messages: [ "effect-sleepy" ]
         probability: 0.1
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
+        minScale: 2
         conditions:
-        - !type:ReagentThreshold
-          reagent: NitrousOxide
-          min: 1
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
+          inverted: true
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
       - !type:ModifyStatusEffect
+        minScale: 3.6
         conditions:
-        - !type:ReagentThreshold
-          reagent: NitrousOxide
-          min: 1.8
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
+          inverted: true
         effectProto: StatusEffectForcedSleeping
         time: 3
         type: Update
       - !type:HealthChange
+        minScale: 7
         conditions:
-        - !type:ReagentThreshold
-          reagent: NitrousOxide
-          min: 3.5
-        - !type:OrganType
-          type: Slime
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Slime ]
+          inverted: true
         ignoreResistances: true
         damage:
           types:
     Narcotic:
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         damage:
           types:
-            Cellular: 1
+            Cellular: 0.5
       - !type:ModifyStatusEffect
         effectProto: StatusEffectSeeingRainbow
         type: Add
         type: Local
         messages: [ "frezon-lungs-cold" ]
         probability: 0.1
-        conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 0.5
+        minScale: 1
       - !type:PopupMessage
         type: Local
         visualType: Medium
         messages: [ "frezon-euphoric" ]
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 1
+        minScale: 2
     Gas:
       effects:
       - !type:HealthChange
-        conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 0.5
-        scaleByQuantity: true
+        minScale: 1
+        scaling: true
         ignoreResistances: true
         damage:
           types:
-            Cellular: 0.5
+            Cellular: 0.05
       - !type:ModifyStatusEffect
-        conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 1
+        minScale: 2
         effectProto: StatusEffectSeeingRainbow
         type: Add
         time: 500
       - !type:Drunk
         boozePower: 500
-        conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 1
+        minScale: 2
       - !type:PopupMessage
         type: Local
         messages: [ "frezon-lungs-cold" ]
         probability: 0.1
-        conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 0.5
+        minScale: 2
       - !type:PopupMessage
         type: Local
         visualType: Medium
         messages: [ "frezon-euphoric" ]
         probability: 0.1
-        conditions:
-        - !type:ReagentThreshold
-          reagent: Frezon
-          min: 1
+        minScale: 2
index 71ae11dad9a5e230bdddcfb361e3a2264e17bee4..d81ec12b3da37135ce5d723276211b46b03912d6 100644 (file)
             Poison: -1
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dylovene
           min: 20
         damage:
           groups:
             Brute: 2
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dylovene
           min: 20
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dylovene
           min: 20
         type: Local
         visualType: Medium
         messages: [ "generic-reagent-effect-nauseous" ]
         probability: 0.2
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dylovene
           min: 20
         probability: 0.02
       - !type:Drunk
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dylovene
           min: 15
   plantMetabolism:
   - !type:PlantAdjustToxins
           Brute: -1.5
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Bicaridine
           min: 15
         damage:
           types:
             Asphyxiation: 0.5
             Poison: 1.5
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Bicaridine
           min: 30
         probability: 0.02
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Bicaridine
           min: 15
       - !type:Drunk
 
       effects:
         - !type:HealthChange
           conditions:
-          - !type:Temperature
+          - !type:TemperatureCondition
             # this is a little arbitrary but they gotta be pretty cold
             max: 213.0
           damage:
       effects:
         - !type:HealthChange
           conditions:
-          - !type:Temperature
+          - !type:TemperatureCondition
             max: 213.0
           damage:
             types:
             Cold: -1.5
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dermaline
           min: 10
         damage:
           types:
             Brute: 0.5
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Dermaline
           min: 10
 
 - type: reagent
             Bloodloss: -0.5
       - !type:HealthChange
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: Dexalin
             min: 20
         damage:
           types:
             Bloodloss: -3
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: HeartbreakerToxin
           min: 1
         reagent: HeartbreakerToxin
         amount: -3
       - !type:HealthChange
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: DexalinPlus
             min: 25
         damage:
           types:
       effects:
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Histamine
           min: 45
         reagent: Histamine
           # they gotta be in crit first
         - !type:MobStateCondition
           mobstate: Critical
-        - !type:ReagentThreshold
-          min: 0
+        - !type:ReagentCondition
+          reagent: Epinephrine
           max: 20
         damage:
           types:
             Burn: -0.5
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Epinephrine
           min: 20
         damage:
           types:
         amount: -2
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: HeartbreakerToxin
           min: 1
         reagent: Epinephrine
       - !type:AdjustReagent
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: HeartbreakerToxin
           min: 1
         reagent: Histamine
         damage:
           types:
             Radiation: -1
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.02
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Hyronalin
           min: 30
         damage:
           types:
            Heat: 2
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Hyronalin
           min: 30
 
 - type: reagent
   metabolisms:
     Medicine:
       effects:
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ipecac
           min: 4
         probability: 0.3
 
         damage:
           types:
             Asphyxiation: -2
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -0.25
 
 - type: reagent
       - !type:SatiateThirst
         factor: -10
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Kelotane
           min: 30
       - !type:PopupMessage
         type: Local
         - generic-reagent-effect-parched
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Kelotane
           min: 25
 
 - type: reagent
             Cold: -4
       - !type:AdjustTemperature
         conditions:
-        - !type:Temperature
+        - !type:TemperatureCondition
           max: 293.15
         amount: 100000 # thermal energy, not temperature!
       - !type:AdjustTemperature
         conditions:
-        - !type:Temperature
+        - !type:TemperatureCondition
           min: 293.15
         amount: -10000
       - !type:PopupMessage
             Heat: 0.5
       - !type:HealthChange
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: Barozine
             min: 30
         damage:
           types:
             Poison: 3
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.15
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: Barozine
             min: 30
       - !type:GenericStatusEffect
         key: PressureImmunity
       amount: 6
     - !type:PlantPhalanximine
       conditions:
-      - !type:ReagentThreshold
+      - !type:ReagentCondition
+        reagent: Phalanximine
         min: 4
   metabolisms:
     Medicine:
             Caustic: 0.15
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Phalanximine
           min: 11
         damage:
           types:
             Radiation: 0.2
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Arithrazine
           min: 1
         damage:
             Asphyxiation: -2.5
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: PolypyryliumOligomers
           min: 30
         damage:
           types:
             Asphyxiation: 3.5
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -0.25
 
 - type: reagent
       effects:
         - !type:CureZombieInfection
           conditions:
-            - !type:ReagentThreshold
+            - !type:ReagentCondition
+              reagent: Ambuzol
               min: 10
 
 - type: reagent
         - !type:CureZombieInfection
           innoculate: true
           conditions:
-            - !type:ReagentThreshold
+            - !type:ReagentCondition
+              reagent: AmbuzolPlus
               min: 5
 
 - type: reagent
   metabolisms:
     Medicine:
       effects:
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -0.5
 
 - type: reagent
             Poison: -4
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Amatoxin
           min: 1
         reagent: Amatoxin
       effects:
       # Medium-large quantities can hurt you instead,
       # but still technically stop your bleeding.
-      - !type:ModifyBleedAmount
+      - !type:ModifyBleed
         amount: -1.5
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: TranexamicAcid
           min: 15
         damage:
           types:
       effects:
       - !type:HealthChange
         conditions:
-        - !type:TotalDamage
+        - !type:TotalDamageCondition
           max: 50
         damage:
           groups:
       effects:
       - !type:EvenHealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ultravasculine
           min: 0
           max: 20
         damage:
           Toxin: -6
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ultravasculine
           min: 0
           max: 20
         damage:
             Brute: 1.5
       - !type:EvenHealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ultravasculine
           min: 20
         damage:
           Toxin: -2
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ultravasculine
           min: 20
         damage:
           groups:
             Brute: 6
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Histamine
           min: 1
         reagent: Histamine
         amount: -1
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Histamine
           min: 1
         reagent: Ultravasculine
   metabolisms:
     Medicine:
       effects:
-      - !type:ChemHealEyeDamage
+      - !type:EyeDamage
 
 - type: reagent
   id: Cognizine
       effects:
       - !type:MakeSentient
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Cognizine
           min: 5
 
 - type: reagent
         type: Remove
       - !type:ResetNarcolepsy
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ethyloxyephedrine
           min: 10
       - !type:PopupMessage
         visualType: Medium
         type: Remove
       - !type:ResetNarcolepsy
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Diphenylmethylamine
           min: 5
       - !type:PopupMessage
         visualType: Medium
             Caustic: -1.25 # 5 per u
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Sigynate
           min: 16
         damage:
           types:
             Heat: 1 # 4 per u
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Sigynate
           min: 20
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Sigynate
           min: 20
         type: Local
         visualType: Medium
         messages: [ "generic-reagent-effect-nauseous" ]
         probability: 0.2
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Sigynate
           min: 30
         probability: 0.02
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Arithrazine
           min: 1
         probability: 0.1
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Arithrazine
           min: 1
         type: Local
             Heat: 0.2 # 0.8 per u
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Lacerinol
           min: 12
         damage:
           types:
             Radiation: 0.05 # 0.2 per u, 3 for 15u
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Puncturase
           min: 12
         damage:
           types:
         factor: -1
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Bruizine
           min: 10.5
         damage:
           types:
         factor: 3
       - !type:HealthChange
         conditions:
-        - !type:TotalDamage
+        - !type:TotalDamageCondition
           max: 50
         damage:
           types:
             Heat: -0.2
             Shock: -0.2
             Cold: -0.2
-
   reactiveEffects:
     Extinguish:
       methods: [ Touch ]
       effects:
-      - !type:ExtinguishReaction
+      - !type:Extinguish
     Acidic:
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: false
         damage:
           types:
       # od causes massive bleeding
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Pyrazine
           min: 20
         damage:
           types:
             Slash: 0.5
             Piercing: 0.5
-      - !type:ChemVomit
+      - !type:Vomit
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Pyrazine
           min: 15
         probability: 0.1
       - !type:Emote
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Pyrazine
           min: 20
         emote: Scream
         probability: 0.2
       # od makes you freeze to death
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Insuzine
           min: 12
         damage:
           types:
             Cold: 1 # 4 per u
       - !type:AdjustTemperature
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Insuzine
           min: 12
         amount: -30000
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Insuzine
           min: 12
 
 - type: reagent
           seconds: 20
           conditions:
           #Patient must be dead and in a cryo tube (or something cold)
-          - !type:Temperature
+          - !type:TemperatureCondition
             max: 150.0
           - !type:MobStateCondition
             mobstate: Dead
       effects:
         - !type:HealthChange
           conditions:
-          - !type:Temperature
+          - !type:TemperatureCondition
             max: 213.0
           damage:
             groups:
       effects:
       - !type:HealthChange
         conditions:
-        - !type:Temperature
+        - !type:TemperatureCondition
           max: 213.0
         damage:
           types:
       effects:
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Mannitol
           min: 15
         type: Local
         visualType: Medium
       effects:
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Psicodine
           min: 30
         damage:
           types:
       - !type:ModifyStatusEffect
         effectProto: StatusEffectSeeingRainbow
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Psicodine
           min: 30
         type: Add
         time: 8
         component: RadiationProtection
         time: 2
         type: Add
-        refresh: false
       - !type:HealthChange
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: PotassiumIodide
             min: 20
         damage:
           types:
index 92236d746ca711fd319be61e08011521483e93b2..48fc61885710cfca08a9f883cf21e9878b669f40 100644 (file)
@@ -18,7 +18,8 @@
             Poison: 0.75
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Desoxyephedrine
           min: 30
         damage:
           types:
@@ -26,7 +27,7 @@
             Asphyxiation: 2
     Narcotic:
       effects:
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 1.35
         sprintSpeedModifier: 1.35
       - !type:GenericStatusEffect
@@ -42,7 +43,7 @@
         type: Remove
       - !type:ModifyStatusEffect
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Haloperidol
           max: 0.01
         effectProto: StatusEffectDrowsiness
@@ -52,7 +53,8 @@
       effects:
       - !type:ResetNarcolepsy
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Desoxyephedrine
           min: 20
 
 - type: reagent
   metabolisms:
     Narcotic:
       effects:
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 1.25
         sprintSpeedModifier: 1.25
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ephedrine
           min: 20
         damage:
           types:
@@ -90,7 +93,7 @@
         type: Remove
       - !type:ModifyStatusEffect
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Haloperidol
           max: 0.01
         effectProto: StatusEffectDrowsiness
       effects:
       - !type:ResetNarcolepsy
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Ephedrine
           min: 30
 
 - type: reagent
     Narcotic:
       metabolismRate: 1.0
       effects:
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 1.3
         sprintSpeedModifier: 1.3
       - !type:HealthChange
         conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: Stimulants
             min: 80 #please wait 3 minutes before using another stimpack
         damage:
           types:
             Poison: 1
       - !type:AdjustReagent
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: ChloralHydrate
           min: 1
         reagent: ChloralHydrate
         key: StaminaModifier
         component: StaminaModifier
         time: 3
-        type: Add
       - !type:ModifyStatusEffect
         effectProto: StatusEffectForcedSleeping
         time: 3
         type: Remove
       - !type:ModifyStatusEffect
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Haloperidol
           max: 0.01
         effectProto: StatusEffectDrowsiness
           factor: 1
         - !type:HealthChange
           conditions:
-          - !type:TotalDamage
+          - !type:TotalDamageCondition
             min: 70 # only heals when you're more dead than alive
           damage: # heals at the same rate as tricordrazine, doesn't heal poison because if you OD'd I'm not giving you a safety net
             groups:
   metabolisms:
     Narcotic:
       effects:
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
       - !type:HealthChange
         effectProto: StatusEffectSeeingRainbow
         time: 10
         type: Add
-      - !type:ChemVomit # Vomiting is a symptom of brain damage
+      - !type:Vomit # Vomiting is a symptom of brain damage
         probability: 0.05
       - !type:Drunk # Headaches and slurring are major symptoms of brain damage, this is close enough
         boozePower: 5
   metabolisms:
     Narcotic:
       effects: # It would be nice to have speech slurred or mumbly, but accents are a bit iffy atm. Same for distortion effects.
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
       - !type:ModifyStatusEffect
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Nocturine
           min: 8
         effectProto: StatusEffectForcedSleeping
         time: 6
         delay: 5
-        type: Update
 
 - type: reagent
   id: MuteToxin
         - norepinephricacid-effect-darkness
         - norepinephricacid-effect-blindness
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: NorepinephricAcid
           min: 20
         probability: 0.03
         #If anyone wants to add a light dimming or grayscale effect when under 20u, be my guest
         key: TemporaryBlindness
         component: TemporaryBlindness
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: NorepinephricAcid
           min: 20
 
 - type: reagent
         emote: Scream
         probability: 0.08
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: TearGas
           min: 4
       - !type:Emote
         emote: Cough
         key: TemporaryBlindness
         component: TemporaryBlindness
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: TearGas
           min: 4
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
-        statusLifetime: 1.5
+        time: 1.5
         conditions: # because of the remainding after effect, threshold is given so the effects ends simultaniously
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: TearGas
           min: 4
 
 - type: reagent
         showInChat: true
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Happiness
           max: 20
       - !type:Emote
         emote: Whistle
         showInChat: true
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Happiness
           max: 20
       - !type:Emote
         emote: Crying
         showInChat: true
         probability: 0.1
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Happiness
           min: 20
       - !type:PopupMessage # we dont have sanity/mood so this will have to do
         type: Local
         - "psicodine-effect-at-peace"
         probability: 0.2
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Happiness
           max: 20
       - !type:ModifyStatusEffect
         effectProto: StatusEffectSeeingRainbow
index aa5ebe5145e3c782d7670b5b8cc541319ddf63c5..16dfc692862086aedde7ffdf1ca2ba2cb5513427 100644 (file)
@@ -6,7 +6,7 @@
     Flammable:
       methods: [ Touch ]
       effects:
-        - !type:FlammableReaction
+        - !type:Flammable
   plantMetabolism:
     - !type:PlantAdjustWeeds
       amount: -2
@@ -52,7 +52,7 @@
     Flammable:
       methods: [ Touch ]
       effects:
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.4
   metabolisms:
     Poison:
@@ -63,7 +63,7 @@
             Heat: 2
             Poison: 1
             Caustic: 0.5 # based off napalm being an irritant
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.4
 
 - type: reagent
@@ -83,7 +83,7 @@
           types:
             Heat: 3
             Poison: 1
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.1
       - !type:AdjustTemperature
         amount: 6000
@@ -92,7 +92,7 @@
     Flammable:
       methods: [ Touch ]
       effects:
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.2
       - !type:Ignite
 
             Heat: 2
             Poison: 1
             Caustic: 0.5 # CLF3 is corrosive
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.2
       - !type:AdjustTemperature
         amount: 6000
     Flammable:
       methods: [ Touch ]
       effects:
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.3
       - !type:Ignite
       - !type:Emote
       - !type:SatiateThirst
         factor: 1
         conditions:
-        - !type:OrganType
-          type: Vox
+        - !type:MetabolizerTypeCondition
+          type: [Vox]
     Poison:
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Vox]
+          inverted: true
         damage:
           types:
             Poison: 1
-      - !type:FlammableReaction
+      - !type:Flammable
         multiplier: 0.4
 
 - type: reagent
index 33a94c079271ee1eccfd3c5d97fb5671cfbe99ee..ea7437aa7b57ffd5fe26d7f9267475bfd24b12db 100644 (file)
         emote: Yawn
         showInChat: true
         probability: 0.1
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 0.65
         sprintSpeedModifier: 0.65
       - !type:ModifyStatusEffect
         effectProto: StatusEffectDrowsiness
         time: 4
-        type: Add
+        type: Update
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: ChloralHydrate
           min: 20
         damage:
           damage:
             types:
               Poison: 2
-        - !type:ChemVomit
+        - !type:Vomit
           conditions:
-          - !type:ReagentThreshold
+          - !type:ReagentCondition
+            reagent: GastroToxin
             min: 2
           probability: 0.2
 
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: false
         damage:
           types:
       - !type:SatiateThirst
         factor: -1.5
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: true
         damage:
           types:
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: false
         damage:
           types:
       methods: [ Touch ]
       effects:
       - !type:HealthChange
-        scaleByQuantity: true
+        scaling: true
         ignoreResistances: false
         damage:
           types:
       # todo: cough, sneeze
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Histamine
           min: 45
         damage:
           groups:
         probability: 0.1
       - !type:PopupMessage
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Histamine
           min: 45
         type: Local
         visualType: Medium
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Animal # Applying damage to the mobs with lower metabolism capabilities
+        - !type:MetabolizerTypeCondition
+          type: [Animal] # Applying damage to the mobs with lower metabolism capabilities
         damage:
           types:
             Poison: 0.4
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.04 #Scaled for time, not metabolismrate.
         conditions:
-          - !type:OrganType
-            type: Animal
+          - !type:MetabolizerTypeCondition
+            type: [Animal]
 
 - type: reagent
   id: Amatoxin
       effects:
         - !type:CauseZombieInfection
           conditions:
-            - !type:ReagentThreshold
+            - !type:ReagentCondition
+              reagent: Romerol
               min: 5
 
 - type: reagent
       effects:
       - !type:PopupMessage
         conditions:
-          - !type:OrganType
-            type: Animal
-            shouldHave: false
-          - !type:OrganType
-            type: Vox
-            shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Animal, Vox ]
+          inverted: true
         type: Local
         visualType: MediumCaution
         messages: [ "generic-reagent-effect-sick" ]
         probability: 0.5
-      - !type:ChemVomit
+      - !type:Vomit
         probability: 0.1
         conditions:
-          - !type:OrganType
-            type: Animal
-            shouldHave: false
-          - !type:OrganType
-            type: Vox
-            shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Animal, Vox ]
+          inverted: true
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Animal
-          shouldHave: false
-        - !type:OrganType
-          type: Vox
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [ Animal, Vox ]
+          inverted: true
         damage:
           types:
             Poison: 1
       - !type:AdjustReagent
         conditions:
-        - !type:OrganType
-          type: Animal
+        - !type:MetabolizerTypeCondition
+          type: [ Animal ]
         reagent: Protein
         amount: 0.5
       - !type:AdjustReagent
         conditions:
-        - !type:OrganType
-          type: Vox
+        - !type:MetabolizerTypeCondition
+          type: [ Vox ]
         reagent: Protein
         amount: 0.25
 
       effects:
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Allicin
           min: 1
-        - !type:OrganType
-          type: Animal
+        - !type:MetabolizerTypeCondition
+          type: [Animal]
         damage:
           types:
             Poison: 0.06
       - !type:GenericStatusEffect
         key: Pacified
         component: Pacified
-        type: Add
         time: 4
 
 - type: reagent
         probability: 0.2
       - !type:HealthChange
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Honk
           min: 1
-        - !type:OrganType
-          type: Animal
+        - !type:MetabolizerTypeCondition
+          type: [ Animal ]
         damage:
           types:
             Poison: 0.06
       effects:
       - !type:Jitter
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
+          reagent: Vestine
           min: 5
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         walkSpeedModifier: 0.8
         sprintSpeedModifier: 0.8
       - !type:HealthChange
       effects:
       - !type:HealthChange
         conditions:
-        - !type:Hunger
+        - !type:HungerCondition
           max: 50
         damage:
           types:
       effects:
       - !type:HealthChange
         conditions:
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Arachnid]
+          inverted: true
         damage:
           types:
             Poison: 1.6
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Mechanotoxin
           min: 2
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Arachnid]
+          inverted: true
         walkSpeedModifier: 0.8
         sprintSpeedModifier: 0.8
-      - !type:MovespeedModifier
+      - !type:MovementSpeedModifier
         conditions:
-        - !type:ReagentThreshold
+        - !type:ReagentCondition
           reagent: Mechanotoxin
           min: 4
-        - !type:OrganType
-          type: Arachnid
-          shouldHave: false
+        - !type:MetabolizerTypeCondition
+          type: [Arachnid]
+          inverted: true
         walkSpeedModifier: 0.4
         sprintSpeedModifier: 0.4
 
       - !type:SatiateHunger
         factor: 1
         conditions:
-        - !type:OrganType
-          type: Vox
+        - !type:MetabolizerTypeCondition
+          type: [Vox]
index fef96df5cfcd7972fd79b35b2ec391ba753cadbf..b2a09a1b7ff62cf5e54f5553273f778c0e1b7aa2 100644 (file)
     Potassium:
       amount: 1
   effects:
-    - !type:ExplosionReactionEffect
+    - !type:ExplosionEffect
       explosionType: Default
       intensityPerUnit: 0.25
       maxTotalIntensity: 100
     Aluminium:
       amount: 1
   effects:
-    - !type:EmpReactionEffect
-      rangePerUnit: 0.2
+    - !type:Emp
+      rangeModifier: 0.2
       maxRange: 6
       energyConsumption: 12500
       duration: 15
     Sulfur:
       amount: 1
   effects:
-    - !type:FlashReactionEffect
+    - !type:Flash
 
 - type: reaction
   id: TableSalt
index e98d49c521c8a2f0c32d786ca5ba576e030b8c19..f9b819e43e7b6404a74dce42a34328bdcb2a48a3 100644 (file)
@@ -10,7 +10,7 @@
       amount: 5
       catalyst: true
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodCheese
 
 - type: reaction
@@ -25,7 +25,7 @@
       amount: 5
       catalyst: true
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodChevre
 
 - type: reaction
@@ -39,7 +39,7 @@
     Water:
       amount: 10
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodDough
 
 - type: reaction
@@ -55,7 +55,7 @@
     Egg:
       amount: 6
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodDoughCornmeal
 
 - type: reaction
@@ -69,7 +69,7 @@
     Water:
       amount: 10
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodDoughTortilla
 
 - type: reaction
@@ -85,7 +85,7 @@
     Water:
       amount: 10
   effects:
-  - !type:CreateEntityReactionEffect
+  - !type:SpawnEntity
     entity: FoodDoughCotton
 
 - type: reaction
     Milk:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodCakeBatter
 
 - type: reaction
       amount: 5
       catalyst: true
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodButter
 
 - type: reaction
     TableSalt:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodDoughPie
 
 # TG has a cake recipe that uses soy milk instead of eggs.
     Sugar:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodCakeBatter
 
 - type: reaction
       amount: 5
       catalyst: true
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodTofu
 
 - type: reaction
     Egg:
       amount: 6
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodMeatMeatball
 
 - type: reaction
     Sugar:
       amount: 2
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodSnackChocolateBar
 
 # Condiments
index 35987b6be5c30752b48d7e6a87f19a881ee75e87..9b335a2acaaa3319092bd05e67246d2b7eb37f8a 100644 (file)
@@ -38,7 +38,7 @@
     Carbon:
       amount: 10
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: FoodMeat
 
 - type: reaction
@@ -75,7 +75,7 @@
     SulfuricAcid:
       amount: 2
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: SheetPlastic1
 
 - type: reaction
     Ethanol:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: ShardCrystalRandom
 
 - type: reaction
     Charcoal:
       amount: 2
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: MaterialGunpowder
 
 - type: reaction
     FrostOil:
       amount: 5
   effects:
-  - !type:CreateEntityReactionEffect
+  - !type:SpawnEntity
     entity: IngotGold1
 
 - type: reaction
     FrostOil:
       amount: 5
   effects:
-  - !type:CreateEntityReactionEffect
+  - !type:SpawnEntity
     entity: IngotSilver1
 
 - type: reaction
     JuiceThatMakesYouHew:
       amount: 1
   effects:
-  - !type:ExplosionReactionEffect
+  - !type:ExplosionEffect
     explosionType: Radioactive
     maxIntensity: 200
     intensityPerUnit: 2
index 3591ce700869b9c3735c9313b7496e5bb5dc2596..21bea1ff2b8540564fbfcb2dda26c4540d6fb14c 100644 (file)
@@ -39,7 +39,7 @@
     Fluorine:
       amount: 3
   effects:
-  - !type:ExplosionReactionEffect
+  - !type:ExplosionEffect
     explosionType: Default # 15 damage per intensity.
     maxIntensity: 200
     intensityPerUnit: 5
@@ -72,4 +72,4 @@
   products:
     Ethanol: 5
     Hydrogen: 3
-    Sulfur: 2
\ No newline at end of file
+    Sulfur: 2
index d07c582f8b2bc4ae4e5ebd8095dc1fbae6f96098..6fddf1e479ac2a063f36a03ef9ed83b9a8e9ac53 100644 (file)
@@ -11,7 +11,7 @@
     TableSalt:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: Soap
 
 - type: reaction
@@ -29,7 +29,7 @@
     Plasma:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: SoapNT
 
 - type: reaction
@@ -47,7 +47,7 @@
     JuiceBerry:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: SoapDeluxe
 
 - type: reaction
@@ -65,7 +65,7 @@
     Blood:
       amount: 10
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: SoapHomemade
 
 - type: reaction
@@ -83,7 +83,7 @@
     Stimulants:
       amount: 5
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: SoapSyndie
 
 - type: reaction
     Amatoxin:
       amount: 1
   effects:
-    - !type:CreateEntityReactionEffect
+    - !type:SpawnEntity
       entity: SoapOmega