Add simple miss chances for NPCs (#12978)
Doesn't consider juking potential but okay for now.
This commit is contained in:
@@ -11,6 +11,12 @@ public sealed class NPCMeleeCombatComponent : Component
|
||||
/// </summary>
|
||||
[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]
|
||||
public EntityUid Target;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public sealed class MeleeOperator : HTNOperator
|
||||
{
|
||||
base.Startup(blackboard);
|
||||
var melee = _entManager.EnsureComponent<NPCMeleeCombatComponent>(blackboard.GetValue<EntityUid>(NPCBlackboard.Owner));
|
||||
melee.MissChance = blackboard.GetValueOrDefault<float>(NPCBlackboard.MeleeMissChance, _entManager);
|
||||
melee.Target = blackboard.GetValue<EntityUid>(TargetKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
||||
{"IdleRange", 7f},
|
||||
{"MaximumIdleTime", 7f},
|
||||
{MedibotInjectRange, 4f},
|
||||
{MeleeMissChance, 0.3f},
|
||||
{"MeleeRange", 1f},
|
||||
{"MinimumIdleTime", 2f},
|
||||
{"MovementRange", 1.5f},
|
||||
@@ -190,6 +191,9 @@ public sealed class NPCBlackboard : IEnumerable<KeyValuePair<string, object>>
|
||||
public const string CanMove = "CanMove";
|
||||
public const string FollowTarget = "FollowTarget";
|
||||
public const string MedibotInjectRange = "MedibotInjectRange";
|
||||
|
||||
public const string MeleeMissChance = "MeleeMissChance";
|
||||
|
||||
public const string Owner = "Owner";
|
||||
public const string OwnerCoordinates = "OwnerCoordinates";
|
||||
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 VisionRadius = "VisionRadius";
|
||||
public const float MeleeRange = 1f;
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@ using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
|
||||
@@ -42,6 +44,8 @@ public sealed partial class NPCCombatSystem
|
||||
{
|
||||
var combatQuery = GetEntityQuery<CombatModeComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
foreach (var (comp, _) in EntityQuery<NPCMeleeCombatComponent, ActiveNPCComponent>())
|
||||
{
|
||||
@@ -51,17 +55,14 @@ public sealed partial class NPCCombatSystem
|
||||
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;
|
||||
|
||||
// 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))
|
||||
{
|
||||
component.Status = CombatStatus.NoWeapon;
|
||||
@@ -105,6 +106,19 @@ public sealed partial class NPCCombatSystem
|
||||
|
||||
// Gets unregistered on component shutdown.
|
||||
_steering.TryRegister(component.Owner, new EntityCoordinates(component.Target, Vector2.Zero), steering);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.NPC.Systems;
|
||||
@@ -13,7 +12,9 @@ namespace Content.Server.NPC.Systems;
|
||||
/// </summary>
|
||||
public sealed partial class NPCCombatSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GunSystem _gun = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
@@ -261,6 +261,11 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
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)
|
||||
{
|
||||
if (!TryComp<TransformComponent>(target, out var targetXform))
|
||||
|
||||
Reference in New Issue
Block a user