Fix : 후원 시스템 최종 패치

This commit is contained in:
user 2026-06-03 02:00:33 +09:00
parent 27ac11e2b4
commit 4780ff764f
2 changed files with 102 additions and 49 deletions

View File

@ -42,6 +42,9 @@ namespace Streamingle
[Tooltip("Time to stay after reaching target before deactivating")] [Tooltip("Time to stay after reaching target before deactivating")]
public float postArrivalLifetime = 3f; public float postArrivalLifetime = 3f;
[Tooltip("Time spent shrinking out before disappearing (0 = pop instantly)")]
public float shrinkDuration = 0.4f;
// Trajectory state // Trajectory state
private Vector3 startPosition; private Vector3 startPosition;
private Vector3 targetPosition; private Vector3 targetPosition;
@ -64,11 +67,16 @@ namespace Streamingle
private float lifetime; private float lifetime;
private float spawnTime; private float spawnTime;
// Despawn (shrink-out)
private Vector3 originalScale;
private bool isDespawning = false;
void Awake() void Awake()
{ {
rb = GetComponent<Rigidbody>(); rb = GetComponent<Rigidbody>();
audioSource = GetComponent<AudioSource>(); audioSource = GetComponent<AudioSource>();
colliders = GetComponents<Collider>(); colliders = GetComponents<Collider>();
originalScale = transform.localScale;
} }
/// <summary> /// <summary>
@ -89,7 +97,11 @@ namespace Streamingle
this.hasArrived = false; this.hasArrived = false;
this.hasCollided = false; this.hasCollided = false;
this.hasPlayedHitFeedback = false; this.hasPlayedHitFeedback = false;
this.isDespawning = false;
this.isFlying = true; this.isFlying = true;
// Restore scale in case this pooled object was shrunk out last time
transform.localScale = originalScale;
this.currentTime = 0f; this.currentTime = 0f;
this.flightTime = flightDuration; this.flightTime = flightDuration;
this.startPosition = transform.position; this.startPosition = transform.position;
@ -124,7 +136,7 @@ namespace Streamingle
// Check lifetime // Check lifetime
if (lifetime > 0 && Time.time - spawnTime > lifetime) if (lifetime > 0 && Time.time - spawnTime > lifetime)
{ {
Deactivate(); BeginDespawn();
return; return;
} }
@ -195,8 +207,8 @@ namespace Streamingle
rb.angularVelocity = rotationAxis * rotationSpeed * Mathf.Deg2Rad; rb.angularVelocity = rotationAxis * rotationSpeed * Mathf.Deg2Rad;
} }
// Schedule deactivation (object falls if no collision) // Schedule despawn (object falls if no collision, then shrinks out)
Invoke(nameof(Deactivate), postArrivalLifetime); Invoke(nameof(BeginDespawn), postArrivalLifetime);
} }
void OnCollisionEnter(Collision collision) void OnCollisionEnter(Collision collision)
@ -286,13 +298,56 @@ namespace Streamingle
} }
} }
/// <summary>
/// Begin the despawn sequence. Shrinks the object out over shrinkDuration
/// before actually deactivating, so it doesn't pop out abruptly.
/// </summary>
private void BeginDespawn()
{
if (isDespawning) return;
isDespawning = true;
isFlying = false;
// Cancel the scheduled BeginDespawn so it can't fire again mid-shrink.
CancelInvoke();
if (shrinkDuration > 0f && gameObject.activeInHierarchy)
{
StartCoroutine(ShrinkAndDeactivate());
}
else
{
Deactivate();
}
}
private System.Collections.IEnumerator ShrinkAndDeactivate()
{
Vector3 from = transform.localScale;
float t = 0f;
while (t < shrinkDuration)
{
t += Time.deltaTime;
float k = Mathf.Clamp01(t / shrinkDuration);
transform.localScale = Vector3.Lerp(from, Vector3.zero, k);
yield return null;
}
transform.localScale = Vector3.zero;
Deactivate();
}
private void Deactivate() private void Deactivate()
{ {
CancelInvoke(); CancelInvoke();
StopAllCoroutines();
isFlying = false; isFlying = false;
hasArrived = false; hasArrived = false;
hasCollided = false; hasCollided = false;
hasPlayedHitFeedback = false; hasPlayedHitFeedback = false;
isDespawning = false;
// Restore scale so the pooled/recycled object starts at full size next time.
transform.localScale = originalScale;
if (launcher != null && launcher.usePooling) if (launcher != null && launcher.usePooling)
{ {
@ -313,6 +368,7 @@ namespace Streamingle
hasArrived = false; hasArrived = false;
hasCollided = false; hasCollided = false;
hasPlayedHitFeedback = false; hasPlayedHitFeedback = false;
isDespawning = false;
} }
} }
} }

View File

@ -234,8 +234,16 @@ namespace WefLab
public string ttsLang = "ko"; public string ttsLang = "ko";
[Header("Debug")] [Header("Debug")]
[Tooltip("Log the full raw JSON payload of every received socket event. Use this to inspect real platform donation/chat structures.")] [Tooltip("Show detailed WefLab logs in the console (raw payloads, every socket event, ping/pong, handshakes, platform-feed keepalives, chat). " +
public bool verboseRawLog = true; "Off keeps the console clean - only donations, connection status and errors are logged.")]
public bool verboseLog = false;
/// <summary>Console log that only fires when verboseLog (detail logging) is enabled.</summary>
private void VLog(string message)
{
if (verboseLog)
UnityEngine.Debug.Log(message);
}
// Queue state (visible in Inspector for debugging) // Queue state (visible in Inspector for debugging)
[Header("Queue Status (Read Only)")] [Header("Queue Status (Read Only)")]
@ -641,13 +649,13 @@ namespace WefLab
return; return;
} }
Debug.Log($"[WefLab] ({conn.Label}) Connecting to: {conn.wsUrl}"); VLog($"[WefLab] ({conn.Label}) Connecting to: {conn.wsUrl}");
conn.ws = new WebSocket(conn.wsUrl); conn.ws = new WebSocket(conn.wsUrl);
// Capture conn in the handlers so each message knows which socket it came from // Capture conn in the handlers so each message knows which socket it came from
conn.ws.OnOpen += (sender, e) => conn.ws.OnOpen += (sender, e) =>
EnqueueMainThreadAction(() => Debug.Log($"[WefLab] ({conn.Label}) WebSocket connection opened")); EnqueueMainThreadAction(() => VLog($"[WefLab] ({conn.Label}) WebSocket connection opened"));
conn.ws.OnMessage += (sender, e) => conn.ws.OnMessage += (sender, e) =>
{ {
@ -666,7 +674,7 @@ namespace WefLab
conn.ws.OnClose += (sender, e) => conn.ws.OnClose += (sender, e) =>
EnqueueMainThreadAction(() => EnqueueMainThreadAction(() =>
{ {
Debug.Log($"[WefLab] ({conn.Label}) WebSocket closed. Code: {e.Code}, Reason: {e.Reason}"); VLog($"[WefLab] ({conn.Label}) WebSocket closed. Code: {e.Code}, Reason: {e.Reason}");
conn.connected = false; conn.connected = false;
conn.engineSid = ""; conn.engineSid = "";
conn.socketSid = ""; conn.socketSid = "";
@ -752,7 +760,7 @@ namespace WefLab
break; break;
case "3": // Engine.IO PONG case "3": // Engine.IO PONG
Debug.Log($"[WefLab] ({conn.Label}) Received PONG"); VLog($"[WefLab] ({conn.Label}) Received PONG");
break; break;
case "40": // Socket.IO CONNECT case "40": // Socket.IO CONNECT
@ -764,7 +772,7 @@ namespace WefLab
break; break;
default: default:
Debug.Log($"[WefLab] ({conn.Label}) Unknown message type: {messageType} | Data: {data}"); VLog($"[WefLab] ({conn.Label}) Unknown message type: {messageType} | Data: {data}");
break; break;
} }
} }
@ -786,8 +794,8 @@ namespace WefLab
UpdateAggregateState(); UpdateAggregateState();
Debug.Log($"[WefLab] ({conn.Label}) Engine.IO OPEN - SID: {conn.engineSid}, PingInterval: {pingInterval}s"); VLog($"[WefLab] ({conn.Label}) Engine.IO OPEN - SID: {conn.engineSid}, PingInterval: {pingInterval}s");
Debug.Log($"[WefLab] ({conn.Label}) Full handshake data: {payload}"); VLog($"[WefLab] ({conn.Label}) Full handshake data: {payload}");
// Send Socket.IO CONNECT // Send Socket.IO CONNECT
SendSocketConnect(conn); SendSocketConnect(conn);
@ -803,7 +811,7 @@ namespace WefLab
/// </summary> /// </summary>
private void HandlePing(PlatformConnection conn) private void HandlePing(PlatformConnection conn)
{ {
Debug.Log($"[WefLab] ({conn.Label}) Received PING, sending PONG"); VLog($"[WefLab] ({conn.Label}) Received PING, sending PONG");
SendMessage(conn, "3"); // Send PONG SendMessage(conn, "3"); // Send PONG
} }
@ -818,8 +826,8 @@ namespace WefLab
{ {
var connectData = JsonConvert.DeserializeObject<JObject>(payload); var connectData = JsonConvert.DeserializeObject<JObject>(payload);
conn.socketSid = connectData["sid"]?.ToString() ?? ""; conn.socketSid = connectData["sid"]?.ToString() ?? "";
Debug.Log($"[WefLab] ({conn.Label}) Socket.IO CONNECT - SID: {conn.socketSid}"); VLog($"[WefLab] ({conn.Label}) Socket.IO CONNECT - SID: {conn.socketSid}");
Debug.Log($"[WefLab] ({conn.Label}) Full connect data: {payload}"); VLog($"[WefLab] ({conn.Label}) Full connect data: {payload}");
} }
conn.connected = true; conn.connected = true;
@ -868,7 +876,7 @@ namespace WefLab
}; };
string fullMessage = "42" + JsonConvert.SerializeObject(new object[] { "msg", msg }); string fullMessage = "42" + JsonConvert.SerializeObject(new object[] { "msg", msg });
Debug.Log($"[WefLab] ({conn.Label}) Sending PLATFORM feed message (start={start})"); VLog($"[WefLab] ({conn.Label}) Sending PLATFORM feed message (start={start})");
SendMessage(conn, fullMessage); SendMessage(conn, fullMessage);
} }
@ -914,7 +922,7 @@ namespace WefLab
// The server also emits bare events like ["pong"] - ignore anything that isn't a "msg" payload // The server also emits bare events like ["pong"] - ignore anything that isn't a "msg" payload
if (eventName != "msg") if (eventName != "msg")
{ {
Debug.Log($"[WefLab] ({conn.Label}) Socket event '{eventName}' (ignored)"); VLog($"[WefLab] ({conn.Label}) Socket event '{eventName}' (ignored)");
return; return;
} }
@ -923,10 +931,10 @@ namespace WefLab
var envelope = eventArray[1] as JObject; var envelope = eventArray[1] as JObject;
Debug.Log($"[WefLab] ===== EVENT ({conn.Label}) name={eventName} type={envelope?["type"]} ====="); VLog($"[WefLab] ===== EVENT ({conn.Label}) name={eventName} type={envelope?["type"]} =====");
// Verbose: dump the full raw payload so real (non-test) platform structures can be verified // Verbose: dump the full raw payload so real (non-test) platform structures can be verified
if (verboseRawLog && envelope != null) if (verboseLog && envelope != null)
Debug.Log($"[WefLab] RAW ({conn.Label}):\n{envelope.ToString(Formatting.Indented)}"); Debug.Log($"[WefLab] RAW ({conn.Label}):\n{envelope.ToString(Formatting.Indented)}");
HandleMessageEnvelope(conn.platform, envelope); HandleMessageEnvelope(conn.platform, envelope);
@ -966,15 +974,16 @@ namespace WefLab
break; break;
default: default:
Debug.Log($"[WefLab] ({fallbackPlatform}) Ignored envelope type: '{envType}'"); VLog($"[WefLab] ({fallbackPlatform}) Ignored envelope type: '{envType}'");
break; break;
} }
} }
/// <summary> /// <summary>
/// Convert a platform's raw donation value to KRW. /// Convert a platform's raw donation value to KRW.
/// Prefers the server-provided currency.real (already KRW); otherwise applies the /// Unit semantics confirmed by live testing:
/// per-platform conversion derived from weflab's client (comm.donation.currency). /// - afreeca/soop/soopg/twitch: value is an item count (balloon/bit), 1 unit = 100 KRW (x100)
/// - naver(chzzk)/youtube/cime/extdona: value is already the KRW amount (x1, as-is)
/// </summary> /// </summary>
private static int ConvertToKrw(string platform, JObject data, out int rawValue) private static int ConvertToKrw(string platform, JObject data, out int rawValue)
{ {
@ -982,32 +991,16 @@ namespace WefLab
if (data["value"] != null) if (data["value"] != null)
int.TryParse(data["value"].ToString(), out rawValue); int.TryParse(data["value"].ToString(), out rawValue);
// 1) Trust server-provided KRW when present (naver/youtube/twitch send this)
if (data["currency"] is JObject currency && currency["real"] != null
&& int.TryParse(currency["real"].ToString(), out int realKrw))
{
return realKrw;
}
// 2) Per-platform fallback conversion
switch ((platform ?? "").ToLowerInvariant()) switch ((platform ?? "").ToLowerInvariant())
{ {
case "afreeca": case "afreeca":
case "soop": case "soop":
case "soopg": case "soopg":
return rawValue * 100; // 1 balloon / gem = 100 KRW
case "twitch": case "twitch":
return rawValue / 10; // bits -> KRW (approx; currency.real preferred) return rawValue * 100; // count (balloon / bit) -> KRW
case "naver": // chzzk cheese
case "chzzk":
case "youtube": // superchat
case "cime":
case "extdona":
return rawValue / 100; // raw value is in 1/100 KRW units
default: default:
// naver(chzzk), youtube, cime, extdona: value is already KRW
return rawValue; return rawValue;
} }
} }
@ -1021,19 +1014,23 @@ namespace WefLab
return; return;
string platform = data["platform"]?.ToString() ?? fallbackPlatform; string platform = data["platform"]?.ToString() ?? fallbackPlatform;
string subtype = data["subtype"]?.ToString() ?? ""; // Real alerts use "subtype"; dashboard test donations use "type" instead.
string subtype = data["subtype"]?.ToString();
if (string.IsNullOrEmpty(subtype))
subtype = data["type"]?.ToString() ?? "";
int amount = ConvertToKrw(platform, data, out int rawValue); int amount = ConvertToKrw(platform, data, out int rawValue);
// Skip non-monetary alerts (subscribe / membership / follow / emoticon, value = 0, ...) // Skip non-monetary alerts (subscribe / membership / follow / emoticon, value = 0, ...)
if (NonMonetarySubtypes.Contains(subtype) || amount <= 0) if (NonMonetarySubtypes.Contains(subtype) || amount <= 0)
{ {
Debug.Log($"[WefLab] ({platform}) Non-monetary alert ignored (subtype:'{subtype}', value:{rawValue}, krw:{amount})"); VLog($"[WefLab] ({platform}) Non-monetary alert ignored (subtype:'{subtype}', value:{rawValue}, krw:{amount})");
return; return;
} }
string message = data["msg"]?.ToString() ?? data["message"]?.ToString() ?? ""; string message = data["msg"]?.ToString() ?? data["message"]?.ToString() ?? "";
string donorName = data["name"]?.ToString() ?? data["id"]?.ToString() ?? "Anonymous"; // Real alerts use "name"; test donations use "uname".
string donorName = data["name"]?.ToString() ?? data["uname"]?.ToString() ?? data["id"]?.ToString() ?? "Anonymous";
string donorId = data["id"]?.ToString() ?? ""; string donorId = data["id"]?.ToString() ?? "";
long timestamp = data["time"]?.Value<long>() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); long timestamp = data["time"]?.Value<long>() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -1092,9 +1089,9 @@ namespace WefLab
}; };
if (isWarn) if (isWarn)
Debug.Log($"[WefLab] CHAT WARN ({platform}): {chat.warnType}"); VLog($"[WefLab] CHAT WARN ({platform}): {chat.warnType}");
else else
Debug.Log($"[WefLab] CHAT ({platform}): {chat.nickname}: {chat.message}" + VLog($"[WefLab] CHAT ({platform}): {chat.nickname}: {chat.message}" +
(chat.emoticons.Count > 0 ? $" [+{chat.emoticons.Count} emoticon]" : "")); (chat.emoticons.Count > 0 ? $" [+{chat.emoticons.Count} emoticon]" : ""));
onChatReceived?.Invoke(chat); onChatReceived?.Invoke(chat);
@ -1189,7 +1186,7 @@ namespace WefLab
// Drop duplicates that arrive on more than one socket within the window // Drop duplicates that arrive on more than one socket within the window
if (IsDuplicateDonation(donation)) if (IsDuplicateDonation(donation))
{ {
Debug.Log($"[WefLab] Duplicate donation ignored: {donation.donorName} {donation.amount} ({donation.platform})"); VLog($"[WefLab] Duplicate donation ignored: {donation.donorName} {donation.amount} ({donation.platform})");
return; return;
} }
@ -1207,7 +1204,7 @@ namespace WefLab
donationQueue.Enqueue(donation); donationQueue.Enqueue(donation);
queueCount = donationQueue.Count; queueCount = donationQueue.Count;
Debug.Log($"[WefLab] Donation queued. Queue size: {queueCount}"); VLog($"[WefLab] Donation queued. Queue size: {queueCount}");
// Start queue processor if not running // Start queue processor if not running
if (!isProcessingQueue) if (!isProcessingQueue)
@ -1518,7 +1515,7 @@ namespace WefLab
/// </summary> /// </summary>
private void SendSocketConnect(PlatformConnection conn) private void SendSocketConnect(PlatformConnection conn)
{ {
Debug.Log($"[WefLab] ({conn.Label}) Sending Socket.IO CONNECT (40)"); VLog($"[WefLab] ({conn.Label}) Sending Socket.IO CONNECT (40)");
SendMessage(conn, "40"); SendMessage(conn, "40");
} }
@ -1561,7 +1558,7 @@ namespace WefLab
string json = JsonConvert.SerializeObject(message); string json = JsonConvert.SerializeObject(message);
string fullMessage = "42" + json; string fullMessage = "42" + json;
Debug.Log($"[WefLab] ({conn.Label}) Sending JOIN message: {fullMessage}"); VLog($"[WefLab] ({conn.Label}) Sending JOIN message: {fullMessage}");
SendMessage(conn, fullMessage); SendMessage(conn, fullMessage);
} }