# Conflicts: # .github/CODEOWNERS # .github/workflows/publish.yml # Content.Client/Administration/UI/BanPanel/BanPanel.xaml # Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs # Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs # Content.Client/Entry/EntryPoint.cs # Content.Client/Hands/Systems/HandsSystem.cs # Content.Client/Humanoid/HumanoidAppearanceSystem.cs # Content.Client/Humanoid/SingleMarkingPicker.xaml.cs # Content.Client/IoC/ClientContentIoC.cs # Content.Client/Lobby/LobbyState.cs # Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs # Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs # Content.Client/Lobby/UI/LobbyGui.xaml # Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml # Content.Client/Options/UI/EscapeMenu.xaml # Content.Client/Stealth/StealthSystem.cs # Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorWindow.xaml.cs # Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs # Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs # Content.Client/Weapons/Ranged/Systems/GunSystem.cs # Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs # Content.IntegrationTests/Tests/Damageable/DamageableTest.cs # Content.IntegrationTests/Tests/PostMapInitTest.cs # Content.Server/Administration/Commands/AdminWhoCommand.cs # Content.Server/Administration/Commands/PardonCommand.cs # Content.Server/Administration/Commands/RoleUnbanCommand.cs # Content.Server/Administration/Commands/SetOutfitCommand.cs # Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs # Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs # Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs # Content.Server/Body/Components/BloodstreamComponent.cs # Content.Server/Body/Systems/BloodstreamSystem.cs # Content.Server/Body/Systems/MetabolizerSystem.cs # Content.Server/Body/Systems/RespiratorSystem.cs # Content.Server/Chat/Managers/ChatSanitizationManager.cs # Content.Server/Chat/Systems/ChatSystem.Emote.cs # Content.Server/Chat/Systems/ChatSystem.cs # Content.Server/CrewManifest/CrewManifestSystem.cs # Content.Server/Emp/EmpSystem.cs # Content.Server/Explosion/EntitySystems/TriggerSystem.cs # Content.Server/Fax/FaxSystem.cs # Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs # Content.Server/GameTicking/Commands/ForceMapCommand.cs # Content.Server/GameTicking/Commands/ForcePresetCommand.cs # Content.Server/GameTicking/Presets/GamePresetPrototype.cs # Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs # Content.Server/GameTicking/Rules/Components/ChangelingRuleComponent.cs # Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs # Content.Server/Ghost/Roles/GhostRoleSystem.cs # Content.Server/Implants/Components/ScramImplantComponent.cs # Content.Server/Implants/SubdermalImplantSystem.cs # Content.Server/Materials/ProduceMaterialExtractorSystem.cs # Content.Server/Medical/HealthAnalyzerSystem.cs # Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs # Content.Server/NPC/Systems/NPCSystem.cs # Content.Server/Polymorph/Systems/PolymorphSystem.cs # Content.Server/Radio/EntitySystems/HeadsetSystem.cs # Content.Server/Repairable/RepairableSystem.cs # Content.Server/Revenant/EntitySystems/RevenantSystem.cs # Content.Server/RoundEnd/RoundEndSystem.cs # Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs # Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs # Content.Server/Speech/EntitySystems/FrontalLispSystem.cs # Content.Server/Speech/EntitySystems/LizardAccentSystem.cs # Content.Server/Speech/EntitySystems/MothAccentSystem.cs # Content.Server/Speech/EntitySystems/OwOAccentSystem.cs # Content.Server/Station/Systems/StationSpawningSystem.cs # Content.Server/StationEvents/Events/MeteorSwarmSystem.cs # Content.Server/Stunnable/Components/StunOnCollideComponent.cs # Content.Server/SurveillanceCamera/Components/SurveillanceCameraRouterComponent.cs # Content.Server/Weapons/Ranged/Systems/GunSystem.Cartridges.cs # Content.Server/Zombies/ZombieSystem.Transform.cs # Content.Server/Zombies/ZombieSystem.cs # Content.Shared/Actions/SharedActionsSystem.cs # Content.Shared/Bed/SharedBedSystem.cs # Content.Shared/Blocking/BlockingSystem.cs # Content.Shared/Body/Systems/SharedBodySystem.Parts.cs # Content.Shared/Chemistry/Components/SolutionRegenerationComponent.cs # Content.Shared/Chemistry/EntitySystems/HypospraySystem.cs # Content.Shared/Construction/Conditions/TileNotBlocked.cs # Content.Shared/Cuffs/SharedCuffableSystem.cs # Content.Shared/Doors/Systems/SharedDoorSystem.cs # Content.Shared/Emoting/EmoteAttemptEvent.cs # Content.Shared/Eye/Blinding/Systems/BlindableSystem.cs # Content.Shared/Eye/VisibilityFlags.cs # Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs # Content.Shared/Humanoid/HumanoidCharacterAppearance.cs # Content.Shared/Interaction/SmartEquipSystem.cs # Content.Shared/Inventory/InventorySystem.Relay.cs # Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs # Content.Shared/Medical/Healing/HealingSystem.cs # Content.Shared/Medical/HealingComponent.cs # Content.Shared/Nutrition/EntitySystems/FoodSystem.cs # Content.Shared/Slippery/SlipperySystem.cs # Content.Shared/SprayPainter/SharedSprayPainterSystem.cs # Content.Shared/Standing/StandingStateComponent.cs # Content.Shared/Standing/StandingStateSystem.cs # Content.Shared/Station/SharedStationSpawningSystem.cs # Content.Shared/Stunnable/SharedStunSystem.cs # Content.Shared/SurveillanceCamera/SharedSurveillanceCameraMonitorSystem.cs # Content.Shared/VendingMachines/VendingMachineComponent.cs # Content.Shared/Warps/WarpPointSystem.cs # Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs # Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs # Content.Shared/Wieldable/SharedWieldableSystem.cs # Resources/Audio/Ambience/Antag/attributions.yml # Resources/Audio/Ambience/Antag/changeling_start.ogg # Resources/Audio/Effects/attributions.yml # Resources/Audio/Lobby/attributions.yml # Resources/Audio/Machines/attributions.yml # Resources/Audio/Weapons/Guns/Gunshots/attributions.yml # Resources/Locale/en-US/_strings/administration/antag.ftl # Resources/Locale/en-US/_strings/administration/commands/camera.ftl # Resources/Locale/en-US/_strings/administration/commands/dsay-command.ftl # Resources/Locale/en-US/_strings/administration/commands/follow-command.ftl # Resources/Locale/en-US/_strings/administration/smites.ftl # Resources/Locale/en-US/_strings/administration/ui/admin-camera-window.ftl # Resources/Locale/en-US/_strings/administration/ui/role-bans.ftl # Resources/Locale/en-US/_strings/alerts/alerts.ftl # Resources/Locale/en-US/_strings/atmos/gas-pressure-regulator-system.ftl # Resources/Locale/en-US/_strings/bonk/components/bonkable-component.ftl # Resources/Locale/en-US/_strings/commands/add-body-part-command.ftl # Resources/Locale/en-US/_strings/commands/admin-logs-command.ftl # Resources/Locale/en-US/_strings/commands/admin-who-command.ftl # Resources/Locale/en-US/_strings/commands/asay-command.ftl # Resources/Locale/en-US/_strings/commands/crew-manifest-command.ftl # Resources/Locale/en-US/_strings/commands/deadmin-command.ftl # Resources/Locale/en-US/_strings/commands/delete-component-command.ftl # Resources/Locale/en-US/_strings/commands/destroy-mechanism-command.ftl # Resources/Locale/en-US/_strings/commands/dirty-command.ftl # Resources/Locale/en-US/_strings/commands/dsay-command.ftl # Resources/Locale/en-US/_strings/commands/dump-reagent-guide-text-command.ftl # Resources/Locale/en-US/_strings/commands/eye-commands.ftl # Resources/Locale/en-US/_strings/commands/follow-command.ftl # Resources/Locale/en-US/_strings/commands/force-map-command.ftl # Resources/Locale/en-US/_strings/commands/force-preset-command.ftl # Resources/Locale/en-US/_strings/commands/ghost-kick-command.ftl # Resources/Locale/en-US/_strings/commands/ghost-visibility-commands.ftl # Resources/Locale/en-US/_strings/commands/go-lobby-command.ftl # Resources/Locale/en-US/_strings/commands/grid-drag-command.ftl # Resources/Locale/en-US/_strings/commands/is-afk-command.ftl # Resources/Locale/en-US/_strings/commands/makesentient-command.ftl # Resources/Locale/en-US/_strings/commands/map-commands.ftl # Resources/Locale/en-US/_strings/commands/mapping-client-side-setup-command.ftl # Resources/Locale/en-US/_strings/commands/mappingclientsidesetup-command.ftl # Resources/Locale/en-US/_strings/commands/me-command.ftl # Resources/Locale/en-US/_strings/commands/mind-info-command.ftl # Resources/Locale/en-US/_strings/commands/node-vis-commands.ftl # Resources/Locale/en-US/_strings/commands/npc-command.ftl # Resources/Locale/en-US/_strings/commands/nukecodes-command.ftl # Resources/Locale/en-US/_strings/commands/ooc-command.ftl # Resources/Locale/en-US/_strings/commands/pardon-command.ftl # Resources/Locale/en-US/_strings/commands/powerstat-command.ftl # Resources/Locale/en-US/_strings/commands/promote-host-command.ftl # Resources/Locale/en-US/_strings/commands/radiation-command.ftl # Resources/Locale/en-US/_strings/commands/readmin-command.ftl # Resources/Locale/en-US/_strings/commands/remove-extra-components-command.ftl # Resources/Locale/en-US/_strings/commands/role-commands.ftl # Resources/Locale/en-US/_strings/commands/say-command.ftl # Resources/Locale/en-US/_strings/commands/set-battery-percent-command.ftl # Resources/Locale/en-US/_strings/commands/show-ambient-command.ftl # Resources/Locale/en-US/_strings/commands/show-gun-spread-command.ftl # Resources/Locale/en-US/_strings/commands/show-htn-command.ftl # Resources/Locale/en-US/_strings/commands/showrules-command.ftl # Resources/Locale/en-US/_strings/commands/spawn-entity-list-command.ftl # Resources/Locale/en-US/_strings/commands/start-singularity-command.ftl # Resources/Locale/en-US/_strings/commands/tile-pry-command.ftl # Resources/Locale/en-US/_strings/commands/toggle-decals-command.ftl # Resources/Locale/en-US/_strings/commands/toggle-disallow-late-join-command.ftl # Resources/Locale/en-US/_strings/commands/unlocknode-command.ftl # Resources/Locale/en-US/_strings/commands/whisper-command.ftl # Resources/Locale/en-US/_strings/components/gas-pressure-regulator-component.ftl # Resources/Locale/en-US/_strings/engineer-painter/engineer-painter.ftl # Resources/Locale/en-US/_strings/game-ticking/forcemap-command.ftl # Resources/Locale/en-US/_strings/interaction/interaction-popup-component.ftl # Resources/Locale/en-US/_strings/markings/vox_tattoos.ftl # Resources/Locale/en-US/_strings/materials/material-extractor.ftl # Resources/Locale/en-US/_strings/movement/eye.ftl # Resources/Locale/en-US/_strings/nutrition/components/food-component.ftl # Resources/Locale/en-US/_strings/nutrition/components/ingestion-system.ftl # Resources/Locale/en-US/_strings/nutrition/components/sealable-component.ftl # Resources/Locale/en-US/_strings/radiation/radiation-command.ftl # Resources/Locale/en-US/_strings/reagents/meta/physical-desc.ftl # Resources/Locale/en-US/_strings/research/technologies.ftl # Resources/Locale/en-US/_strings/seeds/seeds.ftl # Resources/Locale/en-US/_strings/weapons/grenades/timer-trigger.ftl # Resources/Locale/en-US/_strings/weapons/grenades/voice-trigger.ftl # Resources/Locale/en-US/commands/mapping-client-side-setup-command.ftl # Resources/Locale/en-US/recipes/tags.ftl # Resources/Maps/_Sunrise/Station/bagel.yml # Resources/Maps/_Sunrise/Station/barratry.yml # Resources/Maps/_Sunrise/Station/box.yml # Resources/Maps/_Sunrise/Station/fland.yml # Resources/Maps/_Sunrise/Station/marathon.yml # Resources/Maps/_Sunrise/Station/oasis.yml # Resources/Maps/_Sunrise/Station/packed.yml # Resources/Maps/_Sunrise/Station/plasma.yml # Resources/Maps/amber.yml # Resources/Maps/centcomm.yml # Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml # Resources/Prototypes/Catalog/Fills/Lockers/security.yml # Resources/Prototypes/Catalog/Fills/Lockers/service.yml # Resources/Prototypes/Catalog/Fills/Lockers/suit_storage.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/coffee.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/sustenance.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/winterdrobe.yml # Resources/Prototypes/Entities/Clothing/Belt/belts.yml # Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml # Resources/Prototypes/Entities/Clothing/Eyes/hud.yml # Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml # Resources/Prototypes/Entities/Clothing/Head/hats.yml # Resources/Prototypes/Entities/Clothing/Head/helmets.yml # Resources/Prototypes/Entities/Clothing/Head/hoods.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml # Resources/Prototypes/Entities/Mobs/NPCs/animals.yml # Resources/Prototypes/Entities/Mobs/NPCs/carp.yml # Resources/Prototypes/Entities/Mobs/NPCs/pets.yml # Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml # Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml # Resources/Prototypes/Entities/Mobs/Player/clone.yml # Resources/Prototypes/Entities/Mobs/Species/base.yml # Resources/Prototypes/Entities/Mobs/Species/dwarf.yml # Resources/Prototypes/Entities/Mobs/base.yml # Resources/Prototypes/Entities/Objects/Fun/pai.yml # Resources/Prototypes/Entities/Objects/Fun/toys.yml # Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml # Resources/Prototypes/Entities/Objects/Shields/shields.yml # Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml # Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml # Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml # Resources/Prototypes/Entities/Objects/Tools/matches.yml # Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml # Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/caseless_rifle.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/grenade.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml # Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml # Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml # Resources/Prototypes/Entities/Structures/Doors/airlock_groups.yml # Resources/Prototypes/Entities/Structures/Furniture/beds.yml # Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml # Resources/Prototypes/Entities/Structures/Power/debug_power.yml # Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml # Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml # Resources/Prototypes/GameRules/subgamemodes.yml # Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml # Resources/Prototypes/Reagents/Consumable/Drink/soda.yml # Resources/Prototypes/Reagents/medicine.yml # Resources/Prototypes/Recipes/Lathes/Packs/tiles.yml # Resources/Prototypes/Recipes/Lathes/categories.yml # Resources/Prototypes/Recipes/Reactions/chemicals.yml # Resources/Prototypes/Roles/Antags/changeling.yml # Resources/Prototypes/name_identifier_groups.yml # Resources/Prototypes/tool_qualities.yml # Resources/ServerInfo/Guidebook/Antagonist/Zombies.xml # Resources/ServerInfo/Guidebook/Cargo/CargoBounties.xml # Resources/ServerInfo/Guidebook/Command.xml # Resources/ServerInfo/Guidebook/Glossary.xml # Resources/ServerInfo/Guidebook/NewPlayer/YourFirstCharacter.xml # Resources/ServerInfo/Guidebook/Science/Robotics.xml # Resources/ServerInfo/Guidebook/Science/ScannersAndVessels.xml # Resources/ServerInfo/Guidebook/Science/Science.xml # Resources/ServerInfo/Guidebook/SpaceStation14.xml # Resources/ServerInfo/Guidebook/Survival.xml # Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-equipped-HELMET.png # Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/icon.png # Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-equipped-HELMET.png # Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/security.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/meta.json # Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Suits/atmos_firesuit.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json # Resources/Textures/Interface/Misc/job_icons.rsi/meta.json # Resources/Textures/Objects/Misc/Lights/lampgreen.rsi/meta.json # Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/meta.json # Resources/Textures/Objects/Tools/spray_painter.rsi/meta.json # Resources/Textures/Objects/Weapons/Melee/baseball_bat.rsi/meta.json # Resources/engineCommandPerms.yml # Resources/keybinds.yml # Resources/migration.yml
1123 lines
39 KiB
C#
1123 lines
39 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using Content.Shared._Sunrise.Antags.Abductor;
|
|
using Content.Shared.ActionBlocker;
|
|
using Content.Shared.Actions.Components;
|
|
using Content.Shared.Actions.Events;
|
|
using Content.Shared.Administration.Logs;
|
|
using Content.Shared.Charges.Components;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Hands;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Inventory.Events;
|
|
using Content.Shared.Mind;
|
|
using Content.Shared.Rejuvenate;
|
|
using Content.Shared.Whitelist;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Shared.Actions;
|
|
|
|
public abstract class SharedActionsSystem : EntitySystem
|
|
{
|
|
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
|
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
|
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
|
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
|
[Dependency] private readonly RotateToFaceSystem _rotateToFace = default!;
|
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
|
|
private EntityQuery<ActionComponent> _actionQuery;
|
|
private EntityQuery<TargetActionComponent> _targetActionQuery;
|
|
private EntityQuery<ActionsComponent> _actionsQuery;
|
|
private EntityQuery<MindComponent> _mindQuery;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
_actionQuery = GetEntityQuery<ActionComponent>();
|
|
_actionsQuery = GetEntityQuery<ActionsComponent>();
|
|
_mindQuery = GetEntityQuery<MindComponent>();
|
|
|
|
SubscribeLocalEvent<ActionComponent, MapInitEvent>(OnActionMapInit);
|
|
|
|
SubscribeLocalEvent<ActionComponent, ComponentShutdown>(OnActionShutdown);
|
|
|
|
SubscribeLocalEvent<ActionsComponent, ActionComponentChangeEvent>(OnActionCompChange);
|
|
SubscribeLocalEvent<ActionsComponent, RelayedActionComponentChangeEvent>(OnRelayActionCompChange);
|
|
SubscribeLocalEvent<ActionsComponent, DidEquipEvent>(OnDidEquip);
|
|
SubscribeLocalEvent<ActionsComponent, DidEquipHandEvent>(OnHandEquipped);
|
|
SubscribeLocalEvent<ActionsComponent, DidUnequipEvent>(OnDidUnequip);
|
|
SubscribeLocalEvent<ActionsComponent, DidUnequipHandEvent>(OnHandUnequipped);
|
|
SubscribeLocalEvent<ActionsComponent, RejuvenateEvent>(OnRejuventate);
|
|
|
|
SubscribeLocalEvent<ActionsComponent, ComponentShutdown>(OnShutdown);
|
|
SubscribeLocalEvent<ActionsComponent, ComponentGetState>(OnGetState);
|
|
|
|
SubscribeLocalEvent<ActionComponent, ActionValidateEvent>(OnValidate);
|
|
SubscribeLocalEvent<InstantActionComponent, ActionValidateEvent>(OnInstantValidate);
|
|
SubscribeLocalEvent<EntityTargetActionComponent, ActionValidateEvent>(OnEntityValidate);
|
|
SubscribeLocalEvent<WorldTargetActionComponent, ActionValidateEvent>(OnWorldValidate);
|
|
|
|
SubscribeLocalEvent<InstantActionComponent, ActionGetEventEvent>(OnInstantGetEvent);
|
|
SubscribeLocalEvent<EntityTargetActionComponent, ActionGetEventEvent>(OnEntityGetEvent);
|
|
SubscribeLocalEvent<WorldTargetActionComponent, ActionGetEventEvent>(OnWorldGetEvent);
|
|
|
|
SubscribeLocalEvent<InstantActionComponent, ActionSetEventEvent>(OnInstantSetEvent);
|
|
SubscribeLocalEvent<EntityTargetActionComponent, ActionSetEventEvent>(OnEntitySetEvent);
|
|
SubscribeLocalEvent<WorldTargetActionComponent, ActionSetEventEvent>(OnWorldSetEvent);
|
|
|
|
SubscribeLocalEvent<EntityTargetActionComponent, ActionSetTargetEvent>(OnEntitySetTarget);
|
|
SubscribeLocalEvent<WorldTargetActionComponent, ActionSetTargetEvent>(OnWorldSetTarget);
|
|
|
|
SubscribeAllEvent<RequestPerformActionEvent>(OnActionRequest);
|
|
}
|
|
|
|
private void OnActionMapInit(Entity<ActionComponent> ent, ref MapInitEvent args)
|
|
{
|
|
var comp = ent.Comp;
|
|
comp.OriginalIconColor = comp.IconColor;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.OriginalIconColor));
|
|
}
|
|
|
|
private void OnActionShutdown(Entity<ActionComponent> ent, ref ComponentShutdown args)
|
|
{
|
|
if (ent.Comp.AttachedEntity is {} user && !TerminatingOrDeleted(user))
|
|
RemoveAction(user, (ent, ent));
|
|
}
|
|
|
|
private void OnShutdown(Entity<ActionsComponent> ent, ref ComponentShutdown args)
|
|
{
|
|
foreach (var actionId in ent.Comp.Actions)
|
|
{
|
|
RemoveAction((ent, ent), actionId);
|
|
}
|
|
}
|
|
|
|
private void OnGetState(Entity<ActionsComponent> ent, ref ComponentGetState args)
|
|
{
|
|
args.State = new ActionsComponentState(GetNetEntitySet(ent.Comp.Actions));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolving an action's <see cref="ActionComponent"/>, only returning a value if it exists and has it.
|
|
/// </summary>
|
|
public Entity<ActionComponent>? GetAction(Entity<ActionComponent?>? action, bool logError = true)
|
|
{
|
|
if (action is not {} ent || Deleted(ent))
|
|
return null;
|
|
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp, logError))
|
|
return null;
|
|
|
|
return (ent, ent.Comp);
|
|
}
|
|
|
|
public void SetCooldown(Entity<ActionComponent?>? action, TimeSpan start, TimeSpan end)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return;
|
|
|
|
ent.Comp.Cooldown = new ActionCooldown
|
|
{
|
|
Start = start,
|
|
End = end
|
|
};
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Cooldown));
|
|
}
|
|
|
|
public void RemoveCooldown(Entity<ActionComponent?>? action)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return;
|
|
|
|
ent.Comp.Cooldown = null;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Cooldown));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts a cooldown starting now, lasting for <c>cooldown</c> seconds.
|
|
/// </summary>
|
|
public void SetCooldown(Entity<ActionComponent?>? action, TimeSpan cooldown)
|
|
{
|
|
var start = GameTiming.CurTime;
|
|
SetCooldown(action, start, start + cooldown);
|
|
}
|
|
|
|
public void ClearCooldown(Entity<ActionComponent?>? action)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return;
|
|
|
|
if (ent.Comp.Cooldown is not {} cooldown)
|
|
return;
|
|
|
|
ent.Comp.Cooldown = new ActionCooldown
|
|
{
|
|
Start = cooldown.Start,
|
|
End = GameTiming.CurTime
|
|
};
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Cooldown));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the cooldown for this action only if it is bigger than the one it already has.
|
|
/// </summary>
|
|
public void SetIfBiggerCooldown(Entity<ActionComponent?>? action, TimeSpan cooldown)
|
|
{
|
|
if (GetAction(action) is not {} ent || cooldown < TimeSpan.Zero)
|
|
return;
|
|
|
|
var start = GameTiming.CurTime;
|
|
var end = start + cooldown;
|
|
if (ent.Comp.Cooldown?.End > end)
|
|
return;
|
|
|
|
SetCooldown((ent, ent), start, end);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set an action's cooldown to its use delay, if it has one.
|
|
/// If there is no set use delay this does nothing.
|
|
/// </summary>
|
|
public void StartUseDelay(Entity<ActionComponent?>? action)
|
|
{
|
|
if (GetAction(action) is not {} ent || ent.Comp.UseDelay is not {} delay)
|
|
return;
|
|
|
|
SetCooldown((ent, ent), delay);
|
|
}
|
|
|
|
public void SetUseDelay(Entity<ActionComponent?>? action, TimeSpan? delay)
|
|
{
|
|
if (GetAction(action) is not {} ent || ent.Comp.UseDelay == delay)
|
|
return;
|
|
|
|
ent.Comp.UseDelay = delay;
|
|
UpdateAction(ent);
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.UseDelay));
|
|
}
|
|
|
|
public void ReduceUseDelay(Entity<ActionComponent?>? action, TimeSpan? lowerDelay)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return;
|
|
|
|
if (ent.Comp.UseDelay != null && lowerDelay != null)
|
|
ent.Comp.UseDelay -= lowerDelay;
|
|
|
|
if (ent.Comp.UseDelay < TimeSpan.Zero)
|
|
ent.Comp.UseDelay = null;
|
|
|
|
UpdateAction(ent);
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.UseDelay));
|
|
}
|
|
|
|
private void OnRejuventate(Entity<ActionsComponent> ent, ref RejuvenateEvent args)
|
|
{
|
|
foreach (var act in ent.Comp.Actions)
|
|
{
|
|
ClearCooldown(act);
|
|
}
|
|
}
|
|
|
|
#region ComponentStateManagement
|
|
public virtual void UpdateAction(Entity<ActionComponent> ent)
|
|
{
|
|
// See client-side code.
|
|
}
|
|
|
|
public void SetToggled(Entity<ActionComponent?>? action, bool toggled)
|
|
{
|
|
if (GetAction(action) is not {} ent || ent.Comp.Toggled == toggled)
|
|
return;
|
|
|
|
ent.Comp.Toggled = toggled;
|
|
UpdateAction(ent);
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Toggled));
|
|
}
|
|
|
|
public void SetEnabled(Entity<ActionComponent?>? action, bool enabled)
|
|
{
|
|
if (GetAction(action) is not {} ent || ent.Comp.Enabled == enabled)
|
|
return;
|
|
|
|
ent.Comp.Enabled = enabled;
|
|
UpdateAction(ent);
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Enabled));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Execution
|
|
/// <summary>
|
|
/// When receiving a request to perform an action, this validates whether the action is allowed. If it is, it
|
|
/// will raise the relevant <see cref="InstantActionEvent"/>
|
|
/// </summary>
|
|
private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArgs args)
|
|
{
|
|
if (args.SenderSession.AttachedEntity is not { } user)
|
|
return;
|
|
|
|
if (!_actionsQuery.TryComp(user, out var component))
|
|
return;
|
|
|
|
var actionEnt = GetEntity(ev.Action);
|
|
|
|
if (!TryComp(actionEnt, out MetaDataComponent? metaData))
|
|
return;
|
|
|
|
var name = Name(actionEnt, metaData);
|
|
|
|
// Does the user actually have the requested action?
|
|
if (!component.Actions.Contains(actionEnt))
|
|
{
|
|
_adminLogger.Add(LogType.Action,
|
|
$"{ToPrettyString(user):user} attempted to perform an action that they do not have: {name}.");
|
|
return;
|
|
}
|
|
|
|
if (GetAction(actionEnt) is not {} action)
|
|
return;
|
|
|
|
DebugTools.Assert(action.Comp.AttachedEntity == user);
|
|
if (!action.Comp.Enabled)
|
|
return;
|
|
|
|
var curTime = GameTiming.CurTime;
|
|
if (IsCooldownActive(action, curTime))
|
|
return;
|
|
|
|
// check for action use prevention
|
|
// TODO: make code below use this event with a dedicated component
|
|
var attemptEv = new ActionAttemptEvent(user);
|
|
RaiseLocalEvent(action, ref attemptEv);
|
|
if (attemptEv.Cancelled)
|
|
return;
|
|
|
|
// Validate request by checking action blockers and the like
|
|
var provider = action.Comp.Container ?? user;
|
|
var validateEv = new ActionValidateEvent()
|
|
{
|
|
Input = ev,
|
|
User = user,
|
|
Provider = provider
|
|
};
|
|
RaiseLocalEvent(action, ref validateEv);
|
|
if (validateEv.Invalid)
|
|
return;
|
|
|
|
// All checks passed. Perform the action!
|
|
PerformAction((user, component), action);
|
|
}
|
|
|
|
private void OnValidate(Entity<ActionComponent> ent, ref ActionValidateEvent args)
|
|
{
|
|
if ((ent.Comp.CheckConsciousness && !_actionBlocker.CanConsciouslyPerformAction(args.User))
|
|
|| (ent.Comp.CheckCanInteract && !_actionBlocker.CanInteract(args.User, null)))
|
|
args.Invalid = true;
|
|
}
|
|
|
|
private void OnInstantValidate(Entity<InstantActionComponent> ent, ref ActionValidateEvent args)
|
|
{
|
|
_adminLogger.Add(LogType.Action,
|
|
$"{ToPrettyString(args.User):user} is performing the {Name(ent):action} action provided by {ToPrettyString(args.Provider):provider}.");
|
|
}
|
|
|
|
private void OnEntityValidate(Entity<EntityTargetActionComponent> ent, ref ActionValidateEvent args)
|
|
{
|
|
// let WorldTargetAction handle it
|
|
if (ent.Comp.Event is not {} ev)
|
|
{
|
|
DebugTools.Assert(HasComp<WorldTargetActionComponent>(ent), $"Entity-world targeting action {ToPrettyString(ent)} requires WorldTargetActionComponent");
|
|
return;
|
|
}
|
|
|
|
if (args.Input.EntityTarget is not {} netTarget)
|
|
{
|
|
args.Invalid = true;
|
|
return;
|
|
}
|
|
|
|
var user = args.User;
|
|
|
|
var target = GetEntity(netTarget);
|
|
|
|
var targetWorldPos = _transform.GetWorldPosition(target);
|
|
|
|
if (ent.Comp.RotateOnUse)
|
|
_rotateToFace.TryFaceCoordinates(user, targetWorldPos);
|
|
|
|
if (!ValidateEntityTarget(user, target, ent))
|
|
return;
|
|
|
|
_adminLogger.Add(LogType.Action,
|
|
$"{ToPrettyString(user):user} is performing the {Name(ent):action} action (provided by {ToPrettyString(args.Provider):provider}) targeted at {ToPrettyString(target):target}.");
|
|
|
|
ev.Target = target;
|
|
}
|
|
|
|
private void OnWorldValidate(Entity<WorldTargetActionComponent> ent, ref ActionValidateEvent args)
|
|
{
|
|
if (args.Input.EntityCoordinatesTarget is not { } netTarget)
|
|
{
|
|
args.Invalid = true;
|
|
return;
|
|
}
|
|
|
|
var user = args.User;
|
|
var target = GetCoordinates(netTarget);
|
|
|
|
if (ent.Comp.RotateOnUse)
|
|
_rotateToFace.TryFaceCoordinates(user, _transform.ToMapCoordinates(target).Position);
|
|
|
|
if (!ValidateWorldTarget(user, target, ent))
|
|
return;
|
|
|
|
// if the client specified an entity it needs to be valid
|
|
var targetEntity = GetEntity(args.Input.EntityTarget);
|
|
if (targetEntity != null && (
|
|
!TryComp<EntityTargetActionComponent>(ent, out var entTarget) ||
|
|
!ValidateEntityTarget(user, targetEntity.Value, (ent, entTarget))))
|
|
{
|
|
args.Invalid = true;
|
|
return;
|
|
}
|
|
|
|
_adminLogger.Add(LogType.Action,
|
|
$"{ToPrettyString(user):user} is performing the {Name(ent):action} action (provided by {args.Provider}) targeting {targetEntity} at {target:target}.");
|
|
|
|
if (ent.Comp.Event is {} ev)
|
|
{
|
|
ev.Target = target;
|
|
ev.Entity = targetEntity;
|
|
}
|
|
}
|
|
|
|
public bool ValidateEntityTarget(EntityUid user, EntityUid target, Entity<EntityTargetActionComponent> ent)
|
|
{
|
|
var (uid, comp) = ent;
|
|
if (!target.IsValid() || Deleted(target))
|
|
return false;
|
|
|
|
if (_whitelist.IsWhitelistFail(comp.Whitelist, target))
|
|
return false;
|
|
|
|
if (_whitelist.IsBlacklistPass(comp.Blacklist, target))
|
|
return false;
|
|
|
|
if (_actionQuery.Comp(uid).CheckCanInteract && !_actionBlocker.CanInteract(user, target))
|
|
return false;
|
|
|
|
if (user == target)
|
|
return comp.CanTargetSelf;
|
|
// Sunrise-start
|
|
if (HasComp<AbductorComponent>(target))
|
|
return true;
|
|
// Sunrise-end
|
|
var targetAction = Comp<TargetActionComponent>(uid);
|
|
|
|
// not using the ValidateBaseTarget logic since its raycast fails if the target is e.g. a wall
|
|
if (targetAction.CheckCanAccess)
|
|
return _interaction.InRangeAndAccessible(user, target, targetAction.Range, targetAction.AccessMask);
|
|
|
|
// Just check normal in range, allowing <= 0 range to mean infinite range.
|
|
if (targetAction.Range > 0
|
|
&& !_transform.InRange(user, target, targetAction.Range))
|
|
return false;
|
|
|
|
// Sunrise-Start
|
|
if (targetAction.IgnoreContainer)
|
|
return true;
|
|
// Sunrise-End
|
|
|
|
// If checkCanAccess isn't set, we allow targeting things in containers
|
|
return _interaction.IsAccessible(user, target);
|
|
}
|
|
|
|
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates target, Entity<WorldTargetActionComponent> ent)
|
|
{
|
|
var targetAction = Comp<TargetActionComponent>(ent);
|
|
return ValidateBaseTarget(user, target, (ent, targetAction));
|
|
}
|
|
|
|
private bool ValidateBaseTarget(EntityUid user, EntityCoordinates coords, Entity<TargetActionComponent> ent)
|
|
{
|
|
var comp = ent.Comp;
|
|
if (comp.CheckCanAccess)
|
|
return _interaction.InRangeUnobstructed(user, coords, range: comp.Range);
|
|
|
|
// Sunrise-start
|
|
if (HasComp<AbductorAgentComponent>(user) || HasComp<AbductorScientistComponent>(user))
|
|
return true;
|
|
// Sunrise-end
|
|
// even if we don't check for obstructions, we may still need to check the range.
|
|
var xform = Transform(user);
|
|
if (xform.MapID != _transform.GetMapId(coords))
|
|
return false;
|
|
|
|
if (comp.Range <= 0)
|
|
return true;
|
|
|
|
return _transform.InRange(coords, xform.Coordinates, comp.Range);
|
|
}
|
|
|
|
private void OnInstantGetEvent(Entity<InstantActionComponent> ent, ref ActionGetEventEvent args)
|
|
{
|
|
if (ent.Comp.Event is {} ev)
|
|
args.Event = ev;
|
|
}
|
|
|
|
private void OnEntityGetEvent(Entity<EntityTargetActionComponent> ent, ref ActionGetEventEvent args)
|
|
{
|
|
if (ent.Comp.Event is {} ev)
|
|
args.Event = ev;
|
|
}
|
|
|
|
private void OnWorldGetEvent(Entity<WorldTargetActionComponent> ent, ref ActionGetEventEvent args)
|
|
{
|
|
if (ent.Comp.Event is {} ev)
|
|
args.Event = ev;
|
|
}
|
|
|
|
private void OnInstantSetEvent(Entity<InstantActionComponent> ent, ref ActionSetEventEvent args)
|
|
{
|
|
if (args.Event is InstantActionEvent ev)
|
|
{
|
|
ent.Comp.Event = ev;
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void OnEntitySetEvent(Entity<EntityTargetActionComponent> ent, ref ActionSetEventEvent args)
|
|
{
|
|
if (args.Event is EntityTargetActionEvent ev)
|
|
{
|
|
ent.Comp.Event = ev;
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void OnWorldSetEvent(Entity<WorldTargetActionComponent> ent, ref ActionSetEventEvent args)
|
|
{
|
|
if (args.Event is WorldTargetActionEvent ev)
|
|
{
|
|
ent.Comp.Event = ev;
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void OnEntitySetTarget(Entity<EntityTargetActionComponent> ent, ref ActionSetTargetEvent args)
|
|
{
|
|
if (ent.Comp.Event is {} ev)
|
|
{
|
|
ev.Target = args.Target;
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void OnWorldSetTarget(Entity<WorldTargetActionComponent> ent, ref ActionSetTargetEvent args)
|
|
{
|
|
if (ent.Comp.Event is {} ev)
|
|
{
|
|
ev.Target = Transform(args.Target).Coordinates;
|
|
// only set Entity if the action also has EntityTargetAction
|
|
ev.Entity = HasComp<EntityTargetActionComponent>(ent) ? args.Target : null;
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform an action, bypassing validation checks.
|
|
/// </summary>
|
|
/// <param name="performer">The entity performing the action</param>
|
|
/// <param name="action">The action being performed</param>
|
|
/// <param name="actionEvent">An event override to perform. If null, uses <see cref="GetEvent"/></param>
|
|
/// <param name="predicted">If false, prevents playing the action's sound on the client</param>
|
|
public void PerformAction(Entity<ActionsComponent?> performer, Entity<ActionComponent> action, BaseActionEvent? actionEvent = null, bool predicted = true)
|
|
{
|
|
var handled = false;
|
|
|
|
var toggledBefore = action.Comp.Toggled;
|
|
|
|
// Note that attached entity and attached container are allowed to be null here.
|
|
if (action.Comp.AttachedEntity != null && action.Comp.AttachedEntity != performer)
|
|
{
|
|
Log.Error($"{ToPrettyString(performer)} is attempting to perform an action {ToPrettyString(action)} that is attached to another entity {ToPrettyString(action.Comp.AttachedEntity)}");
|
|
return;
|
|
}
|
|
|
|
actionEvent ??= GetEvent(action);
|
|
|
|
if (actionEvent is not {} ev)
|
|
return;
|
|
|
|
ev.Performer = performer;
|
|
|
|
// This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use).
|
|
ev.Handled = false;
|
|
var target = performer.Owner;
|
|
ev.Performer = performer;
|
|
ev.Action = action;
|
|
|
|
if (!action.Comp.RaiseOnUser && action.Comp.Container is {} container && !_mindQuery.HasComp(container))
|
|
target = container;
|
|
|
|
if (action.Comp.RaiseOnAction)
|
|
target = action;
|
|
|
|
RaiseLocalEvent(target, (object) ev, broadcast: true);
|
|
handled = ev.Handled;
|
|
|
|
if (!handled)
|
|
return; // no interaction occurred.
|
|
|
|
// play sound, reduce charges, start cooldown
|
|
if (ev?.Toggle == true)
|
|
SetToggled((action, action), !action.Comp.Toggled);
|
|
|
|
_audio.PlayPredicted(action.Comp.Sound, performer, predicted ? performer : null);
|
|
|
|
// TODO: move to ActionCooldown ActionPerformedEvent?
|
|
RemoveCooldown((action, action));
|
|
StartUseDelay((action, action));
|
|
|
|
UpdateAction(action);
|
|
|
|
var performed = new ActionPerformedEvent(performer);
|
|
RaiseLocalEvent(action, ref performed);
|
|
}
|
|
#endregion
|
|
|
|
#region AddRemoveActions
|
|
|
|
public EntityUid? AddAction(EntityUid performer,
|
|
[ForbidLiteral] string? actionPrototypeId,
|
|
EntityUid container = default,
|
|
ActionsComponent? component = null)
|
|
{
|
|
EntityUid? actionId = null;
|
|
AddAction(performer, ref actionId, out _, actionPrototypeId, container, component);
|
|
return actionId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an action to an action holder. If the given entity does not exist, it will attempt to spawn one.
|
|
/// If the holder has no actions component, this will give them one.
|
|
/// </summary>
|
|
/// <param name="performer">Entity to receive the actions</param>
|
|
/// <param name="actionId">Action entity to add</param>
|
|
/// <param name="component">The <see cref="performer"/>'s action component of </param>
|
|
/// <param name="actionPrototypeId">The action entity prototype id to use if <see cref="actionId"/> is invalid.</param>
|
|
/// <param name="container">The entity that contains/enables this action (e.g., flashlight).</param>
|
|
public bool AddAction(EntityUid performer,
|
|
[NotNullWhen(true)] ref EntityUid? actionId,
|
|
[ForbidLiteral] string? actionPrototypeId,
|
|
EntityUid container = default,
|
|
ActionsComponent? component = null)
|
|
{
|
|
return AddAction(performer, ref actionId, out _, actionPrototypeId, container, component);
|
|
}
|
|
|
|
/// <inheritdoc cref="AddAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Robust.Shared.GameObjects.EntityUid,ActionsComponent?)"/>
|
|
public bool AddAction(EntityUid performer,
|
|
[NotNullWhen(true)] ref EntityUid? actionId,
|
|
[NotNullWhen(true)] out ActionComponent? action,
|
|
[ForbidLiteral] string? actionPrototypeId,
|
|
EntityUid container = default,
|
|
ActionsComponent? component = null)
|
|
{
|
|
if (!container.IsValid())
|
|
container = performer;
|
|
|
|
if (!_actionContainer.EnsureAction(container, ref actionId, out action, actionPrototypeId))
|
|
return false;
|
|
|
|
return AddActionDirect((performer, component), (actionId.Value, action));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a pre-existing action.
|
|
/// </summary>
|
|
public bool AddAction(Entity<ActionsComponent?> performer,
|
|
Entity<ActionComponent?> action,
|
|
Entity<ActionsContainerComponent?> container)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return false;
|
|
|
|
if (ent.Comp.Container != container.Owner
|
|
|| !Resolve(container, ref container.Comp)
|
|
|| !container.Comp.Container.Contains(ent))
|
|
{
|
|
Log.Error($"Attempted to add an action with an invalid container: {ToPrettyString(ent)}");
|
|
return false;
|
|
}
|
|
|
|
return AddActionDirect(performer, (ent, ent));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a pre-existing action. This also bypasses the requirement that the given action must be stored in a
|
|
/// valid action container.
|
|
/// </summary>
|
|
public bool AddActionDirect(Entity<ActionsComponent?> performer,
|
|
Entity<ActionComponent?>? action)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return false;
|
|
|
|
DebugTools.Assert(ent.Comp.Container == null ||
|
|
(TryComp(ent.Comp.Container, out ActionsContainerComponent? containerComp)
|
|
&& containerComp.Container.Contains(ent)));
|
|
|
|
if (ent.Comp.AttachedEntity is {} user)
|
|
RemoveAction(user, (ent, ent));
|
|
|
|
// TODO: make this an event bruh
|
|
if (ent.Comp.StartDelay && ent.Comp.UseDelay != null)
|
|
SetCooldown((ent, ent), ent.Comp.UseDelay.Value);
|
|
|
|
DebugTools.AssertOwner(performer, performer.Comp);
|
|
performer.Comp ??= EnsureComp<ActionsComponent>(performer);
|
|
ent.Comp.AttachedEntity = performer;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.AttachedEntity));
|
|
performer.Comp.Actions.Add(ent);
|
|
Dirty(performer, performer.Comp);
|
|
ActionAdded((performer, performer.Comp), (ent, ent.Comp));
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method gets called after a new action got added.
|
|
/// </summary>
|
|
protected virtual void ActionAdded(Entity<ActionsComponent> performer, Entity<ActionComponent> action)
|
|
{
|
|
// See client-side system for UI code.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grant pre-existing actions. If the entity has no action component, this will give them one.
|
|
/// </summary>
|
|
/// <param name="performer">Entity to receive the actions</param>
|
|
/// <param name="actions">The actions to add</param>
|
|
/// <param name="container">The entity that enables these actions (e.g., flashlight). May be null (innate actions).</param>
|
|
public void GrantActions(Entity<ActionsComponent?> performer,
|
|
IEnumerable<EntityUid> actions,
|
|
Entity<ActionsContainerComponent?> container)
|
|
{
|
|
if (!Resolve(container, ref container.Comp))
|
|
return;
|
|
|
|
DebugTools.AssertOwner(performer, performer.Comp);
|
|
performer.Comp ??= EnsureComp<ActionsComponent>(performer);
|
|
|
|
foreach (var actionId in actions)
|
|
{
|
|
AddAction(performer, actionId, container);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grants all actions currently contained in some action-container. If the target entity has no action
|
|
/// component, this will give them one.
|
|
/// </summary>
|
|
/// <param name="performer">Entity to receive the actions</param>
|
|
/// <param name="container">The entity that contains thee actions.</param>
|
|
public void GrantContainedActions(Entity<ActionsComponent?> performer, Entity<ActionsContainerComponent?> container)
|
|
{
|
|
if (!Resolve(container, ref container.Comp))
|
|
return;
|
|
|
|
performer.Comp ??= EnsureComp<ActionsComponent>(performer);
|
|
|
|
foreach (var actionId in container.Comp.Container.ContainedEntities)
|
|
{
|
|
if (GetAction(actionId) is {} action)
|
|
AddActionDirect(performer, (action, action));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grants the provided action from the container to the target entity. If the target entity has no action
|
|
/// component, this will give them one.
|
|
/// </summary>
|
|
/// <param name="performer"></param>
|
|
/// <param name="container"></param>
|
|
/// <param name="actionId"></param>
|
|
public void GrantContainedAction(Entity<ActionsComponent?> performer, Entity<ActionsContainerComponent?> container, EntityUid actionId)
|
|
{
|
|
if (!Resolve(container, ref container.Comp))
|
|
return;
|
|
|
|
performer.Comp ??= EnsureComp<ActionsComponent>(performer);
|
|
|
|
AddActionDirect(performer, actionId);
|
|
}
|
|
|
|
public IEnumerable<Entity<ActionComponent>> GetActions(EntityUid holderId, ActionsComponent? actions = null)
|
|
{
|
|
if (!Resolve(holderId, ref actions, false))
|
|
yield break;
|
|
|
|
foreach (var actionId in actions.Actions)
|
|
{
|
|
if (GetAction(actionId) is not {} ent)
|
|
continue;
|
|
|
|
yield return ent;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove any actions that were enabled by some other entity. Useful when unequiping items that grant actions.
|
|
/// </summary>
|
|
public void RemoveProvidedActions(EntityUid performer, EntityUid container, ActionsComponent? comp = null)
|
|
{
|
|
if (!Resolve(performer, ref comp, false))
|
|
return;
|
|
|
|
foreach (var actionId in comp.Actions.ToArray())
|
|
{
|
|
if (GetAction(actionId) is not {} ent)
|
|
return;
|
|
|
|
if (ent.Comp.Container == container)
|
|
RemoveAction((performer, comp), (ent, ent));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a single provided action provided by another entity.
|
|
/// </summary>
|
|
public void RemoveProvidedAction(EntityUid performer, EntityUid container, EntityUid actionId, ActionsComponent? comp = null)
|
|
{
|
|
if (!_actionsQuery.Resolve(performer, ref comp, false) || GetAction(actionId) is not {} ent)
|
|
return;
|
|
|
|
if (ent.Comp.Container == container)
|
|
RemoveAction((performer, comp), (ent, ent));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an action from its container, if it still exists.
|
|
/// </summary>
|
|
public void RemoveAction(Entity<ActionComponent?>? action)
|
|
{
|
|
if (GetAction(action) is not {} ent || ent.Comp.AttachedEntity is not {} actions)
|
|
return;
|
|
|
|
if (!_actionsQuery.TryComp(actions, out var comp))
|
|
return;
|
|
|
|
RemoveAction((actions, comp), (ent, ent));
|
|
}
|
|
|
|
public void RemoveAction(Entity<ActionsComponent?> performer, Entity<ActionComponent?>? action)
|
|
{
|
|
if (GetAction(action) is not {} ent)
|
|
return;
|
|
|
|
if (ent.Comp.AttachedEntity != performer.Owner)
|
|
{
|
|
DebugTools.Assert(!Resolve(performer, ref performer.Comp, false)
|
|
|| performer.Comp.LifeStage >= ComponentLifeStage.Stopping
|
|
|| !performer.Comp.Actions.Contains(ent.Owner));
|
|
|
|
if (!GameTiming.ApplyingState)
|
|
Log.Error($"Attempted to remove an action {ToPrettyString(ent)} from an entity that it was never attached to: {ToPrettyString(performer)}. Trace: {Environment.StackTrace}");
|
|
return;
|
|
}
|
|
|
|
if (!_actionsQuery.Resolve(performer, ref performer.Comp, false))
|
|
{
|
|
DebugTools.Assert(performer == null || TerminatingOrDeleted(performer));
|
|
ent.Comp.AttachedEntity = null;
|
|
// TODO: should this delete the action since it's now orphaned?
|
|
return;
|
|
}
|
|
|
|
performer.Comp.Actions.Remove(ent.Owner);
|
|
Dirty(performer, performer.Comp);
|
|
ent.Comp.AttachedEntity = null;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.AttachedEntity));
|
|
ActionRemoved((performer, performer.Comp), ent);
|
|
|
|
if (ent.Comp.Temporary)
|
|
QueueDel(ent);
|
|
}
|
|
|
|
// Starlight-Abductor-start
|
|
public EntityUid[] HideActions(EntityUid performer, ActionsComponent? comp = null)
|
|
{
|
|
if (!Resolve(performer, ref comp, false))
|
|
return [];
|
|
|
|
var actions = comp.Actions.ToArray();
|
|
comp.Actions.Clear();
|
|
Dirty(performer, comp);
|
|
return actions;
|
|
}
|
|
public void UnHideActions(EntityUid performer, EntityUid[] actions, ActionsComponent? comp = null)
|
|
{
|
|
if (!Resolve(performer, ref comp, false))
|
|
return;
|
|
|
|
foreach (var action in actions)
|
|
comp.Actions.Add(action);
|
|
Dirty(performer, comp);
|
|
}
|
|
// Starlight-Abductor-end
|
|
|
|
/// <summary>
|
|
/// This method gets called after an action got removed.
|
|
/// </summary>
|
|
protected virtual void ActionRemoved(Entity<ActionsComponent> performer, Entity<ActionComponent> action)
|
|
{
|
|
// See client-side system for UI code.
|
|
}
|
|
|
|
public bool ValidAction(Entity<ActionComponent> ent, bool canReach = true)
|
|
{
|
|
var (uid, comp) = ent;
|
|
if (!comp.Enabled)
|
|
return false;
|
|
|
|
var curTime = GameTiming.CurTime;
|
|
if (comp.Cooldown.HasValue && comp.Cooldown.Value.End > curTime)
|
|
return false;
|
|
|
|
// TODO: use event for this
|
|
return canReach || Comp<TargetActionComponent>(ent)?.CheckCanAccess == false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void OnRelayActionCompChange(Entity<ActionsComponent> ent, ref RelayedActionComponentChangeEvent args)
|
|
{
|
|
if (args.Handled)
|
|
return;
|
|
|
|
var ev = new AttemptRelayActionComponentChangeEvent();
|
|
RaiseLocalEvent(ent.Owner, ref ev);
|
|
var target = ev.Target ?? ent.Owner;
|
|
|
|
args.Handled = true;
|
|
args.Toggle = true;
|
|
|
|
if (!args.Action.Comp.Toggled)
|
|
{
|
|
EntityManager.AddComponents(target, args.Components);
|
|
}
|
|
else
|
|
{
|
|
EntityManager.RemoveComponents(target, args.Components);
|
|
}
|
|
}
|
|
|
|
private void OnActionCompChange(Entity<ActionsComponent> ent, ref ActionComponentChangeEvent args)
|
|
{
|
|
if (args.Handled)
|
|
return;
|
|
|
|
args.Handled = true;
|
|
args.Toggle = true;
|
|
var target = ent.Owner;
|
|
|
|
if (!args.Action.Comp.Toggled)
|
|
{
|
|
EntityManager.AddComponents(target, args.Components);
|
|
}
|
|
else
|
|
{
|
|
EntityManager.RemoveComponents(target, args.Components);
|
|
}
|
|
}
|
|
|
|
#region EquipHandlers
|
|
private void OnDidEquip(Entity<ActionsComponent> ent, ref DidEquipEvent args)
|
|
{
|
|
if (GameTiming.ApplyingState)
|
|
return;
|
|
|
|
var ev = new GetItemActionsEvent(_actionContainer, args.Equipee, args.Equipment, args.SlotFlags);
|
|
RaiseLocalEvent(args.Equipment, ev);
|
|
|
|
if (ev.Actions.Count == 0)
|
|
return;
|
|
|
|
GrantActions((ent, ent), ev.Actions, args.Equipment);
|
|
}
|
|
|
|
private void OnHandEquipped(Entity<ActionsComponent> ent, ref DidEquipHandEvent args)
|
|
{
|
|
if (GameTiming.ApplyingState)
|
|
return;
|
|
|
|
var ev = new GetItemActionsEvent(_actionContainer, args.User, args.Equipped);
|
|
RaiseLocalEvent(args.Equipped, ev);
|
|
|
|
if (ev.Actions.Count == 0)
|
|
return;
|
|
|
|
GrantActions((ent, ent), ev.Actions, args.Equipped);
|
|
}
|
|
|
|
private void OnDidUnequip(EntityUid uid, ActionsComponent component, DidUnequipEvent args)
|
|
{
|
|
if (GameTiming.ApplyingState)
|
|
return;
|
|
|
|
RemoveProvidedActions(uid, args.Equipment, component);
|
|
}
|
|
|
|
private void OnHandUnequipped(EntityUid uid, ActionsComponent component, DidUnequipHandEvent args)
|
|
{
|
|
if (GameTiming.ApplyingState)
|
|
return;
|
|
|
|
RemoveProvidedActions(uid, args.Unequipped, component);
|
|
}
|
|
#endregion
|
|
|
|
public void SetEntityIcon(Entity<ActionComponent?> ent, EntityUid? icon)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.EntityIcon == icon)
|
|
return;
|
|
|
|
ent.Comp.EntityIcon = icon;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.EntIcon));
|
|
}
|
|
|
|
public void SetIcon(Entity<ActionComponent?> ent, SpriteSpecifier? icon)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.Icon == icon)
|
|
return;
|
|
|
|
ent.Comp.Icon = icon;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Icon));
|
|
}
|
|
|
|
public void SetIconOn(Entity<ActionComponent?> ent, SpriteSpecifier? iconOn)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.IconOn == iconOn)
|
|
return;
|
|
|
|
ent.Comp.IconOn = iconOn;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.IconOn));
|
|
}
|
|
|
|
public void SetItemIconStyle(Entity<ActionComponent?> ent, ItemActionIconStyle itemIconStyle)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.ItemIconStyle == itemIconStyle)
|
|
return;
|
|
|
|
ent.Comp.ItemIconStyle = itemIconStyle;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.ItemIconStyle));
|
|
}
|
|
|
|
public void SetPriority(Entity<ActionComponent?> ent, int priority)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.Priority == priority)
|
|
return;
|
|
|
|
ent.Comp.Priority = priority;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.Priority));
|
|
}
|
|
|
|
public void SetIconColor(Entity<ActionComponent?> ent, Color color)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.IconColor == color)
|
|
return;
|
|
|
|
ent.Comp.IconColor = color;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.IconColor));
|
|
}
|
|
|
|
public void SetCheckCanInteract(Entity<ActionComponent?> ent, bool value)
|
|
{
|
|
if (!_actionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.CheckCanInteract == value)
|
|
return;
|
|
|
|
ent.Comp.CheckCanInteract = value;
|
|
DirtyField(ent, ent.Comp, nameof(ActionComponent.CheckCanInteract));
|
|
}
|
|
|
|
public void SetCheckCanAccess(Entity<TargetActionComponent?> ent, bool value)
|
|
{
|
|
if (!_targetActionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.CheckCanAccess == value)
|
|
return;
|
|
|
|
ent.Comp.CheckCanAccess = value;
|
|
DirtyField(ent, ent.Comp, nameof(TargetActionComponent.CheckCanAccess));
|
|
}
|
|
|
|
public void SetIgnoreContainer(Entity<TargetActionComponent?> ent, bool value)
|
|
{
|
|
if (!_targetActionQuery.Resolve(ent, ref ent.Comp) || ent.Comp.IgnoreContainer == value)
|
|
return;
|
|
|
|
ent.Comp.IgnoreContainer = value;
|
|
DirtyField(ent, ent.Comp, nameof(TargetActionComponent.IgnoreContainer));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the event of an action.
|
|
/// Since the event isn't required to be serializable this is not networked.
|
|
/// Only use this if it's predicted or for a clientside action.
|
|
/// </summary>
|
|
public void SetEvent(EntityUid uid, BaseActionEvent ev)
|
|
{
|
|
// now this is meta
|
|
var setEv = new ActionSetEventEvent(ev);
|
|
RaiseLocalEvent(uid, ref setEv);
|
|
if (!setEv.Handled)
|
|
Log.Error($"Tried to set event of {ToPrettyString(uid):action} but nothing handled it!");
|
|
}
|
|
|
|
public BaseActionEvent? GetEvent(EntityUid uid)
|
|
{
|
|
DebugTools.Assert(_actionQuery.HasComp(uid), $"Entity {ToPrettyString(uid)} is missing ActionComponent");
|
|
var ev = new ActionGetEventEvent();
|
|
RaiseLocalEvent(uid, ref ev);
|
|
return ev.Event;
|
|
}
|
|
|
|
public bool SetEventTarget(EntityUid uid, EntityUid target)
|
|
{
|
|
DebugTools.Assert(_actionQuery.HasComp(uid), $"Entity {ToPrettyString(uid)} is missing ActionComponent");
|
|
var ev = new ActionSetTargetEvent(target);
|
|
RaiseLocalEvent(uid, ref ev);
|
|
return ev.Handled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the action has a cooldown and if it's still active
|
|
/// </summary>
|
|
public bool IsCooldownActive(ActionComponent action, TimeSpan? curTime = null)
|
|
{
|
|
// TODO: Check for charge recovery timer
|
|
curTime ??= GameTiming.CurTime;
|
|
return action.Cooldown.HasValue && action.Cooldown.Value.End > curTime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks the action as temporary.
|
|
/// Temporary actions get deleted upon being removed from an entity.
|
|
/// </summary>
|
|
public void SetTemporary(Entity<ActionComponent?> ent, bool temporary)
|
|
{
|
|
if (!Resolve(ent.Owner, ref ent.Comp, false))
|
|
return;
|
|
|
|
ent.Comp.Temporary = temporary;
|
|
Dirty(ent);
|
|
}
|
|
}
|