Files
soccar2d/Assets/Scripts/GameTutorialManager.cs
2026-09-06 21:36:26 +05:30

365 lines
9.8 KiB
C#

using System;
using System.Collections;
using Mirror;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using DG.Tweening;
public class GameTutorialManager : MonoBehaviour
{
public const string CpuOpponentName = "CPU";
public const string DefaultGameSceneName = "Game";
public const string CpuL10PrefsKey = "practice_cpu_l10";
const int L10Window = 10;
public static bool tutorialModeTrigger = false;
public bool isTutorial = false;
public static bool tutorialModeEnabled = false;
public AiBotController aiController;
[Header("UI")]
public CanvasGroup intro_shoot;
public CanvasGroup intro_goal;
public float introFadeDuration = 0.3f;
Button _introShootButton;
Button _introGoalButton;
bool _shootIntroDismissed;
bool _goalIntroDismissed;
bool _introVisible;
void Awake()
{
#if UNITY_SERVER
tutorialModeEnabled = false;
tutorialModeTrigger = false;
HideIntroImmediate(intro_shoot);
HideIntroImmediate(intro_goal);
enabled = false;
return;
#endif
tutorialModeEnabled = false;
if (isTutorial)
tutorialModeTrigger = true;
if (tutorialModeTrigger)
tutorialModeEnabled = true;
tutorialModeTrigger = false;
if (!tutorialModeEnabled)
{
HideIntroImmediate(intro_shoot);
HideIntroImmediate(intro_goal);
}
}
void OnEnable()
{
GameManager.OnTeamChanged += OnTeamChanged;
}
void OnDisable()
{
GameManager.OnTeamChanged -= OnTeamChanged;
}
void OnDestroy()
{
if (tutorialModeEnabled)
tutorialModeEnabled = false;
if (intro_shoot != null)
DOTween.Kill(intro_shoot);
if (intro_goal != null)
DOTween.Kill(intro_goal);
}
void Start()
{
if (tutorialModeEnabled)
{
NetManager.StopNetworkingForSceneChange();
NetworkManager.singleton.networkAddress = "localhost";
NetworkManager.singleton.StartHost();
WireIntroButtons();
HideIntroImmediate(intro_goal);
HideIntroImmediate(intro_shoot);
StartCoroutine(SetupAiWhenReady());
StartCoroutine(ShowShootIntroWhenReady());
}
}
void WireIntroButtons()
{
if (intro_shoot != null)
_introShootButton = intro_shoot.GetComponentInChildren<Button>(true);
if (intro_goal != null)
_introGoalButton = intro_goal.GetComponentInChildren<Button>(true);
if (_introShootButton != null)
_introShootButton.onClick.AddListener(OnShootIntroDismissed);
if (_introGoalButton != null)
_introGoalButton.onClick.AddListener(OnGoalIntroDismissed);
}
IEnumerator ShowShootIntroWhenReady()
{
while (GameManager.instance == null || !GameManager.instance.gameStarted)
yield return null;
while (NetPlayer.localPlayer == null)
yield return null;
ShowIntro(intro_shoot);
}
void OnTeamChanged(Team team)
{
if (!tutorialModeEnabled || _introVisible)
return;
if (team != GetPlayerTeam())
return;
if (!_shootIntroDismissed || _goalIntroDismissed)
return;
ShowIntro(intro_goal);
}
void OnShootIntroDismissed()
{
if (_shootIntroDismissed)
return;
_shootIntroDismissed = true;
DismissIntro(intro_shoot);
}
void OnGoalIntroDismissed()
{
if (_goalIntroDismissed)
return;
_goalIntroDismissed = true;
DismissIntro(intro_goal);
}
void ShowIntro(CanvasGroup group)
{
if (group == null)
return;
EnsureTutorialCanvasVisible(group);
DOTween.Kill(group);
group.gameObject.SetActive(true);
group.alpha = 1f;
group.interactable = true;
group.blocksRaycasts = true;
_introVisible = true;
if (GameManager.instance != null)
GameManager.instance.SetTurnTimerPaused(true);
}
void DismissIntro(CanvasGroup group)
{
if (group == null)
return;
group.interactable = false;
group.blocksRaycasts = false;
DOTween.Kill(group);
group.DOFade(0f, introFadeDuration)
.SetEase(Ease.InOutSine)
.OnComplete(() =>
{
group.gameObject.SetActive(false);
_introVisible = false;
if (GameManager.instance != null)
GameManager.instance.SetTurnTimerPaused(false);
});
}
static void HideIntroImmediate(CanvasGroup group)
{
if (group == null)
return;
DOTween.Kill(group);
group.alpha = 0f;
group.interactable = false;
group.blocksRaycasts = false;
group.gameObject.SetActive(false);
}
static void EnsureTutorialCanvasVisible(CanvasGroup group)
{
Transform root = group.transform.root;
if (root.localScale == Vector3.zero)
root.localScale = Vector3.one;
}
static Team GetPlayerTeam()
{
if (NetPlayer.localPlayer != null)
return NetPlayer.localPlayer.myTeam;
return GameManager.MyTeam;
}
/// <summary>Call from the main menu before loading the game scene.</summary>
public static void PrepareLaunch(MatchmadePlayer myPlayer, MatchmadePlayer cpuPlayer, Team myTeam = Team.Red)
{
LoginManager.ClearMatchYourTeam();
GameManager.MyTeam = myTeam;
GameManager.MatchRcPrizeCoins = 0;
GameManager.ConfigureDedicatedMatchReporting(0);
if (myTeam == Team.Red)
{
GameManager.RedPlayer = myPlayer;
GameManager.BluePlayer = cpuPlayer;
}
else
{
GameManager.RedPlayer = cpuPlayer;
GameManager.BluePlayer = myPlayer;
}
LevelLoadManager.myPlayer = myPlayer;
LevelLoadManager.opponentPlayer = cpuPlayer;
if (LevelLoadManager.instance != null)
LevelLoadManager.instance.SetupMatchMade(myTeam, myPlayer, cpuPlayer, "0 RC");
tutorialModeTrigger = true;
}
public static MatchmadePlayer CreateCpuOpponent()
{
GetCpuL10(out int wins, out int losses);
return new MatchmadePlayer
{
Name = CpuOpponentName,
l10_wins = wins,
l10_losses = losses
};
}
public static void GetCpuL10(out int wins, out int losses)
{
CountL10(PlayerPrefs.GetString(CpuL10PrefsKey, string.Empty), out wins, out losses);
}
public static void ApplyCpuL10(MatchmadePlayer cpu)
{
if (cpu == null)
return;
GetCpuL10(out int wins, out int losses);
cpu.l10_wins = wins;
cpu.l10_losses = losses;
}
/// <summary>
/// Records one practice result from the CPU's perspective (rolling last 10).
/// Cleared on logout because <see cref="LoginManager.Logout"/> calls PlayerPrefs.DeleteAll.
/// </summary>
public static void RecordCpuL10Result(bool cpuWon)
{
string history = PlayerPrefs.GetString(CpuL10PrefsKey, string.Empty) ?? string.Empty;
history += cpuWon ? 'W' : 'L';
if (history.Length > L10Window)
history = history.Substring(history.Length - L10Window);
PlayerPrefs.SetString(CpuL10PrefsKey, history);
PlayerPrefs.Save();
}
static void CountL10(string history, out int wins, out int losses)
{
wins = 0;
losses = 0;
if (string.IsNullOrEmpty(history))
return;
for (int i = 0; i < history.Length; i++)
{
if (history[i] == 'W')
wins++;
else if (history[i] == 'L')
losses++;
}
}
public static MatchmadePlayer CreateMyPlayerFromUser(UserData user)
{
return new MatchmadePlayer
{
Name = user.username,
UserId = user.id,
rc = user.rc
};
}
public static IEnumerator CoFetchPlayerL10(MatchmadePlayer player)
{
if (player == null || player.UserId <= 0)
yield break;
string baseUrl = (GameManager.DedicatedInternalApiBase ?? LoginManager.AuthBaseUrl ?? "").TrimEnd('/');
if (string.IsNullOrEmpty(baseUrl))
yield break;
string url = baseUrl + "/players/" + player.UserId + "/l10";
using (var req = UnityWebRequest.Get(url))
{
yield return req.SendWebRequest();
if (req.result != UnityWebRequest.Result.Success)
{
Logger.Log("Tutorial L10 fetch failed: " + req.error + " " + url);
yield break;
}
var resp = JsonUtility.FromJson<PlayerL10Response>(req.downloadHandler.text);
if (resp != null && resp.ok)
{
player.l10_wins = resp.l10_wins;
player.l10_losses = resp.l10_losses;
}
}
}
[Serializable]
class PlayerL10Response
{
public bool ok;
public int player_id;
public int l10_wins;
public int l10_losses;
}
IEnumerator SetupAiWhenReady()
{
float timeout = 5f;
while (NetPlayer.localPlayer == null && timeout > 0f)
{
timeout -= Time.deltaTime;
yield return null;
}
Team playerTeam = NetPlayer.localPlayer != null
? NetPlayer.localPlayer.myTeam
: GameManager.MyTeam;
Team opponentTeam = playerTeam == Team.Red ? Team.Blue : Team.Red;
if (GameManager.instance.SelectedTeam != playerTeam)
GameManager.instance.SwitchTeams();
aiController.Setup(opponentTeam);
}
}