From 638009f5d3cb8ade969b3f5ae3b9a24062978917 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 2 May 2023 00:30:15 +1000 Subject: [PATCH] Cleanbot tweaks (#15821) --- .../Fluids/EntitySystems/AbsorbentSystem.cs | 30 +++-- .../Operators/Fluid/PickPuddleOperator.cs | 106 ++++++++++++++++++ .../Operators/InteractWithOperator.cs | 31 +++++ .../PickAccessibleComponentOperator.cs | 26 ++--- Content.Server/NPC/NPCBlackboard.cs | 3 + .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 22 +++- Resources/Prototypes/NPCs/cleanbot.yml | 6 +- Resources/Prototypes/NPCs/idle.yml | 27 +++-- 8 files changed, 209 insertions(+), 42 deletions(-) create mode 100644 Content.Server/NPC/HTN/PrimitiveTasks/Operators/Fluid/PickPuddleOperator.cs create mode 100644 Content.Server/NPC/HTN/PrimitiveTasks/Operators/InteractWithOperator.cs diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index a63cc350d0..ce6ba487ab 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -31,6 +31,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem base.Initialize(); SubscribeLocalEvent(OnAbsorbentInit); SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnInteractNoHand); SubscribeLocalEvent(OnAbsorbentSolutionChange); } @@ -79,31 +80,38 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem Dirty(component); } - private void OnAfterInteract(EntityUid uid, AbsorbentComponent component, AfterInteractEvent args) + private void OnInteractNoHand(EntityUid uid, AbsorbentComponent component, InteractNoHandEvent args) { - if (!args.CanReach || args.Handled || _useDelay.ActiveDelay(uid)) + if (args.Handled || args.Target == null) return; - if (!_solutionSystem.TryGetSolution(args.Used, AbsorbentComponent.SolutionName, out var absorberSoln)) + Mop(uid, args.Target.Value, uid, component); + args.Handled = true; + } + + private void OnAfterInteract(EntityUid uid, AbsorbentComponent component, AfterInteractEvent args) + { + if (!args.CanReach || args.Handled || args.Target == null) return; - // Didn't click anything so don't do anything. - if (args.Target is not { Valid: true } target) - { + Mop(args.User, args.Target.Value, args.Used, component); + args.Handled = true; + } + + private void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component) + { + if (!_solutionSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln)) return; - } // If it's a puddle try to grab from - if (!TryPuddleInteract(args.User, uid, target, component, absorberSoln)) + if (!TryPuddleInteract(user, used, target, component, absorberSoln)) { // Do a transfer, try to get water onto us and transfer anything else to them. // If it's anything else transfer to - if (!TryTransferAbsorber(args.User, uid, target, component, absorberSoln)) + if (!TryTransferAbsorber(user, used, target, component, absorberSoln)) return; } - - args.Handled = true; } /// diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Fluid/PickPuddleOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Fluid/PickPuddleOperator.cs new file mode 100644 index 0000000000..ecce69ce38 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Fluid/PickPuddleOperator.cs @@ -0,0 +1,106 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.Fluids.EntitySystems; +using Content.Server.NPC.Pathfinding; +using Content.Shared.Fluids.Components; +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Fluid; + +/// +/// Picks a nearby evaporatable puddle. +/// +public sealed class PickPuddleOperator : HTNOperator +{ + // This is similar to PickAccessibleComponent however I have an idea on generic utility queries + // that can also be re-used for melee that needs further fleshing out. + + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + private PathfindingSystem _pathfinding = default!; + private EntityLookupSystem _lookup = default!; + + [DataField("rangeKey", required: true)] + public string RangeKey = string.Empty; + + [DataField("target")] public string Target = "Target"; + + [DataField("targetKey", required: true)] + public string TargetKey = string.Empty; + + /// + /// Where the pathfinding result will be stored (if applicable). This gets removed after execution. + /// + [DataField("pathfindKey")] + public string PathfindKey = NPCBlackboard.PathfindKey; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _lookup = sysManager.GetEntitySystem(); + _pathfinding = sysManager.GetEntitySystem(); + } + + /// + [Obsolete("Obsolete")] + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + var range = blackboard.GetValueOrDefault(RangeKey, _entManager); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates, _entManager)) + { + return (false, null); + } + + var targets = new List(); + var puddleSystem = _entManager.System(); + var solSystem = _entManager.System(); + + foreach (var comp in _lookup.GetComponentsInRange(coordinates, range)) + { + if (comp.Owner == owner || + !solSystem.TryGetSolution(comp.Owner, comp.SolutionName, out var puddleSolution) || + puddleSystem.CanFullyEvaporate(puddleSolution)) + { + continue; + } + + targets.Add((comp.Owner)); + } + + if (targets.Count == 0) + { + return (false, null); + } + + foreach (var target in targets) + { + var path = await _pathfinding.GetPath( + owner, + target, + 1f, + cancelToken, + flags: _pathfinding.GetFlags(blackboard)); + + if (path.Result != PathResult.Path) + { + return (false, null); + } + + var xform = _entManager.GetComponent(target); + + return (true, new Dictionary() + { + { Target, target }, + { TargetKey, xform.Coordinates }, + { PathfindKey, path} + }); + } + + return (false, null); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/InteractWithOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/InteractWithOperator.cs new file mode 100644 index 0000000000..1a4238d438 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/InteractWithOperator.cs @@ -0,0 +1,31 @@ +using Content.Server.Interaction; +using Content.Shared.Timing; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed class InteractWithOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + /// + /// Key that contains the target entity. + /// + [DataField("targetKey", required: true)] + public string TargetKey = default!; + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (_entManager.System().ActiveDelay(owner) || + !blackboard.TryGetValue(TargetKey, out var moveTarget, _entManager) || + !_entManager.TryGetComponent(moveTarget, out var targetXform)) + { + return HTNOperatorStatus.Continuing; + } + + _entManager.System().UserInteraction(owner, targetXform.Coordinates, moveTarget); + + return HTNOperatorStatus.Finished; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs index 174f9559dc..53f1826c82 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs @@ -22,6 +22,9 @@ public sealed class PickAccessibleComponentOperator : HTNOperator [DataField("targetKey", required: true)] public string TargetKey = string.Empty; + [DataField("target")] + public string TargetEntity = "Target"; + [DataField("component", required: true)] public string Component = string.Empty; @@ -58,7 +61,7 @@ public sealed class PickAccessibleComponentOperator : HTNOperator var compType = registration.Type; var query = _entManager.GetEntityQuery(compType); - var targets = new List(); + var targets = new List(); // TODO: Need to get ones that are accessible. // TODO: Look at unreal HTN to see repeatable ones maybe? @@ -68,7 +71,7 @@ public sealed class PickAccessibleComponentOperator : HTNOperator if (entity == owner || !query.TryGetComponent(entity, out var comp)) continue; - targets.Add(comp); + targets.Add(entity); } if (targets.Count == 0) @@ -76,16 +79,12 @@ public sealed class PickAccessibleComponentOperator : HTNOperator return (false, null); } - blackboard.TryGetValue(RangeKey, out var maxRange, _entManager); - - if (maxRange == 0f) - maxRange = 7f; - - while (targets.Count > 0) + foreach (var target in targets) { - var path = await _pathfinding.GetRandomPath( + var path = await _pathfinding.GetPath( owner, - maxRange, + target, + 1f, cancelToken, flags: _pathfinding.GetFlags(blackboard)); @@ -94,12 +93,13 @@ public sealed class PickAccessibleComponentOperator : HTNOperator return (false, null); } - var target = path.Path.Last().Coordinates; + var xform = _entManager.GetComponent(target); return (true, new Dictionary() { - { TargetKey, target }, - { PathfindKey, path} + { TargetEntity, target }, + { TargetKey, xform.Coordinates }, + { PathfindKey, path } }); } diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs index e60be3fb96..0e12827cd7 100644 --- a/Content.Server/NPC/NPCBlackboard.cs +++ b/Content.Server/NPC/NPCBlackboard.cs @@ -1,7 +1,9 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; +using Content.Server.Interaction; using Content.Shared.Access.Systems; using Content.Shared.ActionBlocker; +using Content.Shared.Interaction; using Robust.Shared.Utility; namespace Content.Server.NPC; @@ -18,6 +20,7 @@ public sealed class NPCBlackboard : IEnumerable> {"FollowCloseRange", 3f}, {"FollowRange", 7f}, {"IdleRange", 7f}, + {"InteractRange", SharedInteractionSystem.InteractionRange}, {"MaximumIdleTime", 7f}, {MedibotInjectRange, 4f}, {MeleeMissChance, 0.3f}, diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 6ff998396e..1faa7ec9a0 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -194,18 +194,30 @@ drawdepth: Mobs sprite: Mobs/Silicon/Bots/cleanbot.rsi state: cleanbot - - type: Drain - range: 1 - unitsDestroyedPerSecond: 6 - type: Construction graph: CleanBot node: bot - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-mechanical + - type: Absorbent + pickupAmount: 10 + - type: UseDelay + delay: 2 + - type: SolutionRegeneration + solution: absorbed + generated: + reagents: + - ReagentId: Water + Quantity: 10 + - type: SolutionPurge + solution: absorbed + preserve: + - Water + quantity: 10 - type: SolutionContainerManager solutions: - drainBuffer: - maxVol: 30 + absorbed: + maxVol: 50 - type: MovementSpeedModifier baseWalkSpeed: 2 baseSprintSpeed: 3 diff --git a/Resources/Prototypes/NPCs/cleanbot.yml b/Resources/Prototypes/NPCs/cleanbot.yml index 97d64de659..ca3835338a 100644 --- a/Resources/Prototypes/NPCs/cleanbot.yml +++ b/Resources/Prototypes/NPCs/cleanbot.yml @@ -13,13 +13,11 @@ - tasks: - id: PickPuddlePrimitive - id: MoveToAccessiblePrimitive - - id: SetIdleTimePrimitive - - id: WaitIdleTimePrimitive - + - id: InteractWithPrimitive - type: htnPrimitive id: PickPuddlePrimitive - operator: !type:PickAccessibleComponentOperator + operator: !type:PickPuddleOperator rangeKey: BufferRange targetKey: MovementTarget component: Puddle diff --git a/Resources/Prototypes/NPCs/idle.yml b/Resources/Prototypes/NPCs/idle.yml index d4e0d12988..f97e7dff63 100644 --- a/Resources/Prototypes/NPCs/idle.yml +++ b/Resources/Prototypes/NPCs/idle.yml @@ -32,14 +32,18 @@ # Primitives - type: htnPrimitive - id: PickRandomRotationPrimitive - operator: !type:PickRandomRotationOperator - targetKey: RotateTarget + id: InteractWithPrimitive + preconditions: + - !type:TargetInRangePrecondition + targetKey: Target + rangeKey: InteractRange + operator: !type:InteractWithOperator + targetKey: Target - type: htnPrimitive - id: RotateToTargetPrimitive - operator: !type:RotateToTargetOperator - targetKey: RotateTarget + id: MoveToAccessiblePrimitive + operator: !type:MoveToOperator + pathfindInPlanning: false - type: htnPrimitive id: PickAccessiblePrimitive @@ -48,9 +52,14 @@ targetKey: MovementTarget - type: htnPrimitive - id: MoveToAccessiblePrimitive - operator: !type:MoveToOperator - pathfindInPlanning: false + id: PickRandomRotationPrimitive + operator: !type:PickRandomRotationOperator + targetKey: RotateTarget + +- type: htnPrimitive + id: RotateToTargetPrimitive + operator: !type:RotateToTargetOperator + targetKey: RotateTarget - type: htnPrimitive id: RandomIdleTimePrimitive -- 2.51.2