diff --git a/Assets/AI/Demon/CrystalShooterAI.cs b/Assets/AI/Demon/CrystalShooterAI.cs index 8188bf83a..5bb05b369 100644 --- a/Assets/AI/Demon/CrystalShooterAI.cs +++ b/Assets/AI/Demon/CrystalShooterAI.cs @@ -13,7 +13,7 @@ namespace DemonBoss.Magic [Tooltip("Fireball prefab (projectile with its own targeting logic)")] public GameObject fireballPrefab; - [Tooltip("Seconds between shots")] + [Tooltip("Base seconds between shots")] public float fireRate = 0.7f; [Tooltip("Maximum number of shots before auto-despawn")] @@ -22,6 +22,16 @@ namespace DemonBoss.Magic [Tooltip("Wait time after last shot before despawn")] public float despawnDelay = 3f; + [Header("Randomization (Desync)")] + [Tooltip("Random initial delay after spawn to desync turrets (seconds)")] + public Vector2 initialStaggerRange = new Vector2(0.0f, 0.6f); + + [Tooltip("Per-shot random jitter added to fireRate (seconds). Range x means +/- x.")] + public float fireRateJitter = 0.2f; + + [Tooltip("Aim wobble in degrees (0 = disabled). Small value adds natural dispersion.")] + public float aimJitterDegrees = 0f; + [Header("Rotation Configuration")] [Tooltip("Yaw rotation speed in degrees per second")] public float turnSpeed = 120f; @@ -81,102 +91,69 @@ namespace DemonBoss.Magic if (muzzle == null) { - // Try to find a child named "muzzle"; fallback to self Transform muzzleChild = crystalTransform.Find("muzzle"); - if (muzzleChild != null) - { - muzzle = muzzleChild; - if (enableDebug) Debug.Log("[CrystalShooterAI] Found muzzle child"); - } - else - { - muzzle = crystalTransform; - if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Using crystal center as muzzle"); - } + muzzle = muzzleChild != null ? muzzleChild : crystalTransform; } } private void Start() { - if (enableDebug) Debug.Log("[CrystalShooterAI] Crystal activated"); - if (autoFindPlayer && target == null) FindPlayer(); StartShooting(); } - /// - /// Update tick: rotate towards target or idle spin. - /// + /// Update tick: rotate towards target or idle spin. private void Update() { if (!isActive) return; RotateTowardsTarget(); } - /// - /// Attempts to find the player by tag (for turret-only aiming). - /// + /// Attempts to find the player by tag (for turret-only aiming). private void FindPlayer() { GameObject player = GameObject.FindGameObjectWithTag(playerTag); - if (player != null) - { - SetTarget(player.transform); - if (enableDebug) Debug.Log("[CrystalShooterAI] Automatically found player"); - } - else if (enableDebug) - { - Debug.LogWarning("[CrystalShooterAI] Cannot find player with tag: " + playerTag); - } + if (player != null) SetTarget(player.transform); } - /// - /// Sets the turret's aiming target (does NOT propagate to projectiles). - /// - public void SetTarget(Transform newTarget) - { - target = newTarget; - if (enableDebug && target != null) - Debug.Log($"[CrystalShooterAI] Set target: {target.name}"); - } + /// Sets the turret's aiming target (does NOT propagate to projectiles). + public void SetTarget(Transform newTarget) => target = newTarget; - /// - /// Starts the timed shooting routine (fires until maxShots, then despawns). - /// + /// Starts the timed shooting routine (fires until maxShots, then despawns). public void StartShooting() { if (isActive) return; isActive = true; shotsFired = 0; - shootingCoroutine = StartCoroutine(ShootingCoroutine()); - if (enableDebug) Debug.Log("[CrystalShooterAI] Starting shooting"); } - /// - /// Stops the shooting routine immediately. - /// + /// Stops the shooting routine immediately. public void StopShooting() { isActive = false; - if (shootingCoroutine != null) { StopCoroutine(shootingCoroutine); shootingCoroutine = null; } - - if (enableDebug) Debug.Log("[CrystalShooterAI] Stopped shooting"); } /// - /// Main shooting loop: checks aim/range → spawns fireball → waits fireRate. - /// After finishing, waits a short delay and despawns the turret. + /// Main shooting loop with initial spawn stagger and per-shot jitter. /// private IEnumerator ShootingCoroutine() { + // 1) Initial stagger so multiple crystals don't start at the same frame + if (initialStaggerRange.y > 0f) + { + float stagger = Random.Range(initialStaggerRange.x, initialStaggerRange.y); + if (stagger > 0f) yield return new WaitForSeconds(stagger); + } + + // 2) Normal loop with CanShoot gate and per-shot jittered waits while (shotsFired < maxShots && isActive) { if (CanShoot()) @@ -186,18 +163,17 @@ namespace DemonBoss.Magic lastShotTime = Time.time; } - yield return new WaitForSeconds(fireRate); + float wait = fireRate + (fireRateJitter > 0f ? Random.Range(-fireRateJitter, fireRateJitter) : 0f); + // Clamp wait to something safe so it never becomes non-positive + if (wait < 0.05f) wait = 0.05f; + yield return new WaitForSeconds(wait); } - if (enableDebug) Debug.Log($"[CrystalShooterAI] Finished shooting ({shotsFired} shots)"); - yield return new WaitForSeconds(despawnDelay); DespawnCrystal(); } - /// - /// Aiming/range gate for firing. - /// + /// Aiming/range gate for firing. private bool CanShoot() { if (target == null) return false; @@ -211,16 +187,10 @@ namespace DemonBoss.Magic return angleToTarget <= aimTolerance; } - /// - /// Spawns a fireball oriented towards the turret's current aim direction. - /// + /// Spawns a fireball oriented towards the turret's current aim direction with optional dispersion. private void FireFireball() { - if (fireballPrefab == null || muzzle == null) - { - if (enableDebug) Debug.LogWarning("[CrystalShooterAI] Missing fireball prefab or muzzle"); - return; - } + if (fireballPrefab == null || muzzle == null) return; Vector3 shootDirection; if (target != null) @@ -228,28 +198,24 @@ namespace DemonBoss.Magic Vector3 targetCenter = target.position + Vector3.up * 1f; shootDirection = (targetCenter - muzzle.position).normalized; } - else + else shootDirection = crystalTransform.forward; + + // Apply small aim jitter (random yaw/pitch) to avoid perfect sync volleys + if (aimJitterDegrees > 0f) { - shootDirection = crystalTransform.forward; + float yaw = Random.Range(-aimJitterDegrees, aimJitterDegrees); + float pitch = Random.Range(-aimJitterDegrees * 0.5f, aimJitterDegrees * 0.5f); // usually less pitch dispersion + shootDirection = Quaternion.Euler(pitch, yaw, 0f) * shootDirection; } Vector3 spawnPosition = muzzle.position; Quaternion spawnRotation = Quaternion.LookRotation(shootDirection); LeanPool.Spawn(fireballPrefab, spawnPosition, spawnRotation); - PlayShootEffects(); - - if (enableDebug) - { - Debug.Log($"[CrystalShooterAI] Shot #{shotsFired + 1} at {spawnPosition} dir: {shootDirection}"); - Debug.DrawRay(spawnPosition, shootDirection * 8f, Color.red, 2f); - } } - /// - /// Plays muzzle VFX and shoot SFX (if enabled). - /// + /// Plays muzzle VFX and shoot SFX (if enabled). private void PlayShootEffects() { if (!useShootEffects) return; @@ -261,14 +227,10 @@ namespace DemonBoss.Magic } if (audioSource != null && shootSound != null) - { audioSource.PlayOneShot(shootSound); - } } - /// - /// Smooth yaw rotation towards target; idles by spinning when no target. - /// + /// Smooth yaw rotation towards target; idles by spinning when no target. private void RotateTowardsTarget() { if (target != null) @@ -292,24 +254,15 @@ namespace DemonBoss.Magic } } - /// - /// Despawns the turret via Lean Pool. - /// + /// Despawns the turret via Lean Pool. public void DespawnCrystal() { - if (enableDebug) Debug.Log("[CrystalShooterAI] Despawning crystal"); StopShooting(); LeanPool.Despawn(gameObject); } - /// - /// Forces immediate despawn (e.g., boss death). - /// - public void ForceDespawn() - { - if (enableDebug) Debug.Log("[CrystalShooterAI] Forced despawn"); - DespawnCrystal(); - } + /// Forces immediate despawn (e.g., boss death). + public void ForceDespawn() => DespawnCrystal(); /// Returns crystal state information. public bool IsActive() => isActive; @@ -320,9 +273,6 @@ namespace DemonBoss.Magic public float GetTimeSinceLastShot() => Time.time - lastShotTime; - /// - /// Gizmos for range and aim visualization. - /// private void OnDrawGizmosSelected() { if (!showGizmos) return; @@ -330,24 +280,6 @@ namespace DemonBoss.Magic Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, maxShootingRange); - if (target != null) - { - Gizmos.color = Color.yellow; - Gizmos.DrawLine(transform.position, target.position); - - if (muzzle != null) - { - Gizmos.color = Color.green; - Vector3 forward = transform.forward * 5f; - Vector3 right = Quaternion.AngleAxis(aimTolerance, transform.up) * forward; - Vector3 left = Quaternion.AngleAxis(-aimTolerance, transform.up) * forward; - - Gizmos.DrawLine(muzzle.position, muzzle.position + right); - Gizmos.DrawLine(muzzle.position, muzzle.position + left); - Gizmos.DrawLine(muzzle.position, muzzle.position + forward); - } - } - if (muzzle != null) { Gizmos.color = Color.blue; diff --git a/Assets/AI/Demon/Crystals_red 1.mat b/Assets/AI/Demon/Crystals_red 1.mat new file mode 100644 index 000000000..a31071947 --- /dev/null +++ b/Assets/AI/Demon/Crystals_red 1.mat @@ -0,0 +1,196 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7966782797081402813 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 10 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Crystals_red 1 + m_Shader: {fileID: -6465566751694194690, guid: 00a0b3897399f8b42a61a0333dd40ced, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - BASETEXTYPE_ALBEDO_EMISSIVE + - USEDISSOLVE_DONT_USE + - USEFRESNEL + - _ALPHATEST_ON + - _USEDISTANCEFADE + m_InvalidKeywords: + - _EMISSION + - _ENVIRONMENTREFLECTIONS_OFF + - _METALLICSPECGLOSSMAP + - _NORMALMAP + m_LightmapFlags: 2 + m_EnableInstancingVariants: 1 + m_DoubleSidedGI: 1 + m_CustomRenderQueue: 2450 + stringTagMap: + RenderType: TransparentCutout + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - BaseTex: + m_Texture: {fileID: 2800000, guid: 7905193ef00a1844e9b9e2c14ce9be7f, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - DissolveMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - NOSMap: + m_Texture: {fileID: 2800000, guid: ead716f17a0a0f347adaf3ff0a109e6a, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BaseMap: + m_Texture: {fileID: 2800000, guid: c0d899801acbe0845902935b9e0e857e, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: 5a004b07e91ff744aa9143b4cdd46a2d, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 284695d770a91574cb92ebad906dc6f4, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: c0d899801acbe0845902935b9e0e857e, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 2800000, guid: 18c6ef854a1f94045bcd407172b8d0f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 2800000, guid: 18c6ef854a1f94045bcd407172b8d0f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - AOStrength: 3 + - AlphaClipThreshold: 0.454 + - BASETEXTYPE: 0 + - DissolveNoiseScale: 25 + - EffectStrenght: 1 + - FresnelPower: 1.37 + - Metalness: 0.13 + - NormalStrength: 1.64 + - Smoothness: 0 + - USEDISSOLVE: 0 + - USEDISSOLVEMASK: 0 + - USEFRESNEL: 1 + - Vector1_473704f964214ae2bc68475022d1524b: 0.05 + - _AlphaClip: 1 + - _AlphaToMask: 1 + - _BendEffect: 0 + - _BendMaxDistance: 1 + - _BendMaxHeight: 0 + - _BendMinDistance: 0.2 + - _BendMinHeight: 1 + - _Blend: 0 + - _BlendModePreserveSpecular: 0 + - _BumpScale: 1 + - _CastShadows: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _EffectThreshold: 0 + - _EmissionScaleUI: 1.5 + - _EnvironmentReflections: 0 + - _FadeDistance: 0.28 + - _FarFadeDistance: 500 + - _GlossMapScale: 0 + - _Glossiness: 0.613 + - _GlossyReflections: 0 + - _InverseFadeRange: 1 + - _InverseFarFadeRange: 0.5 + - _Metallic: 0.772 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _QueueControl: 0 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _Surface: 0 + - _Threshold: 0.184 + - _USEDISTANCEFADE: 1 + - _USESCANWAVE: 0 + - _UVSec: 0 + - _WaveTrail: 4 + - _WorkflowMode: 1 + - _ZTest: 4 + - _ZWrite: 1 + - _ZWriteControl: 0 + m_Colors: + - BaseColor: {r: 0.92593974, g: 0.92593974, b: 0.92593974, a: 1} + - Color_613d1588816440ec9b17710effb7528b: {r: 0, g: 13.98681, b: 714.8679, a: 0} + - EmissiveColor: {r: 0.6886792, g: 0.16098994, b: 0, a: 1.5} + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _BendVector: {r: 0, g: -1, b: 0, a: 0} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 29.508846, g: 3.0899315, b: 0, a: 1.5} + - _EmissionColorUI: {r: 1, g: 0.10344824, b: 0, a: 1} + - _FresnelColor: {r: 1, g: 0.113483936, b: 0, a: 0} + - _ScanWaveColor: {r: 0, g: 0.5949242, b: 1, a: 0} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/AI/Demon/Crystals_red 1.mat.meta b/Assets/AI/Demon/Crystals_red 1.mat.meta new file mode 100644 index 000000000..b1060c5f1 --- /dev/null +++ b/Assets/AI/Demon/Crystals_red 1.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c11c6b446cf6c4b4cb5b5cd72263e342 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AI/Demon/Crystals_red 2.mat b/Assets/AI/Demon/Crystals_red 2.mat new file mode 100644 index 000000000..afd862173 --- /dev/null +++ b/Assets/AI/Demon/Crystals_red 2.mat @@ -0,0 +1,196 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &-7966782797081402813 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: + version: 10 +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Crystals_red 2 + m_Shader: {fileID: -6465566751694194690, guid: 00a0b3897399f8b42a61a0333dd40ced, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - BASETEXTYPE_ALBEDO_EMISSIVE + - USEDISSOLVE_DONT_USE + - USEFRESNEL + - _ALPHATEST_ON + - _USEDISTANCEFADE + m_InvalidKeywords: + - _EMISSION + - _ENVIRONMENTREFLECTIONS_OFF + - _METALLICSPECGLOSSMAP + - _NORMALMAP + m_LightmapFlags: 2 + m_EnableInstancingVariants: 1 + m_DoubleSidedGI: 1 + m_CustomRenderQueue: 2450 + stringTagMap: + RenderType: TransparentCutout + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - BaseTex: + m_Texture: {fileID: 2800000, guid: 7905193ef00a1844e9b9e2c14ce9be7f, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - DissolveMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - NOSMap: + m_Texture: {fileID: 2800000, guid: ead716f17a0a0f347adaf3ff0a109e6a, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BaseMap: + m_Texture: {fileID: 2800000, guid: c0d899801acbe0845902935b9e0e857e, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 2800000, guid: 5a004b07e91ff744aa9143b4cdd46a2d, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 284695d770a91574cb92ebad906dc6f4, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: c0d899801acbe0845902935b9e0e857e, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 2800000, guid: 18c6ef854a1f94045bcd407172b8d0f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 2800000, guid: 18c6ef854a1f94045bcd407172b8d0f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - AOStrength: 3 + - AlphaClipThreshold: 0.454 + - BASETEXTYPE: 0 + - DissolveNoiseScale: 25 + - EffectStrenght: 1 + - FresnelPower: 1.37 + - Metalness: 0.13 + - NormalStrength: 1.64 + - Smoothness: 0 + - USEDISSOLVE: 0 + - USEDISSOLVEMASK: 0 + - USEFRESNEL: 1 + - Vector1_473704f964214ae2bc68475022d1524b: 0.05 + - _AlphaClip: 1 + - _AlphaToMask: 1 + - _BendEffect: 0 + - _BendMaxDistance: 1 + - _BendMaxHeight: 0 + - _BendMinDistance: 0.2 + - _BendMinHeight: 1 + - _Blend: 0 + - _BlendModePreserveSpecular: 0 + - _BumpScale: 1 + - _CastShadows: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _EffectThreshold: 0 + - _EmissionScaleUI: 1.5 + - _EnvironmentReflections: 0 + - _FadeDistance: 0.28 + - _FarFadeDistance: 500 + - _GlossMapScale: 0 + - _Glossiness: 0.613 + - _GlossyReflections: 0 + - _InverseFadeRange: 1 + - _InverseFarFadeRange: 0.5 + - _Metallic: 0.772 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _QueueControl: 0 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _Surface: 0 + - _Threshold: 0.184 + - _USEDISTANCEFADE: 1 + - _USESCANWAVE: 0 + - _UVSec: 0 + - _WaveTrail: 4 + - _WorkflowMode: 1 + - _ZTest: 4 + - _ZWrite: 1 + - _ZWriteControl: 0 + m_Colors: + - BaseColor: {r: 0.92593974, g: 0.92593974, b: 0.92593974, a: 1} + - Color_613d1588816440ec9b17710effb7528b: {r: 0, g: 13.98681, b: 714.8679, a: 0} + - EmissiveColor: {r: 1, g: 1, b: 1, a: 1.5} + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} + - _BendVector: {r: 0, g: -1, b: 0, a: 0} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 29.508846, g: 3.0899315, b: 0, a: 1.5} + - _EmissionColorUI: {r: 1, g: 0.10344824, b: 0, a: 1} + - _FresnelColor: {r: 1, g: 1, b: 1, a: 0} + - _ScanWaveColor: {r: 0, g: 0.5949242, b: 1, a: 0} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/AI/Demon/Crystals_red 2.mat.meta b/Assets/AI/Demon/Crystals_red 2.mat.meta new file mode 100644 index 000000000..39c7d1e96 --- /dev/null +++ b/Assets/AI/Demon/Crystals_red 2.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f94eea72c30ac2e45ab55894421ea48c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AI/Demon/DemonShield.prefab b/Assets/AI/Demon/DemonShield.prefab index 9c84f5a5b..1f2b947c3 100644 --- a/Assets/AI/Demon/DemonShield.prefab +++ b/Assets/AI/Demon/DemonShield.prefab @@ -12,7 +12,7 @@ GameObject: - component: {fileID: 8933020562711606400} - component: {fileID: 2735702040698985183} - component: {fileID: 4889297222623638116} - m_Layer: 0 + m_Layer: 30 m_Name: Shield m_TagString: Untagged m_Icon: {fileID: 0} @@ -4828,7 +4828,7 @@ GameObject: - component: {fileID: 1705028462786019458} - component: {fileID: 636964402995770057} - component: {fileID: 3712794430867809983} - m_Layer: 0 + m_Layer: 30 m_Name: TriggerEnter m_TagString: Untagged m_Icon: {fileID: 0} @@ -4900,7 +4900,7 @@ GameObject: m_Component: - component: {fileID: 5385552833772178802} - component: {fileID: 6288768711713787120} - m_Layer: 0 + m_Layer: 30 m_Name: PerPlatformSettings m_TagString: Untagged m_Icon: {fileID: 0} @@ -4948,7 +4948,10 @@ GameObject: m_Component: - component: {fileID: 7058331355936229169} - component: {fileID: 1024337125332675931} - m_Layer: 0 + - component: {fileID: -842443613141667859} + - component: {fileID: 4897060740913123605} + - component: {fileID: 6803317444044382316} + m_Layer: 30 m_Name: DemonShield m_TagString: Untagged m_Icon: {fileID: 0} @@ -4998,7 +5001,87 @@ CapsuleCollider: m_Radius: 2.5 m_Height: 2.5 m_Direction: 1 - m_Center: {x: 0, y: 1.5, z: 0} + m_Center: {x: 0, y: 1.5, z: -0.8} +--- !u!114 &-842443613141667859 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4089514887418515818} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 64156f4d0e036104895ef313e63c6b09, type: 3} + m_Name: + m_EditorClassIdentifier: + growDuration: 0.5 + scaleCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + - serializedVersion: 3 + time: 1 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0 + outWeight: 0 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!136 &4897060740913123605 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4089514887418515818} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 1 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 3 + m_Height: 3 + m_Direction: 1 + m_Center: {x: 0, y: 1.5, z: -0.8} +--- !u!114 &6803317444044382316 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4089514887418515818} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3a4b288b220c78b4ca00e13cb8a72d6b, type: 3} + m_Name: + m_EditorClassIdentifier: + damage: 15 + knockbackForce: 10 + playerTag: Player + damageInterval: 1 + damageSound: {fileID: 8300000, guid: 0af332725d9792840a4caa29e8991d08, type: 3} + damageEffect: {fileID: 1606542427775616, guid: 0de6da49dab6f8b4d93ab32a4cb441af, + type: 3} + enableDebug: 0 --- !u!1 &5185508652979790054 GameObject: m_ObjectHideFlags: 0 @@ -5011,7 +5094,7 @@ GameObject: - component: {fileID: 2972078956396372929} - component: {fileID: 2302274119979875947} - component: {fileID: 7352519013254002557} - m_Layer: 0 + m_Layer: 30 m_Name: ShieldAdd m_TagString: Untagged m_Icon: {fileID: 0} @@ -9833,7 +9916,7 @@ GameObject: - component: {fileID: 4294035958862554727} - component: {fileID: 8165694420557042206} - component: {fileID: 5904661144955708609} - m_Layer: 0 + m_Layer: 30 m_Name: ShieldFringe m_TagString: Untagged m_Icon: {fileID: 0} @@ -14651,10 +14734,34 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 7058331355936229169} m_Modifications: + - target: {fileID: 1137363236680030, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1300024870466346, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1530244028065882, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1606657272102914, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1849043180036032, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 1881634734537756, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_Name value: shield3 objectReference: {fileID: 0} + - target: {fileID: 1881634734537756, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 4855141465030352, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_LocalPosition.x value: -0.76840776 @@ -14748,14 +14855,38 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 7058331355936229169} m_Modifications: + - target: {fileID: 1137363236680030, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1300024870466346, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1530244028065882, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 1606657272102914, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_Name value: Shield02 objectReference: {fileID: 0} + - target: {fileID: 1606657272102914, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1849043180036032, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 1881634734537756, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_Name value: sield2 objectReference: {fileID: 0} + - target: {fileID: 1881634734537756, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 4855141465030352, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_LocalPosition.x value: 0.561755 @@ -14849,10 +14980,34 @@ PrefabInstance: serializedVersion: 3 m_TransformParent: {fileID: 7058331355936229169} m_Modifications: + - target: {fileID: 1137363236680030, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1300024870466346, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1530244028065882, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1606657272102914, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 1849043180036032, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 1881634734537756, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_Name value: shield3 objectReference: {fileID: 0} + - target: {fileID: 1881634734537756, guid: 5b389d585d7681948a86765d14232bdb, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 4855141465030352, guid: 5b389d585d7681948a86765d14232bdb, type: 3} propertyPath: m_LocalPosition.x value: -0.123969555 diff --git a/Assets/AI/Demon/DestroyableTurret.cs b/Assets/AI/Demon/DestroyableTurret.cs new file mode 100644 index 000000000..949ff2e9f --- /dev/null +++ b/Assets/AI/Demon/DestroyableTurret.cs @@ -0,0 +1,367 @@ +using Invector; +using Lean.Pool; +using UnityEngine; + +namespace DemonBoss.Magic +{ + /// + /// Turret component that allows player to destroy crystal turrets + /// Implements Invector interfaces for damage system integration + /// Attach to turret prefab along with appropriate collider (non-trigger) + /// + public class DestroyableTurret : MonoBehaviour, vIDamageReceiver, vIHealthController + { + [Header("Health Configuration")] + [Tooltip("Maximum health points of the turret")] + public int maxHealth = 50; + + [Tooltip("Current health points (read-only)")] + [SerializeField] private int currentHealth; + + [Header("Destruction Effects")] + [Tooltip("Visual effect played when turret is destroyed")] + public GameObject destructionEffect; + + [Tooltip("Sound played when turret is destroyed")] + public AudioClip destructionSound; + + [Tooltip("Sound played when turret receives damage")] + public AudioClip hitSound; + + [Header("Visual Feedback")] + [Tooltip("Material applied when turret is damaged")] + public Material damagedMaterial; + + [Tooltip("HP threshold below which damaged material is applied (percentage)")] + [Range(0f, 1f)] + public float damagedThreshold = 0.5f; + + [Header("Debug")] + [Tooltip("Enable debug logging")] + public bool enableDebug = false; + + // Private fields + private AudioSource audioSource; + + private CrystalShooterAI shooterAI; + private Renderer turretRenderer; + private Material originalMaterial; + private bool isDestroyed = false; + + // Invector system events + public UnityEngine.Events.UnityEvent OnReceiveDamage { get; set; } + + public UnityEngine.Events.UnityEvent OnDead { get; set; } + + // vIHealthController implementation + public int currentHealthRecovery { get; set; } + + public int maxHealthRecovery { get; set; } + public bool isDead => currentHealth <= 0; + + public OnReceiveDamage onStartReceiveDamage => throw new System.NotImplementedException(); + + public OnReceiveDamage onReceiveDamage => throw new System.NotImplementedException(); + + public OnDead onDead => throw new System.NotImplementedException(); + + float vIHealthController.currentHealth => throw new System.NotImplementedException(); + + public int MaxHealth => throw new System.NotImplementedException(); + + bool vIHealthController.isDead { get => isDead; set => throw new System.NotImplementedException(); } + + /// + /// Initialize components and validate setup + /// + private void Awake() + { + InitializeComponents(); + InitializeHealth(); + } + + /// + /// Find and configure required components + /// + private void InitializeComponents() + { + // Find components + shooterAI = GetComponent(); + turretRenderer = GetComponent(); + + // Store original material + if (turretRenderer != null) + { + originalMaterial = turretRenderer.material; + } + + // Setup AudioSource + audioSource = GetComponent(); + if (audioSource == null && (destructionSound != null || hitSound != null)) + { + audioSource = gameObject.AddComponent(); + audioSource.playOnAwake = false; + audioSource.spatialBlend = 1f; // 3D sound + } + + // Validate collider setup + Collider col = GetComponent(); + if (col != null && col.isTrigger && enableDebug) + { + Debug.LogWarning("[DestroyableTurret] Collider should not be trigger for damage system!"); + } + } + + /// + /// Setup initial health values + /// + private void InitializeHealth() + { + currentHealth = maxHealth; + maxHealthRecovery = maxHealth; + currentHealthRecovery = 0; + + if (enableDebug) Debug.Log($"[DestroyableTurret] Initialized with {currentHealth}/{maxHealth} HP"); + } + + #region vIDamageReceiver Implementation + + /// + /// Handle incoming damage from Invector damage system + /// + public void TakeDamage(vDamage damage) + { + if (isDestroyed) return; + + int damageValue = Mathf.RoundToInt(damage.damageValue); + TakeDamage(damageValue); + + if (enableDebug) + Debug.Log($"[DestroyableTurret] Received {damageValue} damage from {damage.sender?.name}"); + } + + /// + /// Handle incoming damage with hit reaction parameter + /// + public void TakeDamage(vDamage damage, bool hitReaction) + { + TakeDamage(damage); + } + + #endregion vIDamageReceiver Implementation + + #region vIHealthController Implementation + + /// + /// Apply damage to the turret and handle destruction + /// + public void TakeDamage(int damage) + { + if (isDestroyed || currentHealth <= 0) return; + + currentHealth = Mathf.Max(0, currentHealth - damage); + + if (enableDebug) + Debug.Log($"[DestroyableTurret] HP: {currentHealth}/{maxHealth} (-{damage})"); + + // Play hit effects + PlayHitEffects(); + UpdateVisualState(); + + // Trigger damage event + OnReceiveDamage?.Invoke(); + + // Check if destroyed + if (currentHealth <= 0) + { + DestroyTurret(); + } + } + + /// + /// Change health by specified amount (positive for healing, negative for damage) + /// + public void ChangeHealth(int value) + { + if (isDestroyed) return; + + if (value < 0) + { + TakeDamage(-value); + } + else + { + currentHealth = Mathf.Min(maxHealth, currentHealth + value); + if (enableDebug) Debug.Log($"[DestroyableTurret] Healed by {value}, HP: {currentHealth}/{maxHealth}"); + } + } + + /// + /// Modify maximum health value + /// + public void ChangeMaxHealth(int value) + { + maxHealth = Mathf.Max(1, maxHealth + value); + currentHealth = Mathf.Min(currentHealth, maxHealth); + } + + #endregion vIHealthController Implementation + + /// + /// Play sound and visual effects when receiving damage + /// + private void PlayHitEffects() + { + // Play hit sound + if (audioSource != null && hitSound != null) + { + audioSource.PlayOneShot(hitSound); + } + } + + /// + /// Update visual appearance based on current health + /// + private void UpdateVisualState() + { + // Change material if turret is heavily damaged + if (turretRenderer != null && damagedMaterial != null) + { + float healthPercent = (float)currentHealth / maxHealth; + if (healthPercent <= damagedThreshold && turretRenderer.material != damagedMaterial) + { + turretRenderer.material = damagedMaterial; + if (enableDebug) Debug.Log("[DestroyableTurret] Changed to damaged material"); + } + } + } + + /// + /// Handle turret destruction sequence + /// + private void DestroyTurret() + { + if (isDestroyed) return; + isDestroyed = true; + + if (enableDebug) Debug.Log("[DestroyableTurret] Turret has been destroyed!"); + + // Stop shooting + if (shooterAI != null) + { + shooterAI.StopShooting(); + } + + // Trigger death event + OnDead?.Invoke(); + + // Play destruction effects + PlayDestructionEffects(); + + // Destroy after brief delay (let effects play) + StartCoroutine(DestroyAfterDelay(0.1f)); + } + + /// + /// Play visual and audio effects for turret destruction + /// + private void PlayDestructionEffects() + { + // Spawn visual effect + if (destructionEffect != null) + { + GameObject effect = Instantiate(destructionEffect, transform.position, transform.rotation); + Destroy(effect, 5f); + } + + // Play destruction sound + if (audioSource != null && destructionSound != null) + { + audioSource.PlayOneShot(destructionSound); + + // Keep AudioSource alive to finish playing + audioSource.transform.parent = null; + Destroy(audioSource.gameObject, destructionSound.length + 1f); + } + } + + /// + /// Coroutine to destroy turret after specified delay + /// + private System.Collections.IEnumerator DestroyAfterDelay(float delay) + { + yield return new UnityEngine.WaitForSeconds(delay); + + // Remove through Lean Pool (if used) or regular Destroy + if (gameObject.scene.isLoaded) // Check if object still exists + { + LeanPool.Despawn(gameObject); + } + } + + /// + /// Force immediate turret destruction (e.g., when boss dies) + /// + public void ForceDestroy() + { + if (enableDebug) Debug.Log("[DestroyableTurret] Forced destruction"); + currentHealth = 0; + //isDead = true; + DestroyTurret(); + } + + /// + /// Returns current health as percentage of maximum health + /// + public float GetHealthPercentage() + { + return maxHealth > 0 ? (float)currentHealth / maxHealth : 0f; + } + +#if UNITY_EDITOR + + /// + /// Draw turret status visualization in Scene View + /// + private void OnDrawGizmosSelected() + { + // Show turret status + if (Application.isPlaying) + { + Gizmos.color = currentHealth > maxHealth * 0.5f ? Color.green : + currentHealth > maxHealth * 0.25f ? Color.yellow : Color.red; + } + else + { + Gizmos.color = Color.green; + } + + Gizmos.DrawWireCube(transform.position + Vector3.up * 2f, Vector3.one * 0.5f); + + // HP text in Scene View +#if UNITY_EDITOR + if (Application.isPlaying) + { + UnityEditor.Handles.Label(transform.position + Vector3.up * 2.5f, $"HP: {currentHealth}/{maxHealth}"); + } +#endif + } + + public void AddHealth(int value) + { + throw new System.NotImplementedException(); + } + + public void ResetHealth(float health) + { + throw new System.NotImplementedException(); + } + + public void ResetHealth() + { + throw new System.NotImplementedException(); + } + +#endif + } +} \ No newline at end of file diff --git a/Assets/AI/Demon/DestroyableTurret.cs.meta b/Assets/AI/Demon/DestroyableTurret.cs.meta new file mode 100644 index 000000000..d00bb858e --- /dev/null +++ b/Assets/AI/Demon/DestroyableTurret.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 06579ea47ceeddd42a05f7720c15b5de \ No newline at end of file diff --git a/Assets/AI/Demon/FireBall.prefab b/Assets/AI/Demon/FireBall.prefab index 8a9f0d738..aea7a3f0f 100644 --- a/Assets/AI/Demon/FireBall.prefab +++ b/Assets/AI/Demon/FireBall.prefab @@ -11,7 +11,7 @@ GameObject: - component: {fileID: 4577187839491108} - component: {fileID: 198086061384069858} - component: {fileID: 199127982807948490} - m_Layer: 2 + m_Layer: 30 m_Name: FireEmbers (4) m_TagString: Untagged m_Icon: {fileID: 0} @@ -4768,7 +4768,7 @@ GameObject: - component: {fileID: 199577645087675124} - component: {fileID: -5586632368230897359} - component: {fileID: 6785567375430979834} - m_Layer: 0 + m_Layer: 30 m_Name: FireBall m_TagString: Untagged m_Icon: {fileID: 0} @@ -6246,8 +6246,8 @@ ParticleSystem: rowMode: 1 sprites: - sprite: {fileID: 0} - flipU: 0.5 - flipV: 0.5 + flipU: 0 + flipV: 0 VelocityModule: enabled: 0 x: @@ -9584,7 +9584,7 @@ ParticleSystemRenderer: m_ShadowBias: 0 m_RenderAlignment: 0 m_Pivot: {x: 0, y: 0, z: 0} - m_Flip: {x: 0, y: 0, z: 0} + m_Flip: {x: 0.5, y: 0.5, z: 0} m_EnableGPUInstancing: 1 m_ApplyActiveColorSpace: 1 m_AllowRoll: 1 @@ -9622,10 +9622,10 @@ CapsuleCollider: m_ProvidesContacts: 0 m_Enabled: 1 serializedVersion: 2 - m_Radius: 0.5 - m_Height: 0.1 + m_Radius: 1 + m_Height: 0 m_Direction: 1 - m_Center: {x: 0.31801516, y: 0, z: 0} + m_Center: {x: 0, y: 0, z: 0} --- !u!114 &6785567375430979834 MonoBehaviour: m_ObjectHideFlags: 0 @@ -9639,10 +9639,11 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targetTag: Player + targetHeightOffset: 1 speed: 6 - lockTime: 15 - maxLifeTime: 30 + lockTime: 0.5 + maxLifeTime: 60 arrivalTolerance: 0.25 - damage: 25 + damage: 5 knockbackForce: 5 enableDebug: 0 diff --git a/Assets/AI/Demon/Meteor.prefab b/Assets/AI/Demon/Meteor.prefab index e3b817858..241406118 100644 --- a/Assets/AI/Demon/Meteor.prefab +++ b/Assets/AI/Demon/Meteor.prefab @@ -11,7 +11,7 @@ GameObject: - component: {fileID: 4577187839491108} - component: {fileID: 198086061384069858} - component: {fileID: 199127982807948490} - m_Layer: 2 + m_Layer: 30 m_Name: FireEmbers (4) m_TagString: Untagged m_Icon: {fileID: 0} @@ -4769,7 +4769,7 @@ GameObject: - component: {fileID: 199577645087675124} - component: {fileID: -5586632368230897359} - component: {fileID: -745564043032181229} - m_Layer: 0 + m_Layer: 30 m_Name: Meteor m_TagString: Untagged m_Icon: {fileID: 0} @@ -9639,30 +9639,35 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0d6ec41ae03923f45a963ca66dbb6c56, type: 3} m_Name: m_EditorClassIdentifier: - targetTag: Player + useOverrideImpactPoint: 0 + overrideImpactPoint: {x: 0, y: 0, z: 0} snapImpactToGround: 1 groundMask: serializedVersion: 2 m_Bits: 1410334711 - spawnHeightAboveTarget: 12 - entryAngleDownFromHorizontal: 20 - azimuthAimWeight: 0.85 + groundSnapRayStart: 3 + groundSnapRayLength: 30 + impactPoint: {x: 0, y: 0, z: 0} speed: 30 - gravityLikeAcceleration: 1 - rotateToVelocity: 1 - maxLifeTime: 12 + gravityLikePull: 14 + spawnHeightAboveTarget: 12 + arriveEpsilon: 0.35 + maxLifetime: 30 + traceRadius: 0.45 + stopOnLayers: + serializedVersion: 2 + m_Bits: 1410334711 + damageLayers: + serializedVersion: 2 + m_Bits: 1410334711 explosionRadius: 5 - explosionMask: - serializedVersion: 2 - m_Bits: 1410334711 - aoeOnlyTargetTag: 0 - castRadius: 0.25 - collisionMask: - serializedVersion: 2 - m_Bits: 1410334711 damage: 40 knockbackForce: 5 - enableDebug: 0 + impactVfxPrefab: {fileID: 1606542427775616, guid: 0de6da49dab6f8b4d93ab32a4cb441af, + type: 3} + onSpawn: + m_PersistentCalls: + m_Calls: [] onImpact: m_PersistentCalls: m_Calls: [] @@ -9678,6 +9683,26 @@ PrefabInstance: propertyPath: m_Name value: Meteor objectReference: {fileID: 0} + - target: {fileID: 100000, guid: 638572418cf33664f88dae09c5bf7652, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 100048, guid: 638572418cf33664f88dae09c5bf7652, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 100050, guid: 638572418cf33664f88dae09c5bf7652, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 100052, guid: 638572418cf33664f88dae09c5bf7652, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} + - target: {fileID: 100054, guid: 638572418cf33664f88dae09c5bf7652, type: 3} + propertyPath: m_Layer + value: 30 + objectReference: {fileID: 0} - target: {fileID: 400000, guid: 638572418cf33664f88dae09c5bf7652, type: 3} propertyPath: m_LocalScale.x value: 20 diff --git a/Assets/AI/Demon/MeteorProjectile.cs b/Assets/AI/Demon/MeteorProjectile.cs index 734ca5765..3046ea746 100644 --- a/Assets/AI/Demon/MeteorProjectile.cs +++ b/Assets/AI/Demon/MeteorProjectile.cs @@ -6,278 +6,287 @@ using UnityEngine.Events; namespace DemonBoss.Magic { + /// + /// Enhanced meteor projectile with fireball-like tracking mechanics + /// Can either track player or fly to a locked impact point + /// public class MeteorProjectile : MonoBehaviour { - #region Configuration + #region Inspector: Targeting [Header("Targeting")] - [Tooltip("Tag of the target to pick impact point from on spawn")] + [Tooltip("If true, use 'overrideImpactPoint' instead of tracking player")] + public bool useOverrideImpactPoint = false; + + [Tooltip("Externally provided locked impact point")] + public Vector3 overrideImpactPoint; + + [Tooltip("Tag to find and track if not using override point")] public string targetTag = "Player"; - [Tooltip("If true, raycasts down from impact point to find ground (better visuals)")] + [Tooltip("Height offset for targeting (aim at chest/head)")] + public float targetHeightOffset = 1.0f; + + [Tooltip("If true, raycast to ground from impact point")] public bool snapImpactToGround = true; - [Tooltip("LayerMask used when snapping impact point to ground")] + [Tooltip("Layers considered 'ground'")] public LayerMask groundMask = ~0; - [Header("Entry Geometry")] - [Tooltip("If > 0, teleports start Y above impact point by this height (keeps current XZ). 0 = keep original height.")] - public float spawnHeightAboveTarget = 12f; + [ReadOnlyInInspector] public Vector3 currentTargetPoint; - [Tooltip("Entry angle measured DOWN from horizontal (e.g. 15° = shallow, 45° = steeper)")] - [Range(0f, 89f)] - public float entryAngleDownFromHorizontal = 20f; + #endregion Inspector: Targeting - [Tooltip("How closely to aim towards the target in XZ before tilting down (0..1). 1 = purely towards target, 0 = purely downward.")] - [Range(0f, 1f)] - public float azimuthAimWeight = 0.85f; + #region Inspector: Flight - [Header("Movement")] - [Tooltip("Initial speed magnitude in units per second")] - public float speed = 30f; + [Header("Flight")] + [Tooltip("Movement speed in m/s")] + public float speed = 25f; - [Tooltip("Extra downward acceleration to add a slight curve (0 = disabled)")] - public float gravityLikeAcceleration = 0f; + [Tooltip("Time to track player before locking to position")] + public float trackingTime = 1.5f; - [Tooltip("Rotate the meteor mesh to face velocity")] - public bool rotateToVelocity = true; + [Tooltip("Distance threshold to consider arrived")] + public float arriveEpsilon = 0.5f; - [Tooltip("Seconds before the meteor auto-despawns")] - public float maxLifeTime = 12f; + [Tooltip("Max lifetime in seconds")] + public float maxLifetime = 15f; - [Header("Impact / AOE")] - [Tooltip("Explosion radius at impact (0 = no AOE, only single target)")] - public float explosionRadius = 0f; + #endregion Inspector: Flight - [Tooltip("Layers that should receive AOE damage")] - public LayerMask explosionMask = ~0; + #region Inspector: Collision & Damage - [Tooltip("If true, AOE will only damage colliders with the same tag as targetTag")] - public bool aoeOnlyTargetTag = false; + [Header("Collision & Damage")] + [Tooltip("Collision detection radius")] + public float collisionRadius = 0.8f; - [Tooltip("Sphere radius for continuous collision checks")] - public float castRadius = 0.25f; + [Tooltip("Layers that stop the meteor")] + public LayerMask stopOnLayers = ~0; - [Tooltip("Layers that block the meteor during flight (ground/walls/etc.)")] - public LayerMask collisionMask = ~0; + [Tooltip("Layers that take damage")] + public LayerMask damageLayers = ~0; - [Tooltip("Damage that will be dealt to Player")] - public float damage = 40.0f; + [Tooltip("Explosion damage radius")] + public float explosionRadius = 4f; - [Tooltip("Knockback that will be applied to Player")] - public float knockbackForce = 5.0f; + [Tooltip("Base damage")] + public int damage = 35; - [Header("Debug")] - [Tooltip("Enable verbose logs and debug rays")] - public bool enableDebug = false; + [Tooltip("Knockback force")] + public float knockbackForce = 12f; - [Header("Events")] - [Tooltip("Invoked once on any impact (use for VFX/SFX/CameraShake)")] + #endregion Inspector: Collision & Damage + + #region Inspector: Effects + + [Header("Effects & Events")] + public GameObject impactVfxPrefab; + + public UnityEvent onSpawn; public UnityEvent onImpact; - #endregion Configuration + [Header("Debug")] + public bool enableDebug = false; + + #endregion Inspector: Effects #region Runtime - private Vector3 impactPoint; - private Vector3 velocityDir; - private Vector3 velocity; - private float timer = 0f; - private bool hasDealtDamage = false; - private bool hasImpacted = false; - private Vector3 prevPos; + private Transform _player; + private Vector3 _lockedTarget; + private bool _isLocked = false; + private bool _hasImpacted = false; + private float _lifetime = 0f; + private readonly Collider[] _overlapCache = new Collider[32]; #endregion Runtime #region Unity - /// - /// Resets runtime state, locks the impact point, computes entry direction and initial velocity. - /// private void OnEnable() { - timer = 0f; - hasDealtDamage = false; - hasImpacted = false; + ResetState(); + InitializeTargeting(); + onSpawn?.Invoke(); - // Acquire player and lock impact point - Vector3 targetPos = GetPlayerPosition(); - impactPoint = snapImpactToGround ? SnapPointToGround(targetPos) : targetPos; - - // Optionally start from above to guarantee top-down feel - if (spawnHeightAboveTarget > 0f) - { - Vector3 p = transform.position; - p.y = impactPoint.y + spawnHeightAboveTarget; - transform.position = p; - } - - Vector3 toImpactXZ = new Vector3(impactPoint.x, transform.position.y, impactPoint.z) - transform.position; - Vector3 azimuthDir = toImpactXZ.sqrMagnitude > 0.0001f ? toImpactXZ.normalized : transform.forward; - - Vector3 tiltAxis = Vector3.Cross(Vector3.up, azimuthDir); - if (tiltAxis.sqrMagnitude < 0.0001f) tiltAxis = Vector3.right; - Quaternion downTilt = Quaternion.AngleAxis(-entryAngleDownFromHorizontal, tiltAxis); - Vector3 tiltedDir = (downTilt * azimuthDir).normalized; - - velocityDir = Vector3.Slerp(Vector3.down, tiltedDir, Mathf.Clamp01(azimuthAimWeight)).normalized; - - velocity = velocityDir * Mathf.Max(0f, speed); - - if (rotateToVelocity && velocity.sqrMagnitude > 0.0001f) - transform.rotation = Quaternion.LookRotation(velocity.normalized); - - if (enableDebug) - { - Debug.Log($"[Meteor] Impact={impactPoint}, start={transform.position}, dir={velocityDir}"); - Debug.DrawLine(transform.position, transform.position + velocityDir * 6f, Color.red, 3f); - Debug.DrawLine(transform.position, impactPoint, Color.yellow, 2f); - } - - prevPos = transform.position; + if (enableDebug) Debug.Log($"[MeteorProjectile] Spawned at {transform.position}"); } - /// - /// Integrates motion with optional downward acceleration, performs a SphereCast for continuous collision, - /// rotates to face velocity, and handles lifetime expiry. - /// private void Update() { - timer += Time.deltaTime; + if (_hasImpacted) return; - // Downward "gravity-like" acceleration - if (gravityLikeAcceleration > 0f) - velocity += Vector3.down * gravityLikeAcceleration * Time.deltaTime; + _lifetime += Time.deltaTime; - Vector3 nextPos = transform.position + velocity * Time.deltaTime; - - // Continuous collision: SphereCast from current position toward next - Vector3 castDir = nextPos - transform.position; - float castDist = castDir.magnitude; - if (castDist > 0.0001f) + // Check lifetime limit + if (_lifetime >= maxLifetime) { - if (Physics.SphereCast(transform.position, castRadius, castDir.normalized, - out RaycastHit hit, castDist, collisionMask, QueryTriggerInteraction.Ignore)) - { - transform.position = hit.point; - Impact(hit.collider, hit.point, hit.normal); - return; - } + if (enableDebug) Debug.Log("[MeteorProjectile] Lifetime expired"); + DoImpact(transform.position); + return; } - // No hit — apply movement - transform.position = nextPos; - - // Face velocity if requested - if (rotateToVelocity && velocity.sqrMagnitude > 0.0001f) - transform.rotation = Quaternion.LookRotation(velocity.normalized); - - prevPos = transform.position; - - // Auto-despawn on lifetime cap - if (timer >= maxLifeTime) + // Handle tracking to lock transition + if (!_isLocked && _lifetime >= trackingTime) { - if (enableDebug) Debug.Log("[Meteor] Max lifetime reached → Despawn"); - Despawn(); + LockTarget(); } - } - /// - /// Trigger-based contact: funnels into unified Impact(). - /// - private void OnTriggerEnter(Collider other) - { - // Even if not the intended target, a meteor typically impacts anything it touches - Impact(other, transform.position, -SafeNormal(velocity)); - } - - /// - /// Collision-based contact: funnels into unified Impact(). - /// - private void OnCollisionEnter(Collision collision) - { - var cp = collision.contacts.Length > 0 ? collision.contacts[0] : default; - Impact(collision.collider, cp.point != default ? cp.point : transform.position, cp.normal != default ? cp.normal : Vector3.up); + // Update target position and move + UpdateTargetPosition(); + MoveTowardsTarget(); + CheckCollisions(); } #endregion Unity - #region Helpers: Target / Ground + #region Targeting & Movement - /// - /// Gets the target's current position (by tag). If not found, projects forward from current position. - /// - private Vector3 GetPlayerPosition() + private void ResetState() { - GameObject playerObj = GameObject.FindGameObjectWithTag(targetTag); - if (playerObj != null) - return playerObj.transform.position; - - if (enableDebug) Debug.LogWarning($"[Meteor] No target with tag '{targetTag}' found, using forward guess."); - return transform.position + transform.forward * 10f; + _hasImpacted = false; + _isLocked = false; + _lifetime = 0f; + _lockedTarget = Vector3.zero; } - /// - /// Raycasts down from above the source point to find ground; returns original point if no hit. - /// - private Vector3 SnapPointToGround(Vector3 source) + private void InitializeTargeting() { - Vector3 start = source + Vector3.up * 50f; - if (Physics.Raycast(start, Vector3.down, out RaycastHit hit, 200f, groundMask, QueryTriggerInteraction.Ignore)) - return hit.point; + if (useOverrideImpactPoint) + { + _lockedTarget = snapImpactToGround ? SnapToGround(overrideImpactPoint) : overrideImpactPoint; + _isLocked = true; + currentTargetPoint = _lockedTarget; - return source; + if (enableDebug) Debug.Log($"[MeteorProjectile] Using override target: {_lockedTarget}"); + } + else + { + // Find player for tracking + var playerGO = GameObject.FindGameObjectWithTag(targetTag); + _player = playerGO ? playerGO.transform : null; + + if (enableDebug) + { + if (_player) Debug.Log($"[MeteorProjectile] Found player: {_player.name}"); + else Debug.LogWarning($"[MeteorProjectile] No player found with tag: {targetTag}"); + } + } } - /// - /// Returns a safe normalized version of a vector (falls back to Vector3.down if tiny). - /// - private static Vector3 SafeNormal(Vector3 v) + private void LockTarget() { - return v.sqrMagnitude > 0.0001f ? v.normalized : Vector3.down; + if (_isLocked) return; + + if (_player != null) + { + Vector3 targetPos = _player.position + Vector3.up * targetHeightOffset; + _lockedTarget = snapImpactToGround ? SnapToGround(targetPos) : targetPos; + } + else + { + // Fallback: lock to current forward direction + _lockedTarget = transform.position + transform.forward * 50f; + } + + _isLocked = true; + + if (enableDebug) Debug.Log($"[MeteorProjectile] Target locked to: {_lockedTarget}"); } - #endregion Helpers: Target / Ground + private void UpdateTargetPosition() + { + if (_isLocked) + { + currentTargetPoint = _lockedTarget; + } + else if (_player != null) + { + currentTargetPoint = _player.position + Vector3.up * targetHeightOffset; + } + else + { + // No player, keep going forward + currentTargetPoint = transform.position + transform.forward * 100f; + } + } + + private void MoveTowardsTarget() + { + Vector3 direction = (currentTargetPoint - transform.position).normalized; + Vector3 movement = direction * speed * Time.deltaTime; + + transform.position += movement; + + // Face movement direction + if (movement.sqrMagnitude > 0.0001f) + { + transform.rotation = Quaternion.LookRotation(movement.normalized); + } + + // Check if we've arrived (only when locked) + if (_isLocked && Vector3.Distance(transform.position, currentTargetPoint) <= arriveEpsilon) + { + if (enableDebug) Debug.Log("[MeteorProjectile] Arrived at target"); + DoImpact(transform.position); + } + } + + private void CheckCollisions() + { + // Use OverlapSphere for collision detection + int hitCount = Physics.OverlapSphereNonAlloc(transform.position, collisionRadius, _overlapCache, stopOnLayers, QueryTriggerInteraction.Ignore); + + if (hitCount > 0) + { + if (enableDebug) Debug.Log($"[MeteorProjectile] Collision detected with {_overlapCache[0].name}"); + DoImpact(transform.position); + } + } + + #endregion Targeting & Movement #region Impact & Damage - /// - /// Unified impact handler (idempotent). Deals single-target damage if applicable, - /// optional AOE damage, invokes events, and despawns. - /// - private void Impact(Collider hitCol, Vector3 hitPoint, Vector3 hitNormal) + private void DoImpact(Vector3 impactPos) { - if (hasImpacted) return; - hasImpacted = true; + if (_hasImpacted) return; + _hasImpacted = true; - if (enableDebug) - Debug.Log($"[Meteor] Impact with {(hitCol ? hitCol.name : "null")} at {hitPoint}"); + if (enableDebug) Debug.Log($"[MeteorProjectile] Impact at {impactPos}"); - // Single target (original logic) - if (!hasDealtDamage && hitCol != null && hitCol.CompareTag(targetTag)) - DealDamageToTarget(hitCol); + // Spawn VFX + if (impactVfxPrefab != null) + { + var vfx = LeanPool.Spawn(impactVfxPrefab, impactPos, Quaternion.identity); + // Auto-despawn VFX after 5 seconds + LeanPool.Despawn(vfx, 5f); + } - // AOE (if enabled) - if (explosionRadius > 0f) - DealAreaDamage(hitPoint); - - // Hook for VFX/SFX/CameraShake onImpact?.Invoke(); - Despawn(); + // Deal area damage + int damageTargets = Physics.OverlapSphereNonAlloc(impactPos, explosionRadius, _overlapCache, damageLayers, QueryTriggerInteraction.Ignore); + + for (int i = 0; i < damageTargets; i++) + { + var col = _overlapCache[i]; + if (col != null) + { + DealDamageToTarget(col, impactPos); + } + } + + // Despawn meteor + LeanPool.Despawn(gameObject); } - /// - /// Deals damage to the intended single target using Invector interfaces/components. - /// - private void DealDamageToTarget(Collider targetCollider) + private void DealDamageToTarget(Collider targetCollider, Vector3 hitPoint) { - if (enableDebug) Debug.Log($"[MeteorDamage] Dealing {damage} damage to: {targetCollider.name}"); + Vector3 hitDirection = (targetCollider.bounds.center - hitPoint).normalized; + if (hitDirection.sqrMagnitude < 0.0001f) hitDirection = Vector3.up; - Vector3 hitPoint = GetClosestPointOnCollider(targetCollider); - Vector3 hitDirection = GetHitDirection(targetCollider); - - vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage)) + vDamage damageInfo = new vDamage(damage) { sender = transform, hitPosition = hitPoint @@ -288,185 +297,104 @@ namespace DemonBoss.Magic bool damageDealt = false; - // vIDamageReceiver + // Try vIDamageReceiver first var receiver = targetCollider.GetComponent() ?? - targetCollider.GetComponentInParent(); - if (receiver != null) + targetCollider.GetComponentInParent(); + if (receiver != null && !damageDealt) { receiver.TakeDamage(damageInfo); damageDealt = true; - hasDealtDamage = true; - if (enableDebug) Debug.Log("[MeteorDamage] Damage via vIDamageReceiver"); } - // vHealthController + // Fallback to vHealthController if (!damageDealt) { - var health = targetCollider.GetComponent() ?? - targetCollider.GetComponentInParent(); - if (health != null) + var hc = targetCollider.GetComponent() ?? + targetCollider.GetComponentInParent(); + if (hc != null) { - health.TakeDamage(damageInfo); + hc.TakeDamage(damageInfo); damageDealt = true; - hasDealtDamage = true; - if (enableDebug) Debug.Log("[MeteorDamage] Damage via vHealthController"); } } - // vThirdPersonController (including Beyond variant) + // Fallback to vThirdPersonController if (!damageDealt) { var tpc = targetCollider.GetComponent() ?? - targetCollider.GetComponentInParent(); + targetCollider.GetComponentInParent(); if (tpc != null) { + // Handle Beyond variant if (tpc is Beyond.bThirdPersonController beyond) { if (!beyond.GodMode && !beyond.isImmortal) { tpc.TakeDamage(damageInfo); damageDealt = true; - hasDealtDamage = true; - if (enableDebug) Debug.Log("[MeteorDamage] Damage via bThirdPersonController"); - } - else if (enableDebug) - { - Debug.Log("[MeteorDamage] Target is immortal/GodMode – no damage"); } } else { tpc.TakeDamage(damageInfo); damageDealt = true; - hasDealtDamage = true; - if (enableDebug) Debug.Log("[MeteorDamage] Damage via vThirdPersonController"); } } } - if (!damageDealt && enableDebug) - Debug.LogWarning("[MeteorDamage] No valid damage receiver found!"); - } - - /// - /// Deals AOE damage to all colliders within explosionRadius using Invector-compatible logic. - /// - private void DealAreaDamage(Vector3 center) - { - Collider[] hits = Physics.OverlapSphere(center, explosionRadius, explosionMask, QueryTriggerInteraction.Ignore); - foreach (var col in hits) + // Apply physics force + var rb = targetCollider.attachedRigidbody; + if (rb != null && knockbackForce > 0f) { - if (col == null) continue; - if (aoeOnlyTargetTag && !col.CompareTag(targetTag)) continue; - - // Avoid double-hitting the same single target if already processed - if (col.CompareTag(targetTag) && hasDealtDamage) continue; - - Vector3 hp = col.ClosestPoint(center); - Vector3 dir = (col.bounds.center - center).normalized; - - vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage)) - { - sender = transform, - hitPosition = hp, - force = knockbackForce > 0f ? dir * knockbackForce : Vector3.zero - }; - - bool dealt = false; - - var receiver = col.GetComponent() ?? col.GetComponentInParent(); - if (receiver != null) { receiver.TakeDamage(damageInfo); dealt = true; } - - if (!dealt) - { - var health = col.GetComponent() ?? col.GetComponentInParent(); - if (health != null) { health.TakeDamage(damageInfo); dealt = true; } - } - - if (!dealt) - { - var tpc = col.GetComponent() ?? col.GetComponentInParent(); - if (tpc != null) - { - if (tpc is Beyond.bThirdPersonController beyond) - { - if (!beyond.GodMode && !beyond.isImmortal) { tpc.TakeDamage(damageInfo); dealt = true; } - } - else { tpc.TakeDamage(damageInfo); dealt = true; } - } - } + rb.AddForce(hitDirection * knockbackForce, ForceMode.Impulse); } - // Consider AOE as the final damage application for this projectile - hasDealtDamage = true; - } - - /// - /// Gets the closest point on a collider to this projectile's position. - /// - private Vector3 GetClosestPointOnCollider(Collider col) - { - return col.ClosestPoint(transform.position); - } - - /// - /// Computes a hit direction from projectile toward a collider's center; falls back to current velocity. - /// - private Vector3 GetHitDirection(Collider col) - { - Vector3 dir = (col.bounds.center - transform.position).normalized; - return dir.sqrMagnitude > 0.0001f ? dir : SafeNormal(velocity); + if (enableDebug && damageDealt) + { + Debug.Log($"[MeteorProjectile] Dealt {damage} damage to {targetCollider.name}"); + } } #endregion Impact & Damage - #region Pooling + #region Helpers - /// - /// Returns this projectile to the LeanPool. - /// - private void Despawn() + private Vector3 SnapToGround(Vector3 point) { - if (enableDebug) Debug.Log("[Meteor] Despawn via LeanPool"); - LeanPool.Despawn(gameObject); + Vector3 rayStart = point + Vector3.up * 10f; + if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 50f, groundMask, QueryTriggerInteraction.Ignore)) + { + return hit.point; + } + return point; } - #endregion Pooling + #endregion Helpers #if UNITY_EDITOR - /// - /// Editor-only gizmos to visualize entry direction and impact point when selected. - /// private void OnDrawGizmosSelected() { - Gizmos.color = Color.cyan; - Gizmos.DrawWireSphere(transform.position, 0.25f); - + // Draw collision sphere Gizmos.color = Color.red; - Gizmos.DrawLine(transform.position, transform.position + SafeNormal(velocity.sqrMagnitude > 0 ? velocity : velocityDir) * 3f); + Gizmos.DrawWireSphere(transform.position, collisionRadius); - Gizmos.color = Color.yellow; - Gizmos.DrawWireSphere(impactPoint, 0.35f); + // Draw explosion radius + Gizmos.color = Color.red; + Gizmos.DrawWireSphere(transform.position, explosionRadius); + + // Draw target point + if (currentTargetPoint != Vector3.zero) + { + Gizmos.color = Color.yellow; + Gizmos.DrawWireSphere(currentTargetPoint, 0.5f); + Gizmos.DrawLine(transform.position, currentTargetPoint); + } } #endif - - #region Validation - - /// - /// Clamps and validates configuration values in the inspector. - /// - private void OnValidate() - { - speed = Mathf.Max(0f, speed); - maxLifeTime = Mathf.Max(0.01f, maxLifeTime); - explosionRadius = Mathf.Max(0f, explosionRadius); - castRadius = Mathf.Clamp(castRadius, 0.01f, 2f); - entryAngleDownFromHorizontal = Mathf.Clamp(entryAngleDownFromHorizontal, 0f, 89f); - azimuthAimWeight = Mathf.Clamp01(azimuthAimWeight); - } - - #endregion Validation } + + public sealed class ReadOnlyInInspectorAttribute : PropertyAttribute + { } } \ No newline at end of file diff --git a/Assets/AI/Demon/SA_CallMeteor.cs b/Assets/AI/Demon/SA_CallMeteor.cs index 6f0d3decb..dc397bd07 100644 --- a/Assets/AI/Demon/SA_CallMeteor.cs +++ b/Assets/AI/Demon/SA_CallMeteor.cs @@ -1,15 +1,12 @@ using Invector.vCharacterController.AI.FSMBehaviour; using Lean.Pool; -using System.Collections; using UnityEngine; namespace DemonBoss.Magic { /// - /// FSM Action: Boss calls down a meteor. - /// Shows a decal at the player's position, locks an impact point on ground, - /// then spawns the MeteorProjectile prefab above that point after a delay. - /// Cancels cleanly on state exit. + /// Spawns a meteor behind the BOSS and launches it toward the player's position + /// Similar mechanics to FireballProjectile but coming from above /// [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Call Meteor")] public class SA_CallMeteor : vStateAction @@ -21,124 +18,88 @@ namespace DemonBoss.Magic [Tooltip("Prefab with MeteorProjectile component")] public GameObject meteorPrefab; - [Tooltip("Visual decal prefab marking impact point")] - public GameObject decalPrefab; + [Tooltip("Distance behind the BOSS to spawn meteor (meters)")] + public float behindBossDistance = 3f; - [Tooltip("Height above ground at which meteor spawns")] - public float spawnHeight = 40f; + [Tooltip("Height above the BOSS to spawn meteor (meters)")] + public float aboveBossHeight = 8f; - [Tooltip("Delay before meteor spawns after decal")] - public float castDelay = 1.5f; + [Tooltip("Delay before meteor spawns (wind-up)")] + public float castDelay = 0.4f; - [Header("Ground")] - [Tooltip("Layer mask for ground raycast")] - public LayerMask groundMask = -1; + [Header("Targeting")] + [Tooltip("Tag used to find the target (usually Player)")] + public string targetTag = "Player"; [Header("Debug")] public bool enableDebug = false; - private Transform player; - private GameObject spawnedDecal; - private Vector3 impactPoint; + private Transform _boss; + private Transform _target; - private Coroutine _spawnRoutine; - private CoroutineRunner _runner; - - /// - /// Entry point for the FSM action, delegates to OnEnter/OnExit depending on execution type. - /// - public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate) + public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType execType = vFSMComponentExecutionType.OnStateUpdate) { - if (executionType == vFSMComponentExecutionType.OnStateEnter) + if (execType == vFSMComponentExecutionType.OnStateEnter) + { OnEnter(fsm); - - if (executionType == vFSMComponentExecutionType.OnStateExit) - OnExit(); + } } - /// - /// Acquires the player, locks the impact point on the ground, shows the decal, - /// and starts the delayed meteor spawn coroutine. - /// private void OnEnter(vIFSMBehaviourController fsm) { - player = GameObject.FindGameObjectWithTag("Player")?.transform; - if (player == null) + _boss = fsm.transform; + _target = GameObject.FindGameObjectWithTag(targetTag)?.transform; + + if (_target == null) { - if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No player found!"); + if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No target found – abort"); return; } - // Raycast down from the player to lock the impact point - Vector3 rayStart = player.position + Vector3.up * 5f; - if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 100f, groundMask, QueryTriggerInteraction.Ignore)) - impactPoint = hit.point; - else - impactPoint = player.position; + if (enableDebug) Debug.Log($"[SA_CallMeteor] Boss: {_boss.name}, Target: {_target.name}"); - // Spawn decal - if (decalPrefab != null) - spawnedDecal = LeanPool.Spawn(decalPrefab, impactPoint, Quaternion.identity); - - // Get or add a dedicated runner for coroutines - var hostGO = fsm.transform.gameObject; - _runner = hostGO.GetComponent(); - if (_runner == null) _runner = hostGO.AddComponent(); - - // Start delayed spawn - _spawnRoutine = _runner.StartCoroutine(SpawnMeteorAfterDelay()); - } - - /// - /// Cancels the pending spawn and cleans up the decal when exiting the state. - /// - private void OnExit() - { - if (_runner != null && _spawnRoutine != null) - { - _runner.StopCoroutine(_spawnRoutine); - _spawnRoutine = null; - } - - if (spawnedDecal != null) - { - LeanPool.Despawn(spawnedDecal); - spawnedDecal = null; - } - } - - /// - /// Waits for the configured cast delay and then spawns the meteor. - /// - private IEnumerator SpawnMeteorAfterDelay() - { - yield return new WaitForSeconds(castDelay); SpawnMeteor(); } - /// - /// Spawns the meteor prefab above the locked impact point and cleans up the decal. - /// private void SpawnMeteor() { - if (meteorPrefab == null) return; - - Vector3 spawnPos = impactPoint + Vector3.up * spawnHeight; - LeanPool.Spawn(meteorPrefab, spawnPos, Quaternion.identity); - - if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor spawned at {spawnPos}, impact={impactPoint}"); - - if (spawnedDecal != null) + if (meteorPrefab == null) { - LeanPool.Despawn(spawnedDecal); - spawnedDecal = null; + if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing meteorPrefab"); + return; + } + + if (_boss == null || _target == null) + { + if (enableDebug) Debug.LogError("[SA_CallMeteor] Missing boss or target reference"); + return; + } + + // Calculate spawn position: behind the BOSS + height + Vector3 bossForward = _boss.forward.normalized; + Vector3 behindBoss = _boss.position - (bossForward * behindBossDistance); + Vector3 spawnPos = behindBoss + Vector3.up * aboveBossHeight; + + if (enableDebug) Debug.Log($"[SA_CallMeteor] Spawning meteor at: {spawnPos}"); + + // Spawn the meteor + var meteorGO = LeanPool.Spawn(meteorPrefab, spawnPos, Quaternion.identity); + + // Configure the projectile to target the player + var meteorScript = meteorGO.GetComponent(); + if (meteorScript != null) + { + // Set it to target the player's current position + meteorScript.useOverrideImpactPoint = true; + meteorScript.overrideImpactPoint = _target.position; + meteorScript.snapImpactToGround = true; + + if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor configured to target: {_target.position}"); + } + else + { + if (enableDebug) Debug.LogError("[SA_CallMeteor] Meteor prefab missing MeteorProjectile component!"); } } } - - /// - /// Lightweight helper component dedicated to running coroutines for ScriptableObject actions. - /// - public sealed class CoroutineRunner : MonoBehaviour - { } } \ No newline at end of file diff --git a/Assets/AI/Demon/SA_SpawnTurretSmart.cs b/Assets/AI/Demon/SA_SpawnTurretSmart.cs index 54dadff32..c00440bc0 100644 --- a/Assets/AI/Demon/SA_SpawnTurretSmart.cs +++ b/Assets/AI/Demon/SA_SpawnTurretSmart.cs @@ -5,140 +5,97 @@ using UnityEngine; namespace DemonBoss.Magic { /// - /// StateAction for intelligent crystal turret spawning - /// Searches for optimal position in 2-6m ring from boss, prefers "behind boss" relative to player + /// Spawns exactly 3 crystal turrets around the opponent. + /// Now with per-spawn randomness: global rotation offset and per-turret angle/radius jitter. + /// Validates positions (ground/obstacles) and enforces minimum separation. /// - [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn Turret Smart")] + [CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Spawn 3 Turrets Radial")] public class SA_SpawnTurretSmart : vStateAction { public override string categoryName => "DemonBoss/Magic"; - public override string defaultName => "Spawn Turret Smart"; + public override string defaultName => "Spawn 3 Turrets Radial"; [Header("Turret Configuration")] - [Tooltip("Crystal prefab with CrystalShooterAI component")] public GameObject crystalPrefab; - [Tooltip("Minimum distance from boss for crystal spawn")] public float minSpawnDistance = 2f; - - [Tooltip("Maximum distance from boss for crystal spawn")] public float maxSpawnDistance = 6f; - - [Tooltip("Collision check radius when choosing position")] public float obstacleCheckRadius = 1f; - - [Tooltip("Height above ground for raycast ground checking")] public float groundCheckHeight = 2f; - - [Tooltip("Layer mask for obstacles")] public LayerMask obstacleLayerMask = -1; - - [Tooltip("Layer mask for ground")] public LayerMask groundLayerMask = -1; - - [Tooltip("Animator bool parameter name for blocking state")] public string animatorBlockingBool = "IsBlocking"; - [Header("Smart Positioning")] - [Tooltip("Preference multiplier for positions behind boss (relative to player)")] - public float backPreferenceMultiplier = 2f; + [Header("Placement Search (Validation)")] + public int perTurretAdjustmentTries = 10; - [Tooltip("Number of attempts to find valid position")] - public int maxSpawnAttempts = 12; + public float maxAngleAdjust = 25f; + public float maxRadiusAdjust = 1.0f; + public float minSeparationBetweenTurrets = 1.5f; + + [Header("Randomization (Formation)")] + [Tooltip("Random global rotation offset (degrees) applied to the 120° spokes.")] + public bool globalStartAngleRandom = true; + + [Tooltip("Per-turret angle jitter around the spoke (degrees). 0 = disabled.")] + public float perTurretAngleJitter = 10f; + + [Tooltip("Per-turret radius jitter (meters). 0 = disabled.")] + public float perTurretRadiusJitter = 0.75f; [Header("Debug")] - [Tooltip("Enable debug logging")] public bool enableDebug = false; - [Tooltip("Show gizmos in Scene View")] public bool showGizmos = true; - private GameObject spawnedCrystal; private Animator npcAnimator; private Transform npcTransform; private Transform playerTransform; - /// - /// Main action execution method called by FSM - /// + private readonly System.Collections.Generic.List _lastPlanned = new System.Collections.Generic.List(3); + public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate) { - if (executionType == vFSMComponentExecutionType.OnStateEnter) - { - OnStateEnter(fsmBehaviour); - } - else if (executionType == vFSMComponentExecutionType.OnStateExit) - { - OnStateExit(fsmBehaviour); - } + if (executionType == vFSMComponentExecutionType.OnStateEnter) OnStateEnter(fsmBehaviour); + else if (executionType == vFSMComponentExecutionType.OnStateExit) OnStateExit(fsmBehaviour); } - /// - /// Called when entering state - intelligently spawns crystal - /// private void OnStateEnter(vIFSMBehaviourController fsmBehaviour) { - if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Starting intelligent crystal spawn"); - - // Store NPC references npcTransform = fsmBehaviour.transform; npcAnimator = npcTransform.GetComponent(); FindPlayer(fsmBehaviour); if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool)) - { npcAnimator.SetBool(animatorBlockingBool, true); - if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set bool: {animatorBlockingBool} = true"); - } - SpawnCrystalSmart(fsmBehaviour); + SpawnThreeTurretsRadial(fsmBehaviour); DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Turret", 12f); } - /// - /// Called when exiting state - cleanup - /// private void OnStateExit(vIFSMBehaviourController fsmBehaviour) { - if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Exiting turret spawn state"); - if (npcAnimator != null && !string.IsNullOrEmpty(animatorBlockingBool)) - { npcAnimator.SetBool(animatorBlockingBool, false); - if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Set bool: {animatorBlockingBool} = false"); - } } - /// - /// Finds player transform - /// private void FindPlayer(vIFSMBehaviourController fsmBehaviour) { GameObject player = GameObject.FindGameObjectWithTag("Player"); if (player != null) { playerTransform = player.transform; - if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found by tag"); return; } var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI; if (aiController != null && aiController.currentTarget != null) - { playerTransform = aiController.currentTarget.transform; - if (enableDebug) Debug.Log("[SA_SpawnTurretSmart] Player found through AI target"); - return; - } - - if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Player not found!"); } - /// - /// Intelligently spawns crystal in optimal position - /// - private void SpawnCrystalSmart(vIFSMBehaviourController fsmBehaviour) + private void SpawnThreeTurretsRadial(vIFSMBehaviourController fsmBehaviour) { if (crystalPrefab == null) { @@ -146,63 +103,85 @@ namespace DemonBoss.Magic return; } - Vector3 bestPosition = Vector3.zero; - bool foundValidPosition = false; - float bestScore = float.MinValue; + _lastPlanned.Clear(); - Vector3 bossPos = npcTransform.position; - Vector3 playerDirection = Vector3.zero; + Vector3 center = playerTransform != null ? playerTransform.position : npcTransform.position; + float baseRadius = Mathf.Clamp((minSpawnDistance + maxSpawnDistance) * 0.5f, minSpawnDistance, maxSpawnDistance); - if (playerTransform != null) + Vector3 refDir = Vector3.forward; + if (playerTransform != null && npcTransform != null) { - playerDirection = (playerTransform.position - bossPos).normalized; + Vector3 d = (npcTransform.position - center); d.y = 0f; + if (d.sqrMagnitude > 0.0001f) refDir = d.normalized; + } + else if (npcTransform != null) + { + refDir = npcTransform.forward; } - for (int i = 0; i < maxSpawnAttempts; i++) + float globalOffset = globalStartAngleRandom ? Random.Range(0f, 360f) : 0f; + + const int turretCount = 3; + for (int i = 0; i < turretCount; i++) { - float angle = (360f / maxSpawnAttempts) * i + Random.Range(-15f, 15f); - Vector3 direction = new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0, Mathf.Sin(angle * Mathf.Deg2Rad)); + float baseAngle = globalOffset + i * 120f; + float angle = baseAngle + (perTurretAngleJitter > 0f ? Random.Range(-perTurretAngleJitter, perTurretAngleJitter) : 0f); - float distance = Random.Range(minSpawnDistance, maxSpawnDistance); - Vector3 testPosition = bossPos + direction * distance; + float radius = baseRadius + (perTurretRadiusJitter > 0f ? Random.Range(-perTurretRadiusJitter, perTurretRadiusJitter) : 0f); + radius = Mathf.Clamp(radius, minSpawnDistance, maxSpawnDistance); - if (IsPositionValid(testPosition, out Vector3 groundPosition)) + Vector3 ideal = center + Quaternion.Euler(0f, angle, 0f) * refDir * radius; + + Vector3? chosen = IsPositionValidAndSeparated(ideal) + ? (Vector3?)ideal + : FindValidPositionAroundSpoke(center, refDir, angle, radius); + + if (!chosen.HasValue) { - float score = EvaluatePosition(groundPosition, playerDirection, direction, bossPos); - - if (score > bestScore) - { - bestScore = score; - bestPosition = groundPosition; - foundValidPosition = true; - } + Vector3 rough = center + Quaternion.Euler(0f, angle, 0f) * refDir * radius; + chosen = EnforceSeparationFallback(rough, center); } - } - if (foundValidPosition) - { - SpawnCrystal(bestPosition, fsmBehaviour); - if (enableDebug) Debug.Log($"[SA_SpawnTurretSmart] Crystal spawned at position: {bestPosition} (score: {bestScore:F2})"); - } - else - { - Vector3 fallbackPos = bossPos + npcTransform.forward * minSpawnDistance; - SpawnCrystal(fallbackPos, fsmBehaviour); - if (enableDebug) Debug.LogWarning("[SA_SpawnTurretSmart] Using fallback position"); + Vector3 spawnPos = chosen.Value; + _lastPlanned.Add(spawnPos); + SpawnCrystal(spawnPos, fsmBehaviour); } } - /// - /// Checks if position is valid (no obstacles, has ground) - /// + private Vector3? FindValidPositionAroundSpoke(Vector3 center, Vector3 refDir, float baseAngleDeg, float desiredRadius) + { + Vector3 ideal = center + Quaternion.Euler(0f, baseAngleDeg, 0f) * refDir * desiredRadius; + if (IsPositionValidAndSeparated(ideal)) return ideal; + + for (int t = 0; t < perTurretAdjustmentTries; t++) + { + float ang = baseAngleDeg + Random.Range(-maxAngleAdjust, maxAngleAdjust); + float rad = Mathf.Clamp(desiredRadius + Random.Range(-maxRadiusAdjust, maxRadiusAdjust), minSpawnDistance, maxSpawnDistance); + Vector3 cand = center + Quaternion.Euler(0f, ang, 0f) * refDir * rad; + + if (IsPositionValidAndSeparated(cand)) return cand; + } + return null; + } + + private bool IsPositionValidAndSeparated(Vector3 position) + { + if (!IsPositionValid(position, out Vector3 grounded)) return false; + + for (int i = 0; i < _lastPlanned.Count; i++) + { + if (Vector3.Distance(_lastPlanned[i], grounded) < Mathf.Max(minSeparationBetweenTurrets, obstacleCheckRadius * 2f)) + return false; + } + return true; + } + private bool IsPositionValid(Vector3 position, out Vector3 groundPosition) { groundPosition = position; if (Physics.CheckSphere(position + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask)) - { return false; - } Ray groundRay = new Ray(position + Vector3.up * groundCheckHeight, Vector3.down); if (Physics.Raycast(groundRay, out RaycastHit hit, groundCheckHeight + 2f, groundLayerMask)) @@ -210,109 +189,82 @@ namespace DemonBoss.Magic groundPosition = hit.point; if (Physics.CheckSphere(groundPosition + Vector3.up * obstacleCheckRadius, obstacleCheckRadius, obstacleLayerMask)) - { return false; - } return true; } - return false; } - /// - /// Evaluates position quality (higher score = better position) - /// - private float EvaluatePosition(Vector3 position, Vector3 playerDirection, Vector3 positionDirection, Vector3 bossPos) + private Vector3 EnforceSeparationFallback(Vector3 desired, Vector3 center) { - float score = 0f; - - if (playerTransform != null && playerDirection != Vector3.zero) + Vector3 candidate = desired; + float step = 0.5f; + for (int i = 0; i < 10; i++) { - float angleToPlayer = Vector3.Angle(-playerDirection, positionDirection); + bool ok = true; + for (int k = 0; k < _lastPlanned.Count; k++) + { + if (Vector3.Distance(_lastPlanned[k], candidate) < Mathf.Max(minSeparationBetweenTurrets, obstacleCheckRadius * 2f)) + { ok = false; break; } + } + if (ok) return candidate; - // The smaller the angle (closer to "behind"), the better the score - float backScore = (180f - angleToPlayer) / 180f; - score += backScore * backPreferenceMultiplier; + Vector3 dir = (candidate - center); dir.y = 0f; + if (dir.sqrMagnitude < 0.0001f) dir = Vector3.right; + candidate = center + dir.normalized * (dir.magnitude + step); } - - float distance = Vector3.Distance(position, bossPos); - float optimalDistance = (minSpawnDistance + maxSpawnDistance) * 0.5f; - float distanceScore = 1f - Mathf.Abs(distance - optimalDistance) / maxSpawnDistance; - score += distanceScore; - - score += Random.Range(-0.1f, 0.1f); - - return score; + return candidate; } - /// - /// Spawns crystal at given position - /// private void SpawnCrystal(Vector3 position, vIFSMBehaviourController fsmBehaviour) { Quaternion rotation = Quaternion.identity; - if (playerTransform != null) + Transform lookAt = playerTransform != null ? playerTransform : npcTransform; + + if (lookAt != null) { - Vector3 lookDirection = (playerTransform.position - position).normalized; + Vector3 lookDirection = (lookAt.position - position).normalized; lookDirection.y = 0; if (lookDirection != Vector3.zero) - { rotation = Quaternion.LookRotation(lookDirection); - } } - spawnedCrystal = LeanPool.Spawn(crystalPrefab, position, rotation); + var spawned = LeanPool.Spawn(crystalPrefab, position, rotation); - var shooterAI = spawnedCrystal.GetComponent(); + var shooterAI = spawned.GetComponent(); if (shooterAI == null) { Debug.LogError("[SA_SpawnTurretSmart] Crystal prefab doesn't have CrystalShooterAI component!"); } - else + else if (playerTransform != null) { - if (playerTransform != null) - { - shooterAI.SetTarget(playerTransform); - } + shooterAI.SetTarget(playerTransform); } } - /// - /// Draws gizmos in Scene View for debugging - /// private void OnDrawGizmosSelected() { - if (!showGizmos) return; + if (!showGizmos || npcTransform == null) return; - if (npcTransform != null) - { - Vector3 pos = npcTransform.position; + Vector3 c = playerTransform ? playerTransform.position : npcTransform.position; - // Spawn ring - Gizmos.color = Color.green; - DrawWireCircle(pos, minSpawnDistance); - Gizmos.color = Color.red; - DrawWireCircle(pos, maxSpawnDistance); + Gizmos.color = Color.green; + DrawWireCircle(c, minSpawnDistance); + Gizmos.color = Color.red; + DrawWireCircle(c, maxSpawnDistance); - // Obstacle check radius - Gizmos.color = Color.yellow; - Gizmos.DrawWireSphere(pos + Vector3.up * obstacleCheckRadius, obstacleCheckRadius); - } + Gizmos.color = Color.cyan; + foreach (var p in _lastPlanned) Gizmos.DrawWireSphere(p + Vector3.up * 0.1f, 0.2f); } - /// - /// Helper method for drawing circles - /// private void DrawWireCircle(Vector3 center, float radius) { int segments = 32; - float angle = 0f; Vector3 prevPoint = center + new Vector3(radius, 0, 0); - for (int i = 1; i <= segments; i++) { - angle = (float)i / segments * 360f * Mathf.Deg2Rad; + float angle = (float)i / segments * 360f * Mathf.Deg2Rad; Vector3 newPoint = center + new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius); Gizmos.DrawLine(prevPoint, newPoint); prevPoint = newPoint; diff --git a/Assets/AI/Demon/ShieldDamage.cs b/Assets/AI/Demon/ShieldDamage.cs new file mode 100644 index 000000000..9d9b2398a --- /dev/null +++ b/Assets/AI/Demon/ShieldDamage.cs @@ -0,0 +1,191 @@ +using Invector; +using Invector.vCharacterController; +using UnityEngine; + +namespace DemonBoss.Magic +{ + /// + /// Component that adds damage when the player gets too close to the shield. + /// Add to the shield object along with Sphere Collider (IsTrigger = true). + /// + public class ShieldDamage : MonoBehaviour + { + [Header("Damage Configuration")] + [Tooltip("Damage dealt to the player")] + public int damage = 15; + + [Tooltip("Knockback force")] + public float knockbackForce = 10f; + + [Tooltip("Player Tag")] + public string playerTag = "Player"; + + [Tooltip("Time between consecutive hits (so as not to deal damage to every frame)")] + public float damageInterval = 1f; + + [Header("Effects")] + [Tooltip("Sound effect when dealing damage")] + public AudioClip damageSound; + + [Tooltip("Visual effect when dealing damage")] + public GameObject damageEffect; + + [Header("Debug")] + [Tooltip("Enable debug logs")] + public bool enableDebug = false; + + private AudioSource audioSource; + private float lastDamageTime; + + private void Awake() + { + if (damageSound != null) + { + audioSource = GetComponent(); + if (audioSource == null) + { + audioSource = gameObject.AddComponent(); + audioSource.playOnAwake = false; + audioSource.spatialBlend = 1f; // 3D sound + } + } + } + + private void OnTriggerStay(Collider other) + { + if (other.CompareTag(playerTag) && Time.time >= lastDamageTime + damageInterval) + { + DealDamageToPlayer(other); + lastDamageTime = Time.time; + } + } + + private void DealDamageToPlayer(Collider playerCollider) + { + if (enableDebug) Debug.Log($"[ShieldDamage] I deal {damage} damage to the player: {playerCollider.name}"); + + Vector3 hitPoint = playerCollider.ClosestPoint(transform.position); + Vector3 hitDirection = (playerCollider.transform.position - transform.position).normalized; + + vDamage damageInfo = new vDamage(damage) + { + sender = transform, + hitPosition = hitPoint + }; + + if (knockbackForce > 0f) + { + damageInfo.force = hitDirection * knockbackForce; + } + + bool damageDealt = false; + + // 1) vIDamageReceiver + var damageReceiver = playerCollider.GetComponent() ?? + playerCollider.GetComponentInParent(); + + if (damageReceiver != null) + { + damageReceiver.TakeDamage(damageInfo); + damageDealt = true; + if (enableDebug) Debug.Log("[ShieldDamage] Damage dealt by vIDamageReceiver"); + } + + // 2) vHealthController + if (!damageDealt) + { + var healthController = playerCollider.GetComponent() ?? + playerCollider.GetComponentInParent(); + + if (healthController != null) + { + healthController.TakeDamage(damageInfo); + damageDealt = true; + if (enableDebug) Debug.Log("[ShieldDamage] Damage dealt by vHealthController"); + } + } + + // 3) vThirdPersonController + if (!damageDealt) + { + var tpc = playerCollider.GetComponent() ?? + playerCollider.GetComponentInParent(); + + if (tpc != null) + { + // SprawdŸ czy to Beyond variant + if (tpc is Beyond.bThirdPersonController beyond) + { + if (!beyond.GodMode && !beyond.isImmortal) + { + tpc.TakeDamage(damageInfo); + damageDealt = true; + if (enableDebug) Debug.Log("[ShieldDamage] Damage dealt by bThirdPersonController"); + } + else + { + if (enableDebug) Debug.Log("[ShieldDamage] Player is immortal - no damage"); + } + } + else + { + tpc.TakeDamage(damageInfo); + damageDealt = true; + if (enableDebug) Debug.Log("[ShieldDamage] Damage dealt by vThirdPersonController"); + } + } + } + + if (damageDealt) + { + PlayEffects(hitPoint); + } + else if (enableDebug) + { + Debug.LogWarning("[ShieldDamage] Cannot deal damage - missing required component!"); + } + } + + private void PlayEffects(Vector3 hitPoint) + { + // Play hit sound + if (audioSource != null && damageSound != null) + { + audioSource.PlayOneShot(damageSound); + } + + // Show visual effect + if (damageEffect != null) + { + GameObject effect = Instantiate(damageEffect, hitPoint, Quaternion.identity); + Destroy(effect, 2f); // Remove after 2 seconds + } + } + +#if UNITY_EDITOR + + /// + /// Draw damage range visualization in Scene View + /// + private void OnDrawGizmosSelected() + { + // Show damage range + Collider col = GetComponent(); + if (col != null && col.isTrigger) + { + Gizmos.color = Color.red; + if (col is SphereCollider sphere) + { + Gizmos.DrawWireSphere(transform.position, sphere.radius); + } + else if (col is CapsuleCollider capsule) + { + // Approximate capsule visualization + Gizmos.DrawWireSphere(transform.position, capsule.radius); + } + } + } + +#endif + } +} \ No newline at end of file diff --git a/Assets/AI/Demon/ShieldDamage.cs.meta b/Assets/AI/Demon/ShieldDamage.cs.meta new file mode 100644 index 000000000..9eb45d81b --- /dev/null +++ b/Assets/AI/Demon/ShieldDamage.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a4b288b220c78b4ca00e13cb8a72d6b \ No newline at end of file diff --git a/Assets/AI/Demon/Turet.prefab b/Assets/AI/Demon/Turet.prefab index 8b5ee6dad..488995536 100644 --- a/Assets/AI/Demon/Turet.prefab +++ b/Assets/AI/Demon/Turet.prefab @@ -13,6 +13,9 @@ GameObject: - component: {fileID: 2323612} - component: {fileID: 13662188} - component: {fileID: 7020133711031364094} + - component: {fileID: -8194063383422297065} + - component: {fileID: 1536958584606674588} + - component: {fileID: 8851143338676511289} m_Layer: 26 m_Name: Turet m_TagString: Untagged @@ -104,7 +107,7 @@ CapsuleCollider: serializedVersion: 2 m_Bits: 0 m_LayerOverridePriority: 0 - m_IsTrigger: 0 + m_IsTrigger: 1 m_ProvidesContacts: 0 m_Enabled: 1 serializedVersion: 2 @@ -128,19 +131,73 @@ MonoBehaviour: fireballPrefab: {fileID: 1947871717301538, guid: 9591667a35466484096c6e63785e136c, type: 3} fireRate: 5 - maxShots: 3 + maxShots: 10 despawnDelay: 10 - turnSpeed: 120 - idleSpinSpeed: 30 - aimTolerance: 5 + initialStaggerRange: {x: 0, y: 0.6} + fireRateJitter: 0.5 + aimJitterDegrees: 0 + turnSpeed: 0 + idleSpinSpeed: 0 + aimTolerance: 360 autoFindPlayer: 1 playerTag: Player maxShootingRange: 50 - useShootEffects: 0 + useShootEffects: 1 muzzleFlashPrefab: {fileID: 0} - shootSound: {fileID: 8300000, guid: d44a96f293b66b74ea13a2e6fcc6c8fa, type: 3} - enableDebug: 0 + shootSound: {fileID: 8300000, guid: f58578c3593cfcd40a4b21417e49421a, type: 3} + enableDebug: 1 showGizmos: 1 +--- !u!114 &-8194063383422297065 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 115880} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 06579ea47ceeddd42a05f7720c15b5de, type: 3} + m_Name: + m_EditorClassIdentifier: + maxHealth: 50 + currentHealth: 0 + destructionEffect: {fileID: 8876690725160639820, guid: c1b3da9fe585c44479dda2cd5c3a7b83, + type: 3} + destructionSound: {fileID: 8300000, guid: 3c1f8b87da670734991b73bf5c30f0af, type: 3} + hitSound: {fileID: 8300000, guid: 0af332725d9792840a4caa29e8991d08, type: 3} + damagedMaterial: {fileID: 2100000, guid: f94eea72c30ac2e45ab55894421ea48c, type: 2} + damagedThreshold: 0.5 + enableDebug: 1 +--- !u!114 &1536958584606674588 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 115880} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 605ff11ee57b0c241a82c0c37c40c0bc, type: 3} + m_Name: + m_EditorClassIdentifier: + openCloseEvents: 0 + openCloseWindow: 0 + selectedToolbar: 0 + messagesListeners: [] +--- !u!114 &8851143338676511289 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 115880} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 312629d966d1a40e3874daa93364e1f3, type: 3} + m_Name: + m_EditorClassIdentifier: + targetIcon: {fileID: 8317937118480752073, guid: 82581d6c5dd5945b89394d83db5f4c8b, + type: 3} --- !u!1 &6534933121460188189 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/AI/FSM/FSM_Demon.asset b/Assets/AI/FSM/FSM_Demon.asset index ad64ecdef..e3657fcb6 100644 --- a/Assets/AI/FSM/FSM_Demon.asset +++ b/Assets/AI/FSM/FSM_Demon.asset @@ -135,38 +135,6 @@ MonoBehaviour: shieldDuration: 10 animatorBlockingBool: IsBlocking enableDebug: 1 ---- !u!114 &-7016508256595524775 -MonoBehaviour: - m_ObjectHideFlags: 1 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: af82d1d082ce4b9478d420f8ca1e72c2, type: 3} - m_Name: Check Cooldown Meteor - m_EditorClassIdentifier: - parentFSM: {fileID: 11400000} - editingName: 0 - trueRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 10 - height: 10 - falseRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 10 - height: 10 - selectedTrue: 0 - selectedFalse: 0 - cooldownKey: Meteor - cooldownTime: 75 - availableAtStart: 1 - enableDebug: 0 --- !u!114 &-6568372008305276654 MonoBehaviour: m_ObjectHideFlags: 1 @@ -190,17 +158,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -368 - y: 311 + x: -35 + y: 290 width: 150 height: 62 - positionRect: {x: -368, y: 311} + positionRect: {x: -35, y: 290} rectWidth: 150 editingName: 1 nodeColor: {r: 0, g: 1, b: 1, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 0 transitions: - decisions: @@ -221,14 +189,14 @@ MonoBehaviour: parentState: {fileID: -6568372008305276654} trueRect: serializedVersion: 2 - x: -218 - y: 341 + x: 115 + y: 320 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -218 - y: 351 + x: 115 + y: 330 width: 10 height: 10 selectedTrue: 0 @@ -261,12 +229,10 @@ MonoBehaviour: editingName: 0 meteorPrefab: {fileID: 1947871717301538, guid: f99aa3faf46a5f94985344f44aaf21aa, type: 3} - decalPrefab: {fileID: 0} - spawnHeight: 40 + behindBossDistance: 10 + aboveBossHeight: 20 castDelay: 1.5 - groundMask: - serializedVersion: 2 - m_Bits: 4294967295 + targetTag: Player enableDebug: 1 --- !u!114 &-6379838510941931433 MonoBehaviour: @@ -321,17 +287,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -328 - y: 466 + x: 0 + y: 445 width: 150 height: 106 - positionRect: {x: -328, y: 466} + positionRect: {x: 0, y: 445} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 0, b: 0, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 0 transitions: - decisions: @@ -352,14 +318,14 @@ MonoBehaviour: parentState: {fileID: -6144582714324757854} trueRect: serializedVersion: 2 - x: -178 - y: 496 + x: 150 + y: 475 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -178 - y: 506 + x: 150 + y: 485 width: 10 height: 10 selectedTrue: 0 @@ -380,14 +346,14 @@ MonoBehaviour: parentState: {fileID: -6144582714324757854} trueRect: serializedVersion: 2 - x: -178 - y: 518 + x: 150 + y: 497 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -178 - y: 528 + x: 150 + y: 507 width: 10 height: 10 selectedTrue: 0 @@ -416,14 +382,14 @@ MonoBehaviour: parentState: {fileID: -6144582714324757854} trueRect: serializedVersion: 2 - x: -178 - y: 540 + x: 150 + y: 519 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -178 - y: 550 + x: 150 + y: 529 width: 10 height: 10 selectedTrue: 0 @@ -574,17 +540,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -48 - y: 316 + x: 280 + y: 295 width: 150 height: 62 - positionRect: {x: -48, y: 316} + positionRect: {x: 280, y: 295} rectWidth: 150 editingName: 1 nodeColor: {r: 0.10323405, g: 1, b: 0, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 1 transitions: - decisions: [] @@ -597,14 +563,14 @@ MonoBehaviour: parentState: {fileID: -3177478727897100882} trueRect: serializedVersion: 2 - x: 102 - y: 346 + x: 430 + y: 325 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 102 - y: 356 + x: 430 + y: 335 width: 10 height: 10 selectedTrue: 0 @@ -643,17 +609,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 252 - y: 271 + x: 580 + y: 250 width: 150 height: 62 - positionRect: {x: 252, y: 271} + positionRect: {x: 580, y: 250} rectWidth: 150 editingName: 1 nodeColor: {r: 0, g: 1, b: 0.004989147, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 0 transitions: - decisions: @@ -670,14 +636,14 @@ MonoBehaviour: parentState: {fileID: -2904979146780567904} trueRect: serializedVersion: 2 - x: 242 - y: 301 + x: 570 + y: 280 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 402 - y: 311 + x: 730 + y: 290 width: 10 height: 10 selectedTrue: 0 @@ -778,8 +744,13 @@ MonoBehaviour: serializedVersion: 2 m_Bits: 4294967295 animatorBlockingBool: IsBlocking - backPreferenceMultiplier: 2 - maxSpawnAttempts: 12 + perTurretAdjustmentTries: 10 + maxAngleAdjust: 25 + maxRadiusAdjust: 1 + minSeparationBetweenTurrets: 1.5 + globalStartAngleRandom: 1 + perTurretAngleJitter: 10 + perTurretRadiusJitter: 0.75 enableDebug: 1 showGizmos: 1 --- !u!114 &-712571192746352845 @@ -805,17 +776,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -48 - y: 256 + x: 280 + y: 235 width: 150 height: 30 - positionRect: {x: -48, y: 256} + positionRect: {x: 280, y: 235} rectWidth: 150 editingName: 0 nodeColor: {r: 0, g: 1, b: 0, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 0 transitions: [] actions: [] @@ -881,27 +852,27 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: -243 - y: 726 + x: 85 + y: 705 width: 150 height: 150 - positionRect: {x: -243, y: 726} + positionRect: {x: 85, y: 705} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 0.95132554, b: 0, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 1 transitions: - decisions: - trueValue: 1 decision: {fileID: 4031404829621142413} - isValid: 1 + isValid: 0 validated: 0 - trueValue: 1 decision: {fileID: -2866484833343459521} - isValid: 1 + isValid: 0 validated: 0 trueState: {fileID: 2986668563461644515} falseState: {fileID: 0} @@ -912,14 +883,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -93 - y: 756 + x: 235 + y: 735 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -93 - y: 766 + x: 235 + y: 745 width: 10 height: 10 selectedTrue: 0 @@ -933,7 +904,7 @@ MonoBehaviour: - decisions: - trueValue: 0 decision: {fileID: 4031404829621142413} - isValid: 0 + isValid: 1 validated: 0 trueState: {fileID: -2904979146780567904} falseState: {fileID: 0} @@ -944,14 +915,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -93 - y: 778 + x: 235 + y: 757 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -93 - y: 788 + x: 235 + y: 767 width: 10 height: 10 selectedTrue: 0 @@ -965,16 +936,16 @@ MonoBehaviour: - decisions: - trueValue: 1 decision: {fileID: 4031404829621142413} - isValid: 1 - validated: 0 - - trueValue: 0 - decision: {fileID: -2866484833343459521} isValid: 0 validated: 0 - trueValue: 0 - decision: {fileID: 7927421991537792917} + decision: {fileID: -2866484833343459521} isValid: 1 validated: 0 + - trueValue: 0 + decision: {fileID: 7927421991537792917} + isValid: 0 + validated: 0 trueState: {fileID: -6144582714324757854} falseState: {fileID: 0} muteTrue: 0 @@ -984,14 +955,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -253 - y: 800 + x: 75 + y: 779 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -93 - y: 810 + x: 235 + y: 789 width: 10 height: 10 selectedTrue: 0 @@ -1005,11 +976,11 @@ MonoBehaviour: - decisions: - trueValue: 1 decision: {fileID: -3690511210373239573} - isValid: 1 + isValid: 0 validated: 0 - trueValue: 0 decision: {fileID: 7927421991537792917} - isValid: 1 + isValid: 0 validated: 0 trueState: {fileID: 0} falseState: {fileID: 0} @@ -1020,14 +991,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -93 - y: 822 + x: 235 + y: 801 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -93 - y: 832 + x: 235 + y: 811 width: 10 height: 10 selectedTrue: 0 @@ -1041,7 +1012,7 @@ MonoBehaviour: - decisions: - trueValue: 1 decision: {fileID: 7927421991537792917} - isValid: 0 + isValid: 1 validated: 0 trueState: {fileID: -2904979146780567904} falseState: {fileID: 0} @@ -1052,14 +1023,14 @@ MonoBehaviour: parentState: {fileID: -312774025800194259} trueRect: serializedVersion: 2 - x: -93 - y: 844 + x: 235 + y: 823 width: 10 height: 10 falseRect: serializedVersion: 2 - x: -93 - y: 854 + x: 235 + y: 833 width: 10 height: 10 selectedTrue: 0 @@ -1106,7 +1077,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a5fc604039227434d8b4e63ebc5e74a5, type: 3} m_Name: FSM_Demon m_EditorClassIdentifier: - selectedNode: {fileID: 4162026404432437805} + selectedNode: {fileID: 2986668563461644515} wantConnection: 0 connectionNode: {fileID: 0} showProperties: 1 @@ -1123,7 +1094,7 @@ MonoBehaviour: - {fileID: 9112689765763526057} - {fileID: 766956384951898899} - {fileID: 4162026404432437805} - panOffset: {x: -410, y: 290} + panOffset: {x: -795, y: 390} overNode: 0 actions: - {fileID: 0} @@ -1207,17 +1178,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 772 - y: 956 + x: 1100 + y: 935 width: 150 height: 62 - positionRect: {x: 772, y: 956} + positionRect: {x: 1100, y: 935} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 1 transitions: - decisions: [] @@ -1230,14 +1201,14 @@ MonoBehaviour: parentState: {fileID: 762670965814380212} trueRect: serializedVersion: 2 - x: 762 - y: 986 + x: 1090 + y: 965 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 922 - y: 996 + x: 1250 + y: 975 width: 10 height: 10 selectedTrue: 0 @@ -1277,11 +1248,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 840 - y: 410 + x: 1165 + y: 385 width: 150 height: 62 - positionRect: {x: 840, y: 410} + positionRect: {x: 1165, y: 385} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1300,14 +1271,14 @@ MonoBehaviour: parentState: {fileID: 766956384951898899} trueRect: serializedVersion: 2 - x: 830 - y: 440 + x: 1155 + y: 415 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 990 - y: 450 + x: 1315 + y: 425 width: 10 height: 10 selectedTrue: 0 @@ -1396,17 +1367,17 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 562 - y: 956 + x: 890 + y: 935 width: 150 height: 62 - positionRect: {x: 562, y: 956} + positionRect: {x: 890, y: 935} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 1 transitions: - decisions: [] @@ -1419,14 +1390,14 @@ MonoBehaviour: parentState: {fileID: 2691300596403639167} trueRect: serializedVersion: 2 - x: 552 - y: 986 + x: 880 + y: 965 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 712 - y: 996 + x: 1040 + y: 975 width: 10 height: 10 selectedTrue: 0 @@ -1456,33 +1427,33 @@ MonoBehaviour: m_Name: Combat m_EditorClassIdentifier: description: FSM State - selectedDecisionIndex: 0 + selectedDecisionIndex: 4 canRemove: 1 canTranstTo: 1 canSetAsDefault: 1 canEditName: 1 canEditColor: 1 isOpen: 1 - isSelected: 0 + isSelected: 1 nodeRect: serializedVersion: 2 - x: 307 - y: 676 + x: 635 + y: 655 width: 150 height: 150 - positionRect: {x: 307, y: 676} + positionRect: {x: 635, y: 655} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 0, b: 0, a: 1} resizeLeft: 0 resizeRight: 0 - inDrag: 1 + inDrag: 0 resetCurrentDestination: 1 transitions: - decisions: - trueValue: 1 decision: {fileID: 7927421991537792917} - isValid: 1 + isValid: 0 validated: 0 trueState: {fileID: -2904979146780567904} falseState: {fileID: 0} @@ -1493,14 +1464,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 297 - y: 706 + x: 625 + y: 685 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 457 - y: 716 + x: 785 + y: 695 width: 10 height: 10 selectedTrue: 0 @@ -1525,14 +1496,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 297 - y: 728 + x: 625 + y: 707 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 457 - y: 738 + x: 785 + y: 717 width: 10 height: 10 selectedTrue: 0 @@ -1561,14 +1532,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 457 - y: 750 + x: 785 + y: 729 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 457 - y: 760 + x: 785 + y: 739 width: 10 height: 10 selectedTrue: 0 @@ -1586,7 +1557,7 @@ MonoBehaviour: validated: 0 - trueValue: 1 decision: {fileID: 8113515040269600600} - isValid: 1 + isValid: 0 validated: 0 trueState: {fileID: 9112689765763526057} falseState: {fileID: 0} @@ -1597,14 +1568,14 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 457 - y: 772 + x: 785 + y: 751 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 457 - y: 782 + x: 785 + y: 761 width: 10 height: 10 selectedTrue: 0 @@ -1618,11 +1589,11 @@ MonoBehaviour: - decisions: - trueValue: 0 decision: {fileID: -6379838510941931433} - isValid: 0 + isValid: 1 validated: 0 - trueValue: 1 - decision: {fileID: -7016508256595524775} - isValid: 1 + decision: {fileID: 2998305265418220943} + isValid: 0 validated: 0 trueState: {fileID: 4162026404432437805} falseState: {fileID: 0} @@ -1633,17 +1604,17 @@ MonoBehaviour: parentState: {fileID: 2986668563461644515} trueRect: serializedVersion: 2 - x: 457 - y: 794 + x: 785 + y: 773 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 457 - y: 804 + x: 785 + y: 783 width: 10 height: 10 - selectedTrue: 0 + selectedTrue: 1 selectedFalse: 0 trueSideRight: 1 falseSideRight: 1 @@ -1657,6 +1628,38 @@ MonoBehaviour: useDecisions: 1 parentGraph: {fileID: 11400000} defaultTransition: {fileID: 0} +--- !u!114 &2998305265418220943 +MonoBehaviour: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: af82d1d082ce4b9478d420f8ca1e72c2, type: 3} + m_Name: Check Cooldown Meteor + m_EditorClassIdentifier: + parentFSM: {fileID: 11400000} + editingName: 0 + trueRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 10 + height: 10 + falseRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 10 + height: 10 + selectedTrue: 0 + selectedFalse: 0 + cooldownKey: Meteor + cooldownTime: 100 + availableAtStart: 1 + enableDebug: 1 --- !u!114 &4031404829621142413 MonoBehaviour: m_ObjectHideFlags: 1 @@ -1705,14 +1708,14 @@ MonoBehaviour: canEditName: 1 canEditColor: 1 isOpen: 0 - isSelected: 1 + isSelected: 0 nodeRect: serializedVersion: 2 - x: 840 - y: 625 + x: 1160 + y: 600 width: 150 height: 30 - positionRect: {x: 840, y: 625} + positionRect: {x: 1160, y: 600} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1731,14 +1734,14 @@ MonoBehaviour: parentState: {fileID: 4162026404432437805} trueRect: serializedVersion: 2 - x: 915 - y: 640 + x: 1235 + y: 615 width: 0 height: 0 falseRect: serializedVersion: 2 - x: 915 - y: 640 + x: 1235 + y: 615 width: 0 height: 0 selectedTrue: 0 @@ -1862,7 +1865,7 @@ MonoBehaviour: cooldownKey: Shield cooldownTime: 75 availableAtStart: 1 - enableDebug: 0 + enableDebug: 1 --- !u!114 &8860036500635384459 MonoBehaviour: m_ObjectHideFlags: 1 @@ -1904,11 +1907,11 @@ MonoBehaviour: isSelected: 0 nodeRect: serializedVersion: 2 - x: 845 - y: 230 + x: 1170 + y: 205 width: 150 height: 62 - positionRect: {x: 845, y: 230} + positionRect: {x: 1170, y: 205} rectWidth: 150 editingName: 1 nodeColor: {r: 1, g: 1, b: 1, a: 1} @@ -1927,14 +1930,14 @@ MonoBehaviour: parentState: {fileID: 9112689765763526057} trueRect: serializedVersion: 2 - x: 835 - y: 260 + x: 1160 + y: 235 width: 10 height: 10 falseRect: serializedVersion: 2 - x: 995 - y: 270 + x: 1320 + y: 245 width: 10 height: 10 selectedTrue: 0