From 8812237108a194b0fdc9bacc1f28774d6e3a1f79 Mon Sep 17 00:00:00 2001 From: kaiserbirch <150971100+kaiserbirch@users.noreply.github.com> Date: Fri, 25 Apr 2025 22:53:50 +0200 Subject: [PATCH] Land mine armament (#33883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Land Mine is now armable, it will not explode unless armed. * Land Mine is now armable, it will not explode unless armed. * Explicitly have Armed as false * SharedLandMineSystem.cs adds the "Arm"-verb in "Content.Shared" with the Arming logic being implemented in "Content.Server" * Land Mines now blink only when armed. * Added prediction components, moved logic to SharedLandMineSystem.cs and inherit it in client content. * Accessing the datafield directly instead of using methods * Mines are now armed by default with a unarmed prototype * Land mine now shows if it is armed when examined and in range. * Landmine is unarmed by default with an armed variant for mapping purposes. * Removed properties that were already defined by inheritance. * Access the bool directly from the component * Add booleans to change if the Arm-verb is showed and if examining the mine shows the status. * Added status message for unarmed mine, removed using PushGroup since only one string is displayed. * Added properties to the explosive floor sign to ensure that it is armed, not showing neither status nor arm-verb. * The prototypes work now as before with added unarmed versions. Sprite is now only one toggable layer. * Make the craftable land mine unarmed. * Refactored the arming mechanic into own component and system. * Reverted the explosive wet floor sign to previous prototype and added the Armable component and ItemToggle to the landmines. * Moved the examination strings from land-mines.ftl to armable.ftl. * Removed unused property. * Formatting and fixing imports * Added prefixes to the ftl naming. Moved LocId from system to component * Added documentation. Moved check for armable to HandleStepTriggerAttempt. Moved the LocId to component. * Removed the TryArming method. Added documentation. * Removed unnecessary TryComp * Simplified the logic for the trigger attempt * HasComp instead of TryComp on logic * EmoGarbage Review --------- Co-authored-by: Franz - Josef Björck Co-authored-by: EmoGarbage404 --- Content.Server/LandMines/LandMineComponent.cs | 13 ---- Content.Server/LandMines/LandMineSystem.cs | 38 ++++++++--- Content.Shared/Armable/ArmableComponent.cs | 35 +++++++++++ Content.Shared/Armable/ArmableSystem.cs | 54 ++++++++++++++++ .../Components/ItemToggleComponent.cs | 2 +- .../Item/ItemToggle/ItemToggleSystem.cs | 15 +++++ Content.Shared/LandMines/LandMineComponent.cs | 23 +++++++ Resources/Locale/en-US/armable/armable.ftl | 2 + .../Locale/en-US/land-mines/land-mines.ftl | 1 + .../Entities/Objects/Misc/land_mine.yml | 63 +++++++++++++++++-- .../Graphs/weapons/modular_mine.yml | 4 +- 11 files changed, 219 insertions(+), 31 deletions(-) delete mode 100644 Content.Server/LandMines/LandMineComponent.cs create mode 100644 Content.Shared/Armable/ArmableComponent.cs create mode 100644 Content.Shared/Armable/ArmableSystem.cs create mode 100644 Content.Shared/LandMines/LandMineComponent.cs create mode 100644 Resources/Locale/en-US/armable/armable.ftl diff --git a/Content.Server/LandMines/LandMineComponent.cs b/Content.Server/LandMines/LandMineComponent.cs deleted file mode 100644 index 1c4ba06691..0000000000 --- a/Content.Server/LandMines/LandMineComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Audio; - -namespace Content.Server.LandMines; - -[RegisterComponent] -public sealed partial class LandMineComponent : Component -{ - /// - /// Trigger sound effect when stepping onto landmine - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public SoundSpecifier? Sound; -} diff --git a/Content.Server/LandMines/LandMineSystem.cs b/Content.Server/LandMines/LandMineSystem.cs index 22dedb9337..57d25ef8ab 100644 --- a/Content.Server/LandMines/LandMineSystem.cs +++ b/Content.Server/LandMines/LandMineSystem.cs @@ -1,7 +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 Robust.Shared.Audio; using Robust.Shared.Audio.Systems; namespace Content.Server.LandMines; @@ -14,30 +16,46 @@ public sealed class LandMineSystem : EntitySystem public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(HandleStepOnTriggered); SubscribeLocalEvent(HandleStepOffTriggered); - SubscribeLocalEvent(HandleStepTriggerAttempt); } + /// + /// Warns the player when stepped on. + /// private void HandleStepOnTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOnEvent args) { - _popupSystem.PopupCoordinates( - Loc.GetString("land-mine-triggered", ("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); } + /// + /// Sends a trigger when stepped off. + /// private void HandleStepOffTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOffEvent args) { _trigger.Trigger(uid, args.Tripper); } - private static void HandleStepTriggerAttempt(EntityUid uid, LandMineComponent component, ref StepTriggerAttemptEvent args) + /// + /// Presumes that the landmine isn't armable and should be treated as always armed. + /// If Armable and ItemToggle is present the event will continue only if the mine is activated. + /// + private void HandleStepTriggerAttempt(EntityUid uid, LandMineComponent component, ref StepTriggerAttemptEvent args) { args.Continue = true; + + if (HasComp(uid) && TryComp(uid, out var itemToggle)) + args.Continue = itemToggle.Activated; } } diff --git a/Content.Shared/Armable/ArmableComponent.cs b/Content.Shared/Armable/ArmableComponent.cs new file mode 100644 index 0000000000..11809c156c --- /dev/null +++ b/Content.Shared/Armable/ArmableComponent.cs @@ -0,0 +1,35 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Armable; + +/// +/// Makes an item armable, needs ItemToggleComponent to work. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(ArmableSystem))] +public sealed partial class ArmableComponent : Component +{ + /// + /// Does it show its status on examination? + /// + [DataField, AutoNetworkedField] + public bool ShowStatusOnExamination = true; + + /// + /// Does it change appearance when activated? + /// + [DataField, AutoNetworkedField] + public bool ChangeAppearance = true; + + /// + /// Text to show on examination when the entity is armed. + /// + [DataField] + public LocId? ExamineTextArmed = "armable-examine-armed"; + + /// + /// Text to show on examination when the entity is not armed + /// + [DataField] + public LocId? ExamineTextNotArmed ="armable-examine-not-armed"; +} diff --git a/Content.Shared/Armable/ArmableSystem.cs b/Content.Shared/Armable/ArmableSystem.cs new file mode 100644 index 0000000000..b0752ccc73 --- /dev/null +++ b/Content.Shared/Armable/ArmableSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared.Examine; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Item.ItemToggle.Components; + +namespace Content.Shared.Armable; + +/// +/// When used together with ItemToggle this will make the ItemToggle one way which is then used to represent an armed +/// state. If ItemComponent.Activated is true then the item is considered to be armed and should be able to be +/// triggered. +/// +public sealed class ArmableSystem : EntitySystem +{ + [Dependency] private readonly ItemToggleSystem _itemToggle = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(ArmingDone); + } + + /// + /// Shows the status of the armable entity on examination. + /// + private void OnExamine(EntityUid uid, ArmableComponent comp, ExaminedEvent args) + { + if (!args.IsInDetailsRange || !comp.ShowStatusOnExamination || !TryComp(uid, out var itemToggle)) + return; + + if (itemToggle.Activated) + { + if (!string.IsNullOrEmpty(comp.ExamineTextArmed)) + args.PushMarkup(Loc.GetString(comp.ExamineTextArmed, ("name", uid))); + } + else + { + if (!string.IsNullOrEmpty(comp.ExamineTextNotArmed)) + args.PushMarkup(Loc.GetString(comp.ExamineTextNotArmed,("name", uid))); + } + } + + /// + /// Changes the appearance and disables the ItemToggleComponent as to not show the deactivate verb. + /// Whatever is armed should probably not be trivially disarmed. + /// + private void ArmingDone(Entity entity, ref ItemToggledEvent args) + { + if (!args.Activated) + return; + _itemToggle.SetOnActivate(entity.Owner, false); + } +} diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs index cb6470f5d6..424bd12bb3 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs @@ -22,7 +22,7 @@ public sealed partial class ItemToggleComponent : Component /// /// Can the entity be activated in the world. /// - [DataField] + [DataField, AutoNetworkedField] public bool OnActivate = true; /// diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index 819975ecda..2c3e596a27 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -263,6 +263,21 @@ public sealed class ItemToggleSystem : EntitySystem RaiseLocalEvent(uid, ref toggleUsed); } + /// + /// Sets if this toggleable item can be activated in world by pressing "e" + /// + public void SetOnActivate(Entity ent, bool val) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + if (ent.Comp.OnActivate == val) + return; + + ent.Comp.OnActivate = val; + Dirty(ent); + } + private void UpdateVisuals(Entity ent) { if (TryComp(ent, out AppearanceComponent? appearance)) diff --git a/Content.Shared/LandMines/LandMineComponent.cs b/Content.Shared/LandMines/LandMineComponent.cs new file mode 100644 index 0000000000..1ed8091585 --- /dev/null +++ b/Content.Shared/LandMines/LandMineComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Audio; + +namespace Content.Shared.LandMines; + +/// +/// Give a warning if stepped on and will execute a trigger on step off. When used together with ArmableComponent and +/// ItemToggleComponent it will only trigger if "ItemToggle.Activated" is true. +/// +[RegisterComponent] +public sealed partial class LandMineComponent : Component +{ + /// + /// The text that popups when the landmine is stepped on. + /// + [DataField] + public LocId? TriggerText = "land-mine-triggered"; + + /// + /// Trigger sound effect when stepping onto landmine + /// + [DataField] + public SoundSpecifier? Sound; +} diff --git a/Resources/Locale/en-US/armable/armable.ftl b/Resources/Locale/en-US/armable/armable.ftl new file mode 100644 index 0000000000..ebdae76096 --- /dev/null +++ b/Resources/Locale/en-US/armable/armable.ftl @@ -0,0 +1,2 @@ +armable-examine-armed = {CAPITALIZE(THE($name))} is [color=red]armed[/color]. +armable-examine-not-armed = {CAPITALIZE(THE($name))} needs to be armed. diff --git a/Resources/Locale/en-US/land-mines/land-mines.ftl b/Resources/Locale/en-US/land-mines/land-mines.ftl index b00c9fd2b5..f6ce2aa1f7 100644 --- a/Resources/Locale/en-US/land-mines/land-mines.ftl +++ b/Resources/Locale/en-US/land-mines/land-mines.ftl @@ -1 +1,2 @@ land-mine-triggered = You step on the { $mine }! +land-mine-verb-begin = Arm diff --git a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml index a3e3485bc6..08991ec11f 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml @@ -4,7 +4,11 @@ components: - type: Clickable - type: InteractionOutline - - type: Anchorable + - type: ItemToggle + soundActivate: + path: /Audio/Weapons/click.ogg + params: + maxDistance: 1 - type: Pullable - type: MovedByPressure - type: Physics @@ -22,7 +26,16 @@ - type: Sprite drawdepth: Items sprite: Objects/Misc/landmine.rsi - state: landmine + layers: + - state: landmine-inactive + map: [ "enum.ToggleVisuals.Layer" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ToggleVisuals.Toggled: + enum.ToggleVisuals.Layer: + True: {state: landmine} + False: {state: landmine-inactive} - type: Damageable damageContainer: Inorganic - type: Destructible @@ -39,33 +52,60 @@ path: /Audio/Effects/beep_landmine.ogg params: maxDistance: 10 + - type: Armable - type: StepTrigger requiredTriggeredSpeed: 0 stepOn: true - type: entity + id: LandMineKickUnarmed name: kick mine parent: BaseLandMine - id: LandMineKick components: - type: GhostKickUserOnTrigger - type: DeleteOnTrigger +- type: entity + id: LandMineKick + suffix: armed + parent: LandMineKickUnarmed + components: + - type: ItemToggle + activated: true + onActivate: false + - type: Armable + - type: Sprite + layers: + - state: landmine + - type: entity name: modular mine description: This bad boy could be packing any number of dangers. Or a bike horn. parent: BaseLandMine - id: LandMineModular + id: LandMineModularUnarmed components: - type: PayloadCase - type: Construction graph: ModularMineGraph node: emptyCase +- type: entity + id: LandMineModular + suffix: armed + parent: LandMineModularUnarmed + components: + - type: ItemToggle + activated: true + onActivate: false + - type: Armable + - type: Sprite + layers: + - state: landmine + - type: entity name: explosive mine parent: BaseLandMine - id: LandMineExplosive + id: LandMineExplosiveUnarmed components: - type: ExplodeOnTrigger - type: Explosive @@ -74,3 +114,16 @@ intensitySlope: 3 totalIntensity: 120 # about a ~4 tile radius canCreateVacuum: false + +- type: entity + suffix: armed + parent: LandMineExplosiveUnarmed + id: LandMineExplosive + components: + - type: ItemToggle + activated: true + onActivate: false + - type: Armable + - type: Sprite + layers: + - state: landmine diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_mine.yml b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_mine.yml index 39272a9b61..b598c01cde 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_mine.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/weapons/modular_mine.yml @@ -12,7 +12,7 @@ doAfter: 1 - node: emptyCase - entity: LandMineModular + entity: LandMineModularUnarmed edges: - to: wiredCase steps: @@ -34,7 +34,7 @@ doAfter: 2 - node: wiredCase - entity: LandMineModular + entity: LandMineModularUnarmed actions: - !type:PlaySound sound: /Audio/Machines/button.ogg