private void Start() { // Setup light and shadows myLight = GetComponent <Light>(); myLight.enabled = EnableLight; forceDisableSpellLighting = !DaggerfallUnity.Settings.EnableSpellLighting; if (forceDisableSpellLighting) { myLight.enabled = false; } if (!DaggerfallUnity.Settings.EnableSpellShadows) { myLight.shadows = LightShadows.None; } initialRange = myLight.range; initialIntensity = myLight.intensity; // Setup collider myCollider = GetComponent <SphereCollider>(); myCollider.radius = ColliderRadius; // Setup rigidbody myRigidbody = GetComponent <Rigidbody>(); myRigidbody.useGravity = false; // Use payload when available if (payload != null) { // Set payload missile properties caster = payload.CasterEntityBehaviour; targetType = payload.Settings.TargetType; elementType = payload.Settings.ElementType; // Set spell billboard anims automatically from payload for mobile missiles if (targetType == TargetTypes.SingleTargetAtRange || targetType == TargetTypes.AreaAtRange) { UseSpellBillboardAnims(); } } // Setup senses if (caster != GameManager.Instance.PlayerEntityBehaviour) { enemySenses = caster.GetComponent <EnemySenses>(); } // Setup arrow if (isArrow) { // Create and orient 3d arrow goModel = GameObjectHelper.CreateDaggerfallMeshGameObject(99800, transform); MeshCollider arrowCollider = GetComponent <MeshCollider>(); arrowCollider.sharedMesh = goModel.GetComponent <MeshFilter>().sharedMesh; // Offset up so it comes from same place LOS check is done from Vector3 adjust; if (caster != GameManager.Instance.PlayerEntityBehaviour) { CharacterController controller = caster.transform.GetComponent <CharacterController>(); adjust = caster.transform.forward * 0.6f; adjust.y += controller.height / 3; } else { // Offset forward to avoid collision with player adjust = GameManager.Instance.MainCamera.transform.forward * 0.6f; // Adjust slightly downward to match bow animation adjust.y -= 0.11f; // Adjust to the right or left to match bow animation if (!GameManager.Instance.WeaponManager.ScreenWeapon.FlipHorizontal) { adjust += GameManager.Instance.MainCamera.transform.right * 0.15f; } else { adjust -= GameManager.Instance.MainCamera.transform.right * 0.15f; } } goModel.transform.localPosition = adjust; goModel.transform.rotation = Quaternion.LookRotation(GetAimDirection()); } // Ignore missile collision with caster (this is a different check to AOE targets) if (caster) { Physics.IgnoreCollision(caster.GetComponent <Collider>(), this.GetComponent <Collider>()); } }
void GetTargets() { DaggerfallEntityBehaviour highestPriorityTarget = null; DaggerfallEntityBehaviour secondHighestPriorityTarget = null; float highestPriority = -1; float secondHighestPriority = -1; bool sawSelectedTarget = false; Vector3 directionToTargetHolder = directionToTarget; float distanceToTargetHolder = distanceToTarget; DaggerfallEntityBehaviour[] entityBehaviours = FindObjectsOfType <DaggerfallEntityBehaviour>(); for (int i = 0; i < entityBehaviours.Length; i++) { DaggerfallEntityBehaviour targetBehaviour = entityBehaviours[i]; EnemyEntity targetEntity = null; if (targetBehaviour != player) { targetEntity = targetBehaviour.Entity as EnemyEntity; } // Can't target self if (targetBehaviour == entityBehaviour) { continue; } // Evaluate potential targets if (targetBehaviour.EntityType == EntityTypes.EnemyMonster || targetBehaviour.EntityType == EntityTypes.EnemyClass || targetBehaviour.EntityType == EntityTypes.Player) { // NoTarget mode if ((GameManager.Instance.PlayerEntity.NoTargetMode || !motor.IsHostile || enemyEntity.MobileEnemy.Team == MobileTeams.PlayerAlly) && targetBehaviour == player) { continue; } // Can't target ally if (targetBehaviour == player && enemyEntity.Team == MobileTeams.PlayerAlly) { continue; } else if (DaggerfallUnity.Settings.EnemyInfighting) { if (targetEntity != null && targetEntity.Team == enemyEntity.Team) { continue; } } else { if (targetBehaviour != player && enemyEntity.MobileEnemy.Team != MobileTeams.PlayerAlly) { continue; } } // For now, quest AI only targets player if (questBehaviour && targetBehaviour != player) { continue; } EnemySenses targetSenses = null; if (targetBehaviour.EntityType == EntityTypes.EnemyMonster || targetBehaviour.EntityType == EntityTypes.EnemyClass) { targetSenses = targetBehaviour.GetComponent <EnemySenses>(); } // For now, quest AI can't be targeted if (targetSenses && targetSenses.QuestBehaviour && !targetSenses.QuestBehaviour.IsAttackableByAI) { continue; } Vector3 toTarget = targetBehaviour.transform.position - transform.position; directionToTarget = toTarget.normalized; distanceToTarget = toTarget.magnitude; bool see = CanSeeTarget(targetBehaviour); // Is potential target neither visible nor in area around player? If so, reject as target. if (targetSenses && !targetSenses.WouldBeSpawnedInClassic && !see) { continue; } float priority = 0; // Add 5 priority if this potential target isn't already targeting someone if (targetSenses && targetSenses.Target == null) { priority += 5; } if (see) { priority += 10; } // Add distance priority float distancePriority = 30 - distanceToTarget; if (distancePriority < 0) { distancePriority = 0; } priority += distancePriority; if (priority > highestPriority) { secondHighestPriority = highestPriority; highestPriority = priority; secondHighestPriorityTarget = highestPriorityTarget; highestPriorityTarget = targetBehaviour; sawSecondaryTarget = sawSelectedTarget; sawSelectedTarget = see; directionToTargetHolder = directionToTarget; distanceToTargetHolder = distanceToTarget; } else if (priority > secondHighestPriority) { sawSecondaryTarget = see; secondHighestPriority = priority; secondHighestPriorityTarget = targetBehaviour; } } } // Restore direction and distance values directionToTarget = directionToTargetHolder; distanceToTarget = distanceToTargetHolder; targetInSight = sawSelectedTarget; target = highestPriorityTarget; if (DaggerfallUnity.Settings.EnhancedCombatAI && secondHighestPriorityTarget) { secondaryTarget = secondHighestPriorityTarget; if (sawSecondaryTarget) { secondaryTargetPos = secondaryTarget.transform.position; } } }
void FixedUpdate() { if (GameManager.Instance.DisableAI) { return; } targetPosPredictTimer += Time.deltaTime; if (targetPosPredictTimer >= predictionInterval) { targetPosPredictTimer = 0f; targetPosPredict = true; } else { targetPosPredict = false; } // Update whether enemy would be spawned or not in classic. // Only check if within the maximum possible distance (Just under 1094 classic units) if (GameManager.ClassicUpdate) { if (distanceToPlayer < 1094 * MeshReader.GlobalScale) { float upperXZ; float upperY = 0; float lowerY = 0; bool playerInside = GameManager.Instance.PlayerGPS.GetComponent <PlayerEnterExit>().IsPlayerInside; if (!playerInside) { upperXZ = classicSpawnDespawnExterior; } else { if (!wouldBeSpawnedInClassic) { upperXZ = classicSpawnXZDist; upperY = classicSpawnYDistUpper; lowerY = classicSpawnYDistLower; } else { upperXZ = classicDespawnXZDist; upperY = classicDespawnYDist; } } float YDiffToPlayer = transform.position.y - player.transform.position.y; float YDiffToPlayerAbs = Mathf.Abs(YDiffToPlayer); float distanceToPlayerXZ = Mathf.Sqrt(distanceToPlayer * distanceToPlayer - YDiffToPlayerAbs * YDiffToPlayerAbs); wouldBeSpawnedInClassic = true; if (distanceToPlayerXZ > upperXZ) { wouldBeSpawnedInClassic = false; } if (playerInside) { if (lowerY == 0) { if (YDiffToPlayerAbs > upperY) { wouldBeSpawnedInClassic = false; } } else if (YDiffToPlayer < lowerY || YDiffToPlayer > upperY) { wouldBeSpawnedInClassic = false; } } } else { wouldBeSpawnedInClassic = false; } } if (GameManager.ClassicUpdate) { classicTargetUpdateTimer += Time.deltaTime / systemTimerUpdatesDivisor; if (target != null && target.Entity.CurrentHealth <= 0) { target = null; } // Non-hostile mode if (GameManager.Instance.PlayerEntity.NoTargetMode || !motor.IsHostile) { if (target == player) { target = null; } if (secondaryTarget == player) { secondaryTarget = null; } } // Reset these values if no target if (target == null) { lastKnownTargetPos = ResetPlayerPos; predictedTargetPos = ResetPlayerPos; directionToTarget = ResetPlayerPos; lastDistanceToTarget = 0; targetRateOfApproach = 0; distanceToTarget = 0; targetSenses = null; // If we have a valid secondary target that we acquired when we got the primary, switch to it. // There will only be a secondary target if using enhanced combat AI. if (secondaryTarget != null && secondaryTarget.Entity.CurrentHealth > 0) { target = secondaryTarget; // If the secondary target was actually seen, use the last place we saw it to begin pursuit. if (sawSecondaryTarget) { lastKnownTargetPos = secondaryTargetPos; } awareOfTargetForLastPrediction = false; } } // Compare change in target position to give AI some ability to read opponent's movements if (target != null && target == targetOnLastUpdate) { if (DaggerfallUnity.Settings.EnhancedCombatAI) { targetRateOfApproach = (lastDistanceToTarget - distanceToTarget); } } else { lastDistanceToTarget = 0; targetRateOfApproach = 0; } if (target != null) { lastDistanceToTarget = distanceToTarget; targetOnLastUpdate = target; } } if (player != null) { // Get distance to player Vector3 toPlayer = player.transform.position - transform.position; distanceToPlayer = toPlayer.magnitude; // If out of classic spawn range, still check for direct LOS to player so that enemies who see player will // try to attack. if (!wouldBeSpawnedInClassic) { distanceToTarget = distanceToPlayer; directionToTarget = toPlayer.normalized; playerInSight = CanSeeTarget(player); } if (classicTargetUpdateTimer > 5) { classicTargetUpdateTimer = 0f; // Is enemy in area around player or can see player? if (wouldBeSpawnedInClassic || playerInSight) { GetTargets(); if (target != null && target != player) { targetSenses = target.GetComponent <EnemySenses>(); } else { targetSenses = null; } } // Make targeted character also target this character if it doesn't have a target yet. if (target != null && targetSenses && targetSenses.Target == null) { targetSenses.Target = entityBehaviour; } } if (target == null) { targetInSight = false; detectedTarget = false; return; } if (!wouldBeSpawnedInClassic && target == player) { distanceToTarget = distanceToPlayer; directionToTarget = toPlayer.normalized; targetInSight = playerInSight; } else { Vector3 toTarget = target.transform.position - transform.position; distanceToTarget = toTarget.magnitude; directionToTarget = toTarget.normalized; targetInSight = CanSeeTarget(target); } // Classic stealth mechanics would be interfered with by hearing, so only enable // hearing if the enemy has detected the target. If target is visible we can omit hearing. if (detectedTarget && !targetInSight) { targetInEarshot = CanHearTarget(); } else { targetInEarshot = false; } // Note: In classic an enemy can continue to track the player as long as their // giveUpTimer is > 0. Since the timer is reset to 200 on every detection this // would make chameleon and shade essentially useless, since the enemy is sure // to detect the player during one of the many AI updates. Here, the enemy has to // successfully see through the illusion spell each classic update to continue // to know where the player is. if (GameManager.ClassicUpdate) { blockedByIllusionEffect = BlockedByIllusionEffect(); if (lastHadLOSTimer > 0) { lastHadLOSTimer--; } } if (!blockedByIllusionEffect && (targetInSight || targetInEarshot)) { detectedTarget = true; lastKnownTargetPos = target.transform.position; lastHadLOSTimer = 200f; } else if (!blockedByIllusionEffect && StealthCheck()) { detectedTarget = true; // Only get the target's location from the stealth check if we haven't had // actual LOS for a while. This gives better pursuit behavior since enemies // will go to the last spot they saw the player instead of walking into walls. if (lastHadLOSTimer <= 0) { lastKnownTargetPos = target.transform.position; } } else { detectedTarget = false; } if (oldLastKnownTargetPos == ResetPlayerPos) { oldLastKnownTargetPos = lastKnownTargetPos; } if (predictedTargetPos == ResetPlayerPos || !DaggerfallUnity.Settings.EnhancedCombatAI) { predictedTargetPos = lastKnownTargetPos; } // Predict target's next position if (targetPosPredict && lastKnownTargetPos != ResetPlayerPos) { // Be sure to only take difference of movement if we've seen the target for two consecutive prediction updates if (!blockedByIllusionEffect && targetInSight) { if (awareOfTargetForLastPrediction) { lastPositionDiff = lastKnownTargetPos - oldLastKnownTargetPos; } // Store current last known target position for next prediction update oldLastKnownTargetPos = lastKnownTargetPos; awareOfTargetForLastPrediction = true; } else { awareOfTargetForLastPrediction = false; } if (DaggerfallUnity.Settings.EnhancedCombatAI) { float moveSpeed = (enemyEntity.Stats.LiveSpeed + PlayerSpeedChanger.dfWalkBase) * MeshReader.GlobalScale; predictedTargetPos = PredictNextTargetPos(moveSpeed); } } if (detectedTarget && !hasEncounteredPlayer && target == player) { hasEncounteredPlayer = true; // Check appropriate language skill to see if player can pacify enemy if (!questBehaviour && entityBehaviour && motor && (entityBehaviour.EntityType == EntityTypes.EnemyMonster || entityBehaviour.EntityType == EntityTypes.EnemyClass)) { DFCareer.Skills languageSkill = enemyEntity.GetLanguageSkill(); if (languageSkill != DFCareer.Skills.None) { PlayerEntity player = GameManager.Instance.PlayerEntity; if (FormulaHelper.CalculateEnemyPacification(player, languageSkill)) { motor.IsHostile = false; DaggerfallUI.AddHUDText(HardStrings.languagePacified.Replace("%e", enemyEntity.Name).Replace("%s", languageSkill.ToString()), 5); player.TallySkill(languageSkill, 3); // BCHG: increased skill uses from 1 in classic on success to make raising language skills easier } else if (languageSkill != DFCareer.Skills.Etiquette && languageSkill != DFCareer.Skills.Streetwise) { player.TallySkill(languageSkill, 1); } } } } } // If target is player and in sight then raise enemy alert on player // This can only be lowered again by killing an enemy or escaping for some amount of time // Any enemies actively targeting player will continue to raise alert state if (Target == GameManager.Instance.PlayerEntityBehaviour && TargetInSight) { GameManager.Instance.PlayerEntity.SetEnemyAlert(true); } }
void CompleteDeath() { if (!entityBehaviour) { return; } // If enemy associated with quest system, make sure quest system is done with it first QuestResourceBehaviour questResourceBehaviour = GetComponent <QuestResourceBehaviour>(); if (questResourceBehaviour) { if (!questResourceBehaviour.IsFoeDead) { return; } } // Disable enemy gameobject // Do not destroy as we must still save enemy state when dead gameObject.SetActive(false); // Show death message string deathMessage = TextManager.Instance.GetLocalizedText("thingJustDied"); deathMessage = deathMessage.Replace("%s", TextManager.Instance.GetLocalizedEnemyName(mobile.Summary.Enemy.ID)); DaggerfallUI.Instance.PopupMessage(deathMessage); // Generate lootable corpse marker DaggerfallLoot loot = GameObjectHelper.CreateLootableCorpseMarker( GameManager.Instance.PlayerObject, entityBehaviour.gameObject, enemyEntity, mobile.Summary.Enemy.CorpseTexture, DaggerfallUnity.NextUID); // This is still required so enemy equipment is not marked as equipped // This item collection is transferred to loot container below for (int i = (int)Items.EquipSlots.Head; i <= (int)Items.EquipSlots.Feet; i++) { Items.DaggerfallUnityItem item = enemyEntity.ItemEquipTable.GetItem((Items.EquipSlots)i); if (item != null) { enemyEntity.ItemEquipTable.UnequipItem((Items.EquipSlots)i); } } entityBehaviour.CorpseLootContainer = loot; // Transfer any items owned by entity to loot container // Many quests will stash a reward in enemy inventory for player to find // This will be in addition to normal random loot table generation loot.Items.TransferAll(entityBehaviour.Entity.Items); // Play body collapse sound if (DaggerfallUI.Instance.DaggerfallAudioSource) { DaggerfallUI.Instance.DaggerfallAudioSource.PlayClipAtPoint(SoundClips.BodyFall, loot.transform.position, 1f); } // Lower enemy alert state on player now that enemy is dead // If this is final enemy targeting player then alert state will remain clear // Other enemies still targeting player will continue to raise alert state every update EnemySenses senses = entityBehaviour.GetComponent <EnemySenses>(); if (senses && senses.Target == GameManager.Instance.PlayerEntityBehaviour) { GameManager.Instance.PlayerEntity.SetEnemyAlert(false); } // Raise static event if (OnEnemyDeath != null) { OnEnemyDeath(this, null); } }
void Start() { senses = GetComponent<EnemySenses>(); controller = GetComponent<CharacterController>(); mobile = GetComponentInChildren<DaggerfallMobileUnit>(); isHostile = (mobile.Summary.Enemy.Reactions == MobileReactions.Hostile); }
void Start() { motor = GetComponent<EnemyMotor>(); senses = GetComponent<EnemySenses>(); sounds = GetComponent<EnemySounds>(); mobile = GetComponentInChildren<DaggerfallMobileUnit>(); entityBehaviour = GetComponent<DaggerfallEntityBehaviour>(); }