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

408 lines
11 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BlockSpace.Account;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
namespace BlockSpace.Voxels
{
[DisallowMultipleComponent]
public sealed class RandomLoginCodeButton : MonoBehaviour
{
[Serializable]
private sealed class LinkStatusRequest
{
public string code;
}
[Serializable]
private sealed class LinkStatusResponse
{
public bool ok;
public bool exists;
public bool claimed;
public bool expired;
public string code;
public string playerId;
public DiscordInfo discord;
}
[Serializable]
private sealed class DiscordInfo
{
public string id;
public string username;
public string globalName;
public string displayName;
}
private const float ButtonPressCooldown = 0.2f;
private const float ButtonShrinkDuration = 0.08f;
private const float ButtonSettleDuration = 0.12f;
private const float ButtonPressedScale = 0.84f;
private const float ButtonOvershootScale = 1.04f;
private const string ButtonThockResourcePath = "Thock";
private const string DefaultCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
[Header("Button")]
[SerializeField]
private Collider buttonCollider;
[SerializeField]
private VRVoxelBrush voxelBrush;
[Header("Code Output")]
[SerializeField]
private TMP_Text codeText;
[SerializeField]
private int codeLength = 6;
[SerializeField]
private string allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
[SerializeField]
private bool generateOnStart;
[Header("Discord Link (optional)")]
[SerializeField]
private bool pollForDiscordLink = true;
[SerializeField]
private string backendBaseUrl = "https://blockspacebackend-production.up.railway.app";
[SerializeField]
private float pollIntervalSeconds = 2f;
[SerializeField]
private float pollTimeoutSeconds = 180f;
[Header("Audio")]
[SerializeField]
private AudioClip buttonThockClip;
private readonly Dictionary<Transform, Vector3> buttonBaseScales = new Dictionary<Transform, Vector3>();
private readonly Dictionary<Transform, Coroutine> buttonAnimationCoroutines = new Dictionary<Transform, Coroutine>();
private bool insidePrevious;
private float nextButtonPressTime;
private string currentCode = "";
private Coroutine pollCoroutine;
public string CurrentCode => currentCode;
private void Awake()
{
ResolveReferences();
EnsureButtonAudioAssigned();
if (generateOnStart)
{
GenerateAndShowCode();
}
}
private void OnDisable()
{
StopAllCoroutines();
ResetAllButtonScales();
}
private void Update()
{
ResolveReferences();
HandleButton();
}
private void ResolveReferences()
{
if (buttonCollider == null)
{
buttonCollider = GetComponent<Collider>();
}
if (voxelBrush == null)
{
voxelBrush = ((VRVoxelBrush.Instance != null) ? VRVoxelBrush.Instance : UnityEngine.Object.FindObjectOfType<VRVoxelBrush>());
}
if (codeText == null)
{
codeText = GetComponentInChildren<TMP_Text>(includeInactive: true);
}
}
private void HandleButton()
{
bool flag = IsHandInside(buttonCollider, (voxelBrush != null) ? voxelBrush.LeftHandAnchor : null) || IsHandInside(buttonCollider, (voxelBrush != null) ? voxelBrush.RightHandAnchor : null);
if (flag && !insidePrevious)
{
PlayButtonEnterAnimation(buttonCollider);
if (Time.unscaledTime >= nextButtonPressTime)
{
nextButtonPressTime = Time.unscaledTime + 0.2f;
PlayButtonClickFeedback(buttonCollider);
GenerateAndShowCode();
}
}
insidePrevious = flag;
}
public void GenerateAndShowCode()
{
currentCode = GenerateCode();
if (codeText != null)
{
codeText.text = currentCode;
}
else
{
Debug.LogWarning("[RandomLoginCodeButton] No TMP_Text assigned.");
}
Debug.Log("[RandomLoginCodeButton] Generated code: " + currentCode);
if (pollForDiscordLink)
{
if (pollCoroutine != null)
{
StopCoroutine(pollCoroutine);
pollCoroutine = null;
}
pollCoroutine = StartCoroutine(PollDiscordLink(currentCode));
}
}
private string GenerateCode()
{
int num = Mathf.Max(6, codeLength);
string text = (string.IsNullOrEmpty(allowedCharacters) ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" : allowedCharacters.ToUpperInvariant());
char[] array = new char[num];
for (int i = 0; i < array.Length; i++)
{
int index = UnityEngine.Random.Range(0, text.Length);
array[i] = text[index];
}
return new string(array);
}
private IEnumerator PollDiscordLink(string code)
{
if (string.IsNullOrWhiteSpace(code))
{
yield break;
}
string baseUrl = (backendBaseUrl ?? string.Empty).Trim();
if (string.IsNullOrEmpty(baseUrl))
{
Debug.LogWarning("[RandomLoginCodeButton] No backendBaseUrl set; cannot poll link status.");
yield break;
}
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - start <= Mathf.Max(1f, pollTimeoutSeconds))
{
string url = baseUrl.TrimEnd('/') + "/api/link-codes/status";
string s = JsonUtility.ToJson(new LinkStatusRequest
{
code = code
});
using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
request.uploadHandler = new UploadHandlerRaw(bytes);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
LinkStatusResponse linkStatusResponse = null;
try
{
linkStatusResponse = JsonUtility.FromJson<LinkStatusResponse>(request.downloadHandler.text);
}
catch
{
}
if (linkStatusResponse != null && linkStatusResponse.ok && linkStatusResponse.exists && linkStatusResponse.claimed)
{
string text = linkStatusResponse.playerId ?? string.Empty;
string text2 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.id : string.Empty);
string text3 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.displayName : string.Empty);
if (string.IsNullOrEmpty(text3))
{
text3 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.globalName : string.Empty);
}
if (string.IsNullOrEmpty(text3))
{
text3 = ((linkStatusResponse.discord != null) ? linkStatusResponse.discord.username : string.Empty);
}
string userFolderId = BlockSpaceAccountPrefs.GetUserFolderId();
BlockSpaceAccountPrefs.SetLinkedAccount(text, text2, text3);
TryMigrateAvatarPayload(userFolderId, text);
if (!string.IsNullOrEmpty(text3))
{
PlayerPrefs.SetString("Username", text3);
PlayerPrefs.Save();
if (PhotonNetwork.LocalPlayer != null)
{
PhotonNetwork.LocalPlayer.NickName = text3;
}
}
Debug.Log("[RandomLoginCodeButton] Linked to Discord user '" + text3 + "' (" + text2 + ").");
pollCoroutine = null;
yield break;
}
}
else if (request.result != UnityWebRequest.Result.ProtocolError)
{
Debug.LogWarning($"[RandomLoginCodeButton] Link poll failed ({request.result}): {request.error}");
}
}
yield return new WaitForSecondsRealtime(Mathf.Clamp(pollIntervalSeconds, 0.25f, 30f));
}
Debug.LogWarning("[RandomLoginCodeButton] Link poll timed out; generate a new code if needed.");
pollCoroutine = null;
}
private static void TryMigrateAvatarPayload(string fromUserId, string toUserId)
{
if (string.IsNullOrEmpty(fromUserId) || string.IsNullOrEmpty(toUserId) || fromUserId == toUserId)
{
return;
}
try
{
string text = "avatar_payload.json";
string text2 = Path.Combine(Application.persistentDataPath, "BlockSpace", "Users", fromUserId, text);
string text3 = Path.Combine(Application.persistentDataPath, "BlockSpace", "Users", toUserId, text);
if (File.Exists(text2) && !File.Exists(text3))
{
string directoryName = Path.GetDirectoryName(text3);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(directoryName);
}
File.Copy(text2, text3, overwrite: false);
}
}
catch (Exception ex)
{
Debug.LogWarning("[RandomLoginCodeButton] Avatar migration failed: " + ex.Message);
}
}
private void PlayButtonEnterAnimation(Collider button)
{
if (!(button == null))
{
Transform transform = button.transform;
if (!buttonBaseScales.TryGetValue(transform, out var value))
{
value = transform.localScale;
buttonBaseScales.Add(transform, value);
}
if (buttonAnimationCoroutines.TryGetValue(transform, out var value2) && value2 != null)
{
StopCoroutine(value2);
}
transform.localScale = value;
buttonAnimationCoroutines[transform] = StartCoroutine(AnimateButtonScale(transform, value));
}
}
private void PlayButtonClickFeedback(Collider button)
{
if (!(buttonThockClip == null) && !(button == null))
{
AudioSource.PlayClipAtPoint(buttonThockClip, button.bounds.center, 1f);
}
}
private IEnumerator AnimateButtonScale(Transform buttonTransform, Vector3 baseScale)
{
Vector3 pressedScale = baseScale * 0.84f;
Vector3 overshootScale = baseScale * 1.04f;
float elapsed = 0f;
while (elapsed < 0.08f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float t = Mathf.Clamp01(elapsed / 0.08f);
buttonTransform.localScale = Vector3.LerpUnclamped(baseScale, pressedScale, t);
yield return null;
}
elapsed = 0f;
while (elapsed < 0.12f)
{
if (buttonTransform == null)
{
yield break;
}
elapsed += Time.unscaledDeltaTime;
float num = Mathf.Clamp01(elapsed / 0.12f);
float num2 = num * num * (3f - 2f * num);
Vector3 localScale = ((num2 < 0.5f) ? Vector3.LerpUnclamped(pressedScale, overshootScale, num2 / 0.5f) : Vector3.LerpUnclamped(overshootScale, baseScale, (num2 - 0.5f) / 0.5f));
buttonTransform.localScale = localScale;
yield return null;
}
if (buttonTransform != null)
{
buttonTransform.localScale = baseScale;
buttonAnimationCoroutines[buttonTransform] = null;
}
}
private void ResetAllButtonScales()
{
foreach (KeyValuePair<Transform, Vector3> buttonBaseScale in buttonBaseScales)
{
if (buttonBaseScale.Key != null)
{
buttonBaseScale.Key.localScale = buttonBaseScale.Value;
}
}
buttonAnimationCoroutines.Clear();
}
private void EnsureButtonAudioAssigned()
{
if (buttonThockClip == null)
{
buttonThockClip = Resources.Load<AudioClip>("Thock");
}
}
private static bool IsHandInside(Collider button, Transform handAnchor)
{
if (button == null || handAnchor == null)
{
return false;
}
Vector3 position = handAnchor.position;
return (button.ClosestPoint(position) - position).sqrMagnitude <= 1E-06f;
}
}
}