From 23c80e9941b71e98e4758aba4b9319fcc84a393e Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Tue, 15 Sep 2026 23:33:46 +0700 Subject: [PATCH] feat(debug-panel): add realtime 3D cutscenes & timelines section with full preset dropdowns and API endpoint --- .../proxy/public/fastpatch_4229938.zip | Bin 4239 -> 4295 bytes .../tools/generate_client_config_patch.js | 13 +++ Ananta.Server/Ananta.App/DebugApiServer.cs | 28 +++++++ .../Ananta.App/DebugPanel/index.html | 77 ++++++++++++++---- 4 files changed, 103 insertions(+), 15 deletions(-) diff --git a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip index debb48f707c73899ba4f506f60cfcb9c76323ba3..014b5751302924def2f7fadf892792b37566f6b0 100644 GIT binary patch delta 2606 zcmV+}3eokCA;%#MP)h>@6aWAK2ml1Au?$@S5(KC(UAc8j0+R^<0FEsH04V^I><=q{ z?OJVb+c*;b?q5OVzSwuJwoTi;Yk=TjD^8-@$T1?P-9^zeXlZN{p-3%Bxpj-)Z$FTf zY`yul*#!pRcGcHg_2{47; zoS(mY_xc~L7cbBajv@F=G(!Y4oKhu!yXXdo3yg56$P!S(IYrY|f~D*t%pxT54AcrO zIDe8(t3`Q;De}=J`u4rm@||GCLUf5{DGL?hto;P5E?Q!mLZ^igLjK1mSbaj5mew+~ zB*c`q*Ig{5{*f7)`yeHrgO3@WgD@t{2TNee>6N>LbLDXSd)zPrFRkwM6ln!zJ1)T+jr5JA)! zQ;qv-hXq%>?mtos#c07LNb32}qIajLi=xF+5uYBhq0;A+E=D-uFY zg_}ynYrga}uyoFH)-!)=0Zr9v3?e41!33-ZUlIZJ01PREX%x$>bmnCb^^%0H zdX(N!KE?D_*Vn8R?y8S}?!;)z%VVv&mD?RpjyR`gklKYCnMi<21v7(Q7qKluOc}Yw z(u%QZuf-lFuv8)UmjAwXoj-3F*}BQ`q{fA$r4OL4@zPAGM@T<5We78CneB;FWU$5sPlV~E zRd0z2trjAM#T-=5vJ0tO<@b`r#VlCx&qcvFNq#`uql9M?R)lal^m~LJ7nw1BvdhtJ zkts7cL|(x(onLbit`|bP_|4-;FaD$~tj$g|mc-CYwpQPNVzc$5C&ySmU2`!dQ3T9g zU^dV99QF0Wv&~++rY$GK+9?YM z_H0vd(c4FR-VMxR*A1Mzw=WBiP7n%`s5-^D`}OB$suMt%3cU)ijomJqCFnzj?ft4w zl@K~tcu81)NFE^JLNz*wlq5bM5mwX>?56A z8OVIsNaiOFWq#^d<|hv3u-QJV9rHr*$ex`6o9p?1w@z{%K=M?C>oet=ldR+&T^xHL z6fAHA;sX|OYNzSryS2@Fr|!1>CU|lP5Lqds;Qk=})jm5HV9&A?C|C4COpXu9X%TtG zfSeGIKP4RIGehMxaU^&M((V&n9>bL#~a?OrOh3boMw5#*gkVlm1=L{UvZ827{sPvo>)nKyrBZDGr~4Pyn!E*qa(I zTnbo>1!j^MX^haWS-&=C&U+F8FAFV_F3g!_K~1|^jM=G|zC0O<7nNR|VDjF(8r)At zcfH>R50g=EIJn!gYA;H(R(v2V;-A|)g_V7O*I;R$W*TcZk6!WFxk=4Tu-$z3-uLf^ zHjb$j#>ww6zI& z24!@YGbK13uM%)H+Bq}9t;;U<)d%|?OH#Ei2B7kfDM_l@$5~#-f(bHNgnzdgE9OIJeBIOXh zMg9Yo78ffiwg)Ix!Xc`+u{UV`0Z_VskK@1{oDNFM$I{p{U#U4u<0*);jr)LWdvB`M zA6JX-6JUpw;x77zmU=2Nqq6pW{{e*mxk*==7mYndiUE@$Y_Xs;hpw61)(c#qAI9Ig z#a}S$eHcy#!-4->Gy0NOU6h=6QSy2N!dTyPY1gE_?sU;v`{TbaKK-q8@lW%A@Yl`5 zSrxCqAYPRbo4o4i!M%C;?&Zt3e}7l$5aKu~ai1IR^C}xp?UjHv-)qD7>hL0dQ2FRZ z(LfL3fYOn(wzDyt5a{7_t+jiL*iBqEB70v8{y@~i)$ZA9%eEdHle9fQY{=mTrTG+G56dyg=E8KQY4!sUH__Dobq3P)8=c7ytkeD zBGtO|&@;5$qs3@sG~>47UU4fZu82%1UgcNe<|3;-cnA>xrJ)zUHN)u}`EiPry=A5 zs4rc)bxQ)12><|&EtA?0NgxEMFI{n8KlCyL0046e001-q0000000000006duISZ2= Q3pSH^5D*5v4*&oF017SZqW}N^ delta 2523 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 diff --git a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js index 6a5d220..7b13b57 100644 --- a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js +++ b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js @@ -260,6 +260,19 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` LX6.Manager.GameInputManager.SetEnableInput(gPanelId.COMMON_BLACK_TRANSITION)\n` + ` end\n` + ` end)\n` + +` elseif string.sub(cmd, 1, 14) == "PLAY_TIMELINE:" then\n` + +` local arg = string.sub(cmd, 15)\n` + +` pcall(function()\n` + +` if gTimelineManager and gTimelineManager.Timeline_LoadAndPlay then\n` + +` gTimelineManager:Timeline_LoadAndPlay(arg, nil)\n` + +` elseif gTimelineManager and gTimelineManager.PlayTimeline then\n` + +` gTimelineManager:PlayTimeline(arg)\n` + +` elseif gDramaManager and gDramaManager.PlayTimeline then\n` + +` gDramaManager:PlayTimeline(arg)\n` + +` elseif gCS and gCS.LuaUtils and gCS.LuaUtils.PlayTimeline then\n` + +` gCS.LuaUtils.PlayTimeline(arg)\n` + +` end\n` + +` end)\n` + ` elseif string.sub(cmd, 1, 14) == "PLAY_CUTSCENE:" then\n` + ` local arg = string.sub(cmd, 15)\n` + ` local numId = tonumber(arg)\n` + diff --git a/Ananta.Server/Ananta.App/DebugApiServer.cs b/Ananta.Server/Ananta.App/DebugApiServer.cs index 92d5eaf..a0f2132 100644 --- a/Ananta.Server/Ananta.App/DebugApiServer.cs +++ b/Ananta.Server/Ananta.App/DebugApiServer.cs @@ -128,6 +128,8 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub await WriteJsonAsync(ctx, await SpawnEnemyAsync(await ReadBodyAsync(ctx.Request, token), token), token); else if (method == "POST" && path == "/api/cutscene/play") await WriteJsonAsync(ctx, await PlayCutsceneAsync(await ReadBodyAsync(ctx.Request, token), token), token); + else if (method == "POST" && path == "/api/timeline/play") + await WriteJsonAsync(ctx, await PlayTimelineAsync(await ReadBodyAsync(ctx.Request, token), token), token); else await WriteJsonAsync(ctx, new { ok = false, error = "unknown route" }, token, 404); } @@ -1004,6 +1006,32 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub return new { ok = true, id = cutsceneId }; } + private async Task PlayTimelineAsync(string json, CancellationToken token) + { + var session = hub.Current; + if (session is null) + return new { ok = false, error = "no live game session (is the client in the world?)" }; + + string timeline = "SwitchChar_tafei_01"; + try + { + using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(json) ? "{}" : json); + var root = doc.RootElement; + if (root.TryGetProperty("name", out var pn)) timeline = pn.GetString() ?? pn.GetRawText(); + else if (root.TryGetProperty("id", out var pi)) timeline = pi.GetString() ?? pi.GetRawText(); + } + catch (Exception ex) + { + return new { ok = false, error = $"bad request: {ex.Message}" }; + } + + var cmd = $"CMD:PLAY_TIMELINE:{timeline}"; + await session.NotifyAsync(MethodId.SyncNotice, UxSerializer.Serialize(cmd), token); + + session.Log.Info($"[DEBUG-API] play realtime timeline={timeline}"); + return new { ok = true, timeline }; + } + private static async Task ReadBodyAsync(HttpListenerRequest request, CancellationToken token) { if (!request.HasEntityBody) diff --git a/Ananta.Server/Ananta.App/DebugPanel/index.html b/Ananta.Server/Ananta.App/DebugPanel/index.html index 640de78..a57e0fb 100644 --- a/Ananta.Server/Ananta.App/DebugPanel/index.html +++ b/Ananta.Server/Ananta.App/DebugPanel/index.html @@ -156,25 +156,57 @@
Spawns enemies near the player for live combat testing. Use with active weapon & combat sandbox.
-
-

Cutscene & Video Player

-
- Cutscene / Video: - + + + + + + + + + + + + + + + - ID: +
+
+ Name / Path: + + +
+
💡 เรนเดอร์โมเดล 3D และมุมกล้อง Cinemachine สดๆ ใน Engine ไม่ต้องพึ่งพาไฟล์วิดีโอ หมดปัญหาไฟล์วิดีโอหาย
+
+ +
+

🎞️ Video Player Cutscenes (Prerendered Video)

+
+ Video Preset: + + ID:
- - + +
-
Directly triggers in-game video playback or timeline cutscene on the client.
+
เล่นไฟล์วิดีโอผ่าน S_VIDEO_PLAYER_PANEL (หากคลิปไม่มีในตัวเกมอาจขึ้นจอดำ)
@@ -419,6 +451,21 @@ async function spawnEnemy() { log('spawn enemy: ' + JSON.stringify(r)); } +function onTimelinePresetChange() { + const sel = document.getElementById('timelinepreset').value; + if (sel !== 'custom') { + document.getElementById('timelineName').value = sel; + } +} + +async function playTimeline() { + const body = { + name: document.getElementById('timelineName').value || 'SwitchChar_tafei_01' + }; + const r = await api('/api/timeline/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); + log('play timeline: ' + JSON.stringify(r)); +} + function onCutscenePresetChange() { const sel = document.getElementById('cutscenepreset').value; if (sel !== 'custom') {