EVERYTHING

This commit is contained in:
niko
2026-06-01 17:19:55 +02:00
parent d063b81359
commit b62bac090b
1081 changed files with 1716544 additions and 0 deletions
@@ -0,0 +1,470 @@
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();
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 06715e3dff39b88da77c8f77f24a4212
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
namespace BlockSpace.Gamemodes
{
public enum GamemodeType : byte
{
BuildBattle = 0,
Tag = 1
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0a05560d801134644a774c189adb0943
@@ -0,0 +1,132 @@
using System.Collections.Generic;
using BlockSpace.Voxels;
using Photon.VR.Player;
using UnityEngine;
namespace BlockSpace.Gamemodes
{
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider))]
public sealed class GamemodeZone : MonoBehaviour
{
[SerializeField]
public int zoneID;
[SerializeField]
public GamemodeType type;
private readonly HashSet<int> playersInside = new HashSet<int>();
private Collider zoneCollider;
public IReadOnlyCollection<int> PlayersInside => playersInside;
private void Awake()
{
zoneCollider = GetComponent<Collider>();
if (zoneCollider != null)
{
zoneCollider.isTrigger = true;
}
if (GamemodeZoneManager.Instance == null)
{
GameObject obj = new GameObject("GamemodeZoneManager");
obj.AddComponent<GamemodeZoneManager>();
Object.DontDestroyOnLoad(obj);
}
}
private void OnEnable()
{
EnsureZoneManager();
GamemodeZoneManager.Instance?.RegisterZone(this);
}
private void OnDisable()
{
GamemodeZoneManager.Instance?.UnregisterZone(this);
}
private void Reset()
{
Collider component = GetComponent<Collider>();
if (component != null)
{
component.isTrigger = true;
}
}
public bool ContainsGroup(VoxelGroup group)
{
if (group == null || group.size.x <= 0 || group.size.y <= 0 || group.size.z <= 0)
{
return false;
}
if (zoneCollider == null)
{
zoneCollider = GetComponent<Collider>();
}
if (zoneCollider != null)
{
return zoneCollider.bounds.Intersects(group.GetWorldBounds());
}
return false;
}
public bool ContainsWorldPosition(Vector3 worldPosition)
{
if (zoneCollider == null)
{
zoneCollider = GetComponent<Collider>();
}
if (zoneCollider != null)
{
return zoneCollider.bounds.Contains(worldPosition);
}
return false;
}
internal void RefreshPlayersInside(PhotonVRPlayer[] players)
{
playersInside.Clear();
if (players == null)
{
return;
}
foreach (PhotonVRPlayer photonVRPlayer in players)
{
if (!(photonVRPlayer == null) && !(photonVRPlayer.photonView == null) && photonVRPlayer.photonView.Owner != null && ContainsWorldPosition(GetPlayerZonePosition(photonVRPlayer)))
{
playersInside.Add(photonVRPlayer.photonView.Owner.ActorNumber);
}
}
}
internal static Vector3 GetPlayerZonePosition(PhotonVRPlayer player)
{
if (player == null)
{
return Vector3.zero;
}
if (player.Head != null)
{
return player.Head.position;
}
if (!(player.Body != null))
{
return player.transform.position;
}
return player.Body.position;
}
private static void EnsureZoneManager()
{
if (!(GamemodeZoneManager.Instance != null))
{
GameObject obj = new GameObject("GamemodeZoneManager");
obj.AddComponent<GamemodeZoneManager>();
Object.DontDestroyOnLoad(obj);
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b3fcd6fe0b347da5e9598a2a20a86acc
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,93 @@
using System.Collections.Generic;
using Photon.VR.Player;
using UnityEngine;
namespace BlockSpace.Gamemodes
{
[DisallowMultipleComponent]
public sealed class GamemodeZoneManager : MonoBehaviour
{
private readonly List<GamemodeZone> zones = new List<GamemodeZone>();
public static GamemodeZoneManager Instance { get; private set; }
public GamemodeZone CurrentZone { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Object.Destroy(base.gameObject);
}
else
{
Instance = this;
}
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
private void Update()
{
RefreshZonesFromPlayerPositions();
}
internal void RegisterZone(GamemodeZone zone)
{
if (!(zone == null) && !zones.Contains(zone))
{
zones.Add(zone);
}
}
internal void UnregisterZone(GamemodeZone zone)
{
if (!(zone == null))
{
zones.Remove(zone);
if (CurrentZone == zone)
{
CurrentZone = null;
}
}
}
private void RefreshZonesFromPlayerPositions()
{
if (zones.Count == 0)
{
CurrentZone = null;
return;
}
PhotonVRPlayer[] array = Object.FindObjectsByType<PhotonVRPlayer>(FindObjectsInactive.Include);
PhotonVRPlayer photonVRPlayer = null;
foreach (PhotonVRPlayer photonVRPlayer2 in array)
{
if (!(photonVRPlayer2 == null) && photonVRPlayer2.photonView != null && photonVRPlayer2.photonView.IsMine)
{
photonVRPlayer = photonVRPlayer2;
}
}
CurrentZone = null;
Vector3 worldPosition = ((photonVRPlayer != null) ? GamemodeZone.GetPlayerZonePosition(photonVRPlayer) : Vector3.zero);
for (int j = 0; j < zones.Count; j++)
{
GamemodeZone gamemodeZone = zones[j];
if (!(gamemodeZone == null))
{
gamemodeZone.RefreshPlayersInside(array);
if (photonVRPlayer != null && CurrentZone == null && gamemodeZone.ContainsWorldPosition(worldPosition))
{
CurrentZone = gamemodeZone;
}
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e68242b621309c19393fc31f439cd809
timeCreated: 1780325055
licenseType: Free
MonoImporter:
serializedVersion: 2
externalObjects: {}
defaultReferences: []
executionOrder: 0
icon: {fileID: 0}
userData:
assetBundleName:
assetBundleVariant: