431 lines
14 KiB
C#
431 lines
14 KiB
C#
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; // Для ситуативной озвучки
|
||
}
|
||
} |