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")]
public float postArrivalLifetime = 3f;
[Tooltip("Time spent shrinking out before disappearing (0 = pop instantly)")]
public float shrinkDuration = 0.4f;
// Trajectory state
private Vector3 startPosition;
private Vector3 targetPosition;
@ -64,11 +67,16 @@ namespace Streamingle
private float lifetime;
private float spawnTime;
// Despawn (shrink-out)
private Vector3 originalScale;
private bool isDespawning = false;
void Awake()
{
rb = GetComponent<Rigidbody>();
audioSource = GetComponent<AudioSource>();
colliders = GetComponents<Collider>();
originalScale = transform.localScale;
}
/// <summary>
@ -89,7 +97,11 @@ namespace Streamingle
this.hasArrived = false;
this.hasCollided = false;
this.hasPlayedHitFeedback = false;
this.isDespawning = false;
this.isFlying = true;
// Restore scale in case this pooled object was shrunk out last time
transform.localScale = originalScale;
this.currentTime = 0f;
this.flightTime = flightDuration;
this.startPosition = transform.position;
@ -124,7 +136,7 @@ namespace Streamingle
// Check lifetime
if (lifetime > 0 && Time.time - spawnTime > lifetime)
{
Deactivate();
BeginDespawn();
return;
}
@ -195,8 +207,8 @@ namespace Streamingle
rb.angularVelocity = rotationAxis * rotationSpeed * Mathf.Deg2Rad;
}
// Schedule deactivation (object falls if no collision)
Invoke(nameof(Deactivate), postArrivalLifetime);
// Schedule despawn (object falls if no collision, then shrinks out)
Invoke(nameof(BeginDespawn), postArrivalLifetime);
}
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()
{
CancelInvoke();
StopAllCoroutines();
isFlying = false;
hasArrived = false;
hasCollided = 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)
{
@ -313,6 +368,7 @@ namespace Streamingle
hasArrived = false;
hasCollided = false;
hasPlayedHitFeedback = false;
isDespawning = false;
}
}
}

View File

@ -234,8 +234,16 @@ namespace WefLab
public string ttsLang = "ko";
[Header("Debug")]
[Tooltip("Log the full raw JSON payload of every received socket event. Use this to inspect real platform donation/chat structures.")]
public bool verboseRawLog = true;
[Tooltip("Show detailed WefLab logs in the console (raw payloads, every socket event, ping/pong, handshakes, platform-feed keepalives, chat). " +
"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)
[Header("Queue Status (Read Only)")]
@ -641,13 +649,13 @@ namespace WefLab
return;
}
Debug.Log($"[WefLab] ({conn.Label}) Connecting to: {conn.wsUrl}");
VLog($"[WefLab] ({conn.Label}) Connecting to: {conn.wsUrl}");
conn.ws = new WebSocket(conn.wsUrl);
// Capture conn in the handlers so each message knows which socket it came from
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) =>
{
@ -666,7 +674,7 @@ namespace WefLab
conn.ws.OnClose += (sender, e) =>
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.engineSid = "";
conn.socketSid = "";
@ -752,7 +760,7 @@ namespace WefLab
break;
case "3": // Engine.IO PONG
Debug.Log($"[WefLab] ({conn.Label}) Received PONG");
VLog($"[WefLab] ({conn.Label}) Received PONG");
break;
case "40": // Socket.IO CONNECT
@ -764,7 +772,7 @@ namespace WefLab
break;
default:
Debug.Log($"[WefLab] ({conn.Label}) Unknown message type: {messageType} | Data: {data}");
VLog($"[WefLab] ({conn.Label}) Unknown message type: {messageType} | Data: {data}");
break;
}
}
@ -786,8 +794,8 @@ namespace WefLab
UpdateAggregateState();
Debug.Log($"[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}) Engine.IO OPEN - SID: {conn.engineSid}, PingInterval: {pingInterval}s");
VLog($"[WefLab] ({conn.Label}) Full handshake data: {payload}");
// Send Socket.IO CONNECT
SendSocketConnect(conn);
@ -803,7 +811,7 @@ namespace WefLab
/// </summary>
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
}
@ -818,8 +826,8 @@ namespace WefLab
{
var connectData = JsonConvert.DeserializeObject<JObject>(payload);
conn.socketSid = connectData["sid"]?.ToString() ?? "";
Debug.Log($"[WefLab] ({conn.Label}) Socket.IO CONNECT - SID: {conn.socketSid}");
Debug.Log($"[WefLab] ({conn.Label}) Full connect data: {payload}");
VLog($"[WefLab] ({conn.Label}) Socket.IO CONNECT - SID: {conn.socketSid}");
VLog($"[WefLab] ({conn.Label}) Full connect data: {payload}");
}
conn.connected = true;
@ -868,7 +876,7 @@ namespace WefLab
};
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);
}
@ -914,7 +922,7 @@ namespace WefLab
// The server also emits bare events like ["pong"] - ignore anything that isn't a "msg" payload
if (eventName != "msg")
{
Debug.Log($"[WefLab] ({conn.Label}) Socket event '{eventName}' (ignored)");
VLog($"[WefLab] ({conn.Label}) Socket event '{eventName}' (ignored)");
return;
}
@ -923,10 +931,10 @@ namespace WefLab
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
if (verboseRawLog && envelope != null)
if (verboseLog && envelope != null)
Debug.Log($"[WefLab] RAW ({conn.Label}):\n{envelope.ToString(Formatting.Indented)}");
HandleMessageEnvelope(conn.platform, envelope);
@ -966,15 +974,16 @@ namespace WefLab
break;
default:
Debug.Log($"[WefLab] ({fallbackPlatform}) Ignored envelope type: '{envType}'");
VLog($"[WefLab] ({fallbackPlatform}) Ignored envelope type: '{envType}'");
break;
}
}
/// <summary>
/// Convert a platform's raw donation value to KRW.
/// Prefers the server-provided currency.real (already KRW); otherwise applies the
/// per-platform conversion derived from weflab's client (comm.donation.currency).
/// Unit semantics confirmed by live testing:
/// - 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>
private static int ConvertToKrw(string platform, JObject data, out int rawValue)
{
@ -982,32 +991,16 @@ namespace WefLab
if (data["value"] != null)
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())
{
case "afreeca":
case "soop":
case "soopg":
return rawValue * 100; // 1 balloon / gem = 100 KRW
case "twitch":
return rawValue / 10; // bits -> KRW (approx; currency.real preferred)
case "naver": // chzzk cheese
case "chzzk":
case "youtube": // superchat
case "cime":
case "extdona":
return rawValue / 100; // raw value is in 1/100 KRW units
return rawValue * 100; // count (balloon / bit) -> KRW
default:
// naver(chzzk), youtube, cime, extdona: value is already KRW
return rawValue;
}
}
@ -1021,19 +1014,23 @@ namespace WefLab
return;
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);
// Skip non-monetary alerts (subscribe / membership / follow / emoticon, value = 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;
}
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() ?? "";
long timestamp = data["time"]?.Value<long>() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -1092,9 +1089,9 @@ namespace WefLab
};
if (isWarn)
Debug.Log($"[WefLab] CHAT WARN ({platform}): {chat.warnType}");
VLog($"[WefLab] CHAT WARN ({platform}): {chat.warnType}");
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]" : ""));
onChatReceived?.Invoke(chat);
@ -1189,7 +1186,7 @@ namespace WefLab
// Drop duplicates that arrive on more than one socket within the window
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;
}
@ -1207,7 +1204,7 @@ namespace WefLab
donationQueue.Enqueue(donation);
queueCount = donationQueue.Count;
Debug.Log($"[WefLab] Donation queued. Queue size: {queueCount}");
VLog($"[WefLab] Donation queued. Queue size: {queueCount}");
// Start queue processor if not running
if (!isProcessingQueue)
@ -1518,7 +1515,7 @@ namespace WefLab
/// </summary>
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");
}
@ -1561,7 +1558,7 @@ namespace WefLab
string json = JsonConvert.SerializeObject(message);
string fullMessage = "42" + json;
Debug.Log($"[WefLab] ({conn.Label}) Sending JOIN message: {fullMessage}");
VLog($"[WefLab] ({conn.Label}) Sending JOIN message: {fullMessage}");
SendMessage(conn, fullMessage);
}