Files
tbd-station-14/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
Jezithyr eeb5b17b34 Mobstate Refactor (#13389)
Refactors mobstate and moves mob health thresholds to their own component

Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2023-01-13 16:57:10 -08:00

265 lines
9.0 KiB
C#

using Content.Client.CombatMode;
using Content.Client.Gameplay;
using Content.Client.Hands;
using Content.Shared.Mobs.Components;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.StatusEffect;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Weapons.Melee;
public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
private const string MeleeLungeKey = "melee-lunge";
public override void Initialize()
{
base.Initialize();
InitializeEffect();
_overlayManager.AddOverlay(new MeleeWindupOverlay(EntityManager, _timing, _player, _protoManager));
SubscribeAllEvent<DamageEffectEvent>(OnDamageEffect);
SubscribeNetworkEvent<MeleeLungeEvent>(OnMeleeLunge);
}
public override void Shutdown()
{
base.Shutdown();
_overlayManager.RemoveOverlay<MeleeWindupOverlay>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (!Timing.IsFirstTimePredicted)
return;
var entityNull = _player.LocalPlayer?.ControlledEntity;
if (entityNull == null)
return;
var entity = entityNull.Value;
var weapon = GetWeapon(entity);
if (weapon == null)
return;
if (!CombatMode.IsInCombatMode(entity) || !Blocker.CanAttack(entity))
{
weapon.Attacking = false;
if (weapon.WindUpStart != null)
{
EntityManager.RaisePredictiveEvent(new StopHeavyAttackEvent(weapon.Owner));
}
return;
}
// TODO using targeted actions while combat mode is enabled should NOT trigger attacks.
var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary);
var currentTime = Timing.CurTime;
// Heavy attack.
if (altDown == BoundKeyState.Down)
{
// We did the click to end the attack but haven't pulled the key up.
if (weapon.Attacking)
{
return;
}
// If it's an unarmed attack then do a disarm
if (weapon.Owner == entity)
{
EntityUid? target = null;
var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
EntityCoordinates coordinates;
if (MapManager.TryFindGridAt(mousePos, out var grid))
{
coordinates = EntityCoordinates.FromMap(grid.Owner, mousePos, EntityManager);
}
else
{
coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, EntityManager);
}
if (_stateManager.CurrentState is GameplayStateBase screen)
{
target = screen.GetClickedEntity(mousePos);
}
EntityManager.RaisePredictiveEvent(new DisarmAttackEvent(target, coordinates));
return;
}
// Otherwise do heavy attack if it's a weapon.
// Start a windup
if (weapon.WindUpStart == null)
{
EntityManager.RaisePredictiveEvent(new StartHeavyAttackEvent(weapon.Owner));
weapon.WindUpStart = currentTime;
}
// Try to do a heavy attack.
if (useDown == BoundKeyState.Down)
{
var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
EntityCoordinates coordinates;
// Bro why would I want a ternary here
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (MapManager.TryFindGridAt(mousePos, out var grid))
{
coordinates = EntityCoordinates.FromMap(grid.Owner, mousePos, EntityManager);
}
else
{
coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, EntityManager);
}
EntityManager.RaisePredictiveEvent(new HeavyAttackEvent(weapon.Owner, coordinates));
}
return;
}
if (weapon.WindUpStart != null)
{
EntityManager.RaisePredictiveEvent(new StopHeavyAttackEvent(weapon.Owner));
}
// Light attack
if (useDown == BoundKeyState.Down)
{
if (weapon.Attacking || weapon.NextAttack > Timing.CurTime)
{
return;
}
var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
var attackerPos = Transform(entity).MapPosition;
if (mousePos.MapId != attackerPos.MapId ||
(attackerPos.Position - mousePos.Position).Length > weapon.Range)
{
return;
}
EntityCoordinates coordinates;
// Bro why would I want a ternary here
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (MapManager.TryFindGridAt(mousePos, out var grid))
{
coordinates = EntityCoordinates.FromMap(grid.Owner, mousePos, EntityManager);
}
else
{
coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, EntityManager);
}
EntityUid? target = null;
// TODO: UI Refactor update I assume
if (_stateManager.CurrentState is GameplayStateBase screen)
{
target = screen.GetClickedEntity(mousePos);
}
RaisePredictiveEvent(new LightAttackEvent(target, weapon.Owner, coordinates));
return;
}
if (weapon.Attacking)
{
RaisePredictiveEvent(new StopAttackEvent(weapon.Owner));
}
}
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.
if (_timing.IsFirstTimePredicted)
RaiseLocalEvent(new DamageEffectEvent(Color.Red, targets));
}
protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, MeleeWeaponComponent component, ICommonSession? session)
{
if (!base.DoDisarm(user, ev, component, session))
return false;
if (!TryComp<CombatModeComponent>(user, out var combatMode) ||
combatMode.CanDisarm != true)
{
return false;
}
// They need to either have hands...
if (!HasComp<HandsComponent>(ev.Target!.Value))
{
// or just be able to be shoved over.
if (TryComp<StatusEffectsComponent>(ev.Target!.Value, out var status) && status.AllowedEffects.Contains("KnockedDown"))
return true;
if (Timing.IsFirstTimePredicted && HasComp<MobStateComponent>(ev.Target.Value))
PopupSystem.PopupEntity(Loc.GetString("disarm-action-disarmable", ("targetName", ev.Target.Value)), ev.Target.Value);
return false;
}
return true;
}
protected override void Popup(string message, EntityUid? uid, EntityUid? user)
{
if (!Timing.IsFirstTimePredicted || uid == null)
return;
PopupSystem.PopupEntity(message, uid.Value);
}
private void OnMeleeLunge(MeleeLungeEvent ev)
{
// Entity might not have been sent by PVS.
if (Exists(ev.Entity))
DoLunge(ev.Entity, ev.Angle, ev.LocalPos, ev.Animation);
}
}