diff --git a/Assets/Scripts/Game/DaggerfallMissile.cs b/Assets/Scripts/Game/DaggerfallMissile.cs index e19184f823..20ff7a5f2a 100644 --- a/Assets/Scripts/Game/DaggerfallMissile.cs +++ b/Assets/Scripts/Game/DaggerfallMissile.cs @@ -64,6 +64,20 @@ public class DaggerfallMissile : MonoBehaviour public const float SphereCastRadius = 0.25f; public const float TouchRange = 3.0f; + private static int TouchMask = -1; + + private static readonly Collider[] aoeBuffer = new Collider[64]; + private readonly List tmpTargets = new List(32); + + // Cached references + private GameManager gm; + private Camera mainCamera; + private WeaponManager weaponManager; + private Collider casterCollider; + private EnemySenses cachedEnemySenses; + private EnemyAttack cachedEnemyAttack; + private CharacterController casterController; + Vector3 direction; Light myLight; SphereCollider myCollider; @@ -169,88 +183,106 @@ public DaggerfallEntityBehaviour[] Targets private void Awake() { audioSource = transform.GetComponent(); + + gm = GameManager.Instance; + mainCamera = gm.MainCamera; + weaponManager = gm.WeaponManager; + audioSource = GetComponent(); } private void Start() { // Setup light and shadows myLight = GetComponent(); - myLight.enabled = EnableLight; + myLight.enabled = EnableLight && DaggerfallUnity.Settings.EnableSpellLighting; forceDisableSpellLighting = !DaggerfallUnity.Settings.EnableSpellLighting; - if (forceDisableSpellLighting) myLight.enabled = false; - if (!DaggerfallUnity.Settings.EnableSpellShadows) myLight.shadows = LightShadows.None; + if (!DaggerfallUnity.Settings.EnableSpellShadows) + { + myLight.shadows = LightShadows.None; + } + initialRange = myLight.range; initialIntensity = myLight.intensity; - // Setup collider + // Setup collider and rigidbody myCollider = GetComponent(); myCollider.radius = ColliderRadius; - // Setup rigidbody myRigidbody = GetComponent(); myRigidbody.useGravity = false; - // Use payload when available + // Setup payload 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) + if (targetType == TargetTypes.SingleTargetAtRange || targetType == TargetTypes.AreaAtRange) { UseSpellBillboardAnims(); } } - // Setup senses - if (caster && caster != GameManager.Instance.PlayerEntityBehaviour) + // Cache caster components + if (caster) + { + casterCollider = caster.GetComponent(); + cachedEnemySenses = caster.GetComponent(); + cachedEnemyAttack = caster.GetComponent(); + casterController = caster.GetComponent(); + + var missileCollider = GetComponent(); + if (casterCollider && missileCollider) + { + Physics.IgnoreCollision(casterCollider, missileCollider); + } + } + + // Cache enemy senses (non-player casters only) + if (caster && caster != gm.PlayerEntityBehaviour) { - enemySenses = caster.GetComponent(); + enemySenses = cachedEnemySenses; } // Setup arrow if (isArrow) { - // Create and orient 3d arrow goModel = GameObjectHelper.CreateDaggerfallMeshGameObject(99800, transform); - MeshCollider arrowCollider = goModel.GetComponent(); + var arrowCollider = goModel.GetComponent(); arrowCollider.sharedMesh = goModel.GetComponent().sharedMesh; arrowCollider.convex = true; arrowCollider.isTrigger = true; - // Offset up so it comes from same place LOS check is done from Vector3 adjust; - if (caster != GameManager.Instance.PlayerEntityBehaviour) + if (caster != gm.PlayerEntityBehaviour) { - CharacterController controller = caster.transform.GetComponent(); adjust = caster.transform.forward * 0.6f; - adjust.y += controller.height / 3; + if (casterController) + { + adjust.y += casterController.height / 3f; + } } else { - // Adjust slightly downward to match bow animation - adjust = (GameManager.Instance.MainCamera.transform.rotation * -Caster.transform.up) * 0.11f; - // Offset forward to avoid collision with player - adjust += GameManager.Instance.MainCamera.transform.forward * 0.6f; - // Adjust to the right or left to match bow animation - if (!GameManager.Instance.WeaponManager.ScreenWeapon.FlipHorizontal) - adjust += GameManager.Instance.MainCamera.transform.right * 0.15f; + adjust = (gm.MainCamera.transform.rotation * -caster.transform.up) * 0.11f; + adjust += gm.MainCamera.transform.forward * 0.6f; + + var right = gm.MainCamera.transform.right * 0.15f; + if (!gm.WeaponManager.ScreenWeapon.FlipHorizontal) + { + adjust += right; + } else - adjust -= GameManager.Instance.MainCamera.transform.right * 0.15f; + { + adjust -= right; + } } goModel.transform.localPosition = adjust; goModel.transform.rotation = Quaternion.LookRotation(GetAimDirection()); goModel.layer = gameObject.layer; } - - // Ignore missile collision with caster (this is a different check to AOE targets) - if (caster) - Physics.IgnoreCollision(caster.GetComponent(), this.GetComponent()); } private void Update() @@ -390,15 +422,19 @@ void DoCollision(Collision collision, Collider other) public static DaggerfallEntityBehaviour GetEntityTargetInTouchRange(Vector3 aimPosition, Vector3 aimDirection) { - // Fire ray along caster facing - // Origin point of ray is set back slightly to fix issue where strikes against target capsules touching caster capsule do not connect + // Nudge origin slightly forward to avoid intersecting the caster capsule when looking down + const float originNudge = 0.01f; + Vector3 origin = aimPosition + aimDirection * originNudge; + RaycastHit hit; - aimPosition -= aimDirection * 0.1f; - Ray ray = new Ray(aimPosition, aimDirection); - if (Physics.SphereCast(ray, SphereCastRadius, out hit, TouchRange)) + Ray ray = new Ray(origin, aimDirection); + + if (Physics.SphereCast(ray, SphereCastRadius, out hit, TouchRange, GetTouchMask(), QueryTriggerInteraction.Ignore)) + { return hit.transform.GetComponent(); - else - return null; + } + + return null; } #endregion @@ -438,34 +474,70 @@ void DoMissile() // AOE can strike any number of targets within range with an option to exclude caster void DoAreaOfEffect(Vector3 position, bool ignoreCaster = false) { - List entities = new List(); - transform.position = position; - // Collect AOE targets and ignore duplicates - Collider[] overlaps = Physics.OverlapSphere(position, ExplosionRadius); - for (int i = 0; i < overlaps.Length; i++) + int count = Physics.OverlapSphereNonAlloc(position, ExplosionRadius, aoeBuffer, GetTouchMask(), QueryTriggerInteraction.Ignore); + tmpTargets.Clear(); + + for (int i = 0; i < count; i++) { - DaggerfallEntityBehaviour aoeEntity = overlaps[i].GetComponent(); + var beh = aoeBuffer[i].GetComponent(); + if (!beh) + { + continue; + } - if (ignoreCaster && aoeEntity == caster) + if (ignoreCaster && beh == caster) + { continue; + } - if (aoeEntity && !targetEntities.Contains(aoeEntity)) + if (!targetEntities.Contains(beh)) { - entities.Add(aoeEntity); - //Debug.LogFormat("Missile hit target {0} by AOE", aoeEntity.name); + tmpTargets.Add(beh); } } - // Add collection to target entities - if (entities.Count > 0) - targetEntities.AddRange(entities); + if (tmpTargets.Count > 0) + { + targetEntities.AddRange(tmpTargets); + } impactDetected = true; missileReleased = true; } + /// + /// Returns a cached layer mask for touch targeting. Built lazily to avoid Unity init errors. + /// Excludes Player, Ignore Raycast, and Automap so the cast never hits the caster or UI layers. + /// + private static int GetTouchMask() + { + if (TouchMask != -1) + { + return TouchMask; + } + + int mask = Physics.DefaultRaycastLayers; + + int player = LayerMask.NameToLayer("Player"); + if (player >= 0) + { + mask &= ~(1 << player); + } + + mask &= ~(1 << Physics.IgnoreRaycastLayer); + + int automap = LayerMask.NameToLayer("Automap"); + if (automap >= 0) + { + mask &= ~(1 << automap); + } + + TouchMask = mask; + return TouchMask; + } + // Get missile aim position from player or enemy mobile Vector3 GetAimPosition() { @@ -608,7 +680,14 @@ void AssignBowDamageToTarget(Collider arrowHitCollider) else { Transform hitTransform = arrowHitCollider.gameObject.transform; - GameManager.Instance.WeaponManager.WeaponDamage(GameManager.Instance.WeaponManager.LastBowUsed, true, isArrowSummoned, hitTransform, hitTransform.position, goModel.transform.forward); + + GameManager.Instance.WeaponManager.WeaponDamage( + GameManager.Instance.WeaponManager.LastBowUsed, + true, + isArrowSummoned, + hitTransform, + hitTransform.position, + goModel.transform.forward); } } diff --git a/Assets/Scripts/Game/WeaponManager.cs b/Assets/Scripts/Game/WeaponManager.cs index f26f276ad6..7340d21ec5 100644 --- a/Assets/Scripts/Game/WeaponManager.cs +++ b/Assets/Scripts/Game/WeaponManager.cs @@ -193,12 +193,15 @@ public enum MouseDirections void Start() { - //weaponSensitivity = DaggerfallUnity.Settings.WeaponSensitivity; mainCamera = GameObject.FindGameObjectWithTag("MainCamera"); - player = transform.gameObject; - playerLayerMask = ~(1 << LayerMask.NameToLayer("Player")); + player = gameObject; + + playerLayerMask = Physics.DefaultRaycastLayers; + playerLayerMask &= ~(1 << LayerMask.NameToLayer("Player")); + playerLayerMask &= ~(1 << Physics.IgnoreRaycastLayer); + _gesture = new Gesture(); - _longestDim = Math.Max(Screen.width, Screen.height); + _longestDim = Mathf.Max(Screen.width, Screen.height); SetMelee(ScreenWeapon); }