feat(debug-panel): add realtime 3D cutscenes & timelines section with full preset dropdowns and API endpoint
This commit is contained in:
1 parent
dbcb8addf6
commit
23c80e9941
4 files changed
+103
-15
No files matched your search
Binary file not shown.
@@ -260,6 +260,19 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") +
|
|||||||
` LX6.Manager.GameInputManager.SetEnableInput(gPanelId.COMMON_BLACK_TRANSITION)\n` +
|
` LX6.Manager.GameInputManager.SetEnableInput(gPanelId.COMMON_BLACK_TRANSITION)\n` +
|
||||||
` end\n` +
|
` end\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` +
|
` elseif string.sub(cmd, 1, 14) == "PLAY_CUTSCENE:" then\n` +
|
||||||
` local arg = string.sub(cmd, 15)\n` +
|
` local arg = string.sub(cmd, 15)\n` +
|
||||||
` local numId = tonumber(arg)\n` +
|
` local numId = tonumber(arg)\n` +
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub
|
|||||||
await WriteJsonAsync(ctx, await SpawnEnemyAsync(await ReadBodyAsync(ctx.Request, token), token), token);
|
await WriteJsonAsync(ctx, await SpawnEnemyAsync(await ReadBodyAsync(ctx.Request, token), token), token);
|
||||||
else if (method == "POST" && path == "/api/cutscene/play")
|
else if (method == "POST" && path == "/api/cutscene/play")
|
||||||
await WriteJsonAsync(ctx, await PlayCutsceneAsync(await ReadBodyAsync(ctx.Request, token), token), token);
|
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
|
else
|
||||||
await WriteJsonAsync(ctx, new { ok = false, error = "unknown route" }, token, 404);
|
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 };
|
return new { ok = true, id = cutsceneId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<object> 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<string> ReadBodyAsync(HttpListenerRequest request, CancellationToken token)
|
private static async Task<string> ReadBodyAsync(HttpListenerRequest request, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!request.HasEntityBody)
|
if (!request.HasEntityBody)
|
||||||
|
|||||||
@@ -156,25 +156,57 @@
|
|||||||
<div class="muted" style="margin-top:6px">Spawns enemies near the player for live combat testing. Use with active weapon & combat sandbox.</div>
|
<div class="muted" style="margin-top:6px">Spawns enemies near the player for live combat testing. Use with active weapon & combat sandbox.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card" style="border-color:#4caf50">
|
||||||
<h2>Cutscene & Video Player</h2>
|
<h2 style="color:#81c784">🎬 Realtime 3D Cutscenes & Timelines (In-Engine 3D)</h2>
|
||||||
<div style="margin-bottom:6px">
|
<div style="margin-bottom:8px">
|
||||||
Cutscene / Video:
|
Preset:
|
||||||
<select id="cutscenepreset" onchange="onCutscenePresetChange()">
|
<select id="timelinepreset" onchange="onTimelinePresetChange()" style="min-width:380px">
|
||||||
<option value="24100064">Gacha 3D Box Opening [24100064]</option>
|
<option value="SwitchChar_tafei_01">🐰 Taffy 3D Entrance (คัทซีนเปิดตัว Taffy ถือค้อนกระโดดลงมา)</option>
|
||||||
<option value="24100001">Prologue Story Intro [24100001]</option>
|
<option value="SwitchChar_nanzhujue_03">🗡️ Male MC 3D Entrance (คัทซีนเปิดตัวเอกชาย)</option>
|
||||||
<option value="24100002">Xinqi Main City Arrival [24100002]</option>
|
<option value="SwitchChar_saimo_05">⚡ Seymour 3D Showcase (คัทซีนเปิดตัว Seymour)</option>
|
||||||
<option value="24100010">Metro Transit Cutscene [24100010]</option>
|
<option value="SwitchChar_character_13">🌸 Rin 3D Entrance (คัทซีนเปิดตัว Rin)</option>
|
||||||
<option value="24100020">Boss Appearance Timeline [24100020]</option>
|
<option value="SwitchChar_character_19">🎸 Bansy 3D Entrance (คัทซีนเปิดตัว Bansy)</option>
|
||||||
<option value="custom">Custom ID / Timeline...</option>
|
<option value="SwitchChar_character_20">🐺 Garm 3D Entrance (คัทซีนเปิดตัว Garm)</option>
|
||||||
|
<option value="SwitchChar_character_22">🎯 Aileen 3D Showcase (คัทซีนเปิดตัว Aileen)</option>
|
||||||
|
<option value="SwitchChar_common_12_SA1">💥 Ground Slam Action (คัทซีนกระโดดกระแทกพื้น)</option>
|
||||||
|
<option value="SwitchChar_common_20a_SA1">🪂 High Altitude Combat Drop A (ดิ่งพสุธาสู่สนามรบ A)</option>
|
||||||
|
<option value="SwitchChar_common_20b_SA1">🪂 High Altitude Combat Drop B (ดิ่งพสุธาสู่สนามรบ B)</option>
|
||||||
|
<option value="SwitchChar_common_24">🤸 Acrobatic Flip Entrance (คัทซีนตีลังกาลงสู่พื้น)</option>
|
||||||
|
<option value="SwitchChar_common_25_SA1">🌪️ Heavy Impact Shockwave (คัทซีนกระแทกพื้นคลื่นพลัง)</option>
|
||||||
|
<option value="TL_SEYM_010_Q010_S01">📜 Seymour Story Quest Scene 1 (คัทซีนเควสต์เนื้อเรื่อง Seymour)</option>
|
||||||
|
<option value="TL_NewMilk">🥛 Milk Delivery Mission (คัทซีนภารกิจส่งนม)</option>
|
||||||
|
<option value="custom">Custom Timeline Name...</option>
|
||||||
</select>
|
</select>
|
||||||
ID: <input id="cutsceneId" type="text" style="width:140px" value="24100064">
|
</div>
|
||||||
|
<div style="margin-bottom:8px">
|
||||||
|
Name / Path: <input id="timelineName" type="text" style="width:280px" value="SwitchChar_tafei_01">
|
||||||
|
<button style="background:#2e7d32;margin-left:8px" onclick="playTimeline()">▶️ Play 3D Timeline (Realtime)</button>
|
||||||
|
<button class="ghost" onclick="unstuckBlackScreen()">⏹️ Stop / Reset</button>
|
||||||
|
</div>
|
||||||
|
<div class="muted">💡 เรนเดอร์โมเดล 3D และมุมกล้อง Cinemachine สดๆ ใน Engine ไม่ต้องพึ่งพาไฟล์วิดีโอ หมดปัญหาไฟล์วิดีโอหาย</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>🎞️ Video Player Cutscenes (Prerendered Video)</h2>
|
||||||
|
<div style="margin-bottom:6px">
|
||||||
|
Video Preset:
|
||||||
|
<select id="cutscenepreset" onchange="onCutscenePresetChange()" style="min-width:320px">
|
||||||
|
<option value="24100064">🎁 Gacha 3D Box Opening (เปิดกล่องกาชา 3D) [24100064]</option>
|
||||||
|
<option value="24100001">📖 Prologue Story Intro (คัทซีนเปิดเรื่อง) [24100001]</option>
|
||||||
|
<option value="24100002">🏙️ Xinqi Main City Arrival (เดินทางเข้าเมืองซินฉี) [24100002]</option>
|
||||||
|
<option value="24100010">🚇 Metro Transit Cutscene (ขึ้นรถไฟใต้ดิน) [24100010]</option>
|
||||||
|
<option value="24100020">👹 Boss Appearance (บอสเปิดตัว) [24100020]</option>
|
||||||
|
<option value="24107001">👤 Character Dossier 01 [24107001]</option>
|
||||||
|
<option value="24107003">🐰 Taffy Animated Dossier [24107003]</option>
|
||||||
|
<option value="custom">Custom Video ID...</option>
|
||||||
|
</select>
|
||||||
|
ID: <input id="cutsceneId" type="text" style="width:120px" value="24100064">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button style="background:#7b1fa2" onclick="playCutscene()">Play Cutscene / Video</button>
|
<button style="background:#7b1fa2" onclick="playCutscene()">▶️ Play Video</button>
|
||||||
<button class="ghost" onclick="unstuckBlackScreen()">Skip / Stop Video</button>
|
<button class="ghost" onclick="unstuckBlackScreen()">⏹️ Skip / Close</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted" style="margin-top:6px">Directly triggers in-game video playback or timeline cutscene on the client.</div>
|
<div class="muted" style="margin-top:6px">เล่นไฟล์วิดีโอผ่าน S_VIDEO_PLAYER_PANEL (หากคลิปไม่มีในตัวเกมอาจขึ้นจอดำ)</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -419,6 +451,21 @@ async function spawnEnemy() {
|
|||||||
log('spawn enemy: ' + JSON.stringify(r));
|
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() {
|
function onCutscenePresetChange() {
|
||||||
const sel = document.getElementById('cutscenepreset').value;
|
const sel = document.getElementById('cutscenepreset').value;
|
||||||
if (sel !== 'custom') {
|
if (sel !== 'custom') {
|
||||||
|
|||||||
Reference in new issue
Block a user