From 2c40a950f788058d8665de9fc68454d4388d33e2 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:20:37 +0200 Subject: [PATCH] Trigger Refactor (#39034) --- .../Explosion/SmokeOnTriggerSystem.cs | 7 - .../Explosion/TriggerOnProximityComponent.cs | 7 - Content.Client/Explosion/TriggerSystem.cs | 10 - Content.Client/HotPotato/HotPotatoSystem.cs | 6 +- .../TimerTriggerVisualizerComponent.cs | 12 +- .../ProximityTriggerAnimationSystem.cs} | 13 +- .../Systems/ReleaseGasOnTriggerSystem.cs | 5 + .../TimerTriggerVisualizerSystem.cs | 20 +- .../Tests/Payload/ModularGrenadeTests.cs | 17 +- .../AlertLevelChangeOnTriggerComponent.cs | 33 -- .../Animals/Systems/ParrotMemorySystem.cs | 4 +- .../Chat/SpeakOnTriggerComponent.cs | 17 - Content.Server/Chat/SuicideSystem.cs | 3 + Content.Server/Chat/Systems/ChatSystem.cs | 5 - .../Systems/DamageUserOnTriggerSystem.cs | 50 -- .../Defusable/Systems/DefusableSystem.cs | 35 +- .../Destructible/DestructibleSystem.cs | 7 +- .../Behaviors/TimerStartBehavior.cs | 2 +- .../Thresholds/Behaviors/TriggerBehavior.cs | 12 +- .../DeviceLinking/Systems/DeviceLinkSystem.cs | 2 + .../DeviceLinking/Systems/MemoryCellSystem.cs | 1 - .../DeviceLinking/Systems/SignallerSystem.cs | 15 - .../Electrocution/ElectrocutionSystem.cs | 6 +- Content.Server/Emp/EmpOnTriggerComponent.cs | 24 - Content.Server/Emp/EmpSystem.cs | 17 +- .../ActiveTriggerOnTimedCollideComponent.cs | 4 - .../Components/AutomatedTimerComponent.cs | 9 - .../OnTrigger/AnchorOnTriggerComponent.cs | 13 - .../OnTrigger/DeleteOnTriggerComponent.cs | 11 - .../OnTrigger/GibOnTriggerComponent.cs | 16 - .../OnTrigger/SoundOnTriggerComponent.cs | 17 - .../OnTrigger/TwoStageTriggerComponent.cs | 34 -- .../Components/ProjectileGrenadeComponent.cs | 6 + .../Components/ShockOnTriggerComponent.cs | 37 -- .../Components/SpawnOnTriggerComponent.cs | 24 - .../Components/TimerStartOnSignalComponent.cs | 15 - .../Components/TriggerOnActivateComponent.cs | 7 - .../Components/TriggerOnCollideComponent.cs | 20 - .../TriggerOnMobstateChangeComponent.cs | 24 - .../Components/TriggerOnProximityComponent.cs | 93 ---- .../Components/TriggerOnSignalComponent.cs | 15 - .../Components/TriggerOnSlipComponent.cs | 6 - .../Components/TriggerOnSpawnComponent.cs | 9 - .../TriggerOnStepTriggerComponent.cs | 10 - .../TriggerOnTimedCollideComponent.cs | 18 - .../Components/TriggerOnUseComponent.cs | 7 - .../Components/TriggerOnVoiceComponent.cs | 28 -- .../Components/TriggerWhenEmptyComponent.cs | 9 - .../Components/TriggerWhitelistComponent.cs | 23 - .../EntitySystems/ProjectileGrenadeSystem.cs | 4 + .../ReleaseGasOnTriggerSystem.cs | 79 --- .../RepulseAttractOnTriggerSystem.cs | 29 -- .../EntitySystems/ScatteringGrenadeSystem.cs | 16 +- .../EntitySystems/SmokeOnTriggerSystem.cs | 57 --- .../EntitySystems/TriggerSystem.OnUse.cs | 170 ------- .../EntitySystems/TriggerSystem.Proximity.cs | 154 ------ .../EntitySystems/TriggerSystem.Signal.cs | 43 -- .../TriggerSystem.TimedCollide.cs | 59 --- .../EntitySystems/TriggerSystem.Voice.cs | 154 ------ .../Explosion/EntitySystems/TriggerSystem.cs | 454 ------------------ .../EntitySystems/TwoStageTriggerSystem.cs | 64 --- .../GhostKickUserOnTriggerComponent.cs | 7 - .../GhostKick/GhostKickUserOnTriggerSystem.cs | 26 - Content.Server/Holopad/HolopadSystem.cs | 4 +- Content.Server/HotPotato/HotPotatoSystem.cs | 58 +-- .../IgniteOnTriggerComponent.cs | 30 -- Content.Server/LandMines/LandMineSystem.cs | 23 +- Content.Server/Mousetrap/MousetrapSystem.cs | 79 --- .../Ninja/Systems/SpiderChargeSystem.cs | 5 +- .../Nutrition/EntitySystems/CreamPieSystem.cs | 14 +- .../Payload/EntitySystems/PayloadSystem.cs | 11 +- .../Components/PolymorphOnTriggerComponent.cs | 18 - .../Systems/PolymorphSystem.Trigger.cs | 41 -- .../Polymorph/Systems/PolymorphSystem.cs | 3 - .../Radio/EntitySystems/RadioDeviceSystem.cs | 5 +- .../Silicons/Borgs/BorgSystem.Transponder.cs | 2 +- Content.Server/Silicons/Borgs/BorgSystem.cs | 2 +- .../Components/EmitSoundOnTriggerComponent.cs | 13 - Content.Server/Sound/EmitSoundSystem.cs | 10 - .../Components/ActiveListenerComponent.cs | 13 - .../EntitySystems/BlockListeningSystem.cs | 1 + .../Speech/EntitySystems/ListeningSystem.cs | 3 +- .../SurveillanceCameraMicrophoneSystem.cs | 4 +- Content.Server/Telephone/TelephoneSystem.cs | 3 +- .../AlertLevelChangeOnTriggerSystem.cs | 11 +- .../Systems/GhostKickUserOnTriggerSystem.cs | 38 ++ .../Systems}/IgniteOnTriggerSystem.cs | 25 +- .../Systems/PolymorphOnTriggerSystem.cs | 51 ++ .../Trigger/Systems/RattleOnTriggerSystem.cs | 49 ++ .../Systems/ReleaseGasOnTriggerSystem.cs | 48 ++ .../Trigger/Systems/SmokeOnTriggerSystem.cs | 68 +++ .../Systems/SpeakOnTriggerSystem.cs | 38 +- .../VoiceTrigger/StorageVoiceControlSystem.cs | 1 + .../XAETriggerExplosivesComponent.cs | 4 +- Content.Shared/Chat/SharedChatSystem.cs | 5 + .../DamageUserOnTriggerComponent.cs | 10 - Content.Shared/Emp/SharedEmpSystem.cs | 12 + .../Components/ActiveTimerTriggerComponent.cs | 21 - .../OnTrigger/ExplodeOnTriggerComponent.cs | 11 - .../Components/OnUseTimerTriggerComponent.cs | 66 --- .../Components/ScatteringGrenadeComponent.cs | 6 + .../SharedTriggerOnProximityComponent.cs | 9 - .../SharedReleaseGasOnTriggerSystem.cs | 5 - .../SharedRepulseAttractOnTriggerSystem.cs | 3 - .../SharedSmokeOnTriggerSystem.cs | 5 - .../EntitySystems/SharedTriggerSystem.cs | 6 - .../Components/FlashOnTriggerComponent.cs | 18 - .../HotPotato/ActiveHotPotatoComponent.cs | 2 +- .../HotPotato/HotPotatoComponent.cs | 3 +- .../HotPotato/SharedHotPotatoSystem.cs | 65 ++- .../Implants/Components/RattleComponent.cs | 21 - .../TriggerImplantActionComponent.cs | 12 - .../Implants/SharedSubdermalImplantSystem.cs | 7 +- .../Components/ItemToggleComponent.cs | 20 +- .../Item/ItemToggle/ItemToggleSystem.cs | 42 +- .../Mousetrap/MousetrapComponent.cs | 22 +- Content.Shared/Mousetrap/MousetrapSystem.cs | 42 ++ Content.Shared/Mousetrap/MousetrapVisuals.cs | 11 - .../Ninja/Components/SpiderChargeComponent.cs | 14 +- .../Components/PayloadTriggerComponent.cs | 9 +- .../Rootable/SharedRootableSystem.cs | 6 +- .../Components/ActiveListenerComponent.cs | 17 + .../Speech/ListenEvent.cs | 2 +- .../Sticky/Components/StickyComponent.cs | 2 +- Content.Shared/Timing/UseDelaySystem.cs | 2 +- .../Components/ActiveTimerTriggerComponent.cs | 10 + .../ActiveTwoStageTriggerComponent.cs | 10 + .../BaseTriggerConditionComponent.cs | 15 + .../ToggleTriggerConditionComponent.cs | 34 ++ .../UseDelayTriggerConditionComponent.cs | 20 + .../WhitelistTriggerConditionComponent.cs | 24 + .../AddComponentsOnTriggerComponent.cs | 37 ++ .../AlertLevelChangeOnTriggerComponent.cs | 34 ++ .../Effects/AnchorOnTriggerComponent.cs | 34 ++ .../Effects/BaseXOnTriggerComponent.cs | 22 + .../Effects/DamageOnTriggerComponent.cs | 25 + .../Effects/DeleteOnTriggerComponent.cs | 10 + .../Effects/EmitSoundOnTriggerComponent.cs | 31 ++ .../Effects/EmpOnTriggerComponent.cs | 29 ++ .../Effects/ExplodeOnTriggerComponent.cs | 14 + .../Effects/FlashOnTriggerComponent.cs | 29 ++ .../Effects/GhostKickOnTriggerComponent.cs | 19 + .../Effects/GibOnTriggerComponent.cs | 17 + .../Effects/IgniteOnTriggerComponent.cs | 27 ++ .../Effects/ItemToggleOnTriggerComponent.cs | 37 ++ .../Effects/PolymorphOnTriggerComponent.cs | 19 + .../Effects/RattleOnTriggerComponent.cs | 30 ++ .../Effects}/ReleaseGasOnTriggerComponent.cs | 8 +- .../RepulseAttractOnTriggerComponent.cs} | 24 +- .../Effects/ShockOnTriggerComponent.cs | 34 ++ .../Effects/SignalOnTriggerComponent.cs | 18 + .../Effects}/SmokeOnTriggerComponent.cs | 22 +- .../Effects/SpawnOnTriggerComponent.cs | 31 ++ .../Effects/SpeakOnTriggerComponent.cs | 26 + .../Effects/UseDelayOnTriggerComponent.cs | 26 + .../Components/RandomTimerTriggerComponent.cs | 12 +- .../Components/TimerTriggerComponent.cs | 109 +++++ .../ActiveTriggerOnTimedCollideComponent.cs | 6 + .../Triggers/BaseTriggerOnXComponent.cs | 16 + .../Triggers}/RepeatingTriggerComponent.cs | 17 +- .../Triggers/TriggerOnActivateComponent.cs | 17 + .../TriggerOnActivateImplantComponent.cs | 10 + .../Triggers/TriggerOnCollideComponent.cs | 23 + .../TriggerOnEmptyGunshotComponent.cs | 10 + .../TriggerOnMobstateChangeComponent.cs | 35 ++ .../Triggers/TriggerOnProximityComponent.cs | 91 ++++ .../Triggers/TriggerOnSignalComponent.cs | 19 + .../Triggers/TriggerOnSlipComponent.cs | 10 + .../Triggers/TriggerOnSpawnComponent.cs | 10 + .../Triggers/TriggerOnStepTriggerComponent.cs | 14 + .../Triggers/TriggerOnStuckComponent.cs | 11 + .../TriggerOnTimedCollideComponent.cs | 26 + .../Triggers/TriggerOnUseComponent.cs | 10 + .../Components/Triggers/TriggerOnVerb.cs | 20 + .../Triggers/TriggerOnVoiceComponent.cs | 47 ++ .../Components/TwoStageTriggerComponent.cs | 58 +++ .../Systems/AddComponentsOnTriggerSystem.cs | 33 ++ .../Trigger/Systems/DamageOnTriggerSystem.cs | 40 ++ .../Systems/EmitSoundOnTriggerSystem.cs | 55 +++ .../Trigger/Systems/EmpOnTriggerSystem.cs | 31 ++ .../Trigger/Systems/ExplodeOnTriggerSystem.cs | 30 ++ .../Trigger/Systems/FlashOnTriggerSystem.cs | 30 ++ .../Trigger/Systems/GibOnTriggerSystem.cs | 40 ++ .../Systems/RepulseAttractOnTriggerSystem.cs | 34 ++ .../SharedReleaseGasOnTriggerSystem.cs | 36 ++ .../Trigger/Systems/ShockOnTriggerSystem.cs | 44 ++ .../Systems/TriggerOnActivateImplantSystem.cs | 22 + .../Systems/TriggerOnEmptyGunshotSystem.cs | 20 + .../Systems/TriggerOnMobstateChangeSystem.cs | 57 +-- .../Trigger/Systems/TriggerOnSlipSystem.cs | 21 + .../Trigger/Systems/TriggerOnStuckSystem.cs | 21 + .../Trigger/Systems/TriggerOnVerbSystem.cs | 31 ++ .../Trigger/Systems/TriggerSystem.Collide.cs | 73 +++ .../Systems/TriggerSystem.Condition.cs | 57 +++ .../Systems/TriggerSystem.Interaction.cs | 96 ++++ .../Systems/TriggerSystem.Proximity.cs | 138 ++++++ .../Trigger/Systems/TriggerSystem.Signal.cs | 44 ++ .../Trigger/Systems/TriggerSystem.Spawn.cs | 70 +++ .../Trigger/Systems/TriggerSystem.Timer.cs | 179 +++++++ .../Trigger/Systems/TriggerSystem.Voice.cs | 160 ++++++ .../Trigger/Systems/TriggerSystem.cs | 186 +++++++ .../Trigger/Systems/TwoStageTriggerSystem.cs | 51 ++ Content.Shared/Trigger/TriggerEvent.cs | 33 ++ Content.Shared/Trigger/TriggerVisuals.cs | 53 +- Content.Shared/Trigger/VoiceTriggeredEvent.cs | 10 + .../Ranged/Events/OnEmptyGunShotEvent.cs | 2 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 +- .../en-US/machine-linking/receiver_ports.ftl | 7 +- .../machine-linking/transmitter_ports.ftl | 7 +- .../en-US/triggers/ghost-kick-on-trigger.ftl | 1 + .../Locale/en-US/triggers/timer-trigger.ftl | 10 + .../triggers/toggle-trigger-condition.ftl | 7 + .../Locale/en-US/triggers/trigger-on-verb.ftl | 2 + .../en-US/triggers/trigger-on-voice.ftl | 12 + .../en-US/weapons/grenades/timer-trigger.ftl | 16 - .../en-US/weapons/grenades/voice-trigger.ftl | 12 - .../Maps/Shuttles/ShuttleEvent/quark.yml | 6 +- .../Prototypes/DeviceLinking/sink_ports.yml | 4 +- .../Prototypes/DeviceLinking/source_ports.yml | 6 + .../Entities/Clothing/Back/backpacks.yml | 2 +- .../Entities/Effects/admin_triggers.yml | 28 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 5 +- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 5 +- .../Entities/Mobs/NPCs/elemental.yml | 5 - .../Objects/Consumable/Food/Baked/pie.yml | 2 +- .../Objects/Devices/Electronics/igniter.yml | 5 + .../Objects/Devices/Electronics/signaller.yml | 6 +- .../Objects/Devices/Electronics/triggers.yml | 17 +- .../Objects/Devices/desynchronizer.yml | 1 + .../Entities/Objects/Devices/mousetrap.yml | 35 +- .../Entities/Objects/Devices/payload.yml | 1 + .../Entities/Objects/Fun/bike_horn.yml | 1 + .../Prototypes/Entities/Objects/Fun/dice.yml | 3 +- .../Entities/Objects/Fun/figurines.yml | 2 + .../Prototypes/Entities/Objects/Fun/toys.yml | 2 +- .../Entities/Objects/Materials/shards.yml | 13 +- .../Entities/Objects/Misc/land_mine.yml | 3 +- .../Objects/Misc/subdermal_implants.yml | 107 +++-- .../Objects/Specific/Janitorial/janitor.yml | 15 +- .../Objects/Specific/Janitorial/soap.yml | 5 +- .../Objects/Weapons/Bombs/firebomb.yml | 5 +- .../Entities/Objects/Weapons/Bombs/funny.yml | 9 +- .../Entities/Objects/Weapons/Bombs/pen.yml | 7 +- .../Objects/Weapons/Bombs/pipebomb.yml | 5 +- .../Objects/Weapons/Bombs/plastic.yml | 46 +- .../Entities/Objects/Weapons/Bombs/spider.yml | 9 +- .../Weapons/Guns/Projectiles/magic.yml | 23 +- .../Weapons/Guns/Projectiles/projectiles.yml | 6 +- .../Guns/Turrets/turrets_ballistic.yml | 4 +- .../Objects/Weapons/Throwable/grenades.yml | 121 ++++- .../Weapons/Throwable/projectile_grenades.yml | 21 +- .../Weapons/Throwable/scattering_grenades.yml | 18 +- .../Entities/Objects/Weapons/security.yml | 1 + .../Entities/Structures/Machines/bombs.yml | 6 +- .../Entities/Structures/Walls/asteroid.yml | 5 +- .../ServerInfo/Guidebook/Security/Defusal.xml | 2 +- 256 files changed, 3987 insertions(+), 2892 deletions(-) delete mode 100644 Content.Client/Explosion/SmokeOnTriggerSystem.cs delete mode 100644 Content.Client/Explosion/TriggerOnProximityComponent.cs delete mode 100644 Content.Client/Explosion/TriggerSystem.cs rename Content.Client/Trigger/{ => Components}/TimerTriggerVisualizerComponent.cs (84%) rename Content.Client/{Explosion/TriggerSystem.Proximity.cs => Trigger/Systems/ProximityTriggerAnimationSystem.cs} (91%) create mode 100644 Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs rename Content.Client/Trigger/{ => Systems}/TimerTriggerVisualizerSystem.cs (80%) delete mode 100644 Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs delete mode 100644 Content.Server/Chat/SpeakOnTriggerComponent.cs delete mode 100644 Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs delete mode 100644 Content.Server/Emp/EmpOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs delete mode 100644 Content.Server/Explosion/Components/AutomatedTimerComponent.cs delete mode 100644 Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/ShockOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnActivateComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnCollideComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnProximityComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnSignalComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnSlipComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnUseComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs delete mode 100644 Content.Server/Explosion/Components/TriggerWhitelistComponent.cs delete mode 100644 Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs delete mode 100644 Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs delete mode 100644 Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TriggerSystem.cs delete mode 100644 Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs delete mode 100644 Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs delete mode 100644 Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs delete mode 100644 Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs delete mode 100644 Content.Server/Mousetrap/MousetrapSystem.cs delete mode 100644 Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs delete mode 100644 Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs delete mode 100644 Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs delete mode 100644 Content.Server/Speech/Components/ActiveListenerComponent.cs rename Content.Server/{AlertLevel => Trigger}/Systems/AlertLevelChangeOnTriggerSystem.cs (74%) create mode 100644 Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs rename Content.Server/{IgnitionSource => Trigger/Systems}/IgniteOnTriggerSystem.cs (66%) create mode 100644 Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs create mode 100644 Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs create mode 100644 Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs create mode 100644 Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs rename Content.Server/{Chat => Trigger}/Systems/SpeakOnTriggerSystem.cs (53%) delete mode 100644 Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs delete mode 100644 Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs delete mode 100644 Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs delete mode 100644 Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs delete mode 100644 Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs delete mode 100644 Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs delete mode 100644 Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs delete mode 100644 Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs delete mode 100644 Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs delete mode 100644 Content.Shared/Flash/Components/FlashOnTriggerComponent.cs delete mode 100644 Content.Shared/Implants/Components/RattleComponent.cs delete mode 100644 Content.Shared/Implants/Components/TriggerImplantActionComponent.cs create mode 100644 Content.Shared/Mousetrap/MousetrapSystem.cs delete mode 100644 Content.Shared/Mousetrap/MousetrapVisuals.cs create mode 100644 Content.Shared/Speech/Components/ActiveListenerComponent.cs rename {Content.Server => Content.Shared}/Speech/ListenEvent.cs (93%) create mode 100644 Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs create mode 100644 Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs create mode 100644 Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs create mode 100644 Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs rename Content.Shared/{Explosion/Components/OnTrigger => Trigger/Components/Effects}/ReleaseGasOnTriggerComponent.cs (90%) rename Content.Shared/{Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs => Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs} (57%) create mode 100644 Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs rename Content.Shared/{Explosion/Components/OnTrigger => Trigger/Components/Effects}/SmokeOnTriggerComponent.cs (59%) create mode 100644 Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs rename {Content.Server/Explosion => Content.Shared/Trigger}/Components/RandomTimerTriggerComponent.cs (50%) create mode 100644 Content.Shared/Trigger/Components/TimerTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs rename {Content.Server/Explosion/Components => Content.Shared/Trigger/Components/Triggers}/RepeatingTriggerComponent.cs (56%) create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs create mode 100644 Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs rename Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs => Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs (56%) create mode 100644 Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs create mode 100644 Content.Shared/Trigger/Systems/TriggerSystem.cs create mode 100644 Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs create mode 100644 Content.Shared/Trigger/TriggerEvent.cs create mode 100644 Content.Shared/Trigger/VoiceTriggeredEvent.cs create mode 100644 Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl create mode 100644 Resources/Locale/en-US/triggers/timer-trigger.ftl create mode 100644 Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl create mode 100644 Resources/Locale/en-US/triggers/trigger-on-verb.ftl create mode 100644 Resources/Locale/en-US/triggers/trigger-on-voice.ftl delete mode 100644 Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl delete mode 100644 Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl diff --git a/Content.Client/Explosion/SmokeOnTriggerSystem.cs b/Content.Client/Explosion/SmokeOnTriggerSystem.cs deleted file mode 100644 index cac255e1ba..0000000000 --- a/Content.Client/Explosion/SmokeOnTriggerSystem.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Content.Shared.Explosion.EntitySystems; - -namespace Content.Client.Explosion; - -public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem -{ -} \ No newline at end of file diff --git a/Content.Client/Explosion/TriggerOnProximityComponent.cs b/Content.Client/Explosion/TriggerOnProximityComponent.cs deleted file mode 100644 index 5fa9bbfd23..0000000000 --- a/Content.Client/Explosion/TriggerOnProximityComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Content.Shared.Explosion; -using Content.Shared.Explosion.Components; - -namespace Content.Client.Explosion; - -[RegisterComponent, Access(typeof(TriggerSystem))] -public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent {} diff --git a/Content.Client/Explosion/TriggerSystem.cs b/Content.Client/Explosion/TriggerSystem.cs deleted file mode 100644 index e18569a18e..0000000000 --- a/Content.Client/Explosion/TriggerSystem.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Client.Explosion; - -public sealed partial class TriggerSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - InitializeProximity(); - } -} diff --git a/Content.Client/HotPotato/HotPotatoSystem.cs b/Content.Client/HotPotato/HotPotatoSystem.cs index 028a3b70d9..a1495ab994 100644 --- a/Content.Client/HotPotato/HotPotatoSystem.cs +++ b/Content.Client/HotPotato/HotPotatoSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.HotPotato; using Robust.Shared.Random; +using Robust.Shared.Prototypes; using Robust.Shared.Timing; namespace Content.Client.HotPotato; @@ -10,6 +11,9 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + private readonly EntProtoId _hotPotatoEffectId = "HotPotatoEffect"; + + // TODO: particle system public override void Update(float frameTime) { base.Update(frameTime); @@ -23,7 +27,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem if (_timing.CurTime < comp.TargetTime) continue; comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown); - Spawn("HotPotatoEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f))); + Spawn(_hotPotatoEffectId, _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f))); } } } diff --git a/Content.Client/Trigger/TimerTriggerVisualizerComponent.cs b/Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs similarity index 84% rename from Content.Client/Trigger/TimerTriggerVisualizerComponent.cs rename to Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs index 0e5a74b83b..0cb89edd89 100644 --- a/Content.Client/Trigger/TimerTriggerVisualizerComponent.cs +++ b/Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs @@ -1,7 +1,8 @@ +using Content.Client.Trigger.Systems; using Robust.Client.Animations; using Robust.Shared.Audio; -namespace Content.Client.Trigger; +namespace Content.Client.Trigger.Components; [RegisterComponent] [Access(typeof(TimerTriggerVisualizerSystem))] @@ -16,28 +17,27 @@ public sealed partial class TimerTriggerVisualsComponent : Component /// /// The RSI state used while the device has not been primed. /// - [DataField("unprimedSprite")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public string UnprimedSprite = "icon"; /// /// The RSI state used when the device is primed. /// Not VVWrite-able because it's only used at component init to construct the priming animation. /// - [DataField("primingSprite")] + [DataField] public string PrimingSprite = "primed"; /// /// The sound played when the device is primed. /// Not VVWrite-able because it's only used at component init to construct the priming animation. /// - [DataField("primingSound")] + [DataField, ViewVariables] public SoundSpecifier? PrimingSound; /// /// The actual priming animation. /// Constructed at component init from the sprite and sound. /// - [ViewVariables(VVAccess.ReadWrite)] + [ViewVariables] public Animation PrimingAnimation = default!; } diff --git a/Content.Client/Explosion/TriggerSystem.Proximity.cs b/Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs similarity index 91% rename from Content.Client/Explosion/TriggerSystem.Proximity.cs rename to Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs index 03e7436971..7954399505 100644 --- a/Content.Client/Explosion/TriggerSystem.Proximity.cs +++ b/Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs @@ -1,11 +1,12 @@ using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Triggers; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Animations; -namespace Content.Client.Explosion; +namespace Content.Client.Trigger.Systems; -public sealed partial class TriggerSystem +public sealed class ProximityTriggerAnimationSystem : EntitySystem { [Dependency] private readonly AnimationPlayerSystem _player = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -18,7 +19,7 @@ public sealed partial class TriggerSystem private const string AnimKey = "proximity"; - private static readonly Animation _flasherAnimation = new Animation + private static readonly Animation FlasherAnimation = new Animation { Length = TimeSpan.FromSeconds(0.6f), AnimationTracks = { @@ -42,8 +43,10 @@ public sealed partial class TriggerSystem } }; - private void InitializeProximity() + public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(OnProximityInit); SubscribeLocalEvent(OnProxAppChange); SubscribeLocalEvent(OnProxAnimation); @@ -94,7 +97,7 @@ public sealed partial class TriggerSystem break; case ProximityTriggerVisuals.Active: if (_player.HasRunningAnimation(uid, player, AnimKey)) return; - _player.Play((uid, player), _flasherAnimation, AnimKey); + _player.Play((uid, player), FlasherAnimation, AnimKey); break; case ProximityTriggerVisuals.Off: default: diff --git a/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs b/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs new file mode 100644 index 0000000000..a183282dde --- /dev/null +++ b/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Trigger.Systems; + +namespace Content.Client.Trigger.Systems; + +public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem; diff --git a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs b/Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs similarity index 80% rename from Content.Client/Trigger/TimerTriggerVisualizerSystem.cs rename to Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs index b3d85f2017..7c977c7589 100644 --- a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs +++ b/Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs @@ -1,11 +1,10 @@ +using Content.Client.Trigger.Components; using Content.Shared.Trigger; using Robust.Client.Animations; using Robust.Client.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; -using Robust.Shared.GameObjects; -namespace Content.Client.Trigger; +namespace Content.Client.Trigger.Systems; public sealed class TimerTriggerVisualizerSystem : VisualizerSystem { @@ -17,25 +16,26 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem(OnComponentInit); } - private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, ComponentInit args) + private void OnComponentInit(Entity ent, ref ComponentInit args) { - comp.PrimingAnimation = new Animation + ent.Comp.PrimingAnimation = new Animation { Length = TimeSpan.MaxValue, AnimationTracks = { - new AnimationTrackSpriteFlick() { + new AnimationTrackSpriteFlick() + { LayerKey = TriggerVisualLayers.Base, - KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.PrimingSprite, 0f) } + KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.PrimingSprite, 0f) } } }, }; - if (comp.PrimingSound != null) + if (ent.Comp.PrimingSound != null) { - comp.PrimingAnimation.AnimationTracks.Add( + ent.Comp.PrimingAnimation.AnimationTracks.Add( new AnimationTrackPlaySound() { - KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) } + KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.PrimingSound), 0) } } ); } diff --git a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs index 4db79373d3..f5a15295fd 100644 --- a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs +++ b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs @@ -1,6 +1,6 @@ using Content.IntegrationTests.Tests.Interaction; -using Content.Server.Explosion.Components; -using Content.Shared.Explosion.Components; +using Content.Shared.Trigger.Components; +using Content.Shared.Trigger.Systems; using Robust.Shared.Containers; using Robust.Shared.GameObjects; @@ -25,19 +25,19 @@ public sealed class ModularGrenadeTests : InteractionTest await InteractUsing(Cable); // Insert & remove trigger - AssertComp(false); + AssertComp(false); await InteractUsing(Trigger); - AssertComp(); + AssertComp(); await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false); await InteractUsing(Pry); - AssertComp(false); + AssertComp(false); // Trigger was dropped to floor, not deleted. await FindEntity(Trigger, LookupFlags.Uncontained); // Re-insert await InteractUsing(Trigger); - AssertComp(); + AssertComp(); // Insert & remove payload. await InteractUsing(Payload); @@ -56,13 +56,14 @@ public sealed class ModularGrenadeTests : InteractionTest await Pickup(); AssertComp(false); await UseInHand(); + AssertComp(true); // So uhhh grenades in hands don't destroy themselves when exploding. Maybe that will be fixed eventually. await Drop(); // Wait until grenade explodes - var timer = Comp(); - while (timer.TimeRemaining >= 0) + var triggerSys = SEntMan.System(); + while (Target != null && triggerSys.GetRemainingTime(SEntMan.GetEntity(Target.Value))?.TotalSeconds >= 0.0) { await RunTicks(10); } diff --git a/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs b/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs deleted file mode 100644 index aa6c5ba2bd..0000000000 --- a/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Server.AlertLevel.Systems; - -namespace Content.Server.AlertLevel; -/// -/// This component is for changing the alert level of the station when triggered. -/// -[RegisterComponent, Access(typeof(AlertLevelChangeOnTriggerSystem))] -public sealed partial class AlertLevelChangeOnTriggerComponent : Component -{ - /// - ///The alert level to change to when triggered. - /// - [DataField] - public string Level = "blue"; - - /// - ///Whether to play the sound when the alert level changes. - /// - [DataField] - public bool PlaySound = true; - - /// - ///Whether to say the announcement when the alert level changes. - /// - [DataField] - public bool Announce = true; - - /// - ///Force the alert change. This applies if the alert level is not selectable or not. - /// - [DataField] - public bool Force = false; -} diff --git a/Content.Server/Animals/Systems/ParrotMemorySystem.cs b/Content.Server/Animals/Systems/ParrotMemorySystem.cs index 56843094a1..a88957913f 100644 --- a/Content.Server/Animals/Systems/ParrotMemorySystem.cs +++ b/Content.Server/Animals/Systems/ParrotMemorySystem.cs @@ -5,13 +5,13 @@ using Content.Server.Animals.Components; using Content.Server.Mind; using Content.Server.Popups; using Content.Server.Radio; -using Content.Server.Speech; -using Content.Server.Speech.Components; using Content.Server.Vocalization.Systems; using Content.Shared.Animals.Components; using Content.Shared.Animals.Systems; using Content.Shared.Database; using Content.Shared.Mobs.Systems; +using Content.Shared.Speech; +using Content.Shared.Speech.Components; using Content.Shared.Whitelist; using Robust.Shared.Network; using Robust.Shared.Random; diff --git a/Content.Server/Chat/SpeakOnTriggerComponent.cs b/Content.Server/Chat/SpeakOnTriggerComponent.cs deleted file mode 100644 index d879cbf1bf..0000000000 --- a/Content.Server/Chat/SpeakOnTriggerComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.Dataset; -using Robust.Shared.Prototypes; - -namespace Content.Server.Chat; - -/// -/// Makes the entity speak when triggered. If the item has UseDelay component, the system will respect that cooldown. -/// -[RegisterComponent] -public sealed partial class SpeakOnTriggerComponent : Component -{ - /// - /// The identifier for the dataset prototype containing messages to be spoken by this entity. - /// - [DataField(required: true)] - public ProtoId Pack = string.Empty; -} diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs index 9ae50a8c97..dca2959f98 100644 --- a/Content.Server/Chat/SuicideSystem.cs +++ b/Content.Server/Chat/SuicideSystem.cs @@ -67,6 +67,9 @@ public sealed class SuicideSystem : EntitySystem if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, CannotSuicideTag)) return false; + // TODO: fix this + // This is a handled event, but the result is never used + // It looks like TriggerOnMobstateChange is supposed to prevent you from suiciding var suicideEvent = new SuicideEvent(victim); RaiseLocalEvent(victim, suicideEvent); diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 0b3a9c0a66..d49da57801 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -60,11 +60,6 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly ExamineSystemShared _examineSystem = default!; - public const int VoiceRange = 10; // how far voice goes in world units - public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units - public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units - public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg"; - private bool _loocEnabled = true; private bool _deadLoocEnabled; private bool _critLoocEnabled; diff --git a/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs b/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs deleted file mode 100644 index 8a0ee51076..0000000000 --- a/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Damage; -using Content.Shared.Damage.Components; - -namespace Content.Server.Damage.Systems; - -// System for damage that occurs on specific trigger, towards the user.. -public sealed class DamageUserOnTriggerSystem : EntitySystem -{ - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - - public override void Initialize() - { - SubscribeLocalEvent(OnTrigger); - } - - private void OnTrigger(EntityUid uid, DamageUserOnTriggerComponent component, TriggerEvent args) - { - if (args.User is null) - return; - - args.Handled |= OnDamageTrigger(uid, args.User.Value, component); - } - - private bool OnDamageTrigger(EntityUid source, EntityUid target, DamageUserOnTriggerComponent? component = null) - { - if (!Resolve(source, ref component)) - { - return false; - } - - var damage = new DamageSpecifier(component.Damage); - var ev = new BeforeDamageUserOnTriggerEvent(damage, target); - RaiseLocalEvent(source, ev); - - return _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: source) is not null; - } -} - -public sealed class BeforeDamageUserOnTriggerEvent : EntityEventArgs -{ - public DamageSpecifier Damage { get; set; } - public EntityUid Tripper { get; } - - public BeforeDamageUserOnTriggerEvent(DamageSpecifier damage, EntityUid target) - { - Damage = damage; - Tripper = target; - } -} diff --git a/Content.Server/Defusable/Systems/DefusableSystem.cs b/Content.Server/Defusable/Systems/DefusableSystem.cs index 1e9caece94..5c589d2131 100644 --- a/Content.Server/Defusable/Systems/DefusableSystem.cs +++ b/Content.Server/Defusable/Systems/DefusableSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Defusable.Components; -using Content.Server.Explosion.Components; using Content.Server.Explosion.EntitySystems; using Content.Server.Popups; using Content.Server.Wires; @@ -8,13 +7,13 @@ using Content.Shared.Construction.Components; using Content.Shared.Database; using Content.Shared.Defusable; using Content.Shared.Examine; -using Content.Shared.Explosion.Components; -using Content.Shared.Explosion.Components.OnTrigger; using Content.Shared.Popups; +using Content.Shared.Trigger.Components; +using Content.Shared.Trigger.Components.Effects; +using Content.Shared.Trigger.Systems; using Content.Shared.Verbs; using Content.Shared.Wires; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; namespace Content.Server.Defusable.Systems; @@ -74,12 +73,13 @@ public sealed class DefusableSystem : SharedDefusableSystem { args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid))); } - else if (comp.Activated && TryComp(uid, out var activeComp)) + else if (comp.Activated) { - if (comp.DisplayTime) + var remaining = _trigger.GetRemainingTime(uid); + if (comp.DisplayTime && remaining != null) { args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid), - ("time", MathF.Floor(activeComp.TimeRemaining)))); + ("time", Math.Floor(remaining.Value.TotalSeconds)))); } else { @@ -139,16 +139,9 @@ public sealed class DefusableSystem : SharedDefusableSystem SetActivated(comp, true); _popup.PopupEntity(Loc.GetString("defusable-popup-begun", ("name", uid)), uid); - if (TryComp(uid, out var timerTrigger)) + if (TryComp(uid, out var timerTrigger)) { - _trigger.HandleTimerTrigger( - uid, - user, - timerTrigger.Delay, - timerTrigger.BeepInterval, - timerTrigger.InitialBeepDelay, - timerTrigger.BeepSound - ); + _trigger.ActivateTimerTrigger((uid, timerTrigger)); } RaiseLocalEvent(uid, new BombArmedEvent(uid)); @@ -168,7 +161,7 @@ public sealed class DefusableSystem : SharedDefusableSystem RaiseLocalEvent(uid, new BombDetonatedEvent(uid)); - _explosion.TriggerExplosive(uid, user:detonator); + _explosion.TriggerExplosive(uid, user: detonator); QueueDel(uid); _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated); @@ -188,7 +181,7 @@ public sealed class DefusableSystem : SharedDefusableSystem { SetUsable(comp, false); RemComp(uid); - RemComp(uid); + RemComp(uid); } RemComp(uid); @@ -246,7 +239,7 @@ public sealed class DefusableSystem : SharedDefusableSystem if (comp is not { Activated: true, DelayWireUsed: false }) return; - _trigger.TryDelay(wire.Owner, 30f); + _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(30)); _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner); comp.DelayWireUsed = true; } @@ -268,7 +261,7 @@ public sealed class DefusableSystem : SharedDefusableSystem if (comp is { Activated: true, ProceedWireUsed: false }) { comp.ProceedWireUsed = true; - _trigger.TryDelay(wire.Owner, -15f); + _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(-15)); } _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner); @@ -298,7 +291,7 @@ public sealed class DefusableSystem : SharedDefusableSystem { if (!comp.ActivatedWireUsed) { - _trigger.TryDelay(wire.Owner, 30f); + _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(30)); _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner); comp.ActivatedWireUsed = true; } diff --git a/Content.Server/Destructible/DestructibleSystem.cs b/Content.Server/Destructible/DestructibleSystem.cs index 82d5ffcb9a..b4a79c6830 100644 --- a/Content.Server/Destructible/DestructibleSystem.cs +++ b/Content.Server/Destructible/DestructibleSystem.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Systems; @@ -14,15 +15,13 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Destructible; using Content.Shared.FixedPoint; +using Content.Shared.Humanoid; +using Content.Shared.Trigger.Systems; using JetBrains.Annotations; using Robust.Server.Audio; -using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using System.Linq; -using Content.Shared.Humanoid; -using Robust.Shared.Player; namespace Content.Server.Destructible { diff --git a/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs index 97a5f8b7ef..cf337ac76a 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs @@ -5,6 +5,6 @@ public sealed partial class TimerStartBehavior : IThresholdBehavior { public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) { - system.TriggerSystem.StartTimer(owner, cause); + system.TriggerSystem.ActivateTimerTrigger(owner, cause); } } diff --git a/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs index 03bdb8ff69..66da79063c 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs @@ -1,10 +1,18 @@ -namespace Content.Server.Destructible.Thresholds.Behaviors; +using Content.Shared.Trigger.Systems; + +namespace Content.Server.Destructible.Thresholds.Behaviors; [DataDefinition] public sealed partial class TriggerBehavior : IThresholdBehavior { + /// + /// The trigger key to use when triggering. + /// + [DataField] + public string? KeyOut { get; set; } = TriggerSystem.DefaultTriggerKey; + public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) { - system.TriggerSystem.Trigger(owner, cause); + system.TriggerSystem.Trigger(owner, cause, KeyOut); } } diff --git a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs index d957f0171e..ff20ac4d8d 100644 --- a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs @@ -1,4 +1,6 @@ using Content.Server.DeviceLinking.Components; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking.Events; diff --git a/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs b/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs index 45e4d21750..7743a97d72 100644 --- a/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs +++ b/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs @@ -1,5 +1,4 @@ using Content.Server.DeviceLinking.Components; -using Content.Server.DeviceNetwork; using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking.Events; using Content.Shared.DeviceNetwork; diff --git a/Content.Server/DeviceLinking/Systems/SignallerSystem.cs b/Content.Server/DeviceLinking/Systems/SignallerSystem.cs index a5091508ed..7d684d1cd5 100644 --- a/Content.Server/DeviceLinking/Systems/SignallerSystem.cs +++ b/Content.Server/DeviceLinking/Systems/SignallerSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Administration.Logs; using Content.Server.DeviceLinking.Components; -using Content.Server.Explosion.EntitySystems; using Content.Shared.Database; using Content.Shared.Interaction.Events; using Content.Shared.Timing; @@ -10,7 +9,6 @@ namespace Content.Server.DeviceLinking.Systems; public sealed class SignallerSystem : EntitySystem { [Dependency] private readonly DeviceLinkSystem _link = default!; - [Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; public override void Initialize() @@ -19,7 +17,6 @@ public sealed class SignallerSystem : EntitySystem SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnTrigger); } private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args) @@ -36,16 +33,4 @@ public sealed class SignallerSystem : EntitySystem _link.InvokePort(uid, component.Port); args.Handled = true; } - - private void OnTrigger(EntityUid uid, SignallerComponent component, TriggerEvent args) - { - if (!TryComp(uid, out UseDelayComponent? useDelay) - // if on cooldown, do nothing - // and set cooldown to prevent clocks - || !_useDelay.TryResetDelay((uid, useDelay), true)) - return; - - _link.InvokePort(uid, component.Port); - args.Handled = true; - } } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index bb9e71cfc3..05dfffa4a9 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -58,7 +58,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly TurfSystem _turf = default!; - private static readonly ProtoId StatusEffectKey = "Electrocution"; + private static readonly ProtoId StatusKeyIn = "Electrocution"; private static readonly ProtoId DamageType = "Shock"; private static readonly ProtoId WindowTag = "Window"; @@ -386,12 +386,12 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem } if (!Resolve(uid, ref statusEffects, false) || - !_statusEffects.CanApplyEffect(uid, StatusEffectKey, statusEffects)) + !_statusEffects.CanApplyEffect(uid, StatusKeyIn, statusEffects)) { return false; } - if (!_statusEffects.TryAddStatusEffect(uid, StatusEffectKey, time, refresh, statusEffects)) + if (!_statusEffects.TryAddStatusEffect(uid, StatusKeyIn, time, refresh, statusEffects)) return false; var shouldStun = siemensCoefficient > 0.5f; diff --git a/Content.Server/Emp/EmpOnTriggerComponent.cs b/Content.Server/Emp/EmpOnTriggerComponent.cs deleted file mode 100644 index fac509ea9f..0000000000 --- a/Content.Server/Emp/EmpOnTriggerComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Content.Server.Emp; - -/// -/// Upon being triggered will EMP area around it. -/// -[RegisterComponent] -[Access(typeof(EmpSystem))] -public sealed partial class EmpOnTriggerComponent : Component -{ - [DataField("range"), ViewVariables(VVAccess.ReadWrite)] - public float Range = 1.0f; - - /// - /// How much energy will be consumed per battery in range - /// - [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)] - public float EnergyConsumption; - - /// - /// How long it disables targets in seconds - /// - [DataField("disableDuration"), ViewVariables(VVAccess.ReadWrite)] - public float DisableDuration = 60f; -} diff --git a/Content.Server/Emp/EmpSystem.cs b/Content.Server/Emp/EmpSystem.cs index 4b5143aa40..b8bfc63afe 100644 --- a/Content.Server/Emp/EmpSystem.cs +++ b/Content.Server/Emp/EmpSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Explosion.EntitySystems; using Content.Server.Power.EntitySystems; using Content.Server.Radio; using Content.Server.SurveillanceCamera; @@ -20,7 +19,6 @@ public sealed class EmpSystem : SharedEmpSystem { base.Initialize(); SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent(HandleEmpTrigger); SubscribeLocalEvent(OnRadioSendAttempt); SubscribeLocalEvent(OnRadioReceiveAttempt); @@ -28,14 +26,7 @@ public sealed class EmpSystem : SharedEmpSystem SubscribeLocalEvent(OnCameraSetActive); } - /// - /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range. - /// - /// The location to trigger the EMP pulse at. - /// The range of the EMP pulse. - /// The amount of energy consumed by the EMP pulse. - /// The duration of the EMP effects. - public void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration) + public override void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration) { foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range)) { @@ -118,12 +109,6 @@ public sealed class EmpSystem : SharedEmpSystem args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine")); } - private void HandleEmpTrigger(EntityUid uid, EmpOnTriggerComponent comp, TriggerEvent args) - { - EmpPulse(_transform.GetMapCoordinates(uid), comp.Range, comp.EnergyConsumption, comp.DisableDuration); - args.Handled = true; - } - private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args) { args.Cancelled = true; diff --git a/Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs b/Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs deleted file mode 100644 index 9d8073413c..0000000000 --- a/Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Content.Server.Explosion.Components; - -[RegisterComponent] -public sealed partial class ActiveTriggerOnTimedCollideComponent : Component { } diff --git a/Content.Server/Explosion/Components/AutomatedTimerComponent.cs b/Content.Server/Explosion/Components/AutomatedTimerComponent.cs deleted file mode 100644 index c01aeb91e5..0000000000 --- a/Content.Server/Explosion/Components/AutomatedTimerComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Disallows starting the timer by hand, must be stuck or triggered by a system using StartTimer. -/// -[RegisterComponent] -public sealed partial class AutomatedTimerComponent : Component -{ -} diff --git a/Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs deleted file mode 100644 index d83b57c188..0000000000 --- a/Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Server.Explosion.EntitySystems; - -namespace Content.Server.Explosion.Components; - -/// -/// Will anchor the attached entity upon a . -/// -[RegisterComponent] -public sealed partial class AnchorOnTriggerComponent : Component -{ - [DataField("removeOnTrigger")] - public bool RemoveOnTrigger = true; -} diff --git a/Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs deleted file mode 100644 index 9846cad84b..0000000000 --- a/Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Server.Explosion.EntitySystems; - -namespace Content.Server.Explosion.Components; - -/// -/// Will delete the attached entity upon a . -/// -[RegisterComponent] -public sealed partial class DeleteOnTriggerComponent : Component -{ -} diff --git a/Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs deleted file mode 100644 index da58467659..0000000000 --- a/Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Gibs on trigger, self explanatory. -/// Also in case of an implant using this, gibs the implant user instead. -/// -[RegisterComponent] -public sealed partial class GibOnTriggerComponent : Component -{ - /// - /// Should gibbing also delete the owners items? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("deleteItems")] - public bool DeleteItems = false; -} diff --git a/Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs deleted file mode 100644 index 7f9f16a227..0000000000 --- a/Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Robust.Shared.Audio; - -namespace Content.Server.Explosion.Components; - -/// -/// Will play sound from the attached entity upon a . -/// -[RegisterComponent] -public sealed partial class SoundOnTriggerComponent : Component -{ - [DataField("removeOnTrigger")] - public bool RemoveOnTrigger = true; - - [DataField("sound")] - public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Effects/Grenades/supermatter_start.ogg"); -} diff --git a/Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs deleted file mode 100644 index a63d6fcf66..0000000000 --- a/Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Server.Explosion.Components.OnTrigger; - -/// -/// After being triggered applies the specified components and runs triggers again. -/// -[RegisterComponent, AutoGenerateComponentPause] -public sealed partial class TwoStageTriggerComponent : Component -{ - /// - /// How long it takes for the second stage to be triggered. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("triggerDelay")] - public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10); - - /// - /// This list of components that will be added for the second trigger. - /// - [DataField("components", required: true)] - public ComponentRegistry SecondStageComponents = new(); - - [DataField("nextTriggerTime", customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan? NextTriggerTime; - - [DataField("triggered")] - public bool Triggered = false; - - [DataField("ComponentsIsLoaded")] - public bool ComponentsIsLoaded = false; -} diff --git a/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs b/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs index 58d687e025..20a73b46b5 100644 --- a/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs +++ b/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs @@ -45,4 +45,10 @@ public sealed partial class ProjectileGrenadeComponent : Component /// [DataField] public float MaxVelocity = 6f; + + /// + /// The trigger key that will activate the grenade. + /// + [DataField] + public string TriggerKey = "timer"; } diff --git a/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs b/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs deleted file mode 100644 index a553cc047a..0000000000 --- a/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Content.Server.Explosion.EntitySystems; - -namespace Content.Server.Explosion.Components; - -/// -/// A component that electrocutes an entity having this component when a trigger is triggered. -/// -[RegisterComponent, AutoGenerateComponentPause] -[Access(typeof(TriggerSystem))] -public sealed partial class ShockOnTriggerComponent : Component -{ - /// - /// The force of an electric shock when the trigger is triggered. - /// - [DataField] - public int Damage = 5; - - /// - /// Duration of electric shock when the trigger is triggered. - /// - [DataField] - public TimeSpan Duration = TimeSpan.FromSeconds(2); - - /// - /// The minimum delay between repeating triggers. - /// - [DataField] - public TimeSpan Cooldown = TimeSpan.FromSeconds(4); - - /// - /// When can the trigger run again? - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan NextTrigger = TimeSpan.Zero; -} diff --git a/Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs b/Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs deleted file mode 100644 index c28ec7faeb..0000000000 --- a/Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Robust.Shared.Prototypes; - -namespace Content.Server.Explosion.Components; - -/// -/// Spawns a protoype when triggered. -/// -[RegisterComponent, Access(typeof(TriggerSystem))] -public sealed partial class SpawnOnTriggerComponent : Component -{ - /// - /// The prototype to spawn. - /// - [DataField(required: true)] - public EntProtoId Proto = string.Empty; - - /// - /// Use MapCoordinates for spawning? - /// Set to true if you don't want the new entity parented to the spawner. - /// - [DataField] - public bool mapCoords; -} diff --git a/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs b/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs deleted file mode 100644 index 9adc6dab87..0000000000 --- a/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Shared.DeviceLinking; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Explosion.Components -{ - /// - /// Sends a trigger when signal is received. - /// - [RegisterComponent] - public sealed partial class TimerStartOnSignalComponent : Component - { - [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Port = "Timer"; - } -} diff --git a/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs b/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs deleted file mode 100644 index 1531c7425e..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Triggers on click. -/// -[RegisterComponent] -public sealed partial class TriggerOnActivateComponent : Component { } diff --git a/Content.Server/Explosion/Components/TriggerOnCollideComponent.cs b/Content.Server/Explosion/Components/TriggerOnCollideComponent.cs deleted file mode 100644 index 28c2ed8c34..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnCollideComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Triggers when colliding with another entity. -/// -[RegisterComponent] -public sealed partial class TriggerOnCollideComponent : Component -{ - /// - /// The fixture with which to collide. - /// - [DataField(required: true)] - public string FixtureID = string.Empty; - - /// - /// Doesn't trigger if the other colliding fixture is nonhard. - /// - [DataField] - public bool IgnoreOtherNonHard = true; -} diff --git a/Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs b/Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs deleted file mode 100644 index a6cdda247c..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Shared.Mobs; - -namespace Content.Server.Explosion.Components; - -/// -/// Use where you want something to trigger on mobstate change -/// -[RegisterComponent] -public sealed partial class TriggerOnMobstateChangeComponent : Component -{ - /// - /// What state should trigger this? - /// - [ViewVariables] - [DataField("mobState", required: true)] - public List MobState = new(); - - /// - /// If true, prevents suicide attempts for the trigger to prevent cheese. - /// - [ViewVariables] - [DataField("preventSuicide")] - public bool PreventSuicide = false; -} diff --git a/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs b/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs deleted file mode 100644 index 4f3fb4754e..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Explosion; -using Content.Shared.Explosion.Components; -using Content.Shared.Physics; -using Robust.Shared.Physics.Collision.Shapes; -using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Dynamics; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Server.Explosion.Components -{ - - /// - /// Raises a whenever an entity collides with a fixture attached to the owner of this component. - /// - [RegisterComponent, AutoGenerateComponentPause] - public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent - { - public const string FixtureID = "trigger-on-proximity-fixture"; - - [ViewVariables] - public readonly Dictionary Colliding = new(); - - /// - /// What is the shape of the proximity fixture? - /// - [ViewVariables] - [DataField("shape")] - public IPhysShape Shape = new PhysShapeCircle(2f); - - /// - /// How long the the proximity trigger animation plays for. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("animationDuration")] - public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f); - - /// - /// Whether the entity needs to be anchored for the proximity to work. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("requiresAnchored")] - public bool RequiresAnchored = true; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("enabled")] - public bool Enabled = true; - - /// - /// The minimum delay between repeating triggers. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("cooldown")] - public TimeSpan Cooldown = TimeSpan.FromSeconds(5); - - /// - /// When can the trigger run again? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan NextTrigger = TimeSpan.Zero; - - /// - /// When will the visual state be updated again after activation? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan NextVisualUpdate = TimeSpan.Zero; - - /// - /// What speed should the other object be moving at to trigger the proximity fixture? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("triggerSpeed")] - public float TriggerSpeed = 3.5f; - - /// - /// If this proximity is triggered should we continually repeat it? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("repeating")] - public bool Repeating = true; - - /// - /// What layer is the trigger fixture on? - /// - [ViewVariables] - [DataField("layer", customTypeSerializer: typeof(FlagSerializer))] - public int Layer = (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable); - } -} diff --git a/Content.Server/Explosion/Components/TriggerOnSignalComponent.cs b/Content.Server/Explosion/Components/TriggerOnSignalComponent.cs deleted file mode 100644 index fb7495612c..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnSignalComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Shared.DeviceLinking; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Explosion.Components -{ - /// - /// Sends a trigger when signal is received. - /// - [RegisterComponent] - public sealed partial class TriggerOnSignalComponent : Component - { - [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Port = "Trigger"; - } -} diff --git a/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs b/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs deleted file mode 100644 index 16632b9b72..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Server.Explosion.Components; - -[RegisterComponent] -public sealed partial class TriggerOnSlipComponent : Component -{ -} diff --git a/Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs b/Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs deleted file mode 100644 index 704ff410cd..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// calls the trigger when the object is initialized -/// -[RegisterComponent] -public sealed partial class TriggerOnSpawnComponent : Component -{ -} diff --git a/Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs b/Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs deleted file mode 100644 index 49e99ff785..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// This is used for entities that want the more generic 'trigger' behavior after a step trigger occurs. -/// Not done by default, since it's not useful for everything and might cause weird behavior. But it is useful for a lot of stuff like mousetraps. -/// -[RegisterComponent] -public sealed partial class TriggerOnStepTriggerComponent : Component -{ -} diff --git a/Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs b/Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs deleted file mode 100644 index d53bcbcc5d..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Triggers when the entity is overlapped for the specified duration. -/// -[RegisterComponent] -public sealed partial class TriggerOnTimedCollideComponent : Component -{ - [ViewVariables(VVAccess.ReadWrite)] - [DataField("threshold")] - public float Threshold; - - /// - /// A collection of entities that are colliding with this, and their own unique accumulator. - /// - [ViewVariables] - public readonly Dictionary Colliding = new(); -} diff --git a/Content.Server/Explosion/Components/TriggerOnUseComponent.cs b/Content.Server/Explosion/Components/TriggerOnUseComponent.cs deleted file mode 100644 index 2b44d2fbac..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnUseComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Triggers on use in hand. -/// -[RegisterComponent] -public sealed partial class TriggerOnUseComponent : Component { } diff --git a/Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs b/Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs deleted file mode 100644 index 7d07f496d0..0000000000 --- a/Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Content.Server.Explosion.Components -{ - /// - /// Sends a trigger when the keyphrase is heard - /// - [RegisterComponent] - public sealed partial class TriggerOnVoiceComponent : Component - { - public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase); - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keyPhrase")] - public string? KeyPhrase; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("listenRange")] - public int ListenRange { get; private set; } = 4; - - [DataField("isRecording")] - public bool IsRecording = false; - - [DataField("minLength")] - public int MinLength = 3; - - [DataField("maxLength")] - public int MaxLength = 50; - } -} diff --git a/Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs b/Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs deleted file mode 100644 index ad39b9c515..0000000000 --- a/Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Explosion.Components; - -/// -/// Triggers a gun when attempting to shoot while it's empty -/// -[RegisterComponent] -public sealed partial class TriggerWhenEmptyComponent : Component -{ -} diff --git a/Content.Server/Explosion/Components/TriggerWhitelistComponent.cs b/Content.Server/Explosion/Components/TriggerWhitelistComponent.cs deleted file mode 100644 index 80becf17cc..0000000000 --- a/Content.Server/Explosion/Components/TriggerWhitelistComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Whitelist; - -namespace Content.Server.Explosion.Components; - -/// -/// Checks if the user of a Trigger satisfies a whitelist and blacklist condition. -/// Cancels the trigger otherwise. -/// -[RegisterComponent] -public sealed partial class TriggerWhitelistComponent : Component -{ - /// - /// Whitelist for what entites can cause this trigger. - /// - [DataField] - public EntityWhitelist? Whitelist; - - /// - /// Blacklist for what entites can cause this trigger. - /// - [DataField] - public EntityWhitelist? Blacklist; -} diff --git a/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs index 6953010e46..4b93f22cd1 100644 --- a/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Explosion.Components; using Content.Server.Weapons.Ranged.Systems; +using Content.Shared.Trigger; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -45,6 +46,9 @@ public sealed class ProjectileGrenadeSystem : EntitySystem /// private void OnFragTrigger(Entity entity, ref TriggerEvent args) { + if (args.Key != entity.Comp.TriggerKey) + return; + FragmentIntoProjectiles(entity.Owner, entity.Comp); args.Handled = true; } diff --git a/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs deleted file mode 100644 index ae3f84f843..0000000000 --- a/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Content.Server.Atmos.EntitySystems; -using Content.Shared.Explosion.Components.OnTrigger; -using Content.Shared.Explosion.EntitySystems; -using Robust.Shared.Timing; - -namespace Content.Server.Explosion.EntitySystems; - -/// -/// Releases a gas mixture to the atmosphere when triggered. -/// Can also release gas over a set timespan to prevent trolling people -/// with the instant-wall-of-pressure-inator. -/// -public sealed partial class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - [Dependency] private readonly IGameTiming _timing = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnTrigger); - } - - /// - /// Shrimply sets the component to active when triggered, allowing it to release over time. - /// - private void OnTrigger(Entity ent, ref TriggerEvent args) - { - ent.Comp.Active = true; - ent.Comp.NextReleaseTime = _timing.CurTime; - ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles; - UpdateAppearance(ent.Owner, true); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var curTime = _timing.CurTime; - var query = EntityQueryEnumerator(); - - while (query.MoveNext(out var uid, out var comp)) - { - if (!comp.Active || comp.NextReleaseTime > curTime) - continue; - - var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction); - var environment = _atmosphereSystem.GetContainingMixture(uid, false, true); - - if (environment == null) - { - UpdateAppearance(uid, false); - RemCompDeferred(uid); - continue; - } - - _atmosphereSystem.Merge(environment, giverGasMix); - comp.NextReleaseTime += comp.ReleaseInterval; - - if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit || - comp.Air.TotalMoles <= 0) - { - UpdateAppearance(uid, false); - RemCompDeferred(uid); - continue; - } - } - } - - private void UpdateAppearance(Entity entity, bool state) - { - if (!Resolve(entity, ref entity.Comp, false)) - return; - - _appearance.SetData(entity, ReleaseGasOnTriggerVisuals.Key, state); - } -} diff --git a/Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs deleted file mode 100644 index 9e595c5d9e..0000000000 --- a/Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Shared.Explosion.Components.OnTrigger; -using Content.Shared.Explosion.EntitySystems; -using Content.Shared.RepulseAttract; -using Content.Shared.Timing; - -namespace Content.Server.Explosion.EntitySystems; - -public sealed class RepulseAttractOnTriggerSystem : SharedRepulseAttractOnTriggerSystem -{ - [Dependency] private readonly RepulseAttractSystem _repulse = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly UseDelaySystem _delay = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnTrigger); - } - - private void OnTrigger(Entity ent, ref TriggerEvent args) - { - if (_delay.IsDelayed(ent.Owner)) - return; - - var position = _transform.GetMapCoordinates(ent); - _repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask); - } -} diff --git a/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs index 2657ba3449..5f7df28beb 100644 --- a/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs @@ -1,5 +1,8 @@ using Content.Shared.Explosion.Components; using Content.Shared.Throwing; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Systems; +using Content.Shared.Trigger.Components; using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -15,6 +18,7 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; + [Dependency] private readonly TriggerSystem _trigger = default!; public override void Initialize() { @@ -30,6 +34,9 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem /// private void OnScatteringTrigger(Entity entity, ref TriggerEvent args) { + if (args.Key != entity.Comp.TriggerKey) + return; + entity.Comp.IsTriggered = true; args.Handled = true; } @@ -76,13 +83,12 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem _throwingSystem.TryThrow(contentUid, direction, component.Velocity); - if (component.TriggerContents) + if (component.TriggerContents && TryComp(contentUid, out var contentTimer)) { additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax); - var contentTimer = EnsureComp(contentUid); - contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay; - var ev = new ActiveTimerTriggerEvent(contentUid, uid); - RaiseLocalEvent(contentUid, ref ev); + + _trigger.SetDelay((contentUid, contentTimer), TimeSpan.FromSeconds(component.DelayBeforeTriggerContents + additionalIntervalDelay)); + _trigger.ActivateTimerTrigger((contentUid, contentTimer)); } } diff --git a/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs deleted file mode 100644 index 19335d3446..0000000000 --- a/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Shared.Explosion.Components; -using Content.Shared.Explosion.EntitySystems; -using Content.Server.Fluids.EntitySystems; -using Content.Server.Spreader; -using Content.Shared.Chemistry.Components; -using Content.Shared.Coordinates.Helpers; -using Content.Shared.Maps; -using Robust.Server.GameObjects; -using Robust.Shared.Map; - -namespace Content.Server.Explosion.EntitySystems; - -/// -/// Handles creating smoke when is triggered. -/// -public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem -{ - [Dependency] private readonly IMapManager _mapMan = default!; - [Dependency] private readonly SharedMapSystem _map = default!; - [Dependency] private readonly SmokeSystem _smoke = default!; - [Dependency] private readonly TransformSystem _transform = default!; - [Dependency] private readonly SpreaderSystem _spreader = default!; - [Dependency] private readonly TurfSystem _turf = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnTrigger); - } - - private void OnTrigger(EntityUid uid, SmokeOnTriggerComponent comp, TriggerEvent args) - { - var xform = Transform(uid); - var mapCoords = _transform.GetMapCoordinates(uid, xform); - if (!_mapMan.TryFindGridAt(mapCoords, out var gridUid, out var grid) || - !_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef) || - tileRef.Tile.IsEmpty) - { - return; - } - - if (_spreader.RequiresFloorToSpread(comp.SmokePrototype.ToString()) && _turf.IsSpace(tileRef)) - return; - - var coords = _map.MapToGrid(gridUid, mapCoords); - var ent = Spawn(comp.SmokePrototype, coords.SnapToGrid()); - if (!TryComp(ent, out var smoke)) - { - Log.Error($"Smoke prototype {comp.SmokePrototype} was missing SmokeComponent"); - Del(ent); - return; - } - - _smoke.StartSmoke(ent, comp.Solution, comp.Duration, comp.SpreadAmount, smoke); - } -} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs deleted file mode 100644 index d06e9fa1c2..0000000000 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs +++ /dev/null @@ -1,170 +0,0 @@ -using Content.Server.Explosion.Components; -using Content.Shared.Examine; -using Content.Shared.Explosion.Components; -using Content.Shared.Interaction.Events; -using Content.Shared.Popups; -using Content.Shared.Sticky; -using Content.Shared.Verbs; - -namespace Content.Server.Explosion.EntitySystems; - -public sealed partial class TriggerSystem -{ - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - - private void InitializeOnUse() - { - SubscribeLocalEvent(OnTimerUse); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent>(OnGetAltVerbs); - SubscribeLocalEvent(OnStuck); - SubscribeLocalEvent(OnRandomTimerTriggerMapInit); - } - - private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, ref EntityStuckEvent args) - { - if (!component.StartOnStick) - return; - - StartTimer((uid, component), args.User); - } - - private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args) - { - if (args.IsInDetailsRange && component.Examinable) - args.PushText(Loc.GetString("examine-trigger-timer", ("time", component.Delay))); - } - - /// - /// Add an alt-click interaction that cycles through delays. - /// - private void OnGetAltVerbs(EntityUid uid, OnUseTimerTriggerComponent component, GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess || args.Hands == null) - return; - - if (component.UseVerbInstead) - { - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString("verb-start-detonation"), - Act = () => StartTimer((uid, component), args.User), - Priority = 2 - }); - } - - if (component.AllowToggleStartOnStick) - { - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString("verb-toggle-start-on-stick"), - Act = () => ToggleStartOnStick(uid, args.User, component) - }); - } - - if (component.DelayOptions == null || component.DelayOptions.Count == 1) - return; - - args.Verbs.Add(new AlternativeVerb() - { - Category = TimerOptions, - Text = Loc.GetString("verb-trigger-timer-cycle"), - Act = () => CycleDelay(component, args.User), - Priority = 1 - }); - - foreach (var option in component.DelayOptions) - { - if (MathHelper.CloseTo(option, component.Delay)) - { - args.Verbs.Add(new AlternativeVerb() - { - Category = TimerOptions, - Text = Loc.GetString("verb-trigger-timer-set-current", ("time", option)), - Disabled = true, - Priority = (int) (-100 * option) - }); - continue; - } - - args.Verbs.Add(new AlternativeVerb() - { - Category = TimerOptions, - Text = Loc.GetString("verb-trigger-timer-set", ("time", option)), - Priority = (int) (-100 * option), - - Act = () => - { - component.Delay = option; - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", option)), args.User, args.User); - }, - }); - } - } - - private void OnRandomTimerTriggerMapInit(Entity ent, ref MapInitEvent args) - { - var (_, comp) = ent; - - if (!TryComp(ent, out var timerTriggerComp)) - return; - - timerTriggerComp.Delay = _random.NextFloat(comp.Min, comp.Max); - } - - private void CycleDelay(OnUseTimerTriggerComponent component, EntityUid user) - { - if (component.DelayOptions == null || component.DelayOptions.Count == 1) - return; - - // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short. - - component.DelayOptions.Sort(); - - if (component.DelayOptions[^1] <= component.Delay) - { - component.Delay = component.DelayOptions[0]; - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", component.Delay)), user, user); - return; - } - - foreach (var option in component.DelayOptions) - { - if (option > component.Delay) - { - component.Delay = option; - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", option)), user, user); - return; - } - } - } - - private void ToggleStartOnStick(EntityUid grenade, EntityUid user, OnUseTimerTriggerComponent comp) - { - if (comp.StartOnStick) - { - comp.StartOnStick = false; - _popupSystem.PopupEntity(Loc.GetString("popup-start-on-stick-off"), grenade, user); - } - else - { - comp.StartOnStick = true; - _popupSystem.PopupEntity(Loc.GetString("popup-start-on-stick-on"), grenade, user); - } - } - - private void OnTimerUse(EntityUid uid, OnUseTimerTriggerComponent component, UseInHandEvent args) - { - if (args.Handled || HasComp(uid) || component.UseVerbInstead) - return; - - if (component.DoPopup) - _popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User); - - StartTimer((uid, component), args.User); - - args.Handled = true; - } - - public static VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png"); -} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs deleted file mode 100644 index 426bade10e..0000000000 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Content.Server.Explosion.Components; -using Content.Shared.Trigger; -using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Events; -using Robust.Shared.Utility; -using Robust.Shared.Timing; - -namespace Content.Server.Explosion.EntitySystems; - -public sealed partial class TriggerSystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - - private void InitializeProximity() - { - SubscribeLocalEvent(OnProximityStartCollide); - SubscribeLocalEvent(OnProximityEndCollide); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnProximityShutdown); - // Shouldn't need re-anchoring. - SubscribeLocalEvent(OnProximityAnchor); - } - - private void OnProximityAnchor(EntityUid uid, TriggerOnProximityComponent component, ref AnchorStateChangedEvent args) - { - component.Enabled = !component.RequiresAnchored || - args.Anchored; - - SetProximityAppearance(uid, component); - - if (!component.Enabled) - { - component.Colliding.Clear(); - } - // Re-check for contacts as we cleared them. - else if (TryComp(uid, out var body)) - { - _broadphase.RegenerateContacts((uid, body)); - } - } - - private void OnProximityShutdown(EntityUid uid, TriggerOnProximityComponent component, ComponentShutdown args) - { - component.Colliding.Clear(); - } - - private void OnMapInit(EntityUid uid, TriggerOnProximityComponent component, MapInitEvent args) - { - component.Enabled = !component.RequiresAnchored || - Transform(uid).Anchored; - - SetProximityAppearance(uid, component); - - if (!TryComp(uid, out var body)) - return; - - _fixtures.TryCreateFixture( - uid, - component.Shape, - TriggerOnProximityComponent.FixtureID, - hard: false, - body: body, - collisionLayer: component.Layer); - } - - private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args) - { - if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID) - return; - - component.Colliding[args.OtherEntity] = args.OtherBody; - } - - private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args) - { - if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID) - return; - - component.Colliding.Remove(args.OtherEntity); - } - - private void SetProximityAppearance(EntityUid uid, TriggerOnProximityComponent component) - { - if (TryComp(uid, out AppearanceComponent? appearance)) - { - _appearance.SetData(uid, ProximityTriggerVisualState.State, component.Enabled ? ProximityTriggerVisuals.Inactive : ProximityTriggerVisuals.Off, appearance); - } - } - - private void Activate(EntityUid uid, EntityUid user, TriggerOnProximityComponent component) - { - DebugTools.Assert(component.Enabled); - - var curTime = _timing.CurTime; - - if (!component.Repeating) - { - component.Enabled = false; - component.Colliding.Clear(); - } - else - { - component.NextTrigger = curTime + component.Cooldown; - } - - // Queue a visual update for when the animation is complete. - component.NextVisualUpdate = curTime + component.AnimationDuration; - - if (TryComp(uid, out AppearanceComponent? appearance)) - { - _appearance.SetData(uid, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active, appearance); - } - - Trigger(uid, user); - } - - private void UpdateProximity() - { - var curTime = _timing.CurTime; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var trigger)) - { - if (curTime >= trigger.NextVisualUpdate) - { - // Update the visual state once the animation is done. - trigger.NextVisualUpdate = TimeSpan.MaxValue; - SetProximityAppearance(uid, trigger); - } - - if (!trigger.Enabled) - continue; - - if (curTime < trigger.NextTrigger) - // The trigger's on cooldown. - continue; - - // Check for anything colliding and moving fast enough. - foreach (var (collidingUid, colliding) in trigger.Colliding) - { - if (Deleted(collidingUid)) - continue; - - if (colliding.LinearVelocity.Length() < trigger.TriggerSpeed) - continue; - - // Trigger! - Activate(uid, collidingUid, trigger); - break; - } - } - } -} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs deleted file mode 100644 index 99e8c97d53..0000000000 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.DeviceLinking.Systems; -using Content.Server.Explosion.Components; -using Content.Shared.DeviceLinking.Events; - -namespace Content.Server.Explosion.EntitySystems -{ - public sealed partial class TriggerSystem - { - [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; - private void InitializeSignal() - { - SubscribeLocalEvent(OnSignalReceived); - SubscribeLocalEvent(OnInit); - - SubscribeLocalEvent(OnTimerSignalReceived); - SubscribeLocalEvent(OnTimerSignalInit); - } - - private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args) - { - if (args.Port != component.Port) - return; - - Trigger(uid, args.Trigger); - } - private void OnInit(EntityUid uid, TriggerOnSignalComponent component, ComponentInit args) - { - _signalSystem.EnsureSinkPorts(uid, component.Port); - } - - private void OnTimerSignalReceived(EntityUid uid, TimerStartOnSignalComponent component, ref SignalReceivedEvent args) - { - if (args.Port != component.Port) - return; - - StartTimer(uid, args.Trigger); - } - private void OnTimerSignalInit(EntityUid uid, TimerStartOnSignalComponent component, ComponentInit args) - { - _signalSystem.EnsureSinkPorts(uid, component.Port); - } - } -} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs deleted file mode 100644 index ea10b7c69b..0000000000 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Linq; -using Content.Server.Explosion.Components; -using Content.Server.Explosion.EntitySystems; -using Robust.Shared.Physics.Dynamics; -using Robust.Shared.Physics.Events; - -namespace Content.Server.Explosion.EntitySystems; - -public sealed partial class TriggerSystem -{ - private void InitializeTimedCollide() - { - SubscribeLocalEvent(OnTimerCollide); - SubscribeLocalEvent(OnTimerEndCollide); - SubscribeLocalEvent(OnComponentRemove); - } - - private void OnTimerCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref StartCollideEvent args) - { - //Ensures the entity trigger will have an active component - EnsureComp(uid); - var otherUID = args.OtherEntity; - if (component.Colliding.ContainsKey(otherUID)) - return; - component.Colliding.Add(otherUID, 0); - } - - private void OnTimerEndCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref EndCollideEvent args) - { - var otherUID = args.OtherEntity; - component.Colliding.Remove(otherUID); - - if (component.Colliding.Count == 0 && HasComp(uid)) - RemComp(uid); - } - - private void OnComponentRemove(EntityUid uid, TriggerOnTimedCollideComponent component, ComponentRemove args) - { - if (HasComp(uid)) - RemComp(uid); - } - - private void UpdateTimedCollide(float frameTime) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide)) - { - foreach (var (collidingEntity, collidingTimer) in triggerOnTimedCollide.Colliding) - { - triggerOnTimedCollide.Colliding[collidingEntity] += frameTime; - if (collidingTimer > triggerOnTimedCollide.Threshold) - { - RaiseLocalEvent(uid, new TriggerEvent(uid, collidingEntity), true); - triggerOnTimedCollide.Colliding[collidingEntity] -= triggerOnTimedCollide.Threshold; - } - } - } - } -} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs deleted file mode 100644 index a96508e37c..0000000000 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Content.Server.Explosion.Components; -using Content.Server.Speech; -using Content.Server.Speech.Components; -using Content.Shared.Database; -using Content.Shared.Examine; -using Content.Shared.Verbs; - -namespace Content.Server.Explosion.EntitySystems -{ - public sealed partial class TriggerSystem - { - private void InitializeVoice() - { - SubscribeLocalEvent(OnVoiceInit); - SubscribeLocalEvent(OnVoiceExamine); - SubscribeLocalEvent>(OnVoiceGetAltVerbs); - SubscribeLocalEvent(OnListen); - } - - private void OnVoiceInit(EntityUid uid, TriggerOnVoiceComponent component, ComponentInit args) - { - if (component.IsListening) - EnsureComp(uid).Range = component.ListenRange; - else - RemCompDeferred(uid); - } - - private void OnListen(Entity ent, ref ListenEvent args) - { - var component = ent.Comp; - var message = args.Message.Trim(); - - if (component.IsRecording) - { - var ev = new ListenAttemptEvent(args.Source); - RaiseLocalEvent(ent, ev); - - if (ev.Cancelled) - return; - - if (message.Length >= component.MinLength && message.Length <= component.MaxLength) - FinishRecording(ent, args.Source, args.Message); - else if (message.Length > component.MaxLength) - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-record-failed-too-long"), ent); - else if (message.Length < component.MinLength) - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-record-failed-too-short"), ent); - - return; - } - - if (!string.IsNullOrWhiteSpace(component.KeyPhrase) && message.IndexOf(component.KeyPhrase, StringComparison.InvariantCultureIgnoreCase) is var index and >= 0 ) - { - _adminLogger.Add(LogType.Trigger, LogImpact.Medium, - $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}."); - Trigger(ent, args.Source); - - var messageWithoutPhrase = message.Remove(index, component.KeyPhrase.Length).Trim(); - - var voice = new VoiceTriggeredEvent(args.Source, message, messageWithoutPhrase); - RaiseLocalEvent(ent, ref voice); - } - } - - private void OnVoiceGetAltVerbs(Entity ent, ref GetVerbsEvent args) - { - if (!args.CanInteract || !args.CanAccess) - return; - - var component = ent.Comp; - - var @event = args; - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString(component.IsRecording ? "verb-trigger-voice-stop" : "verb-trigger-voice-record"), - Act = () => - { - if (component.IsRecording) - StopRecording(ent); - else - StartRecording(ent, @event.User); - }, - Priority = 1 - }); - - if (string.IsNullOrWhiteSpace(component.KeyPhrase)) - return; - - args.Verbs.Add(new AlternativeVerb() - { - Text = Loc.GetString("verb-trigger-voice-clear"), - Act = () => - { - component.KeyPhrase = null; - component.IsRecording = false; - RemComp(ent); - } - }); - } - - public void StartRecording(Entity ent, EntityUid user) - { - var component = ent.Comp; - component.IsRecording = true; - EnsureComp(ent).Range = component.ListenRange; - - _adminLogger.Add(LogType.Trigger, LogImpact.Low, - $"A voice-trigger on {ToPrettyString(ent):entity} has started recording. User: {ToPrettyString(user):user}"); - - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-start-recording"), ent); - } - - public void StopRecording(Entity ent) - { - var component = ent.Comp; - component.IsRecording = false; - if (string.IsNullOrWhiteSpace(component.KeyPhrase)) - RemComp(ent); - - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-stop-recording"), ent); - } - - public void FinishRecording(Entity ent, EntityUid source, string message) - { - var component = ent.Comp; - component.KeyPhrase = message; - component.IsRecording = false; - - _adminLogger.Add(LogType.Trigger, LogImpact.Low, - $"A voice-trigger on {ToPrettyString(ent):entity} has recorded a new keyphrase: '{component.KeyPhrase}'. Recorded from {ToPrettyString(source):speaker}"); - - _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-recorded", ("keyphrase", component.KeyPhrase!)), ent); - } - - private void OnVoiceExamine(EntityUid uid, TriggerOnVoiceComponent component, ExaminedEvent args) - { - if (args.IsInDetailsRange) - { - args.PushText(string.IsNullOrWhiteSpace(component.KeyPhrase) - ? Loc.GetString("trigger-voice-uninitialized") - : Loc.GetString("examine-trigger-voice", ("keyphrase", component.KeyPhrase))); - } - } - } -} - - -/// -/// Raised when a voice trigger is activated, containing the message that triggered it. -/// -/// The EntityUid of the entity sending the message -/// The contents of the message -/// The message without the phrase that triggered it. -[ByRefEvent] -public readonly record struct VoiceTriggeredEvent(EntityUid Source, string Message, string MessageWithoutPhrase); diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs deleted file mode 100644 index f3a3f0c87c..0000000000 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ /dev/null @@ -1,454 +0,0 @@ -using Content.Server.Administration.Logs; -using Content.Server.Body.Systems; -using Content.Server.Explosion.Components; -using Content.Shared.Flash; -using Content.Server.Electrocution; -using Content.Server.Pinpointer; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Flash.Components; -using Content.Server.Radio.EntitySystems; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Database; -using Content.Shared.Explosion.Components; -using Content.Shared.Explosion.Components.OnTrigger; -using Content.Shared.Implants.Components; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Inventory; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; -using Content.Shared.Payload.Components; -using Content.Shared.Radio; -using Content.Shared.Slippery; -using Content.Shared.StepTrigger.Systems; -using Content.Shared.Trigger; -using Content.Shared.Weapons.Ranged.Events; -using Content.Shared.Whitelist; -using JetBrains.Annotations; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; -using Robust.Shared.Physics.Events; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.Explosion.EntitySystems -{ - /// - /// Raised whenever something is Triggered on the entity. - /// - public sealed class TriggerEvent : HandledEntityEventArgs - { - public EntityUid Triggered { get; } - public EntityUid? User { get; } - - public TriggerEvent(EntityUid triggered, EntityUid? user = null) - { - Triggered = triggered; - User = user; - } - } - - /// - /// Raised before a trigger is activated. - /// - [ByRefEvent] - public record struct BeforeTriggerEvent(EntityUid Triggered, EntityUid? User, bool Cancelled = false); - - /// - /// Raised when timer trigger becomes active. - /// - [ByRefEvent] - public readonly record struct ActiveTimerTriggerEvent(EntityUid Triggered, EntityUid? User); - - [UsedImplicitly] - public sealed partial class TriggerSystem : EntitySystem - { - [Dependency] private readonly ExplosionSystem _explosions = default!; - [Dependency] private readonly FixtureSystem _fixtures = default!; - [Dependency] private readonly SharedFlashSystem _flashSystem = default!; - [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly BodySystem _body = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - [Dependency] private readonly NavMapSystem _navMap = default!; - [Dependency] private readonly RadioSystem _radioSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly ElectrocutionSystem _electrocution = default!; - [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; - - public override void Initialize() - { - base.Initialize(); - - InitializeProximity(); - InitializeOnUse(); - InitializeSignal(); - InitializeTimedCollide(); - InitializeVoice(); - InitializeMobstate(); - - SubscribeLocalEvent(OnSpawnTriggered); - SubscribeLocalEvent(OnTriggerCollide); - SubscribeLocalEvent(OnActivate); - SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(OnImplantTrigger); - SubscribeLocalEvent(OnStepTriggered); - SubscribeLocalEvent(OnSlipTriggered); - SubscribeLocalEvent(OnEmptyTriggered); - SubscribeLocalEvent(OnRepeatInit); - - SubscribeLocalEvent(OnSpawnTrigger); - SubscribeLocalEvent(HandleDeleteTrigger); - SubscribeLocalEvent(HandleExplodeTrigger); - SubscribeLocalEvent(HandleFlashTrigger); - SubscribeLocalEvent(HandleGibTrigger); - - SubscribeLocalEvent(OnAnchorTrigger); - SubscribeLocalEvent(OnSoundTrigger); - SubscribeLocalEvent(HandleShockTrigger); - SubscribeLocalEvent(HandleRattleTrigger); - - SubscribeLocalEvent(HandleWhitelist); - } - - private void HandleWhitelist(Entity ent, ref BeforeTriggerEvent args) - { - args.Cancelled = !_whitelist.CheckBoth(args.User, ent.Comp.Blacklist, ent.Comp.Whitelist); - } - - private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, TriggerEvent args) - { - if (component.RemoveOnTrigger) // if the component gets removed when it's triggered - { - var xform = Transform(uid); - _audio.PlayPvs(component.Sound, xform.Coordinates); // play the sound at its last known coordinates - } - else // if the component doesn't get removed when triggered - { - _audio.PlayPvs(component.Sound, uid); // have the sound follow the entity itself - } - } - - private void HandleShockTrigger(Entity shockOnTrigger, ref TriggerEvent args) - { - if (!_container.TryGetContainingContainer(shockOnTrigger.Owner, out var container)) - return; - - var containerEnt = container.Owner; - var curTime = _timing.CurTime; - - if (curTime < shockOnTrigger.Comp.NextTrigger) - { - // The trigger's on cooldown. - return; - } - - _electrocution.TryDoElectrocution(containerEnt, null, shockOnTrigger.Comp.Damage, shockOnTrigger.Comp.Duration, true, ignoreInsulation: true); - shockOnTrigger.Comp.NextTrigger = curTime + shockOnTrigger.Comp.Cooldown; - } - - private void OnAnchorTrigger(EntityUid uid, AnchorOnTriggerComponent component, TriggerEvent args) - { - var xform = Transform(uid); - - if (xform.Anchored) - return; - - _transformSystem.AnchorEntity(uid, xform); - - if (component.RemoveOnTrigger) - RemCompDeferred(uid); - } - - private void OnSpawnTrigger(Entity ent, ref TriggerEvent args) - { - var xform = Transform(ent); - - if (ent.Comp.mapCoords) - { - var mapCoords = _transformSystem.GetMapCoordinates(ent, xform); - Spawn(ent.Comp.Proto, mapCoords); - } - else - { - var coords = xform.Coordinates; - if (!coords.IsValid(EntityManager)) - return; - Spawn(ent.Comp.Proto, coords); - - } - } - - private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent component, TriggerEvent args) - { - _explosions.TriggerExplosive(uid, user: args.User); - args.Handled = true; - } - - private void HandleFlashTrigger(EntityUid uid, FlashOnTriggerComponent component, TriggerEvent args) - { - _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration, probability: component.Probability); - args.Handled = true; - } - - private void HandleDeleteTrigger(EntityUid uid, DeleteOnTriggerComponent component, TriggerEvent args) - { - QueueDel(uid); - args.Handled = true; - } - - private void HandleGibTrigger(EntityUid uid, GibOnTriggerComponent component, TriggerEvent args) - { - if (!TryComp(uid, out TransformComponent? xform)) - return; - if (component.DeleteItems) - { - var items = _inventory.GetHandOrInventoryEntities(xform.ParentUid); - foreach (var item in items) - { - Del(item); - } - } - _body.GibBody(xform.ParentUid, true); - args.Handled = true; - } - - - private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args) - { - if (!TryComp(uid, out var implanted)) - return; - - if (implanted.ImplantedEntity == null) - return; - - // Gets location of the implant - var posText = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString(uid)); - var critMessage = Loc.GetString(component.CritMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText)); - var deathMessage = Loc.GetString(component.DeathMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText)); - - if (!TryComp(implanted.ImplantedEntity, out var mobstate)) - return; - - // Sends a message to the radio channel specified by the implant - if (mobstate.CurrentState == MobState.Critical) - _radioSystem.SendRadioMessage(uid, critMessage, _prototypeManager.Index(component.RadioChannel), uid); - if (mobstate.CurrentState == MobState.Dead) - _radioSystem.SendRadioMessage(uid, deathMessage, _prototypeManager.Index(component.RadioChannel), uid); - - args.Handled = true; - } - - private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args) - { - if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard)) - Trigger(uid, args.OtherEntity); - } - - private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args) - { - Trigger(uid); - } - - private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args) - { - if (args.Handled || !args.Complex) - return; - - Trigger(uid, args.User); - args.Handled = true; - } - - private void OnUse(Entity ent, ref UseInHandEvent args) - { - if (args.Handled) - return; - - Trigger(ent.Owner, args.User); - args.Handled = true; - } - - private void OnImplantTrigger(EntityUid uid, TriggerImplantActionComponent component, ActivateImplantEvent args) - { - args.Handled = Trigger(uid); - } - - private void OnStepTriggered(EntityUid uid, TriggerOnStepTriggerComponent component, ref StepTriggeredOffEvent args) - { - Trigger(uid, args.Tripper); - } - - private void OnSlipTriggered(EntityUid uid, TriggerOnSlipComponent component, ref SlipEvent args) - { - Trigger(uid, args.Slipped); - } - - private void OnEmptyTriggered(EntityUid uid, TriggerWhenEmptyComponent component, ref OnEmptyGunShotEvent args) - { - Trigger(uid, args.EmptyGun); - } - - private void OnRepeatInit(Entity ent, ref MapInitEvent args) - { - ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay; - } - - public bool Trigger(EntityUid trigger, EntityUid? user = null) - { - var beforeTriggerEvent = new BeforeTriggerEvent(trigger, user); - RaiseLocalEvent(trigger, ref beforeTriggerEvent); - if (beforeTriggerEvent.Cancelled) - return false; - - var triggerEvent = new TriggerEvent(trigger, user); - EntityManager.EventBus.RaiseLocalEvent(trigger, triggerEvent, true); - return triggerEvent.Handled; - } - - public void TryDelay(EntityUid uid, float amount, ActiveTimerTriggerComponent? comp = null) - { - if (!Resolve(uid, ref comp, false)) - return; - - comp.TimeRemaining += amount; - } - - /// - /// Start the timer for triggering the device. - /// - public void StartTimer(Entity ent, EntityUid? user) - { - if (!Resolve(ent, ref ent.Comp, false)) - return; - - var comp = ent.Comp; - HandleTimerTrigger(ent, user, comp.Delay, comp.BeepInterval, comp.InitialBeepDelay, comp.BeepSound); - } - - public void HandleTimerTrigger(EntityUid uid, EntityUid? user, float delay, float beepInterval, float? initialBeepDelay, SoundSpecifier? beepSound) - { - if (delay <= 0) - { - RemComp(uid); - Trigger(uid, user); - return; - } - - if (HasComp(uid)) - return; - - if (user != null) - { - // Check if entity is bomb/mod. grenade/etc - if (_container.TryGetContainer(uid, "payload", out BaseContainer? container) && - container.ContainedEntities.Count > 0 && - TryComp(container.ContainedEntities[0], out ChemicalPayloadComponent? chemicalPayloadComponent)) - { - // If a beaker is missing, the entity won't explode, so no reason to log it - if (chemicalPayloadComponent?.BeakerSlotA.Item is not { } beakerA || - chemicalPayloadComponent?.BeakerSlotB.Item is not { } beakerB || - !TryComp(beakerA, out SolutionContainerManagerComponent? containerA) || - !TryComp(beakerB, out SolutionContainerManagerComponent? containerB) || - !TryComp(beakerA, out FitsInDispenserComponent? fitsA) || - !TryComp(beakerB, out FitsInDispenserComponent? fitsB) || - !_solutionContainerSystem.TryGetSolution((beakerA, containerA), fitsA.Solution, out _, out var solutionA) || - !_solutionContainerSystem.TryGetSolution((beakerB, containerB), fitsB.Solution, out _, out var solutionB)) - return; - - _adminLogger.Add(LogType.Trigger, - $"{ToPrettyString(user.Value):user} started a {delay} second timer trigger on entity {ToPrettyString(uid):timer}, which contains {SharedSolutionContainerSystem.ToPrettyString(solutionA)} in one beaker and {SharedSolutionContainerSystem.ToPrettyString(solutionB)} in the other."); - } - else - { - _adminLogger.Add(LogType.Trigger, - $"{ToPrettyString(user.Value):user} started a {delay} second timer trigger on entity {ToPrettyString(uid):timer}"); - } - - } - else - { - _adminLogger.Add(LogType.Trigger, - $"{delay} second timer trigger started on entity {ToPrettyString(uid):timer}"); - } - - var active = AddComp(uid); - active.TimeRemaining = delay; - active.User = user; - active.BeepSound = beepSound; - active.BeepInterval = beepInterval; - active.TimeUntilBeep = initialBeepDelay == null ? active.BeepInterval : initialBeepDelay.Value; - - var ev = new ActiveTimerTriggerEvent(uid, user); - RaiseLocalEvent(uid, ref ev); - - if (TryComp(uid, out var appearance)) - _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - UpdateProximity(); - UpdateTimer(frameTime); - UpdateTimedCollide(frameTime); - UpdateRepeat(); - } - - private void UpdateTimer(float frameTime) - { - HashSet toRemove = new(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var timer)) - { - timer.TimeRemaining -= frameTime; - timer.TimeUntilBeep -= frameTime; - - if (timer.TimeRemaining <= 0) - { - Trigger(uid, timer.User); - toRemove.Add(uid); - continue; - } - - if (timer.BeepSound == null || timer.TimeUntilBeep > 0) - continue; - - timer.TimeUntilBeep += timer.BeepInterval; - _audio.PlayPvs(timer.BeepSound, uid, timer.BeepSound.Params); - } - - foreach (var uid in toRemove) - { - RemComp(uid); - - // In case this is a re-usable grenade, un-prime it. - if (TryComp(uid, out var appearance)) - _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance); - } - } - - private void UpdateRepeat() - { - var now = _timing.CurTime; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) - { - if (comp.NextTrigger > now) - continue; - - comp.NextTrigger = now + comp.Delay; - Trigger(uid); - } - } - } -} diff --git a/Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs deleted file mode 100644 index 8488fd14bb..0000000000 --- a/Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Robust.Shared.Timing; -using Robust.Shared.Serialization.Manager; -using Content.Server.Explosion.Components.OnTrigger; - -namespace Content.Server.Explosion.EntitySystems; - -public sealed class TwoStageTriggerSystem : EntitySystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly ISerializationManager _serializationManager = default!; - [Dependency] private readonly TriggerSystem _triggerSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnTrigger); - } - - private void OnTrigger(EntityUid uid, TwoStageTriggerComponent component, TriggerEvent args) - { - if (component.Triggered) - return; - - component.Triggered = true; - component.NextTriggerTime = _timing.CurTime + component.TriggerDelay; - } - - private void LoadComponents(EntityUid uid, TwoStageTriggerComponent component) - { - foreach (var (name, entry) in component.SecondStageComponents) - { - var comp = (Component) Factory.GetComponent(name); - var temp = (object)comp; - - if (EntityManager.TryGetComponent(uid, entry.Component.GetType(), out var c)) - RemComp(uid, c); - - _serializationManager.CopyTo(entry.Component, ref temp); - AddComp(uid, comp); - } - component.ComponentsIsLoaded = true; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var enumerator = EntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var component)) - { - if (!component.Triggered) - continue; - - if (!component.ComponentsIsLoaded) - LoadComponents(uid, component); - - if (_timing.CurTime < component.NextTriggerTime) - continue; - - component.NextTriggerTime = null; - _triggerSystem.Trigger(uid); - } - } -} diff --git a/Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs b/Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs deleted file mode 100644 index 11fb0156a4..0000000000 --- a/Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Server.GhostKick; - -[RegisterComponent] -public sealed partial class GhostKickUserOnTriggerComponent : Component -{ - -} diff --git a/Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs b/Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs deleted file mode 100644 index 2a5d9065fa..0000000000 --- a/Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Robust.Shared.Player; - -namespace Content.Server.GhostKick; - -public sealed class GhostKickUserOnTriggerSystem : EntitySystem -{ - [Dependency] private readonly GhostKickManager _ghostKickManager = default!; - - public override void Initialize() - { - SubscribeLocalEvent(HandleMineTriggered); - } - - private void HandleMineTriggered(EntityUid uid, GhostKickUserOnTriggerComponent userOnTriggerComponent, TriggerEvent args) - { - if (!TryComp(args.User, out ActorComponent? actor)) - return; - - _ghostKickManager.DoDisconnect( - actor.PlayerSession.Channel, - "Tripped over a kick mine, crashed through the fourth wall"); - - args.Handled = true; - } -} diff --git a/Content.Server/Holopad/HolopadSystem.cs b/Content.Server/Holopad/HolopadSystem.cs index f2bd0e05ad..884fb3ae71 100644 --- a/Content.Server/Holopad/HolopadSystem.cs +++ b/Content.Server/Holopad/HolopadSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Chat.Systems; using Content.Server.Popups; using Content.Server.Power.EntitySystems; -using Content.Server.Speech.Components; using Content.Server.Telephone; using Content.Shared.Access.Systems; using Content.Shared.Audio; @@ -12,6 +11,7 @@ using Content.Shared.Labels.Components; using Content.Shared.Power; using Content.Shared.Silicons.StationAi; using Content.Shared.Speech; +using Content.Shared.Speech.Components; using Content.Shared.Telephone; using Content.Shared.UserInterface; using Content.Shared.Verbs; @@ -560,7 +560,7 @@ public sealed class HolopadSystem : SharedHolopadSystem entity.Comp.User = (user.Value, holopadUser); } - // Add the new user to PVS and sync their appearance with any + // Add the new user to PVS and sync their appearance with any // holopads connected to the one they are using _pvs.AddGlobalOverride(user.Value); SyncHolopadHologramAppearanceWithTarget(entity, entity.Comp.User); diff --git a/Content.Server/HotPotato/HotPotatoSystem.cs b/Content.Server/HotPotato/HotPotatoSystem.cs index 8ca33fb8cd..554fe098b0 100644 --- a/Content.Server/HotPotato/HotPotatoSystem.cs +++ b/Content.Server/HotPotato/HotPotatoSystem.cs @@ -1,61 +1,5 @@ -using Content.Server.Audio; -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Damage.Systems; -using Content.Shared.Hands.Components; -using Content.Shared.Hands.EntitySystems; using Content.Shared.HotPotato; -using Content.Shared.Popups; -using Content.Shared.Weapons.Melee.Events; namespace Content.Server.HotPotato; -public sealed class HotPotatoSystem : SharedHotPotatoSystem -{ - [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly AmbientSoundSystem _ambientSound = default!; - [Dependency] private readonly DamageOnHoldingSystem _damageOnHolding = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnActiveTimer); - SubscribeLocalEvent(OnMeleeHit); - } - - private void OnActiveTimer(EntityUid uid, HotPotatoComponent comp, ref ActiveTimerTriggerEvent args) - { - EnsureComp(uid); - comp.CanTransfer = false; - _ambientSound.SetAmbience(uid, true); - _damageOnHolding.SetEnabled(uid, true); - Dirty(uid, comp); - } - - private void OnMeleeHit(EntityUid uid, HotPotatoComponent comp, MeleeHitEvent args) - { - if (!HasComp(uid)) - return; - - comp.CanTransfer = true; - foreach (var hitEntity in args.HitEntities) - { - if (!TryComp(hitEntity, out var hands)) - continue; - - if (!_hands.IsHolding((hitEntity, hands), uid, out _) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands)) - { - _popup.PopupEntity(Loc.GetString("hot-potato-passed", - ("from", args.User), ("to", hitEntity)), uid, PopupType.Medium); - break; - } - - _popup.PopupEntity(Loc.GetString("hot-potato-failed", - ("to", hitEntity)), uid, PopupType.Medium); - - break; - } - comp.CanTransfer = false; - Dirty(uid, comp); - } -} +public sealed class HotPotatoSystem : SharedHotPotatoSystem; diff --git a/Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs b/Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs deleted file mode 100644 index 939198c45e..0000000000 --- a/Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Server.IgnitionSource; - -/// -/// Ignites for a certain length of time when triggered. -/// Requires along with triggering components. -/// -[RegisterComponent, Access(typeof(IgniteOnTriggerSystem))] -public sealed partial class IgniteOnTriggerComponent : Component -{ - /// - /// Once ignited, the time it will unignite at. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan IgnitedUntil = TimeSpan.Zero; - - /// - /// How long the ignition source is active for after triggering. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5); - - /// - /// Sound to play when igniting. - /// - [DataField] - public SoundSpecifier IgniteSound = new SoundCollectionSpecifier("WelderOn"); -} diff --git a/Content.Server/LandMines/LandMineSystem.cs b/Content.Server/LandMines/LandMineSystem.cs index 57d25ef8ab..fdea8e9c65 100644 --- a/Content.Server/LandMines/LandMineSystem.cs +++ b/Content.Server/LandMines/LandMineSystem.cs @@ -1,9 +1,9 @@ -using Content.Server.Explosion.EntitySystems; using Content.Shared.Armable; using Content.Shared.Item.ItemToggle.Components; using Content.Shared.LandMines; using Content.Shared.Popups; using Content.Shared.StepTrigger.Systems; +using Content.Shared.Trigger.Systems; using Robust.Shared.Audio.Systems; namespace Content.Server.LandMines; @@ -28,15 +28,15 @@ public sealed class LandMineSystem : EntitySystem /// private void HandleStepOnTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOnEvent args) { - if (!string.IsNullOrEmpty(component.TriggerText)) - { - _popupSystem.PopupCoordinates( - Loc.GetString(component.TriggerText, ("mine", uid)), - Transform(uid).Coordinates, - args.Tripper, - PopupType.LargeCaution); - } - _audioSystem.PlayPvs(component.Sound, uid); + if (!string.IsNullOrEmpty(component.TriggerText)) + { + _popupSystem.PopupCoordinates( + Loc.GetString(component.TriggerText, ("mine", uid)), + Transform(uid).Coordinates, + args.Tripper, + PopupType.LargeCaution); + } + _audioSystem.PlayPvs(component.Sound, uid); } /// @@ -44,7 +44,8 @@ public sealed class LandMineSystem : EntitySystem /// private void HandleStepOffTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOffEvent args) { - _trigger.Trigger(uid, args.Tripper); + // TODO: Adjust to the new trigger system + _trigger.Trigger(uid, args.Tripper, TriggerSystem.DefaultTriggerKey); } /// diff --git a/Content.Server/Mousetrap/MousetrapSystem.cs b/Content.Server/Mousetrap/MousetrapSystem.cs deleted file mode 100644 index 3afe858ce0..0000000000 --- a/Content.Server/Mousetrap/MousetrapSystem.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Content.Server.Damage.Systems; -using Content.Server.Explosion.EntitySystems; -using Content.Server.Popups; -using Content.Shared.Interaction.Events; -using Content.Shared.Inventory; -using Content.Shared.Mousetrap; -using Content.Shared.StepTrigger; -using Content.Shared.StepTrigger.Systems; -using Robust.Server.GameObjects; -using Robust.Shared.Physics.Components; -using Robust.Shared.Player; - -namespace Content.Server.Mousetrap; - -public sealed class MousetrapSystem : EntitySystem -{ - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - - public override void Initialize() - { - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(BeforeDamageOnTrigger); - SubscribeLocalEvent(OnStepTriggerAttempt); - SubscribeLocalEvent(OnTrigger); - } - - private void OnUseInHand(EntityUid uid, MousetrapComponent component, UseInHandEvent args) - { - if (args.Handled) - return; - - component.IsActive = !component.IsActive; - _popupSystem.PopupEntity(component.IsActive - ? Loc.GetString("mousetrap-on-activate") - : Loc.GetString("mousetrap-on-deactivate"), - uid, - args.User); - - UpdateVisuals(uid); - - args.Handled = true; - } - - private void OnStepTriggerAttempt(EntityUid uid, MousetrapComponent component, ref StepTriggerAttemptEvent args) - { - args.Continue |= component.IsActive; - } - - private void BeforeDamageOnTrigger(EntityUid uid, MousetrapComponent component, BeforeDamageUserOnTriggerEvent args) - { - if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0) - { - // The idea here is inverse, - // Small - big damage, - // Large - small damage - // yes i punched numbers into a calculator until the graph looked right - var scaledDamage = -50 * Math.Atan(physics.Mass - component.MassBalance) + (25 * Math.PI); - args.Damage *= scaledDamage; - } - } - - private void OnTrigger(EntityUid uid, MousetrapComponent component, TriggerEvent args) - { - component.IsActive = false; - UpdateVisuals(uid); - } - - private void UpdateVisuals(EntityUid uid, MousetrapComponent? mousetrap = null, AppearanceComponent? appearance = null) - { - if (!Resolve(uid, ref mousetrap, ref appearance, false)) - { - return; - } - - _appearance.SetData(uid, MousetrapVisuals.Visual, - mousetrap.IsActive ? MousetrapVisuals.Armed : MousetrapVisuals.Unarmed, appearance); - } -} diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs index 6594d7883b..c08576a5ce 100644 --- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs +++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Explosion.EntitySystems; using Content.Server.Mind; using Content.Server.Objectives.Components; using Content.Server.Popups; @@ -7,6 +6,7 @@ using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; using Content.Shared.Roles; using Content.Shared.Sticky; +using Content.Shared.Trigger; namespace Content.Server.Ninja.Systems; @@ -80,6 +80,9 @@ public sealed class SpiderChargeSystem : SharedSpiderChargeSystem /// private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args) { + if (args.Key != comp.TriggerKey) + return; + if (!TryComp(comp.Planter, out var ninja)) return; diff --git a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs index 5c0aca1578..e85393e6f7 100644 --- a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs @@ -1,15 +1,15 @@ -using Content.Server.Explosion.EntitySystems; using Content.Server.Fluids.EntitySystems; using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Shared.Containers.ItemSlots; -using Content.Shared.Explosion.Components; using Content.Shared.IdentityManagement; using Content.Shared.Nutrition; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Rejuvenate; using Content.Shared.Throwing; +using Content.Shared.Trigger.Components; +using Content.Shared.Trigger.Systems; using Content.Shared.Chemistry.EntitySystems; using JetBrains.Annotations; using Robust.Shared.Audio; @@ -77,15 +77,9 @@ namespace Content.Server.Nutrition.EntitySystems { if (_itemSlots.TryEject(uid, itemSlot, user: null, out var item)) { - if (TryComp(item.Value, out var timerTrigger)) + if (TryComp(item.Value, out var timerTrigger)) { - _trigger.HandleTimerTrigger( - item.Value, - null, - timerTrigger.Delay, - timerTrigger.BeepInterval, - timerTrigger.InitialBeepDelay, - timerTrigger.BeepSound); + _trigger.ActivateTimerTrigger((item.Value, timerTrigger)); } } } diff --git a/Content.Server/Payload/EntitySystems/PayloadSystem.cs b/Content.Server/Payload/EntitySystems/PayloadSystem.cs index 18444bc590..11e97c5b93 100644 --- a/Content.Server/Payload/EntitySystems/PayloadSystem.cs +++ b/Content.Server/Payload/EntitySystems/PayloadSystem.cs @@ -1,10 +1,10 @@ using Content.Server.Administration.Logs; -using Content.Server.Explosion.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Payload.Components; using Content.Shared.Tag; +using Content.Shared.Trigger; using Content.Shared.Chemistry.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.Serialization.Manager; @@ -54,18 +54,22 @@ public sealed class PayloadSystem : EntitySystem private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args) { + // TODO: Adjust to the new trigger system + if (!TryComp(uid, out ContainerManagerComponent? contMan)) return; // Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs. foreach (var ent in GetAllPayloads(uid, contMan)) { - RaiseLocalEvent(ent, args, false); + RaiseLocalEvent(ent, ref args, false); } } private void OnTriggerTriggered(EntityUid uid, PayloadTriggerComponent component, TriggerEvent args) { + // TODO: Adjust to the new trigger system + if (!component.Active) return; @@ -75,7 +79,7 @@ public sealed class PayloadSystem : EntitySystem // Ensure we don't enter a trigger-loop DebugTools.Assert(!_tagSystem.HasTag(uid, PayloadTag)); - RaiseLocalEvent(parent, args, false); + RaiseLocalEvent(parent, ref args); } private void OnEntityInserted(EntityUid uid, PayloadCaseComponent _, EntInsertedIntoContainerMessage args) @@ -146,6 +150,7 @@ public sealed class PayloadSystem : EntitySystem private void HandleChemicalPayloadTrigger(Entity entity, ref TriggerEvent args) { + // TODO: Adjust to the new trigger system if (entity.Comp.BeakerSlotA.Item is not EntityUid beakerA || entity.Comp.BeakerSlotB.Item is not EntityUid beakerB || !TryComp(beakerA, out FitsInDispenserComponent? compA) diff --git a/Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs b/Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs deleted file mode 100644 index a11b4f4d4c..0000000000 --- a/Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Polymorph; -using Robust.Shared.Prototypes; - -namespace Content.Server.Polymorph.Components; - -/// -/// Intended for use with the trigger system. -/// Polymorphs the user of the trigger. -/// -[RegisterComponent] -public sealed partial class PolymorphOnTriggerComponent : Component -{ - /// - /// Polymorph settings. - /// - [DataField(required: true)] - public ProtoId Polymorph; -} diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs deleted file mode 100644 index 452b060315..0000000000 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Shared.Polymorph; -using Content.Server.Polymorph.Components; -using Content.Server.Explosion.EntitySystems; -using Robust.Shared.Prototypes; - -namespace Content.Server.Polymorph.Systems; - -public sealed partial class PolymorphSystem -{ - /// - /// Need to do this so we don't get a collection enumeration error in physics by polymorphing - /// an entity we're colliding with in case of TriggerOnCollide. - /// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed. - /// - private Queue<(EntityUid Ent, ProtoId Polymorph)> _queuedPolymorphUpdates = new(); - - private void InitializeTrigger() - { - SubscribeLocalEvent(OnTrigger); - } - - private void OnTrigger(Entity ent, ref TriggerEvent args) - { - if (args.User == null) - return; - - _queuedPolymorphUpdates.Enqueue((args.User.Value, ent.Comp.Polymorph)); - args.Handled = true; - } - - public void UpdateTrigger() - { - while (_queuedPolymorphUpdates.TryDequeue(out var data)) - { - if (TerminatingOrDeleted(data.Item1)) - continue; - - PolymorphEntity(data.Item1, data.Item2); - } - } -} diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index 696b19199e..d411eb7722 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -58,7 +58,6 @@ public sealed partial class PolymorphSystem : EntitySystem SubscribeLocalEvent(OnDestruction); InitializeMap(); - InitializeTrigger(); } public override void Update(float frameTime) @@ -85,8 +84,6 @@ public sealed partial class PolymorphSystem : EntitySystem Revert((uid, comp)); } } - - UpdateTrigger(); } private void OnComponentStartup(Entity ent, ref ComponentStartup args) diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 3829fc34d2..f052c460f5 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -2,15 +2,14 @@ using System.Linq; using Content.Server.Chat.Systems; using Content.Server.Interaction; using Content.Server.Popups; -using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Radio.Components; -using Content.Server.Speech; -using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Power; using Content.Shared.Radio; +using Content.Shared.Speech; +using Content.Shared.Speech.Components; using Content.Shared.Chat; using Content.Shared.Radio.Components; using Robust.Shared.Prototypes; diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs index e950d3f288..debed525f6 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs @@ -119,7 +119,7 @@ public sealed partial class BorgSystem var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent))); Popup.PopupEntity(message, ent); - _trigger.StartTimer(ent.Owner, user: null); + _trigger.ActivateTimerTrigger(ent.Owner); // prevent a shitter borg running into people RemComp(ent); diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 0cd407000f..f33b71c54e 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Actions; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.DeviceNetwork.Systems; -using Content.Server.Explosion.EntitySystems; using Content.Server.Hands.Systems; using Content.Server.PowerCell; using Content.Shared.Alert; @@ -25,6 +24,7 @@ using Content.Shared.Roles; using Content.Shared.Silicons.Borgs; using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Throwing; +using Content.Shared.Trigger.Systems; using Content.Shared.Whitelist; using Content.Shared.Wires; using Robust.Server.GameObjects; diff --git a/Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs b/Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs deleted file mode 100644 index 5338351948..0000000000 --- a/Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Sound.Components; - -namespace Content.Server.Sound.Components -{ - /// - /// Whenever a is run play a sound in PVS range. - /// - [RegisterComponent] - public sealed partial class EmitSoundOnTriggerComponent : BaseEmitSoundComponent - { - } -} diff --git a/Content.Server/Sound/EmitSoundSystem.cs b/Content.Server/Sound/EmitSoundSystem.cs index 9d7e8496c3..1720d67d02 100644 --- a/Content.Server/Sound/EmitSoundSystem.cs +++ b/Content.Server/Sound/EmitSoundSystem.cs @@ -1,6 +1,3 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Server.Sound.Components; -using Content.Shared.UserInterface; using Content.Shared.Sound; using Content.Shared.Sound.Components; using Robust.Shared.Timing; @@ -38,16 +35,9 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem { base.Initialize(); - SubscribeLocalEvent(HandleEmitSoundOnTrigger); SubscribeLocalEvent(HandleSpamEmitSoundMapInit); } - private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args) - { - TryEmitSound(uid, component, args.User, false); - args.Handled = true; - } - private void HandleSpamEmitSoundMapInit(Entity entity, ref MapInitEvent args) { SpamEmitSoundReset(entity); diff --git a/Content.Server/Speech/Components/ActiveListenerComponent.cs b/Content.Server/Speech/Components/ActiveListenerComponent.cs deleted file mode 100644 index 4553fafa51..0000000000 --- a/Content.Server/Speech/Components/ActiveListenerComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Server.Chat.Systems; - -namespace Content.Server.Speech.Components; - -/// -/// This component is used to relay speech events to other systems. -/// -[RegisterComponent] -public sealed partial class ActiveListenerComponent : Component -{ - [DataField("range")] - public float Range = ChatSystem.VoiceRange; -} diff --git a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs index a13eda1f73..6b09877512 100644 --- a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs +++ b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Speech.Components; +using Content.Shared.Speech; namespace Content.Server.Speech.EntitySystems; diff --git a/Content.Server/Speech/EntitySystems/ListeningSystem.cs b/Content.Server/Speech/EntitySystems/ListeningSystem.cs index ea3569e055..17513d80e7 100644 --- a/Content.Server/Speech/EntitySystems/ListeningSystem.cs +++ b/Content.Server/Speech/EntitySystems/ListeningSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; -using Content.Server.Speech.Components; +using Content.Shared.Speech; +using Content.Shared.Speech.Components; namespace Content.Server.Speech.EntitySystems; diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs index 4deb5238a5..4029488159 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Chat.Systems; -using Content.Server.Speech; -using Content.Server.Speech.Components; +using Content.Shared.Speech; +using Content.Shared.Speech.Components; using Content.Shared.Whitelist; using Robust.Shared.Player; using static Content.Server.Chat.Systems.ChatSystem; diff --git a/Content.Server/Telephone/TelephoneSystem.cs b/Content.Server/Telephone/TelephoneSystem.cs index 4c87707cc6..46f45d1286 100644 --- a/Content.Server/Telephone/TelephoneSystem.cs +++ b/Content.Server/Telephone/TelephoneSystem.cs @@ -3,8 +3,6 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Server.Interaction; using Content.Server.Power.EntitySystems; -using Content.Server.Speech; -using Content.Server.Speech.Components; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Labels.Components; @@ -13,6 +11,7 @@ using Content.Shared.Power; using Content.Shared.Silicons.StationAi; using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Speech; +using Content.Shared.Speech.Components; using Content.Shared.Telephone; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs b/Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs similarity index 74% rename from Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs rename to Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs index 0c9734b943..1a3e49dd44 100644 --- a/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs @@ -1,8 +1,9 @@ using Content.Server.AlertLevel; -using Content.Server.Explosion.EntitySystems; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; using Content.Server.Station.Systems; -namespace Content.Server.AlertLevel.Systems; +namespace Content.Server.Trigger.Systems; public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem { @@ -18,10 +19,14 @@ public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem private void OnTrigger(Entity ent, ref TriggerEvent args) { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + var stationUid = _station.GetOwningStation(ent.Owner); - if (!stationUid.HasValue) + if (stationUid == null) return; _alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force); + args.Handled = true; } } diff --git a/Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs b/Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs new file mode 100644 index 0000000000..0fae6ff1c5 --- /dev/null +++ b/Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs @@ -0,0 +1,38 @@ +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; +using Content.Server.GhostKick; +using Robust.Shared.Player; + +namespace Content.Server.Trigger.Systems; + +public sealed class GhostKickUserOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly GhostKickManager _ghostKickManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + if (!TryComp(target, out ActorComponent? actor)) + return; + + _ghostKickManager.DoDisconnect( + actor.PlayerSession.Channel, + Loc.GetString(ent.Comp.Reason)); + + args.Handled = true; + } +} diff --git a/Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs similarity index 66% rename from Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs rename to Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs index 0e9dd56622..f4d88b774a 100644 --- a/Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs @@ -1,10 +1,9 @@ -using Content.Server.Explosion.EntitySystems; using Content.Shared.IgnitionSource; -using Content.Shared.Timing; -using Robust.Shared.Audio.Systems; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; using Robust.Shared.Timing; -namespace Content.Server.IgnitionSource; +namespace Content.Server.Trigger.Systems; /// /// Handles igniting when triggered and stopping ignition after the delay. @@ -13,8 +12,6 @@ public sealed class IgniteOnTriggerSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedIgnitionSourceSystem _source = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly UseDelaySystem _useDelay = default!; public override void Initialize() { @@ -23,6 +20,8 @@ public sealed class IgniteOnTriggerSystem : EntitySystem SubscribeLocalEvent(OnTrigger); } + // TODO: move this into ignition source component + // it already has an update loop public override void Update(float deltaTime) { base.Update(deltaTime); @@ -42,14 +41,18 @@ public sealed class IgniteOnTriggerSystem : EntitySystem private void OnTrigger(Entity ent, ref TriggerEvent args) { - // prevent spamming sound and ignition - if (!TryComp(ent.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((ent.Owner, useDelay))) + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) return; - _source.SetIgnited(ent.Owner); - _audio.PlayPvs(ent.Comp.IgniteSound, ent); + var target = ent.Comp.TargetUser ? args.User : ent.Owner; - _useDelay.TryResetDelay((ent.Owner, useDelay)); + if (target == null) + return; + + _source.SetIgnited(target.Value); ent.Comp.IgnitedUntil = _timing.CurTime + ent.Comp.IgnitedTime; + Dirty(ent); + + args.Handled = true; } } diff --git a/Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs b/Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs new file mode 100644 index 0000000000..8b4741b9ad --- /dev/null +++ b/Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs @@ -0,0 +1,51 @@ +using Content.Server.Polymorph.Systems; +using Content.Shared.Polymorph; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; +using Robust.Shared.Prototypes; + +namespace Content.Server.Trigger.Systems; + +public sealed partial class PolymorphOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly PolymorphSystem _polymorph = default!; + + /// + /// Need to do this so we don't get a collection enumeration error in physics by polymorphing + /// an entity we're colliding with in case of TriggerOnCollide. + /// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed. + /// + private Queue<(EntityUid Uid, ProtoId Polymorph)> _queuedPolymorphUpdates = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _queuedPolymorphUpdates.Enqueue((target.Value, ent.Comp.Polymorph)); + args.Handled = true; + } + + public override void Update(float frametime) + { + while (_queuedPolymorphUpdates.TryDequeue(out var data)) + { + if (TerminatingOrDeleted(data.Uid)) + continue; + + _polymorph.PolymorphEntity(data.Uid, data.Polymorph); + } + } +} diff --git a/Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs b/Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs new file mode 100644 index 0000000000..963ac36b7f --- /dev/null +++ b/Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs @@ -0,0 +1,49 @@ +using Content.Server.Radio.EntitySystems; +using Content.Server.Pinpointer; +using Content.Shared.Mobs.Components; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Trigger.Systems; + +public sealed class RattleOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly NavMapSystem _navMap = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + if (!TryComp(target.Value, out var mobstate)) + return; + + args.Handled = true; + + if (!ent.Comp.Messages.TryGetValue(mobstate.CurrentState, out var messageId)) + return; + + // Gets the location of the user + var posText = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString(target.Value)); + + var message = Loc.GetString(messageId, ("user", target.Value), ("position", posText)); + // Sends a message to the radio channel specified by the implant + _radio.SendRadioMessage(ent.Owner, message, _prototypeManager.Index(ent.Comp.RadioChannel), ent.Owner); + } +} diff --git a/Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs b/Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs new file mode 100644 index 0000000000..a38ca2a759 --- /dev/null +++ b/Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs @@ -0,0 +1,48 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Trigger.Components.Effects; +using Content.Shared.Trigger.Systems; +using Robust.Shared.Timing; + +namespace Content.Server.Trigger.Systems; + +public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem +{ + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (!comp.Active || comp.NextReleaseTime > curTime) + continue; + + var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction); + var environment = _atmosphereSystem.GetContainingMixture(uid, false, true); + + if (environment == null) + { + _appearance.SetData(uid, ReleaseGasOnTriggerVisuals.Key, false); + RemCompDeferred(uid); + continue; + } + + _atmosphereSystem.Merge(environment, giverGasMix); + comp.NextReleaseTime += comp.ReleaseInterval; + + if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit || + comp.Air.TotalMoles <= 0) + { + _appearance.SetData(uid, ReleaseGasOnTriggerVisuals.Key, false); + RemCompDeferred(uid); + } + } + } +} diff --git a/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs b/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs new file mode 100644 index 0000000000..97799b9cc6 --- /dev/null +++ b/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs @@ -0,0 +1,68 @@ +using Content.Server.Fluids.EntitySystems; +using Content.Server.Spreader; +using Content.Shared.Chemistry.Components; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.Maps; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; +using Robust.Server.GameObjects; +using Robust.Shared.Map; + +namespace Content.Server.Trigger.Systems; + +/// +/// Handles creating smoke when is triggered. +/// +public sealed class SmokeOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _mapMan = default!; + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly SmokeSystem _smoke = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly SpreaderSystem _spreader = default!; + [Dependency] private readonly TurfSystem _turf = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + // TODO: move all of this into an API function in SmokeSystem + var xform = Transform(target.Value); + var mapCoords = _transform.GetMapCoordinates(target.Value, xform); + if (!_mapMan.TryFindGridAt(mapCoords, out var gridUid, out var gridComp) || + !_map.TryGetTileRef(gridUid, gridComp, xform.Coordinates, out var tileRef) || + tileRef.Tile.IsEmpty) + { + return; + } + + if (_spreader.RequiresFloorToSpread(ent.Comp.SmokePrototype.ToString()) && _turf.IsSpace(tileRef)) + return; + + var coords = _map.MapToGrid(gridUid, mapCoords); + var smoke = Spawn(ent.Comp.SmokePrototype, coords.SnapToGrid()); + if (!TryComp(smoke, out var smokeComp)) + { + Log.Error($"Smoke prototype {ent.Comp.SmokePrototype} was missing SmokeComponent"); + Del(smoke); + return; + } + + _smoke.StartSmoke(smoke, ent.Comp.Solution, (float)ent.Comp.Duration.TotalSeconds, ent.Comp.SpreadAmount, smokeComp); + + args.Handled = true; + } +} diff --git a/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs b/Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs similarity index 53% rename from Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs rename to Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs index 28be6a5373..6da6f707c1 100644 --- a/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs @@ -1,13 +1,13 @@ -using Content.Server.Explosion.EntitySystems; -using Content.Shared.Timing; +using Content.Server.Chat.Systems; +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; using Robust.Shared.Prototypes; using Robust.Shared.Random; -namespace Content.Server.Chat.Systems; +namespace Content.Server.Trigger.Systems; public sealed class SpeakOnTriggerSystem : EntitySystem { - [Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ChatSystem _chat = default!; @@ -15,32 +15,34 @@ public sealed class SpeakOnTriggerSystem : EntitySystem public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnTrigger); } private void OnTrigger(Entity ent, ref TriggerEvent args) { - TrySpeak(ent); - args.Handled = true; - } - - private void TrySpeak(Entity ent) - { - // If it doesn't have the use delay component, still send the message. - if (TryComp(ent.Owner, out var useDelay) && _useDelay.IsDelayed((ent.Owner, useDelay))) + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) return; - if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack)) + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) return; - var message = Loc.GetString(_random.Pick(messagePack.Values)); + string message; + if (ent.Comp.Text != null) + message = Loc.GetString(ent.Comp.Text); + else + { + if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack)) + return; + message = Loc.GetString(_random.Pick(messagePack.Values)); + } // Chatcode moment: messages starting with "." are considered radio messages. // Prepending ">" forces the message to be spoken instead. // TODO chat refactor: remove this message = '>' + message; - _chat.TrySendInGameICMessage(ent.Owner, message, InGameICChatType.Speak, true); - - if (useDelay != null) - _useDelay.TryResetDelay((ent.Owner, useDelay)); + _chat.TrySendInGameICMessage(target.Value, message, InGameICChatType.Speak, true); + args.Handled = true; } } diff --git a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs index 81bbd48a0d..ade861af98 100644 --- a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs +++ b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Database; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Storage; +using Content.Shared.Trigger; using Robust.Server.Containers; namespace Content.Server.VoiceTrigger; diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs index b9e23f036b..faef4d5078 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs @@ -1,9 +1,9 @@ -using Content.Shared.Explosion.Components.OnTrigger; +using Content.Shared.Explosion.Components; namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components; /// -/// Activates 'trigger' for . +/// Activates to explode. /// [RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))] public sealed partial class XAETriggerExplosivesComponent : Component; diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index 967980302d..34e955a50e 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -24,6 +24,11 @@ public abstract class SharedChatSystem : EntitySystem public const char WhisperPrefix = ','; public const char DefaultChannelKey = 'h'; + public const int VoiceRange = 10; // how far voice goes in world units + public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units + public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units + public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg"; + public static readonly ProtoId CommonChannel = "Common"; public static readonly string DefaultChannelPrefix = $"{RadioChannelPrefix}{DefaultChannelKey}"; diff --git a/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs b/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs deleted file mode 100644 index 87adc0cc90..0000000000 --- a/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.Damage.Components; - -[RegisterComponent] -public sealed partial class DamageUserOnTriggerComponent : Component -{ - [DataField("ignoreResistances")] public bool IgnoreResistances; - - [DataField("damage", required: true)] - public DamageSpecifier Damage = default!; -} diff --git a/Content.Shared/Emp/SharedEmpSystem.cs b/Content.Shared/Emp/SharedEmpSystem.cs index 6e4478bb6d..72dc874935 100644 --- a/Content.Shared/Emp/SharedEmpSystem.cs +++ b/Content.Shared/Emp/SharedEmpSystem.cs @@ -1,3 +1,4 @@ +using Robust.Shared.Map; using Robust.Shared.Timing; namespace Content.Shared.Emp; @@ -7,4 +8,15 @@ public abstract class SharedEmpSystem : EntitySystem [Dependency] protected readonly IGameTiming Timing = default!; protected const string EmpDisabledEffectPrototype = "EffectEmpDisabled"; + + /// + /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range. + /// + /// The location to trigger the EMP pulse at. + /// The range of the EMP pulse. + /// The amount of energy consumed by the EMP pulse. + /// The duration of the EMP effects. + public virtual void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration) + { + } } diff --git a/Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs b/Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs deleted file mode 100644 index 6d43abc9d9..0000000000 --- a/Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.GameStates; - -namespace Content.Shared.Explosion.Components; - -/// -/// Component for tracking active trigger timers. A timers can activated by some other component, e.g. . -/// -[RegisterComponent] -public sealed partial class ActiveTimerTriggerComponent : Component -{ - [DataField] public float TimeRemaining; - - [DataField] public EntityUid? User; - - [DataField] public float BeepInterval; - - [DataField] public float TimeUntilBeep; - - [DataField] public SoundSpecifier? BeepSound; -} diff --git a/Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs b/Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs deleted file mode 100644 index e14cd12464..0000000000 --- a/Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Explosion.Components.OnTrigger; - -/// -/// Explode using the entity's if Triggered. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ExplodeOnTriggerComponent : Component -{ -} diff --git a/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs b/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs deleted file mode 100644 index 983b8a31ee..0000000000 --- a/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Linq; -using Content.Shared.Guidebook; -using Robust.Shared.Audio; -using Robust.Shared.GameStates; - -namespace Content.Shared.Explosion.Components -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class OnUseTimerTriggerComponent : Component - { - [DataField] public float Delay = 1f; - - /// - /// If not null, a user can use verbs to configure the delay to one of these options. - /// - [DataField] public List? DelayOptions = null; - - /// - /// If not null, this timer will periodically play this sound while active. - /// - [DataField] public SoundSpecifier? BeepSound; - - /// - /// Time before beeping starts. Defaults to a single beep interval. If set to zero, will emit a beep immediately after use. - /// - [DataField] public float? InitialBeepDelay; - - [DataField] public float BeepInterval = 1; - - /// - /// Whether the timer should instead be activated through a verb in the right-click menu - /// - [DataField] public bool UseVerbInstead = false; - - /// - /// Should timer be started when it was stuck to another entity. - /// Used for C4 charges and similar behaviour. - /// - [DataField] public bool StartOnStick; - - /// - /// Allows changing the start-on-stick quality. - /// - [DataField("canToggleStartOnStick")] public bool AllowToggleStartOnStick; - - /// - /// Whether you can examine the item to see its timer or not. - /// - [DataField] public bool Examinable = true; - - /// - /// Whether or not to show the user a popup when starting the timer. - /// - [DataField] public bool DoPopup = true; - - #region GuidebookData - - [GuidebookData] - public float? ShortestDelayOption => DelayOptions?.Min(); - - [GuidebookData] - public float? LongestDelayOption => DelayOptions?.Max(); - - #endregion GuidebookData - } -} diff --git a/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs b/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs index 059ad189d1..1cab5d1a77 100644 --- a/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs +++ b/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs @@ -112,4 +112,10 @@ public sealed partial class ScatteringGrenadeComponent : Component /// We need to store this because we are only allowed to spawn and trigger timed entities on the next available frame update /// public bool IsTriggered = false; + + /// + /// The trigger key that will activate the grenade. + /// + [DataField] + public string TriggerKey = "timer"; } diff --git a/Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs b/Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs deleted file mode 100644 index 02d1156104..0000000000 --- a/Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Explosion.Components; - -[NetworkedComponent] -public abstract partial class SharedTriggerOnProximityComponent : Component -{ - -} diff --git a/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs deleted file mode 100644 index 5027b04517..0000000000 --- a/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Content.Shared.Explosion.EntitySystems; - -public abstract partial class SharedReleaseGasOnTriggerSystem : EntitySystem; - -// I have dreams of Atmos in shared. diff --git a/Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs deleted file mode 100644 index 386024fbeb..0000000000 --- a/Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Content.Shared.Explosion.EntitySystems; - -public abstract class SharedRepulseAttractOnTriggerSystem : EntitySystem; diff --git a/Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs deleted file mode 100644 index b206dfa696..0000000000 --- a/Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Content.Shared.Explosion.EntitySystems; - -public abstract class SharedSmokeOnTriggerSystem : EntitySystem -{ -} diff --git a/Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs deleted file mode 100644 index cc5b3f6b74..0000000000 --- a/Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.Explosion.EntitySystems; - -public abstract class SharedTriggerSystem : EntitySystem -{ - -} \ No newline at end of file diff --git a/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs b/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs deleted file mode 100644 index e735b3784a..0000000000 --- a/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.GameStates; -namespace Content.Shared.Flash.Components; - -/// -/// Upon being triggered will flash in an area around it. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class FlashOnTriggerComponent : Component -{ - [DataField] - public float Range = 1.0f; - - [DataField] - public TimeSpan Duration = TimeSpan.FromSeconds(8); - - [DataField] - public float Probability = 1.0f; -} diff --git a/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs b/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs index 9bcd218335..28dbb31324 100644 --- a/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs +++ b/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs @@ -12,7 +12,7 @@ public sealed partial class ActiveHotPotatoComponent : Component /// /// Hot potato effect spawn cooldown in seconds /// - [DataField("effectCooldown"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float EffectCooldown = 0.3f; /// diff --git a/Content.Shared/HotPotato/HotPotatoComponent.cs b/Content.Shared/HotPotato/HotPotatoComponent.cs index f5b2e16189..b077e91e8f 100644 --- a/Content.Shared/HotPotato/HotPotatoComponent.cs +++ b/Content.Shared/HotPotato/HotPotatoComponent.cs @@ -14,7 +14,6 @@ public sealed partial class HotPotatoComponent : Component /// /// If set to true entity can be removed by hitting entities if they have hands /// - [DataField("canTransfer"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool CanTransfer = true; } diff --git a/Content.Shared/HotPotato/SharedHotPotatoSystem.cs b/Content.Shared/HotPotato/SharedHotPotatoSystem.cs index cd7a5d6da5..6f2f498782 100644 --- a/Content.Shared/HotPotato/SharedHotPotatoSystem.cs +++ b/Content.Shared/HotPotato/SharedHotPotatoSystem.cs @@ -1,18 +1,79 @@ +using Content.Shared.Audio; +using Content.Shared.Damage.Systems; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; +using Content.Shared.Popups; +using Content.Shared.Trigger; +using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Containers; +using Robust.Shared.Timing; namespace Content.Shared.HotPotato; public abstract class SharedHotPotatoSystem : EntitySystem { + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!; + [Dependency] private readonly DamageOnHoldingSystem _damageOnHolding = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnRemoveAttempt); + SubscribeLocalEvent(OnActiveTimer); + SubscribeLocalEvent(OnMeleeHit); } - private void OnRemoveAttempt(EntityUid uid, HotPotatoComponent comp, ContainerGettingRemovedAttemptEvent args) + private void OnRemoveAttempt(Entity ent, ref ContainerGettingRemovedAttemptEvent args) { - if (!comp.CanTransfer) + if (!_timing.ApplyingState && !ent.Comp.CanTransfer) args.Cancel(); } + + private void OnActiveTimer(Entity ent, ref ActiveTimerTriggerEvent args) + { + EnsureComp(ent); + ent.Comp.CanTransfer = false; + _ambientSound.SetAmbience(ent.Owner, true); + _damageOnHolding.SetEnabled(ent.Owner, true); + Dirty(ent); + } + + private void OnMeleeHit(Entity ent, ref MeleeHitEvent args) + { + if (!HasComp(ent)) + return; + + ent.Comp.CanTransfer = true; + foreach (var hitEntity in args.HitEntities) + { + if (!TryComp(hitEntity, out var hands)) + continue; + + if (!_hands.IsHolding((hitEntity, hands), ent.Owner, out _) && _hands.TryForcePickupAnyHand(hitEntity, ent.Owner, handsComp: hands)) + { + _popup.PopupPredicted( + Loc.GetString("hot-potato-passed", ("from", Identity.Entity(args.User, EntityManager)), ("to", Identity.Entity(hitEntity, EntityManager))), + ent.Owner, + args.User, + PopupType.Medium); + break; + } + + _popup.PopupClient( + Loc.GetString("hot-potato-failed", ("to", Identity.Entity(hitEntity, EntityManager))), + ent.Owner, + args.User, + PopupType.Medium); + + break; + } + + ent.Comp.CanTransfer = false; + } } diff --git a/Content.Shared/Implants/Components/RattleComponent.cs b/Content.Shared/Implants/Components/RattleComponent.cs deleted file mode 100644 index 3ec63e8e15..0000000000 --- a/Content.Shared/Implants/Components/RattleComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared.Radio; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Implants.Components; - -[RegisterComponent, NetworkedComponent] -public sealed partial class RattleComponent : Component -{ - // The radio channel the message will be sent to - [DataField] - public ProtoId RadioChannel = "Syndicate"; - - // The message that the implant will send when crit - [DataField] - public LocId CritMessage = "deathrattle-implant-critical-message"; - - // The message that the implant will send when dead - [DataField] - public LocId DeathMessage = "deathrattle-implant-dead-message"; -} diff --git a/Content.Shared/Implants/Components/TriggerImplantActionComponent.cs b/Content.Shared/Implants/Components/TriggerImplantActionComponent.cs deleted file mode 100644 index 0f9856f2a4..0000000000 --- a/Content.Shared/Implants/Components/TriggerImplantActionComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Implants.Components; - -/// -/// Triggers implants when the action is pressed -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class TriggerImplantActionComponent : Component -{ - -} diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs index 95c3f8664f..177e24ff02 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs @@ -179,7 +179,7 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer)) return; - var relayEv = new ImplantRelayEvent(args); + var relayEv = new ImplantRelayEvent(args, uid); foreach (var implant in implantContainer.ContainedEntities) { if (args is HandledEntityEventArgs { Handled : true }) @@ -194,9 +194,12 @@ public sealed class ImplantRelayEvent where T : notnull { public readonly T Event; - public ImplantRelayEvent(T ev) + public readonly EntityUid ImplantedEntity; + + public ImplantRelayEvent(T ev, EntityUid implantedEntity) { Event = ev; + ImplantedEntity = implantedEntity; } } diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs index 424bd12bb3..6f6e55b5ef 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs @@ -50,25 +50,37 @@ public sealed partial class ItemToggleComponent : Component /// /// /// If server-side systems affect the item's toggle, like charge/fuel systems, then the item is not predictable. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public bool Predictable = true; /// /// The noise this item makes when it is toggled on. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public SoundSpecifier? SoundActivate; /// /// The noise this item makes when it is toggled off. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public SoundSpecifier? SoundDeactivate; + /// + /// The popup to show to someone activating this item. + /// + [DataField, AutoNetworkedField] + public LocId? PopupActivate; + + /// + /// The popup to show to someone deactivating this item. + /// + [DataField, AutoNetworkedField] + public LocId? PopupDeactivate; + /// /// The noise this item makes when it is toggled on. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public SoundSpecifier? SoundFailToActivate; } diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index 8008ecf9e5..ff31faaaa1 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -117,30 +117,30 @@ public sealed class ItemToggleSystem : EntitySystem /// Sets its state to the opposite of what it is. /// /// Same as - public bool Toggle(Entity ent, EntityUid? user = null, bool predicted = true) + public bool Toggle(Entity ent, EntityUid? user = null, bool predicted = true, bool showPopup = true) { if (!_query.Resolve(ent, ref ent.Comp, false)) return false; - return TrySetActive(ent, !ent.Comp.Activated, user, predicted); + return TrySetActive(ent, !ent.Comp.Activated, user, predicted, showPopup); } /// /// Tries to set the activated bool from a value. /// /// false if the attempt fails for any reason - public bool TrySetActive(Entity ent, bool active, EntityUid? user = null, bool predicted = true) + public bool TrySetActive(Entity ent, bool active, EntityUid? user = null, bool predicted = true, bool showPopup = true) { if (active) - return TryActivate(ent, user, predicted: predicted); + return TryActivate(ent, user, predicted: predicted, showPopup); else - return TryDeactivate(ent, user, predicted: predicted); + return TryDeactivate(ent, user, predicted: predicted, showPopup); } /// /// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation. /// - public bool TryActivate(Entity ent, EntityUid? user = null, bool predicted = true) + public bool TryActivate(Entity ent, EntityUid? user = null, bool predicted = true, bool showPopup = true) { if (!_query.Resolve(ent, ref ent.Comp, false)) return false; @@ -169,7 +169,7 @@ public sealed class ItemToggleSystem : EntitySystem else _audio.PlayPvs(comp.SoundFailToActivate, uid); - if (attempt.Popup != null && user != null) + if (showPopup && attempt.Popup != null && user != null) { if (predicted) _popup.PopupClient(attempt.Popup, uid, user.Value); @@ -180,14 +180,14 @@ public sealed class ItemToggleSystem : EntitySystem return false; } - Activate((uid, comp), predicted, user); + Activate((uid, comp), predicted, user, showPopup); return true; } /// /// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation. /// - public bool TryDeactivate(Entity ent, EntityUid? user = null, bool predicted = true) + public bool TryDeactivate(Entity ent, EntityUid? user = null, bool predicted = true, bool showPopup = true) { if (!_query.Resolve(ent, ref ent.Comp, false)) return false; @@ -211,7 +211,7 @@ public sealed class ItemToggleSystem : EntitySystem if (attempt.Silent) return false; - if (attempt.Popup != null && user != null) + if (showPopup && attempt.Popup != null && user != null) { if (predicted) _popup.PopupClient(attempt.Popup, uid, user.Value); @@ -222,18 +222,26 @@ public sealed class ItemToggleSystem : EntitySystem return false; } - Deactivate((uid, comp), predicted, user); + Deactivate((uid, comp), predicted, user, showPopup); return true; } - private void Activate(Entity ent, bool predicted, EntityUid? user = null) + private void Activate(Entity ent, bool predicted, EntityUid? user = null, bool showPopup = true) { var (uid, comp) = ent; var soundToPlay = comp.SoundActivate; if (predicted) + { _audio.PlayPredicted(soundToPlay, uid, user); + if (showPopup && ent.Comp.PopupActivate != null && user != null) + _popup.PopupClient(Loc.GetString(ent.Comp.PopupActivate), user.Value, user.Value); + } else + { _audio.PlayPvs(soundToPlay, uid); + if (showPopup && ent.Comp.PopupActivate != null && user != null) + _popup.PopupEntity(Loc.GetString(ent.Comp.PopupActivate), user.Value, user.Value); + } comp.Activated = true; UpdateVisuals((uid, comp)); @@ -246,14 +254,22 @@ public sealed class ItemToggleSystem : EntitySystem /// /// Used to make the actual changes to the item's components on deactivation. /// - private void Deactivate(Entity ent, bool predicted, EntityUid? user = null) + private void Deactivate(Entity ent, bool predicted, EntityUid? user = null, bool showPopup = true) { var (uid, comp) = ent; var soundToPlay = comp.SoundDeactivate; if (predicted) + { _audio.PlayPredicted(soundToPlay, uid, user); + if (showPopup && ent.Comp.PopupDeactivate != null && user != null) + _popup.PopupClient(Loc.GetString(ent.Comp.PopupDeactivate), user.Value, user.Value); + } else + { _audio.PlayPvs(soundToPlay, uid); + if (showPopup && ent.Comp.PopupDeactivate != null && user != null) + _popup.PopupEntity(Loc.GetString(ent.Comp.PopupDeactivate), user.Value, user.Value); + } comp.Activated = false; UpdateVisuals((uid, comp)); diff --git a/Content.Shared/Mousetrap/MousetrapComponent.cs b/Content.Shared/Mousetrap/MousetrapComponent.cs index 6d7483802b..4f48f00b3a 100644 --- a/Content.Shared/Mousetrap/MousetrapComponent.cs +++ b/Content.Shared/Mousetrap/MousetrapComponent.cs @@ -2,20 +2,20 @@ using Robust.Shared.GameStates; namespace Content.Shared.Mousetrap; -[RegisterComponent, NetworkedComponent] +/// +/// Component inteded to be used for mouse traps. +/// Will stop step triggers from happening unless armed via +/// and will scale damage taken from +/// depending on mass. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class MousetrapComponent : Component { - [ViewVariables] - [DataField("isActive")] - public bool IsActive = false; - /// - /// Set this to change where the - /// inflection point in the scaling - /// equation will occur. - /// The default is 10. + /// Set this to change where the + /// inflection point in the damage scaling + /// equation will occur. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("massBalance")] + [DataField, AutoNetworkedField] public int MassBalance = 10; } diff --git a/Content.Shared/Mousetrap/MousetrapSystem.cs b/Content.Shared/Mousetrap/MousetrapSystem.cs new file mode 100644 index 0000000000..96f74114b2 --- /dev/null +++ b/Content.Shared/Mousetrap/MousetrapSystem.cs @@ -0,0 +1,42 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Trigger.Systems; +using Content.Shared.StepTrigger.Systems; +using Robust.Shared.Physics.Components; + +namespace Content.Shared.Mousetrap; + +public sealed class MousetrapSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(BeforeDamageOnTrigger); + SubscribeLocalEvent(OnStepTriggerAttempt); + } + + // only allow step triggers to trigger if the trap is armed + // TODO: refactor Steptriggers to get rid of this + // they should just use the new trigger conditions + private void OnStepTriggerAttempt(Entity ent, ref StepTriggerAttemptEvent args) + { + if (!TryComp(ent, out var toggle)) + return; + + args.Continue |= toggle.Activated; + } + + // scale the damage according to mass + private void BeforeDamageOnTrigger(Entity ent, ref BeforeDamageOnTriggerEvent args) + { + if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0) + { + // The idea here is inverse, + // Small - big damage, + // Large - small damage + // yes i punched numbers into a calculator until the graph looked right + var scaledDamage = -50 * Math.Atan(physics.Mass - ent.Comp.MassBalance) + 25 * Math.PI; + args.Damage *= scaledDamage; + } + } +} diff --git a/Content.Shared/Mousetrap/MousetrapVisuals.cs b/Content.Shared/Mousetrap/MousetrapVisuals.cs deleted file mode 100644 index 9685157aad..0000000000 --- a/Content.Shared/Mousetrap/MousetrapVisuals.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Mousetrap; - -[Serializable, NetSerializable] -public enum MousetrapVisuals : byte -{ - Visual, - Armed, - Unarmed -} diff --git a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs index 3ba4494cca..75053011ff 100644 --- a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs +++ b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs @@ -10,11 +10,21 @@ namespace Content.Shared.Ninja.Components; [RegisterComponent, NetworkedComponent, Access(typeof(SharedSpiderChargeSystem))] public sealed partial class SpiderChargeComponent : Component { - /// Range for planting within the target area + /// + /// Range for planting within the target area. + /// [DataField] public float Range = 10f; - /// The ninja that planted this charge + /// + /// The ninja that planted this charge. + /// [DataField] public EntityUid? Planter; + + /// + /// The trigger that will mark the objective as successful. + /// + [DataField] + public string TriggerKey = "timer"; } diff --git a/Content.Shared/Payload/Components/PayloadTriggerComponent.cs b/Content.Shared/Payload/Components/PayloadTriggerComponent.cs index b064e91198..d07da4b24e 100644 --- a/Content.Shared/Payload/Components/PayloadTriggerComponent.cs +++ b/Content.Shared/Payload/Components/PayloadTriggerComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.Explosion.Components; +using Content.Shared.Trigger.Components; +using Content.Shared.Trigger.Components.Triggers; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -10,7 +11,7 @@ namespace Content.Shared.Payload.Components; /// /// This component performs two functions. Firstly, it will add or remove other components to some entity when this /// item is installed inside of it. This is intended for use with constructible grenades. For example, this allows -/// you to add things like , or . +/// you to add things like , or . /// This is required because otherwise you would have to forward arbitrary interaction directed at the casing /// through to the trigger, which would be quite complicated. Also proximity triggers don't really work inside of /// containers. @@ -29,7 +30,7 @@ public sealed partial class PayloadTriggerComponent : Component /// /// List of components to add or remove from an entity when this trigger is (un)installed. /// - [DataField("components", serverOnly:true, readOnly: true)] + [DataField(serverOnly: true, readOnly: true)] public ComponentRegistry? Components = null; /// @@ -41,6 +42,6 @@ public sealed partial class PayloadTriggerComponent : Component /// when removing the component, to ensure that removal of this trigger only removes the components that it was /// responsible for adding. /// - [DataField("grantedComponents", serverOnly: true)] + [DataField(serverOnly: true)] public HashSet GrantedComponents = new(); } diff --git a/Content.Shared/Rootable/SharedRootableSystem.cs b/Content.Shared/Rootable/SharedRootableSystem.cs index 9a6697cf97..c3deca0769 100644 --- a/Content.Shared/Rootable/SharedRootableSystem.cs +++ b/Content.Shared/Rootable/SharedRootableSystem.cs @@ -1,5 +1,4 @@ -using Content.Shared.Damage.Components; -using Content.Shared.Actions; +using Content.Shared.Actions; using Content.Shared.Actions.Components; using Content.Shared.Alert; using Content.Shared.Coordinates; @@ -9,6 +8,7 @@ using Content.Shared.Mobs; using Content.Shared.Movement.Systems; using Content.Shared.Slippery; using Content.Shared.Toggleable; +using Content.Shared.Trigger.Components.Effects; using Robust.Shared.Audio.Systems; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -127,7 +127,7 @@ public abstract class SharedRootableSystem : EntitySystem if (!ent.Comp.Rooted) return; - if (args.SlipCausingEntity != null && HasComp(args.SlipCausingEntity)) + if (args.SlipCausingEntity != null && HasComp(args.SlipCausingEntity)) return; args.NoSlip = true; diff --git a/Content.Shared/Speech/Components/ActiveListenerComponent.cs b/Content.Shared/Speech/Components/ActiveListenerComponent.cs new file mode 100644 index 0000000000..fde108a817 --- /dev/null +++ b/Content.Shared/Speech/Components/ActiveListenerComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Chat; +using Robust.Shared.GameStates; + +namespace Content.Shared.Speech.Components; + +/// +/// This component is used to relay speech events to other systems. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ActiveListenerComponent : Component +{ + /// + /// The range in which to listen to speech. + /// + [DataField] + public float Range = SharedChatSystem.VoiceRange; +} diff --git a/Content.Server/Speech/ListenEvent.cs b/Content.Shared/Speech/ListenEvent.cs similarity index 93% rename from Content.Server/Speech/ListenEvent.cs rename to Content.Shared/Speech/ListenEvent.cs index b67aa92f65..8854bd99f4 100644 --- a/Content.Server/Speech/ListenEvent.cs +++ b/Content.Shared/Speech/ListenEvent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Speech; +namespace Content.Shared.Speech; public sealed class ListenEvent : EntityEventArgs { diff --git a/Content.Shared/Sticky/Components/StickyComponent.cs b/Content.Shared/Sticky/Components/StickyComponent.cs index 4513091754..a4007b0780 100644 --- a/Content.Shared/Sticky/Components/StickyComponent.cs +++ b/Content.Shared/Sticky/Components/StickyComponent.cs @@ -1,5 +1,5 @@ using Content.Shared.Sticky.Systems; -using Content.Shared.Whitelist; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Utility; diff --git a/Content.Shared/Timing/UseDelaySystem.cs b/Content.Shared/Timing/UseDelaySystem.cs index d02752e16b..0950e8981d 100644 --- a/Content.Shared/Timing/UseDelaySystem.cs +++ b/Content.Shared/Timing/UseDelaySystem.cs @@ -9,7 +9,7 @@ public sealed class UseDelaySystem : EntitySystem [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; - private const string DefaultId = "default"; + public const string DefaultId = "default"; public override void Initialize() { diff --git a/Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs b/Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs new file mode 100644 index 0000000000..d7da5cecde --- /dev/null +++ b/Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components; + +/// +/// Component used for tracking active timers triggers. +/// Used internally for performance reasons. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ActiveTimerTriggerComponent : Component; diff --git a/Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs b/Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs new file mode 100644 index 0000000000..bddbbd402e --- /dev/null +++ b/Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components; + +/// +/// Component used for tracking active two-stage triggers. +/// Used internally for performance reasons. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ActiveTwoStageTriggerComponent : Component; diff --git a/Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs new file mode 100644 index 0000000000..7e77c99d93 --- /dev/null +++ b/Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Trigger.Systems; + +namespace Content.Shared.Trigger.Components.Conditions; + +/// +/// Base class for components that add a condition to triggers. +/// +public abstract partial class BaseTriggerConditionComponent : Component +{ + /// + /// The keys that are checked for the condition. + /// + [DataField, AutoNetworkedField] + public HashSet Keys = new() { TriggerSystem.DefaultTriggerKey }; +} diff --git a/Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs new file mode 100644 index 0000000000..478f06602d --- /dev/null +++ b/Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Conditions; + +/// +/// Adds an alt verb that can be used to toggle a trigger. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ToggleTriggerConditionComponent : BaseTriggerConditionComponent +{ + /// + /// Is the component currently enabled? + /// + [DataField, AutoNetworkedField] + public bool Enabled = true; + + /// + /// The text of the toggle verb. + /// + [DataField, AutoNetworkedField] + public LocId ToggleVerb = "toggle-trigger-condition-default-verb"; + + /// + /// The popup to show when toggled on. + /// + [DataField, AutoNetworkedField] + public LocId ToggleOn = "toggle-trigger-condition-default-on"; + + /// + /// The popup to show when toggled off. + /// + [DataField, AutoNetworkedField] + public LocId ToggleOff = "toggle-trigger-condition-default-off"; +} diff --git a/Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs new file mode 100644 index 0000000000..70a331227e --- /dev/null +++ b/Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Timing; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Conditions; + +/// +/// Checks if the triggered entity has an active UseDelay. +/// +/// +/// TODO: Support specific UseDelay IDs for each trigger key. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class UseDelayTriggerConditionComponent : BaseTriggerConditionComponent +{ + /// + /// Checks if the triggered entity has an active UseDelay. + /// + [DataField, AutoNetworkedField] + public string UseDelayId = UseDelaySystem.DefaultId; +} diff --git a/Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs new file mode 100644 index 0000000000..a2779f79c6 --- /dev/null +++ b/Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Conditions; + +/// +/// Checks if the user of a trigger satisfies a whitelist and blacklist condition for the triggered entity or the one triggering it. +/// Cancels the trigger otherwise. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class WhitelistTriggerConditionComponent : BaseTriggerConditionComponent +{ + /// + /// Whitelist for what entites can cause this trigger. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? UserWhitelist; + + /// + /// Blacklist for what entites can cause this trigger. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? UserBlacklist; +} diff --git a/Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs new file mode 100644 index 0000000000..06f5258fbe --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs @@ -0,0 +1,37 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Adds the specified components when triggered. +/// If TargetUser is true they will be added to the user. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class AddComponentsOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The list of components that will be added. + /// + [DataField(required: true)] + public ComponentRegistry Components = new(); + + /// + /// If this component has been triggered at least once already. + /// If this is true the components have been added. + /// + [DataField, AutoNetworkedField] + public bool Triggered = false; + + /// + /// If this effect can only be triggered once. + /// + [DataField, AutoNetworkedField] + public bool TriggerOnce = false; + + /// + /// Should components that already exist on the entity be overwritten? + /// + [DataField, AutoNetworkedField] + public bool RemoveExisting = false; +} diff --git a/Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs new file mode 100644 index 0000000000..0d161344d8 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Changes the alert level of the station when triggered. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class AlertLevelChangeOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The alert level to change to when triggered. + /// + [DataField, AutoNetworkedField] + public string Level = "blue"; + + /// + /// Whether to play the sound when the alert level changes. + /// + [DataField, AutoNetworkedField] + public bool PlaySound = true; + + /// + /// Whether to say the announcement when the alert level changes. + /// + [DataField, AutoNetworkedField] + public bool Announce = true; + + /// + /// Force the alert change. This applies if the alert level is not selectable or not. + /// + [DataField, AutoNetworkedField] + public bool Force = false; +} diff --git a/Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs new file mode 100644 index 0000000000..ed61876f15 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will (un)anchor the entity when triggered. +/// If TargetUser is true they will be (un)anchored instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class AnchorOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Anchor the entity on trigger if it is currently unanchored? + /// + [DataField, AutoNetworkedField] + public bool CanAnchor = true; + + /// + /// Unanchor the entity on trigger if it is currently anchored? + /// If both this and CanAnchor are true then the trigger will toggle between states. + /// + [DataField, AutoNetworkedField] + public bool CanUnanchor = false; + + /// + /// Removes this component when triggered so it can only be activated once. + /// + /// + /// TODO: Make this a generic thing for all triggers. + /// Or just add a RemoveComponentsOnTriggerComponent. + /// + [DataField, AutoNetworkedField] + public bool RemoveOnTrigger = true; +} diff --git a/Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs new file mode 100644 index 0000000000..cf93ad6c4a --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.Trigger.Systems; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Base class for components that do something when triggered. +/// +public abstract partial class BaseXOnTriggerComponent : Component +{ + /// + /// The keys that will activate the effect. + /// + [DataField, AutoNetworkedField] + public HashSet KeysIn = new() { TriggerSystem.DefaultTriggerKey }; + + /// + /// Set to true to make the user of the trigger the effect target. + /// Set to false to make the owner of this component the target. + /// + [DataField, AutoNetworkedField] + public bool TargetUser = false; +} diff --git a/Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs new file mode 100644 index 0000000000..8d35b019b4 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared.Damage; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will damage an entity when triggered. +/// If TargetUser is true it the user will take damage instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class DamageOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Should the damage ignore resistances? + /// + [DataField, AutoNetworkedField] + public bool IgnoreResistances; + + /// + /// The base damage amount that is dealt. + /// May be further modified by subscriptions. + /// + [DataField(required: true), AutoNetworkedField] + public DamageSpecifier Damage = default!; +} diff --git a/Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs new file mode 100644 index 0000000000..9d0a5b7517 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will delete the entity when triggered. +/// If TargetUser is true it will delete them instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class DeleteOnTriggerComponent : BaseXOnTriggerComponent; diff --git a/Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs new file mode 100644 index 0000000000..f3870225e2 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs @@ -0,0 +1,31 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will play a sound in PVS range when triggered. +/// If TargetUser is true it will be played at their position. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class EmitSoundOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The to play. + /// + [DataField(required: true), AutoNetworkedField] + public SoundSpecifier? Sound; + + /// + /// Play the sound at the position instead of parented to the source entity. + /// Useful if the entity is deleted after. + /// + [DataField, AutoNetworkedField] + public bool Positional; + + /// + /// Should this sound be predicted for the User? + /// + [DataField, AutoNetworkedField] + public bool Predicted; +} diff --git a/Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs new file mode 100644 index 0000000000..327e9f57db --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will cause an EMP at the entity's location when triggered. +/// If TargetUser is true then it will be spawned at their position. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class EmpOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// EMP range. + /// + [DataField, AutoNetworkedField] + public float Range = 1.0f; + + /// + /// How much energy (in Joules) will be consumed per battery in range. + /// + [DataField, AutoNetworkedField] + public float EnergyConsumption; + + /// + /// How long it disables targets. + /// + [DataField, AutoNetworkedField] + public TimeSpan DisableDuration = TimeSpan.FromSeconds(60); +} diff --git a/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs new file mode 100644 index 0000000000..2a1af40a2c --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will explode using the entity's when triggered. +/// TargetUser will only work of the user has ExplosiveComponent as well. +/// The User will be logged in the admin logs. +/// +/// +/// TODO: Allow this to work without an ExplosiveComponent on the user via QueueExplosion. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExplodeOnTriggerComponent : BaseXOnTriggerComponent; diff --git a/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs new file mode 100644 index 0000000000..8b75417031 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will cause a flash in an area around the entity when triggered. +/// If TargetUser is true then their location will be used. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FlashOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The range in which to flash entities in. + /// + [DataField, AutoNetworkedField] + public float Range = 1.0f; + + /// + /// The duration of the status effect. + /// + [DataField, AutoNetworkedField] + public TimeSpan Duration = TimeSpan.FromSeconds(8); + + /// + /// The probability to apply the status effect. + /// + [DataField, AutoNetworkedField] + public float Probability = 1.0f; +} diff --git a/Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs new file mode 100644 index 0000000000..16c1365c05 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will kick a player from the server as if their connection dropped if triggered. +/// Yes, really. Don't use this component. +/// If TargetUser is true then the user of the trigger will be kicked, otherwise the entity itself. +/// +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class GhostKickOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The reason that will be displayed in the server log when a player is kicked. + /// + [DataField, AutoNetworkedField] + public LocId Reason = "ghost-kick-on-trigger-default"; +} diff --git a/Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs new file mode 100644 index 0000000000..b3475ac447 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will gib the entity when triggered. +/// If TargetUser is true the user will be gibbed instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class GibOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Should gibbing also delete the owners items? + /// + [DataField, AutoNetworkedField] + public bool DeleteItems = false; +} diff --git a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs new file mode 100644 index 0000000000..36273ef1b2 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will ignite for a certain length of time when triggered. +/// Requires along with triggering components. +/// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well). +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Once ignited, the time it will unignite at. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan IgnitedUntil = TimeSpan.Zero; + + /// + /// How long the ignition source is active for after triggering. + /// + [DataField, AutoNetworkedField] + public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5); +} diff --git a/Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs new file mode 100644 index 0000000000..4c5360f4d2 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs @@ -0,0 +1,37 @@ +using Content.Shared.Item.ItemToggle.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will toggle an item when triggered. Requires . +/// If TargetUser is true and they have that component they will be toggled instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ItemToggleOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Can the item be toggled on using the trigger? + /// + [DataField, AutoNetworkedField] + public bool CanActivate = true; + + /// + /// Can the item be toggled on using the trigger? + /// If both this and CanActivate are true then the trigger will toggle between states. + /// + [DataField, AutoNetworkedField] + public bool CanDeactivate = true; + + /// + /// Can the audio and popups be predicted? + /// + [DataField, AutoNetworkedField] + public bool Predicted = true; + + /// + /// Show a popup to the user when toggling the item? + /// + [DataField, AutoNetworkedField] + public bool ShowPopup = true; +} diff --git a/Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs new file mode 100644 index 0000000000..3d2021e425 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Polymorph; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Polymorphs the enity when triggered. +/// If TargetUser is true it will polymorph the user instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PolymorphOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Polymorph settings. + /// + [DataField(required: true)] + public ProtoId Polymorph; +} diff --git a/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs new file mode 100644 index 0000000000..599a64339a --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Mobs; +using Content.Shared.Radio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Sends an emergency message over coms when triggered giving information about the entity's mob status. +/// If TargetUser is true then the user's mob state will be used instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RattleOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The radio channel the message will be sent to. + /// + [DataField] + public ProtoId RadioChannel = "Syndicate"; + + /// + /// The message to be send depending on the target's current mob state. + /// + [DataField] + public Dictionary Messages = new() + { + {MobState.Critical, "deathrattle-implant-critical-message"}, + {MobState.Dead, "deathrattle-implant-dead-message"} + }; +} diff --git a/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs similarity index 90% rename from Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs rename to Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs index 28a5c5cf81..4edd5f83f4 100644 --- a/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs @@ -1,18 +1,16 @@ using Content.Shared.Atmos; -using Content.Shared.Explosion.EntitySystems; using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Shared.Explosion.Components.OnTrigger; +namespace Content.Shared.Trigger.Components.Effects; /// /// Contains a GasMixture that will release its contents to the atmosphere when triggered. /// [RegisterComponent, NetworkedComponent] -[AutoGenerateComponentPause] -[Access(typeof(SharedReleaseGasOnTriggerSystem))] -public sealed partial class ReleaseGasOnTriggerComponent : Component +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class ReleaseGasOnTriggerComponent : BaseXOnTriggerComponent { /// /// Whether this grenade is active and releasing gas. diff --git a/Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs similarity index 57% rename from Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs rename to Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs index 43febff03b..68af0bb544 100644 --- a/Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs @@ -1,37 +1,39 @@ using Content.Shared.Physics; using Content.Shared.Whitelist; +using Robust.Shared.GameStates; -namespace Content.Shared.Explosion.Components.OnTrigger; +namespace Content.Shared.Trigger.Components.Effects; /// -/// Generates a gravity pulse/repulse using the RepulseAttractComponent when the entity is triggered +/// Generates a gravity pulse/repulse using the RepulseAttractComponent around the entity when triggered. +/// If TargetUser is true their location will be used instead. /// -[RegisterComponent] -public sealed partial class SharedRepulseAttractOnTriggerComponent : Component +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class RepulseAttractOnTriggerComponent : BaseXOnTriggerComponent { /// /// How fast should the Repulsion/Attraction be? - /// A positive value will repulse objects, a negative value will attract + /// A positive value will repulse objects, a negative value will attract. /// - [DataField] - public float Speed; + [DataField, AutoNetworkedField] + public float Speed = 5.0f; /// /// How close do the entities need to be? /// - [DataField] - public float Range; + [DataField, AutoNetworkedField] + public float Range = 5.0f; /// /// What kind of entities should this effect apply to? /// - [DataField] + [DataField, AutoNetworkedField] public EntityWhitelist? Whitelist; /// /// What collision layers should be excluded? /// The default excludes ghost mobs, revenants, the AI camera etc. /// - [DataField] + [DataField, AutoNetworkedField] public CollisionGroup CollisionMask = CollisionGroup.GhostImpassable; } diff --git a/Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs new file mode 100644 index 0000000000..e7da7a3801 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will electrocute the entity when triggered. +/// If TargetUser is true it will electrocute the user instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ShockOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// Electrocute entity containing this entity instead (for example for wearable clothing). + /// Has priority over TargetUser. + /// + /// + /// TODO: Make this more generic so it can be used for all triggers. + /// Maybe a BeforeTriggerEvent where we modify the target. + /// + [DataField, AutoNetworkedField] + public bool TargetContainer; + + /// + /// The force of an electric shock when the trigger is triggered. + /// + [DataField, AutoNetworkedField] + public int Damage = 5; + + /// + /// Duration of electric shock when the trigger is triggered. + /// + [DataField, AutoNetworkedField] + public TimeSpan Duration = TimeSpan.FromSeconds(2); +} diff --git a/Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs new file mode 100644 index 0000000000..0698f2ca33 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.DeviceLinking; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Sends a device link signal when triggered. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SignalOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The port that gets signaled when the switch turns on. + /// + [DataField] + public ProtoId Port = "Trigger"; +} diff --git a/Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs similarity index 59% rename from Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs rename to Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs index 1138e74af8..07a5bad056 100644 --- a/Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs @@ -1,34 +1,34 @@ -using Content.Shared.Explosion.EntitySystems; using Content.Shared.Chemistry.Components; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -namespace Content.Shared.Explosion.Components; +namespace Content.Shared.Trigger.Components.Effects; /// /// Creates a smoke cloud when triggered, with an optional solution to include in it. -/// No sound is played incase a grenade is stealthy, use if you want a sound. +/// No sound is played incase a grenade is stealthy, use if you want a sound. +/// If TargetUser is true the smoke is spawned at their location. /// -[RegisterComponent, NetworkedComponent, Access(typeof(SharedSmokeOnTriggerSystem))] -public sealed partial class SmokeOnTriggerComponent : Component +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SmokeOnTriggerComponent : BaseXOnTriggerComponent { /// - /// How long the smoke stays for, after it has spread. + /// How long the smoke stays for, after it has spread (in seconds). /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Duration = 10; + [DataField, AutoNetworkedField] + public TimeSpan Duration = TimeSpan.FromSeconds(10); /// /// How much the smoke will spread. /// - [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true), AutoNetworkedField] public int SpreadAmount; /// /// Smoke entity to spawn. /// Defaults to smoke but you can use foam if you want. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public EntProtoId SmokePrototype = "Smoke"; /// @@ -37,6 +37,6 @@ public sealed partial class SmokeOnTriggerComponent : Component /// /// When using repeating trigger this essentially gets multiplied so dont do anything crazy like omnizine or lexorin. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public Solution Solution = new(); } diff --git a/Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs new file mode 100644 index 0000000000..782626f479 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs @@ -0,0 +1,31 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Spawns a protoype when triggered. +/// If TargetUser is true it will be spawned at their location. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SpawnOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The prototype to spawn. + /// + [DataField(required: true), AutoNetworkedField] + public EntProtoId Proto = string.Empty; + + /// + /// Use MapCoordinates for spawning? + /// Set to true if you don't want the new entity parented to the spawner. + /// + [DataField, AutoNetworkedField] + public bool UseMapCoords; + + /// + /// Whether or not to use predicted spawning. + /// + [DataField, AutoNetworkedField] + public bool Predicted; +} diff --git a/Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs new file mode 100644 index 0000000000..8fe6927046 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs @@ -0,0 +1,26 @@ +using Content.Shared.Dataset; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Makes the entity speak a message when triggered. +/// If TargetUser is true then they will be forced to speak instead. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SpeakOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The text to speak. This has priority over Pack. + /// + [DataField] + public LocId? Text; + + /// + /// The identifier for the dataset prototype containing messages to be spoken by this entity. + /// The spoken text will be picked randomly from it. + /// + [DataField] + public ProtoId? Pack; +} diff --git a/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs new file mode 100644 index 0000000000..4d43c2860b --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs @@ -0,0 +1,26 @@ +using Content.Shared.Timing; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Effects; + +/// +/// Will activate an UseDelay on the target when triggered. +/// +/// +/// TODO: Support specific UseDelay IDs for each trigger key. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class UseDelayOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + /// The UseDelay Id to delay. + /// + [DataField, AutoNetworkedField] + public string UseDelayId = UseDelaySystem.DefaultId; + + /// + /// If true ongoing delays won't be reset. + /// + [DataField, AutoNetworkedField] + public bool CheckDelayed; +} diff --git a/Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs b/Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs similarity index 50% rename from Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs rename to Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs index 3863b9c313..89b5535854 100644 --- a/Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs @@ -1,22 +1,22 @@ -using Content.Server.Explosion.EntitySystems; +using Robust.Shared.GameStates; -namespace Content.Server.Explosion.Components; +namespace Content.Shared.Trigger.Components; /// -/// This is used for randomizing a on MapInit +/// This is used for randomizing a on MapInit. /// -[RegisterComponent, Access(typeof(TriggerSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class RandomTimerTriggerComponent : Component { /// /// The minimum random trigger time. /// - [DataField] + [DataField, AutoNetworkedField] public float Min; /// /// The maximum random trigger time. /// - [DataField] + [DataField, AutoNetworkedField] public float Max; } diff --git a/Content.Shared/Trigger/Components/TimerTriggerComponent.cs b/Content.Shared/Trigger/Components/TimerTriggerComponent.cs new file mode 100644 index 0000000000..9cc58d3cda --- /dev/null +++ b/Content.Shared/Trigger/Components/TimerTriggerComponent.cs @@ -0,0 +1,109 @@ +using Content.Shared.Guidebook; +using Content.Shared.Trigger.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using System.Linq; + +namespace Content.Shared.Trigger.Components; + +/// +/// Starts a timer when activated by a trigger. +/// Will cause a different trigger once the time is over. +/// Can play a sound while the timer is active. +/// The time can be set by other components, for example . +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class TimerTriggerComponent : Component +{ + /// + /// The keys that will activate the timer. + /// + [DataField, AutoNetworkedField] + public List KeysIn = new() { TriggerSystem.DefaultTriggerKey }; + + /// + /// The key that will trigger once the timer is finished. + /// + [DataField, AutoNetworkedField] + public string? KeyOut = "timer"; + + /// + /// The time after which this timer will trigger after it is activated. + /// + [DataField, AutoNetworkedField] + public TimeSpan Delay = TimeSpan.FromSeconds(1); + + /// + /// If not empty, a user can use verbs to configure the delay to one of these options. + /// + [DataField, AutoNetworkedField] + public List DelayOptions = new(); + + /// + /// The time at which this trigger will activate. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan NextTrigger = TimeSpan.Zero; + + /// + /// Time of the next beeping sound. + /// + /// + /// Not networked because it's only used server side. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoPausedField] + public TimeSpan NextBeep = TimeSpan.Zero; + + /// + /// Initial beep delay. + /// Defaults to a single BeepInterval if null. + /// + /// + /// Not networked because it's only used server side. + /// + [DataField] + public TimeSpan? InitialBeepDelay; + + /// + /// The time between beeps. + /// + [DataField, AutoNetworkedField] + public TimeSpan BeepInterval = TimeSpan.FromSeconds(1); + + /// + /// The entity that activated this trigger. + /// + [DataField, AutoNetworkedField] + public EntityUid? User; + + /// + /// The beeping sound, if any. + /// + [DataField, AutoNetworkedField] + public SoundSpecifier? BeepSound; + + /// + /// Whether you can examine the item to see its timer or not. + /// + [DataField, AutoNetworkedField] + public bool Examinable = true; + + /// + /// The popup to show the user when starting the timer, if any. + /// + [DataField, AutoNetworkedField] + public LocId? Popup = "timer-trigger-activated"; + + #region GuidebookData + + [GuidebookData] + public float? ShortestDelayOption => DelayOptions.Count == 0 ? null : (float)DelayOptions.Min().TotalSeconds; + + [GuidebookData] + public float? LongestDelayOption => DelayOptions.Count == 0 ? null : (float)DelayOptions.Max().TotalSeconds; + + #endregion GuidebookData +} diff --git a/Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs b/Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs new file mode 100644 index 0000000000..88b68913f0 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ActiveTriggerOnTimedCollideComponent : Component; diff --git a/Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs b/Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs new file mode 100644 index 0000000000..1f4807f253 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Trigger.Systems; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Base class for components that cause a trigger to be activated. +/// +public abstract partial class BaseTriggerOnXComponent : Component +{ + /// + /// The key that the trigger will activate. + /// null will activate all triggers. + /// + [DataField, AutoNetworkedField] + public string? KeyOut = TriggerSystem.DefaultTriggerKey; +} diff --git a/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs b/Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs similarity index 56% rename from Content.Server/Explosion/Components/RepeatingTriggerComponent.cs rename to Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs index cc08de53f9..27527d7773 100644 --- a/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs @@ -1,25 +1,26 @@ -using Content.Server.Explosion.EntitySystems; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Explosion.Components; +namespace Content.Shared.Trigger.Components.Triggers; /// /// Constantly triggers after being added to an entity. /// -[RegisterComponent, Access(typeof(TriggerSystem))] -[AutoGenerateComponentPause] -public sealed partial class RepeatingTriggerComponent : Component +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class RepeatingTriggerComponent : BaseTriggerOnXComponent { /// /// How long to wait between triggers. /// The first trigger starts this long after the component is added. /// - [DataField] + [DataField, AutoNetworkedField] public TimeSpan Delay = TimeSpan.FromSeconds(1); /// /// When the next trigger will be. /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] - public TimeSpan NextTrigger; + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan NextTrigger = TimeSpan.Zero; } diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs new file mode 100644 index 0000000000..9dd145bb26 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when activated in hand or by clicking on the entity. +/// The user is the player activating it. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnActivateComponent : BaseTriggerOnXComponent +{ + /// + /// Is this interaction a complex interaction? + /// + [DataField, AutoNetworkedField] + public bool RequireComplex = true; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs new file mode 100644 index 0000000000..b26f3b6875 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when activating an action granted by an implant. +/// The user is the player activating it. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnActivateImplantComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs new file mode 100644 index 0000000000..a1e234bd7a --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when colliding with another entity. +/// The user is the entity collided with. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnCollideComponent : BaseTriggerOnXComponent +{ + /// + /// The fixture with which to collide. + /// + [DataField(required: true), AutoNetworkedField] + public string FixtureID = string.Empty; + + /// + /// Doesn't trigger if the other colliding fixture is nonhard. + /// + [DataField, AutoNetworkedField] + public bool IgnoreOtherNonHard = true; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs new file mode 100644 index 0000000000..40d468ec30 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when attempting to shoot a gun while it's empty. +/// The user is the player holding the gun. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnEmptyGunshotComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs new file mode 100644 index 0000000000..a8dab4e6cd --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Mobs; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when this entity's mob state changes. +/// The user is the entity that caused the state change or the owner depending on the settings. +/// If added to an implant it will trigger when the implanted entity's mob state changes. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnMobstateChangeComponent : BaseTriggerOnXComponent +{ + /// + /// What states should trigger this? + /// + [DataField(required: true), AutoNetworkedField] + public List MobState = new(); + + /// + /// If true, prevents suicide attempts for the trigger to prevent cheese. + /// + [DataField, AutoNetworkedField] + public bool PreventSuicide = false; + + /// + /// If false, the trigger user will be the entity that caused the mobstate to change. + /// If true, the trigger user will the entity that changed its mob state. + /// + /// + /// Set this to true for implants that apply an effect on the implanted entity. + /// + [DataField, AutoNetworkedField] + public bool TargetMobstateEntity = true; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs new file mode 100644 index 0000000000..047d6f0374 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs @@ -0,0 +1,91 @@ +using Content.Shared.Physics; +using Robust.Shared.GameStates; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers whenever an entity collides with a fixture attached to the owner of this component. +/// The user is the entity that collided with the fixture. +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class TriggerOnProximityComponent : BaseTriggerOnXComponent +{ + /// + /// The ID if the fixture that is observed for collisions. + /// + public const string FixtureID = "trigger-on-proximity-fixture"; + + /// + /// Currently colliding entities. + /// + [ViewVariables] + public readonly Dictionary Colliding = new(); + + /// + /// What is the shape of the proximity fixture? + /// + [ViewVariables] + [DataField] + public IPhysShape Shape = new PhysShapeCircle(2f); + + /// + /// How long the the proximity trigger animation plays for. + /// + [DataField, AutoNetworkedField] + public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f); + + /// + /// Whether the entity needs to be anchored for the proximity to work. + /// + [DataField, AutoNetworkedField] + public bool RequiresAnchored = true; + + /// + /// Whether the proximity trigger is currently enabled. + /// + [DataField, AutoNetworkedField] + public bool Enabled = true; + + /// + /// The minimum delay between repeating triggers. + /// + [DataField, AutoNetworkedField] + public TimeSpan Cooldown = TimeSpan.FromSeconds(5); + + /// + /// When can the trigger run again? + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan NextTrigger = TimeSpan.Zero; + + /// + /// When will the visual state be updated again after activation? + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan NextVisualUpdate = TimeSpan.Zero; + + /// + /// What speed should the other object be moving at to trigger the proximity fixture? + /// + [DataField, AutoNetworkedField] + public float TriggerSpeed = 3.5f; + + /// + /// If this proximity is triggered should we continually repeat it? + /// + [DataField, AutoNetworkedField] + public bool Repeating = true; + + /// + /// What layer is the trigger fixture on? + /// + [DataField(customTypeSerializer: typeof(FlagSerializer))] + public int Layer = (int)(CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable); +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs new file mode 100644 index 0000000000..6ed81c5fcf --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.DeviceLinking; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Sends a trigger when signal is received. +/// The user is the sender of the signal. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnSignalComponent : BaseTriggerOnXComponent +{ + /// + /// The sink port prototype we can connect devices to. + /// + [DataField, AutoNetworkedField] + public ProtoId Port = "Trigger"; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs new file mode 100644 index 0000000000..b0381ee74e --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers an entity when someone slipped on it. +/// The user is the entity that was slipped. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnSlipComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs new file mode 100644 index 0000000000..c718a20148 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when the entity is initialized. +/// The user is null. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnSpawnComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs new file mode 100644 index 0000000000..70d33a1179 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers if a StepTrigger is activated by someone stepping on this entity. +/// The user is the mob who stepped on it. +/// +/// +/// This is used for entities that want the more generic 'trigger' behavior after a step trigger occurs. +/// Not done by default, since it's not useful for everything and might cause weird behavior. But it is useful for a lot of stuff like mousetraps. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnStepTriggerComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs new file mode 100644 index 0000000000..073a64f66e --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Sticky.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when an entity with is stuck to something. +/// The user is the player doing so. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnStuckComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs new file mode 100644 index 0000000000..185ea7dbe4 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when the entity is overlapped for the specified duration. +/// The user is the entity that passes the time threshold while colliding. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnTimedCollideComponent : BaseTriggerOnXComponent +{ + /// + /// The time an entity has to collide until the trigger is activated. + /// + [DataField, AutoNetworkedField] + public TimeSpan Threshold = TimeSpan.FromSeconds(1); + + /// + /// A collection of entities that are currently colliding with this, and their own unique accumulator. + /// + /// + /// TODO: Add AutoPausedField and (de)serialize values as time offsets when https://github.com/space-wizards/RobustToolbox/issues/3768 is fixed. + /// + [DataField, AutoNetworkedField] + public Dictionary Colliding = new(); +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs new file mode 100644 index 0000000000..71d289741e --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers on use in hand. +/// The user is the player holding the item. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnUseComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs new file mode 100644 index 0000000000..463860f077 --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs @@ -0,0 +1,20 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Starts a trigger when a verb is selected. +/// The user is the player selecting the verb. +/// +/// +/// TODO: Support multiple verbs and trigger keys. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnVerbComponent : BaseTriggerOnXComponent +{ + /// + /// The text to display in the verb. + /// + [DataField, AutoNetworkedField] + public LocId Text = "trigger-on-verb-default"; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs new file mode 100644 index 0000000000..a36992d7da --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs @@ -0,0 +1,47 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Sends a trigger when the keyphrase is heard. +/// The User is the speaker. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent +{ + /// + /// Whether or not the component is actively listening at the moment. + /// + [ViewVariables] + public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase); + + /// + /// The keyphrase that has been set to trigger it. + /// + [DataField, AutoNetworkedField] + public string? KeyPhrase; + + /// + /// Range in which we listen for the keyphrase. + /// + [DataField, AutoNetworkedField] + public int ListenRange = 4; + + /// + /// Whether we are currently recording a new keyphrase. + /// + [DataField, AutoNetworkedField] + public bool IsRecording; + + /// + /// Minimum keyphrase length. + /// + [DataField, AutoNetworkedField] + public int MinLength = 3; + + /// + /// Maximum keyphrase length. + /// + [DataField, AutoNetworkedField] + public int MaxLength = 50; +} diff --git a/Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs b/Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs new file mode 100644 index 0000000000..f0161c7175 --- /dev/null +++ b/Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs @@ -0,0 +1,58 @@ +using Content.Shared.Trigger.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Trigger.Components; + +/// +/// After being triggered applies the specified components and runs triggers again. +/// +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class TwoStageTriggerComponent : Component +{ + /// + /// The keys that will activate the timer and add the given components (first stage). + /// + [DataField, AutoNetworkedField] + public List KeysIn = new() { TriggerSystem.DefaultTriggerKey }; + + /// + /// The key that will trigger once the timer is finished (second stage). + /// + [DataField, AutoNetworkedField] + public string? KeyOut = "stageTwo"; + + /// + /// How long it takes for the second stage to be triggered. + /// + [DataField, AutoNetworkedField] + public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10); + + /// + /// This list of components that will be added on the first trigger. + /// + [DataField(required: true)] + public ComponentRegistry Components = new(); + + /// + /// The time at which the second stage will trigger. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan? NextTriggerTime; + + /// + /// Has this entity been triggered already? + /// Used to prevent the components from being added multiple times. + /// + [DataField, AutoNetworkedField] + public bool Triggered = false; + + /// + /// The entity that activated this trigger. + /// + [DataField, AutoNetworkedField] + public EntityUid? User; +} diff --git a/Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs new file mode 100644 index 0000000000..908307dad0 --- /dev/null +++ b/Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class AddComponentsOnTriggerSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + if (ent.Comp.TriggerOnce && ent.Comp.Triggered) + return; + + EntityManager.AddComponents(target.Value, ent.Comp.Components, ent.Comp.RemoveExisting); + ent.Comp.Triggered = true; + Dirty(ent); + + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs new file mode 100644 index 0000000000..8f30c852ea --- /dev/null +++ b/Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.Damage; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class DamageOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + var damage = new DamageSpecifier(ent.Comp.Damage); + var ev = new BeforeDamageOnTriggerEvent(damage, target.Value); + RaiseLocalEvent(ent.Owner, ref ev); + + args.Handled |= _damageableSystem.TryChangeDamage(target, ev.Damage, ent.Comp.IgnoreResistances, origin: ent.Owner) is not null; + } +} + +/// +/// Raised on an entity before it deals damage using DamageOnTriggerComponent. +/// Used to modify the damage that will be dealt. +/// +[ByRefEvent] +public record struct BeforeDamageOnTriggerEvent(DamageSpecifier Damage, EntityUid Tripper); diff --git a/Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs new file mode 100644 index 0000000000..e296ccc177 --- /dev/null +++ b/Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs @@ -0,0 +1,55 @@ +using Content.Shared.Trigger.Components.Effects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; + +namespace Content.Shared.Trigger.Systems; + +public sealed class EmitSoundOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly INetManager _netMan = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + args.Handled |= TryEmitSound(ent, target.Value, args.User); + } + + private bool TryEmitSound(Entity ent, EntityUid target, EntityUid? user = null) + { + if (ent.Comp.Sound == null) + return false; + + if (ent.Comp.Positional) + { + var coords = Transform(target).Coordinates; + if (ent.Comp.Predicted) + _audio.PlayPredicted(ent.Comp.Sound, coords, user); + else if (_netMan.IsServer) + _audio.PlayPvs(ent.Comp.Sound, coords); + } + else + { + if (ent.Comp.Predicted) + _audio.PlayPredicted(ent.Comp.Sound, target, user); + else if (_netMan.IsServer) + _audio.PlayPvs(ent.Comp.Sound, target); + } + + return true; + } +} diff --git a/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs new file mode 100644 index 0000000000..136c4474a2 --- /dev/null +++ b/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.Emp; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class EmpOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly SharedEmpSystem _emp = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _emp.EmpPulse(_transform.GetMapCoordinates(target.Value), ent.Comp.Range, ent.Comp.EnergyConsumption, (float)ent.Comp.DisableDuration.TotalSeconds); + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs new file mode 100644 index 0000000000..1c773b79a6 --- /dev/null +++ b/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Explosion.EntitySystems; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class ExplodeOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly SharedExplosionSystem _explosion = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _explosion.TriggerExplosive(target.Value, user: args.User); + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs new file mode 100644 index 0000000000..6153e228bf --- /dev/null +++ b/Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Flash; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class FlashOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly SharedFlashSystem _flash = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + _flash.FlashArea(target.Value, args.User, ent.Comp.Range, ent.Comp.Duration, probability: ent.Comp.Probability); + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs new file mode 100644 index 0000000000..95ef5ec1db --- /dev/null +++ b/Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.Body.Systems; +using Content.Shared.Inventory; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed class GibOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + if (ent.Comp.DeleteItems) + { + var items = _inventory.GetHandOrInventoryEntities(target.Value); + foreach (var item in items) + { + PredictedQueueDel(item); + } + } + _body.GibBody(target.Value, true); + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs new file mode 100644 index 0000000000..9bedc87b6b --- /dev/null +++ b/Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Trigger; +using Content.Shared.Trigger.Components.Effects; +using Content.Shared.RepulseAttract; + +namespace Content.Shared.Trigger.Systems; + +public sealed class RepulseAttractOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly RepulseAttractSystem _repulse = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + var position = _transform.GetMapCoordinates(target.Value); + _repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask); + + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs new file mode 100644 index 0000000000..e1871e0435 --- /dev/null +++ b/Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared.Trigger.Components.Effects; +using Robust.Shared.Timing; + +namespace Content.Shared.Trigger.Systems; + +/// +/// Releases a gas mixture to the atmosphere when triggered. +/// Can also release gas over a set timespan to prevent trolling people +/// with the instant-wall-of-pressure-inator. +/// +public abstract class SharedReleaseGasOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + /// + /// Shrimply sets the component to active when triggered, allowing it to release over time. + /// + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + ent.Comp.Active = true; + ent.Comp.NextReleaseTime = _timing.CurTime; + ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles; + _appearance.SetData(ent, ReleaseGasOnTriggerVisuals.Key, true); + } +} diff --git a/Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs new file mode 100644 index 0000000000..c4d34af7e1 --- /dev/null +++ b/Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Electrocution; +using Content.Shared.Trigger.Components.Effects; +using Robust.Shared.Containers; + +namespace Content.Shared.Trigger.Systems; + +public sealed class ShockOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedElectrocutionSystem _electrocution = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + EntityUid? target; + if (ent.Comp.TargetContainer) + { + // shock whoever is wearing this clothing item + if (!_container.TryGetContainingContainer(ent.Owner, out var container)) + return; + target = container.Owner; + } + else + { + target = ent.Comp.TargetUser ? args.User : ent.Owner; + } + + if (target == null) + return; + + _electrocution.TryDoElectrocution(target.Value, null, ent.Comp.Damage, ent.Comp.Duration, true, ignoreInsulation: true); + args.Handled = true; + } + +} diff --git a/Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs new file mode 100644 index 0000000000..3825708550 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs @@ -0,0 +1,22 @@ +using Content.Shared.Implants.Components; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerOnActivateImplantSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivateImplant); + } + + private void OnActivateImplant(Entity ent, ref ActivateImplantEvent args) + { + _trigger.Trigger(ent.Owner, args.Performer, ent.Comp.KeyOut); + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs new file mode 100644 index 0000000000..cc23fa2b84 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Trigger.Components.Triggers; +using Content.Shared.Weapons.Ranged.Events; + +namespace Content.Shared.Trigger.Systems; +public sealed partial class TriggerOnEmptyGunshotSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEmptyGunShot); + } + + private void OnEmptyGunShot(Entity ent, ref OnEmptyGunShotEvent args) + { + _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut); + } +} diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs b/Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs similarity index 56% rename from Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs rename to Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs index ccd2a6e3df..68c109aef9 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs +++ b/Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs @@ -1,20 +1,25 @@ -using Content.Server.Explosion.Components; -using Content.Shared.Explosion.Components; -using Content.Shared.Implants; +using Content.Shared.Implants; using Content.Shared.Interaction.Events; using Content.Shared.Mobs; +using Content.Shared.Popups; +using Content.Shared.Trigger.Components.Triggers; -namespace Content.Server.Explosion.EntitySystems; +namespace Content.Shared.Trigger.Systems; -public sealed partial class TriggerSystem +public sealed partial class TriggerOnMobstateChangeSystem : EntitySystem { - private void InitializeMobstate() + [Dependency] private readonly TriggerSystem _trigger = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnSuicide); - SubscribeLocalEvent>(OnSuicideRelay); SubscribeLocalEvent>(OnMobStateRelay); + SubscribeLocalEvent>(OnSuicideRelay); } private void OnMobStateChanged(EntityUid uid, TriggerOnMobstateChangeComponent component, MobStateChangedEvent args) @@ -22,25 +27,21 @@ public sealed partial class TriggerSystem if (!component.MobState.Contains(args.NewMobState)) return; - //This chains Mobstate Changed triggers with OnUseTimerTrigger if they have it - //Very useful for things that require a mobstate change and a timer - if (TryComp(uid, out var timerTrigger)) - { - HandleTimerTrigger( - uid, - args.Origin, - timerTrigger.Delay, - timerTrigger.BeepInterval, - timerTrigger.InitialBeepDelay, - timerTrigger.BeepSound); - } - else - Trigger(uid); + _trigger.Trigger(uid, component.TargetMobstateEntity ? uid : args.Origin, component.KeyOut); + } + + private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent args) + { + if (!component.MobState.Contains(args.Event.NewMobState)) + return; + + _trigger.Trigger(uid, component.TargetMobstateEntity ? args.ImplantedEntity : args.Event.Origin, component.KeyOut); } /// /// Checks if the user has any implants that prevent suicide to avoid some cheesy strategies /// Prevents suicide by handling the event without killing the user + /// TODO: This doesn't seem to work at the moment as the event is never checked for being handled. /// private void OnSuicide(EntityUid uid, TriggerOnMobstateChangeComponent component, SuicideEvent args) { @@ -50,17 +51,19 @@ public sealed partial class TriggerSystem if (!component.PreventSuicide) return; - _popupSystem.PopupEntity(Loc.GetString("suicide-prevented"), args.Victim, args.Victim); + _popup.PopupClient(Loc.GetString("suicide-prevented"), args.Victim); args.Handled = true; } private void OnSuicideRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent args) { - OnSuicide(uid, component, args.Event); - } + if (args.Event.Handled) + return; - private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent args) - { - OnMobStateChanged(uid, component, args.Event); + if (!component.PreventSuicide) + return; + + _popup.PopupClient(Loc.GetString("suicide-prevented"), args.Event.Victim); + args.Event.Handled = true; } } diff --git a/Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs new file mode 100644 index 0000000000..6940ea52e2 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Slippery; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerOnSlipSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSlip); + } + + private void OnSlip(Entity ent, ref SlipEvent args) + { + _trigger.Trigger(ent.Owner, args.Slipped, ent.Comp.KeyOut); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs new file mode 100644 index 0000000000..d364adccff --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Sticky; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +public sealed class TriggerOnStuckSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStuck); + } + + private void OnStuck(Entity ent, ref EntityStuckEvent args) + { + _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs new file mode 100644 index 0000000000..d5830dd75d --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs @@ -0,0 +1,31 @@ +using Content.Shared.Verbs; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerOnVerbSystem : EntitySystem +{ + [Dependency] private readonly TriggerSystem _trigger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetAltVerbs); + } + + private void OnGetAltVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess || args.Hands == null) + return; + + var user = args.User; + + args.Verbs.Add(new AlternativeVerb + { + Text = Loc.GetString(ent.Comp.Text), + Act = () => _trigger.Trigger(ent.Owner, user, ent.Comp.KeyOut), + Priority = 2 // should be above any timer settings + }); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs new file mode 100644 index 0000000000..5243b13742 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs @@ -0,0 +1,73 @@ +using Content.Shared.Trigger.Components.Triggers; +using Content.Shared.StepTrigger.Systems; +using Robust.Shared.Physics.Events; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeCollide() + { + SubscribeLocalEvent(OnCollide); + SubscribeLocalEvent(OnStepTriggered); + + SubscribeLocalEvent(OnTimedCollide); + SubscribeLocalEvent(OnTimedEndCollide); + SubscribeLocalEvent(OnTimedShutdown); + } + + private void OnCollide(Entity ent, ref StartCollideEvent args) + { + if (args.OurFixtureId == ent.Comp.FixtureID && (!ent.Comp.IgnoreOtherNonHard || args.OtherFixture.Hard)) + Trigger(ent.Owner, args.OtherEntity, ent.Comp.KeyOut); + } + + private void OnStepTriggered(Entity ent, ref StepTriggeredOffEvent args) + { + Trigger(ent, args.Tripper, ent.Comp.KeyOut); + } + + private void OnTimedCollide(Entity ent, ref StartCollideEvent args) + { + //Ensures the trigger entity will have an active component + EnsureComp(ent); + var otherUID = args.OtherEntity; + if (ent.Comp.Colliding.ContainsKey(otherUID)) + return; + ent.Comp.Colliding.Add(otherUID, _timing.CurTime + ent.Comp.Threshold); + Dirty(ent); + } + + private void OnTimedEndCollide(Entity ent, ref EndCollideEvent args) + { + var otherUID = args.OtherEntity; + ent.Comp.Colliding.Remove(otherUID); + Dirty(ent); + + if (ent.Comp.Colliding.Count == 0) + RemComp(ent); + } + + private void OnTimedShutdown(Entity ent, ref ComponentShutdown args) + { + RemComp(ent); + } + + private void UpdateTimedCollide() + { + var curTime = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide)) + { + foreach (var (collidingEntity, collidingTime) in triggerOnTimedCollide.Colliding) + { + if (curTime > collidingTime) + { + triggerOnTimedCollide.Colliding[collidingEntity] += triggerOnTimedCollide.Threshold; + Dirty(uid, triggerOnTimedCollide); + Trigger(uid, collidingEntity, triggerOnTimedCollide.KeyOut); + } + } + } + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs new file mode 100644 index 0000000000..a917f1ad48 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs @@ -0,0 +1,57 @@ +using Content.Shared.Trigger.Components.Conditions; +using Content.Shared.Verbs; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeCondition() + { + SubscribeLocalEvent(OnWhitelistTriggerAttempt); + + SubscribeLocalEvent(OnUseDelayTriggerAttempt); + + SubscribeLocalEvent(OnToggleTriggerAttempt); + SubscribeLocalEvent>(OnToggleGetAltVerbs); + } + + private void OnWhitelistTriggerAttempt(Entity ent, ref AttemptTriggerEvent args) + { + if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) + args.Cancelled |= !_whitelist.CheckBoth(args.User, ent.Comp.UserBlacklist, ent.Comp.UserWhitelist); + } + + private void OnUseDelayTriggerAttempt(Entity ent, ref AttemptTriggerEvent args) + { + if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) + args.Cancelled |= _useDelay.IsDelayed(ent.Owner, ent.Comp.UseDelayId); + } + + private void OnToggleTriggerAttempt(Entity ent, ref AttemptTriggerEvent args) + { + if (args.Key == null || ent.Comp.Keys.Contains(args.Key)) + args.Cancelled |= !ent.Comp.Enabled; + } + + private void OnToggleGetAltVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess || args.Hands == null) + return; + + var user = args.User; + + args.Verbs.Add(new AlternativeVerb() + { + Text = Loc.GetString(ent.Comp.ToggleVerb), + Act = () => Toggle(ent, user) + }); + } + + private void Toggle(Entity ent, EntityUid user) + { + var msg = ent.Comp.Enabled ? ent.Comp.ToggleOff : ent.Comp.ToggleOn; + _popup.PopupPredicted(Loc.GetString(msg), ent.Owner, user); + ent.Comp.Enabled = !ent.Comp.Enabled; + Dirty(ent); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs new file mode 100644 index 0000000000..f506909760 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs @@ -0,0 +1,96 @@ +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Trigger.Components.Triggers; +using Content.Shared.Trigger.Components.Effects; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeInteraction() + { + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnUse); + + SubscribeLocalEvent(HandleItemToggleOnTrigger); + SubscribeLocalEvent(HandleAnchorOnTrigger); + SubscribeLocalEvent(HandleUseDelayOnTrigger); + } + + private void OnActivate(Entity ent, ref ActivateInWorldEvent args) + { + if (args.Handled) + return; + + if (ent.Comp.RequireComplex && !args.Complex) + return; + + Trigger(ent.Owner, args.User, ent.Comp.KeyOut); + args.Handled = true; + } + + private void OnUse(Entity ent, ref UseInHandEvent args) + { + if (args.Handled) + return; + + Trigger(ent.Owner, args.User, ent.Comp.KeyOut); + args.Handled = true; + } + + private void HandleItemToggleOnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (!TryComp(target, out var itemToggle)) + return; + + var handled = false; + if (itemToggle.Activated && ent.Comp.CanDeactivate) + handled = _itemToggle.TryDeactivate((target.Value, itemToggle), args.User, ent.Comp.Predicted, ent.Comp.ShowPopup); + else if (ent.Comp.CanActivate) + handled = _itemToggle.TryActivate((target.Value, itemToggle), args.User, ent.Comp.Predicted, ent.Comp.ShowPopup); + + args.Handled |= handled; + } + + private void HandleAnchorOnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + var xform = Transform(target.Value); + + if (xform.Anchored && ent.Comp.CanUnanchor) + _transform.Unanchor(target.Value, xform); + else if (ent.Comp.CanAnchor) + _transform.AnchorEntity(target.Value, xform); + + if (ent.Comp.RemoveOnTrigger) + RemCompDeferred(target.Value); + + args.Handled = true; + } + + private void HandleUseDelayOnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + args.Handled |= _useDelay.TryResetDelay(target.Value, ent.Comp.CheckDelayed); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs new file mode 100644 index 0000000000..cf7c11369b --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs @@ -0,0 +1,138 @@ +using Content.Shared.Trigger.Components.Triggers; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Events; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeProximity() + { + SubscribeLocalEvent(OnProximityStartCollide); + SubscribeLocalEvent(OnProximityEndCollide); + SubscribeLocalEvent(OnMapInit); + // Shouldn't need re-anchoring. + SubscribeLocalEvent(OnProximityAnchor); + } + + private void OnProximityAnchor(Entity ent, ref AnchorStateChangedEvent args) + { + ent.Comp.Enabled = !ent.Comp.RequiresAnchored || args.Anchored; + + SetProximityAppearance(ent); + + if (!ent.Comp.Enabled) + { + ent.Comp.Colliding.Clear(); + } + // Re-check for contacts as we cleared them. + else if (TryComp(ent, out var body)) + { + _physics.RegenerateContacts((ent.Owner, body)); + } + + Dirty(ent); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.Enabled = !ent.Comp.RequiresAnchored || Transform(ent).Anchored; + + SetProximityAppearance(ent); + + if (!TryComp(ent, out var body)) + return; + + _fixture.TryCreateFixture( + ent.Owner, + ent.Comp.Shape, + TriggerOnProximityComponent.FixtureID, + hard: false, + body: body, + collisionLayer: ent.Comp.Layer); + + Dirty(ent); + } + + private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args) + { + if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID) + return; + + component.Colliding[args.OtherEntity] = args.OtherBody; + } + + private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args) + { + if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID) + return; + + component.Colliding.Remove(args.OtherEntity); + } + + private void SetProximityAppearance(Entity ent) + { + _appearance.SetData(ent.Owner, ProximityTriggerVisualState.State, ent.Comp.Enabled ? ProximityTriggerVisuals.Inactive : ProximityTriggerVisuals.Off); + } + + private void Activate(Entity ent, EntityUid user) + { + var curTime = _timing.CurTime; + + if (!ent.Comp.Repeating) + { + ent.Comp.Enabled = false; + ent.Comp.Colliding.Clear(); + } + else + { + ent.Comp.NextTrigger = curTime + ent.Comp.Cooldown; + } + + // Queue a visual update for when the animation is complete. + ent.Comp.NextVisualUpdate = curTime + ent.Comp.AnimationDuration; + Dirty(ent); + + _appearance.SetData(ent.Owner, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active); + + Trigger(ent.Owner, user, ent.Comp.KeyOut); + } + + private void UpdateProximity() + { + var curTime = _timing.CurTime; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var trigger)) + { + if (curTime >= trigger.NextVisualUpdate) + { + // Update the visual state once the animation is done. + trigger.NextVisualUpdate = TimeSpan.MaxValue; + Dirty(uid, trigger); + SetProximityAppearance((uid, trigger)); + } + + if (!trigger.Enabled) + continue; + + if (curTime < trigger.NextTrigger) + // The trigger's on cooldown. + continue; + + // Check for anything colliding and moving fast enough. + foreach (var (collidingUid, colliding) in trigger.Colliding) + { + if (TerminatingOrDeleted(collidingUid)) + continue; + + if (colliding.LinearVelocity.Length() < trigger.TriggerSpeed) + continue; + + // Trigger! + Activate((uid, trigger), collidingUid); + break; + } + } + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs new file mode 100644 index 0000000000..fa5aa7ea6e --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs @@ -0,0 +1,44 @@ +using Content.Shared.Trigger.Components.Triggers; +using Content.Shared.Trigger.Components.Effects; +using Content.Shared.DeviceLinking.Events; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeSignal() + { + SubscribeLocalEvent(SignalOnTriggerInit); + SubscribeLocalEvent(TriggerOnSignalInit); + + SubscribeLocalEvent(HandleSignalOnTrigger); + SubscribeLocalEvent(OnSignalReceived); + } + + private void SignalOnTriggerInit(Entity ent, ref ComponentInit args) + { + _deviceLink.EnsureSourcePorts(ent.Owner, ent.Comp.Port); + } + + private void TriggerOnSignalInit(Entity ent, ref ComponentInit args) + { + _deviceLink.EnsureSinkPorts(ent.Owner, ent.Comp.Port); + } + + private void HandleSignalOnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + _deviceLink.InvokePort(ent.Owner, ent.Comp.Port); + args.Handled = true; + } + + private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) + { + if (args.Port != ent.Comp.Port) + return; + + Trigger(ent.Owner, args.Trigger, ent.Comp.KeyOut); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs new file mode 100644 index 0000000000..edcdd03894 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs @@ -0,0 +1,70 @@ +using Content.Shared.Trigger.Components.Effects; +using Content.Shared.Trigger.Components.Triggers; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + + private void InitializeSpawn() + { + SubscribeLocalEvent(OnSpawnInit); + + SubscribeLocalEvent(HandleSpawnOnTrigger); + SubscribeLocalEvent(HandleDeleteOnTrigger); + } + + private void OnSpawnInit(Entity ent, ref MapInitEvent args) + { + Trigger(ent.Owner, null, ent.Comp.KeyOut); + } + + private void HandleSpawnOnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + var xform = Transform(target.Value); + + if (ent.Comp.UseMapCoords) + { + var mapCoords = _transform.GetMapCoordinates(target.Value, xform); + if (ent.Comp.Predicted) + EntityManager.PredictedSpawn(ent.Comp.Proto, mapCoords); + else if (_net.IsServer) + Spawn(ent.Comp.Proto, mapCoords); + + } + else + { + var coords = xform.Coordinates; + if (!coords.IsValid(EntityManager)) + return; + + if (ent.Comp.Predicted) + PredictedSpawnAttachedTo(ent.Comp.Proto, coords); + else if (_net.IsServer) + SpawnAttachedTo(ent.Comp.Proto, coords); + + } + } + + private void HandleDeleteOnTrigger(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + var target = ent.Comp.TargetUser ? args.User : ent.Owner; + + if (target == null) + return; + + PredictedQueueDel(target); + args.Handled = true; + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs new file mode 100644 index 0000000000..776b17bda6 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs @@ -0,0 +1,179 @@ +using Content.Shared.Trigger.Components; +using Content.Shared.Trigger.Components.Triggers; +using Content.Shared.Examine; +using Content.Shared.Verbs; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeTimer() + { + SubscribeLocalEvent(OnRepeatInit); + SubscribeLocalEvent(OnRandomInit); + SubscribeLocalEvent(OnTimerShutdown); + SubscribeLocalEvent(OnTimerExamined); + SubscribeLocalEvent(OnTimerTriggered); + SubscribeLocalEvent>(OnTimerGetAltVerbs); + } + + // set the time of the first trigger after being spawned + private void OnRepeatInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay; + Dirty(ent); + } + + private void OnRandomInit(Entity ent, ref MapInitEvent args) + { + if (_net.IsClient) // Nextfloat will mispredict, so we set it on the server and dirty it + return; + + if (!TryComp(ent, out var timerTriggerComp)) + return; + + timerTriggerComp.Delay = TimeSpan.FromSeconds(_random.NextFloat(ent.Comp.Min, ent.Comp.Max)); + Dirty(ent.Owner, timerTriggerComp); + } + + private void OnTimerShutdown(Entity ent, ref ComponentShutdown args) + { + RemComp(ent); + } + + private void OnTimerExamined(Entity ent, ref ExaminedEvent args) + { + if (args.IsInDetailsRange && ent.Comp.Examinable) + args.PushText(Loc.GetString("timer-trigger-examine", ("time", ent.Comp.Delay.TotalSeconds))); + } + + private void OnTimerTriggered(Entity ent, ref TriggerEvent args) + { + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + args.Handled |= ActivateTimerTrigger(ent.AsNullable(), args.User); + } + + /// + /// Add an alt-click interaction that cycles through delays. + /// + private void OnTimerGetAltVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess || args.Hands == null) + return; + + if (ent.Comp.DelayOptions == null || ent.Comp.DelayOptions.Count == 1) + return; + + var user = args.User; + + args.Verbs.Add(new AlternativeVerb + { + Category = TimerOptions, + Text = Loc.GetString("timer-trigger-verb-cycle"), + Act = () => CycleDelay(ent, user), + Priority = 1 + }); + + foreach (var option in ent.Comp.DelayOptions) + { + if (MathHelper.CloseTo(option.TotalSeconds, ent.Comp.Delay.TotalSeconds)) + { + args.Verbs.Add(new AlternativeVerb + { + Category = TimerOptions, + Text = Loc.GetString("timer-trigger-verb-set-current", ("time", option.TotalSeconds)), + Disabled = true, + Priority = -100 * (int)option.TotalSeconds + }); + } + else + { + args.Verbs.Add(new AlternativeVerb + { + Category = TimerOptions, + Text = Loc.GetString("timer-trigger-verb-set", ("time", option.TotalSeconds)), + Priority = -100 * (int)option.TotalSeconds, + Act = () => + { + ent.Comp.Delay = option; + Dirty(ent); + _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option.TotalSeconds)), user, user); + } + }); + } + } + } + + public static readonly VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png"); + + /// + /// Select the next entry from the DelayOptions. + /// + private void CycleDelay(Entity ent, EntityUid? user) + { + if (ent.Comp.DelayOptions.Count <= 1) + return; + + // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short. + + ent.Comp.DelayOptions.Sort(); + Dirty(ent); + + if (ent.Comp.DelayOptions[^1] <= ent.Comp.Delay) + { + ent.Comp.Delay = ent.Comp.DelayOptions[0]; + _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", ent.Comp.Delay)), ent.Owner, user); + return; + } + + foreach (var option in ent.Comp.DelayOptions) + { + if (option > ent.Comp.Delay) + { + ent.Comp.Delay = option; + _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option)), ent.Owner, user); + return; + } + } + } + + private void UpdateRepeat() + { + var curTime = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.NextTrigger > curTime) + continue; + + comp.NextTrigger += comp.Delay; + Dirty(uid, comp); + Trigger(uid, null, comp.KeyOut); + } + } + + private void UpdateTimer() + { + var curTime = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var timer)) + { + if (_net.IsServer && timer.BeepSound != null && timer.NextBeep <= curTime) + { + _audio.PlayPvs(timer.BeepSound, uid); + timer.NextBeep += timer.BeepInterval; + } + + if (timer.NextTrigger <= curTime) + { + Trigger(uid, timer.User, timer.KeyOut); + // Remove after triggering to prevent it from starting the timer again + RemComp(uid); + if (TryComp(uid, out var appearance)) + _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance); + } + } + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs new file mode 100644 index 0000000000..ac67cb7ed2 --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs @@ -0,0 +1,160 @@ +using Content.Shared.Trigger.Components.Triggers; +using Content.Shared.Speech; +using Content.Shared.Speech.Components; +using Content.Shared.Database; +using Content.Shared.Examine; +using Content.Shared.Verbs; + +namespace Content.Shared.Trigger.Systems; + +public sealed partial class TriggerSystem +{ + private void InitializeVoice() + { + SubscribeLocalEvent(OnVoiceInit); + SubscribeLocalEvent(OnVoiceExamine); + SubscribeLocalEvent(OnListen); + SubscribeLocalEvent>(OnVoiceGetAltVerbs); + } + + private void OnVoiceInit(Entity ent, ref ComponentInit args) + { + if (ent.Comp.IsListening) + EnsureComp(ent).Range = ent.Comp.ListenRange; + else + RemCompDeferred(ent); + } + + private void OnVoiceExamine(Entity ent, ref ExaminedEvent args) + { + if (args.IsInDetailsRange) + { + args.PushText(string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase) + ? Loc.GetString("trigger-on-voice-uninitialized") + : Loc.GetString("trigger-on-voice-examine", ("keyphrase", ent.Comp.KeyPhrase))); + } + } + private void OnListen(Entity ent, ref ListenEvent args) + { + var component = ent.Comp; + var message = args.Message.Trim(); + + if (component.IsRecording) + { + var ev = new ListenAttemptEvent(args.Source); + RaiseLocalEvent(ent, ev); + + if (ev.Cancelled) + return; + + if (message.Length >= component.MinLength && message.Length <= component.MaxLength) + FinishRecording(ent, args.Source, args.Message); + else if (message.Length > component.MaxLength) + _popup.PopupEntity(Loc.GetString("trigger-on-voice-record-failed-too-long"), ent); + else if (message.Length < component.MinLength) + _popup.PopupEntity(Loc.GetString("trigger-on-voice-record-failed-too-short"), ent); + + return; + } + + if (!string.IsNullOrWhiteSpace(component.KeyPhrase) && message.IndexOf(component.KeyPhrase, StringComparison.InvariantCultureIgnoreCase) is var index and >= 0) + { + _adminLogger.Add(LogType.Trigger, LogImpact.Medium, + $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}."); + Trigger(ent, args.Source, ent.Comp.KeyOut); + + var messageWithoutPhrase = message.Remove(index, component.KeyPhrase.Length).Trim(); + var voice = new VoiceTriggeredEvent(args.Source, message, messageWithoutPhrase); + RaiseLocalEvent(ent, ref voice); + } + } + + private void OnVoiceGetAltVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var user = args.User; + args.Verbs.Add(new AlternativeVerb + { + Text = Loc.GetString(ent.Comp.IsRecording ? "trigger-on-voice-stop" : "trigger-on-voice-record"), + Act = () => + { + if (ent.Comp.IsRecording) + StopRecording(ent, user); + else + StartRecording(ent, user); + }, + Priority = 1 + }); + + if (string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase)) + return; + + args.Verbs.Add(new AlternativeVerb + { + Text = Loc.GetString("trigger-on-voice-clear"), + Act = () => + { + ClearRecording(ent); + } + }); + } + + /// + /// Start recording a new keyphrase. + /// + public void StartRecording(Entity ent, EntityUid? user) + { + ent.Comp.IsRecording = true; + Dirty(ent); + EnsureComp(ent).Range = ent.Comp.ListenRange; + + if (user == null) + _adminLogger.Add(LogType.Trigger, LogImpact.Low, $"A voice-trigger on {ToPrettyString(ent):entity} has started recording."); + else + _adminLogger.Add(LogType.Trigger, LogImpact.Low, $"A voice-trigger on {ToPrettyString(ent):entity} has started recording. User: {ToPrettyString(user.Value):user}"); + + _popup.PopupPredicted(Loc.GetString("trigger-on-voice-start-recording"), ent, user); + } + + /// + /// Stop recording without setting a keyphrase. + /// + public void StopRecording(Entity ent, EntityUid? user) + { + ent.Comp.IsRecording = false; + Dirty(ent); + if (string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase)) + RemComp(ent); + + _popup.PopupPredicted(Loc.GetString("trigger-on-voice-stop-recording"), ent, user); + } + + + /// + /// Stop recording and set the current keyphrase message. + /// + public void FinishRecording(Entity ent, EntityUid source, string message) + { + ent.Comp.KeyPhrase = message; + ent.Comp.IsRecording = false; + Dirty(ent); + + _adminLogger.Add(LogType.Trigger, LogImpact.Low, + $"A voice-trigger on {ToPrettyString(ent):entity} has recorded a new keyphrase: '{ent.Comp.KeyPhrase}'. Recorded from {ToPrettyString(source):speaker}"); + + _popup.PopupEntity(Loc.GetString("trigger-on-voice-recorded", ("keyphrase", ent.Comp.KeyPhrase)), ent); + } + + /// + /// Resets the key phrase and stops recording. + /// + public void ClearRecording(Entity ent) + { + ent.Comp.KeyPhrase = null; + ent.Comp.IsRecording = false; + Dirty(ent); + RemComp(ent); + } +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.cs b/Content.Shared/Trigger/Systems/TriggerSystem.cs new file mode 100644 index 0000000000..6a749b87ab --- /dev/null +++ b/Content.Shared/Trigger/Systems/TriggerSystem.cs @@ -0,0 +1,186 @@ +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.DeviceLinking; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Popups; +using Content.Shared.Timing; +using Content.Shared.Trigger.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Network; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Random; +using Robust.Shared.Audio.Systems; + + +namespace Content.Shared.Trigger.Systems; + +/// +/// System containing the basic trigger API. +/// +/// +/// If you add a custom trigger subscription or effect then don't put them here. +/// Put them into a separate system so we don't end up with a giant list of imports. +/// +public sealed partial class TriggerSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly FixtureSystem _fixture = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly UseDelaySystem _useDelay = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly ItemToggleSystem _itemToggle = default!; + [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!; + + public const string DefaultTriggerKey = "trigger"; + + public override void Initialize() + { + base.Initialize(); + + InitializeCollide(); + InitializeCondition(); + InitializeInteraction(); + InitializeProximity(); + InitializeSignal(); + InitializeTimer(); + InitializeSpawn(); + InitializeVoice(); + } + + /// + /// Trigger the given entity. + /// + /// The entity that has the components that should be triggered. + /// The user of the trigger. Some effects may target the user instead of the trigger entity. + /// A key string to allow multiple, independent triggers on the same entity. If null then all triggers will activate. + /// Whether or not the trigger has sucessfully activated an effect. + public bool Trigger(EntityUid trigger, EntityUid? user = null, string? key = null) + { + var attemptTriggerEvent = new AttemptTriggerEvent(user, key); + RaiseLocalEvent(trigger, ref attemptTriggerEvent); + if (attemptTriggerEvent.Cancelled) + return false; + + var triggerEvent = new TriggerEvent(user, key); + RaiseLocalEvent(trigger, ref triggerEvent, true); + return triggerEvent.Handled; + } + + /// + /// Activate a timer trigger on an entity with . + /// + /// Whether or not a timer was activated. + public bool ActivateTimerTrigger(Entity ent, EntityUid? user = null) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + if (HasComp(ent)) + return false; // already activated + + if (user != null) + { + _adminLogger.Add(LogType.Trigger, + $"{ToPrettyString(user.Value):user} started a {ent.Comp.Delay} second timer trigger on entity {ToPrettyString(ent.Owner):timer}"); + } + else + { + _adminLogger.Add(LogType.Trigger, + $"{ent.Comp.Delay} second timer trigger started on entity {ToPrettyString(ent.Owner):timer}"); + } + + if (ent.Comp.Popup != null) + _popup.PopupPredicted(Loc.GetString(ent.Comp.Popup.Value, ("device", ent.Owner)), ent.Owner, user); + + AddComp(ent); + var curTime = _timing.CurTime; + ent.Comp.NextTrigger = curTime + ent.Comp.Delay; + var delay = ent.Comp.InitialBeepDelay ?? ent.Comp.BeepInterval; + ent.Comp.NextBeep = curTime + delay; + Dirty(ent); + + var ev = new ActiveTimerTriggerEvent(user); + RaiseLocalEvent(ent.Owner, ref ev); + + if (TryComp(ent, out var appearance)) + _appearance.SetData(ent.Owner, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance); + + return true; + } + + /// + /// Stop a timer trigger on an entity with . + /// + /// Whether or not a timer was stopped. + public bool StopTimerTrigger(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + if (!HasComp(ent)) + return false; // the timer is not active + + RemComp(ent); + if (TryComp(ent.Owner, out var appearance)) + _appearance.SetData(ent.Owner, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance); + + _adminLogger.Add(LogType.Trigger, $"A timer trigger was stopped before triggering on entity {ToPrettyString(ent.Owner):timer}"); + return true; + } + + /// + /// Delay an active timer trigger. + /// Returns false if not active. + /// + /// The time to add. + public bool TryDelay(Entity ent, TimeSpan amount) + { + if (!Resolve(ent, ref ent.Comp, false) || !HasComp(ent)) + return false; + + ent.Comp.NextTrigger += amount; + Dirty(ent); + return true; + } + + /// + /// Setter for the Delay datafield. + /// + public void SetDelay(Entity ent, TimeSpan delay) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + ent.Comp.Delay = delay; + Dirty(ent); + } + + /// + /// Gets the remaining time until the trigger will activate. + /// Returns null if the trigger is not currently active. + /// + public TimeSpan? GetRemainingTime(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false) || !HasComp(ent)) + return null; // not a timer or not currently active + + return ent.Comp.NextTrigger - _timing.CurTime; + } + public override void Update(float frameTime) + { + base.Update(frameTime); + + UpdateTimer(); + UpdateRepeat(); + UpdateProximity(); + UpdateTimedCollide(); + } +} diff --git a/Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs b/Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs new file mode 100644 index 0000000000..2461e797dc --- /dev/null +++ b/Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs @@ -0,0 +1,51 @@ +using Robust.Shared.Timing; +using Content.Shared.Trigger.Components; + +namespace Content.Shared.Trigger.Systems; + +public sealed class TwoStageTriggerSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly TriggerSystem _triggerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + if (ent.Comp.Triggered) + return; // already triggered + + if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) + return; + + EntityManager.AddComponents(ent, ent.Comp.Components); + EnsureComp(ent); + ent.Comp.Triggered = true; + ent.Comp.NextTriggerTime = _timing.CurTime + ent.Comp.TriggerDelay; + ent.Comp.User = args.User; + Dirty(ent); + + args.Handled = true; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var curTime = _timing.CurTime; + var enumerator = EntityQueryEnumerator(); + while (enumerator.MoveNext(out var uid, out _, out var component)) + { + if (curTime < component.NextTriggerTime) + continue; + + RemComp(uid); + _triggerSystem.Trigger(uid, component.User, component.KeyOut); + } + } +} diff --git a/Content.Shared/Trigger/TriggerEvent.cs b/Content.Shared/Trigger/TriggerEvent.cs new file mode 100644 index 0000000000..e65e3b48a8 --- /dev/null +++ b/Content.Shared/Trigger/TriggerEvent.cs @@ -0,0 +1,33 @@ +namespace Content.Shared.Trigger; + +/// +/// Raised whenever something is Triggered on the entity. +/// +/// The entity that activated the trigger. +/// +/// Allows to have multiple independent triggers on the same entity. +/// Setting this to null will activate all triggers. +/// +/// Marks the event as handled if at least one trigger effect was activated. +[ByRefEvent] +public record struct TriggerEvent(EntityUid? User = null, string? Key = null, bool Handled = false); + +/// +/// Raised before a trigger is activated. +/// Cancelling prevents it from triggering. +/// +/// The entity that activated the trigger. +/// +/// Allows to have multiple independent triggers on the same entity. +/// Setting this to null will activate all triggers. +/// +/// Marks the event as handled if at least one trigger effect was activated. +[ByRefEvent] +public record struct AttemptTriggerEvent(EntityUid? User, string? Key = null, bool Cancelled = false); + +/// +/// Raised when a timer trigger becomes active. +/// +/// The entity that activated the trigger. +[ByRefEvent] +public readonly record struct ActiveTimerTriggerEvent(EntityUid? User); diff --git a/Content.Shared/Trigger/TriggerVisuals.cs b/Content.Shared/Trigger/TriggerVisuals.cs index ed544f3590..f1d4a55fb1 100644 --- a/Content.Shared/Trigger/TriggerVisuals.cs +++ b/Content.Shared/Trigger/TriggerVisuals.cs @@ -1,31 +1,30 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Trigger +namespace Content.Shared.Trigger; + +[Serializable, NetSerializable] +public enum ProximityTriggerVisuals : byte { - [Serializable, NetSerializable] - public enum ProximityTriggerVisuals : byte - { - Off, - Inactive, - Active, - } - - [Serializable, NetSerializable] - public enum ProximityTriggerVisualState : byte - { - State, - } - - [Serializable, NetSerializable] - public enum TriggerVisuals : byte - { - VisualState, - } - - [Serializable, NetSerializable] - public enum TriggerVisualState : byte - { - Primed, - Unprimed, - } + Off, + Inactive, + Active, +} + +[Serializable, NetSerializable] +public enum ProximityTriggerVisualState : byte +{ + State, +} + +[Serializable, NetSerializable] +public enum TriggerVisuals : byte +{ + VisualState, +} + +[Serializable, NetSerializable] +public enum TriggerVisualState : byte +{ + Primed, + Unprimed, } diff --git a/Content.Shared/Trigger/VoiceTriggeredEvent.cs b/Content.Shared/Trigger/VoiceTriggeredEvent.cs new file mode 100644 index 0000000000..af138b56cf --- /dev/null +++ b/Content.Shared/Trigger/VoiceTriggeredEvent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Trigger; + +/// +/// Raised when a voice trigger is activated, containing the message that triggered it. +/// +/// The EntityUid of the entity sending the message +/// The contents of the message +/// The message without the phrase that triggered it. +[ByRefEvent] +public readonly record struct VoiceTriggeredEvent(EntityUid Source, string? Message, string MessageWithoutPhrase); diff --git a/Content.Shared/Weapons/Ranged/Events/OnEmptyGunShotEvent.cs b/Content.Shared/Weapons/Ranged/Events/OnEmptyGunShotEvent.cs index efae42952b..998fa8f0eb 100644 --- a/Content.Shared/Weapons/Ranged/Events/OnEmptyGunShotEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/OnEmptyGunShotEvent.cs @@ -4,4 +4,4 @@ namespace Content.Shared.Weapons.Ranged.Events; /// Raised directed on the gun when trying to fire it while it's out of ammo /// [ByRefEvent] -public record struct OnEmptyGunShotEvent(EntityUid EmptyGun); +public record struct OnEmptyGunShotEvent(EntityUid User); diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index ba8254f68a..61ee8cdada 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -341,7 +341,7 @@ public abstract partial class SharedGunSystem : EntitySystem if (ev.Ammo.Count <= 0) { // triggers effects on the gun if it's empty - var emptyGunShotEvent = new OnEmptyGunShotEvent(); + var emptyGunShotEvent = new OnEmptyGunShotEvent(user); RaiseLocalEvent(gunUid, ref emptyGunShotEvent); gun.BurstActivated = false; diff --git a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl index d7a2636e11..40dc681405 100644 --- a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl @@ -25,11 +25,8 @@ signal-port-description-close = Closes a device. signal-port-name-doorbolt = Door bolt signal-port-description-doorbolt = Bolts door when HIGH. -signal-port-name-trigger = Trigger -signal-port-description-trigger = Triggers some mechanism on the device. - -signal-port-name-timer = Timer -signal-port-description-timer = Starts the timer countdown of the device. +signal-port-name-trigger-receiver = Trigger +signal-port-description-trigger-receiver = Triggers some mechanism on the device. signal-port-name-order-sender = Order sender signal-port-description-order-sender = Cargo console order sender diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl index 89a978479e..d016a7e206 100644 --- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl @@ -25,8 +25,11 @@ signal-port-description-dockstatus = This port is invoked with HIGH when docked signal-port-name-middle = Middle signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position. -signal-port-name-timer-trigger = Timer Trigger -signal-port-description-timer-trigger = This port is invoked whenever the timer triggers. +signal-port-name-trigger-sender = Trigger +signal-port-description-trigger-sender = This port is invoked whenever the device triggers. + +signal-port-name-timer-trigger = Timer +signal-port-description-timer-trigger = This port is invoked whenever the timer is up. signal-port-name-timer-start = Timer Start signal-port-description-timer-start = This port is invoked whenever the timer starts. diff --git a/Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl b/Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl new file mode 100644 index 0000000000..acb06bda47 --- /dev/null +++ b/Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl @@ -0,0 +1 @@ +ghost-kick-on-trigger-default = Tripped over a kick mine, crashed through the fourth wall. diff --git a/Resources/Locale/en-US/triggers/timer-trigger.ftl b/Resources/Locale/en-US/triggers/timer-trigger.ftl new file mode 100644 index 0000000000..5d6d553b45 --- /dev/null +++ b/Resources/Locale/en-US/triggers/timer-trigger.ftl @@ -0,0 +1,10 @@ + +timer-trigger-verb-set = {$time} Seconds +timer-trigger-verb-set-current = {$time} Seconds (current) +timer-trigger-verb-cycle = Cycle Time Delay + +timer-trigger-examine = The timer is set to {$time} seconds. + +timer-trigger-popup-set = Timer set to {$time} seconds. + +timer-trigger-activated = You activate {THE($device)}. diff --git a/Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl b/Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl new file mode 100644 index 0000000000..874490686f --- /dev/null +++ b/Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl @@ -0,0 +1,7 @@ +toggle-trigger-condition-default-verb = Toggle device +toggle-trigger-condition-default-on = Device enabled. +toggle-trigger-condition-default-off = Device disabled. + +toggle-trigger-condition-stick-verb = Toggle auto-activation +toggle-trigger-condition-stick-on = The device will now activate automatically when planted. +toggle-trigger-condition-stick-off = The device will no longer activate automatically when planted. diff --git a/Resources/Locale/en-US/triggers/trigger-on-verb.ftl b/Resources/Locale/en-US/triggers/trigger-on-verb.ftl new file mode 100644 index 0000000000..d9342e7bdd --- /dev/null +++ b/Resources/Locale/en-US/triggers/trigger-on-verb.ftl @@ -0,0 +1,2 @@ +trigger-on-verb-default = Trigger +trigger-on-verb-detonation = Start detonation \ No newline at end of file diff --git a/Resources/Locale/en-US/triggers/trigger-on-voice.ftl b/Resources/Locale/en-US/triggers/trigger-on-voice.ftl new file mode 100644 index 0000000000..7ace486657 --- /dev/null +++ b/Resources/Locale/en-US/triggers/trigger-on-voice.ftl @@ -0,0 +1,12 @@ +trigger-on-voice-examine = The display reads: "{$keyphrase}" +trigger-on-voice-uninitialized = The display reads: Uninitialized... + +trigger-on-voice-record = Record +trigger-on-voice-stop = Stop +trigger-on-voice-clear = Clear recording + +trigger-on-voice-start-recording = Started recording. +trigger-on-voice-stop-recording = Stopped recording. +trigger-on-voice-record-failed-too-long = Message too long, try again. +trigger-on-voice-record-failed-too-short = Message too short, try again. +trigger-on-voice-recorded = Recorded successfully! diff --git a/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl b/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl deleted file mode 100644 index 9a10e9ef04..0000000000 --- a/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl +++ /dev/null @@ -1,16 +0,0 @@ - -verb-trigger-timer-set = {$time} Seconds -verb-trigger-timer-set-current = {$time} Seconds (current) -verb-trigger-timer-cycle = Cycle Time Delay - -examine-trigger-timer = The timer is set to {$time} seconds. - -popup-trigger-timer-set = Timer set to {$time} seconds. - -verb-start-detonation = Start detonation - -verb-toggle-start-on-stick = Toggle auto-activation -popup-start-on-stick-off = The device will no longer activate automatically when planted -popup-start-on-stick-on = The device will now activate automatically when planted - -trigger-activated = You activate {THE($device)}. diff --git a/Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl b/Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl deleted file mode 100644 index 07ee5948d5..0000000000 --- a/Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl +++ /dev/null @@ -1,12 +0,0 @@ -examine-trigger-voice = The display reads: "{$keyphrase}" -trigger-voice-uninitialized = The display reads: Uninitialized... - -verb-trigger-voice-record = Record -verb-trigger-voice-stop = Stop -verb-trigger-voice-clear = Clear recording - -popup-trigger-voice-start-recording = Started recording -popup-trigger-voice-stop-recording = Stopped recording -popup-trigger-voice-record-failed-too-long = Message too long, try again -popup-trigger-voice-record-failed-too-short = Message too short, try again -popup-trigger-voice-recorded = Recorded successfully diff --git a/Resources/Maps/Shuttles/ShuttleEvent/quark.yml b/Resources/Maps/Shuttles/ShuttleEvent/quark.yml index 49e27e3fa9..d8deb51141 100644 --- a/Resources/Maps/Shuttles/ShuttleEvent/quark.yml +++ b/Resources/Maps/Shuttles/ShuttleEvent/quark.yml @@ -1351,6 +1351,8 @@ entities: intensity: 50 type: SingularityDistortion - type: ExplodeOnTrigger + keysIn: + - stageTwo triggerDelay: 500 - proto: GravityGeneratorMini entities: @@ -1479,8 +1481,8 @@ entities: [bold]Crew of the NT-Quark[/bold] - [bold]Nuclear containment breach detected on long range - halcyon scanners. Seek immediate shelter. Avoid + [bold]Nuclear containment breach detected on long range + halcyon scanners. Seek immediate shelter. Avoid contaminated materials. Do not panic.[/bold] [color=red][bold]Alpha decay rates indicate estimated diff --git a/Resources/Prototypes/DeviceLinking/sink_ports.yml b/Resources/Prototypes/DeviceLinking/sink_ports.yml index a5313fcc4e..66e2bee27d 100644 --- a/Resources/Prototypes/DeviceLinking/sink_ports.yml +++ b/Resources/Prototypes/DeviceLinking/sink_ports.yml @@ -45,8 +45,8 @@ - type: sinkPort id: Trigger - name: signal-port-name-trigger - description: signal-port-description-trigger + name: signal-port-name-trigger-receiver + description: signal-port-description-trigger-receiver - type: sinkPort id: Timer diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml index 4a460e722d..302679c4d9 100644 --- a/Resources/Prototypes/DeviceLinking/source_ports.yml +++ b/Resources/Prototypes/DeviceLinking/source_ports.yml @@ -157,6 +157,12 @@ name: signal-port-name-power-discharging description: signal-port-description-power-discharging +- type: sourcePort + id: Trigger + name: signal-port-name-trigger-sender + description: signal-port-description-trigger-sender + defaultLinks: [ AutoClose, On, Open, Forward, Trigger, Timer ] + - type: sourcePort id: ItemDetected name: signal-port-name-item-detected diff --git a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml index 4d406e9508..fc3e557bea 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml @@ -339,9 +339,9 @@ equipDelay: 5 # to avoid accidentally falling into the trap associated with SelfUnremovableClothing - type: SelfUnremovableClothing - type: ShockOnTrigger + targetContainer: true damage: 5 duration: 3 - cooldown: 4 - type: TriggerOnSignal - type: DeviceLinkSink ports: diff --git a/Resources/Prototypes/Entities/Effects/admin_triggers.yml b/Resources/Prototypes/Entities/Effects/admin_triggers.yml index 52c5a6c110..df9e11bcff 100644 --- a/Resources/Prototypes/Entities/Effects/admin_triggers.yml +++ b/Resources/Prototypes/Entities/Effects/admin_triggers.yml @@ -7,7 +7,7 @@ sprite: /Textures/Objects/Fun/goldbikehorn.rsi visible: false state: icon - - type: TriggerOnSpawn + - type: TriggerOnSpawn - type: TimedDespawn lifetime: 5 @@ -25,7 +25,7 @@ suffix: BluespaceFlash parent: AdminInstantEffectBase components: - - type: SpawnOnTrigger + - type: SpawnOnTrigger proto: EffectFlashBluespace - type: entity @@ -35,9 +35,9 @@ components: - type: FlashOnTrigger range: 7 - - type: SpawnOnTrigger + - type: SpawnOnTrigger proto: GrenadeFlashEffect - + - type: entity id: AdminInstantEffectSmoke3 suffix: Smoke (03 sec) @@ -46,12 +46,12 @@ - type: SmokeOnTrigger duration: 3 spreadAmount: 1 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger sound: /Audio/Effects/smoke.ogg - type: TimerTriggerVisuals primingSound: path: /Audio/Effects/Smoke-grenade.ogg - + - type: entity id: AdminInstantEffectSmoke10 suffix: Smoke (10 sec) @@ -60,12 +60,12 @@ - type: SmokeOnTrigger duration: 10 spreadAmount: 30 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger sound: /Audio/Effects/smoke.ogg - type: TimerTriggerVisuals primingSound: path: /Audio/Effects/Smoke-grenade.ogg - + - type: entity id: AdminInstantEffectSmoke30 suffix: Smoke (30 sec) @@ -74,7 +74,7 @@ - type: SmokeOnTrigger duration: 30 spreadAmount: 50 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger sound: /Audio/Effects/smoke.ogg - type: TimerTriggerVisuals primingSound: @@ -97,12 +97,12 @@ id: AdminInstantEffectGravityWell suffix: Gravity Well parent: AdminInstantEffectBase - components: - - type: SoundOnTrigger - removeOnTrigger: true + components: + - type: EmitSoundOnTrigger sound: path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg - volume: 5 + params: + volume: 5 - type: AmbientSound enabled: true volume: -5 @@ -117,4 +117,4 @@ - type: SingularityDistortion intensity: 10 falloffPower: 1.5 - + diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 714c740cf8..f8a12920cd 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -264,7 +264,7 @@ receiveFrequencyId: CyborgControl transmitFrequencyId: RoboticsConsole savableAddress: false - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 10 examinable: false beepSound: @@ -272,8 +272,9 @@ params: volume: -4 # prevent any funnies if someone makes a cyborg item... - - type: AutomatedTimer - type: ExplodeOnTrigger + keysIn: + - timer # explosion does most of its damage in the center and less at the edges - type: Explosive explosionType: Minibomb diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index dcf40dfcde..41a66d2ef0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -2370,7 +2370,8 @@ - type: Item size: Normal sprite: Mobs/Animals/grenadepenguin.rsi - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger delay: 10 beepSound: path: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg #funny sfx use @@ -2383,6 +2384,8 @@ intensitySlope: 20 totalIntensity: 225 - type: ExplodeOnTrigger + keysIn: + - timer - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml index 77bb8e1a7f..ba4c5e3698 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml @@ -53,11 +53,6 @@ critThreshold: 120 - type: Destructible thresholds: - - trigger: - !type:DamageTrigger - damage: 100 - behaviors: - - !type:TriggerBehavior - trigger: !type:DamageTrigger damage: 120 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml index 2748462abc..c031559b58 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml @@ -164,7 +164,7 @@ payloadSlot: whitelist: components: - - OnUseTimerTrigger + - TimerTrigger insertSound: path: /Audio/Weapons/Guns/Empty/empty.ogg ejectSound: diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/igniter.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/igniter.yml index 24843f608a..87bef747d8 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/igniter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/igniter.yml @@ -10,6 +10,11 @@ - type: IgnitionSource temperature: 800 - type: IgniteOnTrigger + - type: EmitSoundOnTrigger + sound: + collection: WelderOn + - type: UseDelayOnTrigger + - type: UseDelayTriggerCondition - type: TriggerOnSignal - type: DeviceNetwork deviceNetId: Wireless diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml index 0a975702aa..4b3d98f198 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml @@ -10,6 +10,10 @@ sprite: Objects/Devices/signaller.rsi state: signaller - type: Signaller + - type: SignalOnTrigger + port: Pressed + - type: UseDelayOnTrigger + - type: UseDelayTriggerCondition - type: UseDelay - type: StaticPrice price: 40 @@ -43,4 +47,4 @@ - type: WirelessNetworkConnection range: 50 - type: StaticPrice - price: 30 \ No newline at end of file + price: 30 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml index 67374a81f1..c6943a63e3 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml @@ -22,7 +22,13 @@ price: 40 - type: PayloadTrigger components: - - type: OnUseTimerTrigger + - type: TriggerOnUse + keyOut: startTimer + - type: TimerTrigger + keysIn: + - startTimer + # use the default trigger key so it can activate any inserted payload + keyOut: trigger delay: 5 delayOptions: [3, 5, 10, 15, 30] initialBeepDelay: 0 @@ -47,14 +53,19 @@ - SignalTrigger - type: PayloadTrigger components: - - type: TimerStartOnSignal - type: DeviceNetwork deviceNetId: Wireless receiveFrequencyId: BasicDevice - type: WirelessNetworkConnection range: 200 - type: DeviceLinkSink - - type: OnUseTimerTrigger + - type: TriggerOnSignal + keyOut: startTimer + - type: TimerTrigger + keysIn: + - startTimer + # use the default trigger key so it can activate any inserted payload + keyOut: trigger delay: 3 initialBeepDelay: 0 beepSound: diff --git a/Resources/Prototypes/Entities/Objects/Devices/desynchronizer.yml b/Resources/Prototypes/Entities/Objects/Devices/desynchronizer.yml index d5f8352baa..526d297f60 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/desynchronizer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/desynchronizer.yml @@ -9,6 +9,7 @@ state: icon - type: TriggerOnUse - type: PolymorphOnTrigger + targetUser: true polymorph: VoidPocket - type: UseDelay delay: 220 # long delay to ensure it can't be spammed, use it wisely diff --git a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml index 04349c3a1c..f7be194370 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml @@ -8,32 +8,37 @@ sprite: Objects/Devices/mousetrap.rsi drawdepth: SmallMobs # if mice can hide under tables, so can mousetraps layers: - - state: mousetrap - map: ["base"] + - state: mousetrap + map: ["enum.ToggleableVisuals.Layer"] - type: StepTrigger intersectRatio: 0.2 requiredTriggeredSpeed: 2 - type: Mousetrap + - type: ItemToggle + soundActivate: "/Audio/Items/Handcuffs/cuff_end.ogg" + soundDeactivate: "/Audio/Items/snap.ogg" + popupActivate: mousetrap-on-activate + popupDeactivate: mousetrap-on-deactivate + - type: UseDelay + - type: ItemToggleOnTrigger + canActivate: false + showPopup: false # only show the popup when arming/disarming the trap in your hand - type: TriggerOnStepTrigger - type: PreventableStepTrigger - - type: DamageUserOnTrigger + - type: DamageOnTrigger + targetUser: true damage: types: Blunt: 2 # base damage, scales based on mass - - type: EmitSoundOnUse - sound: "/Audio/Items/Handcuffs/cuff_end.ogg" - handle: false - - type: EmitSoundOnTrigger - sound: "/Audio/Items/snap.ogg" - type: Item sprite: Objects/Devices/mousetrap.rsi - type: Appearance - type: GenericVisualizer visuals: - enum.MousetrapVisuals.Visual: - base: - Armed: { state: mousetraparmed } - Unarmed: { state: mousetrap } + enum.ToggleableVisuals.Enabled: + enum.ToggleableVisuals.Layer: + True: {state: mousetraparmed} + False: {state: mousetrap} - type: Physics bodyType: Dynamic - type: CollisionWake @@ -66,6 +71,6 @@ - type: Sprite layers: - state: mousetraparmed - map: ["base"] - - type: Mousetrap - isActive: true + map: ["enum.ToggleableVisuals.Layer"] + - type: ItemToggle + activated: true diff --git a/Resources/Prototypes/Entities/Objects/Devices/payload.yml b/Resources/Prototypes/Entities/Objects/Devices/payload.yml index c161b2a028..e5dc07c7c1 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/payload.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/payload.yml @@ -94,3 +94,4 @@ - type: EmitSoundOnTrigger sound: path: "/Audio/Effects/flash_bang.ogg" + positional: true diff --git a/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml b/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml index c4be0aa453..f124be8a42 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml @@ -229,3 +229,4 @@ - MobMover - type: SpawnOnTrigger proto: EffectGravityPulse + predicted: true diff --git a/Resources/Prototypes/Entities/Objects/Fun/dice.yml b/Resources/Prototypes/Entities/Objects/Fun/dice.yml index 32df1f402d..132f92212e 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/dice.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/dice.yml @@ -127,7 +127,8 @@ path: /Audio/Effects/glass_step.ogg slipData: launchForwardsMultiplier: 0 - - type: DamageUserOnTrigger + - type: DamageOnTrigger + targetUser: true damage: types: Piercing: 5 diff --git a/Resources/Prototypes/Entities/Objects/Fun/figurines.yml b/Resources/Prototypes/Entities/Objects/Fun/figurines.yml index c99df854d2..7575457f4d 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/figurines.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/figurines.yml @@ -19,6 +19,8 @@ - Figurine - type: UseDelay delay: 5 + - type: UseDelayOnTrigger + - type: UseDelayTriggerCondition # prevent spam - type: TriggerOnActivate - type: TriggerOnSignal - type: Speech diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index d0be38d504..9ff94db3b1 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -892,7 +892,7 @@ - type: StepTrigger intersectRatio: 0.2 requiredTriggeredSpeed: 2 - - type: TriggerOnStepTrigger + - type: TriggerOnStepTrigger # for payloads - type: Appearance - type: CollisionWake enabled: false diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index 2457404b48..c649ef921c 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -72,7 +72,8 @@ slipData: launchForwardsMultiplier: 0 - type: TriggerOnStepTrigger - - type: DamageUserOnTrigger + - type: DamageOnTrigger + targetUser: true damage: types: Piercing: 5 @@ -91,7 +92,7 @@ - type: ToolRefinable refineResult: - id: SheetGlass1 - - type: DamageUserOnTrigger + - type: DamageOnTrigger damage: types: Piercing: 4.5 @@ -127,7 +128,7 @@ refineResult: - id: SheetGlass1 - id: PartRodMetal1 - - type: DamageUserOnTrigger + - type: DamageOnTrigger damage: types: Piercing: 5.5 @@ -163,7 +164,7 @@ refineResult: - id: SheetGlass1 - id: SheetPlasma1 - - type: DamageUserOnTrigger + - type: DamageOnTrigger damage: types: Piercing: 6.5 @@ -202,7 +203,7 @@ refineResult: - id: SheetGlass1 - id: SheetUranium1 - - type: DamageUserOnTrigger + - type: DamageOnTrigger damage: types: Piercing: 5 @@ -237,7 +238,7 @@ refineResult: - id: SheetGlass1 - id: SheetBrass1 - - type: DamageUserOnTrigger + - type: DamageOnTrigger damage: types: Piercing: 5 diff --git a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml index 7ed1b657ea..a3a2b60401 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml @@ -64,7 +64,8 @@ name: kick mine parent: BaseLandMine components: - - type: GhostKickUserOnTrigger + - type: GhostKickOnTrigger + targetUser: true - type: DeleteOnTrigger - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 7ddf4945d0..a369a730cf 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -61,17 +61,18 @@ description: This implant lets the user honk anywhere at any time. categories: [ HideSpawnMenu ] components: - - type: SubdermalImplant - implantAction: ActionActivateHonkImplant - - type: TriggerImplantAction - - type: EmitSoundOnTrigger - sound: - collection: BikeHorn - params: - variation: 0.125 - - type: Tag - tags: - - BikeHorn + - type: SubdermalImplant + implantAction: ActionActivateHonkImplant + - type: TriggerOnActivateImplant + - type: EmitSoundOnTrigger + predicted: true + sound: + collection: BikeHorn + params: + variation: 0.125 + - type: Tag + tags: + - BikeHorn #Security implants @@ -179,13 +180,13 @@ description: This implant creates an electromagnetic pulse when activated. categories: [ HideSpawnMenu ] components: - - type: SubdermalImplant - implantAction: ActionActivateEmpImplant - - type: TriggerImplantAction - - type: EmpOnTrigger - range: 2.75 - energyConsumption: 50000 - disableDuration: 10 + - type: SubdermalImplant + implantAction: ActionActivateEmpImplant + - type: TriggerOnActivateImplant + - type: EmpOnTrigger + range: 2.75 + energyConsumption: 50000 + disableDuration: 10 - type: entity parent: BaseSubdermalImplant @@ -194,10 +195,10 @@ description: This implant randomly teleports the user within a large radius when activated. categories: [ HideSpawnMenu ] components: - - type: SubdermalImplant - implantAction: ActionActivateScramImplant - - type: TriggerImplantAction - - type: ScramImplant + - type: SubdermalImplant + implantAction: ActionActivateScramImplant + - type: TriggerOnActivateImplant + - type: ScramImplant - type: entity parent: BaseSubdermalImplant @@ -238,27 +239,28 @@ description: This implant detonates the user upon activation or upon death. categories: [ HideSpawnMenu ] components: - - type: SubdermalImplant - permanent: true - implantAction: ActionActivateMicroBomb - - type: TriggerOnMobstateChange - mobState: - - Dead - - type: TriggerImplantAction - - type: ExplodeOnTrigger - - type: GibOnTrigger - deleteItems: true - - type: Explosive - explosionType: MicroBomb - totalIntensity: 120 - intensitySlope: 5 - maxIntensity: 30 - canCreateVacuum: false - - type: Tag - tags: - - SubdermalImplant - - HideContextMenu - - MicroBomb + - type: SubdermalImplant + permanent: true + implantAction: ActionActivateMicroBomb + - type: TriggerOnMobstateChange + mobState: + - Dead + - type: TriggerOnActivateImplant + - type: ExplodeOnTrigger + - type: GibOnTrigger + targetUser: true + deleteItems: true + - type: Explosive + explosionType: MicroBomb + totalIntensity: 120 + intensitySlope: 5 + maxIntensity: 30 + canCreateVacuum: false + - type: Tag + tags: + - SubdermalImplant + - HideContextMenu + - MicroBomb - type: entity @@ -270,20 +272,24 @@ components: - type: SubdermalImplant permanent: true - - type: TriggerOnMobstateChange #Chains with OnUseTimerTrigger + - type: TriggerOnMobstateChange #activates the timer mobState: - Dead preventSuicide: true - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 7 - initialBeepDelay: 0 beepSound: path: /Audio/Machines/Nuke/general_beep.ogg params: volume: -2 - type: ExplodeOnTrigger + keysIn: + - timer - type: GibOnTrigger + targetUser: true deleteItems: true + keysIn: + - timer - type: Explosive explosionType: Default totalIntensity: 3500 @@ -309,11 +315,13 @@ - type: TriggerOnMobstateChange mobState: - Dead - - type: TriggerImplantAction + - type: TriggerOnActivateImplant - type: GibOnTrigger + targetUser: true deleteItems: true - type: SpawnOnTrigger proto: Acidifier + predicted: true - type: Tag tags: - SubdermalImplant @@ -336,7 +344,8 @@ mobState: - Critical - Dead - - type: Rattle + - type: RattleOnTrigger + targetUser: true - type: entity parent: BaseSubdermalImplant @@ -382,5 +391,5 @@ description: This implant will inform the Centcomm radio channel should the user fall into critical condition or die. categories: [ HideSpawnMenu ] components: - - type: Rattle + - type: RattleOnTrigger radioChannel: CentCom diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index d9d4851158..fb740f1cd6 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -181,24 +181,31 @@ path: /Audio/Effects/beep_landmine.ogg params: maxDistance: 10 - - type: ExplodeOnTrigger - type: Explosive explosionType: HardBomb # normally Default and max 5 total 60 maxIntensity: 10 # about a ~67.5 total damage totalIntensity: 30 # about a ~3 tile radius canCreateVacuum: false + - type: ExplodeOnTrigger + keysIn: + - timer + - trigger # directly explode from the landmine - type: DeleteOnTrigger - - type: OnUseTimerTrigger - useVerbInstead: true + keysIn: + - timer + - trigger + - type: TimerTrigger beepInterval: .25 beepSound: path: /Audio/Items/Janitor/floor_sign_beep.ogg params: volume: 1 examinable: false + - type: TriggerOnVerb + text: trigger-on-verb-detonation - type: Tag tags: # ignore "WhitelistChameleon" tag - - WetFloorSign + - WetFloorSign - type: entity name: plunger diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 01283128e6..9f33e3c239 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -158,6 +158,7 @@ - type: Slippery - type: StepTrigger intersectRatio: 0.04 + - type: TriggerOnStepTrigger - type: Item heldPrefix: syndie - type: Fixtures @@ -178,10 +179,12 @@ - ItemMask - type: DeleteOnTrigger - type: EmitSoundOnTrigger + predicted: true + positional: true sound: path: "/Audio/Effects/Fluids/splat.ogg" params: - volume: -20 + volume: -8 - type: entity name: soap diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/firebomb.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/firebomb.yml index 23f5cc4362..fec2cca3ab 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/firebomb.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/firebomb.yml @@ -16,7 +16,8 @@ - state: wires - type: Item sprite: Objects/Weapons/Bombs/ied.rsi - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger delay: 5 examinable: false initialBeepDelay: 0 @@ -34,6 +35,8 @@ maxIntensity: 3 canCreateVacuum: false - type: ExplodeOnTrigger + keysIn: + - timer - type: Appearance - type: AnimationPlayer - type: TimerTriggerVisuals diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml index 73c021748c..298b9d7c12 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml @@ -23,13 +23,16 @@ damage: types: Blunt: 5 - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger delay: 120 beepSound: path: /Audio/Machines/Nuke/general_beep.ogg params: volume: -2 - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: Default maxIntensity: 50 @@ -37,6 +40,8 @@ totalIntensity: 100 canCreateVacuum: false - type: DeleteOnTrigger + keysIn: + - timer - type: HotPotato - type: DamageOnHolding enabled: false @@ -49,7 +54,7 @@ enum.Trigger.TriggerVisuals.VisualState: base: Primed: { state: activated } - Unprimed: { state: complete } + Unprimed: { state: icon } - type: entity id: HotPotatoEffect diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pen.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pen.yml index d553b2b2a4..8de013afff 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pen.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pen.yml @@ -5,7 +5,7 @@ description: A dark ink pen. id: PenExploding components: - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 4 examinable: false - type: Explosive @@ -16,8 +16,11 @@ canCreateVacuum: false - type: ActivateOnPaperOpened - type: ExplodeOnTrigger + keysIn: + - timer + - type: TriggerOnUse - type: TriggerOnSignal - - type: DeviceLinkSink # This should be changed into separate behavior where triggering via signal makes beep, while triggering manually is quiet, when that functionality is supported. + - type: DeviceLinkSink # This should be changed into separate behavior where triggering via signal makes beep, while triggering manually is quiet. ports: - Trigger - type: EmitSoundOnUse diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml index a39a1de671..a661224f31 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml @@ -10,7 +10,8 @@ - state: base map: ["enum.TriggerVisualLayers.Base"] - state: wires - - type: OnUseTimerTrigger # todo: make it activate through welder/lighter/fire instead + - type: TriggerOnActivate # todo: make it activate through welder/lighter/fire instead + - type: TimerTrigger delay: 5 examinable: false initialBeepDelay: 0 @@ -19,6 +20,8 @@ min: 1 max: 10 - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive # Weak explosion in a very small radius. Doesn't break underplating. explosionType: Default totalIntensity: 50 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml index 7e3b3b9e83..b1bfdeef9a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml @@ -33,7 +33,7 @@ enum.Trigger.TriggerVisuals.VisualState: base: Primed: { state: primed } - Unprimed: { state: complete } + Unprimed: { state: icon } - type: entity name: composition C-4 @@ -54,17 +54,27 @@ quickEquip: false equipDelay: 3 unequipDelay: 6 - - type: OnUseTimerTrigger + - type: TriggerOnActivate + - type: TriggerOnSignal + - type: TriggerOnStuck + keyOut: stuck + - type: ToggleTriggerCondition # for toggling the start on stuck ability + keys: + - stuck + toggleVerb: toggle-trigger-condition-stick-verb + toggleOn: toggle-trigger-condition-stick-on + toggleOff: toggle-trigger-condition-stick-off + - type: TimerTrigger + keysIn: + - trigger + - stuck delay: 10 delayOptions: [10, 30, 60, 120, 300] initialBeepDelay: 0 beepSound: /Audio/Machines/Nuke/general_beep.ogg - startOnStick: true - canToggleStartOnStick: true - - type: TimerStartOnSignal - type: DeviceLinkSink ports: - - Timer + - Trigger - type: Explosive # Powerful explosion in a very small radius. Doesn't break underplating. explosionType: DemolitionCharge totalIntensity: 60 @@ -72,6 +82,8 @@ maxIntensity: 30 canCreateVacuum: false - type: ExplodeOnTrigger + keysIn: + - timer - type: HolidayVisuals holidays: festive: @@ -94,7 +106,20 @@ layers: - state: icon map: ["base"] - - type: OnUseTimerTrigger + - type: TriggerOnActivate + - type: TriggerOnSignal + - type: TriggerOnStuck + keyOut: stuck + - type: ToggleTriggerCondition # for toggling the start on stuck ability + keys: + - stuck + toggleVerb: toggle-trigger-condition-stick-verb + toggleOn: toggle-trigger-condition-stick-on + toggleOff: toggle-trigger-condition-stick-off + - type: TimerTrigger + keysIn: + - trigger + - stuck delay: 5 delayOptions: [5, 10, 15, 20] initialBeepDelay: 0 @@ -102,12 +127,9 @@ path: /Audio/Effects/Cargo/buzz_two.ogg params: volume: -6 - startOnStick: false - canToggleStartOnStick: true - - type: TimerStartOnSignal - type: DeviceLinkSink ports: - - Timer + - Trigger - type: Explosive explosionType: Cryo totalIntensity: 120 @@ -115,3 +137,5 @@ maxIntensity: 30 canCreateVacuum: false - type: ExplodeOnTrigger + keysIn: + - timer diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml index 2ac23f8c3f..d00297a723 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml @@ -12,13 +12,12 @@ sprite: Objects/Weapons/Bombs/spidercharge.rsi size: Small - type: SpiderCharge - - type: OnUseTimerTrigger + - type: TriggerOnStuck + - type: TimerTrigger delay: 10 delayOptions: [5, 10, 30, 60] initialBeepDelay: 0 beepSound: /Audio/Machines/Nuke/general_beep.ogg - startOnStick: true - - type: AutomatedTimer - type: Sticky stickDelay: 5 stickPopupStart: comp-sticky-start-stick-bomb @@ -37,6 +36,8 @@ maxIntensity: 120 canCreateVacuum: true - type: ExplodeOnTrigger + keysIn: + - timer - type: StickyVisualizer - type: Appearance - type: GenericVisualizer @@ -44,4 +45,4 @@ enum.Trigger.TriggerVisuals.VisualState: base: Primed: { state: primed } - Unprimed: { state: complete } + Unprimed: { state: icon } diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml index c587787d48..9e2b6d76c1 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml @@ -161,6 +161,7 @@ - type: entity id: ProjectilePolyboltBase + abstract: true parent: BaseBullet categories: [ HideSpawnMenu ] components: @@ -175,6 +176,8 @@ Poison: 5 - type: TriggerOnCollide fixtureID: projectile + - type: PolymorphOnTrigger + targetUser: true - type: entity id: ProjectilePolyboltCarp @@ -185,8 +188,8 @@ components: - type: PolymorphOnTrigger polymorph: WizardForcedCarp - - type: TriggerWhitelist - whitelist: + - type: WhitelistTriggerCondition + userWhitelist: components: - Body @@ -199,8 +202,8 @@ components: - type: PolymorphOnTrigger polymorph: WizardForcedMonkey - - type: TriggerWhitelist - whitelist: + - type: WhitelistTriggerCondition + userWhitelist: components: - Body @@ -218,8 +221,8 @@ color: brown - type: PolymorphOnTrigger polymorph: WizardWallDoor - - type: TriggerWhitelist - whitelist: + - type: WhitelistTriggerCondition + userWhitelist: components: - Airlock - Firelock @@ -269,8 +272,8 @@ components: - type: PolymorphOnTrigger polymorph: WizardForcedCluwne - - type: TriggerWhitelist - whitelist: + - type: WhitelistTriggerCondition + userWhitelist: components: - Body @@ -299,7 +302,7 @@ components: - type: PolymorphOnTrigger polymorph: BreadMorph - - type: TriggerWhitelist - whitelist: + - type: WhitelistTriggerCondition + userWhitelist: components: - Body diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index e89792694d..1cf6ab9a94 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -879,8 +879,6 @@ range: 7 - type: SpawnOnTrigger proto: GrenadeFlashEffect - - type: ActiveTimerTrigger - timeRemaining: 0.3 - type: DeleteOnTrigger - type: entity @@ -917,8 +915,6 @@ sprite: Objects/Weapons/Guns/Projectiles/projectiles2.rsi layers: - state: cleanade - - type: ActiveTimerTrigger - timeRemaining: 0.3 - type: SmokeOnTrigger duration: 3.5 spreadAmount: 30 @@ -927,7 +923,7 @@ reagents: - ReagentId: SpaceCleaner Quantity: 30 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger sound: /Audio/Items/smoke_grenade_smoke.ogg - type: ExplodeOnTrigger - type: Explosive diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_ballistic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_ballistic.yml index c5dbb1b216..5259f71f58 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_ballistic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_ballistic.yml @@ -47,7 +47,7 @@ - type: Repairable qualityNeeded: "Anchoring" doAfterDelay: 3 - - type: TriggerWhenEmpty + - type: TriggerOnEmptyGunshot - type: ExplodeOnTrigger - type: Explosive explosionType: Default @@ -135,4 +135,4 @@ interactSuccessString: petting-success-generic interactFailureString: petting-failure-generic interactSuccessSound: - path: /Audio/Animals/snake_hiss.ogg \ No newline at end of file + path: /Audio/Animals/snake_hiss.ogg diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index 4d65cc0806..d843463886 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -14,7 +14,8 @@ quickEquip: false slots: - Belt - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger delay: 3 - type: Damageable damageContainer: Inorganic @@ -25,6 +26,7 @@ damage: 10 behaviors: - !type:TriggerBehavior + keyOut: timer # explode immediately - !type:DoActsBehavior acts: ["Destruction"] - type: Appearance @@ -56,13 +58,15 @@ id: ExGrenade components: - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: Default maxIntensity: 10 intensitySlope: 3 totalIntensity: 120 # about a ~4 tile radius canCreateVacuum: false - - type: OnUseTimerTrigger + - type: TimerTrigger beepSound: path: "/Audio/Effects/beep1.ogg" params: @@ -79,13 +83,22 @@ - type: Sprite sprite: Objects/Weapons/Grenades/flashbang.rsi - type: FlashOnTrigger + keysIn: + - timer range: 7 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Effects/flash_bang.ogg" - type: DeleteOnTrigger + keysIn: + - timer - type: SpawnOnTrigger + keysIn: + - timer proto: GrenadeFlashEffect + predicted: true - type: Appearance - type: TimerTriggerVisuals primingSound: @@ -137,11 +150,14 @@ damage: 45 behaviors: - !type:TriggerBehavior + keyOut: timer # immediately explode - !type:DoActsBehavior acts: ["Destruction"] - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 5 - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: Minibomb totalIntensity: 200 @@ -164,13 +180,15 @@ categories: [ HideSpawnMenu ] components: - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: Minibomb totalIntensity: 400 intensitySlope: 30 maxIntensity: 125 canCreateVacuum: true - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 4.5 beepSound: path: /Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg @@ -188,7 +206,7 @@ components: - type: Sprite sprite: Objects/Weapons/Grenades/singularitygrenade.rsi - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 3 beepInterval: 0.46 beepSound: @@ -200,14 +218,20 @@ path: /Audio/Effects/Grenades/Supermatter/smbeep.ogg params: volume: -5 - - type: SoundOnTrigger - removeOnTrigger: true + - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg - volume: 5 + params: + volume: 5 - type: AnchorOnTrigger + keysIn: + - timer removeOnTrigger: true - type: TwoStageTrigger + keysIn: + - timer triggerDelay: 10.45 components: - type: AmbientSound @@ -231,12 +255,16 @@ radius: 6 softness: 1 offset: "0, 0" - - type: SoundOnTrigger + - type: EmitSoundOnTrigger sound: path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg params: volume: 5 + keysIn: + - stageTwo - type: DeleteOnTrigger + keysIn: + - stageTwo - type: StaticPrice price: 1000 @@ -248,14 +276,20 @@ components: - type: Sprite sprite: Objects/Weapons/Grenades/whiteholegrenade.rsi - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 3 beepInterval: 0.69 - - type: SoundOnTrigger - removeOnTrigger: true + - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: /Audio/Effects/Grenades/Supermatter/whitehole_start.ogg - volume: 5 + params: + volume: 5 + - type: AnchorOnTrigger + keysIn: + - timer + removeOnTrigger: true - type: TwoStageTrigger triggerDelay: 11.14 components: @@ -280,12 +314,17 @@ radius: 6 softness: 1 offset: "0, 0" - - type: SoundOnTrigger + - type: EmitSoundOnTrigger + keysIn: + - stageTwo sound: path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg params: volume: 15 + positional: true - type: DeleteOnTrigger + keysIn: + - stageTwo - type: StaticPrice price: 1000 @@ -297,9 +336,11 @@ components: - type: Sprite sprite: Objects/Weapons/Grenades/nukenade.rsi - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 5 - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: Default totalIntensity: 20000 # ~15 tile radius. @@ -312,6 +353,7 @@ damage: 50 behaviors: - !type:TriggerBehavior + keyOut: timer # immediately explode - !type:DoActsBehavior acts: ["Destruction"] - type: Appearance @@ -376,9 +418,13 @@ - type: Sprite sprite: Objects/Weapons/Grenades/empgrenade.rsi - type: EmpOnTrigger + keysIn: + - timer range: 5.5 energyConsumption: 50000 - type: DeleteOnTrigger + keysIn: + - timer - type: Appearance - type: TimerTriggerVisuals primingSound: @@ -395,13 +441,15 @@ - type: Sprite sprite: Objects/Weapons/Grenades/holyhandgrenade.rsi - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: Default # same as macrobomb totalIntensity: 3500 intensitySlope: 15 maxIntensity: 70 canCreateVacuum: true - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 3 # by canon - type: PointLight radius: 7 @@ -422,11 +470,17 @@ - type: Sprite sprite: Objects/Weapons/Grenades/smoke.rsi - type: SmokeOnTrigger + keysIn: + - timer duration: 30 spreadAmount: 50 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger + keysIn: + - timer sound: /Audio/Items/smoke_grenade_smoke.ogg - type: DeleteOnTrigger + keysIn: + - timer - type: TimerTriggerVisuals primingSound: path: /Audio/Items/smoke_grenade_prime.ogg @@ -440,6 +494,8 @@ - type: Sprite sprite: Objects/Weapons/Grenades/janitor.rsi - type: SmokeOnTrigger + keysIn: + - timer duration: 15 spreadAmount: 50 smokePrototype: Foam @@ -457,6 +513,8 @@ - type: Sprite sprite: Objects/Weapons/Grenades/tear_gas.rsi - type: SmokeOnTrigger + keysIn: + - timer duration: 10 spreadAmount: 30 smokePrototype: TearGasSmokeWhite @@ -473,9 +531,11 @@ components: - type: Sprite sprite: Objects/Weapons/Grenades/metalfoam.rsi - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 5 - type: SmokeOnTrigger + keysIn: + - timer duration: 10 spreadAmount: 20 smokePrototype: AluminiumMetalFoam @@ -490,14 +550,18 @@ components: - type: Sprite sprite: Objects/Weapons/Grenades/airboom.rsi - - type: SoundOnTrigger + - type: EmitSoundOnTrigger + keysIn: + - timer sound: /Audio/Items/smoke_grenade_smoke.ogg - type: TimerTriggerVisuals primingSound: path: /Audio/Items/smoke_grenade_prime.ogg - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 3 - type: ReleaseGasOnTrigger + keysIn: + - timer removeFraction: 0.25 air: volume: 1000 @@ -525,9 +589,16 @@ - type: Sprite sprite: Objects/Weapons/Grenades/grenade.rsi - type: DeleteOnTrigger + keysIn: + - timer - type: SpawnOnTrigger + keysIn: + - timer proto: GrenadeFlashEffect - - type: SoundOnTrigger + predicted: true + - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: /Audio/Effects/Emotes/parp1.ogg - type: Appearance @@ -543,9 +614,11 @@ components: - type: Sprite sprite: Objects/Weapons/Grenades/syndgrenade.rsi - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 5 - - type: SoundOnTrigger + - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: /Audio/Effects/Emotes/parp1.ogg - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml index bdd8370b10..f9ce11a3b9 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml @@ -7,6 +7,8 @@ - type: Damageable damageContainer: Inorganic - type: DeleteOnTrigger + keysIn: + - timer - type: Destructible thresholds: - trigger: @@ -14,6 +16,7 @@ damage: 10 behaviors: - !type:TriggerBehavior + keyOut: timer # explode immediately - type: ContainerContainer containers: cluster-payload: !type:Container @@ -54,11 +57,17 @@ fillPrototype: PelletClusterRubber capacity: 30 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Effects/flash_bang.ogg" - type: SpawnOnTrigger + keysIn: + - timer proto: GrenadeFlashEffect - - type: OnUseTimerTrigger + predicted: true + - type: TriggerOnUse + - type: TimerTrigger initialBeepDelay: 0 beepInterval: 2 delay: 3.5 @@ -80,7 +89,8 @@ - type: ProjectileGrenade fillPrototype: PelletClusterIncendiary capacity: 30 - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger beepSound: path: "/Audio/Effects/beep1.ogg" params: @@ -89,6 +99,8 @@ beepInterval: 2 delay: 3.5 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" - type: StaticPrice @@ -108,7 +120,8 @@ - type: ProjectileGrenade fillPrototype: PelletClusterLethal capacity: 30 - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger beepSound: path: "/Audio/Effects/beep1.ogg" params: @@ -117,6 +130,8 @@ beepInterval: 2 delay: 3.5 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" - type: StaticPrice diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml index 9038df13e0..ce031ba6ff 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml @@ -17,8 +17,10 @@ damage: 10 behaviors: - !type:TriggerBehavior + keyOut: timer # explode immediately - type: ScatteringGrenade - - type: OnUseTimerTrigger + - type: TriggerOnUse + - type: TimerTrigger delay: 3 - type: Tag tags: @@ -81,6 +83,8 @@ Primed: { state: primed } Unprimed: { state: icon } - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Machines/door_lock_off.ogg" @@ -98,7 +102,7 @@ - type: ScatteringGrenade fillPrototype: ExGrenade distance: 4 - - type: OnUseTimerTrigger + - type: TimerTrigger beepSound: path: "/Audio/Effects/beep1.ogg" params: @@ -106,6 +110,8 @@ initialBeepDelay: 0 beepInterval: 0.5 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Machines/door_lock_off.ogg" - type: StaticPrice @@ -130,6 +136,8 @@ types: Blunt: 10 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Items/bikehorn.ogg" @@ -155,6 +163,8 @@ types: Blunt: 10 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Effects/flash_bang.ogg" - type: StaticPrice @@ -177,7 +187,7 @@ fillPrototype: BulletFoam capacity: 30 velocity: 30 - - type: OnUseTimerTrigger + - type: TimerTrigger beepSound: path: "/Audio/Effects/beep1.ogg" params: @@ -185,5 +195,7 @@ initialBeepDelay: 0 beepInterval: 2 - type: EmitSoundOnTrigger + keysIn: + - timer sound: path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 7f69d77f93..2f6ac834ba 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -192,6 +192,7 @@ description: An ultrabright flashbulb with a proximity trigger, useful for making an area security-only. components: - type: EmitSoundOnTrigger + predicted: true sound: path: /Audio/Weapons/flash.ogg - type: FlashOnTrigger diff --git a/Resources/Prototypes/Entities/Structures/Machines/bombs.yml b/Resources/Prototypes/Entities/Structures/Machines/bombs.yml index 65eb07f064..dc3aa359f4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/bombs.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/bombs.yml @@ -23,9 +23,11 @@ intensitySlope: 5 maxIntensity: 4 - type: ExplodeOnTrigger + keysIn: + - timer # If you nerf the syndicate bomb in any major way, this should probably drop down to at least 100s (not 90s to compensate for slower movement speed & less lag in SS14) # Unless, of course, you want the 90 seconds regardless. I can't stop you. - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 180 delayOptions: [180, 240, 300, 600, 900] initialBeepDelay: 0 @@ -150,6 +152,6 @@ components: - type: Defusable disposable: true - - type: OnUseTimerTrigger + - type: TimerTrigger delay: 10 delayOptions: [10, 20, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300] diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index 2070635676..d3f0d0e2cb 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -77,7 +77,7 @@ Unprimed: state: gibtonite_inactive visible: false - - type: OnUseTimerTrigger + - type: TimerTrigger examinable: false beepInterval: 0.4 beepSound: @@ -86,6 +86,8 @@ min: 8 max: 10 - type: ExplodeOnTrigger + keysIn: + - timer - type: Explosive explosionType: DemolitionCharge totalIntensity: 450 @@ -109,6 +111,7 @@ - !type:DoActsBehavior acts: ["Destruction"] - !type:TriggerBehavior + keyOut: timer # explode immediately # Ore veins - type: entity diff --git a/Resources/ServerInfo/Guidebook/Security/Defusal.xml b/Resources/ServerInfo/Guidebook/Security/Defusal.xml index 8ebd71a346..4d6880d0b8 100644 --- a/Resources/ServerInfo/Guidebook/Security/Defusal.xml +++ b/Resources/ServerInfo/Guidebook/Security/Defusal.xml @@ -29,7 +29,7 @@ To arm a bomb, you can either [color=yellow]right click[/color] and click [color=yellow]Begin countdown[/click], or [color=yellow]alt-click[/color] the bomb. It will begin beeping. ## Time - A bomb has a limited time, at a minimum of [protodata="SyndicateBomb" comp="OnUseTimerTrigger" member="ShortestDelayOption"/] seconds and a maximum of [protodata="SyndicateBomb" comp="OnUseTimerTrigger" member="LongestDelayOption"/] seconds. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate. + A bomb has a limited time, at a minimum of [protodata="SyndicateBomb" comp="TimerTrigger" member="ShortestDelayOption"/] seconds and a maximum of [protodata="SyndicateBomb" comp="TimerTrigger" member="LongestDelayOption"/] seconds. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate. ## Bolts By default, once armed, a bomb will bolt itself to the ground. You must find the BOLT wire and cut it to disable the bolts, after which you can unwrench it and throw it into space.