bug id : 9929d2a0-5bb3-47f9-8289-e90aa0032536
nt;
public override void OnAppStarted()
{
// Subscribe to animation events
AnimationController.Instance.AnimationEvents.OnOutcomeAnimationFinished.AddListener(OutcomeAnimationFinished);
AnimationController.Instance.AnimationEvents.OnProgressBarFinished.AddListener(ProgressBarFinished);
// Initialize the health animations
healthAnimationsStart = new HealthAnimations();
healthAnimationsOngoing = new HealthAnimations();
healthAnimationsOutcome = new HealthAnimations();
}
public override void OnStateStarted()
{
EncounterController.Instance.StartEncounter();
// Reset health animations
healthAnimationsStart.Clear();
healthAnimationsOngoing.Clear();
healthAnimationsOutcome.Clear();
// Get references
uiEventCounter = FindObjectOfType();
uiOutcome = FindObjectOfType();
// Save the current player health to check at the end if the player died or not
startingPlayerHealth = EncounterController.Instance.Player.CurrentHealth;
// Check if this is the first time we’re starting the encounter
bool isFirstStart = !EncounterSingleton.Instance.IsEventCounterActive;
if (isFirstStart)
{
// This is the first time we’re running this encounter
Debug.Log(“Starting encounter for the first time”);
// Mark the event counter as active so subsequent calls know we’re already running
EncounterSingleton.Instance.IsEventCounterActive = true;
// Play the start animation
AnimationController.Instance.PlayStartAnimation();
// Subscribe to UI event counter events
uiEventCounter.OnReadyButtonPressed.AddListener(ReadyButtonPressed);
// Start health changes for the encounter start effects
ProcessEncounterStartEffects();
}
else
{
// We’re resuming an encounter that was already in progress
Debug.Log(“Resuming encounter that was already in progress”);
// Immediately advance to the next appropriate state based on where we left off
if (EncounterSingleton.Instance.CurrentTurn < EncounterSingleton.Instance.TotalTurns)
{
// We’re still in the middle of the encounter
AdvanceToReadyState();
}
else
{
// The encounter is already over, go straight to outcome
AdvanceToOutcomeState();
}
}
}
private void ProcessEncounterStartEffects()
{
List startEffects = EncounterController.Instance.GetEncounterStartEffects();
if (startEffects.Count > 0)
{
Debug.Log($“Processing {startEffects.Count} encounter start effects”);
// Get all health change effects from the start effects
foreach (EffectData effect in startEffects)
{
if (effect.EffectType == EffectType.PlayerHealth || effect.EffectType == EffectType.EnemyHealth)
{
// Add to start health animations
healthAnimationsStart.AddEffect(effect);
}
}
// Apply all encounter start effects (this actually changes the health values)
EncounterController.Instance.ApplyEffects(startEffects);
// Queue the health animations for the start effects
if (healthAnimationsStart.HasAnimations)
{
AnimationController.Instance.QueueHealthAnimations(healthAnimationsStart);
}
// If we have start animations, they’ll play and we’ll move to the ready state
// after they finish. If not, we move to ready state immediately.
if (!AnimationController.Instance.HasQueuedAnimations)
{
AdvanceToReadyState();
}
}
else
{
// No start effects, move directly to the ready state
AdvanceToReadyState();
}
}
private void ReadyButtonPressed()
{
Debug.Log(“Ready button pressed”);
// Increment the turn counter
EncounterSingleton.Instance.CurrentTurn++;
// Unsubscribe from the ready button event to prevent multiple calls
uiEventCounter.OnReadyButtonPressed.RemoveListener(ReadyButtonPressed);
// Process the turn
ProcessTurn();
}
private void ProcessTurn()
{
int currentTurn = EncounterSingleton.Instance.CurrentTurn;
Debug.Log($“Processing turn {currentTurn}”);
// Reset ongoing health animations for this turn
healthAnimationsOngoing.Clear();
// Get the ongoing effects for this turn and apply them
List ongoingEffects = EncounterController.Instance.GetOngoingEffects();
if (ongoingEffects.Count > 0)
{
Debug.Log($“Processing {ongoingEffects.Count} ongoing effects for turn {currentTurn}”);
// Collect health animations from ongoing effects
foreach (EffectData effect in ongoingEffects)
{
if (effect.EffectType == EffectType.PlayerHealth || effect.EffectType == EffectType.EnemyHealth)
{
// Add to ongoing health animations
healthAnimationsOngoing.AddEffect(effect);
}
}
// Apply all ongoing effects (this actually changes the health values)
EncounterController.Instance.ApplyEffects(ongoingEffects);
}
// Start the progress bar animation
AnimationController.Instance.PlayProgressBarAnimation();
// Queue health animations if we have any
if (healthAnimationsOngoing.HasAnimations)
{
AnimationController.Instance.QueueHealthAnimations(healthAnimationsOngoing);
}
}
private void ProgressBarFinished()
{
Debug.Log(“Progress bar animation finished”);
// Check if we’ve reached the max number of turns
if (EncounterSingleton.Instance.CurrentTurn >= EncounterSingleton.Instance.TotalTurns)
{
AdvanceToOutcomeState();
}
else
{
AdvanceToReadyState();
}
}
private void AdvanceToReadyState()
{
Debug.Log(“Advancing to Ready state”);
// Re-subscribe to the ready button event for the next turn
uiEventCounter.OnReadyButtonPressed.AddListener(ReadyButtonPressed);
// Reset the UI for the next turn
uiEventCounter.SetTurnText(EncounterSingleton.Instance.CurrentTurn + 1, EncounterSingleton.Instance.TotalTurns);
uiEventCounter.ShowReadyButton();
}
private void AdvanceToOutcomeState()
{
Debug.Log(“Advancing to Outcome state”);
// Clear any remaining health animations
healthAnimationsOutcome.Clear();
// Get and apply outcome effects
List outcomeEffects = EncounterController.Instance.GetEncounterOutcomeEffects();
if (outcomeEffects.Count > 0)
{
Debug.Log($“Processing {outcomeEffects.Count} outcome effects”);
// Get all health change effects from the outcome effects
foreach (EffectData effect in outcomeEffects)
{
if (effect.EffectType == EffectType.PlayerHealth || effect.EffectType == EffectType.EnemyHealth)
{
// Add to outcome health animations
healthAnimationsOutcome.AddEffect(effect);
}
}
// Apply all outcome effects (this actually changes the health values)
EncounterController.Instance.ApplyEffects(outcomeEffects);
}
// Determine the outcome
bool playerDied = false;
bool enemyDied = EncounterController.Instance.Enemy.CurrentHealth <= 0;
// Only consider the player to have died if they had health at the start but now they’re at 0
if (startingPlayerHealth > 0 && EncounterController.Instance.Player.CurrentHealth <= 0)
{
playerDied = true;
}
// Set outcome UI
if (playerDied)
{
uiOutcome.SetOutcomeText(“You were defeated!”);
uiOutcome.SetButtonText(“Continue”);
}
else if (enemyDied)
{
uiOutcome.SetOutcomeText(“Victory!”);
uiOutcome.SetButtonText(“Continue”);
}
else
{
uiOutcome.SetOutcomeText(“You survived the encounter!”);
uiOutcome.SetButtonText(“Continue”);
}
// Play outcome animations
if (healthAnimationsOutcome.HasAnimations)
{
AnimationController.Instance.QueueHealthAnimations(healthAnimationsOutcome);
}
// Play the outcome animation which will trigger the outcome UI
AnimationController.Instance.PlayOutcomeAnimation();
}
private void OutcomeAnimationFinished()
{
Debug.Log(“Outcome animation finished”);
// Subscribe to the outcome button event
uiOutcome.OnOutcomeButtonPressed.AddListener(OutcomeButtonPressed);
// Show the outcome UI
uiOutcome.ShowOutcomePanel();
}
private void OutcomeButtonPressed()
{
Debug.Log(“Outcome button pressed”);
// Unsubscribe from UI events
uiOutcome.OnOutcomeButtonPressed.RemoveListener(OutcomeButtonPressed);
// Reset the encounter singleton flag since the encounter is completely finished
EncounterSingleton.Instance.IsEventCounterActive = false;
EncounterSingleton.Instance.CurrentTurn = 0;
// End the encounter and go back to map screen
EncounterController.Instance.EndEncounter();
StateController.Instance.TransitionToState(GameState.Map);
}
public override void OnStateEnding()
{
// Unsubscribe from animation events
AnimationController.Instance.AnimationEvents.OnOutcomeAnimationFinished.RemoveListener(OutcomeAnimationFinished);
AnimationController.Instance.AnimationEvents.OnProgressBarFinished.RemoveListener(ProgressBarFinished);
// Unsubscribe from UI events if they’re still active
if (uiEventCounter != null)
{
uiEventCounter.OnReadyButtonPressed.RemoveListener(ReadyButtonPressed);
}
if (uiOutcome != null)
{
uiOutcome.OnOutcomeButtonPressed.RemoveListener(OutcomeButtonPressed);
}
}
}
End File# Scripts/Battle/EncounterController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
///
/// Manages the logical aspects of an encounter between the player and an enemy.
///
public class EncounterController : Singleton
{
[Header(“Prefabs”)]
[SerializeField] private GameObject playerPrefab;
[SerializeField] private GameObject enemyPrefab;
// References to the player and enemy entities
public EntityController Player { get; private set; }
public EntityController Enemy { get; private set; }
// Events
public UnityEvent OnEncounterStarted = new UnityEvent();
public UnityEvent OnEncounterEnded = new UnityEvent();
private EncounterData currentEncounter;
// Store references to all entities
private List entities = new List();
#region Unity Methods
protected override void Awake()
{
base.Awake();
// Ensure we have references to all entity controllers
SetupPlayer();
SetupEnemy();
}
#endregion
#region Encounter Setup and Control
///
/// Starts an encounter with the specified parameters.
///
public void StartEncounter()
{
currentEncounter = GameManager.Instance.CurrentEncounter;
if (currentEncounter == null)
{
Debug.LogError(“Cannot start encounter: no encounter data provided”);
return;
}
Debug.Log($“Starting encounter: {currentEncounter.EncounterName}”);
// Setup the player with current game state
PlayerData playerData = GameManager.Instance.PlayerData;
if (playerData != null)
{
Player.Initialize(playerData);
}
else
{
Debug.LogWarning(“No player data available, using default values”);
Player.Initialize(new PlayerData());
}
// Setup the enemy with data from the encounter
Enemy.Initialize(currentEncounter.Enemy);
// Gather all entities that will participate in the encounter
entities.Clear();
entities.Add(Player);
entities.Add(Enemy);
// Notify listeners that the encounter has started
OnEncounterStarted.Invoke();
}
///
/// Ends the current encounter and performs any cleanup.
///
public void EndEncounter()
{
Debug.Log(“Ending encounter”);
// Save any changes to player state if necessary
SavePlayerState();
// Notify listeners that the encounter has ended
OnEncounterEnded.Invoke();
}
///
/// Creates or retrieves the player entity controller.
///
private void SetupPlayer()
{
if (Player == null)
{
GameObject playerObj = Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
Player = playerObj.GetComponent();
if (Player == null)
{
Debug.LogError(“PlayerController component missing from player prefab”);
}
}
}
///
/// Creates or retrieves the enemy entity controller.
///
private void SetupEnemy()
{
if (Enemy == null)
{
GameObject enemyObj = Instantiate(enemyPrefab, Vector3.zero, Quaternion.identity);
Enemy = enemyObj.GetComponent();
if (Enemy == null)
{
Debug.LogError(“EntityController component missing from enemy prefab”);
}
}
}
///
/// Saves the current player state at the end of the encounter.
///
private void SavePlayerState()
{
if (Player != null && GameManager.Instance != null)
{
// Save any changes to the player that should persist (like health)
PlayerData updatedPlayerData = new PlayerData(
Player.EntityName,
Player.MaxHealth,
Player.CurrentHealth
);
GameManager.Instance.UpdatePlayerData(updatedPlayerData);
}
}
#endregion
#region Effect Management
///
/// Returns effects that should be applied at the start of the encounter.
///
public List GetEncounterStartEffects()
{
List effects = new List();
if (currentEncounter != null && _currentEncounter.StartEffects != null)
{
effects.AddRange(currentEncounter.StartEffects);
}
return effects;
}
///
/// Returns effects that should be applied during each turn of the encounter.
///
public List GetOngoingEffects()
{
List effects = new List();
if (currentEncounter != null && _currentEncounter.OngoingEffects != null)
{
effects.AddRange(currentEncounter.OngoingEffects);
}
return effects;
}
///
/// Returns effects that should be applied at the end of the encounter.
///
public List GetEncounterOutcomeEffects()
{
List effects = new List();
if (currentEncounter != null && _currentEncounter.OutcomeEffects != null)
{
effects.AddRange(currentEncounter.OutcomeEffects);
}
return effects;
}
///
/// Applies a list of effects to the appropriate entities.
///
public void ApplyEffects(List effects)
{
foreach (EffectData effect in effects)
{
ApplyEffect(effect);
}
}
///
/// Applies a single effect to the appropriate entity.
///
private void ApplyEffect(EffectData effect)
{
switch (effect.EffectType)
{
case EffectType.PlayerHealth:
ApplyHealthEffect(Player, effect);
break;
case EffectType.EnemyHealth:
ApplyHealthEffect(Enemy, effect);
break;
default:
Debug.LogWarning($“Unhandled effect type: {effect.EffectType}”);
break;
}
}
///
/// Applies a health effect to the specified entity.
///
private void ApplyHealthEffect(EntityController entity, EffectData effect)
{
if (entity == null)
{
Debug.LogError(“Cannot apply health effect: entity is null”);
return;
}
int amount = effect.Value;
// For damage, we convert the positive value to negative
if (effect.IsHealthDecrease())
{
amount = -amount;
}
entity.ChangeHealth(amount);
}
#endregion
}
End File# Valentin-Butorin/MiniMap
Scripts/Data/EncounterData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// Data structure for an encounter in the game.
///
[CreateAssetMenu(fileName = “New Encounter”, menuName = “Game Data/Encounter”)]
public class EncounterData : ScriptableObject
{
[Header(“Basic Info”)]
[Tooltip(“The name of this encounter”)]
public string EncounterName;
[Tooltip(“A short description of this encounter”)]
[TextArea(2, 5)]
public string Description;
[Header(“Enemy Info”)]
[Tooltip(“The enemy in this encounter”)]
public EnemyData Enemy;
[Header(“Encounter Settings”)]
[Tooltip(“The total number of turns in this encounter”)]
[Range(1, 10)]
public int TotalTurns = 3;
[Header(“Encounter Effects”)]
[Tooltip(“Effects that are applied at the start of the encounter”)]
public List StartEffects = new List();
[Tooltip(“Effects that are applied during each turn of the encounter”)]
public List OngoingEffects = new List();
[Tooltip(“Effects that are applied at the end of the encounter”)]
public List OutcomeEffects = new List();
[Header(“Rewards”)]
[Tooltip(“Health that is restored to the player after the encounter”)]
[Range(0, 100)]
public int HealthRestored = 0;
[Header(“Visuals”)]
[Tooltip(“The image used for this encounter in the UI”)]
public Sprite EncounterSprite;
}
///
/// Types of effects that can be applied during an encounter.
///
public enum EffectType
{
PlayerHealth,
EnemyHealth,
// Add other effect types as needed
}
///
/// Data for an effect that can be applied during an encounter.
///
[System.Serializable]
public class EffectData
{
[Tooltip(“The type of effect to apply”)]
public EffectType EffectType;
[Tooltip(“The value of the effect (e.g. amount of health added or removed)”)]
public int Value;
[Tooltip(“Optional description of what this effect represents in-game”)]
public string Description;
///
/// Helper method to determine if this effect decreases health.
///
public bool IsHealthDecrease()
{
return (EffectType == EffectType.PlayerHealth || EffectType == EffectType.EnemyHealth) && Value > 0;
}
///
/// Helper method to determine if this effect increases health.
///
public bool IsHealthIncrease()
{
return (EffectType == EffectType.PlayerHealth || EffectType == EffectType.EnemyHealth) && Value < 0;
}
}
End File# Scripts/Data/MapNodeData.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// Data structure for a node on the map.
///
[CreateAssetMenu(fileName = “New Map Node”, menuName = “Game Data/Map Node”)]
public class MapNodeData : ScriptableObject
{
[Header(“Node Information”)]
[Tooltip(“The unique identifier for this node”)]
public string NodeID;
[Tooltip(“The type of node”)]
public NodeType NodeType;
[Tooltip(“Human-readable name for this node”)]
public string NodeName;
[Tooltip(“Description of what happens at this node”)]
[TextArea(2, 5)]
public string Description;
[Header(“Node Connections”)]
[Tooltip(“IDs of nodes this node connects to”)]
public List ConnectedNodeIDs = new List();
[Header(“Node Coordinates”)]
[Tooltip(“X position on the map”)]
public float XPosition;
[Tooltip(“Y position on the map”)]
public float YPosition;
[Header(“Node Appearance”)]
[Tooltip(“Icon for the node on the map”)]
public Sprite NodeIcon;
[Header(“Encounter Data”)]
[Tooltip(“Data for an encounter that happens at this node (if applicable)”)]
public EncounterData Encounter;
[Header(“Special Node Properties”)]
[Tooltip(“Health restored at a rest node”)]
[Range(0, 100)]
public int RestNodeHealAmount = 20;
[Tooltip(“Whether this node has been visited”)]
public bool IsVisited = false;
///
/// Returns a unique key for this node based on its position.
///
public string GetPositionKey()
{
return $“{XPosition}{YPosition}”;
}
}
///
/// Types of nodes that can exist on the map.
///
public enum NodeType
{
Start, // Starting point
Enemy, // Combat encounter
Elite, // Harder combat encounter
Rest, // Rest area to heal
Event, // Random event
Boss, // Final boss encounter
Mystery // Unknown node type until visited
}
///
/// Extension methods for the NodeType enum.
///
public static class NodeTypeExtensions
{
///
/// Gets a color associated with the node type for UI representation.
///
public static Color GetColor(this NodeType nodeType)
{
switch (nodeType)
{
case NodeType.Start:
return Color.green;
case NodeType.Enemy:
return Color.red;
case NodeType.Elite:
return new Color(0.8f, 0.2f, 0.2f); // Darker red
case NodeType.Rest:
return Color.blue;
case NodeType.Event:
return Color.yellow;
case NodeType.Boss:
return new Color(0.5f, 0, 0.5f); // Purple
case NodeType.Mystery:
return Color.gray;
default:
return Color.white;
}
}
///
/// Gets a human-readable name for the node type.
///
public static string GetDisplayName(this NodeType nodeType)
{
switch (nodeType)
{
case NodeType.Start:
return “Başlangıç”;
case NodeType.Enemy:
return “Düşman”;
case NodeType.Elite:
return “Elit Düşman”;
case NodeType.Rest:
return “Dinlenme”;
case NodeType.Event:
return “Olay”;
case NodeType.Boss:
return “Şef”;
case NodeType.Mystery:
return “Gizemli”;
default:
return nodeType.ToString();
}
}
}
End File# Valentin-Butorin/MiniMap
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// Controls music and sound effects for the game.
///
public class AudioManager : Singleton
{
[Header(“Audio Sources”)]
[Tooltip(“Audio source for music”)]
[SerializeField] private AudioSource musicSource;
[Tooltip(“Audio source for sound effects”)]
[SerializeField] private AudioSource sfxSource;
[Header(“Audio Clips”)]
[Tooltip(“Main menu music”)]
[SerializeField] private AudioClip mainMenuMusic;
[Tooltip(“Map screen music”)]
[SerializeField] private AudioClip mapMusic;
[Tooltip(“Battle music”)]
[SerializeField] private AudioClip battleMusic;
[Tooltip(“Button click sound”)]
[SerializeField] private AudioClip buttonClickSound;
[Tooltip(“Node selection sound”)]
[SerializeField] private AudioClip nodeSelectSound;
[Tooltip(“Battle hit sound”)]
[SerializeField] private AudioClip hitSound;
[Tooltip(“Health gain sound”)]
[SerializeField] private AudioClip healSound;
[Tooltip(“Victory sound”)]
[SerializeField] private AudioClip victorySound;
[Tooltip(“Defeat sound”)]
[SerializeField] private AudioClip defeatSound;
[Header(“Volume Settings”)]
[Tooltip(“Master volume multiplier”)]
[Range(0, 1)]
[SerializeField] private float masterVolume = 1f;
[Tooltip(“Music volume multiplier”)]
[Range(0, 1)]
[SerializeField] private float musicVolume = 0.5f;
[Tooltip(“SFX volume multiplier”)]
[Range(0, 1)]
[SerializeField] private float sfxVolume = 0.7f;
[Tooltip(“How long the music crossfade takes”)]
[SerializeField] private float musicCrossfadeDuration = 1.5f;
// Keep track of what music is currently playing
private AudioClip currentMusic;
private Coroutine musicFadeCoroutine;
protected override void Awake()
{
base.Awake();
// Make sure audio sources are set up
if (musicSource == null)
{
GameObject musicObj = new GameObject(“Music Source”);
musicObj.transform.parent = transform;
musicSource = musicObj.AddComponent();
musicSource.loop = true;
}
if (sfxSource == null)
{
GameObject sfxObj = new GameObject(“SFX Source”);
sfxObj.transform.parent = transform;
sfxSource = sfxObj.AddComponent();
}
// Initialize volume
UpdateVolume();
}
void Start()
{
// Subscribe to state change events
StateController.Instance.OnStateChanged.AddListener(HandleStateChanged);
}
void OnDestroy()
{
// Unsubscribe from state change events
if (StateController.Instance != null)
{
StateController.Instance.OnStateChanged.RemoveListener(HandleStateChanged);
}
}
///
/// Handles state changes to play appropriate music.
///
private void HandleStateChanged(GameState newState)
{
switch (newState)
{
case GameState.MainMenu:
PlayMusic(mainMenuMusic);
break;
case GameState.Map:
PlayMusic(mapMusic);
break;
case GameState.Battle:
PlayMusic(battleMusic);
break;
}
}
///
/// Play a sound effect.
///
public void PlaySFX(AudioClip clip)
{
if (clip != null)
{
sfxSource.PlayOneShot(clip, _sfxVolume masterVolume);
}
}
///
/// Play the button click sound.
///
public void PlayButtonClick()
{
PlaySFX(buttonClickSound);
}
///
/// Play the node selection sound.
///
public void PlayNodeSelect()
{
PlaySFX(nodeSelectSound);
}
///
/// Play the hit sound.
///
public void PlayHitSound()
{
PlaySFX(hitSound);
}
///
/// Play the heal sound.
///
public void PlayHealSound()
{
PlaySFX(healSound);
}
///
/// Play the victory sound.
///
public void PlayVictorySound()
{
PlaySFX(victorySound);
}
///
/// Play the defeat sound.
///
public void PlayDefeatSound()
{
PlaySFX(defeatSound);
}
///
/// Play music with a smooth crossfade transition.
///
public void PlayMusic(AudioClip music)
{
// Don’t switch if it’s already the current music
if (currentMusic == music)
return;
currentMusic = music;
// If a fade is in progress, stop it
if (musicFadeCoroutine != null)
{
StopCoroutine(musicFadeCoroutine);
}
// If the music source is already playing, fade it out and then in with new music
if (musicSource.isPlaying)
{
musicFadeCoroutine = StartCoroutine(CrossfadeMusic(music));
}
else
{
// If no music is playing, just start the new music
musicSource.clip = music;
musicSource.volume = _musicVolume masterVolume;
musicSource.Play();
}
}
///
/// Coroutine for
Edited 16 files+1975-1808