786 lines
28 KiB
C#
786 lines
28 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace BulletHellTemplate
|
|
{
|
|
/// <summary>
|
|
/// Manages the gameplay, including the survival timer, spawning waves of monsters, handling perk levels, and pooling system.
|
|
/// </summary>
|
|
public class GameplayManager : MonoBehaviour
|
|
{
|
|
public static GameplayManager Singleton { get; private set; }
|
|
|
|
public enum WinCondition
|
|
{
|
|
SurvivalTime, // Win by surviving until the time runs out
|
|
KillBoss, // Win by killing a specific boss monster within the time limit
|
|
SurvivalTimeAndKillBoss // Win by surviving until the time runs out and then killing the final boss
|
|
}
|
|
|
|
public enum UpgradeMode
|
|
{
|
|
UpgradeOnLevelUp,
|
|
UpgradeOnButtonClick,
|
|
UpgradeRandom
|
|
}
|
|
|
|
public WinCondition winCondition; // The selected win condition for this game
|
|
public UpgradeMode upgradeMode = UpgradeMode.UpgradeOnLevelUp;
|
|
public MonsterEntity bossPrefab; // The boss to spawn in KillBoss or SurvivalTimeAndKillBoss modes
|
|
public int survivalTime = 600; // Survival time in seconds (10 minutes)
|
|
public List<Wave> waves; // List of configured waves
|
|
private int currentWaveIndex = 0; // Index of the current wave
|
|
private float timeRemaining; // Time remaining for the next wave or win condition
|
|
private bool gameRunning = true; // Control the game state
|
|
private bool isPaused = false; // Tracks if the game is paused
|
|
private float waveStartTime; // Time when the current wave started
|
|
private Coroutine waveSpawner; // Reference to the wave spawning coroutine
|
|
private int monstersKilled; // Tracks the number of monsters killed
|
|
private int goldGain; // Tracks the total gold gained
|
|
private int xpGain; // Tracks the total XP gained
|
|
public int[] xpToNextLevel; // Array to store XP required for each level
|
|
public int maxLevel; // Maximum level a character can reach
|
|
public int minDamage = 5;
|
|
|
|
[Header("Perks Settings")]
|
|
public int maxLevelStatPerks = 5;
|
|
public int maxLevelSkillPerks = 5;
|
|
public int maxSkills = 5;
|
|
public int maxStats = 5;
|
|
|
|
public List<SpawnPoints> spawnPointsList; // List of available spawn points
|
|
[HideInInspector] public CharacterEntity character;
|
|
|
|
private Dictionary<object, int> perkLevels = new Dictionary<object, int>();
|
|
private Dictionary<SkillPerkData, int> skillLevels = new Dictionary<SkillPerkData, int>(); // Tracks the levels of each skill
|
|
private Dictionary<SkillData, int> skillBaseLevels = new Dictionary<SkillData, int>(); // Tracks levels of base skills
|
|
|
|
public enum TimeFormat
|
|
{
|
|
Seconds,
|
|
MinutesSeconds
|
|
}
|
|
public SkillPerkData[] skillPerkData;
|
|
public StatPerkData[] statPerkData;
|
|
public TimeFormat timeDisplayFormat = TimeFormat.Seconds; // Default time display format
|
|
|
|
[Header("Pooling Settings")]
|
|
[Tooltip("Enable pooling system for mobs.")]
|
|
public bool usePooling = true;
|
|
private Dictionary<MonsterEntity, Queue<MonsterEntity>> mobPools = new Dictionary<MonsterEntity, Queue<MonsterEntity>>();
|
|
private int totalMobsToSpawn = 0; // Total number of mobs to pre-instantiate
|
|
private int mobsInstantiated = 0; // Mobs instantiated so far
|
|
private bool isPoolingReady = false; // Indicates if pooling is completed
|
|
|
|
private void Awake()
|
|
{
|
|
// Implement singleton pattern
|
|
if (Singleton == null)
|
|
{
|
|
Singleton = this;
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
|
|
// Initialize perk levels dictionary
|
|
perkLevels = new Dictionary<object, int>();
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
StartCoroutine(FindCharacterEntity());
|
|
timeRemaining = survivalTime;
|
|
|
|
// Start the pooling process
|
|
if (usePooling)
|
|
{
|
|
StartCoroutine(InitializePooling());
|
|
}
|
|
else
|
|
{
|
|
isPoolingReady = true;
|
|
StartGameplay(); // Start the game immediately if pooling is not used
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine that finds the CharacterEntity in the scene and sets up related gameplay settings.
|
|
/// </summary>
|
|
/// <returns>An IEnumerator for coroutine execution.</returns>
|
|
private IEnumerator FindCharacterEntity()
|
|
{
|
|
// Loop until the CharacterEntity is found in the scene
|
|
while (character == null)
|
|
{
|
|
character = FindObjectOfType<CharacterEntity>();
|
|
|
|
if (character == null)
|
|
{
|
|
yield return new WaitForSeconds(0.1f); // Wait for a short period before retrying
|
|
}
|
|
}
|
|
|
|
if (character.GetCharacterData().autoAttack != null)
|
|
{
|
|
var aaSkill = character.GetCharacterData().autoAttack;
|
|
if (!skillBaseLevels.ContainsKey(aaSkill))
|
|
{
|
|
skillBaseLevels[aaSkill] = 0;
|
|
}
|
|
}
|
|
|
|
foreach (var skill in character.GetCharacterData().skills)
|
|
{
|
|
if (!skillBaseLevels.ContainsKey(skill))
|
|
{
|
|
skillBaseLevels[skill] = 0;
|
|
}
|
|
}
|
|
|
|
// Ensure that the UIGameplay Singleton is initialized
|
|
while (UIGameplay.Singleton == null)
|
|
{
|
|
yield return new WaitForSeconds(0.1f);
|
|
}
|
|
|
|
// Now that the character is found, set maxSkills and maxStats based on the character's current stats
|
|
maxSkills = (int)character.GetCurrentMaxSkills();
|
|
maxStats = (int)character.GetCurrentMaxStats();
|
|
|
|
// Update the UI to reflect the new perk settings
|
|
UIGameplay.Singleton.UpdateSkillPerkUI();
|
|
UIGameplay.Singleton.UpdateStatPerkUI();
|
|
}
|
|
|
|
public CharacterEntity GetCharacterEntity()
|
|
{
|
|
return character;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine to initialize the pooling system by pre-instantiating mobs.
|
|
/// Calculates the total number of mobs needed and instantiates them.
|
|
/// </summary>
|
|
/// <returns>An IEnumerator for coroutine execution.</returns>
|
|
private IEnumerator InitializePooling()
|
|
{
|
|
// Calculate total mobs needed
|
|
CalculateTotalMobsToSpawn();
|
|
|
|
// Instantiate mobs
|
|
foreach (Wave wave in waves)
|
|
{
|
|
foreach (MonsterConfig monsterConfig in wave.monsters)
|
|
{
|
|
MonsterEntity monsterPrefab = monsterConfig.monsterPrefab;
|
|
int mobsNeeded = Mathf.FloorToInt(wave.waveDuration / monsterConfig.spawnInterval);
|
|
|
|
if (!mobPools.ContainsKey(monsterPrefab))
|
|
{
|
|
mobPools[monsterPrefab] = new Queue<MonsterEntity>();
|
|
}
|
|
|
|
for (int i = 0; i < mobsNeeded; i++)
|
|
{
|
|
MonsterEntity mobInstance = Instantiate(monsterPrefab);
|
|
mobInstance.gameObject.SetActive(false);
|
|
mobPools[monsterPrefab].Enqueue(mobInstance);
|
|
|
|
mobsInstantiated++;
|
|
yield return null; // Yield to allow loading progress to update
|
|
}
|
|
}
|
|
}
|
|
|
|
isPoolingReady = true;
|
|
StartGameplay(); // Start the game after pooling is ready
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the gameplay by initiating the survival countdown and spawning waves.
|
|
/// </summary>
|
|
private void StartGameplay()
|
|
{
|
|
StartCoroutine(SurvivalCountdown());
|
|
waveSpawner = StartCoroutine(SpawnWaves());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the pooling is ready.
|
|
/// </summary>
|
|
/// <returns>True if pooling is ready; otherwise, false.</returns>
|
|
public bool IsPoolingReady()
|
|
{
|
|
return isPoolingReady;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the total number of mobs that need to be spawned across all waves.
|
|
/// </summary>
|
|
private void CalculateTotalMobsToSpawn()
|
|
{
|
|
totalMobsToSpawn = 0;
|
|
|
|
foreach (Wave wave in waves)
|
|
{
|
|
foreach (MonsterConfig monsterConfig in wave.monsters)
|
|
{
|
|
int mobsNeeded = Mathf.FloorToInt(wave.waveDuration / monsterConfig.spawnInterval);
|
|
totalMobsToSpawn += mobsNeeded;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the current loading progress percentage of the pooling system.
|
|
/// </summary>
|
|
/// <returns>A float value between 0 and 1 representing the loading progress.</returns>
|
|
public float GetLoadingProgress()
|
|
{
|
|
if (totalMobsToSpawn == 0)
|
|
return 1f;
|
|
|
|
return (float)mobsInstantiated / totalMobsToSpawn;
|
|
}
|
|
|
|
|
|
IEnumerator SurvivalCountdown()
|
|
{
|
|
while (timeRemaining > 0 && gameRunning)
|
|
{
|
|
if (!isPaused)
|
|
{
|
|
yield return new WaitForSeconds(1f);
|
|
timeRemaining--;
|
|
}
|
|
else
|
|
{
|
|
yield return null; // Wait for next frame while paused
|
|
}
|
|
}
|
|
|
|
if (gameRunning && (winCondition == WinCondition.SurvivalTime || winCondition == WinCondition.SurvivalTimeAndKillBoss))
|
|
{
|
|
HandleWinCondition();
|
|
}
|
|
}
|
|
|
|
IEnumerator SpawnWaves()
|
|
{
|
|
// Wait until pooling is ready
|
|
while (usePooling && !isPoolingReady)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
while (currentWaveIndex < waves.Count && gameRunning)
|
|
{
|
|
Wave currentWave = waves[currentWaveIndex];
|
|
waveStartTime = Time.time;
|
|
List<float> nextSpawnTimes = new List<float>(new float[currentWave.monsters.Count]);
|
|
List<int> remainingSpawns = new List<int>(new int[currentWave.monsters.Count]);
|
|
|
|
// Initialize spawn times for each monster configuration and set remaining spawns to initial value
|
|
for (int i = 0; i < currentWave.monsters.Count; i++)
|
|
{
|
|
nextSpawnTimes[i] = waveStartTime + currentWave.monsters[i].spawnInterval;
|
|
remainingSpawns[i] = Mathf.FloorToInt(currentWave.waveDuration / currentWave.monsters[i].spawnInterval); // Calculate total possible spawns in the wave duration
|
|
}
|
|
|
|
// Continue spawning monsters while the wave duration is not reached or until all spawns are completed
|
|
while (Time.time - waveStartTime < currentWave.waveDuration && remainingSpawns.Exists(spawnCount => spawnCount > 0) && gameRunning)
|
|
{
|
|
if (!isPaused)
|
|
{
|
|
float currentTime = Time.time;
|
|
for (int i = 0; i < currentWave.monsters.Count; i++)
|
|
{
|
|
// Check if it's time to spawn the next monster of this type and if there are remaining spawns
|
|
if (currentTime >= nextSpawnTimes[i] && remainingSpawns[i] > 0)
|
|
{
|
|
SpawnMonster(currentWave.monsters[i].monsterPrefab, currentWave.monsters[i].goldPerMonster, currentWave.monsters[i].xpPerMonster);
|
|
// Update the next spawn time for this monster type
|
|
nextSpawnTimes[i] = currentTime + currentWave.monsters[i].spawnInterval;
|
|
remainingSpawns[i]--; // Decrease the count of remaining spawns
|
|
}
|
|
}
|
|
}
|
|
yield return null; // Yield to keep the game responsive and check the next frame
|
|
}
|
|
|
|
// Mark this wave as completed
|
|
currentWaveIndex++;
|
|
|
|
// If there's another wave, start the next wave
|
|
if (currentWaveIndex < waves.Count && gameRunning)
|
|
{
|
|
StopCoroutine(waveSpawner); // Ensure that the previous coroutine stops before starting a new one
|
|
waveSpawner = StartCoroutine(SpawnWaves());
|
|
}
|
|
else if (winCondition != WinCondition.SurvivalTime) // Only handle win condition here if it's not SurvivalTime
|
|
{
|
|
// If there are no more waves, ensure the spawning stops
|
|
StopCoroutine(waveSpawner);
|
|
waveSpawner = null;
|
|
HandleWinCondition();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles the win condition logic based on the selected game mode.
|
|
/// </summary>
|
|
private void HandleWinCondition()
|
|
{
|
|
if (winCondition == WinCondition.SurvivalTime)
|
|
{
|
|
EndGame();
|
|
}
|
|
else if (winCondition == WinCondition.KillBoss)
|
|
{
|
|
// Spawn the boss immediately and show the final boss message
|
|
SpawnMonster(bossPrefab, 0, 0);
|
|
|
|
UIGameplay.Singleton.ShowFinalBossMessage(); // Show the final boss message
|
|
StartCoroutine(CheckForBossDefeat());
|
|
}
|
|
else if (winCondition == WinCondition.SurvivalTimeAndKillBoss)
|
|
{
|
|
// Stop spawning waves when time ends and spawn the boss
|
|
if (waveSpawner != null)
|
|
{
|
|
StopCoroutine(waveSpawner);
|
|
}
|
|
|
|
if (timeRemaining <= 0)
|
|
{
|
|
SpawnMonster(bossPrefab, 0, 0);
|
|
|
|
UIGameplay.Singleton.ShowFinalBossMessage(); // Show the final boss message
|
|
StartCoroutine(CheckForBossDefeat());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine that checks if the boss has been defeated.
|
|
/// </summary>
|
|
/// <returns>An IEnumerator for coroutine execution.</returns>
|
|
private IEnumerator CheckForBossDefeat()
|
|
{
|
|
// Continuously check for any boss entity with isFinalBoss == true
|
|
while (true)
|
|
{
|
|
var bossInstances = FindObjectsOfType<MonsterEntity>();
|
|
bool bossDefeated = true;
|
|
|
|
foreach (var boss in bossInstances)
|
|
{
|
|
if (boss.isFinalBoss && boss.gameObject.activeSelf)
|
|
{
|
|
bossDefeated = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bossDefeated)
|
|
{
|
|
// When the final boss is defeated, end the game
|
|
EndGame();
|
|
yield break;
|
|
}
|
|
|
|
yield return null; // Wait for the next frame and recheck
|
|
}
|
|
}
|
|
|
|
void SpawnMonster(MonsterEntity monsterPrefab, int gold, int xp)
|
|
{
|
|
MonsterEntity monsterInstance = null;
|
|
|
|
if (usePooling && isPoolingReady)
|
|
{
|
|
// Get an inactive mob from the pool
|
|
if (mobPools.ContainsKey(monsterPrefab) && mobPools[monsterPrefab].Count > 0)
|
|
{
|
|
monsterInstance = mobPools[monsterPrefab].Dequeue();
|
|
monsterInstance.transform.position = GetRandomSpawnPoint();
|
|
monsterInstance.gameObject.SetActive(true);
|
|
}
|
|
else
|
|
{
|
|
// Instantiate a new one if pool is empty
|
|
monsterInstance = Instantiate(monsterPrefab, GetRandomSpawnPoint(), Quaternion.identity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Instantiate normally
|
|
monsterInstance = Instantiate(monsterPrefab, GetRandomSpawnPoint(), Quaternion.identity);
|
|
}
|
|
|
|
if (monsterInstance.isFinalBoss)
|
|
{
|
|
UIGameplay.Singleton.SetFinalBoss(monsterInstance);
|
|
}
|
|
monsterInstance.SetMonsterGoldValue(gold);
|
|
monsterInstance.SetMonsterXPValue(xp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a random spawn point from the available spawn points and returns a random position within that point's radius.
|
|
/// </summary>
|
|
/// <returns>A random Vector3 position from a random spawn point in the list.</returns>
|
|
Vector3 GetRandomSpawnPoint()
|
|
{
|
|
if (spawnPointsList == null || spawnPointsList.Count == 0)
|
|
{
|
|
Debug.LogWarning("No spawn points available.");
|
|
return Vector3.zero;
|
|
}
|
|
|
|
// Select a random spawn point from the list
|
|
int randomIndex = Random.Range(0, spawnPointsList.Count);
|
|
SpawnPoints selectedSpawnPoint = spawnPointsList[randomIndex];
|
|
|
|
// Get a random position from the selected spawn point
|
|
return selectedSpawnPoint.GetRandomSpawnPoint();
|
|
}
|
|
|
|
public void EndGame()
|
|
{
|
|
gameRunning = false;
|
|
PauseGame();
|
|
GameManager.Singleton.EndGame(true);
|
|
}
|
|
|
|
public string GetFormattedTime()
|
|
{
|
|
if (timeDisplayFormat == TimeFormat.Seconds)
|
|
{
|
|
return Mathf.CeilToInt(timeRemaining).ToString() + "s";
|
|
}
|
|
else if (timeDisplayFormat == TimeFormat.MinutesSeconds)
|
|
{
|
|
int minutes = Mathf.FloorToInt(timeRemaining / 60);
|
|
int seconds = Mathf.FloorToInt(timeRemaining % 60);
|
|
return string.Format("{0:00}:{1:00}", minutes, seconds);
|
|
}
|
|
return timeRemaining.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles the pause state of the game, stopping or resuming waves accordingly.
|
|
/// </summary>
|
|
public void TogglePause()
|
|
{
|
|
isPaused = !isPaused;
|
|
// The SpawnWaves coroutine handles the pause internally, so no need to stop/restart it here.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicitly pauses the game, intended to be used for direct calls when pausing is needed without toggling.
|
|
/// </summary>
|
|
public void PauseGame()
|
|
{
|
|
if (!isPaused) // Only pause if not already paused.
|
|
{
|
|
isPaused = true;
|
|
// The SpawnWaves coroutine will automatically respect the isPaused state.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicitly resumes the game, intended to be used for direct calls when resuming is needed without toggling.
|
|
/// </summary>
|
|
public void ResumeGame()
|
|
{
|
|
if (isPaused) // Only resume if the game is currently paused.
|
|
{
|
|
isPaused = false;
|
|
// The SpawnWaves coroutine will automatically resume respecting the isPaused state.
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the game is currently paused.
|
|
/// </summary>
|
|
/// <returns>True if the game is paused; otherwise, false.</returns>
|
|
public bool IsPaused()
|
|
{
|
|
return isPaused;
|
|
}
|
|
|
|
public int GetMonstersKilled()
|
|
{
|
|
return monstersKilled;
|
|
}
|
|
|
|
public void IncrementMonstersKilled()
|
|
{
|
|
monstersKilled++;
|
|
}
|
|
|
|
public int GetGainGold()
|
|
{
|
|
return goldGain;
|
|
}
|
|
|
|
public void IncrementGainGold(int amount)
|
|
{
|
|
goldGain += amount;
|
|
}
|
|
|
|
public int GetGainXP()
|
|
{
|
|
return xpGain;
|
|
}
|
|
|
|
public void IncrementGainXP(int amount)
|
|
{
|
|
xpGain += amount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a list of available perks, filtering out any skill levels marked as evolved that the player cannot access yet.
|
|
/// </summary>
|
|
/// <returns>A list of perks available for the player to choose from.</returns>
|
|
public List<object> GetRandomPerks()
|
|
{
|
|
List<object> availablePerks = new List<object>();
|
|
|
|
foreach (var skill in character.GetCharacterData().skills)
|
|
{
|
|
int currentLevel = GetBaseSkillLevel(skill);
|
|
int maxLevelIndex = skill.skillLevels.Count - 1;
|
|
int nextLevel = currentLevel + 1;
|
|
|
|
if (nextLevel <= maxLevelIndex)
|
|
{
|
|
bool isNextLevelEvolved = skill.skillLevels[nextLevel].isEvolved;
|
|
bool hasRequiredStatPerk = skill.requireStatForEvolve == null || character.HasStatPerk(skill.requireStatForEvolve);
|
|
|
|
if (!isNextLevelEvolved || hasRequiredStatPerk)
|
|
{
|
|
availablePerks.Add(skill);
|
|
}
|
|
}
|
|
}
|
|
if (character.GetCharacterData().autoAttack != null)
|
|
{
|
|
SkillData aaSkill = character.GetCharacterData().autoAttack;
|
|
int currentLevel = GetBaseSkillLevel(aaSkill);
|
|
int maxLevelIndex = aaSkill.skillLevels.Count - 1;
|
|
int nextLevel = currentLevel + 1;
|
|
|
|
if (nextLevel <= maxLevelIndex)
|
|
{
|
|
bool isNextLevelEvolved = aaSkill.skillLevels[nextLevel].isEvolved;
|
|
bool hasRequiredStatPerk = aaSkill.requireStatForEvolve == null || character.HasStatPerk(aaSkill.requireStatForEvolve);
|
|
|
|
if (!isNextLevelEvolved || hasRequiredStatPerk)
|
|
{
|
|
availablePerks.Add(aaSkill);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var skillPerk in skillPerkData)
|
|
{
|
|
int currentLevel = GetSkillLevel(skillPerk);
|
|
int maxLevel = skillPerk.maxLevel;
|
|
|
|
if (currentLevel < maxLevel)
|
|
{
|
|
// SkillPerk can be leveled up
|
|
availablePerks.Add(skillPerk);
|
|
}
|
|
else if (currentLevel == maxLevel)
|
|
{
|
|
if (skillPerk.hasEvolution && character.HasStatPerk(skillPerk.perkRequireToEvolveSkill))
|
|
{
|
|
// SkillPerk can be evolved
|
|
availablePerks.Add(skillPerk);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var statPerk in statPerkData)
|
|
{
|
|
int currentLevel = GetPerkLevel(statPerk);
|
|
if (currentLevel < maxLevelStatPerks)
|
|
{
|
|
availablePerks.Add(statPerk);
|
|
}
|
|
}
|
|
|
|
ShufflePerks(availablePerks);
|
|
|
|
return availablePerks.GetRange(0, Mathf.Min(3, availablePerks.Count));
|
|
}
|
|
|
|
|
|
private void ShufflePerks(List<object> perks)
|
|
{
|
|
for (int i = perks.Count - 1; i > 0; i--)
|
|
{
|
|
int j = Random.Range(0, i + 1);
|
|
object temp = perks[i];
|
|
perks[i] = perks[j];
|
|
perks[j] = temp;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current level of a given perk.
|
|
/// </summary>
|
|
/// <param name="perk">The perk whose level is to be retrieved.</param>
|
|
/// <returns>The current level of the perk. Returns 0 if the perk is not found.</returns>
|
|
public int GetPerkLevel(object perk)
|
|
{
|
|
if (perk is SkillPerkData skillPerk)
|
|
{
|
|
if (skillLevels.TryGetValue(skillPerk, out int level))
|
|
{
|
|
return level;
|
|
}
|
|
}
|
|
else if (perk is StatPerkData statPerk)
|
|
{
|
|
if (perkLevels.TryGetValue(statPerk, out int level))
|
|
{
|
|
return level;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Sets the level of a given perk, handling skill and stat perks separately.
|
|
/// </summary>
|
|
/// <param name="perk">The perk whose level is to be set.</param>
|
|
/// <param name="level">The new level of the perk.</param>
|
|
public void SetPerkLevel(object perk)
|
|
{
|
|
if (perk is SkillPerkData skillPerk)
|
|
{
|
|
LevelUpSkill(skillPerk);
|
|
}
|
|
else if (perk is StatPerkData statPerk)
|
|
{
|
|
if (perkLevels.ContainsKey(statPerk))
|
|
{
|
|
perkLevels[statPerk]++;
|
|
}
|
|
else
|
|
{
|
|
perkLevels.Add(statPerk, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Unsupported perk type.");
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Levels up a skill to a specified level, including checks for evolution requirements.
|
|
/// </summary>
|
|
/// <param name="skillPerk">The skill perk to level up.</param>
|
|
/// <param name="level">The new level to set, if requirements are met.</param>
|
|
private void LevelUpSkill(SkillPerkData skillPerk)
|
|
{
|
|
if (skillPerk == null)
|
|
{
|
|
Debug.LogWarning("SkillPerkData is null.");
|
|
return;
|
|
}
|
|
if (!skillLevels.ContainsKey(skillPerk))
|
|
{
|
|
skillLevels[skillPerk] = 0;
|
|
}
|
|
|
|
int currentLevel = skillLevels[skillPerk];
|
|
|
|
if (currentLevel >= skillPerk.maxLevel)
|
|
{
|
|
Debug.LogWarning($"Attempting to level up {skillPerk.name} beyond max level {skillPerk.maxLevel}.");
|
|
return;
|
|
}
|
|
|
|
if (currentLevel == skillPerk.maxLevel - 1 && skillPerk.hasEvolution && !character.HasStatPerk(skillPerk.perkRequireToEvolveSkill))
|
|
{
|
|
Debug.LogWarning($"Cannot level up {skillPerk.name} to max level without having {skillPerk.perkRequireToEvolveSkill.name}.");
|
|
return;
|
|
}
|
|
|
|
skillLevels[skillPerk] = currentLevel + 1;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the current level of a specific skill.
|
|
/// </summary>
|
|
/// <param name="skillPerk">The skill perk to check the level for.</param>
|
|
/// <returns>The current level of the skill.</returns>
|
|
public int GetSkillLevel(SkillPerkData skillPerk)
|
|
{
|
|
if (skillPerk == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (skillLevels.ContainsKey(skillPerk))
|
|
{
|
|
return skillLevels[skillPerk];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Levels up a base skill of the character.
|
|
/// </summary>
|
|
/// <param name="skill">The skill to level up.</param>
|
|
public void LevelUpBaseSkill(SkillData skill)
|
|
{
|
|
if (!skillBaseLevels.ContainsKey(skill))
|
|
{
|
|
skillBaseLevels[skill] = 0;
|
|
}
|
|
|
|
int currentLevel = skillBaseLevels[skill];
|
|
int maxLevelIndex = skill.skillLevels.Count - 1;
|
|
|
|
if (currentLevel <= maxLevelIndex)
|
|
{
|
|
skillBaseLevels[skill]++;
|
|
Debug.Log($"Skill {skill.skillName} leveled up to {skillBaseLevels[skill]}.");
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the current level of a base skill.
|
|
/// </summary>
|
|
/// <param name="skill">The skill to check.</param>
|
|
/// <returns>The current level of the skill.</returns>
|
|
public int GetBaseSkillLevel(SkillData skill)
|
|
{
|
|
if (skillBaseLevels.TryGetValue(skill, out int level))
|
|
{
|
|
return level;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
}
|