using System;
using DanroJump.SceneFlow;
using DanroJump.Settings;
using UnityEngine;
namespace DanroJump.Gameplay
{
///
/// Хранит состояние текущего забега: счет, монеты, жизни, высоту и рекорд.
///
public sealed class GameplaySession : IDisposable
{
private const string RecordHeightPrefsKey = "DanroJump.Gameplay.RecordHeight";
private const int DefaultStartingLives = 3;
private static readonly TimeSpan GameOverReturnDelay = TimeSpan.FromSeconds(0.35f);
private readonly JumpPlayerController player;
private readonly IReadOnlyServiceSettings serviceSettings;
private readonly ISceneFlowService sceneFlow;
private readonly int startingLives;
private float savedRecordHeight;
///
/// Очки, набранные в текущем забеге.
///
public int Score { get; private set; }
///
/// Монеты, собранные в текущем забеге.
///
public int Coins { get; private set; }
///
/// Количество смертей в текущем забеге.
///
public int DeathCount { get; private set; }
///
/// Оставшиеся жизни до завершения забега.
///
public int RemainingLives { get; private set; }
///
/// Текущее состояние gameplay-сессии.
///
public GameplaySessionState State { get; private set; }
///
/// Фактическая текущая высота игрока над стартом.
///
public float CurrentHeight => player != null ? Mathf.Max(0f, player.transform.position.y) : 0f;
///
/// Максимальная высота, достигнутая в текущем забеге.
///
public float MaxRunHeight => player != null ? Mathf.Max(0f, player.HighestY) : 0f;
///
/// Лучшая высота текущего забега.
///
public float BestHeight => MaxRunHeight;
///
/// Абсолютный рекорд с учетом сохраненного значения и текущего забега.
///
public float RecordHeight => Mathf.Max(savedRecordHeight, MaxRunHeight);
///
/// Показывает, находится ли игрок в состоянии смерти или завершенного забега.
///
public bool IsPlayerDead => State == GameplaySessionState.Respawning || State == GameplaySessionState.GameOver;
public event Action Changed;
///
/// Создает сессию и подписывается на события смерти/respawn игрока.
///
public GameplaySession(
JumpPlayerController player,
[Zenject.InjectOptional] IReadOnlyServiceSettings serviceSettings = null,
[Zenject.InjectOptional] ISceneFlowService sceneFlow = null)
{
this.player = player;
this.serviceSettings = serviceSettings;
this.sceneFlow = sceneFlow;
startingLives = ResolveStartingLives();
RemainingLives = startingLives;
State = GameplaySessionState.Running;
savedRecordHeight = PlayerPrefs.GetFloat(RecordHeightPrefsKey, 0f);
if (this.player != null)
{
this.player.Died += HandlePlayerDied;
this.player.Respawned += HandlePlayerRespawned;
}
}
///
/// Добавляет очки в текущий забег.
///
public void AddScore(int amount)
{
if (amount <= 0)
{
return;
}
Score += amount;
Changed?.Invoke();
}
///
/// Добавляет монеты в текущий забег.
///
public void AddCoins(int amount)
{
if (amount <= 0)
{
return;
}
Coins += amount;
Changed?.Invoke();
}
///
/// Полностью сбрасывает забег, сохранив рекорд при необходимости.
///
public void ResetSession()
{
SaveRecordIfNeeded();
Score = 0;
Coins = 0;
DeathCount = 0;
RemainingLives = startingLives;
State = GameplaySessionState.Running;
player?.ResetRun();
Changed?.Invoke();
}
///
/// Сохраняет рекорд и отписывается от событий игрока.
///
public void Dispose()
{
SaveRecordIfNeeded();
if (player == null)
{
return;
}
player.Died -= HandlePlayerDied;
player.Respawned -= HandlePlayerRespawned;
}
///
/// Обновляет состояние сессии после смерти игрока.
///
private void HandlePlayerDied(JumpPlayerController _)
{
DeathCount++;
RemainingLives = Mathf.Max(0, RemainingLives - 1);
State = RemainingLives > 0 ? GameplaySessionState.Respawning : GameplaySessionState.GameOver;
if (State == GameplaySessionState.GameOver)
{
player?.BlockPendingRespawn();
StartGameOverReturn();
}
else
{
player?.ScheduleRespawn(player.DeathRespawnDelay);
}
SaveRecordIfNeeded();
Changed?.Invoke();
}
///
/// Возвращает сессию в Running после respawn, если игра еще не закончена.
///
private void HandlePlayerRespawned(JumpPlayerController _)
{
if (State != GameplaySessionState.GameOver)
{
State = GameplaySessionState.Running;
}
Changed?.Invoke();
}
///
/// Читает стартовое количество жизней из сервисных настроек.
///
private int ResolveStartingLives()
{
if (serviceSettings == null)
{
return DefaultStartingLives;
}
var configuredLives = serviceSettings.GetInt(ServiceSettingsKeys.StartingLives);
return Mathf.Max(1, configuredLives);
}
///
/// Сохраняет лучший результат по высоте в PlayerPrefs.
///
private void SaveRecordIfNeeded()
{
if (MaxRunHeight <= savedRecordHeight)
{
return;
}
savedRecordHeight = MaxRunHeight;
PlayerPrefs.SetFloat(RecordHeightPrefsKey, savedRecordHeight);
PlayerPrefs.Save();
}
///
/// Запускает возврат в главное меню после финальной смерти.
///
private void StartGameOverReturn()
{
if (sceneFlow == null)
{
Debug.LogWarning($"{nameof(GameplaySession)} cannot return to main menu because {nameof(ISceneFlowService)} is not bound.");
return;
}
sceneFlow.ReturnToMainMenuAfterGameOver(GameOverReturnDelay);
}
}
///
/// Состояние игрового забега.
///
public enum GameplaySessionState
{
Waiting = 0,
Running = 1,
Respawning = 2,
GameOver = 3,
}
}