diff --git a/Assets/BossRoom/GameData/Action/Imp/ImpBaseAttack.asset b/Assets/BossRoom/GameData/Action/Imp/ImpBaseAttack.asset index b097bdd16..adafbe9dd 100644 --- a/Assets/BossRoom/GameData/Action/Imp/ImpBaseAttack.asset +++ b/Assets/BossRoom/GameData/Action/Imp/ImpBaseAttack.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47b021843aaa2ca179150668d6c9a1de35b6ef65ceef662ed602260fa7fe967c -size 731 +oid sha256:906480816a53904993391f324b0d2c725d76554819f1401ba092b91de7875d00 +size 864 diff --git a/Assets/BossRoom/Models/Animation Controlers/CharacterSetController.controller b/Assets/BossRoom/Models/Animation Controlers/CharacterSetController.controller index 26bc7adda..86477ba4f 100644 --- a/Assets/BossRoom/Models/Animation Controlers/CharacterSetController.controller +++ b/Assets/BossRoom/Models/Animation Controlers/CharacterSetController.controller @@ -754,6 +754,33 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1102 &-5093654682345616202 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AnticipateMove + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 3599037512052606113} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: -3419257869308726280, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: --- !u!1102 &-4482486829903005221 AnimatorState: serializedVersion: 6 @@ -954,6 +981,7 @@ AnimatorState: - {fileID: -1751189744513987592} - {fileID: -3322349416516825559} - {fileID: 5808351605165405389} + - {fileID: 5286344766070818967} m_StateMachineBehaviours: [] m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 @@ -1316,7 +1344,7 @@ AnimatorState: m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: NoAttack + m_Name: Nothing m_Speed: 1 m_CycleOffset: 0 m_Transitions: @@ -1401,6 +1429,9 @@ AnimatorStateMachine: - serializedVersion: 1 m_State: {fileID: 7103618770137260043} m_Position: {x: -350, y: 330, z: 0} + - serializedVersion: 1 + m_State: {fileID: -5093654682345616202} + m_Position: {x: 379.43, y: 329.91626, z: 0} m_ChildStateMachines: [] m_AnyStateTransitions: - {fileID: 6300214233644051234} @@ -1490,6 +1521,28 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1101 &-97121490881636740 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -3516404760485336223} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.62071574 + m_TransitionOffset: 0.00000004339141 + m_ExitTime: 0.0000000041531494 + m_HasExitTime: 1 + m_HasFixedDuration: 0 + m_InterruptionSource: 2 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!91 &9100000 AnimatorController: m_ObjectHideFlags: 0 @@ -1637,6 +1690,12 @@ AnimatorController: m_DefaultInt: 0 m_DefaultBool: 0 m_Controller: {fileID: 0} + - m_Name: AnticipateMove + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 0} m_AnimatorLayers: - serializedVersion: 5 m_Name: Base Layer @@ -1721,6 +1780,31 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1101 &426919566368059880 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: AnticipateMove + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 7052821593652808675} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.118838675 + m_TransitionOffset: 0 + m_ExitTime: 0.03151326 + m_HasExitTime: 0 + m_HasFixedDuration: 0 + m_InterruptionSource: 2 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!114 &462837921949526196 MonoBehaviour: m_ObjectHideFlags: 1 @@ -2051,6 +2135,28 @@ AnimatorStateMachine: m_ExitPosition: {x: 800, y: 120, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} m_DefaultState: {fileID: -9059899351118470251} +--- !u!1101 &3599037512052606113 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -3516404760485336223} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.35027 + m_TransitionOffset: 0.118458696 + m_ExitTime: 0.40587464 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 2 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1101 &4047612268588016952 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -2279,6 +2385,31 @@ AnimatorStateTransition: m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!1101 &5286344766070818967 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: AnticipateMove + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -5093654682345616202} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.185271 + m_TransitionOffset: 0.051008537 + m_ExitTime: 0.031880304 + m_HasExitTime: 0 + m_HasFixedDuration: 0 + m_InterruptionSource: 2 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1101 &5537143230144326077 AnimatorStateTransition: m_ObjectHideFlags: 1 @@ -2552,6 +2683,33 @@ AnimatorState: m_MirrorParameter: m_CycleOffsetParameter: m_TimeParameter: +--- !u!1102 &7052821593652808675 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AnticipateMove + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: -97121490881636740} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: -3419257869308726280, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: --- !u!1102 &7103618770137260043 AnimatorState: serializedVersion: 6 diff --git a/Assets/BossRoom/Models/CharacterSet.fbx.meta b/Assets/BossRoom/Models/CharacterSet.fbx.meta index 061ff0177..fd904d991 100644 --- a/Assets/BossRoom/Models/CharacterSet.fbx.meta +++ b/Assets/BossRoom/Models/CharacterSet.fbx.meta @@ -2182,7 +2182,14 @@ ModelImporter: mirror: 0 bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 curves: [] - events: [] + events: + - time: 0.504353 + functionName: OnAnimEvent + data: impact + objectReferenceParameter: {instanceID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 transformMask: [] maskType: 3 maskSource: {instanceID: 0} diff --git a/Assets/BossRoom/Models/CharacterSetController_Tank.overrideController b/Assets/BossRoom/Models/CharacterSetController_Tank.overrideController index 4a8195ae1..30d8e82a4 100644 --- a/Assets/BossRoom/Models/CharacterSetController_Tank.overrideController +++ b/Assets/BossRoom/Models/CharacterSetController_Tank.overrideController @@ -21,3 +21,5 @@ AnimatorOverrideController: m_OverrideClip: {fileID: -6239216113759591374, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} - m_OriginalClip: {fileID: -5612658629409835226, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} m_OverrideClip: {fileID: -5612658629409835226, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} + - m_OriginalClip: {fileID: -4428684883894617094, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} + m_OverrideClip: {fileID: -3419257869308726280, guid: 2115c4661f55eff45a5a0f91fc0a12f0, type: 3} diff --git a/Assets/BossRoom/Scripts/Client/ClickFeedback.cs b/Assets/BossRoom/Scripts/Client/ClickFeedback.cs index 3a73121bf..3cb429a6b 100644 --- a/Assets/BossRoom/Scripts/Client/ClickFeedback.cs +++ b/Assets/BossRoom/Scripts/Client/ClickFeedback.cs @@ -27,12 +27,12 @@ void Start() } m_ClientSender = GetComponent(); - m_ClientSender.OnClientClick += onClick; + m_ClientSender.ClientMoveEvent += onClientMove; m_FeedbackObj = Instantiate(m_FeedbackPrefab); m_FeedbackObj.SetActive(false); } - void onClick(Vector3 position) + void onClientMove(Vector3 position) { position.y += HOVER_HEIGHT; @@ -44,7 +44,7 @@ private void OnDestroy() { if (m_ClientSender) { - m_ClientSender.OnClientClick -= onClick; + m_ClientSender.ClientMoveEvent -= onClientMove; } } } diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/ActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/ActionFX.cs index 2bc8f15ad..b9db06571 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/ActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/ActionFX.cs @@ -13,6 +13,11 @@ public abstract class ActionFX : ActionBase /// public const string k_DefaultHitReact = "HitReact1"; + /// + /// True if this actionFX began running immediately, prior to getting a confirmation from the server. + /// + public bool Anticipated { get; protected set; } + public ActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) : base(ref data) { m_Parent = parent; @@ -21,8 +26,16 @@ public ActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) /// /// Starts the ActionFX. Derived classes may return false if they wish to end immediately without their Update being called. /// + /// + /// Derived class should be sure to call base.Start() in their implementation, but note that this resets "Anticipated" to false. + /// /// true to play, false to be immediately cleaned up. - public abstract bool Start(); + public virtual bool Start() + { + Anticipated = false; //once you start for real you are no longer an anticipated action. + TimeStartedSeconds = UnityEngine.Time.time; + return true; + } public abstract bool Update(); @@ -54,17 +67,63 @@ public static ActionFX MakeActionFX(ref ActionRequestData data, ClientCharacterV case ActionLogic.RangedFXTargeted: return new FXProjectileTargetedActionFX(ref data, parent); case ActionLogic.Trample: return new TrampleActionFX(ref data, parent); case ActionLogic.AoE: return new AoeActionFX(ref data, parent); - case ActionLogic.Stunned: return new AnimationOnlyActionFX(ref data, parent); case ActionLogic.Target: return new TargetActionFX(ref data, parent); + case ActionLogic.ChargedShield: case ActionLogic.ChargedLaunchProjectile: return new ChargedActionFX(ref data, parent); + case ActionLogic.StealthMode: return new StealthModeActionFX(ref data, parent); + + case ActionLogic.Stunned: + case ActionLogic.LaunchProjectile: + case ActionLogic.Revive: + case ActionLogic.Emote: return new AnimationOnlyActionFX(ref data, parent); + default: throw new System.NotImplementedException(); } } + /// + /// Should this ActionFX be created anticipatively on the owning client? + /// + /// The ActionVisualization that would be playing this ActionFX. + /// The request being sent to the server + /// If true ActionVisualization should pre-emptively create the ActionFX on the owning client, before hearing back from the server. + public static bool ShouldAnticipate(ActionVisualization parent, ref ActionRequestData data) + { + var actionDescription = GameDataSource.Instance.ActionDataByType[data.ActionTypeEnum]; + + //for actions with ShouldClose set, we check our range locally. If we are out of range, we shouldn't anticipate, as we will + //need to execute a ChaseAction (synthesized on the server) prior to actually playing the skill. + bool isTargetEligible = true; + if( data.ShouldClose == true ) + { + ulong targetId = (data.TargetIds != null && data.TargetIds.Length > 0) ? data.TargetIds[0] : 0; + if( MLAPI.Spawning.NetworkSpawnManager.SpawnedObjects.TryGetValue(targetId, out MLAPI.NetworkObject networkObject ) ) + { + float rangeSquared = actionDescription.Range * actionDescription.Range; + isTargetEligible = (networkObject.transform.position - parent.Parent.transform.position).sqrMagnitude < rangeSquared; + } + } + + //at present all Actionts anticipate except for the Target action, which runs a single instance on the client and is + //responsible for action anticipation on its own. + return isTargetEligible && actionDescription.Logic != ActionLogic.Target; + } + public virtual void OnAnimEvent(string id) { } public virtual void OnStoppedChargingUp() { } + + /// + /// Called when the action is being "anticipated" on the client. For example, if you are the owner of a tank and you swing your hammer, + /// you get this call immediately on the client, before the server round-trip. + /// Overriders should always call the base class in their implementation! + /// + public virtual void AnticipateAction() + { + Anticipated = true; + TimeStartedSeconds = UnityEngine.Time.time; + } } } diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/ActionVisualization.cs b/Assets/BossRoom/Scripts/Client/Game/Action/ActionVisualization.cs index 602ea6138..258551e77 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/ActionVisualization.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/ActionVisualization.cs @@ -9,14 +9,19 @@ namespace BossRoom.Visual /// public class ActionVisualization { - private List m_PlayingActions; + private List m_PlayingActions = new List(); + + /// + /// Don't let anticipated actionFXs persist longer than this. This is a safeguard against scenarios + /// where we never get a confirmed action for an action we anticipated. + /// + private const float k_AnticipationTimeoutSeconds = 1; public ClientCharacterVisualization Parent { get; private set; } public ActionVisualization(ClientCharacterVisualization parent) { Parent = parent; - m_PlayingActions = new List(); } public void Update() @@ -27,15 +32,24 @@ public void Update() var action = m_PlayingActions[i]; bool keepGoing = action.Update(); bool expirable = action.Description.DurationSeconds > 0f; //non-positive value is a sentinel indicating the duration is indefinite. - bool timeExpired = expirable && (Time.time - action.TimeStarted) >= action.Description.DurationSeconds; - if (!keepGoing || timeExpired) + bool timeExpired = expirable && action.TimeRunningSeconds >= action.Description.DurationSeconds; + bool timedOut = action.Anticipated && action.TimeRunningSeconds >= k_AnticipationTimeoutSeconds; + if (!keepGoing || timeExpired || timedOut) { - action.End(); + if (timedOut) { action.Cancel(); } //an anticipated action that timed out shouldn't get its End called. It is canceled instead. + else { action.End(); } + m_PlayingActions.RemoveAt(i); } } } + //helper wrapper for a FindIndex call on m_PlayingActions. + private int FindAction(ActionType action, bool anticipatedOnly ) + { + return m_PlayingActions.FindIndex(a => a.Description.ActionTypeEnum == action && (!anticipatedOnly || a.Anticipated)); + } + public void OnAnimEvent(string id) { foreach (var actionFX in m_PlayingActions) @@ -52,22 +66,55 @@ public void OnStoppedChargingUp() } } - public void PlayAction(ref ActionRequestData data) + /// + /// Called on the client that owns the Character when the player triggers an action. This allows actions to immediately start playing feedback. + /// + /// + /// + /// What is Action Anticipation and what problem does it solve? In short, it lets Actions run logic the moment the input event that triggers them + /// is detected on the local client. The purpose of this is to help mask latency. Because this demo is server authoritative, the default behavior is + /// to only see feedback for your input after a server-client roundtrip. Somewhere over 200ms of round-trip latency, this starts to feel oppressively sluggish. + /// To combat this, you can play visual effects immediately. For example, MeleeActionFX plays both its weapon swing and applies a hit react to the target, + /// without waiting to hear from the server. This can lead to discrepancies when the server doesn't think the target was hit, but on the net, will feel + /// more responsive. + /// + /// An important concept of Action Anticipation is that it is opportunistic--it doesn't make any strong guarantees. You don't get an anticipated + /// action animation if you are already animating in some way, as one example. Another complexity is that you don't know if the server will actually + /// let you play all the actions that you've requested--some may get thrown away, e.g. because you have too many actions in your queue. What this means + /// is that Anticipated Actions (actions that have been constructed but not started) won't match up perfectly with actual approved delivered actions from + /// the server. For that reason, it must always be fine to receive PlayAction and not have an anticipated action already started (this is true for playback + /// Characters belonging to the server and other characters anyway). It also means we need to handle the case where we created an Anticipated Action, but + /// never got a confirmation--actions like that need to eventually get discarded. + /// + /// Another important aspect of Anticipated Actions is that they are an "opt-in" system. You must call base.Start in your Start implementation, but other than + /// that, if you don't have a good way to implement an Anticipation for your action, you don't have to do anything. In this case, that action will play + /// "normally" (with visual feedback starting when the server's action broadcast reaches the client). Every action type will have its own particular set of + /// problems to solve to sell the anticipation effect. For example, in this demo, the mage base attack (FXProjectileTargetedActionFX) just plays the attack animation + /// anticipatively, but it could be revised to create and drive the mage bolt effect as well--leaving only damage to arrive in true server time. + /// + /// How to implement your own Anticipation logic: + /// 1. Isolate the visual feedback you want play anticipatively in a private helper method on your ActionFX, like "PlayAttackAnim". + /// 2. Override ActionFX.AnticipateAction. Be sure to call base.AnticipateAction, as well as play your visual logic (like PlayAttackAnim). + /// 3. In your Start method, be sure to call base.Start (note that this will reset the "Anticipated" field to false). + /// 4. In Start, check if the action was Anticipated. If NOT, then play call your PlayAttackAnim method. + /// + /// + /// The Action that is being requested. + public void AnticipateAction(ref ActionRequestData data) { - ActionDescription actionDesc = GameDataSource.Instance.ActionDataByType[data.ActionTypeEnum]; - - //Do Trivial Actions (actions that just require playing a single animation, and don't require any state tracking). - switch (actionDesc.Logic) + if (!Parent.IsAnimating && ActionFX.ShouldAnticipate(this, ref data)) { - case ActionLogic.LaunchProjectile: - case ActionLogic.Revive: - case ActionLogic.Emote: - Parent.OurAnimator.SetTrigger(actionDesc.Anim); - return; + var actionFX = ActionFX.MakeActionFX(ref data, Parent); + actionFX.AnticipateAction(); + m_PlayingActions.Add(actionFX); } + } - var actionFX = ActionFX.MakeActionFX(ref data, Parent); - actionFX.TimeStarted = Time.time; + public void PlayAction(ref ActionRequestData data) + { + var anticipatedActionIndex = FindAction(data.ActionTypeEnum, true); + + var actionFX = anticipatedActionIndex>=0 ? m_PlayingActions[anticipatedActionIndex] : ActionFX.MakeActionFX(ref data, Parent); if (actionFX.Start()) { m_PlayingActions.Add(actionFX); @@ -85,7 +132,7 @@ public void CancelAllActions() public void CancelAllActionsOfType(ActionType actionType) { - for (int i = m_PlayingActions.Count-1; i >=0; --i) + for (int i = m_PlayingActions.Count - 1; i >= 0; --i) { if (m_PlayingActions[i].Description.ActionTypeEnum == actionType) { @@ -100,7 +147,7 @@ public void CancelAllActionsOfType(ActionType actionType) /// public void CancelAll() { - foreach( var action in m_PlayingActions ) + foreach (var action in m_PlayingActions) { action.Cancel(); } diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/AnimationOnlyActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/AnimationOnlyActionFX.cs index 5840262de..d25db4b7c 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/AnimationOnlyActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/AnimationOnlyActionFX.cs @@ -13,16 +13,32 @@ public AnimationOnlyActionFX(ref ActionRequestData data, ClientCharacterVisualiz public override bool Start() { - m_Parent.OurAnimator.SetTrigger(Description.Anim); + if( !Anticipated ) + { + PlayStartAnim(); + } + + base.Start(); return true; } + private void PlayStartAnim() + { + m_Parent.OurAnimator.SetTrigger(Description.Anim); + } + + public override void AnticipateAction() + { + base.AnticipateAction(); + PlayStartAnim(); + } + public override bool Update() { return ActionConclusion.Continue; } - public override void End() + public override void Cancel() { if (!string.IsNullOrEmpty(Description.Anim2)) { diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/AoeActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/AoeActionFX.cs index 8e6f27ec4..d13eae627 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/AoeActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/AoeActionFX.cs @@ -11,6 +11,7 @@ public AoeActionFX(ref ActionRequestData data, ClientCharacterVisualization pare public override bool Start() { + base.Start(); m_Parent.OurAnimator.SetTrigger(Description.Anim); GameObject.Instantiate(Description.Spawns[0], m_Data.Position, Quaternion.identity); return ActionConclusion.Stop; diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/ChargedActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/ChargedActionFX.cs index 96aeaf7cd..744c23628 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/ChargedActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/ChargedActionFX.cs @@ -27,6 +27,7 @@ public ChargedActionFX(ref ActionRequestData data, ClientCharacterVisualization public override bool Start() { + base.Start(); m_Parent.OurAnimator.SetTrigger(Description.Anim); if (Description.Spawns.Length > 0) diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/FXProjectileTargetedActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/FXProjectileTargetedActionFX.cs index 8905a42fd..df4817c78 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/FXProjectileTargetedActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/FXProjectileTargetedActionFX.cs @@ -25,6 +25,8 @@ public FXProjectileTargetedActionFX(ref ActionRequestData data, ClientCharacterV public override bool Start() { + bool wasAnticipated = Anticipated; + base.Start(); m_Target = GetTarget(); if (HasTarget() && m_Target == null) { @@ -44,14 +46,23 @@ public override bool Start() m_Projectile = SpawnAndInitializeProjectile(); // animate shooting the projectile - m_Parent.OurAnimator.SetTrigger(Description.Anim); + if( !wasAnticipated ) + { + PlayFireAnim(); + } + return true; } + private void PlayFireAnim() + { + m_Parent.OurAnimator.SetTrigger(Description.Anim); + } + public override bool Update() { // we keep going until the projectile's duration ends - return (Time.time - TimeStarted) <= m_ProjectileDuration + Description.ExecTimeSeconds; + return (Time.time - TimeStartedSeconds) <= m_ProjectileDuration + Description.ExecTimeSeconds; } public override void OnAnimEvent(string id) @@ -128,5 +139,11 @@ private FXProjectile SpawnAndInitializeProjectile() projectile.Initialize(m_Parent.transform.position, m_Target?.transform, m_Data.Position, Description.ExecTimeSeconds, m_ProjectileDuration); return projectile; } + + public override void AnticipateAction() + { + base.AnticipateAction(); + PlayFireAnim(); + } } } diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/MeleeActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/MeleeActionFX.cs index 259cee892..2c11cae1b 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/MeleeActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/MeleeActionFX.cs @@ -21,7 +21,12 @@ public MeleeActionFX(ref ActionRequestData data, ClientCharacterVisualization pa public override bool Start() { - m_Parent.OurAnimator.SetTrigger(Description.Anim); + if( !Anticipated) + { + PlayAnim(); + } + + base.Start(); return true; } @@ -45,6 +50,11 @@ public override void End() PlayHitReact(); } + private void PlayAnim() + { + m_Parent.OurAnimator.SetTrigger(Description.Anim); + } + private void PlayHitReact() { if (m_ImpactPlayed) { return; } @@ -74,5 +84,14 @@ private void PlayHitReact() //in the future we may do another physics check to handle the case where a target "ran under our weapon". //But for now, if the original target is no longer present, then we just don't play our hit react on anything. } + + public override void AnticipateAction() + { + base.AnticipateAction(); + + //note: because the hit-react is driven from the animation, this means we can anticipatively trigger a hit-react too. That + //will make combat feel responsive, but of course the actual damage won't be applied until the server tells us about it. + PlayAnim(); + } } } diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/StealthModeActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/StealthModeActionFX.cs index 2f76ba430..d2412107e 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/StealthModeActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/StealthModeActionFX.cs @@ -27,13 +27,14 @@ public StealthModeActionFX(ref ActionRequestData data, ClientCharacterVisualizat public override bool Start() { + base.Start(); m_Parent.OurAnimator.SetTrigger(Description.Anim); return true; } public override bool Update() { - if (TimeRunning >= Description.ExecTimeSeconds && m_SpawnedGraphics == null && m_Parent.IsOwner) + if (TimeRunningSeconds >= Description.ExecTimeSeconds && m_SpawnedGraphics == null && m_Parent.IsOwner) { m_SpawnedGraphics = new List(); foreach (var prefab in Description.Spawns) diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/TargetActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/TargetActionFX.cs index 312559cd3..d92e489ac 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/TargetActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/TargetActionFX.cs @@ -13,6 +13,7 @@ public class TargetActionFX : ActionFX { private GameObject m_TargetReticule; private ulong m_CurrentTarget; + private ulong m_NewTarget; private NetworkCharacterState m_ParentState; private const float k_ReticuleGroundHeight = 0.2f; @@ -23,15 +24,34 @@ public TargetActionFX(ref ActionRequestData data, ClientCharacterVisualization p public override bool Start() { + base.Start(); m_ParentState = m_Parent.Parent.GetComponent(); + + m_ParentState.TargetId.OnValueChanged += OnTargetChanged; + m_ParentState.GetComponent().ActionInputEvent += OnActionInput; + return true; } + private void OnTargetChanged(ulong oldTarget, ulong newTarget ) + { + m_NewTarget = newTarget; + } + + private void OnActionInput(ActionRequestData data ) + { + //this method runs on the owning client, and allows us to anticipate our new target for purposes of FX visualization. + if( data.ActionTypeEnum == ActionType.GeneralTarget ) + { + m_NewTarget = data.TargetIds[0]; + } + } + public override bool Update() { - if( m_CurrentTarget != m_ParentState.TargetId.Value ) + if( m_CurrentTarget != m_NewTarget ) { - m_CurrentTarget = m_ParentState.TargetId.Value; + m_CurrentTarget = m_NewTarget; if (NetworkSpawnManager.SpawnedObjects.TryGetValue(m_CurrentTarget, out NetworkObject targetObject ) ) { @@ -85,6 +105,12 @@ private void ValidateReticule(NetworkObject targetObject) public override void Cancel() { GameObject.Destroy(m_TargetReticule); + + m_ParentState.TargetId.OnValueChanged -= OnTargetChanged; + if( m_ParentState.TryGetComponent(out Client.ClientInputSender inputSender)) + { + inputSender.ActionInputEvent -= OnActionInput; + } } } diff --git a/Assets/BossRoom/Scripts/Client/Game/Action/TrampleActionFX.cs b/Assets/BossRoom/Scripts/Client/Game/Action/TrampleActionFX.cs index def39ff0d..05c3cb1c6 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Action/TrampleActionFX.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Action/TrampleActionFX.cs @@ -37,13 +37,14 @@ public TrampleActionFX(ref ActionRequestData data, ClientCharacterVisualization public override bool Start() { + base.Start(); m_Parent.OurAnimator.SetTrigger(Description.Anim); return true; } public override bool Update() { - float age = Time.time - TimeStarted; + float age = Time.time - TimeStartedSeconds; if (age > k_GraphicsSpawnDelay && m_SpawnedGraphics == null) { m_SpawnedGraphics = new List(); diff --git a/Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterVisualization.cs b/Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterVisualization.cs index 8152f418c..ea93486c9 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterVisualization.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterVisualization.cs @@ -2,8 +2,6 @@ using Cinemachine; using MLAPI; using System; -using System.Collections; -using System.ComponentModel; using UnityEngine; namespace BossRoom.Visual @@ -116,6 +114,12 @@ public override void NetworkStart() m_ActionViz.PlayAction(ref data); gameObject.AddComponent(); m_PartyHUD.SetHeroData(m_NetState); + + if( Parent.TryGetComponent(out ClientInputSender inputSender)) + { + inputSender.ActionInputEvent += OnActionInput; + inputSender.ClientMoveEvent += OnMoveInput; + } } else { @@ -124,6 +128,19 @@ public override void NetworkStart() } } + private void OnActionInput(ActionRequestData data) + { + m_ActionViz.AnticipateAction(ref data); + } + + private void OnMoveInput(Vector3 position) + { + if( !IsAnimating ) + { + OurAnimator.SetTrigger("AnticipateMove"); + } + } + /// /// The switch to certain LifeStates fires an animation on an NPC/PC. This bypasses that initial animation /// and sends an NPC/PC to their eventual looping animation. This is necessary for mid-game player connections. @@ -152,6 +169,12 @@ private void OnDestroy() m_NetState.OnPerformHitReaction -= OnPerformHitReaction; m_NetState.OnStopChargingUpClient -= OnStoppedChargingUp; m_NetState.IsStealthy.OnValueChanged -= OnStealthyChanged; + + if (Parent != null && Parent.TryGetComponent(out ClientInputSender sender)) + { + sender.ActionInputEvent -= OnActionInput; + sender.ClientMoveEvent -= OnMoveInput; + } } } @@ -272,5 +295,24 @@ public void OnAnimEvent(string id) m_ActionViz.OnAnimEvent(id); } + + public bool IsAnimating + { + get + { + //layer 0 is our movement layer, and has a special base state. We detect if we are animating solely based on the Speed parameter. + if (OurAnimator.GetFloat("Speed") > 0.0 ) + { + return true; + } + + for (int i = 1; i < OurAnimator.layerCount; i++) + { + if (!OurAnimator.GetCurrentAnimatorStateInfo(i).IsName("Nothing")) { return true; } + } + + return false; + } + } } } diff --git a/Assets/BossRoom/Scripts/Client/Game/Character/ClientInputSender.cs b/Assets/BossRoom/Scripts/Client/Game/Character/ClientInputSender.cs index 0878bdda4..2f549024e 100644 --- a/Assets/BossRoom/Scripts/Client/Game/Character/ClientInputSender.cs +++ b/Assets/BossRoom/Scripts/Client/Game/Character/ClientInputSender.cs @@ -16,7 +16,9 @@ public class ClientInputSender : NetworkBehaviour { private const float k_MouseInputRaycastDistance = 100f; - private const float k_MoveSendRateSeconds = 0.5f; + private const float k_MoveSendRateSeconds = 0.05f; //20 fps. + + private const float k_TargetMoveTimeout = 0.45f; //prevent moves for this long after targeting someone (helps prevent walking to the guy you clicked). private float m_LastSentMove; @@ -29,6 +31,11 @@ public class ClientInputSender : NetworkBehaviour private NetworkCharacterState m_NetworkCharacter; + /// + /// This event fires at the time when an action request is sent to the server. + /// + public Action ActionInputEvent; + /// /// This describes how a skill was requested. Skills requested via mouse click will do raycasts to determine their target; skills requested /// in other matters will use the stateful target stored in NetworkCharacterState. @@ -74,7 +81,7 @@ private struct ActionRequest Camera m_MainCamera; - public event Action OnClientClick; + public event Action ClientMoveEvent; /// /// Convenience getter that returns our CharacterData @@ -156,10 +163,11 @@ void FixedUpdate() var ray = m_MainCamera.ScreenPointToRay(Input.mousePosition); if (Physics.RaycastNonAlloc(ray, k_CachedHit, k_MouseInputRaycastDistance, k_GroundLayerMask) > 0) { - // The MLAPI_INTERNAL channel is a reliable sequenced channel. Inputs should always arrive and be in order that's why this channel is used. - m_NetworkCharacter.SendCharacterInputServerRpc(k_CachedHit[0].point); + // The MLAPI_INTERNAL channel is a reliable sequenced channel. Inputs should always arrive and be in order that's why this channel is used. + m_NetworkCharacter.SendCharacterInputServerRpc(k_CachedHit[0].point); + //Send our client only click request - OnClientClick?.Invoke(k_CachedHit[0].point); + ClientMoveEvent?.Invoke(k_CachedHit[0].point); } } } @@ -210,7 +218,9 @@ private void PerformSkill(ActionType actionType, SkillTriggerStyle triggerStyle, if (GetActionRequestForTarget(hitTransform, actionType, triggerStyle, out ActionRequestData playerAction)) { //Don't trigger our move logic for another 500ms. This protects us from moving just because we clicked on them to target them. - m_LastSentMove = Time.time; + m_LastSentMove = Time.time + k_TargetMoveTimeout; + + ActionInputEvent?.Invoke(playerAction); m_NetworkCharacter.RecvDoActionServerRPC(playerAction); } else if(actionType != ActionType.GeneralTarget ) @@ -218,7 +228,10 @@ private void PerformSkill(ActionType actionType, SkillTriggerStyle triggerStyle, // clicked on nothing... perform a "miss" attack on the spot they clicked on var data = new ActionRequestData(); PopulateSkillRequest(k_CachedHit[0].point, actionType, ref data); + + ActionInputEvent?.Invoke(data); m_NetworkCharacter.RecvDoActionServerRPC(data); + } } diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/AOEAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/AOEAction.cs index 8fe60a619..ef09ce330 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/AOEAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/AOEAction.cs @@ -41,7 +41,7 @@ public override bool Start() public override bool Update() { - if (TimeRunning >= Description.ExecTimeSeconds && !m_DidAoE) + if (TimeRunningSeconds >= Description.ExecTimeSeconds && !m_DidAoE) { // actually perform the AoE attack m_DidAoE = true; diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/Action.cs b/Assets/BossRoom/Scripts/Server/Game/Action/Action.cs index 5b12ae7ca..82a0f98b2 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/Action.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/Action.cs @@ -55,8 +55,8 @@ public Action(ServerCharacter parent, ref ActionRequestData data) : base(ref dat /// true to become a non-blocking Action, false to remain a blocking Action public virtual bool ShouldBecomeNonBlocking() { - return Description.BlockingMode == BlockingMode.OnlyDuringExecTime ? TimeRunning >= Description.ExecTimeSeconds : - Description.BlockingMode == BlockingMode.ExecTimeWithCooldown ? TimeRunning >= (Description.ExecTimeSeconds + Description.CooldownSeconds) : + return Description.BlockingMode == BlockingMode.OnlyDuringExecTime ? TimeRunningSeconds >= Description.ExecTimeSeconds : + Description.BlockingMode == BlockingMode.ExecTimeWithCooldown ? TimeRunningSeconds >= (Description.ExecTimeSeconds + Description.CooldownSeconds) : false; } diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs b/Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs index ffcb519a6..be78f7fa7 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs @@ -112,7 +112,7 @@ private void StartAction() int index = SynthesizeTargetIfNecessary(0); SynthesizeChaseIfNecessary(index); - m_Queue[0].TimeStarted = Time.time; + m_Queue[0].TimeStartedSeconds = Time.time; bool play = m_Queue[0].Start(); if (!play) { @@ -260,7 +260,7 @@ private bool UpdateAction(Action action) { bool keepGoing = action.Update(); bool expirable = action.Description.DurationSeconds > 0f; //non-positive value is a sentinel indicating the duration is indefinite. - var timeElapsed = Time.time - action.TimeStarted; + var timeElapsed = Time.time - action.TimeStartedSeconds; bool timeExpired = expirable && timeElapsed >= (action.Description.DurationSeconds + action.Description.CooldownSeconds); return keepGoing && !timeExpired; @@ -286,7 +286,7 @@ private float GetQueueTimeDepth() totalTime += actionTime; } - return totalTime - m_Queue[0].TimeRunning; + return totalTime - m_Queue[0].TimeRunningSeconds; } public void OnCollisionEnter(Collision collision) diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/ChargedLaunchProjectileAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/ChargedLaunchProjectileAction.cs index da78d57d1..91bdccfef 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/ChargedLaunchProjectileAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/ChargedLaunchProjectileAction.cs @@ -68,7 +68,7 @@ public override bool Update() if (m_StoppedChargingUpTime == 0) { // we haven't explicitly stopped charging up... but if we've reached max charge, that implicitly stops us - if (TimeRunning >= Description.ExecTimeSeconds) + if (TimeRunningSeconds >= Description.ExecTimeSeconds) { StopChargingUp(); } @@ -129,7 +129,7 @@ protected override ActionDescription.ProjectileInfo GetProjectileInfo() if (Description.Projectiles.Length == 0) // uh oh, this is bad data throw new System.Exception($"Action {Description.ActionTypeEnum} has no Projectiles!"); - float timeSpentChargingUp = m_StoppedChargingUpTime - TimeStarted; + float timeSpentChargingUp = m_StoppedChargingUpTime - TimeStartedSeconds; float pctChargedUp = Mathf.Clamp01(timeSpentChargingUp / Description.ExecTimeSeconds); // Finally, choose which prefab to use based on how charged-up we got. diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/ChargedShieldAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/ChargedShieldAction.cs index 74a99c2d6..c6846fe04 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/ChargedShieldAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/ChargedShieldAction.cs @@ -59,7 +59,7 @@ public override bool Update() if (m_StoppedChargingUpTime == 0) { // we haven't explicitly stopped charging up... but if we've reached max charge, that implicitly stops us - if (TimeRunning >= Description.ExecTimeSeconds) + if (TimeRunningSeconds >= Description.ExecTimeSeconds) { StopChargingUp(); } @@ -78,7 +78,7 @@ public override void BuffValue(BuffableValue buffType, ref float buffedValue) { if (buffType == BuffableValue.PercentDamageReceived) { - float timeSpentChargingUp = m_StoppedChargingUpTime - TimeStarted; + float timeSpentChargingUp = m_StoppedChargingUpTime - TimeStartedSeconds; float pctChargedUp = Mathf.Clamp01(timeSpentChargingUp / Description.ExecTimeSeconds); // the amount of damage reduction starts at 50% (for not-charged-up), then slowly increases to 100% depending on how charged-up we got @@ -93,7 +93,7 @@ public override void BuffValue(BuffableValue buffType, ref float buffedValue) else if (buffType == BuffableValue.ChanceToStunTramplers) { // if we are at "full charge", we stun enemies that try to trample us! - float timeSpentChargingUp = m_StoppedChargingUpTime - TimeStarted; + float timeSpentChargingUp = m_StoppedChargingUpTime - TimeStartedSeconds; if (timeSpentChargingUp / Description.ExecTimeSeconds >= 1 && buffedValue < 1) { buffedValue = 1; diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/FXProjectileTargetedAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/FXProjectileTargetedAction.cs index 31379f0f6..b230c2e31 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/FXProjectileTargetedAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/FXProjectileTargetedAction.cs @@ -42,7 +42,7 @@ public override bool Start() public override bool Update() { - if (!m_ImpactedTarget && m_TimeUntilImpact <= (Time.time - TimeStarted)) + if (!m_ImpactedTarget && m_TimeUntilImpact <= (Time.time - TimeStartedSeconds)) { m_ImpactedTarget = true; if (m_Target != null ) diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/LaunchProjectileAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/LaunchProjectileAction.cs index 2cf073742..e4c4831d5 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/LaunchProjectileAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/LaunchProjectileAction.cs @@ -23,7 +23,7 @@ public override bool Start() public override bool Update() { - if (TimeRunning >= Description.ExecTimeSeconds && !m_Launched) + if (TimeRunningSeconds >= Description.ExecTimeSeconds && !m_Launched) { LaunchProjectile(); } diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/MeleeAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/MeleeAction.cs index a1d47117e..2f8d6ccde 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/MeleeAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/MeleeAction.cs @@ -51,7 +51,7 @@ public override bool Start() public override bool Update() { - if (!m_ExecutionFired && (Time.time - TimeStarted) >= Description.ExecTimeSeconds) + if (!m_ExecutionFired && (Time.time - TimeStartedSeconds) >= Description.ExecTimeSeconds) { m_ExecutionFired = true; var foe = DetectFoe(m_ProvisionalTarget); diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/ReviveAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/ReviveAction.cs index 731dd2abe..1fd921862 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/ReviveAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/ReviveAction.cs @@ -29,7 +29,7 @@ public override bool Start() public override bool Update() { - if (!m_ExecFired && Time.time - TimeStarted >= Description.ExecTimeSeconds) + if (!m_ExecFired && Time.time - TimeStartedSeconds >= Description.ExecTimeSeconds) { m_ExecFired = true; diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/StealthModeAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/StealthModeAction.cs index 6cead3f08..24ce722e6 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/StealthModeAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/StealthModeAction.cs @@ -31,12 +31,12 @@ public override bool Start() public override bool ShouldBecomeNonBlocking() { - return TimeRunning >= Description.ExecTimeSeconds; + return TimeRunningSeconds >= Description.ExecTimeSeconds; } public override bool Update() { - if (TimeRunning >= Description.ExecTimeSeconds && !m_IsStealthStarted && !m_IsStealthEnded) + if (TimeRunningSeconds >= Description.ExecTimeSeconds && !m_IsStealthStarted && !m_IsStealthEnded) { // start actual stealth-mode... NOW! m_IsStealthStarted = true; diff --git a/Assets/BossRoom/Scripts/Server/Game/Action/TrampleAction.cs b/Assets/BossRoom/Scripts/Server/Game/Action/TrampleAction.cs index fedd5f28d..8347fe4f2 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Action/TrampleAction.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Action/TrampleAction.cs @@ -73,7 +73,7 @@ public override bool Start() private ActionStage GetCurrentStage() { - float timeSoFar = Time.time - TimeStarted; + float timeSoFar = TimeRunningSeconds; if (timeSoFar < Description.ExecTimeSeconds) { return ActionStage.Windup; diff --git a/Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs b/Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs index a139caf77..83f9b80ff 100644 --- a/Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs +++ b/Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs @@ -130,6 +130,7 @@ private void OnClientMoveRequest(Vector3 targetPosition) if (NetState.NetworkLifeState.Value == LifeState.Alive && !m_Movement.IsPerformingForcedMovement()) { ClearActions(false); + m_ActionPlayer.CancelRunningActionsByLogic(ActionLogic.Target, true); //clear target on move. m_Movement.SetMovementTarget(targetPosition); } } diff --git a/Assets/BossRoom/Scripts/Shared/Game/Action/ActionBase.cs b/Assets/BossRoom/Scripts/Shared/Game/Action/ActionBase.cs index 610e7204b..8d35afdb1 100644 --- a/Assets/BossRoom/Scripts/Shared/Game/Action/ActionBase.cs +++ b/Assets/BossRoom/Scripts/Shared/Game/Action/ActionBase.cs @@ -12,12 +12,12 @@ public abstract class ActionBase /// /// Time when this Action was started (from Time.time) in seconds. Set by the ActionPlayer or ActionVisualization. /// - public float TimeStarted { get; set; } + public float TimeStartedSeconds { get; set; } /// /// How long the Action has been running (since its Start was called)--in seconds, measured via Time.time. /// - public float TimeRunning { get { return (Time.time - TimeStarted); } } + public float TimeRunningSeconds { get { return (Time.time - TimeStartedSeconds); } } /// /// RequestData we were instantiated with. Value should be treated as readonly. diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 0560e11dd..4db0bd06e 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2020.2.0f1 -m_EditorVersionWithRevision: 2020.2.0f1 (3721df5a8b28) +m_EditorVersion: 2020.3.0f1 +m_EditorVersionWithRevision: 2020.3.0f1 (c7b5465681fb)