2023-05-02 05:07:17 +10:00
|
|
|
using System.Linq;
|
2022-09-29 15:51:59 +10:00
|
|
|
using Content.Client.Gameplay;
|
2023-04-08 13:16:48 -07:00
|
|
|
using Content.Shared.CombatMode;
|
2023-08-01 19:02:54 +03:00
|
|
|
using Content.Shared.Effects;
|
2023-04-07 11:21:12 -07:00
|
|
|
using Content.Shared.Hands.Components;
|
2023-01-13 16:57:10 -08:00
|
|
|
using Content.Shared.Mobs.Components;
|
2023-04-07 11:21:12 -07:00
|
|
|
using Content.Shared.StatusEffect;
|
2021-06-09 22:19:39 +02:00
|
|
|
using Content.Shared.Weapons.Melee;
|
2022-09-29 15:51:59 +10:00
|
|
|
using Content.Shared.Weapons.Melee.Events;
|
2023-05-14 13:15:18 +10:00
|
|
|
using Content.Shared.Weapons.Ranged.Components;
|
2020-08-31 20:54:33 +02:00
|
|
|
using Robust.Client.GameObjects;
|
2022-09-29 15:51:59 +10:00
|
|
|
using Robust.Client.Graphics;
|
|
|
|
|
using Robust.Client.Input;
|
|
|
|
|
using Robust.Client.Player;
|
|
|
|
|
using Robust.Client.State;
|
|
|
|
|
using Robust.Shared.Input;
|
|
|
|
|
using Robust.Shared.Map;
|
2023-08-11 03:44:52 +10:00
|
|
|
using Robust.Shared.Player;
|
2022-10-17 15:54:31 +11:00
|
|
|
using Robust.Shared.Players;
|
2019-09-26 22:32:32 +02:00
|
|
|
using Robust.Shared.Prototypes;
|
2021-02-11 01:13:03 -08:00
|
|
|
using Robust.Shared.Timing;
|
2019-09-26 22:32:32 +02:00
|
|
|
|
2022-09-29 15:51:59 +10:00
|
|
|
namespace Content.Client.Weapons.Melee;
|
|
|
|
|
|
|
|
|
|
public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
2019-09-26 22:32:32 +02:00
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
|
|
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
|
|
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
|
|
|
|
[Dependency] private readonly IPlayerManager _player = default!;
|
|
|
|
|
[Dependency] private readonly IStateManager _stateManager = default!;
|
|
|
|
|
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
|
|
|
|
[Dependency] private readonly InputSystem _inputSystem = default!;
|
2023-08-11 03:44:52 +10:00
|
|
|
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
2022-09-29 15:51:59 +10:00
|
|
|
|
2023-08-06 12:55:38 +10:00
|
|
|
private EntityQuery<TransformComponent> _xformQuery;
|
|
|
|
|
|
2022-09-29 15:51:59 +10:00
|
|
|
private const string MeleeLungeKey = "melee-lunge";
|
|
|
|
|
|
|
|
|
|
public override void Initialize()
|
|
|
|
|
{
|
|
|
|
|
base.Initialize();
|
2023-08-06 12:55:38 +10:00
|
|
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
2022-09-29 15:51:59 +10:00
|
|
|
SubscribeNetworkEvent<MeleeLungeEvent>(OnMeleeLunge);
|
2023-07-24 21:59:31 +12:00
|
|
|
UpdatesOutsidePrediction = true;
|
2022-09-29 15:51:59 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Update(float frameTime)
|
2019-09-26 22:32:32 +02:00
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
base.Update(frameTime);
|
2019-09-26 22:32:32 +02:00
|
|
|
|
2022-09-29 15:51:59 +10:00
|
|
|
if (!Timing.IsFirstTimePredicted)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var entityNull = _player.LocalPlayer?.ControlledEntity;
|
|
|
|
|
|
|
|
|
|
if (entityNull == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var entity = entityNull.Value;
|
|
|
|
|
|
2023-03-12 15:56:05 +11:00
|
|
|
if (!TryGetWeapon(entity, out var weaponUid, out var weapon))
|
2022-09-29 15:51:59 +10:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!CombatMode.IsInCombatMode(entity) || !Blocker.CanAttack(entity))
|
2019-09-26 22:32:32 +02:00
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
weapon.Attacking = false;
|
2023-08-06 12:55:38 +10:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
|
|
|
|
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
|
|
|
|
|
|
|
|
|
|
if (useDown != BoundKeyState.Down && altDown != BoundKeyState.Down)
|
|
|
|
|
{
|
|
|
|
|
if (weapon.Attacking)
|
2022-09-29 15:51:59 +10:00
|
|
|
{
|
2023-08-06 12:55:38 +10:00
|
|
|
RaisePredictiveEvent(new StopAttackEvent(weaponUid));
|
2022-09-29 15:51:59 +10:00
|
|
|
}
|
2023-08-06 12:55:38 +10:00
|
|
|
}
|
2022-09-29 15:51:59 +10:00
|
|
|
|
2023-08-06 12:55:38 +10:00
|
|
|
if (weapon.Attacking || weapon.NextAttack > Timing.CurTime)
|
|
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
return;
|
2019-09-26 22:32:32 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-20 01:02:38 +13:00
|
|
|
// TODO using targeted actions while combat mode is enabled should NOT trigger attacks.
|
|
|
|
|
|
2023-08-06 12:55:38 +10:00
|
|
|
// TODO: Need to make alt-fire melee its own component I guess?
|
|
|
|
|
// Melee and guns share a lot in the middle but share virtually nothing at the start and end so
|
|
|
|
|
// it's kinda tricky.
|
|
|
|
|
// I think as long as we make secondaries their own component it's probably fine
|
|
|
|
|
// as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with.
|
2023-08-08 03:05:18 +10:00
|
|
|
if (TryComp<GunComponent>(weaponUid, out var gun) && gun.UseKey)
|
2023-08-06 12:55:38 +10:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-09-29 15:51:59 +10:00
|
|
|
|
2023-08-12 16:43:07 -07:00
|
|
|
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
|
2023-08-06 12:55:38 +10:00
|
|
|
|
|
|
|
|
if (mousePos.MapId == MapId.Nullspace)
|
2019-09-26 22:32:32 +02:00
|
|
|
{
|
2023-08-06 12:55:38 +10:00
|
|
|
return;
|
|
|
|
|
}
|
2023-05-14 13:15:18 +10:00
|
|
|
|
2023-08-06 12:55:38 +10:00
|
|
|
EntityCoordinates coordinates;
|
|
|
|
|
|
|
|
|
|
if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _))
|
|
|
|
|
{
|
|
|
|
|
coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager);
|
|
|
|
|
}
|
2019-09-26 22:32:32 +02:00
|
|
|
|
2023-08-06 12:55:38 +10:00
|
|
|
// Heavy attack.
|
|
|
|
|
if (altDown == BoundKeyState.Down)
|
|
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
// If it's an unarmed attack then do a disarm
|
2023-07-07 18:45:37 +10:00
|
|
|
if (weapon.AltDisarm && weaponUid == entity)
|
2019-09-26 22:32:32 +02:00
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
EntityUid? target = null;
|
|
|
|
|
|
|
|
|
|
if (_stateManager.CurrentState is GameplayStateBase screen)
|
|
|
|
|
{
|
2023-01-05 18:29:27 +13:00
|
|
|
target = screen.GetClickedEntity(mousePos);
|
2022-09-29 15:51:59 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntityManager.RaisePredictiveEvent(new DisarmAttackEvent(target, coordinates));
|
|
|
|
|
return;
|
2019-09-26 22:32:32 +02:00
|
|
|
}
|
2022-09-29 15:51:59 +10:00
|
|
|
|
2023-08-06 12:55:38 +10:00
|
|
|
ClientHeavyAttack(entity, coordinates, weaponUid, weapon);
|
2022-09-29 15:51:59 +10:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Light attack
|
|
|
|
|
if (useDown == BoundKeyState.Down)
|
2019-09-26 22:32:32 +02:00
|
|
|
{
|
2022-10-17 02:54:43 +11:00
|
|
|
var attackerPos = Transform(entity).MapPosition;
|
|
|
|
|
|
|
|
|
|
if (mousePos.MapId != attackerPos.MapId ||
|
2023-07-08 14:08:32 +10:00
|
|
|
(attackerPos.Position - mousePos.Position).Length() > weapon.Range)
|
2022-10-17 02:54:43 +11:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-29 15:51:59 +10:00
|
|
|
EntityUid? target = null;
|
|
|
|
|
|
|
|
|
|
if (_stateManager.CurrentState is GameplayStateBase screen)
|
2021-01-10 20:12:34 +01:00
|
|
|
{
|
2023-01-05 18:29:27 +13:00
|
|
|
target = screen.GetClickedEntity(mousePos);
|
2022-09-29 15:51:59 +10:00
|
|
|
}
|
|
|
|
|
|
2023-03-12 15:56:05 +11:00
|
|
|
RaisePredictiveEvent(new LightAttackEvent(target, weaponUid, coordinates));
|
2022-09-29 15:51:59 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-14 08:33:54 +11:00
|
|
|
protected override bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session)
|
|
|
|
|
{
|
|
|
|
|
var xform = Transform(target);
|
|
|
|
|
var targetCoordinates = xform.Coordinates;
|
|
|
|
|
var targetLocalAngle = xform.LocalRotation;
|
|
|
|
|
|
|
|
|
|
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
|
|
|
|
{
|
|
|
|
|
// Server never sends the event to us for predictiveeevent.
|
2023-08-11 03:44:52 +10:00
|
|
|
_color.RaiseEffect(Color.Red, targets, Filter.Local());
|
2022-11-14 08:33:54 +11:00
|
|
|
}
|
|
|
|
|
|
2023-02-13 07:55:39 -05:00
|
|
|
protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
|
2022-09-29 15:51:59 +10:00
|
|
|
{
|
2023-02-13 07:55:39 -05:00
|
|
|
if (!base.DoDisarm(user, ev, meleeUid, component, session))
|
2022-09-29 15:51:59 +10:00
|
|
|
return false;
|
2019-09-26 22:32:32 +02:00
|
|
|
|
2022-10-15 15:14:07 +11:00
|
|
|
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
|
|
|
|
|
combatMode.CanDisarm != true)
|
|
|
|
|
{
|
2022-09-29 15:51:59 +10:00
|
|
|
return false;
|
2022-10-15 15:14:07 +11:00
|
|
|
}
|
2019-09-26 22:32:32 +02:00
|
|
|
|
2022-11-08 16:10:59 -05:00
|
|
|
// They need to either have hands...
|
2022-09-29 15:51:59 +10:00
|
|
|
if (!HasComp<HandsComponent>(ev.Target!.Value))
|
|
|
|
|
{
|
2022-11-08 16:10:59 -05:00
|
|
|
// or just be able to be shoved over.
|
|
|
|
|
if (TryComp<StatusEffectsComponent>(ev.Target!.Value, out var status) && status.AllowedEffects.Contains("KnockedDown"))
|
|
|
|
|
return true;
|
|
|
|
|
|
2022-09-30 02:46:54 +10:00
|
|
|
if (Timing.IsFirstTimePredicted && HasComp<MobStateComponent>(ev.Target.Value))
|
2022-12-19 10:41:47 +13:00
|
|
|
PopupSystem.PopupEntity(Loc.GetString("disarm-action-disarmable", ("targetName", ev.Target.Value)), ev.Target.Value);
|
2022-09-29 15:51:59 +10:00
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 05:07:17 +10:00
|
|
|
/// <summary>
|
|
|
|
|
/// Raises a heavy attack event with the relevant attacked entities.
|
|
|
|
|
/// This is to avoid lag effecting the client's perspective too much.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void ClientHeavyAttack(EntityUid user, EntityCoordinates coordinates, EntityUid meleeUid, MeleeWeaponComponent component)
|
|
|
|
|
{
|
|
|
|
|
// Only run on first prediction to avoid the potential raycast entities changing.
|
2023-08-06 12:55:38 +10:00
|
|
|
if (!_xformQuery.TryGetComponent(user, out var userXform) ||
|
2023-05-08 17:46:26 +10:00
|
|
|
!Timing.IsFirstTimePredicted)
|
|
|
|
|
{
|
2023-05-02 05:07:17 +10:00
|
|
|
return;
|
2023-05-08 17:46:26 +10:00
|
|
|
}
|
2023-05-02 05:07:17 +10:00
|
|
|
|
2023-05-08 17:46:26 +10:00
|
|
|
var targetMap = coordinates.ToMap(EntityManager, TransformSystem);
|
2023-05-02 05:07:17 +10:00
|
|
|
|
|
|
|
|
if (targetMap.MapId != userXform.MapID)
|
|
|
|
|
return;
|
|
|
|
|
|
2023-05-08 17:46:26 +10:00
|
|
|
var userPos = TransformSystem.GetWorldPosition(userXform);
|
2023-05-02 05:07:17 +10:00
|
|
|
var direction = targetMap.Position - userPos;
|
2023-07-08 14:08:32 +10:00
|
|
|
var distance = MathF.Min(component.Range, direction.Length());
|
2023-05-02 05:07:17 +10:00
|
|
|
|
|
|
|
|
// This should really be improved. GetEntitiesInArc uses pos instead of bounding boxes.
|
|
|
|
|
// Server will validate it with InRangeUnobstructed.
|
2023-05-08 17:46:26 +10:00
|
|
|
var entities = ArcRayCast(userPos, direction.ToWorldAngle(), component.Angle, distance, userXform.MapID, user).ToList();
|
|
|
|
|
RaisePredictiveEvent(new HeavyAttackEvent(meleeUid, entities.GetRange(0, Math.Min(MaxTargets, entities.Count)), coordinates));
|
2023-05-02 05:07:17 +10:00
|
|
|
}
|
|
|
|
|
|
2022-09-29 15:51:59 +10:00
|
|
|
private void OnMeleeLunge(MeleeLungeEvent ev)
|
|
|
|
|
{
|
2022-10-11 12:34:46 +13:00
|
|
|
// Entity might not have been sent by PVS.
|
|
|
|
|
if (Exists(ev.Entity))
|
|
|
|
|
DoLunge(ev.Entity, ev.Angle, ev.LocalPos, ev.Animation);
|
2022-09-29 15:51:59 +10:00
|
|
|
}
|
2019-09-26 22:32:32 +02:00
|
|
|
}
|