diff --git a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip index debb48f..014b575 100644 Binary files a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip and b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip differ 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') {