577 lines
23 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TextCore.Text;
namespace BulletHellTemplate
{
/// <summary>
/// Manages the game maps, including loading scenes, spawning characters,
/// and unlocking new maps upon game completion.
/// </summary>
public class GameManager : MonoBehaviour
{
#region Public Fields
[Header("Currency earned in matches")]
[Tooltip("Currency used in the game")]
public string goldCurrency = "GO";
[Header("Map Name MainMenu")]
[Tooltip("Name of the main menu scene")]
public string mainMenuScene = "Home";
[Header("BattlePass earn EXP for winning?")]
[Tooltip("Battle Pass EXP awarded for winning a match")]
public int BattlePassEXP = 210;
/// <summary>
/// Singleton reference to the GameManager.
/// </summary>
public static GameManager Singleton;
#endregion
#region Private Fields
/// <summary>
/// Stores the ID of the current map being played.
/// </summary>
private int currentMapId;
#endregion
#region Unity Callbacks
private void Awake()
{
// Implement singleton pattern
if (Singleton == null)
{
Singleton = this;
}
else
{
Destroy(gameObject);
}
DontDestroyOnLoad(gameObject);
}
#endregion
#region Public Methods
/// <summary>
/// Starts the game with the specified MapId and stores the current map ID.
/// </summary>
/// <param name="mapId">The ID of the map to load.</param>
public void StartGame(int mapId)
{
currentMapId = mapId; // Store the ID of the current map
foreach (var map in GameInstance.Singleton.mapInfoData)
{
if (map.mapId == mapId)
{
bool isUnlocked = map.isUnlocked || PlayerSave.GetUnlockedMaps().Contains(map.mapId);
Debug.Log(isUnlocked);
if (isUnlocked)
{
StartCoroutine(LoadSceneAndSpawnCharacter(map));
}
else
{
Debug.LogError("Map is not unlocked.");
}
return;
}
}
Debug.LogError("MapId not found.");
}
/// <summary>
/// Ends the game, displays a win/lose screen, and returns to the main menu.
/// If the player wins, it checks if the current map is the latest unlocked map and unlocks the next map if true.
/// </summary>
/// <param name="won">Whether the player won the game.</param>
public async void EndGame(bool won)
{
AudioManager.Singleton.StopAllAudioPlay();
UIGameplay.Singleton.DisplayEndGameScreen(won);
// Apply gold earned
int goldEarned = MonetizationManager.GetCurrency(goldCurrency) + GameplayManager.Singleton.GetGainGold();
MonetizationManager.SetCurrency(goldCurrency, goldEarned);
if (won)
{
RewardManagerPopup.Singleton.TrySaveCompletedMap(currentMapId);
if (IsLatestUnlockedMap(currentMapId))
{
UnlockNextMap();
}
}
// Update quest progress based on the current game results
UpdateQuestProgress(won);
// Save the number of monsters killed to the player's Firestore document
int monstersKilled = GameplayManager.Singleton.GetMonstersKilled();
if (BackendManager.Singleton != null)
{
await BackendManager.Singleton.SaveMonstersKilledAsync(monstersKilled);
}
// Add Battle Pass EXP if applicable
if (BattlePassEXP > 0 && BattlePassManager.Singleton != null)
{
BattlePassManager.Singleton.AddXP(BattlePassEXP);
}
}
/// <summary>
/// Adds the specified amount of experience to the player's account,
/// allowing for multiple level-ups if enough EXP is provided.
/// Stops granting experience if max level is reached.
/// </summary>
/// <param name="exp">The amount of experience to add to the account.</param>
public async void AddAccountExp(int exp)
{
int currentLevel = PlayerSave.GetAccountLevel();
int maxLevel = GameInstance.Singleton.accountLevels.accountMaxLevel;
// Do not grant EXP if the account has reached max level
if (currentLevel >= maxLevel)
{
Debug.Log("Maximum level reached. No EXP granted.");
return;
}
int updatedExp = PlayerSave.GetAccountCurrentExp() + exp;
PlayerSave.SetAccountCurrentExp(updatedExp);
bool canLevelUp = true;
while (canLevelUp)
{
currentLevel = PlayerSave.GetAccountLevel();
int currentExp = PlayerSave.GetAccountCurrentExp();
// If during leveling up the account reaches max level,
// reset EXP to 0 and stop processing
if (currentLevel >= maxLevel)
{
PlayerSave.SetAccountCurrentExp(0);
Debug.Log("Reached maximum level. Excess EXP discarded.");
break;
}
int requiredExpToLevelUp = GameInstance.Singleton.GetAccountExpForLevel(currentLevel);
if (currentExp >= requiredExpToLevelUp)
{
int newLevel = currentLevel + 1;
int newCurrentExp = currentExp - requiredExpToLevelUp;
PlayerSave.SetAccountLevel(newLevel);
PlayerSave.SetAccountCurrentExp(newCurrentExp);
if (BackendManager.Singleton != null)
{
await BackendManager.Singleton.UpdatePlayerAccountLevel(newLevel, newCurrentExp);
}
}
else
{
canLevelUp = false;
}
}
}
/// <summary>
/// Adds the specified amount of Battle Pass EXP to the player.
/// </summary>
/// <param name="exp">The amount of Battle Pass EXP to add.</param>
public void AddBattlePassExp(int exp)
{
if (BattlePassManager.Singleton != null)
{
BattlePassManager.Singleton.AddXP(exp);
}
}
public async void SaveCharacterBasicInfo(int characterId)
{
if (BackendManager.Singleton != null)
{
int level = PlayerSave.GetCharacterLevel(characterId);
int currentExp = PlayerSave.GetCharacterCurrentExp(characterId);
int masteryLevel = PlayerSave.GetCharacterMasteryLevel(characterId);
int masteryExp = PlayerSave.GetCharacterCurrentMasteryExp(characterId);
int skinId = PlayerSave.GetCharacterSkin(characterId);
await BackendManager.Singleton.SaveCharacterBasicInfoAsync(characterId, skinId, level, currentExp, masteryLevel, masteryExp);
}
}
/// <summary>
/// Adds the specified amount of experience to a particular character.
/// </summary>
/// <param name="characterId">The character's ID.</param>
/// <param name="exp">The amount of EXP to add to the character.</param>
public bool AddCharacterExp(int characterId, int exp)
{
int updatedExp = PlayerSave.GetCharacterCurrentExp(characterId) + exp;
PlayerSave.SetCharacterCurrentExp(characterId, updatedExp);
return true;
}
/// <summary>
/// Adds mastery experience to a character, handling multiple level-ups.
/// Stops granting experience if max mastery level is reached.
/// </summary>
/// <param name="characterId">The ID of the character.</param>
/// <param name="exp">Amount of mastery experience to add.</param>
public bool AddCharacterMasteryExp(int characterId, int exp)
{
int maxMasteryLevel = GameInstance.Singleton.characterMastery.maxMasteryLevel - 1;
int _currentMasteryLevel = PlayerSave.GetCharacterMasteryLevel(characterId);
if (_currentMasteryLevel >= maxMasteryLevel)
{
Debug.Log("Maximum mastery level reached. No EXP granted.");
return false;
}
int updatedMasteryExp = PlayerSave.GetCharacterCurrentMasteryExp(characterId) + exp;
PlayerSave.SetCharacterCurrentMasteryExp(characterId, updatedMasteryExp);
bool canLevelUp = true;
while (canLevelUp)
{
int currentMasteryLevel = PlayerSave.GetCharacterMasteryLevel(characterId);
int currentMasteryExp = PlayerSave.GetCharacterCurrentMasteryExp(characterId);
if (currentMasteryLevel >= maxMasteryLevel)
{
PlayerSave.SetCharacterCurrentMasteryExp(characterId, 0);
Debug.Log("Reached max mastery level. Excess EXP removed.");
break;
}
int requiredExpToLevelUpMastery = GameInstance.Singleton.GetMasteryExpForLevel(currentMasteryLevel);
if (currentMasteryExp >= requiredExpToLevelUpMastery)
{
int newMasteryLevel = currentMasteryLevel + 1;
int newCurrentMasteryExp = currentMasteryExp - requiredExpToLevelUpMastery;
PlayerSave.SetCharacterMasteryLevel(characterId, newMasteryLevel);
PlayerSave.SetCharacterCurrentMasteryExp(characterId, newCurrentMasteryExp);
currentMasteryLevel = newMasteryLevel;
currentMasteryExp = newCurrentMasteryExp;
}
else
{
canLevelUp = false;
}
}
return true;
}
/// <summary>
/// Retrieves the ID of the current map being played.
/// </summary>
/// <returns>The ID of the current map.</returns>
public int GetCurrentMapId()
{
return currentMapId;
}
#endregion
#region Private Methods
/// <summary>
/// Coroutine that loads the specified map scene asynchronously and,
/// once the scene is fully loaded, spawns the player's character at the designated start position.
/// Uses the LoadingManager to display the loading screen during the process.
/// </summary>
/// <param name="map">The map data containing the scene to be loaded.</param>
/// <returns>An IEnumerator used for coroutine execution.</returns>
private IEnumerator LoadSceneAndSpawnCharacter(MapInfoData map)
{
yield return LoadingManager.Singleton.LoadSceneWithLoadingScreen(map.scene);
// Spawn the player's character after the scene has been loaded
SpawnCharacterAtStartPosition(map);
}
/// <summary>
/// Spawns the player's character at the starting position in the loaded scene.
/// The character prefab is instantiated at a default position (Vector3.zero),
/// and the character's data is set based on the game instance.
/// Additionally, it spawns the correct UIGameplay Canvas based on the specified platform type.
/// </summary>
/// <param name="map">The map data used for setting the minimap image if available.</param>
private void SpawnCharacterAtStartPosition(MapInfoData map)
{
Vector3 startPosition = Vector3.zero;
// Check if the character prefab is set in the GameInstance
if (GameInstance.Singleton.characterEntity != null)
{
// Instantiate the character at the start position
CharacterEntity character = Instantiate(GameInstance.Singleton.characterEntity, startPosition, Quaternion.identity);
// Set the character's data using the GameInstance
character.SetCharacterData(GameInstance.Singleton.GetCharacterData());
// Get the correct UIGameplay instance based on the specified platform type
UIGameplay uiGameplayPrefab = GameInstance.Singleton.GetUIGameplayForPlatform();
if (uiGameplayPrefab != null)
{
// Instantiate UIGameplay and store the instance
var uiGameplayInstance = Instantiate(uiGameplayPrefab, Vector3.zero, Quaternion.identity);
// Ensure the instance has the necessary components
if (uiGameplayInstance.minimapImage != null)
{
if (map.mapMinimapImage != null)
{
// Assign the map's sprite to the minimap
uiGameplayInstance.minimapImage.sprite = map.mapMinimapImage;
}
else
{
// Assign the unlockedSprite if map's sprite is null
uiGameplayInstance.minimapImage.sprite = uiGameplayInstance.unlockedSprite;
}
}
else
{
Debug.LogWarning("MinimapImage is not set in the UIGameplay prefab.");
}
}
}
else
{
// Log an error if the character prefab is not set
Debug.LogError("CharacterPrefab is not set in the GameInstance.Singleton.");
}
}
/// <summary>
/// Determines if the map is unlocked based on PlayerPrefs or default settings in the ScriptableObject.
/// </summary>
/// <param name="mapId">The ID of the map.</param>
/// <returns>True if the map is unlocked, false otherwise.</returns>
private bool IsMapUnlocked(int mapId)
{
// Check if the map is marked as unlocked in the editor
foreach (var map in GameInstance.Singleton.mapInfoData)
{
if (map.mapId == mapId && map.isUnlocked)
{
return true;
}
}
// If not, check if it is unlocked in the player's saved data
return PlayerSave.GetUnlockedMaps().Contains(mapId);
}
/// <summary>
/// Checks if the specified map ID is the latest unlocked map.
/// This ensures that only the latest map can unlock the next one.
/// </summary>
/// <param name="mapId">The ID of the map to check.</param>
/// <returns>True if the map is the latest unlocked or marked as unlocked in the editor, false otherwise.</returns>
private bool IsLatestUnlockedMap(int mapId)
{
List<int> unlockedMaps = PlayerSave.GetUnlockedMaps();
MapInfoData[] allMaps = GameInstance.Singleton.mapInfoData;
// Check if no maps are unlocked
if (unlockedMaps.Count == 0)
{
// If no maps are unlocked, consider the current map as the latest if it has isUnlocked marked in the editor
foreach (var map in allMaps)
{
if (map.mapId == mapId && map.isUnlocked)
{
return true;
}
}
}
// Check if the current map is the latest unlocked
return unlockedMaps.Contains(mapId) && unlockedMaps[unlockedMaps.Count - 1] == mapId;
}
/// <summary>
/// Unlocks the next map in the list of map data if the player has completed the latest unlocked map.
/// The next map is unlocked and saved to the player's unlocked maps list.
/// </summary>
private void UnlockNextMap()
{
List<int> unlockedMaps = PlayerSave.GetUnlockedMaps();
MapInfoData[] allMaps = GameInstance.Singleton.mapInfoData;
int lastUnlockedMapId = 0;
if (unlockedMaps.Count > 0)
{
lastUnlockedMapId = unlockedMaps[unlockedMaps.Count - 1];
}
else
{
for (int i = 0; i < allMaps.Length; i++)
{
if (allMaps[i].isUnlocked)
{
lastUnlockedMapId = allMaps[i].mapId;
break;
}
}
}
// Find the next map to unlock
for (int i = 0; i < allMaps.Length; i++)
{
if (allMaps[i].mapId == lastUnlockedMapId && i + 1 < allMaps.Length)
{
// Unlock the next map by adding its ID to the list
int nextMapId = allMaps[i + 1].mapId;
unlockedMaps.Add(nextMapId);
// Save the updated list of unlocked maps
PlayerSave.SetUnlockedMaps(unlockedMaps);
break;
}
}
}
/// <summary>
/// Updates the progress of all relevant quests based on the game's outcome.
/// </summary>
/// <param name="won">Whether the player won the game.</param>
private void UpdateQuestProgress(bool won)
{
QuestItem[] quests = GameInstance.Singleton.questData; // Assume questData is an array
CharacterEntity character = GameplayManager.Singleton.GetCharacterEntity();
int monstersKilled = GameplayManager.Singleton.GetMonstersKilled();
foreach (QuestItem quest in quests)
{
// Skip if the quest is already completed
if (PlayerSave.IsQuestCompleted(quest.questId))
continue;
QuestRequirement requirement = quest.requirement;
int currentProgress = PlayerSave.LoadQuestProgress(quest.questId);
switch (requirement.requirementType)
{
case QuestRequirementType.KillMonster:
// Accumulate the total monsters killed
currentProgress += monstersKilled;
PlayerSave.SaveQuestProgress(quest.questId, currentProgress);
break;
case QuestRequirementType.KillMonsterWithSpecificCharacter:
if (character != null && character.GetCharacterData() == requirement.targetCharacter)
{
currentProgress += monstersKilled;
PlayerSave.SaveQuestProgress(quest.questId, currentProgress);
}
break;
case QuestRequirementType.CompleteMap:
// Increment progress if the player won on the required map
if (won && currentMapId == requirement.targetMap.mapId)
{
currentProgress++;
PlayerSave.SaveQuestProgress(quest.questId, currentProgress);
}
break;
case QuestRequirementType.CompleteMapWithSpecificCharacter:
// Increment progress if the player won on the required map with the required character
if (won && currentMapId == requirement.targetMap.mapId
&& character != null
&& character.GetCharacterData() == requirement.targetCharacter)
{
currentProgress++;
PlayerSave.SaveQuestProgress(quest.questId, currentProgress);
}
break;
case QuestRequirementType.LevelUpAccount:
break;
case QuestRequirementType.LevelUpCharacter:
break;
}
}
}
private CharacterData GetCharacterDataById(int characterId)
{
if (GameInstance.Singleton == null || GameInstance.Singleton.characterData == null)
return null;
foreach (CharacterData item in GameInstance.Singleton.characterData)
if (item.characterId == characterId)
return item;
return null;
}
/// <summary>
/// Initiates the process of returning to the main menu by loading the main menu scene.
/// </summary>
public void ReturnToMainMenu()
{
StartCoroutine(LoadMainMenuAndLoadPlayerInfo());
}
/// <summary>
/// Initiates the process of loading a specific scene.
/// </summary>
/// <param name="scene">The name of the scene to load.</param>
public void LoadSpecificScene(string scene)
{
StartCoroutine(LoadAsyncSpecificScene(scene));
}
/// <summary>
/// Coroutine that loads the main menu scene asynchronously and,
/// once the scene is fully loaded, it calls the method to load the player's information.
/// Uses the LoadingManager to display the loading screen during the process.
/// </summary>
/// <returns>An IEnumerator used for coroutine execution.</returns>
private IEnumerator LoadMainMenuAndLoadPlayerInfo()
{
yield return LoadingManager.Singleton.LoadSceneWithLoadingScreen(mainMenuScene);
// After the scene is loaded, call the method to load player information
UIMainMenu.Singleton.LoadPlayerInfo();
}
/// <summary>
/// Coroutine that loads a specific scene asynchronously.
/// Uses the LoadingManager to display the loading screen during the process.
/// </summary>
/// <param name="scene">The name of the scene to load.</param>
/// <returns>An IEnumerator used for coroutine execution.</returns>
private IEnumerator LoadAsyncSpecificScene(string scene)
{
yield return LoadingManager.Singleton.LoadSceneWithLoadingScreen(scene);
}
#endregion
}
}