public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid?target = null) { // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction. if (!ValidateInteractAndFace(user, coordinates)) { return; } if (!_actionBlockerSystem.CanAttack(user, target)) { return; } if (!wideAttack) { // Check if interacted entity is in the same container, the direct child, or direct parent of the user. if (target != null && !Deleted(target.Value) && !ContainerSystem.IsInSameOrParentContainer(user, target.Value) && !CanAccessViaStorage(user, target.Value)) { Logger.WarningS("system.interaction", $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container"); return; } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. var unobstructed = (target == null) ? InRangeUnobstructed(user, coordinates) : InRangeUnobstructed(user, target.Value); if (!unobstructed) { return; } } // Verify user has a hand, and find what object they are currently holding in their active hand if (TryComp(user, out HandsComponent? hands)) { var item = hands.GetActiveHandItem?.Owner; if (item != null && !Deleted(item.Value)) { if (wideAttack) { var ev = new WideAttackEvent(item.Value, user, coordinates); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{ToPrettyString(user):user} wide attacked with {ToPrettyString(item.Value):used} at {coordinates}"); return; } } else { var ev = new ClickAttackEvent(item.Value, user, coordinates, target); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { if (target != null) { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked with {ToPrettyString(item.Value):used} at {coordinates}"); } return; } } } else if (!wideAttack && target != null && HasComp <SharedItemComponent>(target.Value)) { // We pick up items if our hand is empty, even if we're in combat mode. InteractHand(user, target.Value); return; } } // TODO: Make this saner? // Attempt to do unarmed combat. We don't check for handled just because at this point it doesn't matter. if (wideAttack) { var ev = new WideAttackEvent(user, user, coordinates); RaiseLocalEvent(user, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackUnarmedWide, $"{ToPrettyString(user):user} wide attacked at {coordinates}"); } } else { var ev = new ClickAttackEvent(user, user, coordinates, target); RaiseLocalEvent(user, ev, false); if (ev.Handled) { if (target != null) { _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked at {coordinates}"); } } } }
public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid?target = null) { // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction. if (!ValidateInteractAndFace(user, coordinates)) { return; } // Check general interaction blocking. if (!_actionBlockerSystem.CanInteract(user, target)) { return; } // Check combat-specific action blocking. if (!_actionBlockerSystem.CanAttack(user, target)) { return; } if (!wideAttack) { // Check if interacted entity is in the same container, the direct child, or direct parent of the user. if (target != null && !Deleted(target.Value) && !ContainerSystem.IsInSameOrParentContainer(user, target.Value) && !CanAccessViaStorage(user, target.Value)) { Logger.WarningS("system.interaction", $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container"); return; } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. var unobstructed = (target == null) ? InRangeUnobstructed(user, coordinates) : InRangeUnobstructed(user, target.Value); if (!unobstructed) { return; } } else if (ContainerSystem.IsEntityInContainer(user)) { // No wide attacking while in containers (holos, lockers, etc). // Can't think of a valid case where you would want this. return; } // Verify user has a hand, and find what object they are currently holding in their active hand if (TryComp(user, out HandsComponent? hands)) { var item = hands.ActiveHandEntity; if (!Deleted(item)) { var meleeVee = new MeleeAttackAttemptEvent(); RaiseLocalEvent(item.Value, ref meleeVee, true); if (meleeVee.Cancelled) { return; } if (wideAttack) { var ev = new WideAttackEvent(item.Value, user, coordinates); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { _adminLogger.Add(LogType.AttackArmedWide, LogImpact.Low, $"{ToPrettyString(user):user} wide attacked with {ToPrettyString(item.Value):used} at {coordinates}"); return; } } else { var ev = new ClickAttackEvent(item.Value, user, coordinates, target); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { if (target != null) { _adminLogger.Add(LogType.AttackArmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}"); } else { _adminLogger.Add(LogType.AttackArmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked with {ToPrettyString(item.Value):used} at {coordinates}"); } return; } } } else if (!wideAttack && target != null && HasComp <ItemComponent>(target.Value)) { // We pick up items if our hand is empty, even if we're in combat mode. InteractHand(user, target.Value); return; } } // TODO: Make this saner? // Attempt to do unarmed combat. We don't check for handled just because at this point it doesn't matter. var used = user; if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves) && HasComp <MeleeWeaponComponent>(gloves)) { used = (EntityUid)gloves; } if (wideAttack) { var ev = new WideAttackEvent(used, user, coordinates); RaiseLocalEvent(used, ev, false); if (ev.Handled) { _adminLogger.Add(LogType.AttackUnarmedWide, LogImpact.Low, $"{ToPrettyString(user):user} wide attacked at {coordinates}"); } } else { var ev = new ClickAttackEvent(used, user, coordinates, target); RaiseLocalEvent(used, ev, false); if (ev.Handled) { if (target != null) { _adminLogger.Add(LogType.AttackUnarmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}"); } else { _adminLogger.Add(LogType.AttackUnarmedClick, LogImpact.Low, $"{ToPrettyString(user):user} attacked at {coordinates}"); } } } }