From 2ec493e2af445cb78c8f2e10f43c97fae122d43f Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 26 Jan 2020 03:38:51 +0100 Subject: [PATCH] Combat mode improvements. You now need to hold the mouse for 0.15 seconds in combat mode to actually do an attack. Otherwise it's regular item interaction (for now). --- .../EntitySystems/CombatModeSystem.cs | 137 ++++++++++++++++++ .../EntitySystems/RangedWeaponSystem.cs | 7 +- Content.Client/Input/ContentContexts.cs | 2 + .../EntitySystems/Click/InteractionSystem.cs | 47 ++++-- Content.Shared/Input/ContentKeyFunctions.cs | 2 + Resources/keybinds.yml | 2 +- 6 files changed, 182 insertions(+), 15 deletions(-) diff --git a/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs b/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs index 850e61f477..5f8d4a25df 100644 --- a/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/CombatModeSystem.cs @@ -1,23 +1,62 @@ +using Content.Client.GameObjects.Components.Mobs; using Content.Client.UserInterface; +using Content.Client.Utility; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystemMessages; +using Content.Shared.Input; +using Robust.Client.GameObjects.EntitySystems; +using Robust.Client.Graphics.Drawing; +using Robust.Client.Graphics.Overlays; +using Robust.Client.Interfaces.Graphics.Overlays; +using Robust.Client.Interfaces.Input; +using Robust.Client.Player; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Input; using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Players; +using static Content.Client.StaticIoC; namespace Content.Client.GameObjects.EntitySystems { public sealed class CombatModeSystem : EntitySystem { + private const float AttackTimeThreshold = 0.15f; + #pragma warning disable 649 [Dependency] private readonly IGameHud _gameHud; + [Dependency] private readonly IPlayerManager _playerManager; + [Dependency] private readonly IInputManager _inputManager; + [Dependency] private readonly IOverlayManager _overlayManager; #pragma warning restore 649 + private InputSystem _inputSystem; + + public bool UseOrAttackIsDown { get; private set; } + private float _timeHeld; + public override void Initialize() { base.Initialize(); _gameHud.OnCombatModeChanged = OnCombatModeChanged; _gameHud.OnTargetingZoneChanged = OnTargetingZoneChanged; + + _inputSystem = EntitySystemManager.GetEntitySystem(); + _inputSystem.BindMap.BindFunction(ContentKeyFunctions.UseOrAttack, new InputHandler(this)); + + _overlayManager.AddOverlay(new CombatModeOverlay(this)); + } + + private bool IsInCombatMode() + { + var entity = _playerManager.LocalPlayer.ControlledEntity; + if (entity == null || !entity.TryGetComponent(out CombatModeComponent combatMode)) + { + return false; + } + + return combatMode.IsInCombatMode; } private void OnTargetingZoneChanged(TargetingZone obj) @@ -28,6 +67,104 @@ namespace Content.Client.GameObjects.EntitySystems private void OnCombatModeChanged(bool obj) { RaiseNetworkEvent(new CombatModeSystemMessages.SetCombatModeActiveMessage(obj)); + + // Just in case. + UseOrAttackIsDown = false; + } + + private bool HandleInputMessage(ICommonSession session, InputCmdMessage message) + { + if (!(message is FullInputCmdMessage msg)) + return false; + + void SendMsg(BoundKeyFunction function, BoundKeyState state) + { + var functionId = _inputManager.NetworkBindMap.KeyFunctionID(function); + + var sendMsg = new FullInputCmdMessage(msg.Tick, functionId, state, + msg.Coordinates, msg.ScreenCoordinates, msg.Uid); + _inputSystem.HandleInputCommand(session, function, sendMsg); + } + + // If we are not in combat mode, relay it as a regular Use instead. + if (!IsInCombatMode()) + { + SendMsg(EngineKeyFunctions.Use, msg.State); + return true; + } + + if (msg.State == BoundKeyState.Down) + { + UseOrAttackIsDown = true; + _timeHeld = 0; + return true; + } + + // Up. + if (_timeHeld >= AttackTimeThreshold) + { + // Attack. + SendMsg(ContentKeyFunctions.Attack, BoundKeyState.Down); + SendMsg(ContentKeyFunctions.Attack, BoundKeyState.Up); + } + else + { + // Use. + SendMsg(EngineKeyFunctions.Use, BoundKeyState.Down); + SendMsg(EngineKeyFunctions.Use, BoundKeyState.Up); + } + + UseOrAttackIsDown = false; + + return true; + } + + public override void FrameUpdate(float frameTime) + { + if (UseOrAttackIsDown) + { + _timeHeld += frameTime; + } + } + + // Custom input handler type so we get the ENTIRE InputCmdMessage. + private sealed class InputHandler : InputCmdHandler + { + private readonly CombatModeSystem _combatModeSystem; + + public InputHandler(CombatModeSystem combatModeSystem) + { + _combatModeSystem = combatModeSystem; + } + + public override bool HandleCmdMessage(ICommonSession session, InputCmdMessage message) + { + return _combatModeSystem.HandleInputMessage(session, message); + } + } + + private sealed class CombatModeOverlay : Overlay + { + private readonly CombatModeSystem _system; + + public CombatModeOverlay(CombatModeSystem system) : base(nameof(CombatModeOverlay)) + { + _system = system; + } + + protected override void Draw(DrawingHandleBase handle) + { + var screenHandle = (DrawingHandleScreen) handle; + + var mousePos = IoCManager.Resolve().MouseScreenPosition; + + if (_system.UseOrAttackIsDown && _system._timeHeld > AttackTimeThreshold) + { + var tex = ResC.GetTexture($"/Textures/Objects/Tools/toolbox_r.png"); + + screenHandle.DrawTextureRect(tex, UIBox2.FromDimensions(mousePos, tex.Size * 2)); + } + } } } } diff --git a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs index d54056a00d..d34001cb27 100644 --- a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs @@ -1,5 +1,6 @@ using Content.Client.GameObjects.Components.Weapons.Ranged; using Content.Client.Interfaces.GameObjects; +using Content.Shared.Input; using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Input; @@ -20,6 +21,7 @@ namespace Content.Client.GameObjects.EntitySystems #pragma warning restore 649 private InputSystem _inputSystem; + private CombatModeSystem _combatModeSystem; private bool _isFirstShot; private bool _blocked; @@ -29,6 +31,7 @@ namespace Content.Client.GameObjects.EntitySystems IoCManager.InjectDependencies(this); _inputSystem = EntitySystemManager.GetEntitySystem(); + _combatModeSystem = EntitySystemManager.GetEntitySystem(); } public override void Update(float frameTime) @@ -36,8 +39,8 @@ namespace Content.Client.GameObjects.EntitySystems base.Update(frameTime); var canFireSemi = _isFirstShot; - var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use); - if (state != BoundKeyState.Down) + var state = _inputSystem.CmdStates.GetState(ContentKeyFunctions.Attack); + if (!_combatModeSystem.UseOrAttackIsDown && state != BoundKeyState.Down) { _isFirstShot = true; _blocked = false; diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index bf8a3749db..de4be90abe 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -15,6 +15,7 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.FocusChat); common.AddFunction(ContentKeyFunctions.ExamineEntity); common.AddFunction(ContentKeyFunctions.OpenTutorial); + common.AddFunction(ContentKeyFunctions.UseOrAttack); var human = contexts.GetContext("human"); human.AddFunction(ContentKeyFunctions.SwapHands); @@ -28,6 +29,7 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.OpenInventoryMenu); human.AddFunction(ContentKeyFunctions.MouseMiddle); human.AddFunction(ContentKeyFunctions.ToggleCombatMode); + human.AddFunction(ContentKeyFunctions.Attack); var ghost = contexts.New("ghost", "common"); ghost.AddFunction(EngineKeyFunctions.MoveUp); diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 6ec19ed9b3..b1c647f951 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -257,6 +257,8 @@ namespace Content.Server.GameObjects.EntitySystems var inputSys = EntitySystemManager.GetEntitySystem(); inputSys.BindMap.BindFunction(EngineKeyFunctions.Use, new PointerInputCmdHandler(HandleUseItemInHand)); + inputSys.BindMap.BindFunction(ContentKeyFunctions.Attack, + new PointerInputCmdHandler(HandleAttack)); inputSys.BindMap.BindFunction(ContentKeyFunctions.ActivateItemInWorld, new PointerInputCmdHandler(HandleActivateItemInWorld)); } @@ -313,6 +315,37 @@ namespace Content.Server.GameObjects.EntitySystems activateComp.Activate(new ActivateEventArgs {User = user}); } + private bool HandleAttack(ICommonSession session, GridCoordinates coords, EntityUid uid) + { + // client sanitization + if (!_mapManager.GridExists(coords.GridID)) + { + Logger.InfoS("system.interaction", $"Invalid Coordinates: client={session}, coords={coords}"); + return true; + } + + if (uid.IsClientSide()) + { + Logger.WarningS("system.interaction", + $"Client sent attack with client-side entity. Session={session}, Uid={uid}"); + return true; + } + + var userEntity = ((IPlayerSession) session).AttachedEntity; + + if (userEntity == null || !userEntity.IsValid()) + { + return true; + } + + if (userEntity.TryGetComponent(out CombatModeComponent combatMode) && combatMode.IsInCombatMode) + { + DoAttack(userEntity, coords); + } + + return true; + } + private bool HandleUseItemInHand(ICommonSession session, GridCoordinates coords, EntityUid uid) { // client sanitization @@ -336,14 +369,7 @@ namespace Content.Server.GameObjects.EntitySystems return true; } - if (userEntity.TryGetComponent(out CombatModeComponent combatMode) && combatMode.IsInCombatMode) - { - DoAttack(userEntity, coords); - } - else - { - UserInteraction(userEntity, coords, uid); - } + UserInteraction(userEntity, coords, uid); return true; } @@ -561,10 +587,9 @@ namespace Content.Server.GameObjects.EntitySystems /// public void UseInteraction(IEntity user, IEntity used) { - if (used.TryGetComponent(out var delayComponent)) { - if(delayComponent.ActiveDelay) + if (delayComponent.ActiveDelay) return; else delayComponent.BeginDelay(); @@ -600,7 +625,6 @@ namespace Content.Server.GameObjects.EntitySystems ThrownInteraction(user, item); return true; - } /// @@ -657,7 +681,6 @@ namespace Content.Server.GameObjects.EntitySystems DroppedInteraction(user, item); return true; - } /// diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 501e433c97..9704260982 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -5,6 +5,8 @@ namespace Content.Shared.Input [KeyFunctions] public static class ContentKeyFunctions { + public static readonly BoundKeyFunction UseOrAttack = "UseOrAttack"; + public static readonly BoundKeyFunction Attack = "Attack"; public static readonly BoundKeyFunction ActivateItemInHand = "ActivateItemInHand"; public static readonly BoundKeyFunction ActivateItemInWorld = "ActivateItemInWorld"; // default action on world entity public static readonly BoundKeyFunction Drop = "Drop"; diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 1466aabf36..92176e9469 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -1,6 +1,6 @@ version: 1 # Not used right now, whatever. binds: -- function: Use +- function: UseOrAttack type: state key: MouseLeft canFocus: true