void Update() { if (Physics.RaycastNonAlloc( ray: m_Camera.ScreenPointToRay(Input.mousePosition), results: m_UpdateResult, maxDistance: float.PositiveInfinity, layerMask: m_GroundLayerMask) > 0) { transform.position = m_UpdateResult[0].point; } float range = GameDataSource.Instance.ActionDataByType[m_ActionType].Range; bool isInRange = (m_Origin - transform.position).sqrMagnitude <= range * range; m_InRangeVisualization.SetActive(isInRange); m_OutOfRangeVisualization.SetActive(!isInRange); if (Input.GetMouseButtonUp(0)) { if (isInRange) { var data = new ActionRequestData { Position = transform.position, ActionTypeEnum = m_ActionType, ShouldQueue = false, TargetIds = null }; m_PlayerOwner.RecvDoActionServerRPC(data); } Destroy(gameObject); return; } }
public override void NetworkStart() { if (!IsServer) { enabled = false; } else { NetState = GetComponent <NetworkCharacterState>(); NetState.DoActionEventServer += OnActionPlayRequest; NetState.ReceivedClientInput += OnClientMoveRequest; NetState.OnStopChargingUpServer += OnStoppedChargingUp; NetState.NetworkLifeState.OnValueChanged += OnLifeStateChanged; NetState.ApplyCharacterData(); if (m_StartingAction != ActionType.None) { var startingAction = new ActionRequestData() { ActionTypeEnum = m_StartingAction }; PlayAction(ref startingAction); } } }
/// <summary> /// Targeted skills should implicitly set the active target of the character, if not already set. /// </summary> /// <param name="baseIndex">The new index of the base action in m_Queue</param> /// <returns></returns> private int SynthesizeTargetIfNecessary(int baseIndex) { Action baseAction = m_Queue[baseIndex]; var targets = baseAction.Data.TargetIds; if (targets != null && targets.Length == 1 && targets[0] != m_Parent.NetState.TargetId.Value) { //if this is a targeted skill (with a single requested target), and it is different from our //active target, then we synthesize a TargetAction to change our target over. ActionRequestData data = new ActionRequestData { ActionTypeEnum = ActionType.GeneralTarget, TargetIds = baseAction.Data.TargetIds }; //this shouldn't run redundantly, because the next time the base Action comes up to play, its Target //and the active target in our NetState should match. Action targetAction = Action.MakeAction(m_Parent, ref data); m_Queue.Insert(baseIndex, targetAction); return(baseIndex + 1); } return(baseIndex); }
/// <summary> /// Populates the ActionRequestData with additional information. The TargetIds of the action should already be set before calling this. /// </summary> /// <param name="hitPoint">The point in world space where the click ray hit the target.</param> /// <param name="action">The action to perform (will be stamped on the resultData)</param> /// <param name="resultData">The ActionRequestData to be filled out with additional information.</param> private void PopulateSkillRequest(Vector3 hitPoint, ActionType action, ref ActionRequestData resultData) { resultData.ActionTypeEnum = action; var actionInfo = GameDataSource.Instance.ActionDataByType[action]; //most skill types should implicitly close distance. The ones that don't are explicitly set to false in the following switch. resultData.ShouldClose = true; switch (actionInfo.Logic) { //for projectile logic, infer the direction from the click position. case ActionLogic.LaunchProjectile: Vector3 offset = hitPoint - transform.position; offset.y = 0; resultData.Direction = offset.normalized; resultData.ShouldClose = false; //why? Because you could be lining up a shot, hoping to hit other people between you and your target. Moving you would be quite invasive. return; case ActionLogic.Target: resultData.ShouldClose = false; return; case ActionLogic.Emote: resultData.CancelMovement = true; return; case ActionLogic.RangedFXTargeted: if (resultData.TargetIds == null) { resultData.Position = hitPoint; } return; } }
public static ActionFX MakeActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) { ActionLogic logic = GameDataSource.Instance.ActionDataByType[data.ActionTypeEnum].Logic; switch (logic) { case ActionLogic.Melee: return(new MeleeActionFX(ref data, parent)); 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)); default: throw new System.NotImplementedException(); } }
private void OnActionPlayRequest(ActionRequestData data) { if (!GameDataSource.Instance.ActionDataByType[data.ActionTypeEnum].IsFriendly) { // notify running actions that we're using a new attack. (e.g. so Stealth can cancel itself) RunningActions.OnGameplayActivity(Action.GameplayActivity.UsingAttackAction); } PlayAction(ref data); }
public override void Update() { if (!m_Brain.IsAppropriateFoe(m_Foe)) { // time for a new foe! m_Foe = ChooseFoe(); // whatever we used to be doing, stop that. New plan is coming! m_ActionPlayer.ClearActions(true); } // if we're out of foes, stop! IsEligible() will now return false so we'll soon switch to a new state if (!m_Foe) { return; } // see if we're already chasing or attacking our active foe! if (m_ActionPlayer.GetActiveActionInfo(out var info)) { if (info.ActionTypeEnum == ActionType.GeneralChase) { if (info.TargetIds != null && info.TargetIds[0] == m_Foe.NetworkObjectId) { // yep we're chasing our foe; all set! (The attack is enqueued after it) return; } } else if (info.ActionTypeEnum == m_CurAttackAction) { if (info.TargetIds != null && info.TargetIds[0] == m_Foe.NetworkObjectId) { // yep we're attacking our foe; all set! return; } } else if (info.ActionTypeEnum == ActionType.Stun) { // we can't do anything right now. We're stunned! return; } } // Choose whether we can attack our foe directly, or if we need to get closer first var attackInfo = GetCurrentAttackInfo(); var attackData = new ActionRequestData { ActionTypeEnum = attackInfo.ActionTypeEnum, TargetIds = new ulong[] { m_Foe.NetworkObjectId }, ShouldClose = true }; m_ActionPlayer.PlayAction(ref attackData); }
/// <summary> /// Play a sequence of actions! /// </summary> public void PlayAction(ref ActionRequestData action) { //the character needs to be alive in order to be able to play actions if (NetState.NetworkLifeState.Value == LifeState.Alive && !m_Movement.IsPerformingForcedMovement()) { if (action.CancelMovement) { m_Movement.CancelMove(); } m_ActionPlayer.PlayAction(ref action); } }
/// <summary> /// If an Action is active, fills out 'data' param and returns true. If no Action is active, returns false. /// This only refers to the blocking action! (multiple non-blocking actions can be running in the background, and /// this will still return false). /// </summary> public bool GetActiveActionInfo(out ActionRequestData data) { if (m_Queue.Count > 0) { data = m_Queue[0].Data; return(true); } else { data = new ActionRequestData(); return(false); } }
public override bool ChainIntoNewAction(ref ActionRequestData newAction) { if (m_WasStunned) { newAction = new ActionRequestData() { ActionTypeEnum = ActionType.Stun, ShouldQueue = false, }; return(true); } return(false); }
/// <summary> /// Perform a skill in response to some input trigger. This is the common method to which all input-driven skill plays funnel. /// </summary> /// <param name="actionType">The action you want to play. Note that "Skill1" may be overriden contextually depending on the target.</param> /// <param name="triggerStyle">What sort of input triggered this skill?</param> /// <param name="targetId">(optional) Pass in a specific networkID to target for this action</param> private void PerformSkill(ActionType actionType, SkillTriggerStyle triggerStyle, ulong targetId = 0) { Transform hitTransform = null; if (targetId != 0) { // if a targetId is given, try to find the object NetworkObject targetNetObj; if (NetworkSpawnManager.SpawnedObjects.TryGetValue(targetId, out targetNetObj)) { hitTransform = targetNetObj.transform; } } else { // otherwise try to find an object under the input position int numHits = 0; if (triggerStyle == SkillTriggerStyle.MouseClick) { var ray = m_MainCamera.ScreenPointToRay(Input.mousePosition); numHits = Physics.RaycastNonAlloc(ray, k_CachedHit, k_MouseInputRaycastDistance, k_ActionLayerMask); } int networkedHitIndex = -1; for (int i = 0; i < numHits; i++) { if (k_CachedHit[i].transform.GetComponent <NetworkObject>()) { networkedHitIndex = i; break; } } hitTransform = networkedHitIndex >= 0 ? k_CachedHit[networkedHitIndex].transform : null; } 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 + k_TargetMoveTimeout; m_NetworkCharacter.RecvDoActionServerRPC(playerAction); } else if (actionType != ActionType.GeneralTarget) { // 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); m_NetworkCharacter.RecvDoActionServerRPC(data); } }
private void Start() { // get our particle near the right spot! transform.position = m_PlayerOwner.transform.position; m_StartTime = Time.time; // right now we only support "untargeted" charged attacks. // Will need more input (e.g. click position) for fancier types of charged attacks! var data = new ActionRequestData { Position = transform.position, ActionTypeEnum = m_ActionType, ShouldQueue = false, TargetIds = null }; m_PlayerOwner.RecvDoActionServerRPC(data); }
/// <summary> /// Synthesizes a Chase Action for the action at the Head of the queue, if necessary (the base action must have a target, /// and must have the ShouldClose flag set). This method must not be called when the queue is empty. /// </summary> /// <returns>The new index of the Action being operated on.</returns> private int SynthesizeChaseIfNecessary(int baseIndex) { Action baseAction = m_Queue[baseIndex]; if (baseAction.Data.ShouldClose && baseAction.Data.TargetIds != null) { ActionRequestData data = new ActionRequestData { ActionTypeEnum = ActionType.GeneralChase, TargetIds = baseAction.Data.TargetIds, Amount = baseAction.Description.Range }; baseAction.Data.ShouldClose = false; //you only get to do this once! Action chaseAction = Action.MakeAction(m_Parent, ref data); m_Queue.Insert(baseIndex, chaseAction); return(baseIndex + 1); } return(baseIndex); }
/// <summary> /// Perform a sequence of actions. /// </summary> public void PlayAction(ref ActionRequestData action) { if (!action.ShouldQueue && m_Queue.Count > 0 && m_Queue[0].Description.ActionInterruptible) { ClearActions(false); } if (GetQueueTimeDepth() >= k_MaxQueueTimeDepth) { //the queue is too big (in execution seconds) to accommodate any more actions, so this action must be discarded. return; } var newAction = Action.MakeAction(m_Parent, ref action); m_Queue.Add(newAction); if (m_Queue.Count == 1) { StartAction(); } }
/// <summary> /// Factory method that creates Actions from their request data. /// </summary> /// <param name="parent">The component that owns the ActionPlayer this action is running on. </param> /// <param name="data">the data to instantiate this skill from. </param> /// <returns>the newly created action. </returns> public static Action MakeAction(ServerCharacter parent, ref ActionRequestData data) { if (!GameDataSource.Instance.ActionDataByType.TryGetValue(data.ActionTypeEnum, out var actionDesc)) { throw new System.Exception($"Trying to create Action {data.ActionTypeEnum} but it isn't defined on the GameDataSource!"); } var logic = actionDesc.Logic; switch (logic) { case ActionLogic.Melee: return(new MeleeAction(parent, ref data)); case ActionLogic.AoE: return(new AoeAction(parent, ref data)); case ActionLogic.Chase: return(new ChaseAction(parent, ref data)); case ActionLogic.Revive: return(new ReviveAction(parent, ref data)); case ActionLogic.RangedFXTargeted: return(new FXProjectileTargetedAction(parent, ref data)); case ActionLogic.LaunchProjectile: return(new LaunchProjectileAction(parent, ref data)); case ActionLogic.Emote: return(new EmoteAction(parent, ref data)); case ActionLogic.Trample: return(new TrampleAction(parent, ref data)); case ActionLogic.ChargedShield: return(new ChargedShieldAction(parent, ref data)); case ActionLogic.Stunned: return(new StunnedAction(parent, ref data)); case ActionLogic.Target: return(new TargetAction(parent, ref data)); case ActionLogic.ChargedLaunchProjectile: return(new ChargedLaunchProjectileAction(parent, ref data)); case ActionLogic.StealthMode: return(new StealthModeAction(parent, ref data)); default: throw new System.NotImplementedException(); } }
/// <summary> /// When you right-click on something you will want to do contextually different things. For example you might attack an enemy, /// but revive a friend. You might also decide to do nothing (e.g. right-clicking on a friend who hasn't FAINTED). /// </summary> /// <param name="hit">The Transform of the entity we clicked on, or null if none.</param> /// <param name="actionType">The Action to build for</param> /// <param name="triggerStyle">How did this skill play get triggered? Mouse, Keyboard, UI etc.</param> /// <param name="resultData">Out parameter that will be filled with the resulting action, if any.</param> /// <returns>true if we should play an action, false otherwise. </returns> private bool GetActionRequestForTarget(Transform hit, ActionType actionType, SkillTriggerStyle triggerStyle, out ActionRequestData resultData) { resultData = new ActionRequestData(); var targetNetObj = hit != null?hit.GetComponent <NetworkObject>() : null; //if we can't get our target from the submitted hit transform, get it from our stateful target in our NetworkCharacterState. if (!targetNetObj && actionType != ActionType.GeneralTarget) { ulong targetId = m_NetworkCharacter.TargetId.Value; NetworkSpawnManager.SpawnedObjects.TryGetValue(targetId, out targetNetObj); } //sanity check that this is indeed a valid target. if (targetNetObj == null || !ActionUtils.IsValidTarget(targetNetObj.NetworkObjectId)) { return(false); } var targetNetState = targetNetObj.GetComponent <NetworkCharacterState>(); if (targetNetState != null) { //Skill1 may be contextually overridden if it was generated from a mouse-click. if (actionType == CharacterData.Skill1 && triggerStyle == SkillTriggerStyle.MouseClick) { if (!targetNetState.IsNpc && targetNetState.NetworkLifeState.Value == LifeState.Fainted) { //right-clicked on a downed ally--change the skill play to Revive. actionType = ActionType.GeneralRevive; } } } // record our target in case this action uses that info (non-targeted attacks will ignore this) resultData.ActionTypeEnum = actionType; resultData.TargetIds = new ulong[] { targetNetObj.NetworkObjectId }; PopulateSkillRequest(targetNetObj.transform.position, actionType, ref resultData); return(true); }
public void PlayAction(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) { case ActionLogic.LaunchProjectile: case ActionLogic.Revive: case ActionLogic.Emote: Parent.OurAnimator.SetTrigger(actionDesc.Anim); return; } var actionFX = ActionFX.MakeActionFX(ref data, Parent); actionFX.TimeStarted = Time.time; if (actionFX.Start()) { m_PlayingActions.Add(actionFX); } }
public FXProjectileTargetedAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
public StealthModeActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) : base(ref data, parent) { }
public ActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) : base(ref data) { m_Parent = parent; }
public ChargedLaunchProjectileAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
public ChargedActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) : base(ref data, parent) { }
/// <inheritdoc /> public override void NetworkStart() { if (!IsClient || transform.parent == null) { enabled = false; return; } m_AliveStateTriggerID = Animator.StringToHash("StandUp"); m_FaintedStateTriggerID = Animator.StringToHash("FallDown"); m_DeadStateTriggerID = Animator.StringToHash("Dead"); m_AnticipateMoveTriggerID = Animator.StringToHash("AnticipateMove"); m_SpeedVariableID = Animator.StringToHash("Speed"); m_HitStateTriggerID = Animator.StringToHash(ActionFX.k_DefaultHitReact); m_ActionViz = new ActionVisualization(this); Parent = transform.parent; m_NetState = Parent.gameObject.GetComponent <NetworkCharacterState>(); m_NetState.DoActionEventClient += PerformActionFX; m_NetState.CancelAllActionsEventClient += CancelAllActionFXs; m_NetState.CancelActionsByTypeEventClient += CancelActionFXByType; m_NetState.NetworkLifeState.OnValueChanged += OnLifeStateChanged; m_NetState.OnPerformHitReaction += OnPerformHitReaction; m_NetState.OnStopChargingUpClient += OnStoppedChargingUp; m_NetState.IsStealthy.OnValueChanged += OnStealthyChanged; //we want to follow our parent on a spring, which means it can't be directly in the transform hierarchy. Parent.GetComponent <ClientCharacter>().ChildVizObject = this; transform.SetParent(null); // sync our visualization position & rotation to the most up to date version received from server var parentMovement = Parent.GetComponent <INetMovement>(); transform.position = parentMovement.NetworkPosition.Value; transform.rotation = Quaternion.Euler(0, parentMovement.NetworkRotationY.Value, 0); // listen for char-select info to change (in practice, this info doesn't // change, but we may not have the values set yet) ... m_NetState.CharacterAppearance.OnValueChanged += OnCharacterAppearanceChanged; // ...and visualize the current char-select value that we know about OnCharacterAppearanceChanged(0, m_NetState.CharacterAppearance.Value); // ...and visualize the current char-select value that we know about SetAppearanceSwap(); // sync our animator to the most up to date version received from server SyncEntryAnimation(m_NetState.NetworkLifeState.Value); if (!m_NetState.IsNpc) { // track health for heroes m_NetState.HealthState.HitPoints.OnValueChanged += OnHealthChanged; // find the emote bar to track its buttons GameObject partyHUDobj = GameObject.FindGameObjectWithTag("PartyHUD"); m_PartyHUD = partyHUDobj.GetComponent <Visual.PartyHUD>(); if (IsLocalPlayer) { ActionRequestData data = new ActionRequestData { ActionTypeEnum = ActionType.GeneralTarget }; m_ActionViz.PlayAction(ref data); gameObject.AddComponent <CameraController>(); m_PartyHUD.SetHeroData(m_NetState); if (Parent.TryGetComponent(out ClientInputSender inputSender)) { inputSender.ClientMoveEvent += OnMoveInput; } } else { m_PartyHUD.SetAllyData(m_NetState); // getting our parent's NetworkObjectID for PartyHUD removal on Destroy var parentNetworkObjectID = m_NetState.NetworkObjectId; // once this object is destroyed, remove this ally from the PartyHUD UI // NOTE: architecturally this will be refactored Destroyed += () => { if (m_PartyHUD != null) { m_PartyHUD.RemoveAlly(parentNetworkObjectID); } }; } } }
private void PerformActionFX(ActionRequestData data) { m_ActionViz.PlayAction(ref data); }
/// <summary> /// constructor. The "data" parameter should not be retained after passing in to this method, because we take ownership of its internal memory. /// </summary> public Action(ServerCharacter parent, ref ActionRequestData data) : base(ref data) { m_Parent = parent; m_Data = data; //do a shallow copy. }
/// <summary> /// Called *AFTER* End(). At this point, the Action has ended, meaning its Update() etc. functions will never be /// called again. If the Action wants to immediately segue into a different Action, it can do so here. The new /// Action will take effect in the next Update(). /// /// Note that this is not called on prematurely cancelled Actions, only on ones that have their End() called. /// </summary> /// <param name="newAction">the new Action to immediately transition to</param> /// <returns>true if there's a new action, false otherwise</returns> public virtual bool ChainIntoNewAction(ref ActionRequestData newAction) { return(false); }
public StunnedAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
//cache Physics Cast hits, to minimize allocs. public MeleeAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
public ChargedShieldAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
public StealthModeAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }