/// <summary> /// Notify the AIBrain that we should consider this character an enemy. /// </summary> /// <param name="character"></param> public void Hate(ServerCharacter character) { if (!m_HatedEnemies.Contains(character)) { m_HatedEnemies.Add(character); } }
/// <summary> /// Called when we received some HP. Positive HP is healing, negative is damage. /// </summary> /// <param name="inflicter">The person who hurt or healed us. May be null. </param> /// <param name="amount">The amount of HP received. Negative is damage. </param> public void ReceiveHP(ServerCharacter inflicter, int amount) { if (inflicter != null && amount < 0) { Hate(inflicter); } }
private void Awake() { m_NavMeshAgent = GetComponent <NavMeshAgent>(); m_NetworkCharacterState = GetComponent <NetworkCharacterState>(); m_CharLogic = GetComponent <ServerCharacter>(); m_Rigidbody = GetComponent <Rigidbody>(); m_NavigationSystem = GameObject.FindGameObjectWithTag(NavigationSystem.NavigationSystemTag).GetComponent <NavigationSystem>(); }
/// <summary> /// Receive a Life State change that brings Fainted characters back to Alive state. /// </summary> /// <param name="inflicter">Person reviving the character.</param> /// <param name="HP">The HP to set to a newly revived character.</param> public void Revive(ServerCharacter inflicter, int HP) { if (NetState.NetworkLifeState.Value == LifeState.Fainted) { NetState.HitPoints = NetState.CharacterData.BaseHP.Value; NetState.NetworkLifeState.Value = LifeState.Alive; } }
public void ReceiveHP(ServerCharacter inflicter, int HP) { if (HP < 0) { //any damage at all is enough to slay me. GetComponent <NetworkBreakableState>().IsBroken.Value = true; //don't let us take another blow. GetComponent <Collider>().enabled = false; } }
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> /// Returns true if it be appropriate for us to murder this character, starting right now! /// </summary> public bool IsAppropriateFoe(ServerCharacter potentialFoe) { if (potentialFoe == null || potentialFoe.IsNpc || potentialFoe.NetState.NetworkLifeState.Value != LifeState.Alive || potentialFoe.NetState.IsStealthy.Value != 0) { return(false); } // Also, we could use NavMesh.Raycast() to see if we have line of sight to foe? return(true); }
// TODO: David will create interface hookup for receiving hits on non-NPC/PC objects (GOMPS-ID TBD) void ReceiveHP(ServerCharacter inflicter, int HP) { if (!IsServer) { return; } m_NetworkHealthState.HitPoints.Value += HP; if (m_NetworkHealthState.HitPoints.Value <= 0) { StopWaveSpawning(); } }
public AIBrain(ServerCharacter me, ActionPlayer myActionPlayer) { m_ServerCharacter = me; m_ActionPlayer = myActionPlayer; m_Logics = new Dictionary <AIStateType, AIState> { [AIStateType.IDLE] = new IdleAIState(this), //[ AIStateType.WANDER ] = new WanderAIState(this), // not written yet [AIStateType.ATTACK] = new AttackAIState(this, m_ActionPlayer), }; m_HatedEnemies = new List <ServerCharacter>(); m_CurrentState = AIStateType.IDLE; }
protected void DetectFoes() { float detectionRange = m_Brain.CharacterData.DetectRange; // we are doing this check every Update, so we'll use square-magnitude distance to avoid the expensive sqrt (that's implicit in Vector3.magnitude) float detectionRangeSqr = detectionRange * detectionRange; Vector3 position = m_Brain.GetMyServerCharacter().transform.position; foreach (ServerCharacter character in ServerCharacter.GetAllActiveServerCharacters()) { if (m_Brain.IsAppropriateFoe(character) && (character.transform.position - position).sqrMagnitude <= detectionRangeSqr) { m_Brain.Hate(character); } } }
public override bool Start() { if (m_Data.TargetIds == null || m_Data.TargetIds.Length == 0 || !NetworkSpawnManager.SpawnedObjects.ContainsKey(m_Data.TargetIds[0])) { Debug.Log("Failed to start ReviveAction. The target entity wasn't submitted or doesn't exist anymore"); return(false); } var targetNeworkedObj = NetworkSpawnManager.SpawnedObjects[m_Data.TargetIds[0]]; m_TargetCharacter = targetNeworkedObj.GetComponent <ServerCharacter>(); m_Parent.NetState.RecvDoActionClientRPC(Data); return(true); }
/// <summary> /// We've crashed into a victim! This function determines what happens to them... and to us! /// It's possible for us to be stunned by our victim if they have a special power that allows that. /// This function checks for that special case; if we become stunned, the victim is entirely unharmed, /// and further collisions with other victims will also have no effect. /// </summary> /// <param name="victim">The character we've collided with</param> private void CollideWithVictim(ServerCharacter victim) { if (victim == m_Parent) { // can't collide with ourselves! return; } if (m_WasStunned) { // someone already stunned us, so no further damage can happen return; } // if we collide with allies, we don't want to hurt them (but we do knock them back, see below) if (m_Parent.IsNpc != victim.IsNpc) { // first see if this victim has the special ability to stun us! float chanceToStun = victim.GetBuffedValue(BuffableValue.ChanceToStunTramplers); if (chanceToStun > 0 && Random.Range(0, 1) < chanceToStun) { // we're stunned! No collision behavior for the victim. Stun ourselves and abort. m_WasStunned = true; m_Movement.CancelMove(); m_Parent.NetState.RecvCancelAllActionsClientRpc(); return; } // We deal a certain amount of damage to our "initial" target and a different amount to all other victims. int damage; if (m_Data.TargetIds != null && m_Data.TargetIds.Length > 0 && m_Data.TargetIds[0] == victim.NetworkObjectId) { damage = Description.Amount; } else { damage = Description.SplashDamage; } victim.NetState.RecvPerformHitReactionClientRPC(); victim.ReceiveHP(this.m_Parent, -damage); } var victimMovement = victim.GetComponent <ServerCharacterMovement>(); victimMovement.StartKnockback(m_Parent.transform.position, Description.KnockbackSpeed, Description.KnockbackDuration); }
/// <summary> /// Receive an HP change from somewhere. Could be healing or damage. /// </summary> /// <param name="inflicter">Person dishing out this damage/healing. Can be null. </param> /// <param name="HP">The HP to receive. Positive value is healing. Negative is damage. </param> public void ReceiveHP(ServerCharacter inflicter, int HP) { //to our own effects, and modify the damage or healing as appropriate. But in this game, we just take it straight. if (HP > 0) { m_ActionPlayer.OnGameplayActivity(Action.GameplayActivity.Healed); float healingMod = m_ActionPlayer.GetBuffedValue(Action.BuffableValue.PercentHealingReceived); HP = (int)(HP * healingMod); } else { m_ActionPlayer.OnGameplayActivity(Action.GameplayActivity.AttackedByEnemy); float damageMod = m_ActionPlayer.GetBuffedValue(Action.BuffableValue.PercentDamageReceived); HP = (int)(HP * damageMod); } NetState.HitPoints = Mathf.Min(NetState.CharacterData.BaseHP.Value, NetState.HitPoints + HP); if (m_AIBrain != null) { //let the brain know about the modified amount of damage we received. m_AIBrain.ReceiveHP(inflicter, HP); } //we can't currently heal a dead character back to Alive state. //that's handled by a separate function. if (NetState.HitPoints <= 0) { ClearActions(false); if (IsNpc) { if (m_KilledDestroyDelaySeconds >= 0.0f && NetState.NetworkLifeState.Value != LifeState.Dead) { StartCoroutine(KilledDestroyProcess()); } NetState.NetworkLifeState.Value = LifeState.Dead; } else { NetState.NetworkLifeState.Value = LifeState.Fainted; } } }
/// <summary> /// Picks the most appropriate foe for us to attack right now, or null if none are appropriate /// (Currently just chooses the foe closest to us in distance) /// </summary> /// <returns></returns> private ServerCharacter ChooseFoe() { Vector3 myPosition = m_Brain.GetMyServerCharacter().transform.position; float closestDistanceSqr = int.MaxValue; ServerCharacter closestFoe = null; foreach (var foe in m_Brain.GetHatedEnemies()) { float distanceSqr = (myPosition - foe.transform.position).sqrMagnitude; if (distanceSqr < closestDistanceSqr) { closestDistanceSqr = distanceSqr; closestFoe = foe; } } return(closestFoe); }
/// <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(); } }
public override void Initialize() { m_AttackActions = new List <ActionType>(); if (m_Brain.CharacterData.Skill1 != ActionType.None) { m_AttackActions.Add(m_Brain.CharacterData.Skill1); } if (m_Brain.CharacterData.Skill2 != ActionType.None) { m_AttackActions.Add(m_Brain.CharacterData.Skill2); } if (m_Brain.CharacterData.Skill3 != ActionType.None) { m_AttackActions.Add(m_Brain.CharacterData.Skill3); } // pick a starting attack action from the possible m_CurAttackAction = m_AttackActions[Random.Range(0, m_AttackActions.Count)]; // clear any old foe info; we'll choose a new one in Update() m_Foe = null; }
public ChargedLaunchProjectileAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
public ActionPlayer(ServerCharacter parent) { m_Parent = parent; m_Queue = new List <Action>(); m_NonBlockingActions = new List <Action>(); }
public FXProjectileTargetedAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
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) { }
/// <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. }
public ChargedShieldAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }
public StealthModeAction(ServerCharacter parent, ref ActionRequestData data) : base(parent, ref data) { }