using System;
using RR.Game2D.Core.Managers;
using UnityEngine;
namespace RR.Game2D.Behaviour
{
///
/// Система очков:
/// хранит количество собранных монет и убитых врагов,
/// считает итоговый счет при завершении игры.
/// Формула: Score = E * (P + 45C + 90K + 10K^2) - 0.5T
///
[DefaultExecutionOrder(-900)]
public class ScoreSystem : MonoBehaviour
{
public static ScoreSystem Instance { get; private set; }
[Header("Коэффициенты исхода")]
[SerializeField] private float winMultiplier = 1f;
[SerializeField] private float loseMultiplier = 0.6f;
public int CollectedCoins { get; private set; }
public int TotalCollectedCoins => CollectedCoins;
public int KilledEnemies { get; private set; }
public int EnvironmentalPoints { get; private set; }
public int CurrentScore { get; private set; }
public int FinalScore { get; private set; }
public bool IsFinalized { get; private set; }
public event Action StatsChanged;
public event Action EnvironmentalPointsChanged;
public event Action CurrentScoreChanged;
public event Action ScoreCalculated;
private bool _subscribed;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
{
if (Instance != null) return;
GameObject go = new(nameof(ScoreSystem));
go.AddComponent();
DontDestroyOnLoad(go);
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
ResetStats();
}
private void Start()
{
TrySubscribe();
}
private void OnEnable()
{
TrySubscribe();
}
private void OnDisable()
{
Unsubscribe();
}
public void RegisterCoinCollected(int amount = 1)
{
if (amount <= 0 || IsFinalized) return;
// This is the cumulative coin count for score calculation.
// It is intentionally separate from GameManager.Coins, which is used by gameplay/UI and life-award thresholds.
CollectedCoins += amount;
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
RefreshCurrentScore();
}
public void RegisterEnemyKilled(int amount = 1)
{
if (amount <= 0 || IsFinalized) return;
KilledEnemies += amount;
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
RefreshCurrentScore();
}
public void RegisterEnvironmentalPoints(int amount = 1)
{
if (amount <= 0 || IsFinalized) return;
EnvironmentalPoints += amount;
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
RefreshCurrentScore();
}
public int CalculatePreviewScore(float gameTime, bool isWin)
{
float e = isWin ? winMultiplier : loseMultiplier;
float baseScore = EnvironmentalPoints + 45f * CollectedCoins + 90f * KilledEnemies + 10f * KilledEnemies * KilledEnemies;
float rawScore = e * baseScore - 0.5f * gameTime;
return Mathf.Max(0, Mathf.RoundToInt(rawScore));
}
public int GetDisplayScore(float gameTime, bool isWin = true)
{
if (IsFinalized)
{
return FinalScore;
}
return CalculatePreviewScore(gameTime, isWin);
}
public void ResetStats()
{
CollectedCoins = 0;
KilledEnemies = 0;
EnvironmentalPoints = 0;
CurrentScore = 0;
FinalScore = 0;
IsFinalized = false;
StatsChanged?.Invoke(CollectedCoins, KilledEnemies);
EnvironmentalPointsChanged?.Invoke(EnvironmentalPoints);
CurrentScoreChanged?.Invoke(CurrentScore);
}
private void TrySubscribe()
{
if (_subscribed || GameManager.Instance == null) return;
GameManager.Instance.GameWonEvent += OnGameWon;
GameManager.Instance.GameOverEvent += OnGameOver;
_subscribed = true;
}
private void Unsubscribe()
{
if (!_subscribed || GameManager.Instance == null) return;
GameManager.Instance.GameWonEvent -= OnGameWon;
GameManager.Instance.GameOverEvent -= OnGameOver;
_subscribed = false;
}
private void OnGameWon(float gameTime)
{
Calculate(gameTime, true);
}
private void OnGameOver(float gameTime)
{
Calculate(gameTime, false);
}
private void Calculate(float gameTime, bool isWin)
{
if (IsFinalized) return;
FinalScore = CalculatePreviewScore(gameTime, isWin);
CurrentScore = FinalScore;
IsFinalized = true;
CurrentScoreChanged?.Invoke(CurrentScore);
ScoreCalculated?.Invoke(FinalScore, gameTime, isWin);
}
private void RefreshCurrentScore()
{
if (IsFinalized)
{
return;
}
float gameTime = GameManager.Instance != null ? GameManager.Instance.GetCurrentGameTime() : 0f;
CurrentScore = CalculatePreviewScore(gameTime, true);
CurrentScoreChanged?.Invoke(CurrentScore);
}
}
}