Files
soccar2d/Assets/Scripts/OTA/BootstrapUpdater.cs
T
2026-10-08 00:49:10 +05:30

239 lines
8.0 KiB
C#

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;
using TMPro;
public class BootstrapUpdater : MonoBehaviour
{
public const string ContentReadyPrefsKey = "Ota.ContentReady";
[Header("Host")]
[Tooltip("Apache folder that serves version.json and the Addressables catalog. Example: https://kkapi.playpoolstudios.com/ota")]
[SerializeField] string otaBaseUrl = "https://kkapi.playpoolstudios.com/ota";
[Tooltip("Opened when version.json has no storeUrl. Same role as the login screen Telegram link.")]
[SerializeField] string fallbackStoreUrl = "https://t.me/+dfUhy4gmGXk0ZDRl";
[Header("Content")]
[SerializeField] string introSceneAddress = AddressableSceneLoader.IntroScene;
[SerializeField] string gameContentLabel = "game-content";
[Header("UI")]
[SerializeField] TMP_Text statusText;
[SerializeField] Slider progressSlider;
[SerializeField] TMP_Text progressText;
[SerializeField] GameObject retryButton;
[SerializeField] GameObject updateButton;
string pendingStoreUrl = "";
bool updateRunning;
void Start()
{
SetRetryVisible(false);
SetUpdateVisible(false);
SetProgress(0f, "");
if (!updateRunning)
StartCoroutine(RunUpdate());
}
public void Retry()
{
if (updateRunning)
return;
SetRetryVisible(false);
SetUpdateVisible(false);
StartCoroutine(RunUpdate());
}
public void OpenStore()
{
string url = string.IsNullOrWhiteSpace(pendingStoreUrl) ? fallbackStoreUrl : pendingStoreUrl;
if (!string.IsNullOrWhiteSpace(url))
Application.OpenURL(url.Trim());
}
IEnumerator RunUpdate()
{
updateRunning = true;
SetStatus("Checking for updates");
OtaVersionInfo version = null;
bool versionFetched = false;
yield return FetchVersion(info =>
{
version = info;
versionFetched = info != null;
});
if (!versionFetched)
{
#if UNITY_EDITOR
Debug.LogWarning("OTA version.json was not reachable. Editor play continues into " + introSceneAddress + ".");
#else
if (PlayerPrefs.GetInt(ContentReadyPrefsKey, 0) != 1)
{
Fail("Check your connection and try again.");
yield break;
}
SetStatus("Offline. Loading saved game");
#endif
}
else if (OtaVersionInfo.IsClientBelowRequiredVersion(version.minPlayerVersion))
{
pendingStoreUrl = version.storeUrl;
string message = string.IsNullOrWhiteSpace(version.message)
? "A new version is required."
: version.message.Trim();
SetStatus(message);
SetUpdateVisible(true);
updateRunning = false;
yield break;
}
AsyncOperationHandle<IResourceLocator> init = Addressables.InitializeAsync(false);
yield return init;
bool initOk = init.IsValid() && init.Status == AsyncOperationStatus.Succeeded;
if (init.IsValid())
Addressables.Release(init);
if (!initOk)
{
Fail("Could not start content update.");
yield break;
}
AsyncOperationHandle<System.Collections.Generic.List<string>> check = Addressables.CheckForCatalogUpdates(false);
yield return check;
if (check.Status == AsyncOperationStatus.Succeeded && check.Result != null && check.Result.Count > 0)
{
SetStatus("Updating catalog");
AsyncOperationHandle<System.Collections.Generic.List<IResourceLocator>> catalogs = Addressables.UpdateCatalogs(check.Result, false);
yield return catalogs;
if (catalogs.Status != AsyncOperationStatus.Succeeded)
{
Addressables.Release(catalogs);
Addressables.Release(check);
Fail("Could not update the content catalog.");
yield break;
}
Addressables.Release(catalogs);
}
Addressables.Release(check);
long downloadBytes = 0;
AsyncOperationHandle<long> sizeHandle = Addressables.GetDownloadSizeAsync(gameContentLabel);
yield return sizeHandle;
if (sizeHandle.Status == AsyncOperationStatus.Succeeded)
downloadBytes = sizeHandle.Result;
Addressables.Release(sizeHandle);
if (downloadBytes > 0)
{
AsyncOperationHandle download = Addressables.DownloadDependenciesAsync(gameContentLabel, false);
while (!download.IsDone)
{
DownloadStatus status = download.GetDownloadStatus();
float percent = status.TotalBytes > 0
? (float)status.DownloadedBytes / status.TotalBytes
: download.PercentComplete;
SetProgress(percent, FormatMegabytes(status.DownloadedBytes) + " / " + FormatMegabytes(status.TotalBytes));
SetStatus("Downloading update " + Mathf.RoundToInt(percent * 100f) + "%");
yield return null;
}
if (download.Status != AsyncOperationStatus.Succeeded)
{
Addressables.Release(download);
Fail("Download failed. Check your connection and try again.");
yield break;
}
Addressables.Release(download);
}
PlayerPrefs.SetInt(ContentReadyPrefsKey, 1);
PlayerPrefs.Save();
SetStatus("Loading");
SetProgress(1f, "");
yield return AddressableSceneLoader.LoadRoutine(introSceneAddress);
updateRunning = false;
}
IEnumerator FetchVersion(Action<OtaVersionInfo> onResult)
{
string baseUrl = (otaBaseUrl ?? "").Trim().TrimEnd('/');
if (string.IsNullOrEmpty(baseUrl))
{
onResult?.Invoke(null);
yield break;
}
string url = baseUrl + "/version.json";
using (var request = UnityWebRequest.Get(url))
{
request.timeout = 20;
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
onResult?.Invoke(null);
yield break;
}
try
{
onResult?.Invoke(JsonUtility.FromJson<OtaVersionInfo>(request.downloadHandler.text));
}
catch (Exception e)
{
Debug.LogWarning("OTA version.json parse failed: " + e.Message);
onResult?.Invoke(null);
}
}
}
void Fail(string message)
{
SetStatus(message);
SetRetryVisible(true);
updateRunning = false;
}
void SetStatus(string message)
{
if (statusText != null)
statusText.text = message;
}
void SetProgress(float normalized, string label)
{
if (progressSlider != null)
{
progressSlider.gameObject.SetActive(true);
progressSlider.value = Mathf.Clamp01(normalized);
}
if (progressText != null)
progressText.text = label ?? "";
}
void SetRetryVisible(bool visible)
{
if (retryButton != null)
retryButton.SetActive(visible);
}
void SetUpdateVisible(bool visible)
{
if (updateButton != null)
updateButton.SetActive(visible);
}
static string FormatMegabytes(long bytes)
{
float mb = bytes / (1024f * 1024f);
return mb.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) + " MB";
}
}