foreach (var (comp, _) in EntityQuery<NPCMeleeCombatComponent, ActiveNPCComponent>())
{
- if (!combatQuery.TryGetComponent(comp.Owner, out var combat) || !combat.IsInCombatMode)
+ var uid = comp.Owner;
+
+ if (!combatQuery.TryGetComponent(uid, out var combat) || !combat.IsInCombatMode)
{
- RemComp<NPCMeleeCombatComponent>(comp.Owner);
+ RemComp<NPCMeleeCombatComponent>(uid);
continue;
}
- Attack(comp, curTime, physicsQuery, xformQuery);
+ Attack(uid, comp, curTime, physicsQuery, xformQuery);
}
}
- private void Attack(NPCMeleeCombatComponent component, TimeSpan curTime, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery)
+ private void Attack(EntityUid uid, NPCMeleeCombatComponent component, TimeSpan curTime, EntityQuery<PhysicsComponent> physicsQuery, EntityQuery<TransformComponent> xformQuery)
{
component.Status = CombatStatus.Normal;
return;
}
- if (!xformQuery.TryGetComponent(component.Owner, out var xform) ||
+ if (!xformQuery.TryGetComponent(uid, out var xform) ||
!xformQuery.TryGetComponent(component.Target, out var targetXform))
{
component.Status = CombatStatus.TargetUnreachable;
return;
}
- if (TryComp<NPCSteeringComponent>(component.Owner, out var steering) &&
+ if (TryComp<NPCSteeringComponent>(uid, out var steering) &&
steering.Status == SteeringStatus.NoPath)
{
component.Status = CombatStatus.TargetUnreachable;
return;
}
- steering = EnsureComp<NPCSteeringComponent>(component.Owner);
+ steering = EnsureComp<NPCSteeringComponent>(uid);
steering.Range = MathF.Max(0.2f, weapon.Range - 0.4f);
// Gets unregistered on component shutdown.
- _steering.TryRegister(component.Owner, new EntityCoordinates(component.Target, Vector2.Zero), steering);
+ _steering.TryRegister(uid, new EntityCoordinates(component.Target, Vector2.Zero), steering);
if (weapon.NextAttack > curTime || !Enabled)
return;
physicsQuery.TryGetComponent(component.Target, out var targetPhysics) &&
targetPhysics.LinearVelocity.LengthSquared != 0f)
{
- _melee.AttemptLightAttackMiss(component.Owner, weapon, targetXform.Coordinates.Offset(_random.NextVector2(0.5f)));
+ _melee.AttemptLightAttackMiss(uid, component.Weapon, weapon, targetXform.Coordinates.Offset(_random.NextVector2(0.5f)));
}
else
{
- _melee.AttemptLightAttack(component.Owner, weapon, component.Target);
+ _melee.AttemptLightAttack(uid, component.Weapon, weapon, component.Target);
}
}
}
[Dependency] protected readonly SharedInteractionSystem Interaction = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] protected readonly SharedPopupSystem PopupSystem = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly StaminaSystem _stamina = default!;
protected ISawmill Sawmill = default!;
if (weapon?.Owner != msg.Weapon)
return;
- AttemptAttack(args.SenderSession.AttachedEntity!.Value, weapon, msg, args.SenderSession);
+ AttemptAttack(args.SenderSession.AttachedEntity!.Value, msg.Weapon, weapon, msg, args.SenderSession);
}
private void OnStopHeavyAttack(StopHeavyAttackEvent msg, EntitySessionEventArgs args)
if (userWeapon != weapon)
return;
- AttemptAttack(args.SenderSession.AttachedEntity.Value, weapon, msg, args.SenderSession);
+ AttemptAttack(args.SenderSession.AttachedEntity.Value, msg.Weapon, weapon, msg, args.SenderSession);
}
private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args)
if (userWeapon == null)
return;
- AttemptAttack(args.SenderSession.AttachedEntity.Value, userWeapon, msg, args.SenderSession);
+ AttemptAttack(args.SenderSession.AttachedEntity.Value, userWeapon.Owner, userWeapon, msg, args.SenderSession);
}
private void OnGetState(EntityUid uid, MeleeWeaponComponent component, ref ComponentGetState args)
return null;
}
- public void AttemptLightAttackMiss(EntityUid user, MeleeWeaponComponent weapon, EntityCoordinates coordinates)
+ public void AttemptLightAttackMiss(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityCoordinates coordinates)
{
- AttemptAttack(user, weapon, new LightAttackEvent(null, weapon.Owner, coordinates), null);
+ AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(null, weaponUid, coordinates), null);
}
- public void AttemptLightAttack(EntityUid user, MeleeWeaponComponent weapon, EntityUid target)
+ public void AttemptLightAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target)
{
if (!TryComp<TransformComponent>(target, out var targetXform))
return;
- AttemptAttack(user, weapon, new LightAttackEvent(target, weapon.Owner, targetXform.Coordinates), null);
+ AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(target, weaponUid, targetXform.Coordinates), null);
}
- public void AttemptDisarmAttack(EntityUid user, MeleeWeaponComponent weapon, EntityUid target)
+ public void AttemptDisarmAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target)
{
if (!TryComp<TransformComponent>(target, out var targetXform))
return;
- AttemptAttack(user, weapon, new DisarmAttackEvent(target, targetXform.Coordinates), null);
+ AttemptAttack(user, weaponUid, weapon, new DisarmAttackEvent(target, targetXform.Coordinates), null);
}
/// <summary>
/// Called when a windup is finished and an attack is tried.
/// </summary>
- private void AttemptAttack(EntityUid user, MeleeWeaponComponent weapon, AttackEvent attack, ICommonSession? session)
+ private void AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, AttackEvent attack, ICommonSession? session)
{
var curTime = Timing.CurTime;
switch (attack)
{
case LightAttackEvent light:
- DoLightAttack(user, light, weapon, session);
+ DoLightAttack(user, light, weaponUid, weapon, session);
animation = weapon.ClickAnimation;
break;
case DisarmAttackEvent disarm:
- if (!DoDisarm(user, disarm, weapon, session))
+ if (!DoDisarm(user, disarm, weaponUid, weapon, session))
return;
animation = weapon.ClickAnimation;
break;
case HeavyAttackEvent heavy:
- DoHeavyAttack(user, heavy, weapon, session);
+ DoHeavyAttack(user, heavy, weaponUid, weapon, session);
animation = weapon.WideAnimation;
break;
default:
protected abstract bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session);
- protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
+ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
{
+ var damage = component.Damage * GetModifier(component, true);
+
// Can't attack yourself
- // Not in LOS.
+ // For consistency with wide attacks stuff needs damageable.
if (user == ev.Target ||
- ev.Target == null ||
Deleted(ev.Target) ||
- // For consistency with wide attacks stuff needs damageable.
!HasComp<DamageableComponent>(ev.Target) ||
- !TryComp<TransformComponent>(ev.Target, out var targetXform))
- {
- Audio.PlayPredicted(component.SwingSound, component.Owner, user);
- return;
- }
-
- if (!InRange(user, ev.Target.Value, component.Range, session))
+ !TryComp<TransformComponent>(ev.Target, out var targetXform) ||
+ // Not in LOS.
+ !InRange(user, ev.Target.Value, component.Range, session))
{
- Audio.PlayPredicted(component.SwingSound, component.Owner, user);
+ // Leave IsHit set to true, because the only time it's set to false
+ // is when a melee weapon is examined. Misses are inferred from an
+ // empty HitEntities.
+ // TODO: This needs fixing
+ var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, damage);
+ RaiseLocalEvent(meleeUid, missEvent);
+ Audio.PlayPredicted(component.SwingSound, meleeUid, user);
return;
}
- var damage = component.Damage * GetModifier(component, true);
-
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
// Raise event before doing damage so we can cancel damage if the event is handled
var hitEvent = new MeleeHitEvent(new List<EntityUid> { ev.Target.Value }, user, damage);
- RaiseLocalEvent(component.Owner, hitEvent);
+ RaiseLocalEvent(meleeUid, hitEvent);
if (hitEvent.Handled)
return;
Interaction.DoContactInteraction(user, ev.Target);
// For stuff that cares about it being attacked.
- RaiseLocalEvent(ev.Target.Value, new AttackedEvent(component.Owner, user, targetXform.Coordinates));
+ RaiseLocalEvent(ev.Target.Value, new AttackedEvent(meleeUid, user, targetXform.Coordinates));
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage, hitEvent.ModifiersList);
var damageResult = Damageable.TryChangeDamage(ev.Target, modifiedDamage, origin:user);
_stamina.TakeStaminaDamage(ev.Target.Value, (bluntDamage * component.BluntStaminaDamageFactor).Float(), source:user, with:(component.Owner == user ? null : component.Owner));
}
- if (component.Owner == user)
+ if (meleeUid == user)
{
AdminLogger.Add(LogType.MeleeHit,
$"{ToPrettyString(user):user} melee attacked {ToPrettyString(ev.Target.Value):target} using their hands and dealt {damageResult.Total:damage} damage");
{
if (hitEvent.HitSoundOverride != null)
{
- Audio.PlayPredicted(hitEvent.HitSoundOverride, component.Owner, user);
+ Audio.PlayPredicted(hitEvent.HitSoundOverride, meleeUid, user);
}
else
{
- Audio.PlayPredicted(component.NoDamageSound, component.Owner, user);
+ Audio.PlayPredicted(component.NoDamageSound, meleeUid, user);
}
}
protected abstract void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform);
- protected virtual void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
+ protected virtual void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
{
// TODO: This is copy-paste as fuck with DoPreciseAttack
if (!TryComp<TransformComponent>(user, out var userXform))
return;
}
- var userPos = userXform.WorldPosition;
+ var userPos = _transform.GetWorldPosition(userXform);
var direction = targetMap.Position - userPos;
var distance = Math.Min(component.Range, direction.Length);
+ var damage = component.Damage * GetModifier(component, false);
+
// This should really be improved. GetEntitiesInArc uses pos instead of bounding boxes.
var entities = ArcRayCast(userPos, direction.ToWorldAngle(), component.Angle, distance, userXform.MapID, user);
if (entities.Count == 0)
{
- Audio.PlayPredicted(component.SwingSound, component.Owner, user);
+ var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, damage);
+ RaiseLocalEvent(meleeUid, missEvent);
+
+ Audio.PlayPredicted(component.SwingSound, meleeUid, user);
return;
}
targets.Add(entity);
}
- var damage = component.Damage * GetModifier(component, false);
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
// Raise event before doing damage so we can cancel damage if the event is handled
var hitEvent = new MeleeHitEvent(targets, user, damage);
- RaiseLocalEvent(component.Owner, hitEvent);
+ RaiseLocalEvent(meleeUid, hitEvent);
if (hitEvent.Handled)
return;
// somewhat messy scuffle. See also, light attacks.
Interaction.DoContactInteraction(user, target);
- RaiseLocalEvent(target, new AttackedEvent(component.Owner, user, Transform(target).Coordinates));
+ RaiseLocalEvent(target, new AttackedEvent(meleeUid, user, Transform(target).Coordinates));
}
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage, hitEvent.ModifiersList);
foreach (var entity in targets)
{
- RaiseLocalEvent(entity, new AttackedEvent(component.Owner, user, ev.Coordinates));
+ RaiseLocalEvent(entity, new AttackedEvent(meleeUid, user, ev.Coordinates));
var damageResult = Damageable.TryChangeDamage(entity, modifiedDamage, origin:user);
{
appliedDamage += damageResult;
- if (component.Owner == user)
+ if (meleeUid == user)
{
AdminLogger.Add(LogType.MeleeHit,
$"{ToPrettyString(user):user} melee attacked {ToPrettyString(entity):target} using their hands and dealt {damageResult.Total:damage} damage");
{
if (hitEvent.HitSoundOverride != null)
{
- Audio.PlayPredicted(hitEvent.HitSoundOverride, component.Owner, user);
+ Audio.PlayPredicted(hitEvent.HitSoundOverride, meleeUid, user);
}
else
{
- Audio.PlayPredicted(component.NoDamageSound, component.Owner, user);
+ Audio.PlayPredicted(component.NoDamageSound, meleeUid, user);
}
}
}
return highestDamageType;
}
- protected virtual bool DoDisarm(EntityUid user, DisarmAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
+ protected virtual bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
{
if (Deleted(ev.Target) ||
user == ev.Target)
return false;
// Play a sound to give instant feedback; same with playing the animations
- Audio.PlayPredicted(component.SwingSound, component.Owner, user);
+ Audio.PlayPredicted(component.SwingSound, meleeUid, user);
return true;
}