using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 8 — MythoClash's loop: a monster appears, you choose FIGHT or SKIP,
/// and skipping makes the next one likelier to be nasty. Fozaan's core design.
///
/// THE RULES FROM THE PITCH
/// - Beat a monster, earn Drachma (ExperienceWallet).
/// - You may skip, but each skip raises the chance of drawing a higher-tier monster.
/// - Skips are limited (Fozaan suggested 3) so you can't skip your way past the game.
/// - Monsters come from a roster; each world is one pantheon.
///
/// SETUP
/// 1. Empty GameObject "EncounterManager" + this script.
/// 2. Roster: drag in MonsterEntry assets. Filter By Pantheon restricts this world to
/// one mythology (leave blank for all).
/// 3. Spawn Point: where the monster appears. The prefab needs Health + a Hurtbox
/// (Tier 2) so the player can actually fight it; wire its Health.OnDied to
/// EncounterManager.OnMonsterDefeated via the spawned instance (this script does it
/// automatically if the prefab has a Health component).
/// 4. Fight / Skip buttons: wire them to ChooseFight() and ChooseSkip().
/// 5. Difficulty: Base Tier is what you'd normally draw; each unused skip adds
/// Tier Per Skip to the roll. Max Skips is the hard limit.
/// 6. Encounters Per World: how many fights before OnWorldCleared (Fozaan said ~12).
///
/// The interesting design question to put to Fozaan: skipping should feel like a real
/// temptation, which means the tier bump has to be visible. Show the player the risk
/// ("Threat rising") rather than hiding it — a gamble you can't see isn't a decision.
/// </summary>
public class EncounterManager : MonoBehaviour
{
[Header("Roster")]
public List<MonsterEntry> roster = new List<MonsterEntry>();
[Tooltip("Only draw monsters from this pantheon. Blank = any.")]
public string filterByPantheon = "";
public bool avoidImmediateRepeats = true;
[Header("Where")]
public Transform spawnPoint;
public Transform playerStart;
[Header("Difficulty")]
[Range(1, 5)] public int baseTier = 1;
[Tooltip("Each skip taken raises the drawn tier by this much.")]
public float tierPerSkip = 0.8f;
public int maxSkips = 3;
[Tooltip("Tier also creeps up as you clear encounters.")]
public float tierPerEncounter = 0.15f;
[Header("World")]
public int encountersPerWorld = 12;
public bool autoStart = true;
public float delayBetweenEncounters = 1.5f;
[Header("Events")]
public UnityEvent<MonsterEntry> OnMonsterPresented;
public UnityEvent<MonsterEntry> OnFightStarted;
public UnityEvent<MonsterEntry> OnMonsterDefeated;
public UnityEvent<int> OnSkipsRemainingChanged;
public UnityEvent<int> OnThreatLevelChanged; // the tier you'd draw next
public UnityEvent<int, int> OnProgressChanged; // (cleared, total)
public UnityEvent OnWorldCleared;
public UnityEvent OnOutOfSkips;
public MonsterEntry CurrentMonster { get; private set; }
public int SkipsUsed { get; private set; }
public int Cleared { get; private set; }
public int SkipsRemaining => Mathf.Max(0, maxSkips - SkipsUsed);
public int ThreatTier => Mathf.Clamp(
Mathf.RoundToInt(baseTier + SkipsUsed * tierPerSkip + Cleared * tierPerEncounter), 1, 5);
private GameObject currentInstance;
private MonsterEntry lastPresented;
private bool awaitingChoice;
void Start()
{
OnSkipsRemainingChanged?.Invoke(SkipsRemaining);
OnProgressChanged?.Invoke(Cleared, encountersPerWorld);
OnThreatLevelChanged?.Invoke(ThreatTier);
if (autoStart) PresentNext();
}
/// <summary>Draw and show the next monster (without spawning it yet).</summary>
public void PresentNext()
{
if (Cleared >= encountersPerWorld) { OnWorldCleared?.Invoke(); return; }
CurrentMonster = Draw();
if (CurrentMonster == null)
{
Debug.LogWarning("EncounterManager: the roster is empty (or the pantheon filter matches nothing).");
return;
}
lastPresented = CurrentMonster;
awaitingChoice = true;
OnMonsterPresented?.Invoke(CurrentMonster);
OnThreatLevelChanged?.Invoke(ThreatTier);
}
MonsterEntry Draw()
{
var pool = new List<MonsterEntry>();
foreach (var m in roster)
{
if (m == null) continue;
if (!string.IsNullOrEmpty(filterByPantheon) && m.pantheon != filterByPantheon) continue;
if (avoidImmediateRepeats && m == lastPresented && roster.Count > 1) continue;
pool.Add(m);
}
if (pool.Count == 0) return null;
int target = ThreatTier;
// Weight each monster by how close its tier is to the target — so a high threat
// makes tough monsters likely without making weak ones impossible.
float total = 0f;
var weights = new List<float>();
foreach (var m in pool)
{
float w = 1f / (1f + Mathf.Abs(m.difficultyTier - target));
weights.Add(w);
total += w;
}
float r = Random.value * total;
for (int i = 0; i < pool.Count; i++)
{
r -= weights[i];
if (r <= 0f) return pool[i];
}
return pool[pool.Count - 1];
}
/// <summary>Wire the FIGHT button here.</summary>
public void ChooseFight()
{
if (!awaitingChoice || CurrentMonster == null) return;
awaitingChoice = false;
if (CurrentMonster.monsterPrefab != null)
{
Vector3 where = spawnPoint != null ? spawnPoint.position : transform.position;
currentInstance = Instantiate(CurrentMonster.monsterPrefab, where, Quaternion.identity);
var hp = currentInstance.GetComponent<Health>();
if (hp != null)
{
hp.maxHealth = CurrentMonster.maxHealth;
hp.ResetHealth();
hp.OnDied.AddListener(HandleMonsterDied);
}
else Debug.LogWarning($"Monster prefab '{CurrentMonster.monsterName}' has no Health component — it can't be defeated.");
}
if (CurrentMonster.roarSound != null) AudioManager.Instance?.PlaySFX(CurrentMonster.roarSound);
OnFightStarted?.Invoke(CurrentMonster);
}
/// <summary>Wire the SKIP button here.</summary>
public void ChooseSkip()
{
if (!awaitingChoice) return;
if (SkipsRemaining <= 0)
{
OnOutOfSkips?.Invoke();
return;
}
SkipsUsed++;
awaitingChoice = false;
OnSkipsRemainingChanged?.Invoke(SkipsRemaining);
ChoiceFlagManager.Instance?.AddCount("monsters_skipped", 1);
StartCoroutine(NextAfterDelay());
}
void HandleMonsterDied()
{
var defeated = CurrentMonster;
Cleared++;
ExperienceWallet.Instance?.Add(defeated.experienceReward);
ChoiceFlagManager.Instance?.AddCount("monsters_defeated", 1);
ChoiceFlagManager.Instance?.SetFlag("defeated_" + defeated.name);
OnMonsterDefeated?.Invoke(defeated);
OnProgressChanged?.Invoke(Cleared, encountersPerWorld);
StartCoroutine(NextAfterDelay());
}
IEnumerator NextAfterDelay()
{
yield return new WaitForSeconds(delayBetweenEncounters);
if (currentInstance != null) Destroy(currentInstance);
// Put the player back on their mark between fights
if (playerStart != null)
{
var p = GameObject.FindGameObjectWithTag("Player");
if (p != null)
{
p.transform.position = playerStart.position;
p.GetComponent<PlayerController2D>()?.ResetMotion();
p.GetComponent<Health>()?.ResetHealth();
}
}
PresentNext();
}
/// <summary>Start a new world with a fresh roster and skip allowance.</summary>
public void StartWorld(string pantheon)
{
filterByPantheon = pantheon;
Cleared = 0;
SkipsUsed = 0;
lastPresented = null;
OnSkipsRemainingChanged?.Invoke(SkipsRemaining);
OnProgressChanged?.Invoke(Cleared, encountersPerWorld);
PresentNext();
}
}