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:
metalgearsloth
2020-06-18 22:52:44 +10:00
committed by GitHub
parent 9b8cedf6c6
commit 5391d3c72a
211 changed files with 10335 additions and 527 deletions

View 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.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()),
};
}
}

View File

@@ -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()),
};
}
}

View 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()),
};
}
}

View File

@@ -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()),
};
}
}

View 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.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()),
};
}
}

View File

@@ -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()),
};
}
}

View 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.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()),
};
}
}

View File

@@ -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()),
};
}
}

View 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)),
};
}
}

View File

@@ -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)),
};
}
}

View File

@@ -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)),
};
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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()),
};
}
}

View File

@@ -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)),
};
}
}

View File

@@ -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)),
};
}
}

View File

@@ -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
};
}
}

View File

@@ -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)),
};
}
}

View File

@@ -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()),
};
}
}

View 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.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)),
};
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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)),
};
}
}

View File

@@ -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)),
};
}
}

View File

@@ -0,0 +1,9 @@
using Content.Server.AI.Utility.AiLogic;
namespace Content.Server.AI.Utility.Actions
{
public interface IAiUtility
{
float Bonus { get; }
}
}

View File

@@ -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),
});
}
}
}

View 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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
});
}
}
}

View 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;
}
}
}