From 1b53520849a60538598cae641c6e598b1a5d7534 Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Thu, 12 Sep 2024 05:04:19 -0700 Subject: [PATCH 01/10] Added the revenant Animate action --- .../Revenant/RevenantAnimatedComponent.cs | 20 ++++ .../Revenant/RevenantAnimatedSystem.cs | 54 +++++++++ .../Systems/AdminVerbSystem.Tools.cs | 22 ++++ .../Components/RevenantAnimatedComponent.cs | 16 +++ .../EntitySystems/BloodCrayonSystem.cs | 2 +- .../EntitySystems/RevenantSystem.Abilities.cs | 110 ++++++++++++++++++ .../Revenant/Components/RevenantComponent.cs | 16 ++- Content.Shared/Revenant/SharedRevenant.cs | 4 + .../Locale/en-US/store/revenant-catalog.ftl | 5 +- Resources/Prototypes/Actions/revenant.yml | 13 +++ .../Prototypes/Catalog/revenant_catalog.yml | 13 +++ 11 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 Content.Client/Revenant/RevenantAnimatedComponent.cs create mode 100644 Content.Client/Revenant/RevenantAnimatedSystem.cs create mode 100644 Content.Server/Revenant/Components/RevenantAnimatedComponent.cs diff --git a/Content.Client/Revenant/RevenantAnimatedComponent.cs b/Content.Client/Revenant/RevenantAnimatedComponent.cs new file mode 100644 index 000000000000..8cdda1e0e7ed --- /dev/null +++ b/Content.Client/Revenant/RevenantAnimatedComponent.cs @@ -0,0 +1,20 @@ +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Client.Revenant; + +[RegisterComponent, NetworkedComponent] +public sealed partial class RevenantAnimatedComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadOnly)] + public float Accumulator = 0f; + + [DataField, ViewVariables(VVAccess.ReadOnly)] + public Entity? LightOverlay; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public Color LightColor = Color.MediumPurple; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float LightRadius = 2f; +} \ No newline at end of file diff --git a/Content.Client/Revenant/RevenantAnimatedSystem.cs b/Content.Client/Revenant/RevenantAnimatedSystem.cs new file mode 100644 index 000000000000..2a3fd8462296 --- /dev/null +++ b/Content.Client/Revenant/RevenantAnimatedSystem.cs @@ -0,0 +1,54 @@ +using Robust.Client.GameObjects; +using Robust.Shared.Map; + +namespace Content.Client.Revenant; + +public sealed class RevenantAnimatedSystem : EntitySystem +{ + [Dependency] private readonly SharedPointLightSystem _lights = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var enumerator = EntityQueryEnumerator(); + + while (enumerator.MoveNext(out var uid, out var comp, out var light)) + { + if (comp.LightOverlay == null) + continue; + comp.Accumulator += frameTime; + _lights.SetEnergy(comp.LightOverlay.Value.Owner, 2f * Math.Abs((float)Math.Sin(0.25 * Math.PI * comp.Accumulator)), comp.LightOverlay.Value.Comp); + } + } + + private void OnStartup(EntityUid uid, RevenantAnimatedComponent comp, ComponentStartup args) + { + var lightEnt = Spawn(null, new EntityCoordinates(uid, 0, 0)); + var light = AddComp(lightEnt); + + comp.LightOverlay = (lightEnt, light); + + _lights.SetEnabled(uid, true, light); + _lights.SetColor(uid, comp.LightColor, light); + _lights.SetRadius(uid, comp.LightRadius, light); + Dirty(uid, light); + } + + private void OnShutdown(EntityUid uid, RevenantAnimatedComponent comp, ComponentShutdown args) + { + if (comp.LightOverlay != null) + { + Del(comp.LightOverlay); + return; + } + } +} \ No newline at end of file diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index fef8a031d9da..36df7fed86aa 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -9,6 +9,7 @@ using Content.Server.Hands.Systems; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Revenant.EntitySystems; using Content.Server.Stack; using Content.Server.Station.Components; using Content.Server.Station.Systems; @@ -25,6 +26,7 @@ using Content.Shared.Doors.Components; using Content.Shared.Hands.Components; using Content.Shared.Inventory; +using Content.Shared.Item; using Content.Shared.PDA; using Content.Shared.Stacks; using Content.Shared.Verbs; @@ -55,6 +57,7 @@ public sealed partial class AdminVerbSystem [Dependency] private readonly SharedTransformSystem _xformSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly GunSystem _gun = default!; + [Dependency] private readonly RevenantSystem _revenant = default!; private void AddTricksVerbs(GetVerbsEvent args) { @@ -734,6 +737,24 @@ private void AddTricksVerbs(GetVerbsEvent args) }; args.Verbs.Add(setCapacity); } + + if (TryComp(args.Target, out var item)) + { + Verb makeAnimate = new() + { + Text = "Animate Item", + Category = VerbCategory.Tricks, + Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Ghosts/revenant.rsi"), "icon"), + Act = () => + { + _revenant.AnimateObject(args.Target, TimeSpan.FromSeconds(60)); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-trick-make-animate-description"), + Priority = (int) TricksVerbPriorities.MakeAnimate, + }; + args.Verbs.Add(makeAnimate); + } } private void RefillEquippedTanks(EntityUid target, Gas gasType) @@ -879,5 +900,6 @@ public enum TricksVerbPriorities SnapJoints = -27, MakeMinigun = -28, SetBulletAmount = -29, + MakeAnimate = -30, } } diff --git a/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs b/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs new file mode 100644 index 000000000000..11e3010c1d30 --- /dev/null +++ b/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Weapons.Melee; +using Robust.Shared.GameStates; + +namespace Content.Server.Revenant.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class RevenantAnimatedComponent : Component +{ + /// + /// The MeleeWeaponComponent that was added when this item was animated, which + /// will be deleted when the item goes inanimate. + /// If the animated item already had a MeleeWeaponComponent, this will be null. + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public MeleeWeaponComponent? AddedMelee; +} \ No newline at end of file diff --git a/Content.Server/Revenant/EntitySystems/BloodCrayonSystem.cs b/Content.Server/Revenant/EntitySystems/BloodCrayonSystem.cs index 35e45dabe7be..d31b4e4d8bb4 100644 --- a/Content.Server/Revenant/EntitySystems/BloodCrayonSystem.cs +++ b/Content.Server/Revenant/EntitySystems/BloodCrayonSystem.cs @@ -26,7 +26,7 @@ private void OnCrayonUse(EntityUid uid, BloodCrayonComponent comp, AfterInteract if (!TryComp(args.User, out var revenant)) return; - if (!_revenant.ChangeEssenceAmount(args.User, revenant.BloodWritingCost, allowDeath: false)) + if (!_revenant.ChangeEssenceAmount(args.User, -revenant.BloodWritingCost, allowDeath: false)) { _popup.PopupEntity(Loc.GetString("revenant-not-enough-essence"), uid, args.User); args.Handled = true; diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 08e305348001..d405528bf952 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -32,6 +32,23 @@ using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Components; +using Content.Shared.Mind.Components; +using Content.Server.NPC.HTN; +using Content.Server.NPC; +using Robust.Shared.Timing; +using Content.Shared.Weapons.Melee; +using Content.Shared.CombatMode; +using Content.Server.NPC.Systems; +using Content.Shared.NPC.Components; +using Content.Shared.NPC.Systems; +using Robust.Shared.Prototypes; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Movement.Components; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Content.Shared.Cuffs.Components; namespace Content.Server.Revenant.EntitySystems; @@ -47,6 +64,13 @@ public sealed partial class RevenantSystem [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly HTNSystem _htnSystem = default!; + [Dependency] private readonly NPCSystem _npcSystem = default!; + [Dependency] private readonly NpcFactionSystem _factionSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ItemToggleSystem _itemToggleSystem = default!; + [Dependency] private readonly SharedGunSystem _gunSystem = default!; private void InitializeAbilities() { @@ -59,6 +83,7 @@ private void InitializeAbilities() SubscribeLocalEvent(OnBlightAction); SubscribeLocalEvent(OnMalfunctionAction); SubscribeLocalEvent(OnBloodWritingAction); + SubscribeLocalEvent(OnAnimateAction); } private void OnInteract(EntityUid uid, RevenantComponent component, UserActivateInWorldEvent args) @@ -371,4 +396,89 @@ private void OnBloodWritingAction(EntityUid uid, RevenantComponent component, Re EnsureComp(crayon); } } + + public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity? revenant = null) + { + if (HasComp(target) || HasComp(target)) + return; + + // TODO: Make animated handcuffs cuff people and then go inanimate + // Disabling them for now because it causes a ton of errors. + if (HasComp(target)) + return; + + if (revenant != null && !TryUseAbility(revenant.Value.Owner, revenant.Value.Comp, revenant.Value.Comp.AnimateCost, revenant.Value.Comp.AnimateDebuffs)) + return; + + if (HasComp(target) && TryComp(target, out var toggle)) + { + // Turn on welders and stun prods + _itemToggleSystem.TryActivate((target, toggle)); + } + + var animate = EnsureComp(target); + + EnsureComp(target); + if (!HasComp(target)) + { + var melee = AddComp(target); + melee.Damage = new DamageSpecifier(_prototypeManager.Index("Blunt"), 5); + animate.AddedMelee = melee; + } + + EnsureComp(target); + EnsureComp(target); + var factions = EnsureComp(target); + _factionSystem.ClearFactions((target, factions)); + _factionSystem.AddFaction((target, factions), "SimpleHostile"); + + var htn = EnsureComp(target); + if (TryComp(target, out var gun)) + { + if (TryComp(target, out var bolt)) + _gunSystem.SetBoltClosed(target, bolt, true); + htn.RootTask = new HTNCompoundTask() { Task = "SimpleRangedHostileCompound" }; + } + else + htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; + htn.Blackboard.SetValue(NPCBlackboard.Owner, target); + + _npcSystem.WakeNPC(target, htn); + _htnSystem.Replan(htn); + + if (revenant != null) + Timer.Spawn(time ?? revenant.Value.Comp.AnimateTime, () => + { + if (!animate.Deleted) + InanimateTarget(target, animate); + }); + else if (time != null) + Timer.Spawn(time.Value, () => + { + if (!animate.Deleted) + InanimateTarget(target, animate); + }); + } + + public void InanimateTarget(EntityUid target, RevenantAnimatedComponent? comp = null) + { + if (!target.Valid || !Resolve(target, ref comp)) + return; + + RemComp(target); + RemComp(target); + + if (comp.AddedMelee != null) + RemComp(target); + + RemComp(target); + } + + private void OnAnimateAction(EntityUid uid, RevenantComponent comp, RevenantAnimateEvent args) + { + if (args.Handled) + return; + + AnimateObject(args.Target, comp.AnimateTime, (uid, comp)); + } } diff --git a/Content.Shared/Revenant/Components/RevenantComponent.cs b/Content.Shared/Revenant/Components/RevenantComponent.cs index 2064edd406c8..c0c09af8808c 100644 --- a/Content.Shared/Revenant/Components/RevenantComponent.cs +++ b/Content.Shared/Revenant/Components/RevenantComponent.cs @@ -203,13 +203,27 @@ public sealed partial class RevenantComponent : Component #region Blood Writing [ViewVariables(VVAccess.ReadWrite), DataField("bloodWritingCost")] - public FixedPoint2 BloodWritingCost = -2; + public FixedPoint2 BloodWritingCost = 2; [ViewVariables(VVAccess.ReadOnly), DataField] public EntityUid? BloodCrayon; #endregion + #region Animate + [ViewVariables(VVAccess.ReadWrite), DataField] + public FixedPoint2 AnimateCost = 50; + + /// + /// How long an item should be animated for + /// + [ViewVariables(VVAccess.ReadWrite), DataField] + public TimeSpan AnimateTime = TimeSpan.FromSeconds(15); + + [ViewVariables(VVAccess.ReadWrite), DataField] + public Vector2 AnimateDebuffs = new(3, 8); + #endregion + [DataField] public ProtoId EssenceAlert = "Essence"; diff --git a/Content.Shared/Revenant/SharedRevenant.cs b/Content.Shared/Revenant/SharedRevenant.cs index ce5e2772e82c..672834012f54 100644 --- a/Content.Shared/Revenant/SharedRevenant.cs +++ b/Content.Shared/Revenant/SharedRevenant.cs @@ -66,6 +66,10 @@ public sealed partial class RevenantBloodWritingEvent : InstantActionEvent { } +public sealed partial class RevenantAnimateEvent : EntityTargetActionEvent +{ +} + [NetSerializable, Serializable] public enum RevenantVisuals : byte diff --git a/Resources/Locale/en-US/store/revenant-catalog.ftl b/Resources/Locale/en-US/store/revenant-catalog.ftl index 42f244c5f341..a9503ade18ec 100644 --- a/Resources/Locale/en-US/store/revenant-catalog.ftl +++ b/Resources/Locale/en-US/store/revenant-catalog.ftl @@ -11,4 +11,7 @@ revenant-malfunction-name = Malfunction revenant-malfunction-desc = Makes nearby electronics stop working properly. Using it leaves you vulnerable to attacks for a long period of time. revenant-blood-writing-name = Blood writing -revenant-blood-writing-desc = Summons an ethereal crayon of blood to draw glyphs with. Costs essence to use. \ No newline at end of file +revenant-blood-writing-desc = Summons an ethereal crayon of blood to draw glyphs with. Costs essence to use. + +revenant-animate-name = Animate +revenant-animate-desc = Bring handheld objects to life, making them lash out at any living beings near them. Using it leaves you vulnerable to attacks for a long period of time. \ No newline at end of file diff --git a/Resources/Prototypes/Actions/revenant.yml b/Resources/Prototypes/Actions/revenant.yml index b2b0271a9959..87e4e439d5cb 100644 --- a/Resources/Prototypes/Actions/revenant.yml +++ b/Resources/Prototypes/Actions/revenant.yml @@ -56,4 +56,17 @@ icon: Interface/Actions/blood-writing.png event: !type:RevenantBloodWritingEvent useDelay: 1 + +- type: entity + id: ActionRevenantAnimate + name: Animate + description: Costs 50 Essence. + components: + - type: EntityTargetAction + event: !type:RevenantAnimateEvent + useDelay: 1 + canTargetSelf: false + whitelist: + components: + - Item diff --git a/Resources/Prototypes/Catalog/revenant_catalog.yml b/Resources/Prototypes/Catalog/revenant_catalog.yml index b84072664335..55a180b0c74a 100644 --- a/Resources/Prototypes/Catalog/revenant_catalog.yml +++ b/Resources/Prototypes/Catalog/revenant_catalog.yml @@ -62,3 +62,16 @@ conditions: - !type:ListingLimitedStockCondition stock: 1 + +- type: listing + id: RevenantAnimate + name: revenant-animate-name + description: revenant-animate-desc + productAction: ActionRevenantAnimate + cost: + StolenEssence: 30 + categories: + - RevenantAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 From 89e2e8de3b7d90777449a2e0e4a0d44bb461a4ff Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Thu, 12 Sep 2024 14:48:39 -0700 Subject: [PATCH 02/10] Lowered the base speed of animated objects Fixed the ghostly light on animated objects not animating --- .../Revenant/RevenantAnimatedSystem.cs | 4 ++-- .../EntitySystems/RevenantSystem.Abilities.cs | 18 +++++++++++++++++- .../Revenant/Components/RevenantComponent.cs | 9 +++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Content.Client/Revenant/RevenantAnimatedSystem.cs b/Content.Client/Revenant/RevenantAnimatedSystem.cs index 2a3fd8462296..458eaa9cdd42 100644 --- a/Content.Client/Revenant/RevenantAnimatedSystem.cs +++ b/Content.Client/Revenant/RevenantAnimatedSystem.cs @@ -19,9 +19,9 @@ public override void Update(float frameTime) { base.Update(frameTime); - var enumerator = EntityQueryEnumerator(); + var enumerator = EntityQueryEnumerator(); - while (enumerator.MoveNext(out var uid, out var comp, out var light)) + while (enumerator.MoveNext(out var uid, out var comp)) { if (comp.LightOverlay == null) continue; diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index d405528bf952..4bc830b50878 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -49,6 +49,7 @@ using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Cuffs.Components; +using Content.Shared.Movement.Systems; namespace Content.Server.Revenant.EntitySystems; @@ -71,6 +72,7 @@ public sealed partial class RevenantSystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ItemToggleSystem _itemToggleSystem = default!; [Dependency] private readonly SharedGunSystem _gunSystem = default!; + [Dependency] private readonly MovementSpeedModifierSystem _moveSpeed = default!; private void InitializeAbilities() { @@ -401,7 +403,7 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target) || HasComp(target)) return; - + // TODO: Make animated handcuffs cuff people and then go inanimate // Disabling them for now because it causes a ton of errors. if (HasComp(target)) @@ -428,6 +430,20 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target); EnsureComp(target); + var moveSpeed = EnsureComp(target); + if (revenant != null) + _moveSpeed.ChangeBaseSpeed(target, + revenant.Value.Comp.AnimateWalkSpeed, + revenant.Value.Comp.AnimateSprintSpeed, + MovementSpeedModifierComponent.DefaultAcceleration + ); + else + _moveSpeed.ChangeBaseSpeed(target, + RevenantComponent.DefaultAnimateWalkSpeed, + RevenantComponent.DefaultAnimateSprintSpeed, + MovementSpeedModifierComponent.DefaultAcceleration + ); + var factions = EnsureComp(target); _factionSystem.ClearFactions((target, factions)); _factionSystem.AddFaction((target, factions), "SimpleHostile"); diff --git a/Content.Shared/Revenant/Components/RevenantComponent.cs b/Content.Shared/Revenant/Components/RevenantComponent.cs index c0c09af8808c..1cc83db732af 100644 --- a/Content.Shared/Revenant/Components/RevenantComponent.cs +++ b/Content.Shared/Revenant/Components/RevenantComponent.cs @@ -222,6 +222,15 @@ public sealed partial class RevenantComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField] public Vector2 AnimateDebuffs = new(3, 8); + + public const float DefaultAnimateWalkSpeed = 1.5f; + public const float DefaultAnimateSprintSpeed = 3.5f; + + [ViewVariables(VVAccess.ReadWrite), DataField] + public float AnimateWalkSpeed = DefaultAnimateWalkSpeed; + + [ViewVariables(VVAccess.ReadWrite), DataField] + public float AnimateSprintSpeed = DefaultAnimateSprintSpeed; #endregion [DataField] From 1e3e2e6a51e6987ca1a04dd3a5559364d06f48b3 Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Thu, 12 Sep 2024 17:32:48 -0700 Subject: [PATCH 03/10] Animated handcuffs now chase down and restrain people Sentient handcuffs can now restain people using themselves --- .../Revenant/RevenantAnimatedSystem.cs | 3 - .../InteractionActivateOperator.cs | 57 +++++++++++++ .../Considerations/TargetIsCuffableCon.cs | 10 +++ .../NPC/Systems/NPCUtilitySystem.cs | 13 +++ .../EntitySystems/RevenantAnimatedSystem.cs | 84 +++++++++++++++++++ .../EntitySystems/RevenantSystem.Abilities.cs | 9 +- Content.Shared/Cuffs/SharedCuffableSystem.cs | 17 +++- Resources/Locale/en-US/revenant/revenant.ftl | 5 +- .../Entities/Objects/Misc/handcuffs.yml | 4 + .../Prototypes/NPCs/Animated/handcuffs.yml | 49 +++++++++++ 10 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs create mode 100644 Content.Server/NPC/Queries/Considerations/TargetIsCuffableCon.cs create mode 100644 Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs create mode 100644 Resources/Prototypes/NPCs/Animated/handcuffs.yml diff --git a/Content.Client/Revenant/RevenantAnimatedSystem.cs b/Content.Client/Revenant/RevenantAnimatedSystem.cs index 458eaa9cdd42..2fcd2f95df59 100644 --- a/Content.Client/Revenant/RevenantAnimatedSystem.cs +++ b/Content.Client/Revenant/RevenantAnimatedSystem.cs @@ -46,9 +46,6 @@ private void OnStartup(EntityUid uid, RevenantAnimatedComponent comp, ComponentS private void OnShutdown(EntityUid uid, RevenantAnimatedComponent comp, ComponentShutdown args) { if (comp.LightOverlay != null) - { Del(comp.LightOverlay); - return; - } } } \ No newline at end of file diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs new file mode 100644 index 000000000000..fc3a10882f03 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs @@ -0,0 +1,57 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions; + +public sealed partial class InteractionActivateOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField("targetKey")] + public string Key = "Target"; + + /// + /// If this alt-interaction started a do_after where does the key get stored. + /// + [DataField("idleKey")] + public string IdleKey = "IdleTime"; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) + { + return new(true, new Dictionary() + { + { IdleKey, 1f } + }); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var target = blackboard.GetValue(Key); + var intSystem = _entManager.System(); + var count = 0; + + if (_entManager.TryGetComponent(owner, out var doAfter)) + { + count = doAfter.DoAfters.Count; + } + + var result = intSystem.InteractionActivate(owner, target); + + // Interaction started a doafter so set the idle time to it. + if (result && doAfter != null && count != doAfter.DoAfters.Count) + { + var wait = doAfter.DoAfters.First().Value.Args.Delay; + blackboard.SetValue(IdleKey, (float) wait.TotalSeconds + 0.5f); + } + else + { + blackboard.SetValue(IdleKey, 1f); + } + + return result ? HTNOperatorStatus.Finished : HTNOperatorStatus.Failed; + } +} diff --git a/Content.Server/NPC/Queries/Considerations/TargetIsCuffableCon.cs b/Content.Server/NPC/Queries/Considerations/TargetIsCuffableCon.cs new file mode 100644 index 000000000000..7b355dd3ae9d --- /dev/null +++ b/Content.Server/NPC/Queries/Considerations/TargetIsCuffableCon.cs @@ -0,0 +1,10 @@ +namespace Content.Server.NPC.Queries.Considerations; + +/// +/// Returns 1f if the target can be cuffed or 0f if not. +/// + +public sealed partial class TargetIsCuffableCon : UtilityConsideration +{ + +} diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index 8dff93648bff..c4485e50ce88 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -7,6 +7,8 @@ using Content.Server.Nutrition.EntitySystems; using Content.Server.Storage.Components; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Cuffs; +using Content.Shared.Cuffs.Components; using Content.Shared.Damage; using Content.Shared.Examine; using Content.Shared.Fluids.Components; @@ -52,6 +54,7 @@ public sealed class NPCUtilitySystem : EntitySystem [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly MobThresholdSystem _thresholdSystem = default!; + [Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!; private EntityQuery _puddleQuery; private EntityQuery _xformQuery; @@ -351,6 +354,16 @@ private float GetScore(NPCBlackboard blackboard, EntityUid targetUid, UtilityCon return 0f; } + case TargetIsCuffableCon: + { + if (TryComp(targetUid, out var cuffable)) + { + if(_cuffableSystem.IsCuffed((targetUid, cuffable), true)) + return 0f; + return 1f; + } + return 0f; + } default: throw new NotImplementedException(); } diff --git a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs new file mode 100644 index 000000000000..ed0cacf4c93b --- /dev/null +++ b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs @@ -0,0 +1,84 @@ +using Content.Shared.Cuffs; +using Content.Shared.Cuffs.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Inventory; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Containers; +using Content.Server.Revenant.Components; +using Content.Shared.Interaction; +using Content.Shared.Popups; + +namespace Content.Server.Revenant.EntitySystems; + +public sealed class RevenantAnimatedSystem : EntitySystem +{ + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMeleeHit, before: [typeof(SharedCuffableSystem)]); + SubscribeLocalEvent(OnCuffInteract, before: [typeof(SharedCuffableSystem)]); + } + + private void OnMeleeHit(EntityUid uid, RevenantAnimatedComponent comp, MeleeHitEvent args) + { + if (args.Handled) + return; + + if (args.HitEntities.Count == 0) + return; + + var hitEntity = args.HitEntities[0]; + + // Handcuffs will attempt to jump into the victim's hands/pockets before trying to cuff them + if (HasComp(uid) && HasComp(hitEntity)) + TryJumpIntoSlots(uid, hitEntity); + } + + private void OnCuffInteract(EntityUid uid, RevenantAnimatedComponent comp, UserActivateInWorldEvent args) + { + if (args.Handled) + return; + + if (HasComp(uid) && HasComp(args.Target)) + TryJumpIntoSlots(uid, args.Target); + } + + private void TryJumpIntoSlots(EntityUid uid, EntityUid target) + { + if (_container.ContainsEntity(target, uid)) + return; + + Log.Debug($"{uid} trying to jump into {target} pocket1"); + + if (_inventory.TryGetSlotContainer(target, "pocket1", out var pocket1, out _) + && _container.Insert(uid, pocket1) + ) + { + _popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp(uid).EntityName)), target, target); + return; + } + + Log.Debug($"{uid} trying to jump into {target} pocket2"); + + if (_inventory.TryGetSlotContainer(target, "pocket2", out var pocket2, out _) + && _container.Insert(uid, pocket2) + ) + { + _popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp(uid).EntityName)), target, target); + return; + } + + Log.Debug($"{uid} trying to jump into {target} hands"); + if (_hands.TryPickupAnyHand(target, uid)) + { + _popup.PopupEntity(Loc.GetString("item-jump-into-hands", ("name", Comp(uid).EntityName)), target, target); + return; + } + } +} \ No newline at end of file diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 4bc830b50878..2c6b9b2f4c09 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -404,11 +404,6 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target) || HasComp(target)) return; - // TODO: Make animated handcuffs cuff people and then go inanimate - // Disabling them for now because it causes a ton of errors. - if (HasComp(target)) - return; - if (revenant != null && !TryUseAbility(revenant.Value.Owner, revenant.Value.Comp, revenant.Value.Comp.AnimateCost, revenant.Value.Comp.AnimateDebuffs)) return; @@ -448,6 +443,8 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target); + var htn = EnsureComp(target); if (TryComp(target, out var gun)) { @@ -455,6 +452,8 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target, out var handcuff)) + htn.RootTask = new HTNCompoundTask() { Task = "AnimatedHandcuffsCompound" }; else htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; htn.Blackboard.SetValue(NPCBlackboard.Owner, target); diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 21d09c744cd6..e4ef732f8fc8 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -87,6 +87,7 @@ public override void Initialize() SubscribeLocalEvent(OnCuffMeleeHit); SubscribeLocalEvent(OnAddCuffDoAfter); SubscribeLocalEvent(OnCuffVirtualItemDeleted); + SubscribeLocalEvent(OnCuffInteract); } private void CheckInteract(Entity ent, ref InteractionAttemptEvent args) @@ -323,6 +324,15 @@ private void OnCuffMeleeHit(EntityUid uid, HandcuffComponent component, MeleeHit args.Handled = true; } + private void OnCuffInteract(EntityUid uid, HandcuffComponent component, UserActivateInWorldEvent args) + { + if (args.Handled) + return; + + TryCuffing(args.User, args.Target, uid, component); + args.Handled = true; + } + private void OnAddCuffDoAfter(EntityUid uid, HandcuffComponent component, AddCuffDoAfterEvent args) { var user = args.Args.User; @@ -462,7 +472,8 @@ public bool TryAddNewCuffs(EntityUid target, EntityUid user, EntityUid handcuff, return false; // Success! - _hands.TryDrop(user, handcuff); + if (user != handcuff) + _hands.TryDrop(user, handcuff); _container.Insert(handcuff, component.Container); UpdateHeldItems(target, handcuff, component); @@ -489,7 +500,7 @@ public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, Han return true; } - if (!_hands.CanDrop(user, handcuff)) + if (user != handcuff && !_hands.CanDrop(user, handcuff)) { _popup.PopupClient(Loc.GetString("handcuff-component-cannot-drop-cuffs", ("target", Identity.Name(target, EntityManager, user))), user, user); return false; @@ -508,7 +519,7 @@ public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, Han BreakOnMove = true, BreakOnWeightlessMove = false, BreakOnDamage = true, - NeedHand = true, + NeedHand = user != handcuff, DistanceThreshold = 1f // shorter than default but still feels good }; diff --git a/Resources/Locale/en-US/revenant/revenant.ftl b/Resources/Locale/en-US/revenant/revenant.ftl index f4ea6d79ff39..f6b1238f9eb7 100644 --- a/Resources/Locale/en-US/revenant/revenant.ftl +++ b/Resources/Locale/en-US/revenant/revenant.ftl @@ -20,4 +20,7 @@ revenant-soul-finish-harvest = {CAPITALIZE(THE($target))} slumps onto the ground revenant-user-interface-title = Ability Shop revenant-user-interface-essence-amount = [color=plum]{$amount}[/color] Stolen Essence -revenant-user-interface-cost = {$price} Essence \ No newline at end of file +revenant-user-interface-cost = {$price} Essence + +item-jump-into-pocket = The {$name} jumps into your pocket! +item-jump-into-hands = The {$name} jumps into your hands! \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml b/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml index ee796f1d7662..0f0e041a42ed 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/handcuffs.yml @@ -10,6 +10,7 @@ - type: Handcuff cuffedRSI: Objects/Misc/handcuffs.rsi bodyIconState: body-overlay + - type: DoAfter - type: Sprite sprite: Objects/Misc/handcuffs.rsi state: handcuff @@ -53,6 +54,7 @@ path: /Audio/Items/Handcuffs/rope_breakout.ogg startBreakoutSound: path: /Audio/Items/Handcuffs/rope_takeoff.ogg + - type: DoAfter - type: Construction graph: makeshifthandcuffs node: cuffscable @@ -94,6 +96,7 @@ path: /Audio/Items/Handcuffs/rope_breakout.ogg startBreakoutSound: path: /Audio/Items/Handcuffs/rope_takeoff.ogg + - type: DoAfter - type: Sprite sprite: Objects/Misc/zipties.rsi state: cuff @@ -149,6 +152,7 @@ components: - type: Item size: Normal + - type: DoAfter - type: Handcuff cuffedRSI: Clothing/OuterClothing/Misc/straight_jacket.rsi breakoutTime: 100 diff --git a/Resources/Prototypes/NPCs/Animated/handcuffs.yml b/Resources/Prototypes/NPCs/Animated/handcuffs.yml new file mode 100644 index 000000000000..4998dbdf04f3 --- /dev/null +++ b/Resources/Prototypes/NPCs/Animated/handcuffs.yml @@ -0,0 +1,49 @@ +- type: htnCompound + id: AnimatedHandcuffsCompound + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:UtilityOperator + proto: NearbyCuffableTargets + + - !type:HTNPrimitiveTask + operator: !type:MoveToOperator + pathfindInPlanning: true + removeKeyOnFinish: false + targetKey: TargetCoordinates + pathfindKey: TargetPathfind + rangeKey: MeleeRange + + - !type:PrimitiveTask + preconditions: + - !type:KeyExistsPrecondition + key: Target + operator: !type:InteractionActivateOperator + + - !type:HTNPrimitiveTask + preconditions: + - !type:KeyExistsPrecondition + key: IdleTime + operator: !type:WaitOperator + key: IdleTime + + - tasks: + - !type:HTNCompoundTask + task: IdleCompound + +- type: utilityQuery + id: NearbyCuffableTargets + query: + - !type:NearbyHostilesQuery + considerations: + - !type:TargetIsAliveCon + curve: !type:BoolCurve + - !type:TargetDistanceCon + curve: !type:PresetCurve + preset: TargetDistance + - !type:TargetAccessibleCon + curve: !type:BoolCurve + - !type:TargetInLOSOrCurrentCon + curve: !type:BoolCurve + - !type:TargetIsCuffableCon + curve: !type:BoolCurve \ No newline at end of file From a7a739acabf10321c1ece1cf43e73c37f747b81b Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Thu, 12 Sep 2024 18:02:07 -0700 Subject: [PATCH 04/10] Added a popup message upon animating an item --- .../Revenant/EntitySystems/RevenantSystem.Abilities.cs | 5 +++++ Resources/Locale/en-US/revenant/revenant.ftl | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 2c6b9b2f4c09..20fb66a7d49e 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -50,6 +50,7 @@ using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Cuffs.Components; using Content.Shared.Movement.Systems; +using Robust.Shared.Player; namespace Content.Server.Revenant.EntitySystems; @@ -415,6 +416,8 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target); + _popup.PopupEntity(Loc.GetString("revenant-animate-item-animate", ("name", Comp(target).EntityName)), target, Filter.Pvs(target), true); + EnsureComp(target); if (!HasComp(target)) { @@ -487,6 +490,8 @@ public void InanimateTarget(EntityUid target, RevenantAnimatedComponent? comp = RemComp(target); RemComp(target); + + _popup.PopupEntity(Loc.GetString("revenant-animate-item-inanimate", ("name", Comp(target).EntityName)), target, Filter.Pvs(target), true); } private void OnAnimateAction(EntityUid uid, RevenantComponent comp, RevenantAnimateEvent args) diff --git a/Resources/Locale/en-US/revenant/revenant.ftl b/Resources/Locale/en-US/revenant/revenant.ftl index f6b1238f9eb7..2af2be895316 100644 --- a/Resources/Locale/en-US/revenant/revenant.ftl +++ b/Resources/Locale/en-US/revenant/revenant.ftl @@ -23,4 +23,7 @@ revenant-user-interface-essence-amount = [color=plum]{$amount}[/color] Stolen Es revenant-user-interface-cost = {$price} Essence item-jump-into-pocket = The {$name} jumps into your pocket! -item-jump-into-hands = The {$name} jumps into your hands! \ No newline at end of file +item-jump-into-hands = The {$name} jumps into your hands! + +revenant-animate-item-animate = The {$name} becomes aggressive! +revenant-animate-item-inanimate = The {$name} falls inert. \ No newline at end of file From 00ec5f78a456bb62fbb6ad3c457d338ad8a18e76 Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Thu, 12 Sep 2024 21:52:22 -0700 Subject: [PATCH 05/10] Animated grenades now jump into people's pockets and activate JumpIntoSlots is now an HTN task instead of a weird hack involving the components --- .../EntitySystems/TriggerSystem.OnUse.cs | 15 ++++ .../Preconditions/ActiveTimerPrecondition.cs | 25 ++++++ .../Preconditions/InContainerPrecondition.cs | 3 +- .../HTN/Preconditions/ThrownPrecondition.cs | 20 +++++ .../InteractionActivateSelfOperator.cs | 53 ++++++++++++ ...s => InteractionActivateTargetOperator.cs} | 2 +- .../Interactions/JumpInSlotsOperator.cs | 39 +++++++++ .../EntitySystems/RevenantAnimatedSystem.cs | 84 ------------------- .../EntitySystems/RevenantSystem.Abilities.cs | 7 +- .../Inventory/InventorySystem.Slots.cs | 30 +++++++ .../Prototypes/NPCs/Animated/grenades.yml | 43 ++++++++++ .../Prototypes/NPCs/Animated/handcuffs.yml | 14 +++- 12 files changed, 244 insertions(+), 91 deletions(-) create mode 100644 Content.Server/NPC/HTN/Preconditions/ActiveTimerPrecondition.cs create mode 100644 Content.Server/NPC/HTN/Preconditions/ThrownPrecondition.cs create mode 100644 Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateSelfOperator.cs rename Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/{InteractionActivateOperator.cs => InteractionActivateTargetOperator.cs} (95%) create mode 100644 Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/JumpInSlotsOperator.cs delete mode 100644 Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs create mode 100644 Resources/Prototypes/NPCs/Animated/grenades.yml diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs index d06e9fa1c2da..5337460b2993 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs @@ -1,6 +1,7 @@ using Content.Server.Explosion.Components; using Content.Shared.Examine; using Content.Shared.Explosion.Components; +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Popups; using Content.Shared.Sticky; @@ -15,12 +16,26 @@ public sealed partial class TriggerSystem private void InitializeOnUse() { SubscribeLocalEvent(OnTimerUse); + SubscribeLocalEvent(OnUseSelf); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent>(OnGetAltVerbs); SubscribeLocalEvent(OnStuck); SubscribeLocalEvent(OnRandomTimerTriggerMapInit); } + private void OnUseSelf(EntityUid uid, OnUseTimerTriggerComponent comp, UserActivateInWorldEvent args) + { + // Allow grenades to activate themselves (if they're posessed or ghost role) + + if (args.Handled) + return; + + if (args.Target != uid) + return; + + StartTimer((uid, comp), uid); + } + private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, ref EntityStuckEvent args) { if (!component.StartOnStick) diff --git a/Content.Server/NPC/HTN/Preconditions/ActiveTimerPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/ActiveTimerPrecondition.cs new file mode 100644 index 000000000000..ed370970c92b --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/ActiveTimerPrecondition.cs @@ -0,0 +1,25 @@ +using Content.Shared.Explosion.Components; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Checks for the presence of +/// +public sealed partial class ActiveTimerPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + /// + /// When true, passes this precondition when the entity has an active timer. + /// Otherwise, passes this precondition when the entity does not have an active timer. + /// + [DataField] + public bool Active = true; + + public override bool IsMet(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + return Active == _entManager.HasComponent(owner); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs index aa0ad98edecc..a32bae79f94f 100644 --- a/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs @@ -21,7 +21,6 @@ public override bool IsMet(NPCBlackboard blackboard) { var owner = blackboard.GetValue(NPCBlackboard.Owner); - return IsInContainer && _container.IsEntityInContainer(owner) || - !IsInContainer && !_container.IsEntityInContainer(owner); + return IsInContainer == _container.IsEntityInContainer(owner); } } diff --git a/Content.Server/NPC/HTN/Preconditions/ThrownPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/ThrownPrecondition.cs new file mode 100644 index 000000000000..74d13520d890 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/ThrownPrecondition.cs @@ -0,0 +1,20 @@ +using Content.Shared.Throwing; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Checks if the owner is being thrown or not +/// +public sealed partial class ThrownPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entMan = default!; + + [ViewVariables(VVAccess.ReadWrite)] [DataField] public bool IsBeingThrown = true; + + public override bool IsMet(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + return IsBeingThrown == _entMan.HasComponent(owner); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateSelfOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateSelfOperator.cs new file mode 100644 index 000000000000..038612c4b9d8 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateSelfOperator.cs @@ -0,0 +1,53 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions; + +public sealed partial class InteractionActivateSelfOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + /// + /// If this alt-interaction started a do_after where does the key get stored. + /// + [DataField("idleKey")] + public string IdleKey = "IdleTime"; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) + { + return new(true, new Dictionary() + { + { IdleKey, 1f } + }); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var intSystem = _entManager.System(); + var count = 0; + + if (_entManager.TryGetComponent(owner, out var doAfter)) + { + count = doAfter.DoAfters.Count; + } + + var result = intSystem.InteractionActivate(owner, owner); + + // Interaction started a doafter so set the idle time to it. + if (result && doAfter != null && count != doAfter.DoAfters.Count) + { + var wait = doAfter.DoAfters.First().Value.Args.Delay; + blackboard.SetValue(IdleKey, (float) wait.TotalSeconds + 0.5f); + } + else + { + blackboard.SetValue(IdleKey, 1f); + } + + return result ? HTNOperatorStatus.Finished : HTNOperatorStatus.Failed; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateTargetOperator.cs similarity index 95% rename from Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs rename to Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateTargetOperator.cs index fc3a10882f03..e5002542a709 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/InteractionActivateTargetOperator.cs @@ -6,7 +6,7 @@ namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions; -public sealed partial class InteractionActivateOperator : HTNOperator +public sealed partial class InteractionActivateTargetOperator : HTNOperator { [Dependency] private readonly IEntityManager _entManager = default!; diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/JumpInSlotsOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/JumpInSlotsOperator.cs new file mode 100644 index 000000000000..784cd298cc89 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/JumpInSlotsOperator.cs @@ -0,0 +1,39 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Inventory; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions; + +public sealed partial class JumpInSlotsOperator : HTNOperator +{ + private InventorySystem _inventory = default!; + + [DataField("targetKey")] + public string Key = "Target"; + + /// + /// If true, a failed attempt to jump into slots will still return + /// + [DataField] + public bool IgnoreFail = false; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + + _inventory = sysManager.GetEntitySystem(); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var target = blackboard.GetValue(Key); + + var result = _inventory.TryJumpIntoSlots(owner, target); + + return result || IgnoreFail ? HTNOperatorStatus.Finished : HTNOperatorStatus.Failed; + } +} diff --git a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs deleted file mode 100644 index ed0cacf4c93b..000000000000 --- a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Content.Shared.Cuffs; -using Content.Shared.Cuffs.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Inventory; -using Content.Shared.Weapons.Melee.Events; -using Robust.Shared.Containers; -using Content.Server.Revenant.Components; -using Content.Shared.Interaction; -using Content.Shared.Popups; - -namespace Content.Server.Revenant.EntitySystems; - -public sealed class RevenantAnimatedSystem : EntitySystem -{ - [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMeleeHit, before: [typeof(SharedCuffableSystem)]); - SubscribeLocalEvent(OnCuffInteract, before: [typeof(SharedCuffableSystem)]); - } - - private void OnMeleeHit(EntityUid uid, RevenantAnimatedComponent comp, MeleeHitEvent args) - { - if (args.Handled) - return; - - if (args.HitEntities.Count == 0) - return; - - var hitEntity = args.HitEntities[0]; - - // Handcuffs will attempt to jump into the victim's hands/pockets before trying to cuff them - if (HasComp(uid) && HasComp(hitEntity)) - TryJumpIntoSlots(uid, hitEntity); - } - - private void OnCuffInteract(EntityUid uid, RevenantAnimatedComponent comp, UserActivateInWorldEvent args) - { - if (args.Handled) - return; - - if (HasComp(uid) && HasComp(args.Target)) - TryJumpIntoSlots(uid, args.Target); - } - - private void TryJumpIntoSlots(EntityUid uid, EntityUid target) - { - if (_container.ContainsEntity(target, uid)) - return; - - Log.Debug($"{uid} trying to jump into {target} pocket1"); - - if (_inventory.TryGetSlotContainer(target, "pocket1", out var pocket1, out _) - && _container.Insert(uid, pocket1) - ) - { - _popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp(uid).EntityName)), target, target); - return; - } - - Log.Debug($"{uid} trying to jump into {target} pocket2"); - - if (_inventory.TryGetSlotContainer(target, "pocket2", out var pocket2, out _) - && _container.Insert(uid, pocket2) - ) - { - _popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp(uid).EntityName)), target, target); - return; - } - - Log.Debug($"{uid} trying to jump into {target} hands"); - if (_hands.TryPickupAnyHand(target, uid)) - { - _popup.PopupEntity(Loc.GetString("item-jump-into-hands", ("name", Comp(uid).EntityName)), target, target); - return; - } - } -} \ No newline at end of file diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 20fb66a7d49e..ec544ec4ca69 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -51,6 +51,7 @@ using Content.Shared.Cuffs.Components; using Content.Shared.Movement.Systems; using Robust.Shared.Player; +using Content.Shared.Explosion.Components; namespace Content.Server.Revenant.EntitySystems; @@ -449,14 +450,16 @@ public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity(target); var htn = EnsureComp(target); - if (TryComp(target, out var gun)) + if (HasComp(target)) { if (TryComp(target, out var bolt)) _gunSystem.SetBoltClosed(target, bolt, true); htn.RootTask = new HTNCompoundTask() { Task = "SimpleRangedHostileCompound" }; } - else if (TryComp(target, out var handcuff)) + else if (HasComp(target)) htn.RootTask = new HTNCompoundTask() { Task = "AnimatedHandcuffsCompound" }; + else if (HasComp(target)) + htn.RootTask = new HTNCompoundTask() { Task = "AnimatedGrenadeCompound" }; else htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; htn.Blackboard.SetValue(NPCBlackboard.Owner, target); diff --git a/Content.Shared/Inventory/InventorySystem.Slots.cs b/Content.Shared/Inventory/InventorySystem.Slots.cs index 2522dd5d0a33..52151d7289bf 100644 --- a/Content.Shared/Inventory/InventorySystem.Slots.cs +++ b/Content.Shared/Inventory/InventorySystem.Slots.cs @@ -124,6 +124,36 @@ public bool TryGetSlot(EntityUid uid, string slot, [NotNullWhen(true)] out SlotD return false; } + public bool TryJumpIntoSlots(EntityUid uid, EntityUid target) + { + if (_containerSystem.ContainsEntity(target, uid)) + return true; + + if (TryGetSlotContainer(target, "pocket1", out var pocket1, out _) + && _containerSystem.Insert(uid, pocket1) + ) + { + _popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp(uid).EntityName)), target, target); + return true; + } + + if (TryGetSlotContainer(target, "pocket2", out var pocket2, out _) + && _containerSystem.Insert(uid, pocket2) + ) + { + _popup.PopupEntity(Loc.GetString("item-jump-into-pocket", ("name", Comp(uid).EntityName)), target, target); + return true; + } + + if (_handsSystem.TryPickupAnyHand(target, uid)) + { + _popup.PopupEntity(Loc.GetString("item-jump-into-hands", ("name", Comp(uid).EntityName)), target, target); + return true; + } + + return false; + } + public bool TryGetContainerSlotEnumerator(Entity entity, out InventorySlotEnumerator containerSlotEnumerator, SlotFlags flags = SlotFlags.All) { if (!Resolve(entity.Owner, ref entity.Comp, false)) diff --git a/Resources/Prototypes/NPCs/Animated/grenades.yml b/Resources/Prototypes/NPCs/Animated/grenades.yml new file mode 100644 index 000000000000..dc1bbb1c2ad4 --- /dev/null +++ b/Resources/Prototypes/NPCs/Animated/grenades.yml @@ -0,0 +1,43 @@ +- type: htnCompound + id: AnimatedGrenadeCompound + branches: + # Try and find an entity with pockets, jump into the pockets and activate self. + - preconditions: + - !type:InContainerPrecondition + isInContainer: false + - !type:ThrownPrecondition + isBeingThrown: false + tasks: + - !type:HTNPrimitiveTask + operator: !type:UtilityOperator + proto: NearbyMeleeTargets + + - !type:HTNPrimitiveTask + operator: !type:MoveToOperator + pathfindInPlanning: true + removeKeyOnFinish: false + targetKey: TargetCoordinates + pathfindKey: TargetPathfind + rangeKey: MovementRangeClose + + - !type:HTNPrimitiveTask + preconditions: + - !type:KeyExistsPrecondition + key: Target + - !type:TargetInRangePrecondition + targetKey: Target + rangeKey: MeleeRange + operator: !type:JumpInSlotsOperator + ignoreFail: true + + - !type:PrimitiveTask + preconditions: + - !type:KeyExistsPrecondition + key: Target + - !type:TargetInRangePrecondition + targetKey: Target + rangeKey: MeleeRange + operator: !type:InteractionActivateSelfOperator + - tasks: + - !type:HTNCompoundTask + task: IdleCompound \ No newline at end of file diff --git a/Resources/Prototypes/NPCs/Animated/handcuffs.yml b/Resources/Prototypes/NPCs/Animated/handcuffs.yml index 4998dbdf04f3..d5c650431167 100644 --- a/Resources/Prototypes/NPCs/Animated/handcuffs.yml +++ b/Resources/Prototypes/NPCs/Animated/handcuffs.yml @@ -12,13 +12,23 @@ removeKeyOnFinish: false targetKey: TargetCoordinates pathfindKey: TargetPathfind - rangeKey: MeleeRange + rangeKey: MovementRangeClose + + - !type:HTNPrimitiveTask + preconditions: + - !type:KeyExistsPrecondition + key: Target + - !type:TargetInRangePrecondition + targetKey: Target + rangeKey: MeleeRange + operator: !type:JumpInSlotsOperator + ignoreFail: true - !type:PrimitiveTask preconditions: - !type:KeyExistsPrecondition key: Target - operator: !type:InteractionActivateOperator + operator: !type:InteractionActivateTargetOperator - !type:HTNPrimitiveTask preconditions: From 6e4f41dbc97405997c85a13911f5d6d9a74ab62e Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Fri, 13 Sep 2024 00:30:22 -0700 Subject: [PATCH 06/10] Move AnimateItem to its own system --- .../Revenant/RevenantAnimatedSystem.cs | 7 +- .../Systems/AdminVerbSystem.Tools.cs | 24 ++- .../Components/RevenantAnimatedComponent.cs | 19 +- .../EntitySystems/RevenantAnimatedSystem.cs | 168 ++++++++++++++++++ .../EntitySystems/RevenantSystem.Abilities.cs | 128 +------------ 5 files changed, 210 insertions(+), 136 deletions(-) create mode 100644 Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs diff --git a/Content.Client/Revenant/RevenantAnimatedSystem.cs b/Content.Client/Revenant/RevenantAnimatedSystem.cs index 2fcd2f95df59..d93eb0d9276b 100644 --- a/Content.Client/Revenant/RevenantAnimatedSystem.cs +++ b/Content.Client/Revenant/RevenantAnimatedSystem.cs @@ -37,10 +37,9 @@ private void OnStartup(EntityUid uid, RevenantAnimatedComponent comp, ComponentS comp.LightOverlay = (lightEnt, light); - _lights.SetEnabled(uid, true, light); - _lights.SetColor(uid, comp.LightColor, light); - _lights.SetRadius(uid, comp.LightRadius, light); - Dirty(uid, light); + _lights.SetEnabled(lightEnt, true, light); + _lights.SetColor(lightEnt, comp.LightColor, light); + _lights.SetRadius(lightEnt, comp.LightRadius, light); } private void OnShutdown(EntityUid uid, RevenantAnimatedComponent comp, ComponentShutdown args) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index 36df7fed86aa..96d95f0648bc 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -9,6 +9,7 @@ using Content.Server.Hands.Systems; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Revenant.Components; using Content.Server.Revenant.EntitySystems; using Content.Server.Stack; using Content.Server.Station.Components; @@ -57,7 +58,7 @@ public sealed partial class AdminVerbSystem [Dependency] private readonly SharedTransformSystem _xformSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly GunSystem _gun = default!; - [Dependency] private readonly RevenantSystem _revenant = default!; + [Dependency] private readonly RevenantAnimatedSystem _revenantAnimate = default!; private void AddTricksVerbs(GetVerbsEvent args) { @@ -747,7 +748,7 @@ private void AddTricksVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Ghosts/revenant.rsi"), "icon"), Act = () => { - _revenant.AnimateObject(args.Target, TimeSpan.FromSeconds(60)); + _revenantAnimate.TryAnimateObject(args.Target, TimeSpan.FromSeconds(60)); }, Impact = LogImpact.High, Message = Loc.GetString("admin-trick-make-animate-description"), @@ -755,6 +756,24 @@ private void AddTricksVerbs(GetVerbsEvent args) }; args.Verbs.Add(makeAnimate); } + + if (TryComp(args.Target, out var animate)) + { + Verb makeInanimate = new() + { + Text = "Inanimate Item", + Category = VerbCategory.Tricks, + Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Ghosts/revenant.rsi"), "icon"), + Act = () => + { + _revenantAnimate.InanimateTarget(args.Target, animate); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-trick-make-inanimate-description"), + Priority = (int) TricksVerbPriorities.MakeInanimate, + }; + args.Verbs.Add(makeInanimate); + } } private void RefillEquippedTanks(EntityUid target, Gas gasType) @@ -901,5 +920,6 @@ public enum TricksVerbPriorities MakeMinigun = -28, SetBulletAmount = -29, MakeAnimate = -30, + MakeInanimate = -31, } } diff --git a/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs b/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs index 11e3010c1d30..43b3a991336f 100644 --- a/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs +++ b/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs @@ -1,16 +1,25 @@ -using Content.Shared.Weapons.Melee; +using System.Threading; +using Content.Server.Revenant.EntitySystems; +using Content.Shared.Revenant.Components; using Robust.Shared.GameStates; namespace Content.Server.Revenant.Components; [RegisterComponent, NetworkedComponent] +[Access(typeof(RevenantAnimatedSystem))] public sealed partial class RevenantAnimatedComponent : Component { /// - /// The MeleeWeaponComponent that was added when this item was animated, which - /// will be deleted when the item goes inanimate. - /// If the animated item already had a MeleeWeaponComponent, this will be null. + /// The revenant that animated this item. Used for initialization. /// [DataField, ViewVariables(VVAccess.ReadOnly)] - public MeleeWeaponComponent? AddedMelee; + public Entity? Revenant; + + /// + /// Components added to make this item animated. + /// Removed when the item becomes inanimate. + /// + public List AddedComponents = new(); + + public CancellationTokenSource CancelToken; } \ No newline at end of file diff --git a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs new file mode 100644 index 000000000000..2948488460a7 --- /dev/null +++ b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs @@ -0,0 +1,168 @@ +using Content.Shared.Popups; +using Content.Shared.Damage; +using Content.Server.Revenant.Components; +using Content.Shared.DoAfter; +using Content.Shared.Revenant.Components; +using Content.Server.NPC.HTN; +using Content.Server.NPC; +using Content.Shared.Weapons.Melee; +using Content.Shared.CombatMode; +using Content.Shared.NPC.Components; +using Content.Shared.NPC.Systems; +using Robust.Shared.Prototypes; +using Content.Shared.Damage.Prototypes; +using Content.Shared.Movement.Components; +using Content.Shared.Item.ItemToggle; +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Content.Shared.Cuffs.Components; +using Content.Shared.Movement.Systems; +using Robust.Shared.Player; +using Content.Shared.Explosion.Components; +using Content.Shared.Mind.Components; +using System.Threading; +using Timer = Robust.Shared.Timing.Timer; + +namespace Content.Server.Revenant.EntitySystems; + +public sealed partial class RevenantAnimatedSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly NpcFactionSystem _factionSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ItemToggleSystem _itemToggleSystem = default!; + [Dependency] private readonly SharedGunSystem _gunSystem = default!; + [Dependency] private readonly MovementSpeedModifierSystem _moveSpeed = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnComponentShutdown); + + } + + private void OnComponentStartup(Entity ent, ref ComponentStartup _) + { + if (ent.Comp.LifeStage != ComponentLifeStage.Starting) + return; + + if (HasComp(ent.Owner) && TryComp(ent.Owner, out var toggle)) + _itemToggleSystem.TryActivate((ent.Owner, toggle)); + + _popup.PopupEntity(Loc.GetString("revenant-animate-item-animate", ("name", Comp(ent.Owner).EntityName)), ent.Owner, Filter.Pvs(ent.Owner), true); + + if (EnsureHelper(ent, out var melee)) + melee.Damage = new DamageSpecifier(_prototypeManager.Index("Blunt"), 5); + + EnsureHelper(ent); + + EnsureHelper(ent, out var moveSpeed); + if (ent.Comp.Revenant != null) + _moveSpeed.ChangeBaseSpeed(ent, + ent.Comp.Revenant.Value.Comp.AnimateWalkSpeed, + ent.Comp.Revenant.Value.Comp.AnimateSprintSpeed, + MovementSpeedModifierComponent.DefaultAcceleration, + moveSpeed + ); + else + _moveSpeed.ChangeBaseSpeed(ent, + RevenantComponent.DefaultAnimateWalkSpeed, + RevenantComponent.DefaultAnimateSprintSpeed, + MovementSpeedModifierComponent.DefaultAcceleration, + moveSpeed + ); + + EnsureHelper(ent, out var factions); + _factionSystem.ClearFactions((ent, factions)); + _factionSystem.AddFaction((ent, factions), "SimpleHostile"); + + EnsureHelper(ent); + + EnsureHelper(ent, out var htn); + if (HasComp(ent)) + { + if (TryComp(ent, out var bolt)) + _gunSystem.SetBoltClosed(ent, bolt, true); + htn.RootTask = new HTNCompoundTask() { Task = "SimpleRangedHostileCompound" }; + } + else if (HasComp(ent)) + htn.RootTask = new HTNCompoundTask() { Task = "AnimatedHandcuffsCompound" }; + else if (HasComp(ent)) + htn.RootTask = new HTNCompoundTask() { Task = "AnimatedGrenadeCompound" }; + else + htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; + htn.Blackboard.SetValue(NPCBlackboard.Owner, ent.Owner); + + EnsureHelper(ent); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown _) + { + if (ent.Comp.LifeStage != ComponentLifeStage.Stopping) + return; + + ent.Comp.CancelToken.Cancel(); + ent.Comp.CancelToken.Dispose(); + + foreach (var comp in ent.Comp.AddedComponents) + { + if (comp.Deleted) + continue; + + RemComp(ent, comp); + } + + _popup.PopupEntity(Loc.GetString("revenant-animate-item-inanimate", ("name", Comp(ent).EntityName)), ent, Filter.Pvs(ent), true); + } + + // Returns true if a new component was added to the target. + private bool EnsureHelper(Entity target, out T comp) where T : Component, new() + { + if (TryComp(target, out var existing)) + { + comp = existing; + return false; + } + + comp = AddComp(target); + target.Comp.AddedComponents.Add(comp); + return true; + } + + private bool EnsureHelper(Entity target) where T : Component, new() + { + return EnsureHelper(target, out _); + } + + public bool CanAnimateObject(EntityUid target) + { + return !(HasComp(target) || HasComp(target) || HasComp(target)); + } + + public bool TryAnimateObject(EntityUid target, TimeSpan? duration = null, Entity? revenant = null) + { + if (!CanAnimateObject(target)) + return false; + + var animate = EnsureComp(target); + animate.Revenant = revenant; + if (duration != null) + { + animate.CancelToken = new CancellationTokenSource(); + Timer.Spawn(duration.Value, () => InanimateTarget(target, animate), animate.CancelToken.Token); + } + + return true; + } + + public void InanimateTarget(EntityUid target, RevenantAnimatedComponent? comp = null) + { + if (!target.Valid || !Resolve(target, ref comp)) + return; + + RemComp(target); + } +} \ No newline at end of file diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index ec544ec4ca69..31b0775ac465 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -32,26 +32,6 @@ using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Components; -using Content.Shared.Mind.Components; -using Content.Server.NPC.HTN; -using Content.Server.NPC; -using Robust.Shared.Timing; -using Content.Shared.Weapons.Melee; -using Content.Shared.CombatMode; -using Content.Server.NPC.Systems; -using Content.Shared.NPC.Components; -using Content.Shared.NPC.Systems; -using Robust.Shared.Prototypes; -using Content.Shared.Damage.Prototypes; -using Content.Shared.Movement.Components; -using Content.Shared.Item.ItemToggle; -using Content.Shared.Item.ItemToggle.Components; -using Content.Shared.Weapons.Ranged.Components; -using Content.Shared.Weapons.Ranged.Systems; -using Content.Shared.Cuffs.Components; -using Content.Shared.Movement.Systems; -using Robust.Shared.Player; -using Content.Shared.Explosion.Components; namespace Content.Server.Revenant.EntitySystems; @@ -67,14 +47,7 @@ public sealed partial class RevenantSystem [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly HTNSystem _htnSystem = default!; - [Dependency] private readonly NPCSystem _npcSystem = default!; - [Dependency] private readonly NpcFactionSystem _factionSystem = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly ItemToggleSystem _itemToggleSystem = default!; - [Dependency] private readonly SharedGunSystem _gunSystem = default!; - [Dependency] private readonly MovementSpeedModifierSystem _moveSpeed = default!; + [Dependency] private readonly RevenantAnimatedSystem _revenantAnimated = default!; private void InitializeAbilities() { @@ -401,107 +374,12 @@ private void OnBloodWritingAction(EntityUid uid, RevenantComponent component, Re } } - public void AnimateObject(EntityUid target, TimeSpan? time = null, Entity? revenant = null) - { - if (HasComp(target) || HasComp(target)) - return; - - if (revenant != null && !TryUseAbility(revenant.Value.Owner, revenant.Value.Comp, revenant.Value.Comp.AnimateCost, revenant.Value.Comp.AnimateDebuffs)) - return; - - if (HasComp(target) && TryComp(target, out var toggle)) - { - // Turn on welders and stun prods - _itemToggleSystem.TryActivate((target, toggle)); - } - - var animate = EnsureComp(target); - - _popup.PopupEntity(Loc.GetString("revenant-animate-item-animate", ("name", Comp(target).EntityName)), target, Filter.Pvs(target), true); - - EnsureComp(target); - if (!HasComp(target)) - { - var melee = AddComp(target); - melee.Damage = new DamageSpecifier(_prototypeManager.Index("Blunt"), 5); - animate.AddedMelee = melee; - } - - EnsureComp(target); - EnsureComp(target); - var moveSpeed = EnsureComp(target); - if (revenant != null) - _moveSpeed.ChangeBaseSpeed(target, - revenant.Value.Comp.AnimateWalkSpeed, - revenant.Value.Comp.AnimateSprintSpeed, - MovementSpeedModifierComponent.DefaultAcceleration - ); - else - _moveSpeed.ChangeBaseSpeed(target, - RevenantComponent.DefaultAnimateWalkSpeed, - RevenantComponent.DefaultAnimateSprintSpeed, - MovementSpeedModifierComponent.DefaultAcceleration - ); - - var factions = EnsureComp(target); - _factionSystem.ClearFactions((target, factions)); - _factionSystem.AddFaction((target, factions), "SimpleHostile"); - - EnsureComp(target); - - var htn = EnsureComp(target); - if (HasComp(target)) - { - if (TryComp(target, out var bolt)) - _gunSystem.SetBoltClosed(target, bolt, true); - htn.RootTask = new HTNCompoundTask() { Task = "SimpleRangedHostileCompound" }; - } - else if (HasComp(target)) - htn.RootTask = new HTNCompoundTask() { Task = "AnimatedHandcuffsCompound" }; - else if (HasComp(target)) - htn.RootTask = new HTNCompoundTask() { Task = "AnimatedGrenadeCompound" }; - else - htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; - htn.Blackboard.SetValue(NPCBlackboard.Owner, target); - - _npcSystem.WakeNPC(target, htn); - _htnSystem.Replan(htn); - - if (revenant != null) - Timer.Spawn(time ?? revenant.Value.Comp.AnimateTime, () => - { - if (!animate.Deleted) - InanimateTarget(target, animate); - }); - else if (time != null) - Timer.Spawn(time.Value, () => - { - if (!animate.Deleted) - InanimateTarget(target, animate); - }); - } - - public void InanimateTarget(EntityUid target, RevenantAnimatedComponent? comp = null) - { - if (!target.Valid || !Resolve(target, ref comp)) - return; - - RemComp(target); - RemComp(target); - - if (comp.AddedMelee != null) - RemComp(target); - - RemComp(target); - - _popup.PopupEntity(Loc.GetString("revenant-animate-item-inanimate", ("name", Comp(target).EntityName)), target, Filter.Pvs(target), true); - } - private void OnAnimateAction(EntityUid uid, RevenantComponent comp, RevenantAnimateEvent args) { if (args.Handled) return; - AnimateObject(args.Target, comp.AnimateTime, (uid, comp)); + if (comp.Essence > comp.AnimateCost && _revenantAnimated.TryAnimateObject(args.Target, comp.AnimateTime, (uid, comp))) + TryUseAbility(uid, comp, comp.AnimateCost, comp.AnimateDebuffs); } } From e6b9ace03e80799f6cb44b38325423e531ea0961 Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Fri, 13 Sep 2024 01:14:55 -0700 Subject: [PATCH 07/10] Animated items can now be attacked to render them inert --- .../EntitySystems/RevenantAnimatedSystem.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs index 2948488460a7..06ed3c63b1e4 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs @@ -23,6 +23,9 @@ using Content.Shared.Mind.Components; using System.Threading; using Timer = Robust.Shared.Timing.Timer; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Mobs; namespace Content.Server.Revenant.EntitySystems; @@ -34,6 +37,7 @@ public sealed partial class RevenantAnimatedSystem : EntitySystem [Dependency] private readonly ItemToggleSystem _itemToggleSystem = default!; [Dependency] private readonly SharedGunSystem _gunSystem = default!; [Dependency] private readonly MovementSpeedModifierSystem _moveSpeed = default!; + [Dependency] private readonly MobThresholdSystem _thresholds = default!; public override void Initialize() { @@ -41,7 +45,7 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnComponentShutdown); - + SubscribeLocalEvent(OnMobStateChange); } private void OnComponentStartup(Entity ent, ref ComponentStartup _) @@ -49,16 +53,17 @@ private void OnComponentStartup(Entity ent, ref Compo if (ent.Comp.LifeStage != ComponentLifeStage.Starting) return; + // Turn on welding rods and stun prods if (HasComp(ent.Owner) && TryComp(ent.Owner, out var toggle)) _itemToggleSystem.TryActivate((ent.Owner, toggle)); _popup.PopupEntity(Loc.GetString("revenant-animate-item-animate", ("name", Comp(ent.Owner).EntityName)), ent.Owner, Filter.Pvs(ent.Owner), true); + // Add melee damage if an item doesn't already have it if (EnsureHelper(ent, out var melee)) melee.Damage = new DamageSpecifier(_prototypeManager.Index("Blunt"), 5); EnsureHelper(ent); - EnsureHelper(ent, out var moveSpeed); if (ent.Comp.Revenant != null) _moveSpeed.ChangeBaseSpeed(ent, @@ -79,23 +84,34 @@ private void OnComponentStartup(Entity ent, ref Compo _factionSystem.ClearFactions((ent, factions)); _factionSystem.AddFaction((ent, factions), "SimpleHostile"); + // For things like handcuffs EnsureHelper(ent); EnsureHelper(ent, out var htn); if (HasComp(ent)) { + // Goals: Magdump into any nearby creatures, and melee hit them if empty if (TryComp(ent, out var bolt)) _gunSystem.SetBoltClosed(ent, bolt, true); htn.RootTask = new HTNCompoundTask() { Task = "SimpleRangedHostileCompound" }; } else if (HasComp(ent)) + // Goals: Jump into any creature's pockets/hands and cuff them htn.RootTask = new HTNCompoundTask() { Task = "AnimatedHandcuffsCompound" }; else if (HasComp(ent)) + // Goals: Jump into any creature's pockets/hands and activate self htn.RootTask = new HTNCompoundTask() { Task = "AnimatedGrenadeCompound" }; else + // Goals: Fist fight anyone near you htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; + htn.Blackboard.SetValue(NPCBlackboard.Owner, ent.Owner); + EnsureHelper(ent); + EnsureHelper(ent); + EnsureHelper(ent, out var thresholds); + _thresholds.SetMobStateThreshold(ent, 30, MobState.Dead, thresholds); + EnsureHelper(ent); } @@ -112,12 +128,20 @@ private void OnComponentShutdown(Entity ent, ref Comp if (comp.Deleted) continue; - RemComp(ent, comp); + RemCompDeferred(ent, comp); } _popup.PopupEntity(Loc.GetString("revenant-animate-item-inanimate", ("name", Comp(ent).EntityName)), ent, Filter.Pvs(ent), true); } + private void OnMobStateChange(Entity ent, ref MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + { + InanimateTarget(ent); + } + } + // Returns true if a new component was added to the target. private bool EnsureHelper(Entity target, out T comp) where T : Component, new() { From e8032f4428823b2b6df36e991e7076cceac45c3e Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Fri, 13 Sep 2024 01:32:19 -0700 Subject: [PATCH 08/10] Localized admin verbs for animate and inanimate --- Resources/Locale/en-US/administration/smites.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/Locale/en-US/administration/smites.ftl b/Resources/Locale/en-US/administration/smites.ftl index e77725765bef..8bb350acdf5e 100644 --- a/Resources/Locale/en-US/administration/smites.ftl +++ b/Resources/Locale/en-US/administration/smites.ftl @@ -133,3 +133,5 @@ admin-trick-pause-map-description = Pause the selected map. Note this doesn't en admin-trick-snap-joints-description = Remove all physics joints from an object. Unfortunately does not snap every bone in their body. admin-trick-minigun-fire-description = Makes the targetted gun fire like a minigun (very fast). admin-trick-set-bullet-amount-description = Quickly set the amount of unspawned bullets in a gun. +admin-trick-make-animate-description = Animate an item and make it hostile for 60 seconds. +admin-trick-make-inanimate-description = Render an animate item inanimate. \ No newline at end of file From 5c88ce9b21cf517023cd136ddb8aaa361f72fbfe Mon Sep 17 00:00:00 2001 From: TGRCDev Date: Fri, 13 Sep 2024 14:40:20 -0700 Subject: [PATCH 09/10] Added Animate action sprite and fixed revenant action sprites being small --- .../Systems/AdminVerbSystem.Tools.cs | 4 ++-- Resources/Prototypes/Actions/revenant.yml | 6 ++++++ Resources/Textures/Interface/Actions/animate.png | Bin 0 -> 5049 bytes .../Textures/Interface/Actions/inanimate.png | Bin 0 -> 5081 bytes 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 Resources/Textures/Interface/Actions/animate.png create mode 100644 Resources/Textures/Interface/Actions/inanimate.png diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index 96d95f0648bc..0a2d9fc915ba 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -745,7 +745,7 @@ private void AddTricksVerbs(GetVerbsEvent args) { Text = "Animate Item", Category = VerbCategory.Tricks, - Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Ghosts/revenant.rsi"), "icon"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Actions/animate.png")), Act = () => { _revenantAnimate.TryAnimateObject(args.Target, TimeSpan.FromSeconds(60)); @@ -763,7 +763,7 @@ private void AddTricksVerbs(GetVerbsEvent args) { Text = "Inanimate Item", Category = VerbCategory.Tricks, - Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Ghosts/revenant.rsi"), "icon"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Actions/inanimate.png")), Act = () => { _revenantAnimate.InanimateTarget(args.Target, animate); diff --git a/Resources/Prototypes/Actions/revenant.yml b/Resources/Prototypes/Actions/revenant.yml index 87e4e439d5cb..c36a530f688d 100644 --- a/Resources/Prototypes/Actions/revenant.yml +++ b/Resources/Prototypes/Actions/revenant.yml @@ -13,6 +13,7 @@ description: Costs 30 Essence. components: - type: InstantAction + itemIconStyle: NoItem icon: Interface/Actions/defile.png event: !type:RevenantDefileActionEvent useDelay: 15 @@ -23,6 +24,7 @@ description: Costs 40 Essence. components: - type: InstantAction + itemIconStyle: NoItem icon: Interface/Actions/overloadlight.png event: !type:RevenantOverloadLightsActionEvent useDelay: 20 @@ -43,6 +45,7 @@ description: Costs 60 Essence. components: - type: InstantAction + itemIconStyle: NoItem icon: Interface/Actions/malfunction.png event: !type:RevenantMalfunctionActionEvent useDelay: 20 @@ -53,6 +56,7 @@ description: Costs 2 Essence per glyph. components: - type: InstantAction + itemIconStyle: NoItem icon: Interface/Actions/blood-writing.png event: !type:RevenantBloodWritingEvent useDelay: 1 @@ -63,6 +67,8 @@ description: Costs 50 Essence. components: - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/animate.png event: !type:RevenantAnimateEvent useDelay: 1 canTargetSelf: false diff --git a/Resources/Textures/Interface/Actions/animate.png b/Resources/Textures/Interface/Actions/animate.png new file mode 100644 index 0000000000000000000000000000000000000000..c84dc8bcafd14b3f333ba5fa924ecfa344717663 GIT binary patch literal 5049 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/inanimate.png b/Resources/Textures/Interface/Actions/inanimate.png new file mode 100644 index 0000000000000000000000000000000000000000..27f56926716682b50d0e1d8435d4c0358f402dd0 GIT binary patch literal 5081 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 qZ;Q Date: Fri, 13 Sep 2024 15:13:47 -0700 Subject: [PATCH 10/10] Replaced CancelTokens with update loop Fixed an exploit where you could animate objects for free while floating inside of a wall --- .../Components/RevenantAnimatedComponent.cs | 8 ++++- .../EntitySystems/RevenantAnimatedSystem.cs | 30 +++++++++++++------ .../EntitySystems/RevenantSystem.Abilities.cs | 4 +-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs b/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs index 43b3a991336f..c71fe156553c 100644 --- a/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs +++ b/Content.Server/Revenant/Components/RevenantAnimatedComponent.cs @@ -7,6 +7,7 @@ namespace Content.Server.Revenant.Components; [RegisterComponent, NetworkedComponent] [Access(typeof(RevenantAnimatedSystem))] +[AutoGenerateComponentPause] public sealed partial class RevenantAnimatedComponent : Component { /// @@ -21,5 +22,10 @@ public sealed partial class RevenantAnimatedComponent : Component /// public List AddedComponents = new(); - public CancellationTokenSource CancelToken; + /// + /// When the item should become inanimate. If null, + /// the item never becomes inanimate. + /// + [AutoPausedField] + public TimeSpan? EndTime; } \ No newline at end of file diff --git a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs index 06ed3c63b1e4..a8bda9cf2be0 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantAnimatedSystem.cs @@ -21,11 +21,10 @@ using Robust.Shared.Player; using Content.Shared.Explosion.Components; using Content.Shared.Mind.Components; -using System.Threading; -using Timer = Robust.Shared.Timing.Timer; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Mobs; +using Robust.Shared.Timing; namespace Content.Server.Revenant.EntitySystems; @@ -38,6 +37,7 @@ public sealed partial class RevenantAnimatedSystem : EntitySystem [Dependency] private readonly SharedGunSystem _gunSystem = default!; [Dependency] private readonly MovementSpeedModifierSystem _moveSpeed = default!; [Dependency] private readonly MobThresholdSystem _thresholds = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; public override void Initialize() { @@ -48,6 +48,22 @@ public override void Initialize() SubscribeLocalEvent(OnMobStateChange); } + public override void Update(float frameTime) + { + base.Update(frameTime); + + var enumerator = EntityQueryEnumerator(); + + while (enumerator.MoveNext(out var uid, out var animate)) + { + if (animate.EndTime == null) + continue; + + if (animate.EndTime <= _gameTiming.CurTime) + InanimateTarget(uid, animate); + } + } + private void OnComponentStartup(Entity ent, ref ComponentStartup _) { if (ent.Comp.LifeStage != ComponentLifeStage.Starting) @@ -120,9 +136,6 @@ private void OnComponentShutdown(Entity ent, ref Comp if (ent.Comp.LifeStage != ComponentLifeStage.Stopping) return; - ent.Comp.CancelToken.Cancel(); - ent.Comp.CancelToken.Dispose(); - foreach (var comp in ent.Comp.AddedComponents) { if (comp.Deleted) @@ -174,10 +187,9 @@ public bool TryAnimateObject(EntityUid target, TimeSpan? duration = null, Entity var animate = EnsureComp(target); animate.Revenant = revenant; if (duration != null) - { - animate.CancelToken = new CancellationTokenSource(); - Timer.Spawn(duration.Value, () => InanimateTarget(target, animate), animate.CancelToken.Token); - } + animate.EndTime = _gameTiming.CurTime + duration.Value; + else if (revenant != null) + animate.EndTime = _gameTiming.CurTime + revenant.Value.Comp.AnimateTime; return true; } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 31b0775ac465..6526ff0fa8bf 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -379,7 +379,7 @@ private void OnAnimateAction(EntityUid uid, RevenantComponent comp, RevenantAnim if (args.Handled) return; - if (comp.Essence > comp.AnimateCost && _revenantAnimated.TryAnimateObject(args.Target, comp.AnimateTime, (uid, comp))) - TryUseAbility(uid, comp, comp.AnimateCost, comp.AnimateDebuffs); + if (_revenantAnimated.CanAnimateObject(args.Target) && TryUseAbility(uid, comp, comp.AnimateCost, comp.AnimateDebuffs)) + _revenantAnimated.TryAnimateObject(args.Target, comp.AnimateTime, (uid, comp)); } }