This commit is contained in:
SzymonMis
2026-02-19 21:34:07 +01:00
parent 8de064552e
commit 06f9c7349d
25 changed files with 45269 additions and 346 deletions

View File

@@ -2,6 +2,7 @@ using Invector;
using Invector.vCharacterController.AI;
using System.Collections;
using System.Collections.Generic;
using Lean.Pool;
using UnityEngine;
namespace DemonBoss.Summoner
@@ -43,6 +44,16 @@ namespace DemonBoss.Summoner
[Tooltip("Minimum distance to player before engaging in melee")]
public float meleeEngageDistance = 3f;
[Header("Melee Cooldown")]
[Tooltip("Minimum seconds between melee attacks")]
public float meleeCooldownMin = 5f;
[Tooltip("Maximum seconds between melee attacks")]
public float meleeCooldownMax = 8f;
[Tooltip("Preferred safe distance from player (used for flee/spacing)")]
public float safeDistance = 8f;
[Tooltip("Should summoner fight when minions are alive?")]
public bool fightWithMinions = false;
@@ -61,6 +72,29 @@ namespace DemonBoss.Summoner
[Tooltip("Sound played when spawning minions")]
public AudioClip summonSound;
[Header("Summon Restrictions")]
[Tooltip("If true, summoner will only summon when no minions are alive")]
public bool requireZeroMinionsToSummon = true;
[Header("Spell Casting")]
[Tooltip("Fireball prefab (projectile handles its own targeting)")]
public GameObject fireballPrefab;
[Tooltip("Optional spawn pivot for spells (e.g. staff tip). If null, uses summoner transform.")]
public Transform spellSpawnPivot;
[Tooltip("Cooldown between spell casts (seconds)")]
public float spellCooldown = 6f;
[Tooltip("Minimum distance required to cast spell")]
public float spellMinDistance = 5f;
[Tooltip("Maximum distance allowed to cast spell (0 = no limit)")]
public float spellMaxDistance = 20f;
[Tooltip("Only cast spell when minions are alive")]
public bool spellRequiresMinionsAlive = true;
[Header("Debug")]
[Tooltip("Enable debug logging")]
public bool enableDebug = false;
@@ -76,12 +110,23 @@ namespace DemonBoss.Summoner
private vHealthController healthController;
private bool isSpawning = false;
private Coroutine spawnCoroutine;
private float lastSpellTime = -999f;
private bool spawnRequested = false;
private float nextMeleeTime = 0f;
// Public properties for FSM decisions
public bool IsSpawning => isSpawning;
public int ActiveMinionCount => activeMinions.Count;
public bool CanSpawnMinions => activeMinions.Count < maxActiveMinions && !isSpawning;
public bool CanSpawnMinions
{
get
{
if (isSpawning) return false;
if (requireZeroMinionsToSummon && activeMinions.Count > 0) return false;
return activeMinions.Count < maxActiveMinions;
}
}
public bool HasActiveMinions => activeMinions.Count > 0;
private void Awake()
@@ -125,6 +170,7 @@ namespace DemonBoss.Summoner
/// </summary>
public void StartSpawning()
{
spawnRequested = false;
if (isSpawning)
{
if (enableDebug) Debug.Log("[SummonerAI] Already spawning minions");
@@ -137,9 +183,44 @@ namespace DemonBoss.Summoner
return;
}
if (requireZeroMinionsToSummon && HasActiveMinions)
{
if (enableDebug) Debug.Log("[SummonerAI] Minions alive - summon blocked");
return;
}
spawnCoroutine = StartCoroutine(SpawnMinionsCoroutine());
}
/// <summary>
/// Request spawning to begin on an animation event.
/// </summary>
public void RequestSpawn()
{
spawnRequested = true;
}
/// <summary>
/// Cancel a pending spawn request (e.g., on state exit).
/// </summary>
public void CancelSpawnRequest()
{
spawnRequested = false;
}
/// <summary>
/// Animation event hook. Call from the summon animation.
/// </summary>
public void OnSummonAnimationEvent()
{
if (!spawnRequested)
{
return;
}
StartSpawning();
}
/// <summary>
/// Stop spawning minions immediately
/// </summary>
@@ -282,14 +363,102 @@ namespace DemonBoss.Summoner
return GetDistanceToPlayer() <= meleeEngageDistance;
}
/// <summary>
/// Check if player is within the desired safe distance
/// </summary>
public bool IsPlayerTooCloseForSafeDistance()
{
return GetDistanceToPlayer() < safeDistance;
}
/// <summary>
/// Check if summoner has reached/maintains safe distance
/// </summary>
public bool IsAtSafeDistance()
{
return GetDistanceToPlayer() >= safeDistance;
}
/// <summary>
/// Should summoner try to flee to reach safe distance
/// </summary>
public bool ShouldFleeToSafeDistance()
{
float distance = GetDistanceToPlayer();
if (distance <= meleeEngageDistance) return false;
if (CanSpawnMinions || CanCastSpell()) return false;
return distance < safeDistance;
}
/// <summary>
/// Check if summoner should engage in melee combat
/// </summary>
public bool ShouldEngageMelee()
{
if (!IsPlayerInMeleeRange()) return false;
if (fightWithMinions) return true;
return !HasActiveMinions;
if (!CanMeleeNow()) return false;
return !CanSpawnMinions && !CanCastSpell();
}
public bool CanMeleeNow()
{
return Time.time >= nextMeleeTime;
}
public void NotifyMeleeAttack()
{
float min = Mathf.Min(meleeCooldownMin, meleeCooldownMax);
float max = Mathf.Max(meleeCooldownMin, meleeCooldownMax);
if (max <= 0f)
{
nextMeleeTime = 0f;
return;
}
nextMeleeTime = Time.time + Random.Range(min, max);
}
/// <summary>
/// Check if summoner can cast a ranged spell now
/// </summary>
public bool CanCastSpell()
{
if (isSpawning) return false;
if (fireballPrefab == null) return false;
if (spellRequiresMinionsAlive && !HasActiveMinions) return false;
float distance = GetDistanceToPlayer();
if (distance < spellMinDistance) return false;
if (spellMaxDistance > 0f && distance > spellMaxDistance) return false;
return (Time.time - lastSpellTime) >= spellCooldown;
}
/// <summary>
/// Notify that a spell was cast (updates cooldown timer)
/// </summary>
public void NotifySpellCast()
{
lastSpellTime = Time.time;
}
/// <summary>
/// Spawn a fireball from pivot towards player
/// </summary>
public void CastFireball()
{
if (!CanCastSpell()) return;
Transform pivot = spellSpawnPivot != null ? spellSpawnPivot : transform;
Vector3 targetPos = playerTransform != null ? playerTransform.position + Vector3.up * 1f : (pivot.position + pivot.forward);
Vector3 dir = (targetPos - pivot.position).normalized;
if (dir == Vector3.zero) dir = pivot.forward;
Quaternion rot = Quaternion.LookRotation(dir);
LeanPool.Spawn(fireballPrefab, pivot.position, rot);
NotifySpellCast();
if (enableDebug) Debug.Log("[SummonerAI] Cast fireball");
}
/// <summary>
@@ -327,6 +496,10 @@ namespace DemonBoss.Summoner
Gizmos.color = Color.red;
DrawCircle(transform.position, meleeEngageDistance, 16);
// Draw safe distance
Gizmos.color = Color.yellow;
DrawCircle(transform.position, safeDistance, 24);
// Draw lines to active minions
Gizmos.color = Color.green;
foreach (GameObject minion in activeMinions)
@@ -356,4 +529,4 @@ namespace DemonBoss.Summoner
}
}
}
}
}