EVERYTHING
This commit is contained in:
@@ -0,0 +1,407 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user