TH1/Unity/Assets/Scripts/TH1_AOT/AotMainBootstrap.cs
2026-06-11 00:34:50 +08:00

257 lines
8.5 KiB
C#

using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using TH1_Logic.Hotfix;
using TH1Resource;
using UnityEngine;
using UnityEngine.SceneManagement;
using YooAsset;
namespace TH1_AOT
{
[DisallowMultipleComponent]
public sealed class AotMainBootstrap : MonoBehaviour
{
private const string GameSceneAssetRoot = "Assets/BundleResources/";
[Header("Game Scene")]
public string GameSceneLocation = "Scenes/SampleScene.unity";
public bool LoadGameSceneFromYooAsset = true;
[Header("Debug Param")]
public bool NoAI = false;
public bool FullSight = true;
public float AIActionTime = 0.2f;
public bool AIAllTech = true;
public bool AIMoreMoney = true;
public float LandThreshold = -1f;
public float AnimationSpeed = 1f;
public bool DebugMode = false;
public bool DebugHideCenterMessage = false;
[Header("Play Settings")]
public int cityCount;
public int unitCount;
public int turn;
public int renko;
public bool IsNetActionExecuting;
[Header("RenderObject")]
public GameObject ROMapRenderer;
[Header("SuperBank")]
public Material URPDefaultMat;
private bool _started;
private static bool _runtimeStarted;
private static SceneHandle _gameSceneHandle;
private static readonly string[] ForwardedFieldNames =
{
nameof(GameSceneLocation),
nameof(LoadGameSceneFromYooAsset),
nameof(NoAI),
nameof(FullSight),
nameof(AIActionTime),
nameof(AIAllTech),
nameof(AIMoreMoney),
nameof(LandThreshold),
nameof(AnimationSpeed),
nameof(DebugMode),
nameof(DebugHideCenterMessage),
nameof(cityCount),
nameof(unitCount),
nameof(turn),
nameof(renko),
nameof(IsNetActionExecuting),
nameof(ROMapRenderer),
nameof(URPDefaultMat),
};
private IEnumerator Start()
{
if (_started) yield break;
_started = true;
if (_runtimeStarted)
{
yield break;
}
_runtimeStarted = true;
Application.runInBackground = true;
DontDestroyOnLoad(gameObject);
Debug.Log("[TH1.AOT] Bootstrap start.");
var hotfixAssembly = ResolveHotfixAssembly();
if (hotfixAssembly == null)
{
Debug.LogError("[TH1.AOT] Hotfix assembly is not available.");
yield break;
}
HotfixBootstrap.EnsureAotMetadataLoaded();
HotfixBootstrap.InvokeHotfixEntry(hotfixAssembly);
HotfixBootstrap.RefreshRuntimeTypeCaches();
Debug.Log($"[TH1.AOT] Hotfix assembly ready: {hotfixAssembly.GetName().Name}");
yield return ResourceManager.InitializeCoroutine();
var sceneBootstrap = this;
if (LoadGameSceneFromYooAsset && !IsGameSceneLoaded(GameSceneLocation))
{
var sceneHandle = ResourceManager.LoadSceneAsync(GameSceneLocation, LoadSceneMode.Single);
yield return sceneHandle;
if (sceneHandle.Status != EOperationStatus.Succeed)
{
Debug.LogError($"[TH1.AOT] Load game scene failed: {GameSceneLocation}, {sceneHandle.LastError}");
_runtimeStarted = false;
yield break;
}
_gameSceneHandle = sceneHandle;
sceneHandle.ActivateScene();
sceneBootstrap = FindBootstrapInScene(sceneHandle.SceneObject) ?? this;
if (sceneBootstrap != this)
{
sceneBootstrap.enabled = false;
}
Debug.Log($"[TH1.AOT] Loaded game scene from YooAsset: {GameSceneLocation}");
}
var hotfixMainType = hotfixAssembly.GetType(HotfixManifest.RuntimeMainTypeName, false);
if (hotfixMainType == null)
{
Debug.LogError($"[TH1.AOT] Hotfix Main type not found: {HotfixManifest.RuntimeMainTypeName}");
yield break;
}
if (!typeof(MonoBehaviour).IsAssignableFrom(hotfixMainType))
{
Debug.LogError($"[TH1.AOT] Hotfix Main type is not a MonoBehaviour: {hotfixMainType.FullName}");
yield break;
}
var mainHost = sceneBootstrap != null ? sceneBootstrap.gameObject : gameObject;
var hotfixMain = mainHost.GetComponent(hotfixMainType);
if (hotfixMain == null)
{
hotfixMain = mainHost.AddComponent(hotfixMainType);
}
ForwardSceneFields(sceneBootstrap, hotfixMain);
Debug.Log($"[TH1.AOT] Started hotfix Main from {hotfixAssembly.GetName().Name}.");
}
private static Assembly ResolveHotfixAssembly()
{
#if UNITY_EDITOR
var editorAssembly = FindLoadedAssembly(HotfixManifest.HotfixAssemblyName);
if (editorAssembly != null)
{
return editorAssembly;
}
#endif
if (HotfixBootstrap.LoadedAssembly != null)
{
return HotfixBootstrap.LoadedAssembly;
}
if (HotfixBootstrap.InitializeFromStreamingAssets(true))
{
return HotfixBootstrap.LoadedAssembly;
}
return FindLoadedAssembly(HotfixManifest.HotfixAssemblyName);
}
private static Assembly FindLoadedAssembly(string assemblyName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(assembly => assembly.GetName().Name == assemblyName);
}
private bool IsGameSceneLoaded(string location)
{
var expectedPath = NormalizeGameSceneAssetPath(location);
for (var i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (!scene.isLoaded) continue;
var scenePath = scene.path.Replace('\\', '/');
if (scenePath.Equals(expectedPath, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
private static string NormalizeGameSceneAssetPath(string location)
{
if (string.IsNullOrWhiteSpace(location))
{
return string.Empty;
}
var normalized = location.Replace('\\', '/').TrimStart('/');
return normalized.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)
? normalized
: GameSceneAssetRoot + normalized;
}
private AotMainBootstrap FindBootstrapInScene(Scene scene)
{
if (scene.IsValid() && scene.isLoaded)
{
foreach (var root in scene.GetRootGameObjects())
{
var bootstrap = root.GetComponentsInChildren<AotMainBootstrap>(true)
.FirstOrDefault(item => item != null && item != this);
if (bootstrap != null)
{
return bootstrap;
}
}
}
return FindObjectsOfType<AotMainBootstrap>(true)
.FirstOrDefault(item => item != null && item != this);
}
private static void ForwardSceneFields(AotMainBootstrap source, Component hotfixMain)
{
if (source == null || hotfixMain == null) return;
var sourceType = source.GetType();
var targetType = hotfixMain.GetType();
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
foreach (var fieldName in ForwardedFieldNames)
{
var sourceField = sourceType.GetField(fieldName, flags);
var targetField = targetType.GetField(fieldName, flags);
if (sourceField == null || targetField == null) continue;
try
{
targetField.SetValue(hotfixMain, sourceField.GetValue(source));
}
catch (Exception e)
{
Debug.LogWarning($"[TH1.AOT] Forward field failed: {fieldName}, {e.Message}");
}
}
}
}
}