Files
BlockSpace-v1.5.0b/Assets/Scripts/Assembly-CSharp/BlockSpace/Gamemodes/BuildBattleController.cs
T
2026-06-01 17:19:55 +02:00

471 lines
12 KiB
C#

using System.Collections;
using System.Text;
using BlockSpace.Voxels;
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
namespace BlockSpace.Gamemodes
{
[DisallowMultipleComponent]
[RequireComponent(typeof(PhotonView))]
[RequireComponent(typeof(GamemodeZone))]
public sealed class BuildBattleController : MonoBehaviourPunCallbacks
{
private enum Phase : byte
{
Build = 0,
Play = 1
}
private const double BuildDurationSeconds = 60.0;
private const double PlayDurationSeconds = 30.0;
[SerializeField]
private string[] themes = new string[5] { "Castle", "Rocket", "Robot", "Dragon", "Treehouse" };
[SerializeField]
private TMP_Text themeText;
[SerializeField]
private TMP_Text timerText;
[SerializeField]
private VoxelGroupManager groupManager;
[SerializeField]
private bool enableLogs = true;
private GamemodeZone zone;
private string currentTheme;
private bool localWasInZone;
private bool cachedCreatorMode;
private bool cachedEditingLocked;
private Coroutine masterLoop;
private bool bootstrapAttempted;
private bool hasPhaseCache;
private Phase cachedPhase;
private double cachedStartTime;
private double nextMissingPropsLogTime;
private string PhaseKey => $"BB_{zone.zoneID}_phase";
private string StartKey => $"BB_{zone.zoneID}_start";
private void Awake()
{
zone = GetComponent<GamemodeZone>();
if (groupManager == null)
{
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : Object.FindObjectOfType<VoxelGroupManager>());
}
}
private void Start()
{
if (themeText != null)
{
themeText.text = "Waiting for next round...";
}
Log($"Start (inRoom={PhotonNetwork.InRoom}, isMaster={PhotonNetwork.IsMasterClient}, viewID={((base.photonView != null) ? base.photonView.ViewID : (-1))})");
TryStartMasterLoop();
}
private new void OnEnable()
{
Log($"OnEnable (inRoom={PhotonNetwork.InRoom}, isMaster={PhotonNetwork.IsMasterClient})");
TryStartMasterLoop();
}
private void Update()
{
UpdateTimerText();
UpdateMasterPhaseAdvance();
ApplyLocalToolRules();
}
private IEnumerator MasterLoop()
{
while (true)
{
yield return WaitForSecondsNetwork(60.0);
StartPlayPhase();
yield return WaitForSecondsNetwork(30.0);
StartBuildPhase();
}
}
public override void OnJoinedRoom()
{
if (themeText != null)
{
themeText.text = "Waiting for next round...";
}
Log($"OnJoinedRoom (isMaster={PhotonNetwork.IsMasterClient}, viewID={((base.photonView != null) ? base.photonView.ViewID : (-1))})");
TryStartMasterLoop();
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
Log($"OnMasterClientSwitched (newMaster={newMasterClient?.ActorNumber}, amMaster={PhotonNetwork.IsMasterClient})");
TryStartMasterLoop();
}
private void TryStartMasterLoop()
{
if (!PhotonNetwork.InRoom)
{
Log("TryStartMasterLoop: not in room yet");
}
else if (!PhotonNetwork.IsMasterClient)
{
Log("TryStartMasterLoop: not master");
if (masterLoop != null)
{
StopCoroutine(masterLoop);
masterLoop = null;
Log("Stopped master loop.");
}
}
else
{
EnsurePhotonViewReady();
if (masterLoop == null)
{
Log("Starting master loop; kicking BUILD immediately.");
StartBuildPhase();
masterLoop = StartCoroutine(MasterLoop());
}
}
}
private void EnsurePhotonViewReady()
{
if (!(base.photonView == null) && base.photonView.ViewID == 0 && PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient)
{
bool flag = PhotonNetwork.AllocateViewID(base.photonView);
Log($"AllocateViewID -> {flag} (viewID={base.photonView.ViewID})");
}
}
private void StartBuildPhase()
{
if (PhotonNetwork.IsMasterClient && !IsFreshPhase(Phase.Build))
{
EnsurePhotonViewReady();
Log($"BUILD start (zoneID={zone.zoneID})");
int num = ((groupManager != null) ? groupManager.RemoveGroupsForZone(zone) : 0);
Log($"BUILD cleared {num} group(s) in zone {zone.zoneID}");
string text = PickRandomTheme();
RPC_SetTheme(text);
Log("BUILD theme picked: " + text);
base.photonView.RPC("RPC_SetTheme", RpcTarget.AllViaServer, text);
SetPhaseProperties(Phase.Build, PhotonNetwork.Time);
}
}
private void StartPlayPhase()
{
if (PhotonNetwork.IsMasterClient && !IsFreshPhase(Phase.Play))
{
EnsurePhotonViewReady();
Log($"PLAY start (zoneID={zone.zoneID})");
SetPhaseProperties(Phase.Play, PhotonNetwork.Time);
}
}
private bool IsFreshPhase(Phase phase)
{
if (hasPhaseCache && cachedPhase == phase)
{
return PhotonNetwork.Time - cachedStartTime < 1.0;
}
return false;
}
private string PickRandomTheme()
{
if (themes == null || themes.Length == 0)
{
return "Something";
}
int num = Random.Range(0, themes.Length);
if (!string.IsNullOrEmpty(themes[num]))
{
return themes[num];
}
return "Something";
}
private void SetPhaseProperties(Phase phase, double startTimestamp)
{
if (PhotonNetwork.InRoom)
{
if (PhotonNetwork.CurrentRoom == null)
{
Log("SetPhaseProperties: CurrentRoom is null");
return;
}
ExitGames.Client.Photon.Hashtable propertiesToSet = new ExitGames.Client.Photon.Hashtable
{
{
PhaseKey,
(int)phase
},
{ StartKey, startTimestamp }
};
PhotonNetwork.CurrentRoom.SetCustomProperties(propertiesToSet);
Log(string.Format("SetPhaseProperties: {0} start={1:F3}", (phase == Phase.Build) ? "BUILD" : "PLAY", startTimestamp));
LogRoomProps("After SetCustomProperties");
cachedPhase = phase;
cachedStartTime = startTimestamp;
hasPhaseCache = true;
base.photonView.RPC("RPC_SyncPhase", RpcTarget.AllViaServer, (int)phase, startTimestamp);
}
}
private void UpdateTimerText()
{
if (timerText == null || !PhotonNetwork.InRoom || PhotonNetwork.CurrentRoom == null)
{
return;
}
if (!TryGetSyncedPhase(out var phase, out var startTime))
{
if (PhotonNetwork.Time >= nextMissingPropsLogTime)
{
nextMissingPropsLogTime = PhotonNetwork.Time + 2.0;
Log("UpdateTimerText: missing phase/start properties");
LogRoomProps("Timer missing props");
}
if (!bootstrapAttempted && PhotonNetwork.IsMasterClient)
{
bootstrapAttempted = true;
Log("UpdateTimerText: bootstrapping BUILD phase (master detected, no phase cache present).");
StartBuildPhase();
}
if (!TryGetSyncedPhase(out phase, out startTime))
{
return;
}
}
bootstrapAttempted = false;
double num = ((phase == Phase.Build) ? 60.0 : 30.0);
double num2 = PhotonNetwork.Time - startTime;
int num3 = Mathf.Max(0, Mathf.CeilToInt((float)(num - num2)));
timerText.text = ((phase == Phase.Build) ? "BUILD: " : "PLAY: ") + num3;
}
private void UpdateMasterPhaseAdvance()
{
if (!PhotonNetwork.InRoom || !PhotonNetwork.IsMasterClient || !TryGetSyncedPhase(out var phase, out var startTime))
{
return;
}
double num = ((phase == Phase.Build) ? 60.0 : 30.0);
double num2 = PhotonNetwork.Time - startTime;
if (!(num2 < num))
{
Log(string.Format("Master advancing phase from {0} after {1:F2}s", (phase == Phase.Build) ? "BUILD" : "PLAY", num2));
if (phase == Phase.Build)
{
StartPlayPhase();
}
else
{
StartBuildPhase();
}
}
}
private bool TryGetSyncedPhase(out Phase phase, out double startTime)
{
if (hasPhaseCache)
{
phase = cachedPhase;
startTime = cachedStartTime;
return true;
}
if (TryGetPhaseAndStart(out phase, out startTime))
{
cachedPhase = phase;
cachedStartTime = startTime;
hasPhaseCache = true;
return true;
}
return false;
}
private bool TryGetPhaseAndStart(out Phase phase, out double startTime)
{
phase = Phase.Build;
startTime = 0.0;
if (PhotonNetwork.CurrentRoom.CustomProperties == null)
{
return false;
}
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(PhaseKey, out var value);
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(StartKey, out var value2);
if (value == null || value2 == null)
{
return false;
}
if (value is int value3)
{
phase = (Phase)Mathf.Clamp(value3, 0, 1);
}
else if (value is byte value4)
{
phase = (Phase)Mathf.Clamp(value4, 0, 1);
}
if (value2 is double num)
{
startTime = num;
}
else if (value2 is float num2)
{
startTime = num2;
}
else if (value2 is int num3)
{
startTime = num3;
}
return true;
}
public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
{
if (propertiesThatChanged != null && (propertiesThatChanged.ContainsKey(PhaseKey) || propertiesThatChanged.ContainsKey(StartKey)))
{
LogRoomProps("OnRoomPropertiesUpdate (phase/start changed)");
}
}
private void ApplyLocalToolRules()
{
if (zone == null || groupManager == null)
{
return;
}
bool flag = GamemodeZoneManager.Instance != null && GamemodeZoneManager.Instance.CurrentZone == zone;
if (flag != localWasInZone)
{
if (flag)
{
cachedCreatorMode = groupManager.CreatorMode;
cachedEditingLocked = groupManager.EditingLocked;
}
else
{
groupManager.SetEditingLocked(cachedEditingLocked);
groupManager.SetCreatorMode(cachedCreatorMode);
}
localWasInZone = flag;
}
if (flag && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null && TryGetSyncedPhase(out var phase, out var _))
{
if (phase == Phase.Build)
{
groupManager.SetEditingLocked(locked: false);
groupManager.SetCreatorMode(enabled: true);
}
else
{
groupManager.SetEditingLocked(locked: true);
groupManager.SetCreatorMode(enabled: false);
}
}
}
private IEnumerator WaitForSecondsNetwork(double seconds)
{
double start = PhotonNetwork.Time;
while (PhotonNetwork.Time - start < seconds)
{
yield return null;
}
}
[PunRPC]
private void RPC_SetTheme(string theme)
{
currentTheme = theme;
if (themeText != null)
{
themeText.text = "Theme: " + theme;
}
Log("RPC_SetTheme received: " + theme);
}
[PunRPC]
private void RPC_SyncPhase(int phase, double startTimestamp)
{
cachedPhase = (Phase)Mathf.Clamp(phase, 0, 1);
cachedStartTime = startTimestamp;
hasPhaseCache = true;
Log(string.Format("RPC_SyncPhase received: {0} start={1:F3}", (cachedPhase == Phase.Build) ? "BUILD" : "PLAY", startTimestamp));
}
private void Log(string message)
{
if (enableLogs)
{
Debug.Log($"[BuildBattle zone={zone.zoneID}] {message}", this);
}
}
private void LogRoomProps(string prefix)
{
if (enableLogs && PhotonNetwork.InRoom && PhotonNetwork.CurrentRoom != null)
{
object value = null;
object value2 = null;
if (PhotonNetwork.CurrentRoom.CustomProperties != null)
{
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(PhaseKey, out value);
PhotonNetwork.CurrentRoom.CustomProperties.TryGetValue(StartKey, out value2);
}
string text = ((PhotonNetwork.CurrentRoom.CustomProperties != null) ? FormatHashtable(PhotonNetwork.CurrentRoom.CustomProperties) : "null");
Debug.Log(string.Format("[BuildBattle zone={0}] {1} keys=({2}={3}, {4}={5}) all={6}", zone.zoneID, prefix, PhaseKey, value ?? "null", StartKey, value2 ?? "null", text), this);
}
}
private static string FormatHashtable(IDictionary table)
{
if (table == null)
{
return "null";
}
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.Append("{");
bool flag = true;
foreach (DictionaryEntry item in table)
{
if (!flag)
{
stringBuilder.Append(", ");
}
flag = false;
stringBuilder.Append((item.Key != null) ? item.Key.ToString() : "null");
stringBuilder.Append("=");
stringBuilder.Append((item.Value != null) ? item.Value.ToString() : "null");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}
}
}