| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- using System.Linq;
- using Content.Client.Gameplay;
- using Content.Shared.CombatMode;
- using Content.Shared.Effects;
- using Content.Shared.Hands.Components;
- using Content.Shared.Mobs.Components;
- using Content.Shared.StatusEffect;
- using Content.Shared.Weapons.Melee;
- using Content.Shared.Weapons.Melee.Components;
- using Content.Shared.Weapons.Melee.Events;
- using Content.Shared.Weapons.Ranged.Components;
- using Robust.Client.GameObjects;
- using Robust.Client.Graphics;
- using Robust.Client.Input;
- using Robust.Client.Player;
- using Robust.Client.State;
- using Robust.Shared.Input;
- using Robust.Shared.Map;
- using Robust.Shared.Player;
- namespace Content.Client.Weapons.Melee;
- public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
- {
- [Dependency] private readonly IEyeManager _eyeManager = 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!;
- [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
- [Dependency] private readonly MapSystem _map = default!;
- private EntityQuery<TransformComponent> _xformQuery;
- private const string MeleeLungeKey = "melee-lunge";
- public override void Initialize()
- {
- base.Initialize();
- _xformQuery = GetEntityQuery<TransformComponent>();
- SubscribeNetworkEvent<MeleeLungeEvent>(OnMeleeLunge);
- UpdatesOutsidePrediction = true;
- }
- public override void FrameUpdate(float frameTime)
- {
- base.FrameUpdate(frameTime);
- UpdateEffects();
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- if (!Timing.IsFirstTimePredicted)
- return;
- var entityNull = _player.LocalEntity;
- if (entityNull == null)
- return;
- var entity = entityNull.Value;
- if (!TryGetWeapon(entity, out var weaponUid, out var weapon))
- return;
- if (!CombatMode.IsInCombatMode(entity) || !Blocker.CanAttack(entity, weapon: (weaponUid, weapon)))
- {
- weapon.Attacking = false;
- return;
- }
- var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
- var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
- if (weapon.AutoAttack || useDown != BoundKeyState.Down && altDown != BoundKeyState.Down)
- {
- if (weapon.Attacking)
- {
- RaisePredictiveEvent(new StopAttackEvent(GetNetEntity(weaponUid)));
- }
- }
- if (weapon.Attacking || weapon.NextAttack > Timing.CurTime)
- {
- return;
- }
- // TODO using targeted actions while combat mode is enabled should NOT trigger attacks.
- var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
- if (mousePos.MapId == MapId.Nullspace)
- {
- return;
- }
- EntityCoordinates coordinates;
- if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _))
- {
- coordinates = TransformSystem.ToCoordinates(gridUid, mousePos);
- }
- else
- {
- coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos);
- }
-
- // If the gun has AltFireComponent, it can be used to attack.
- if (TryComp<GunComponent>(weaponUid, out var gun) && gun.UseKey)
- {
- if (!TryComp<AltFireMeleeComponent>(weaponUid, out var altFireComponent) || altDown != BoundKeyState.Down)
- return;
-
- switch(altFireComponent.AttackType)
- {
- case AltFireAttackType.Light:
- ClientLightAttack(entity, mousePos, coordinates, weaponUid, weapon);
- break;
-
- case AltFireAttackType.Heavy:
- ClientHeavyAttack(entity, coordinates, weaponUid, weapon);
- break;
-
- case AltFireAttackType.Disarm:
- ClientDisarm(entity, mousePos, coordinates);
- break;
- }
-
- return;
- }
- // Heavy attack.
- if (altDown == BoundKeyState.Down)
- {
- // If it's an unarmed attack then do a disarm
- if (weapon.AltDisarm && weaponUid == entity)
- {
- ClientDisarm(entity, mousePos, coordinates);
- return;
- }
- ClientHeavyAttack(entity, coordinates, weaponUid, weapon);
- return;
- }
- // Light attack
- if (useDown == BoundKeyState.Down)
- ClientLightAttack(entity, mousePos, coordinates, weaponUid, weapon);
- }
- 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, overlapCheck: false);
- }
- protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
- {
- // Server never sends the event to us for predictiveeevent.
- _color.RaiseEffect(Color.Red, targets, Filter.Local());
- }
- protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
- {
- if (!base.DoDisarm(user, ev, meleeUid, component, session))
- return false;
- if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
- combatMode.CanDisarm != true)
- {
- return false;
- }
- var target = GetEntity(ev.Target);
- // They need to either have hands...
- if (!HasComp<HandsComponent>(target!.Value))
- {
- // or just be able to be shoved over.
- if (TryComp<StatusEffectsComponent>(target, out var status) && status.AllowedEffects.Contains("KnockedDown"))
- return true;
- if (Timing.IsFirstTimePredicted && HasComp<MobStateComponent>(target.Value))
- PopupSystem.PopupEntity(Loc.GetString("disarm-action-disarmable", ("targetName", target.Value)), target.Value);
- return false;
- }
- return true;
- }
- /// <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.
- if (!_xformQuery.TryGetComponent(user, out var userXform) ||
- !Timing.IsFirstTimePredicted)
- {
- return;
- }
- var targetMap = TransformSystem.ToMapCoordinates(coordinates);
- if (targetMap.MapId != userXform.MapID)
- return;
- var userPos = TransformSystem.GetWorldPosition(userXform);
- var direction = targetMap.Position - userPos;
- var distance = MathF.Min(component.Range, direction.Length());
- // This should really be improved. GetEntitiesInArc uses pos instead of bounding boxes.
- // Server will validate it with InRangeUnobstructed.
- var entities = GetNetEntityList(ArcRayCast(userPos, direction.ToWorldAngle(), component.Angle, distance, userXform.MapID, user).ToList());
- RaisePredictiveEvent(new HeavyAttackEvent(GetNetEntity(meleeUid), entities.GetRange(0, Math.Min(MaxTargets, entities.Count)), GetNetCoordinates(coordinates)));
- }
-
- private void ClientDisarm(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates)
- {
- EntityUid? target = null;
- if (_stateManager.CurrentState is GameplayStateBase screen)
- target = screen.GetClickedEntity(mousePos);
- RaisePredictiveEvent(new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(coordinates)));
- }
-
- private void ClientLightAttack(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates, EntityUid weaponUid, MeleeWeaponComponent meleeComponent)
- {
- var attackerPos = TransformSystem.GetMapCoordinates(attacker);
- if (mousePos.MapId != attackerPos.MapId || (attackerPos.Position - mousePos.Position).Length() > meleeComponent.Range)
- return;
- EntityUid? target = null;
- if (_stateManager.CurrentState is GameplayStateBase screen)
- target = screen.GetClickedEntity(mousePos);
- // Don't light-attack if interaction will be handling this instead
- if (Interaction.CombatModeCanHandInteract(attacker, target))
- return;
- RaisePredictiveEvent(new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(coordinates)));
- }
- private void OnMeleeLunge(MeleeLungeEvent ev)
- {
- var ent = GetEntity(ev.Entity);
- var entWeapon = GetEntity(ev.Weapon);
- // Entity might not have been sent by PVS.
- if (Exists(ent) && Exists(entWeapon))
- DoLunge(ent, entWeapon, ev.Angle, ev.LocalPos, ev.Animation);
- }
- }
|