using Cysharp.Threading.Tasks; using System; using System.Threading; using UnityEngine; namespace Learn { public class LearnSystem : MonoBehaviour { [Header("Настройки подсказок")] [SerializeField] private Tip shootTip; [SerializeField] private Tip diveTip; [SerializeField] private Tip weaponTip; [SerializeField] private Tip ammoTip; [Header("Настройки обучения")] [SerializeField] private float shootDelay = 5f; // Время без стрельбы [SerializeField] private float diveDepthPercent = 0.3f; // Глубина погружения для уворота [SerializeField] private float ammoDelay = 3f; // Задержка перед подсказкой о патронах [Header("Компоненты")] [SerializeField] private TipView view; [SerializeField] private GameObject player; // Ссылка на игрока // Состояния обучения private bool isShootLearned = false; private bool isDiveLearned = false; private bool isWeaponLearned = false; private bool isAmmoLearned = false; // Отмена задач private CancellationTokenSource shootCts; private CancellationTokenSource diveCts; private CancellationTokenSource weaponCts; private CancellationTokenSource ammoCts; private CancellationTokenSource viewCts; // Таймеры для отложенных проверок private float lastShootTime; private float weaponEmptyTime; private float ammoEmptyTime; // Флаги состояний private bool isDiveLearningActive = false; private bool isWeaponLearningActive = false; private bool isAmmoLearningActive = false; private bool isWeaponSwitchRequested = false; public void Init() { view.Hide(); if (!SettingsManager.GetBool(SettingKey.Learn)) return; // Подписываемся на события (замените G.WeaponModel на ваши реальные системы) SubscribeToEvents(); // Запускаем все системы обучения StartShootLearning(); StartDiveLearning(); StartWeaponLearning(); StartAmmoLearning(); } #region Подписка на события private void Update() { if (!isDiveLearned) OnPlayerDive(G.PlayerStateModel.PlayerComponent.DiveSystem.DepthPercent); } private void SubscribeToEvents() { // ЗДЕСЬ ПОДПИШИТЕСЬ НА ВАШИ СОБЫТИЯ // Примеры: // PlayerController.OnWeaponSwitch += OnWeaponSwitch; // PlayerController.OnAmmoRequest += OnAmmoRequest; // PlayerController.OnWeaponEmpty += OnWeaponEmpty; // PlayerController.OnAmmoEmpty += OnAmmoEmpty; G.PlayerStateModel.PlayerHealthComponent.Damaged += OnProjectileHitPlayer; // PlayerController.OnProjectileHit += OnProjectileHit; G.DangersModel.OnProjectileSpawned += OnFirstShoot; G.WeaponModel.OnPlayerShoot += OnPlayerShoot; G.WeaponModel.OnWeaponChanged += OnWeaponSwitch; G.WeaponModel.OnCurrentWeaponEmpty += OnCurrentWeaponEmpty; G.WeaponModel.OnNoAmmo += OnAmmoEmpty; AirDropManager.AmmoRequested += OnAmmoRequest; } private void UnsubscribeFromEvents() { G.WeaponModel.OnPlayerShoot -= OnPlayerShoot; G.PlayerStateModel.PlayerHealthComponent.Damaged -= OnProjectileHitPlayer; G.DangersModel.OnProjectileSpawned -= OnFirstShoot; G.WeaponModel.OnWeaponChanged -= OnWeaponSwitch; G.WeaponModel.OnCurrentWeaponEmpty -= OnCurrentWeaponEmpty; G.WeaponModel.OnNoAmmo -= OnAmmoEmpty; AirDropManager.AmmoRequested -= OnAmmoRequest; } #endregion #region 1. Обучение стрельбе private void StartShootLearning() { shootCts?.Cancel(); shootCts?.Dispose(); shootCts = new CancellationTokenSource(); lastShootTime = Time.time; ShootLearnLoop(shootCts.Token).Forget(); } public void OnPlayerShoot() { lastShootTime = Time.time; // Если обучение стрельбе активно - прерываем if (!isShootLearned) { StopCurrentView(); isShootLearned = true; shootCts?.Cancel(); Debug.Log("[Learn] Обучение стрельбе завершено!"); } } private async UniTask ShootLearnLoop(CancellationToken ct) { while (!ct.IsCancellationRequested && !isShootLearned) { // Ждем указанное время без выстрела await UniTask.Delay(TimeSpan.FromSeconds(shootDelay), cancellationToken: ct); if (!isShootLearned && !ct.IsCancellationRequested) { // Показываем подсказку await ShowTip(shootTip, ct); // Ждем пока игрок не выстрелит while (!isShootLearned && !ct.IsCancellationRequested) { await UniTask.Yield(ct); } // Скрываем подсказку после выстрела await HideTip(ct); } } } #endregion #region 2. Обучение нырянию private void StartDiveLearning() { diveCts?.Cancel(); diveCts?.Dispose(); diveCts = new CancellationTokenSource(); } // Вызывается при первом выстреле ИЛИ при попадании снаряда public void OnFirstShoot(AbstractProjectile projectile) { if (isDiveLearned) return; if(projectile.Owner == player) return; // Начинаем процесс обучения нырянию isDiveLearningActive = true; DiveLearnProcess(diveCts.Token).Forget(); } public void OnProjectileHitPlayer(float damage) { if (isDiveLearned || !isDiveLearningActive) return; // Снаряд попал в игрока - показываем подсказку StopCurrentView(); ShowTip(diveTip, diveCts.Token).Forget(); } public void OnPlayerDive(float depthPercent) { if (isDiveLearned || !isDiveLearningActive) return; // Проверяем глубину погружения if (depthPercent >= diveDepthPercent) { // Игрок нырнул на нужную глубину - обучение завершено isDiveLearningActive = false; isDiveLearned = true; diveCts?.Cancel(); StopCurrentView(); Debug.Log($"[Learn] Обучение нырянию завершено! Глубина: {depthPercent:P0}"); } } private async UniTask DiveLearnProcess(CancellationToken ct) { // Ждем пока активен процесс обучения while (isDiveLearningActive && !ct.IsCancellationRequested) { await UniTask.Yield(ct); } // Скрываем подсказку если она висит await HideTip(ct); } #endregion #region 3. Обучение смене оружия private void StartWeaponLearning() { weaponCts?.Cancel(); weaponCts?.Dispose(); weaponCts = new CancellationTokenSource(); isWeaponLearningActive = true; WeaponLearnLoop(weaponCts.Token).Forget(); } public void OnCurrentWeaponEmpty() { if (isWeaponLearned) return; weaponEmptyTime = Time.time; isWeaponSwitchRequested = true; } public void OnWeaponSwitch(Weapon _) { if (isWeaponLearned) return; // Игрок сам поменял оружие - прерываем обучение isWeaponLearningActive = false; isWeaponLearned = true; isWeaponSwitchRequested = false; weaponCts?.Cancel(); StopCurrentView(); Debug.Log("[Learn] Обучение смене оружия завершено!"); } private async UniTask WeaponLearnLoop(CancellationToken ct) { while (isWeaponLearningActive && !ct.IsCancellationRequested) { // Проверяем: оружие пустое и прошло 3 секунды if (isWeaponSwitchRequested && Time.time - weaponEmptyTime >= 3f) { // Показываем настойчивую подсказку await ShowTip(weaponTip, ct); // Ждем пока игрок не поменяет оружие while (!isWeaponLearned && !ct.IsCancellationRequested) { await UniTask.Yield(ct); } await HideTip(ct); isWeaponSwitchRequested = false; } await UniTask.Delay(100, cancellationToken: ct); } } #endregion #region 4. Обучение запросу боеприпасов private void StartAmmoLearning() { ammoCts?.Cancel(); ammoCts?.Dispose(); ammoCts = new CancellationTokenSource(); isAmmoLearningActive = true; AmmoLearnLoop(ammoCts.Token).Forget(); } public void OnAmmoEmpty() { if (isAmmoLearned) return; if(!AirDropManager.CanRequestAmmo) return; ammoEmptyTime = Time.time; } public void OnAmmoRequest() { if (isAmmoLearned) return; // Игрок запросил боеприпасы - обучение завершено isAmmoLearningActive = false; isAmmoLearned = true; ammoCts?.Cancel(); StopCurrentView(); Debug.Log("[Learn] Обучение запросу боеприпасов завершено!"); } private async UniTask AmmoLearnLoop(CancellationToken ct) { while (isAmmoLearningActive && !ct.IsCancellationRequested) { // Проверяем: патроны кончились и прошло 3 секунды if (ammoEmptyTime > 0 && Time.time - ammoEmptyTime >= ammoDelay) { // Показываем подсказку await ShowTip(ammoTip, ct); // Ждем пока игрок не запросит патроны while (!isAmmoLearned && !ct.IsCancellationRequested) { await UniTask.Yield(ct); } await HideTip(ct); ammoEmptyTime = 0; } await UniTask.Delay(100, cancellationToken: ct); } } #endregion #region Вспомогательные методы private async UniTask ShowTip(Tip tip, CancellationToken ct) { // Отменяем текущую подсказку await StopCurrentView(); viewCts?.Cancel(); viewCts?.Dispose(); viewCts = CancellationTokenSource.CreateLinkedTokenSource(ct); view.Configure(tip); await view.Show(viewCts.Token); } private async UniTask HideTip(CancellationToken ct) { if (viewCts != null && !viewCts.IsCancellationRequested) { await view.Hide(viewCts.Token); } } private async UniTask StopCurrentView() { if (viewCts != null) { viewCts.Cancel(); await view.Hide(viewCts.Token); viewCts?.Dispose(); viewCts = null; } } #endregion #region Cleanup private void OnDestroy() { UnsubscribeFromEvents(); shootCts?.Cancel(); diveCts?.Cancel(); weaponCts?.Cancel(); ammoCts?.Cancel(); viewCts?.Cancel(); shootCts?.Dispose(); diveCts?.Dispose(); weaponCts?.Dispose(); ammoCts?.Dispose(); viewCts?.Dispose(); } #endregion #region Тестовые методы (для отладки) [ContextMenu("Reset All Learning")] public void ResetAllLearning() { isShootLearned = false; isDiveLearned = false; isWeaponLearned = false; isAmmoLearned = false; isDiveLearningActive = false; isWeaponLearningActive = true; isAmmoLearningActive = true; isWeaponSwitchRequested = false; lastShootTime = Time.time; weaponEmptyTime = 0; ammoEmptyTime = 0; StopCurrentView(); Init(); Debug.Log("[Learn] Все обучения сброшены!"); } #endregion } [Serializable] public struct Tip { [TextArea(2, 4)] public string text; public InSignal signal; public AudioClip voiceClip; // Для ситуативной озвучки } }