base.Initialize();
SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<AbsorbentComponent, InteractNoHandEvent>(OnInteractNoHand);
SubscribeLocalEvent<AbsorbentComponent, SolutionChangedEvent>(OnAbsorbentSolutionChange);
}
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;
}
/// <summary>
--- /dev/null
+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;
+
+/// <summary>
+/// Picks a nearby evaporatable puddle.
+/// </summary>
+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;
+
+ /// <summary>
+ /// Where the pathfinding result will be stored (if applicable). This gets removed after execution.
+ /// </summary>
+ [DataField("pathfindKey")]
+ public string PathfindKey = NPCBlackboard.PathfindKey;
+
+ public override void Initialize(IEntitySystemManager sysManager)
+ {
+ base.Initialize(sysManager);
+ _lookup = sysManager.GetEntitySystem<EntityLookupSystem>();
+ _pathfinding = sysManager.GetEntitySystem<PathfindingSystem>();
+ }
+
+ /// <inheritdoc/>
+ [Obsolete("Obsolete")]
+ public override async Task<(bool Valid, Dictionary<string, object>? Effects)> Plan(NPCBlackboard blackboard,
+ CancellationToken cancelToken)
+ {
+ var range = blackboard.GetValueOrDefault<float>(RangeKey, _entManager);
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+ if (!blackboard.TryGetValue<EntityCoordinates>(NPCBlackboard.OwnerCoordinates, out var coordinates, _entManager))
+ {
+ return (false, null);
+ }
+
+ var targets = new List<EntityUid>();
+ var puddleSystem = _entManager.System<PuddleSystem>();
+ var solSystem = _entManager.System<SolutionContainerSystem>();
+
+ foreach (var comp in _lookup.GetComponentsInRange<PuddleComponent>(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<TransformComponent>(target);
+
+ return (true, new Dictionary<string, object>()
+ {
+ { Target, target },
+ { TargetKey, xform.Coordinates },
+ { PathfindKey, path}
+ });
+ }
+
+ return (false, null);
+ }
+}
--- /dev/null
+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!;
+
+ /// <summary>
+ /// Key that contains the target entity.
+ /// </summary>
+ [DataField("targetKey", required: true)]
+ public string TargetKey = default!;
+
+ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
+ {
+ var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+ if (_entManager.System<UseDelaySystem>().ActiveDelay(owner) ||
+ !blackboard.TryGetValue<EntityUid>(TargetKey, out var moveTarget, _entManager) ||
+ !_entManager.TryGetComponent<TransformComponent>(moveTarget, out var targetXform))
+ {
+ return HTNOperatorStatus.Continuing;
+ }
+
+ _entManager.System<InteractionSystem>().UserInteraction(owner, targetXform.Coordinates, moveTarget);
+
+ return HTNOperatorStatus.Finished;
+ }
+}
[DataField("targetKey", required: true)]
public string TargetKey = string.Empty;
+ [DataField("target")]
+ public string TargetEntity = "Target";
+
[DataField("component", required: true)]
public string Component = string.Empty;
var compType = registration.Type;
var query = _entManager.GetEntityQuery(compType);
- var targets = new List<Component>();
+ var targets = new List<EntityUid>();
// TODO: Need to get ones that are accessible.
// TODO: Look at unreal HTN to see repeatable ones maybe?
if (entity == owner || !query.TryGetComponent(entity, out var comp))
continue;
- targets.Add(comp);
+ targets.Add(entity);
}
if (targets.Count == 0)
return (false, null);
}
- blackboard.TryGetValue<float>(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));
return (false, null);
}
- var target = path.Path.Last().Coordinates;
+ var xform = _entManager.GetComponent<TransformComponent>(target);
return (true, new Dictionary<string, object>()
{
- { TargetKey, target },
- { PathfindKey, path}
+ { TargetEntity, target },
+ { TargetKey, xform.Coordinates },
+ { PathfindKey, path }
});
}
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;
{"FollowCloseRange", 3f},
{"FollowRange", 7f},
{"IdleRange", 7f},
+ {"InteractRange", SharedInteractionSystem.InteractionRange},
{"MaximumIdleTime", 7f},
{MedibotInjectRange, 4f},
{MeleeMissChance, 0.3f},
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
- 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
# 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
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