Add utility AI (#806)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com> Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
||||
{
|
||||
public sealed class EquipGloves : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipGloves(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new UseItemInHandsOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.GLOVES,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Gloves
|
||||
{
|
||||
public sealed class PickUpGloves : UtilityAction
|
||||
{
|
||||
private readonly IEntity _entity;
|
||||
|
||||
public PickUpGloves(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.GLOVES,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.GLOVES,
|
||||
new InverseBoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
47
Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs
Normal file
47
Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
||||
{
|
||||
public sealed class EquipHead : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipHead(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new UseItemInHandsOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.HEAD,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Head
|
||||
{
|
||||
public sealed class PickUpHead : UtilityAction
|
||||
{
|
||||
private readonly IEntity _entity;
|
||||
|
||||
public PickUpHead(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.HEAD,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.HEAD,
|
||||
new InverseBoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
||||
{
|
||||
public sealed class EquipOuterClothing : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipOuterClothing(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new UseItemInHandsOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.OUTERCLOTHING,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing
|
||||
{
|
||||
public sealed class PickUpOuterClothing : UtilityAction
|
||||
{
|
||||
private readonly IEntity _entity;
|
||||
|
||||
public PickUpOuterClothing(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.OUTERCLOTHING,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.OUTERCLOTHING,
|
||||
new InverseBoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
||||
{
|
||||
public sealed class EquipShoes : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipShoes(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new UseItemInHandsOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.SHOES,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Clothing;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Clothing.Shoes
|
||||
{
|
||||
public sealed class PickUpShoes : UtilityAction
|
||||
{
|
||||
private readonly IEntity _entity;
|
||||
|
||||
public PickUpShoes(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new ClothingInSlotCon(EquipmentSlotDefines.Slots.SHOES,
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new ClothingInInventoryCon(EquipmentSlotDefines.SlotFlags.SHOES,
|
||||
new InverseBoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
55
Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs
Normal file
55
Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class EquipMelee : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipMelee(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new MeleeWeaponEquippedCon(
|
||||
new InverseBoolCurve()),
|
||||
// We'll prioritise equipping ranged weapons; If we try and score this then it'll just keep swapping between ranged and melee
|
||||
new RangedWeaponEquippedCon(
|
||||
new InverseBoolCurve()),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new MeleeWeaponSpeedCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
new MeleeWeaponDamageCon(
|
||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Combat;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class MeleeAttackEntity : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public MeleeAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var moveOperator = new MoveToEntityOperator(Owner, _entity);
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
if (equipped != null && equipped.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent))
|
||||
{
|
||||
moveOperator.DesiredRange = meleeWeaponComponent.Range - 0.01f;
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
moveOperator,
|
||||
new SwingMeleeWeaponOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
// Check if we have a weapon; easy-out
|
||||
new MeleeWeaponEquippedCon(
|
||||
new BoolCurve()),
|
||||
// Don't attack a dead target
|
||||
new TargetIsDeadCon(
|
||||
new InverseBoolCurve()),
|
||||
// Deprioritise a target in crit
|
||||
new TargetIsCritCon(
|
||||
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
||||
// Somewhat prioritise distance
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
// Prefer weaker targets
|
||||
new TargetHealthCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
||||
new MeleeWeaponSpeedCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
new MeleeWeaponDamageCon(
|
||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
||||
{
|
||||
public sealed class PickUpMeleeWeapon : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public PickUpMeleeWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
new HasMeleeWeaponCon(
|
||||
new InverseBoolCurve()),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
new MeleeWeaponDamageCon(
|
||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||
new MeleeWeaponSpeedCon(
|
||||
new QuadraticCurve(-1.0f, 0.5f, 1.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Combat.Ranged;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||
{
|
||||
public sealed class BallisticAttackEntity : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
private MoveToEntityOperator _moveOperator;
|
||||
|
||||
public BallisticAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
if (_moveOperator != null)
|
||||
{
|
||||
_moveOperator.MovedATile -= InLos;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
_moveOperator = new MoveToEntityOperator(Owner, _entity);
|
||||
_moveOperator.MovedATile += InLos;
|
||||
|
||||
// TODO: Accuracy in blackboard
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
_moveOperator,
|
||||
new ShootAtEntityOperator(Owner, _entity, 0.7f),
|
||||
});
|
||||
|
||||
// We will do a quick check now to see if we even need to move which also saves a pathfind
|
||||
InLos();
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
// Check if we have a weapon; easy-out
|
||||
new BallisticWeaponEquippedCon(
|
||||
new BoolCurve()),
|
||||
new BallisticAmmoCon(
|
||||
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
|
||||
// Don't attack a dead target
|
||||
new TargetIsDeadCon(
|
||||
new InverseBoolCurve()),
|
||||
// Deprioritise a target in crit
|
||||
new TargetIsCritCon(
|
||||
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
||||
// Somewhat prioritise distance
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
|
||||
// Prefer weaker targets
|
||||
new TargetHealthCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
||||
};
|
||||
|
||||
private void InLos()
|
||||
{
|
||||
// This should only be called if the movement operator is the current one;
|
||||
// if that turns out not to be the case we can just add a check here.
|
||||
if (Visibility.InLineOfSight(Owner, _entity))
|
||||
{
|
||||
_moveOperator.HaveArrived();
|
||||
var mover = ActionOperators.Dequeue();
|
||||
mover.Shutdown(Outcome.Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||
{
|
||||
public sealed class DropEmptyBallistic : UtilityAction
|
||||
{
|
||||
public sealed override float Bonus => 20.0f;
|
||||
private IEntity _entity;
|
||||
|
||||
public DropEmptyBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new DropEntityOperator(Owner, _entity)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new TargetInOurInventoryCon(
|
||||
new BoolCurve()),
|
||||
// Need to put in hands to drop
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
// Drop that sucker
|
||||
new BallisticAmmoCon(
|
||||
new InverseBoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||
{
|
||||
public sealed class EquipBallistic : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new EquippedBallisticCon(
|
||||
new InverseBoolCurve()),
|
||||
new MeleeWeaponEquippedCon(
|
||||
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new BallisticAmmoCon(
|
||||
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
|
||||
new RangedWeaponFireRateCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||
{
|
||||
public sealed class PickUpAmmo : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public PickUpAmmo(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
//TODO: Consider ammo's type and what guns we have
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
||||
{
|
||||
public sealed class PickUpBallisticMagWeapon : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public PickUpBallisticMagWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new HeldRangedWeaponsCon(
|
||||
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
// For now don't grab empty guns - at least until we can start storing stuff in inventory
|
||||
new BallisticAmmoCon(
|
||||
new BoolCurve()),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
new RangedWeaponFireRateCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
// TODO: Ballistic accuracy? Depends how the design transitions
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||
{
|
||||
public sealed class PutHitscanInCharger : UtilityAction
|
||||
{
|
||||
// Maybe a bad idea to not allow override
|
||||
public override bool CanOverride => false;
|
||||
private readonly IEntity _charger;
|
||||
|
||||
public PutHitscanInCharger(IEntity owner, IEntity charger, float weight) : base(owner)
|
||||
{
|
||||
_charger = charger;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var weapon = context.GetState<EquippedEntityState>().GetValue();
|
||||
|
||||
if (weapon == null || _charger.GetComponent<WeaponCapacitorChargerComponent>().HeldItem != null)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>();
|
||||
return;
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToEntityOperator(Owner, _charger),
|
||||
new InteractWithEntityOperator(Owner, _charger),
|
||||
// Separate task will deal with picking it up
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<MoveTargetState>().SetValue(_charger);
|
||||
context.GetState<TargetEntityState>().SetValue(_charger);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } =
|
||||
{
|
||||
new HitscanWeaponEquippedCon(
|
||||
new BoolCurve()),
|
||||
new HitscanChargerFullCon(
|
||||
new InverseBoolCurve()),
|
||||
new HitscanChargerRateCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
new HitscanChargeCon(
|
||||
new QuadraticCurve(-1.2f, 2.0f, 1.2f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||
{
|
||||
public sealed class DropEmptyHitscan : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public DropEmptyHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new DropEntityOperator(Owner, _entity)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new TargetInOurInventoryCon(
|
||||
new BoolCurve()),
|
||||
// Need to put in hands to drop
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
// If completely empty then drop that sucker
|
||||
new HitscanChargeCon(
|
||||
new InverseBoolCurve()),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||
{
|
||||
public sealed class EquipHitscan : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public EquipHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity)
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new EquippedHitscanCon(
|
||||
new InverseBoolCurve()),
|
||||
new MeleeWeaponEquippedCon(
|
||||
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
|
||||
new CanPutTargetInHandsCon(
|
||||
new BoolCurve()),
|
||||
new HitscanChargeCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.0f, 0.0f)),
|
||||
new RangedWeaponFireRateCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
new HitscanWeaponDamageCon(
|
||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Combat.Ranged;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.Utils;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||
{
|
||||
public sealed class HitscanAttackEntity : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
private MoveToEntityOperator _moveOperator;
|
||||
|
||||
public HitscanAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
if (_moveOperator != null)
|
||||
{
|
||||
_moveOperator.MovedATile -= InLos;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
_moveOperator = new MoveToEntityOperator(Owner, _entity);
|
||||
_moveOperator.MovedATile += InLos;
|
||||
|
||||
// TODO: Accuracy in blackboard
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
_moveOperator,
|
||||
new ShootAtEntityOperator(Owner, _entity, 0.7f),
|
||||
});
|
||||
|
||||
InLos();
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
// Check if we have a weapon; easy-out
|
||||
new HitscanWeaponEquippedCon(
|
||||
new BoolCurve()),
|
||||
new HitscanChargeCon(
|
||||
new QuadraticCurve(1.0f, 0.1f, 0.0f, 0.0f)),
|
||||
// Don't attack a dead target
|
||||
new TargetIsDeadCon(
|
||||
new InverseBoolCurve()),
|
||||
// Deprioritise a target in crit
|
||||
new TargetIsCritCon(
|
||||
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
||||
// Somewhat prioritise distance
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
|
||||
// Prefer weaker targets
|
||||
new TargetHealthCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
||||
};
|
||||
|
||||
private void InLos()
|
||||
{
|
||||
// This should only be called if the movement operator is the current one;
|
||||
// if that turns out not to be the case we can just add a check here.
|
||||
if (Visibility.InLineOfSight(Owner, _entity))
|
||||
{
|
||||
_moveOperator.HaveArrived();
|
||||
var mover = ActionOperators.Dequeue();
|
||||
mover.Shutdown(Outcome.Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Combat.Ranged;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||
{
|
||||
public sealed class PickUpHitscanFromCharger : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
private IEntity _charger;
|
||||
|
||||
public PickUpHitscanFromCharger(IEntity owner, IEntity entity, IEntity charger, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
_charger = charger;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToEntityOperator(Owner, _charger),
|
||||
new WaitForHitscanChargeOperator(_entity),
|
||||
new PickupEntityOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new HeldRangedWeaponsCon(
|
||||
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
// TODO: ChargerHasPower
|
||||
new RangedWeaponFireRateCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
new HitscanWeaponDamageCon(
|
||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Server.AI.WorldState.States.Combat;
|
||||
using Content.Server.AI.WorldState.States.Movement;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
||||
{
|
||||
public sealed class PickUpHitscanWeapon : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public PickUpHitscanWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new HeldRangedWeaponsCon(
|
||||
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
// For now don't grab empty guns - at least until we can start storing stuff in inventory
|
||||
new HitscanChargeCon(
|
||||
new BoolCurve()),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
// TODO: Weapon charge level
|
||||
new RangedWeaponFireRateCon(
|
||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
||||
new HitscanWeaponDamageCon(
|
||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
||||
};
|
||||
}
|
||||
}
|
||||
9
Content.Server/AI/Utility/Actions/IAiUtility.cs
Normal file
9
Content.Server/AI/Utility/Actions/IAiUtility.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions
|
||||
{
|
||||
public interface IAiUtility
|
||||
{
|
||||
float Bonus { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Considerations.State;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Idle
|
||||
{
|
||||
/// <summary>
|
||||
/// If we just picked up a bunch of stuff and have time then close it
|
||||
/// </summary>
|
||||
public sealed class CloseLastEntityStorage : UtilityAction
|
||||
{
|
||||
public override float Bonus => 1.5f;
|
||||
|
||||
public CloseLastEntityStorage(IEntity owner) : base(owner) {}
|
||||
|
||||
protected override Consideration[] Considerations => new Consideration[]
|
||||
{
|
||||
new StoredStateIsNullCon<LastOpenedStorageState, IEntity>(
|
||||
new InverseBoolCurve()),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
};
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var lastStorage = context.GetState<LastOpenedStorageState>().GetValue();
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToEntityOperator(Owner, lastStorage),
|
||||
new CloseLastStorageOperator(Owner),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
83
Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs
Normal file
83
Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Generic;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.ActionBlocker;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Idle
|
||||
{
|
||||
/// <summary>
|
||||
/// Will move to a random spot close by
|
||||
/// </summary>
|
||||
public sealed class WanderAndWait : UtilityAction
|
||||
{
|
||||
public override bool CanOverride => false;
|
||||
public override float Bonus => IdleBonus;
|
||||
|
||||
public WanderAndWait(IEntity owner) : base(owner)
|
||||
{
|
||||
// TODO: Need a Success method that gets called to update context (e.g. when we last did X)
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var randomGrid = FindRandomGrid();
|
||||
float waitTime;
|
||||
if (randomGrid != GridCoordinates.InvalidGrid)
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
waitTime = random.NextFloat() * 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
waitTime = 0.0f;
|
||||
}
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new MoveToGridOperator(Owner, randomGrid),
|
||||
new WaitOperator(waitTime),
|
||||
});
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new CanMoveCon(
|
||||
new BoolCurve())
|
||||
// Last wander? If we also want to sit still
|
||||
};
|
||||
|
||||
private GridCoordinates FindRandomGrid()
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var grid = mapManager.GetGrid(Owner.Transform.GridID);
|
||||
|
||||
// Just find a random spot in bounds
|
||||
// If the grid's a single-tile wide but really tall this won't really work but eh future problem
|
||||
var gridBounds = grid.WorldBounds;
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
var newPosition = gridBounds.BottomLeft + new Vector2(
|
||||
robustRandom.Next((int) gridBounds.Width),
|
||||
robustRandom.Next((int) gridBounds.Height));
|
||||
// Conversions blah blah
|
||||
var mapIndex = grid.WorldToTile(grid.LocalToWorld(newPosition));
|
||||
// Didn't find one? Fuck it we're not walkin' into space
|
||||
if (grid.GetTileRef(mapIndex).Tile.IsEmpty)
|
||||
{
|
||||
return GridCoordinates.InvalidGrid;
|
||||
}
|
||||
var target = grid.GridTileToLocal(mapIndex);
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
||||
{
|
||||
public sealed class PickUpDrink : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public PickUpDrink(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations => new Consideration[]
|
||||
{
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
new ThirstCon(
|
||||
new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
new DrinkValueCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)),
|
||||
};
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition.Drink;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Drink
|
||||
{
|
||||
public sealed class UseDrinkInInventory : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public UseDrinkInInventory(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new UseItemInHandsOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations => new Consideration[]
|
||||
{
|
||||
new TargetInOurHandsCon(
|
||||
new BoolCurve()),
|
||||
new ThirstCon(
|
||||
new LogisticCurve(1000f, 1.3f, -0.3f, 0.5f)),
|
||||
new DrinkValueCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f))
|
||||
};
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Content.Server.AI.Operators.Sequences;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Containers;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Movement;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
||||
{
|
||||
public sealed class PickUpFood : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public PickUpFood(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations => new Consideration[]
|
||||
{
|
||||
new TargetAccessibleCon(
|
||||
new BoolCurve()),
|
||||
new FreeHandCon(
|
||||
new BoolCurve()),
|
||||
new HungerCon(
|
||||
new LogisticCurve(1000f, 1.3f, -1.0f, 0.5f)),
|
||||
new DistanceCon(
|
||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
||||
new FoodValueCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f)),
|
||||
};
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Inventory;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Considerations.Hands;
|
||||
using Content.Server.AI.Utility.Considerations.Nutrition;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Nutrition.Food
|
||||
{
|
||||
public sealed class UseFoodInInventory : UtilityAction
|
||||
{
|
||||
private IEntity _entity;
|
||||
|
||||
public UseFoodInInventory(IEntity owner, IEntity entity, float weight) : base(owner)
|
||||
{
|
||||
_entity = entity;
|
||||
Bonus = weight;
|
||||
}
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
new EquipEntityOperator(Owner, _entity),
|
||||
new UseItemInHandsOperator(Owner, _entity),
|
||||
});
|
||||
}
|
||||
|
||||
protected override Consideration[] Considerations => new Consideration[]
|
||||
{
|
||||
new TargetInOurHandsCon(
|
||||
new BoolCurve()),
|
||||
new HungerCon(
|
||||
new LogisticCurve(1000f, 1.3f, -0.3f, 0.5f)),
|
||||
new FoodValueCon(
|
||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, 0.0f))
|
||||
};
|
||||
|
||||
protected override void UpdateBlackboard(Blackboard context)
|
||||
{
|
||||
base.UpdateBlackboard(context);
|
||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Operators.Movement;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.Utility.Curves;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for pathfinding debugging
|
||||
/// </summary>
|
||||
public class MoveRightAndLeftTen : UtilityAction
|
||||
{
|
||||
public override bool CanOverride => false;
|
||||
|
||||
public MoveRightAndLeftTen(IEntity owner) : base(owner) {}
|
||||
|
||||
protected override Consideration[] Considerations { get; } = {
|
||||
new DummyCon(
|
||||
new BoolCurve())
|
||||
};
|
||||
|
||||
public override void SetupOperators(Blackboard context)
|
||||
{
|
||||
var currentPosition = Owner.Transform.GridPosition;
|
||||
var nextPosition = Owner.Transform.GridPosition.Offset(new Vector2(10.0f, 0.0f));
|
||||
var originalPosOp = new MoveToGridOperator(Owner, currentPosition, 0.25f);
|
||||
var newPosOp = new MoveToGridOperator(Owner, nextPosition, 0.25f);
|
||||
|
||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
||||
{
|
||||
newPosOp,
|
||||
originalPosOp
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
149
Content.Server/AI/Utility/Actions/UtilityAction.cs
Normal file
149
Content.Server/AI/Utility/Actions/UtilityAction.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Operators;
|
||||
using Content.Server.AI.Utility.Considerations;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.AI.Utility.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// The same DSE can be used across multiple actions.
|
||||
/// </summary>
|
||||
public abstract class UtilityAction : IAiUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// If we're trying to find a new action can we replace a currently running one with one of the same type.
|
||||
/// e.g. If you're already wandering you don't want to replace it with a different wander.
|
||||
/// </summary>
|
||||
public virtual bool CanOverride => false;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to sort actions; if there's a top-tier action available we won't bother checking the lower tiers.
|
||||
/// Threshold doesn't necessarily mean we'll do an action at a higher threshold;
|
||||
/// if it's really un-optimal (i.e. low score) then we'll also check lower tiers
|
||||
/// </summary>
|
||||
public virtual float Bonus { get; protected set; } = IdleBonus;
|
||||
// For GW2 they had the bonuses close together but IMO it feels better when they're more like discrete tiers.
|
||||
|
||||
// These are just baselines to make mass-updates easier; actions can do whatever
|
||||
// e.g. if you want shooting a gun to be considered before picking up a gun you could + 1.0f it or w/e
|
||||
public const float IdleBonus = 1.0f;
|
||||
public const float NormalBonus = 5.0f;
|
||||
public const float NeedsBonus = 10.0f;
|
||||
public const float CombatPrepBonus = 20.0f;
|
||||
public const float CombatBonus = 30.0f;
|
||||
public const float DangerBonus = 50.0f;
|
||||
|
||||
protected IEntity Owner { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All the considerations are multiplied together to get the final score; a consideration of 0.0 means the action is not possible.
|
||||
/// Ideally you put anything that's easy to assess and can cause an early-out first just so the rest aren't evaluated.
|
||||
/// </summary>
|
||||
protected abstract Consideration[] Considerations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// To keep the operators simple we can chain them together here, e.g. move to can be chained with other operators.
|
||||
/// </summary>
|
||||
public Queue<AiOperator> ActionOperators { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sometimes we may need to set the target for an action or the likes.
|
||||
/// This is mainly useful for expandable states so each one can have a separate target.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
protected virtual void UpdateBlackboard(Blackboard context) {}
|
||||
|
||||
protected UtilityAction(IEntity owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public virtual void Shutdown() {}
|
||||
|
||||
/// <summary>
|
||||
/// If this action is chosen then setup the operators to run. This also allows for operators to be reset.
|
||||
/// </summary>
|
||||
public abstract void SetupOperators(Blackboard context);
|
||||
|
||||
// Call the task's operator with Execute and get the outcome
|
||||
public Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!ActionOperators.TryPeek(out var op))
|
||||
{
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
op.TryStartup();
|
||||
var outcome = op.Execute(frameTime);
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case Outcome.Success:
|
||||
op.Shutdown(outcome);
|
||||
ActionOperators.Dequeue();
|
||||
break;
|
||||
case Outcome.Continuing:
|
||||
break;
|
||||
case Outcome.Failed:
|
||||
op.Shutdown(outcome);
|
||||
ActionOperators.Clear();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return outcome;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AKA the Decision Score Evaluator (DSE)
|
||||
/// This is where the magic happens
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="bonus"></param>
|
||||
/// <param name="min"></param>
|
||||
/// <returns></returns>
|
||||
public float GetScore(Blackboard context, float min)
|
||||
{
|
||||
UpdateBlackboard(context);
|
||||
DebugTools.Assert(Considerations.Length > 0);
|
||||
// I used the IAUS video although I did have some confusion on how to structure it overall
|
||||
// as some of the slides seemed contradictory
|
||||
|
||||
// Ideally we should early-out each action as cheaply as possible if it's not valid
|
||||
|
||||
// We also need some way to tell if the action isn't going to
|
||||
// have a better score than the current action (if applicable) and early-out that way as well.
|
||||
|
||||
// 23:00 Building a better centaur
|
||||
var finalScore = 1.0f;
|
||||
var minThreshold = min / Bonus;
|
||||
var modificationFactor = 1.0f - 1.0f / Considerations.Length;
|
||||
// See 10:09 for this and the adjustments
|
||||
|
||||
foreach (var consideration in Considerations)
|
||||
{
|
||||
var score = consideration.GetScore(context);
|
||||
var makeUpValue = (1.0f - score) * modificationFactor;
|
||||
var adjustedScore = score + makeUpValue * score;
|
||||
var response = consideration.ComputeResponseCurve(adjustedScore);
|
||||
|
||||
finalScore *= response;
|
||||
|
||||
DebugTools.Assert(!float.IsNaN(response));
|
||||
|
||||
// The score can only ever go down from each consideration so if we're below minimum no point continuing.
|
||||
if (0.0f >= finalScore || finalScore < minThreshold) {
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
DebugTools.Assert(finalScore <= 1.0f);
|
||||
|
||||
return finalScore * Bonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user