AI pickup changes (#1811)
* AI pickup changes Eating and drinking isn't spammed anymore. AI can do InRangeUnobstructed checks for item pickups. AI can open drink cans. AI littering to be coded. * #nullable enable * github's nullable fails are actively shortening my lifespan * Use a const instead So it's easier to find given the performance implications. Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.AI.Utility;
|
#nullable enable
|
||||||
|
using Content.Server.AI.Utility;
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
using Content.Server.AI.WorldState.States.Inventory;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.EntitySystems.Click;
|
using Content.Server.GameObjects.EntitySystems.Click;
|
||||||
@@ -20,11 +21,9 @@ namespace Content.Server.AI.Operators.Inventory
|
|||||||
_target = target;
|
_target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When I spawn new entities they seem to duplicate clothing or something?
|
|
||||||
public override Outcome Execute(float frameTime)
|
public override Outcome Execute(float frameTime)
|
||||||
{
|
{
|
||||||
if (_target == null ||
|
if (_target.Deleted ||
|
||||||
_target.Deleted ||
|
|
||||||
!_target.HasComponent<ItemComponent>() ||
|
!_target.HasComponent<ItemComponent>() ||
|
||||||
ContainerHelpers.IsInContainer(_target) ||
|
ContainerHelpers.IsInContainer(_target) ||
|
||||||
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -7,12 +8,12 @@ namespace Content.Server.AI.Operators.Inventory
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will find the item in storage, put it in an active hand, then use it
|
/// Will find the item in storage, put it in an active hand, then use it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UseItemInHandsOperator : AiOperator
|
public class UseItemInInventoryOperator : AiOperator
|
||||||
{
|
{
|
||||||
private readonly IEntity _owner;
|
private readonly IEntity _owner;
|
||||||
private readonly IEntity _target;
|
private readonly IEntity _target;
|
||||||
|
|
||||||
public UseItemInHandsOperator(IEntity owner, IEntity target)
|
public UseItemInInventoryOperator(IEntity owner, IEntity target)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
_target = target;
|
_target = target;
|
||||||
@@ -20,11 +21,6 @@ namespace Content.Server.AI.Operators.Inventory
|
|||||||
|
|
||||||
public override Outcome Execute(float frameTime)
|
public override Outcome Execute(float frameTime)
|
||||||
{
|
{
|
||||||
if (_target == null)
|
|
||||||
{
|
|
||||||
return Outcome.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Also have this check storage a la backpack etc.
|
// TODO: Also have this check storage a la backpack etc.
|
||||||
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
if (!_owner.TryGetComponent(out HandsComponent handsComponent))
|
||||||
{
|
{
|
||||||
@@ -16,12 +16,20 @@ namespace Content.Server.AI.Operators.Movement
|
|||||||
public float ArrivalDistance { get; }
|
public float ArrivalDistance { get; }
|
||||||
public float PathfindingProximity { get; }
|
public float PathfindingProximity { get; }
|
||||||
|
|
||||||
public MoveToEntityOperator(IEntity owner, IEntity target, float arrivalDistance = 1.0f, float pathfindingProximity = 1.5f)
|
private bool _requiresInRangeUnobstructed;
|
||||||
|
|
||||||
|
public MoveToEntityOperator(
|
||||||
|
IEntity owner,
|
||||||
|
IEntity target,
|
||||||
|
float arrivalDistance = 1.0f,
|
||||||
|
float pathfindingProximity = 1.5f,
|
||||||
|
bool requiresInRangeUnobstructed = false)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
_target = target;
|
_target = target;
|
||||||
ArrivalDistance = arrivalDistance;
|
ArrivalDistance = arrivalDistance;
|
||||||
PathfindingProximity = pathfindingProximity;
|
PathfindingProximity = pathfindingProximity;
|
||||||
|
_requiresInRangeUnobstructed = requiresInRangeUnobstructed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool TryStartup()
|
public override bool TryStartup()
|
||||||
@@ -32,7 +40,7 @@ namespace Content.Server.AI.Operators.Movement
|
|||||||
}
|
}
|
||||||
|
|
||||||
var steering = EntitySystem.Get<AiSteeringSystem>();
|
var steering = EntitySystem.Get<AiSteeringSystem>();
|
||||||
_request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity);
|
_request = new EntityTargetSteeringRequest(_target, ArrivalDistance, PathfindingProximity, _requiresInRangeUnobstructed);
|
||||||
steering.Register(_owner, _request);
|
steering.Register(_owner, _request);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
using Content.Server.GameObjects.Components.Nutrition;
|
||||||
|
using Content.Shared.GameObjects.Components.Nutrition;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Nutrition
|
||||||
|
{
|
||||||
|
public class UseDrinkInInventoryOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _target;
|
||||||
|
private float _interactionCooldown;
|
||||||
|
|
||||||
|
public UseDrinkInInventoryOperator(IEntity owner, IEntity target)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_interactionCooldown >= 0)
|
||||||
|
{
|
||||||
|
_interactionCooldown -= frameTime;
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Also have this check storage a la backpack etc.
|
||||||
|
if (_target.Deleted ||
|
||||||
|
!_owner.TryGetComponent(out HandsComponent handsComponent) ||
|
||||||
|
!_target.TryGetComponent(out ItemComponent itemComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrinkComponent? drinkComponent = null;
|
||||||
|
|
||||||
|
foreach (var slot in handsComponent.ActivePriorityEnumerable())
|
||||||
|
{
|
||||||
|
if (handsComponent.GetItem(slot) != itemComponent) continue;
|
||||||
|
handsComponent.ActiveHand = slot;
|
||||||
|
if (!_target.TryGetComponent(out drinkComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should also implicitly open it.
|
||||||
|
handsComponent.ActivateItem();
|
||||||
|
_interactionCooldown = IoCManager.Resolve<IRobustRandom>().NextFloat() + 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drinkComponent == null)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drinkComponent.Deleted ||
|
||||||
|
drinkComponent.Empty ||
|
||||||
|
_owner.TryGetComponent(out ThirstComponent thirstComponent) &&
|
||||||
|
thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay])
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
using Content.Server.GameObjects.Components.Nutrition;
|
||||||
|
using Content.Shared.GameObjects.Components.Nutrition;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.AI.Operators.Nutrition
|
||||||
|
{
|
||||||
|
public class UseFoodInInventoryOperator : AiOperator
|
||||||
|
{
|
||||||
|
private readonly IEntity _owner;
|
||||||
|
private readonly IEntity _target;
|
||||||
|
private float _interactionCooldown;
|
||||||
|
|
||||||
|
public UseFoodInInventoryOperator(IEntity owner, IEntity target)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Outcome Execute(float frameTime)
|
||||||
|
{
|
||||||
|
if (_interactionCooldown >= 0)
|
||||||
|
{
|
||||||
|
_interactionCooldown -= frameTime;
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Also have this check storage a la backpack etc.
|
||||||
|
if (_target.Deleted ||
|
||||||
|
!_owner.TryGetComponent(out HandsComponent handsComponent) ||
|
||||||
|
!_target.TryGetComponent(out ItemComponent itemComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
FoodComponent? foodComponent = null;
|
||||||
|
|
||||||
|
foreach (var slot in handsComponent.ActivePriorityEnumerable())
|
||||||
|
{
|
||||||
|
if (handsComponent.GetItem(slot) != itemComponent) continue;
|
||||||
|
handsComponent.ActiveHand = slot;
|
||||||
|
if (!_target.TryGetComponent(out foodComponent))
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should also implicitly open it.
|
||||||
|
handsComponent.ActivateItem();
|
||||||
|
_interactionCooldown = IoCManager.Resolve<IRobustRandom>().NextFloat() + 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foodComponent == null)
|
||||||
|
{
|
||||||
|
return Outcome.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_target.Deleted ||
|
||||||
|
foodComponent.UsesRemaining == 0 ||
|
||||||
|
_owner.TryGetComponent(out HungerComponent hungerComponent) &&
|
||||||
|
hungerComponent.CurrentHunger >= hungerComponent.HungerThresholds[HungerThreshold.Okay])
|
||||||
|
{
|
||||||
|
return Outcome.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Outcome.Continuing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ namespace Content.Server.AI.Operators.Sequences
|
|||||||
{
|
{
|
||||||
Sequence = new Queue<AiOperator>(new AiOperator[]
|
Sequence = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new MoveToEntityOperator(owner, target),
|
new MoveToEntityOperator(owner, target, requiresInRangeUnobstructed: true),
|
||||||
new OpenStorageOperator(owner, target),
|
new OpenStorageOperator(owner, target),
|
||||||
new PickupEntityOperator(owner, target),
|
new PickupEntityOperator(owner, target),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
|||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, _entity),
|
||||||
new UseItemInHandsOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, _entity),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
|||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, _entity),
|
||||||
new UseItemInHandsOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, _entity),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
|||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, _entity),
|
||||||
new UseItemInHandsOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, _entity),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
|||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, _entity),
|
||||||
new UseItemInHandsOperator(Owner, _entity),
|
new UseItemInInventoryOperator(Owner, _entity),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
|||||||
|
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
considerationsManager.Get<FreeHandCon>()
|
|
||||||
.BoolCurve(context),
|
|
||||||
considerationsManager.Get<ThirstCon>()
|
|
||||||
.PresetCurve(context, PresetCurve.Nutrition),
|
|
||||||
considerationsManager.Get<TargetDistanceCon>()
|
considerationsManager.Get<TargetDistanceCon>()
|
||||||
.PresetCurve(context, PresetCurve.Distance),
|
.PresetCurve(context, PresetCurve.Distance),
|
||||||
considerationsManager.Get<DrinkValueCon>()
|
considerationsManager.Get<DrinkValueCon>()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Operators;
|
using Content.Server.AI.Operators;
|
||||||
using Content.Server.AI.Operators.Inventory;
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Operators.Nutrition;
|
||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||||
@@ -27,7 +28,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
|||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, _entity),
|
||||||
new UseItemInHandsOperator(Owner, _entity),
|
new UseDrinkInInventoryOperator(Owner, _entity),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Operators;
|
using Content.Server.AI.Operators;
|
||||||
using Content.Server.AI.Operators.Inventory;
|
using Content.Server.AI.Operators.Inventory;
|
||||||
|
using Content.Server.AI.Operators.Nutrition;
|
||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
using Content.Server.AI.Utility.Considerations.Nutrition.Food;
|
using Content.Server.AI.Utility.Considerations.Nutrition.Food;
|
||||||
@@ -27,7 +28,7 @@ namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
|||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||||
{
|
{
|
||||||
new EquipEntityOperator(Owner, _entity),
|
new EquipEntityOperator(Owner, _entity),
|
||||||
new UseItemInHandsOperator(Owner, _entity),
|
new UseFoodInInventoryOperator(Owner, _entity),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
if (target == null || target.Deleted || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Content.Server.AI.Utility.Considerations.Movement
|
|||||||
{
|
{
|
||||||
var self = context.GetState<SelfState>().GetValue();
|
var self = context.GetState<SelfState>().GetValue();
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
if (target == null || target.Transform.GridID != self.Transform.GridID)
|
if (target == null || target.Deleted || target.Transform.GridID != self.Transform.GridID)
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public ReagentUnit TransferAmount { get; private set; } = ReagentUnit.New(2);
|
public ReagentUnit TransferAmount { get; private set; } = ReagentUnit.New(2);
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
protected bool Opened { get; set; }
|
public bool Opened { get; protected set; }
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public bool Empty => _contents.CurrentVolume.Float() <= 0;
|
public bool Empty => _contents.CurrentVolume.Float() <= 0;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Content.Server.GameObjects.Components.Movement;
|
|||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding;
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Pathfinders;
|
||||||
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Content.Server.Utility;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Robust.Server.Interfaces.Timing;
|
using Robust.Server.Interfaces.Timing;
|
||||||
using Robust.Shared.GameObjects.Components;
|
using Robust.Shared.GameObjects.Components;
|
||||||
@@ -42,6 +43,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float TileTolerance = 0.8f;
|
private const float TileTolerance = 0.8f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long to wait between checks (if necessary).
|
||||||
|
/// </summary>
|
||||||
|
private const float InRangeUnobstructedCooldown = 0.25f;
|
||||||
|
|
||||||
private Dictionary<IEntity, IAiSteeringRequest> RunningAgents => _agentLists[_listIndex];
|
private Dictionary<IEntity, IAiSteeringRequest> RunningAgents => _agentLists[_listIndex];
|
||||||
|
|
||||||
// We'll cycle the running list every tick as all we're doing is getting a vector2 for the
|
// We'll cycle the running list every tick as all we're doing is getting a vector2 for the
|
||||||
@@ -208,7 +214,8 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
|
|
||||||
foreach (var (agent, steering) in RunningAgents)
|
foreach (var (agent, steering) in RunningAgents)
|
||||||
{
|
{
|
||||||
var result = Steer(agent, steering);
|
// Yeah look it's not true frametime but good enough.
|
||||||
|
var result = Steer(agent, steering, frameTime * RunningAgents.Count);
|
||||||
steering.Status = result;
|
steering.Status = result;
|
||||||
|
|
||||||
switch (result)
|
switch (result)
|
||||||
@@ -236,9 +243,10 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entity"></param>
|
/// <param name="entity"></param>
|
||||||
/// <param name="steeringRequest"></param>
|
/// <param name="steeringRequest"></param>
|
||||||
|
/// <param name="frameTime"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="NotImplementedException"></exception>
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest)
|
private SteeringStatus Steer(IEntity entity, IAiSteeringRequest steeringRequest, float frameTime)
|
||||||
{
|
{
|
||||||
// Main optimisation to be done below is the redundant calls and adding more variables
|
// Main optimisation to be done below is the redundant calls and adding more variables
|
||||||
if (!entity.TryGetComponent(out AiControllerComponent controller) || !ActionBlockerSystem.CanMove(entity))
|
if (!entity.TryGetComponent(out AiControllerComponent controller) || !ActionBlockerSystem.CanMove(entity))
|
||||||
@@ -262,15 +270,29 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
|
|
||||||
// Check if we have arrived
|
// Check if we have arrived
|
||||||
var targetDistance = (entity.Transform.MapPosition.Position - steeringRequest.TargetMap.Position).Length;
|
var targetDistance = (entity.Transform.MapPosition.Position - steeringRequest.TargetMap.Position).Length;
|
||||||
if (targetDistance <= steeringRequest.ArrivalDistance)
|
steeringRequest.TimeUntilInteractionCheck -= frameTime;
|
||||||
|
|
||||||
|
if (targetDistance <= steeringRequest.ArrivalDistance && steeringRequest.TimeUntilInteractionCheck <= 0.0f)
|
||||||
{
|
{
|
||||||
// TODO: If we need LOS and are moving to an entity then we may not be in range yet
|
if (!steeringRequest.RequiresInRangeUnobstructed ||
|
||||||
// Chuck out a ray every half second or so and keep moving until we are?
|
InteractionChecks.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, ignoredEnt: entity))
|
||||||
// Alternatively could use tile-based LOS checks via the pathfindingsystem I guess
|
{
|
||||||
|
// TODO: Need cruder LOS checks for ranged weaps
|
||||||
controller.VelocityDir = Vector2.Zero;
|
controller.VelocityDir = Vector2.Zero;
|
||||||
return SteeringStatus.Arrived;
|
return SteeringStatus.Arrived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
steeringRequest.TimeUntilInteractionCheck = InRangeUnobstructedCooldown;
|
||||||
|
// Welp, we'll keep on moving.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're really close don't swiggity swoogity back and forth and just wait for the interaction check maybe?
|
||||||
|
if (steeringRequest.TimeUntilInteractionCheck > 0.0f && targetDistance <= 0.1f)
|
||||||
|
{
|
||||||
|
controller.VelocityDir = Vector2.Zero;
|
||||||
|
return SteeringStatus.Moving;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle pathfinding job
|
// Handle pathfinding job
|
||||||
// If we still have an existing path then keep following that until the new path arrives
|
// If we still have an existing path then keep following that until the new path arrives
|
||||||
if (_pathfindingRequests.TryGetValue(entity, out var pathRequest) && pathRequest.Job.Status == JobStatus.Finished)
|
if (_pathfindingRequests.TryGetValue(entity, out var pathRequest) && pathRequest.Job.Status == JobStatus.Finished)
|
||||||
|
|||||||
@@ -10,20 +10,26 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
public GridCoordinates TargetGrid => _target.Transform.GridPosition;
|
public GridCoordinates TargetGrid => _target.Transform.GridPosition;
|
||||||
public IEntity Target => _target;
|
public IEntity Target => _target;
|
||||||
private IEntity _target;
|
private IEntity _target;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public float ArrivalDistance { get; }
|
public float ArrivalDistance { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public float PathfindingProximity { get; }
|
public float PathfindingProximity { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How far the target can move before we re-path
|
/// How far the target can move before we re-path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float TargetMaxMove { get; } = 1.5f;
|
public float TargetMaxMove { get; } = 1.5f;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// If we need LOS on the entity first before interaction
|
|
||||||
/// </summary>
|
|
||||||
public bool RequiresInRangeUnobstructed { get; }
|
public bool RequiresInRangeUnobstructed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To avoid spamming InRangeUnobstructed we'll apply a cd to it.
|
||||||
|
/// </summary>
|
||||||
|
public float TimeUntilInteractionCheck { get; set; }
|
||||||
|
|
||||||
public EntityTargetSteeringRequest(IEntity target, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false)
|
public EntityTargetSteeringRequest(IEntity target, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false)
|
||||||
{
|
{
|
||||||
_target = target;
|
_target = target;
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public float PathfindingProximity { get; }
|
public float PathfindingProximity { get; }
|
||||||
|
|
||||||
public GridTargetSteeringRequest(GridCoordinates targetGrid, float arrivalDistance, float pathfindingProximity = 0.5f)
|
public bool RequiresInRangeUnobstructed { get; }
|
||||||
|
|
||||||
|
public float TimeUntilInteractionCheck { get; set; } = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
|
public GridTargetSteeringRequest(GridCoordinates targetGrid, float arrivalDistance, float pathfindingProximity = 0.5f, bool requiresInRangeUnobstructed = false)
|
||||||
{
|
{
|
||||||
// Get it once up front so we the manager doesn't have to continuously get it
|
// Get it once up front so we the manager doesn't have to continuously get it
|
||||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
@@ -22,6 +27,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
TargetGrid = targetGrid;
|
TargetGrid = targetGrid;
|
||||||
ArrivalDistance = arrivalDistance;
|
ArrivalDistance = arrivalDistance;
|
||||||
PathfindingProximity = pathfindingProximity;
|
PathfindingProximity = pathfindingProximity;
|
||||||
|
RequiresInRangeUnobstructed = requiresInRangeUnobstructed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,5 +16,15 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
|
|||||||
/// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance
|
/// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
float PathfindingProximity { get; }
|
float PathfindingProximity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If we need LOS on the entity first before interaction
|
||||||
|
/// </summary>
|
||||||
|
bool RequiresInRangeUnobstructed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To avoid spamming InRangeUnobstructed we'll apply a cd to it.
|
||||||
|
/// </summary>
|
||||||
|
public float TimeUntilInteractionCheck { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user