private void UpdateState() { //forced death check if (CurrentAiState != ActorAiState.Dead) { if (Health <= 0) { EnterState(ActorAiState.Dead); } } //hack to retrieve swizzled target after a load GetSwizzledTarget(); switch (CurrentAiState) { case ActorAiState.Idle: if (Aggressive) { //search for targets, select target SelectTarget(); if (Target != null) { EnterState(ActorAiState.Chasing); } } break; case ActorAiState.Wandering: //TODO aggression if (MovementComponent.DistToTarget <= WanderThreshold || TimeInState >= WanderTimeout) { Vector2 newpos = VectorUtils.GetRandomVector2(InitialPosition.GetFlatVector(), WanderRadius); MovementComponent.SetDestination(newpos.GetSpaceVector()); TimeInState = 0; } if (Aggressive) { //search for targets, select target SelectTarget(); if (Target != null) { EnterState(ActorAiState.Chasing); } } break; case ActorAiState.Chasing: if (!RpgWorldUtils.TargetIsAlive(Target)) { EnterState(BaseAiState); break; } if ((MetaState.Instance.SessionFlags.Contains("NoTarget") || GameState.Instance.PlayerFlags.Contains(PlayerFlags.NoTarget)) && Target.GetComponent <PlayerController>()) { EnterState(BaseAiState); break; } if (AttackComponent != null && AttackComponent.ReadyToAttack && AttackComponent.TargetInRange) { EnterState(ActorAiState.Attacking); return; } else { //set target var d = Target.position; MovementComponent.SetDestination(d); } if (!Relentless) { //break off if we are too far away or too badly hurt if (Health <= (MaxHealth * 0.2f)) { EnterState(ActorAiState.Fleeing); } else if ((Target.position - transform.position).magnitude > SearchRadius) { EnterState(BaseAiState); Target = null; } } break; case ActorAiState.ScriptedMoveTo: if (MovementComponent.AtTarget) //we made it! { EnterState(ActorAiState.Idle); //don't wander off if you were sent there! } break; case ActorAiState.Attacking: //wait... if (!AttackComponent.DidAttack && AttackComponent.WarmupIsDone) { AttackComponent.DoAttack(); //waaaaay too complicated to cram here } if (AttackComponent.AttackIsDone) { //just return if (!RpgWorldUtils.TargetIsAlive(Target)) { EnterState(BaseAiState); } else { EnterState(ActorAiState.Chasing); } } break; case ActorAiState.Hurting: if (TimeInState >= PainWaitTime) { if (BeenHit && Target != null) { EnterState(ActorAiState.Chasing); } else { EnterState(LastAiState); } } break; case ActorAiState.Fleeing: //stop running if far enough away, or target is gone if (!RpgWorldUtils.TargetIsAlive(Target) || (Target.position - transform.position).magnitude > SearchRadius) { EnterState(BaseAiState); Target = null; break; } { //set target var d = transform.position + ((Target.position - transform.position).normalized * -1); MovementComponent.SetDestination(d); } break; } }
private void SelectTarget() { var gameplayConfig = ConfigState.Instance.GetGameplayConfig(); if (TotalTickCount % SearchInterval * (1f / gameplayConfig.Difficulty.ActorAggression) != 0) { return; } var detectionDifficultyFactor = 1f / gameplayConfig.Difficulty.ActorPerception; //check player first since it's (relatively) cheap if (FactionModel.GetRelation(Faction, "Player") == FactionRelationStatus.Hostile && !MetaState.Instance.SessionFlags.Contains("NoTarget") && !GameState.Instance.PlayerFlags.Contains(PlayerFlags.NoTarget)) { var playerObj = WorldUtils.GetPlayerObject(); if (playerObj != null && RpgWorldUtils.TargetIsAlive(playerObj.transform)) { PlayerController pc = playerObj.GetComponent <PlayerController>(); if ((playerObj.transform.position - transform.position).magnitude <= SearchRadius && UnityEngine.Random.Range(0f, 1f * detectionDifficultyFactor) <= RpgValues.DetectionChance(GameState.Instance.PlayerRpgState, pc.MovementComponent.IsCrouching, pc.MovementComponent.IsRunning)) { if (UseLineOfSight) { //additional check RaycastHit hitinfo; if (Physics.Raycast(transform.position + new Vector3(0, 1.0f, 0), (playerObj.transform.position - transform.position), out hitinfo)) { if (hitinfo.collider.gameObject == playerObj) { Target = playerObj.transform; return; } } } else { //otherwise, close enough Target = playerObj.transform; return; } } } } //if(TargetNpc) { //var sw = System.Diagnostics.Stopwatch.StartNew(); //new code should be faster if n is large but it may not bear out in practice, and it probably allocs more dedotated wam var colliders = Physics.OverlapSphere(transform.position, SearchRadius, LayerMask.GetMask("Default", "ActorHitbox")); HashSet <ActorController> potentialTargets = new HashSet <ActorController>(); foreach (var collider in colliders) { var actorController = collider.GetComponent <ActorController>(); if (actorController != null) { potentialTargets.Add(actorController); break; } var hitboxComponent = collider.GetComponent <IHitboxComponent>(); if (hitboxComponent != null && hitboxComponent.ParentController is ActorController hitboxAC) { potentialTargets.Add(hitboxAC); break; } } //old stupid code: should work well enough as long as n is small and your computer is fast enough //IEnumerable<ActorController> potentialTargets = transform.root.GetComponentsInChildren<ActorController>(); foreach (var potentialTarget in potentialTargets) { if (RpgWorldUtils.TargetIsAlive(potentialTarget.transform) && (potentialTarget.transform.position - transform.position).magnitude <= SearchRadius && FactionModel.GetRelation(Faction, potentialTarget.Faction) == FactionRelationStatus.Hostile && !(potentialTarget == this)) { //roll some dice if (potentialTarget.Detectability < 1 && UnityEngine.Random.Range(0f, 1f * detectionDifficultyFactor) > potentialTarget.Detectability) //probably correct { continue; } if (UseLineOfSight) { //additional check RaycastHit hitinfo; if (Physics.Raycast(transform.position + new Vector3(0, 1.0f, 0), (potentialTarget.transform.position - transform.position), out hitinfo)) { if (hitinfo.collider.gameObject == potentialTarget.gameObject) { Target = potentialTarget.transform; break; } } } else { //otherwise, close enough Target = potentialTarget.transform; break; } } } //sw.Stop(); //Debug.Log($"Target lookup: {sw.Elapsed.TotalMilliseconds:F4} ms"); } if (!RpgWorldUtils.TargetIsAlive(Target)) { Target = null; } }
private void SelectTarget() { var gameplayConfig = ConfigState.Instance.GetGameplayConfig(); if (TotalTickCount % SearchInterval * (1f / gameplayConfig.Difficulty.ActorAggression) != 0) { return; } if (TargetPicker != null) { Target = TargetPicker(); return; } var detectionDifficultyFactor = 1f / gameplayConfig.Difficulty.ActorPerception; //check player first since it's (relatively) cheap if (GameState.Instance.FactionState.GetRelation(Faction, "Player") == FactionRelationStatus.Hostile && !MetaState.Instance.SessionFlags.Contains("NoTarget") && !GameState.Instance.PlayerFlags.Contains(PlayerFlags.NoTarget)) { var playerObj = WorldUtils.GetPlayerObject(); if (playerObj != null && RpgWorldUtils.TargetIsAlive(playerObj.transform)) { PlayerController pc = playerObj.GetComponent <PlayerController>(); if ((playerObj.transform.position - transform.position).magnitude <= SearchRadius && UnityEngine.Random.Range(0f, 1f * detectionDifficultyFactor) <= ((IAmTargetable)pc).Detectability) { if (UseLineOfSight) { if (CheckLineOfSight(pc)) { Target = playerObj.transform; return; } } else { //otherwise, close enough Target = playerObj.transform; return; } } } } //if(TargetNpc) { //var sw = System.Diagnostics.Stopwatch.StartNew(); //new code should be faster if n is large but it may not bear out in practice, and it probably allocs more dedotated wam var colliders = Physics.OverlapSphere(transform.position, SearchRadius, WorldUtils.GetAttackLayerMask()); HashSet <IAmTargetable> potentialTargets = new HashSet <IAmTargetable>(); foreach (var collider in colliders) { var baseController = collider.GetComponent <BaseController>(); if (baseController != null) { if (baseController is IAmTargetable iat && iat.ValidTarget) { potentialTargets.Add(iat); } continue; //continue anyway since we've found a base controller and it's either targetable or it's not } var hitboxComponent = collider.GetComponent <IHitboxComponent>(); if (hitboxComponent != null && hitboxComponent.ParentController is IAmTargetable iat2 && iat2.ValidTarget) { potentialTargets.Add(iat2); continue; } } //old stupid code: should work well enough as long as n is small and your computer is fast enough //IEnumerable<ActorController> potentialTargets = transform.root.GetComponentsInChildren<ActorController>(); foreach (var potentialTarget in potentialTargets) { BaseController targetController = potentialTarget as BaseController; if (targetController == null) { continue; } if (RpgWorldUtils.TargetIsAlive(targetController.transform) && (targetController.transform.position - transform.position).magnitude <= SearchRadius && GameState.Instance.FactionState.GetRelation(Faction, potentialTarget.Faction) == FactionRelationStatus.Hostile && !(potentialTarget == this)) { //roll some dice if (potentialTarget.Detectability < 1 && UnityEngine.Random.Range(0f, 1f * detectionDifficultyFactor) > potentialTarget.Detectability) //probably correct { continue; } if (UseLineOfSight) { if (CheckLineOfSight(targetController)) { Target = targetController.transform; break; } } else { //otherwise, close enough Target = targetController.transform; break; } } } //sw.Stop(); //Debug.Log($"Target lookup: {sw.Elapsed.TotalMilliseconds:F4} ms"); } if (!RpgWorldUtils.TargetIsAlive(Target)) { Target = null; } }