Add simple miss chances for NPCs (#12978)

Doesn't consider juking potential but okay for now.
This commit is contained in:
metalgearsloth
2022-12-12 00:37:09 +11:00
committed by GitHub
parent da31f9e5ee
commit 83fede79eb
6 changed files with 39 additions and 9 deletions

View File

@@ -11,6 +11,12 @@ public sealed class NPCMeleeCombatComponent : Component
/// </summary> /// </summary>
[ViewVariables] public EntityUid Weapon; [ViewVariables] public EntityUid Weapon;
/// <summary>
/// If the target is moving what is the chance for this NPC to miss.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float MissChance;
[ViewVariables] [ViewVariables]
public EntityUid Target; public EntityUid Target;

View File

@@ -31,6 +31,7 @@ public sealed class MeleeOperator : HTNOperator
{ {
base.Startup(blackboard); base.Startup(blackboard);
var melee = _entManager.EnsureComponent<NPCMeleeCombatComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner)); var melee = _entManager.EnsureComponent<NPCMeleeCombatComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
melee.MissChance = blackboard.GetValueOrDefault<float>(NPCBlackboard.MeleeMissChance, _entManager);
melee.Target = blackboard.GetValue<EntityUid>(TargetKey); melee.Target = blackboard.GetValue<EntityUid>(TargetKey);
} }

View File

@@ -20,6 +20,7 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
{"IdleRange", 7f}, {"IdleRange", 7f},
{"MaximumIdleTime", 7f}, {"MaximumIdleTime", 7f},
{MedibotInjectRange, 4f}, {MedibotInjectRange, 4f},
{MeleeMissChance, 0.3f},
{"MeleeRange", 1f}, {"MeleeRange", 1f},
{"MinimumIdleTime", 2f}, {"MinimumIdleTime", 2f},
{"MovementRange", 1.5f}, {"MovementRange", 1.5f},
@@ -190,6 +191,9 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
public const string CanMove = "CanMove"; public const string CanMove = "CanMove";
public const string FollowTarget = "FollowTarget"; public const string FollowTarget = "FollowTarget";
public const string MedibotInjectRange = "MedibotInjectRange"; public const string MedibotInjectRange = "MedibotInjectRange";
public const string MeleeMissChance = "MeleeMissChance";
public const string Owner = "Owner"; public const string Owner = "Owner";
public const string OwnerCoordinates = "OwnerCoordinates"; public const string OwnerCoordinates = "OwnerCoordinates";
public const string MovementTarget = "MovementTarget"; public const string MovementTarget = "MovementTarget";
@@ -216,7 +220,6 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
public const string RotateSpeed = "RotateSpeed"; public const string RotateSpeed = "RotateSpeed";
public const string VisionRadius = "VisionRadius"; public const string VisionRadius = "VisionRadius";
public const float MeleeRange = 1f;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{ {

View File

@@ -4,6 +4,8 @@ using Content.Shared.MobState;
using Content.Shared.MobState.Components; using Content.Shared.MobState.Components;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
namespace Content.Server.NPC.Systems; namespace Content.Server.NPC.Systems;
@@ -42,6 +44,8 @@ public sealed partial class NPCCombatSystem
{ {
var combatQuery = GetEntityQuery<CombatModeComponent>(); var combatQuery = GetEntityQuery<CombatModeComponent>();
var xformQuery = GetEntityQuery<TransformComponent>(); var xformQuery = GetEntityQuery<TransformComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var curTime = _timing.CurTime;
foreach (var (comp, _) in EntityQuery<NPCMeleeCombatComponent, ActiveNPCComponent>()) foreach (var (comp, _) in EntityQuery<NPCMeleeCombatComponent, ActiveNPCComponent>())
{ {
@@ -51,17 +55,14 @@ public sealed partial class NPCCombatSystem
continue; continue;
} }
Attack(comp, xformQuery); Attack(comp, curTime, physicsQuery, xformQuery);
} }
} }
private void Attack(NPCMeleeCombatComponent component, EntityQuery<TransformComponent> xformQuery) private void Attack(NPCMeleeCombatComponent component, TimeSpan curTime, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery)
{ {
component.Status = CombatStatus.Normal; component.Status = CombatStatus.Normal;
// TODO:
// Also need some blackboard data for stuff like juke frequency, assigning target slots (to surround targets), etc.
// miss %
if (!TryComp<MeleeWeaponComponent>(component.Weapon, out var weapon)) if (!TryComp<MeleeWeaponComponent>(component.Weapon, out var weapon))
{ {
component.Status = CombatStatus.NoWeapon; component.Status = CombatStatus.NoWeapon;
@@ -105,6 +106,19 @@ public sealed partial class NPCCombatSystem
// Gets unregistered on component shutdown. // Gets unregistered on component shutdown.
_steering.TryRegister(component.Owner, new EntityCoordinates(component.Target, Vector2.Zero), steering); _steering.TryRegister(component.Owner, new EntityCoordinates(component.Target, Vector2.Zero), steering);
_melee.AttemptLightAttack(component.Owner, weapon, component.Target);
if (weapon.NextAttack > curTime)
return;
if (_random.Prob(component.MissChance) &&
physicsQuery.TryGetComponent(component.Target, out var targetPhysics) &&
targetPhysics.LinearVelocity.LengthSquared != 0f)
{
_melee.AttemptLightAttackMiss(component.Owner, weapon, targetXform.Coordinates.Offset(_random.NextVector2(0.5f)));
}
else
{
_melee.AttemptLightAttack(component.Owner, weapon, component.Target);
}
} }
} }

View File

@@ -1,9 +1,8 @@
using Content.Server.Interaction; using Content.Server.Interaction;
using Content.Server.Weapons.Ranged.Systems; using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.CombatMode;
using Content.Shared.Interaction;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Server.NPC.Systems; namespace Content.Server.NPC.Systems;
@@ -13,7 +12,9 @@ namespace Content.Server.NPC.Systems;
/// </summary> /// </summary>
public sealed partial class NPCCombatSystem : EntitySystem public sealed partial class NPCCombatSystem : EntitySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly GunSystem _gun = default!; [Dependency] private readonly GunSystem _gun = default!;
[Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;

View File

@@ -261,6 +261,11 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
return null; return null;
} }
public void AttemptLightAttackMiss(EntityUid user, MeleeWeaponComponent weapon, EntityCoordinates coordinates)
{
AttemptAttack(user, weapon, new LightAttackEvent(null, weapon.Owner, coordinates), null);
}
public void AttemptLightAttack(EntityUid user, MeleeWeaponComponent weapon, EntityUid target) public void AttemptLightAttack(EntityUid user, MeleeWeaponComponent weapon, EntityUid target)
{ {
if (!TryComp<TransformComponent>(target, out var targetXform)) if (!TryComp<TransformComponent>(target, out var targetXform))