From fb429bd6db54a203bf764f6473023a4135fc8e62 Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Fri, 18 Sep 2026 01:22:10 +0700 Subject: [PATCH] feat(cutscene): expand 60+ cutscene catalog and fix in-engine timeline playback via native SwitchSpiritConfigId and Lua Fastpatch --- .../proxy/public/fastpatch_4229938.zip | Bin 10642 -> 11167 bytes .../tools/generate_client_config_patch.js | 120 ++++++++++++++---- Ananta.Server/Ananta.App/DebugApiServer.cs | 110 +++++++++++++--- .../Ananta.App/DebugPanel/index.html | 39 +++++- 4 files changed, 225 insertions(+), 44 deletions(-) diff --git a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip index 9763a138cf427410a67a19c20ba80f52300213b0..e79d75d06a23342a9b9984bb50c4d636cab167fd 100644 GIT binary patch delta 9509 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 DIk1cn delta 9003 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 REhaXTi7OBWh$sL6005-FULpVh diff --git a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js index b5a60d9..9858e6e 100644 --- a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js +++ b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js @@ -719,14 +719,48 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` 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` + +` local unit = gCS and gCS.MyPlayerManager and gCS.MyPlayerManager.PlayerUnit\n` + +` local myPos = (unit and unit.Position) or (UnityEngine and UnityEngine.Vector3 and UnityEngine.Vector3.zero)\n` + +` local myRot = (unit and unit.Facing and UnityEngine and UnityEngine.Vector3 and UnityEngine.Vector3(0, unit.Facing, 0)) or (UnityEngine and UnityEngine.Vector3 and UnityEngine.Vector3.zero)\n` + +` local numId = tonumber(arg)\n` + +` if numId and CS and CS.LX6 and CS.LX6.GUI and CS.LX6.GUI.SwitchTeleportManager and CS.LX6.GUI.SwitchTeleportManager.Instance then\n` + +` pcall(function() CS.LX6.GUI.SwitchTeleportManager.Instance:OnSyncSwitchSpiritConfigId(numId) end)\n` + +` end\n` + +` local played = false\n` + +` if gTimelineManager then\n` + +` local tlData = nil\n` + +` if gTimelineManager.Timeline_CreateTimelineData then\n` + +` tlData = gTimelineManager:Timeline_CreateTimelineData()\n` + +` if tlData and myPos then\n` + +` tlData.pos = myPos\n` + +` tlData.rot = myRot\n` + +` end\n` + +` end\n` + +` if gTimelineManager.Timeline_LoadAndPlay then\n` + +` pcall(function() gTimelineManager:Timeline_LoadAndPlay(arg, tlData) played = true end)\n` + +` end\n` + +` if not played and gTimelineManager.PlayTimeline then\n` + +` pcall(function() gTimelineManager:PlayTimeline(arg) played = true end)\n` + +` end\n` + +` end\n` + +` if not played and CS and CS.LX6 and CS.LX6.TimelineScript and CS.LX6.TimelineScript.CutsceneManager and CS.LX6.TimelineScript.CutsceneManager.Instance then\n` + +` local cm = CS.LX6.TimelineScript.CutsceneManager.Instance\n` + +` local tlData = cm:CreateTimelineData()\n` + +` if tlData and myPos then\n` + +` tlData.pos = myPos\n` + +` tlData.rot = myRot\n` + +` end\n` + +` pcall(function() cm:LoadAndPlay(arg, tlData) played = true end)\n` + +` end\n` + +` if not played and gCS and gCS.LuaUtils and gCS.LuaUtils.PlayTimeline then\n` + +` pcall(function() gCS.LuaUtils.PlayTimeline(arg) end)\n` + +` end\n` + +` local tip = "Playing Timeline: " .. tostring(arg)\n` + +` if gDisplayMessageMgr and gDisplayMessageMgr.ShowMessageContent then\n` + +` gDisplayMessageMgr:ShowMessageContent(tip)\n` + +` end\n` + +` if gCS and gCS.MessageTipsMgr and gCS.MessageTipsMgr.ShowMessageTips then\n` + +` gCS.MessageTipsMgr:ShowMessageTips(tip)\n` + ` end\n` + ` end)\n` + ` elseif string.sub(cmd, 1, 14) == "PLAY_CUTSCENE:" then\n` + @@ -735,10 +769,8 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` pcall(function()\n` + ` if numId and gPanelManager and gPanelId and gPanelId.S_VIDEO_PLAYER_PANEL then\n` + ` gPanelManager:OpenWindow(gPanelId.S_VIDEO_PLAYER_PANEL, { videoId = numId })\n` + -` elseif gTimelineManager and gTimelineManager.Timeline_LoadAndPlay then\n` + -` gTimelineManager:Timeline_LoadAndPlay(arg, nil)\n` + -` elseif gVideoManager and gVideoManager.PlayVideo then\n` + -` gVideoManager:PlayVideo(numId or arg)\n` + +` else\n` + +` ProcessDebugCommand("PLAY_TIMELINE:" .. tostring(arg))\n` + ` end\n` + ` end)\n` + ` elseif string.sub(cmd, 1, 11) == "SPAWN_ENEMY" then\n` + @@ -964,23 +996,35 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + `local function HookNoticeTable(tbl)\n` + ` if not tbl or type(tbl) ~= "table" or tbl._cmdHooked then return end\n` + ` tbl._cmdHooked = true\n` + -` local oldSync = tbl.SyncNotice\n` + -` local newSync = function(...)\n` + -` local allArgs = {...}\n` + -` for i = 1, #allArgs do\n` + -` if type(allArgs[i]) == "string" and string.sub(allArgs[i], 1, 4) == "CMD:" then\n` + -` ProcessDebugCommand(string.sub(allArgs[i], 5))\n` + -` return\n` + +` local function makeCmdWrapper(origFunc)\n` + +` return function(...)\n` + +` local allArgs = {...}\n` + +` for i = 1, #allArgs do\n` + +` local arg = allArgs[i]\n` + +` if type(arg) == "string" and string.sub(arg, 1, 4) == "CMD:" then\n` + +` ProcessDebugCommand(string.sub(arg, 5))\n` + +` return\n` + +` elseif type(arg) == "table" then\n` + +` for _, val in pairs(arg) do\n` + +` if type(val) == "string" and string.sub(val, 1, 4) == "CMD:" then\n` + +` ProcessDebugCommand(string.sub(val, 5))\n` + +` return\n` + +` end\n` + +` end\n` + +` end\n` + ` end\n` + +` if origFunc then return origFunc(...) end\n` + ` end\n` + -` if oldSync then return oldSync(...) end\n` + ` end\n` + -` if tbl._meta and type(tbl._meta) == "table" then\n` + -` tbl._meta.SyncNotice = newSync\n` + +` if tbl.SyncNotice then\n` + +` tbl.SyncNotice = makeCmdWrapper(tbl.SyncNotice)\n` + +` end\n` + +` if tbl.SyncShowMessage then\n` + +` tbl.SyncShowMessage = makeCmdWrapper(tbl.SyncShowMessage)\n` + +` end\n` + +` if tbl.SyncShowTipMessage then\n` + +` tbl.SyncShowTipMessage = makeCmdWrapper(tbl.SyncShowTipMessage)\n` + ` end\n` + -` tbl.SyncNotice = nil\n` + -` tbl.SyncNotice = newSync\n` + -` rawset(tbl, "SyncNotice", newSync)\n` + `end\n\n` + `local function HookDisplayMessageMgr()\n` + ` if gDisplayMessageMgr and not gDisplayMessageMgr._cmdHooked then\n` + @@ -1000,9 +1044,33 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` if package and package.loaded then\n` + ` HookNoticeTable(package.loaded["LX6/Service/MasterToClientImpl"])\n` + ` HookNoticeTable(package.loaded["LX6/Service/GameToClientImpl"])\n` + +` HookNoticeTable(package.loaded["LX6/Service/GameSceneToClientImpl"])\n` + +` local autoLoaded = package.loaded["LuaGen/AutoGen/RPCDeserializeAuto"]\n` + +` if autoLoaded and autoLoaded.midToReader then\n` + +` autoLoaded.midToReader[45418774] = function(reader) return reader:ReadString() end\n` + +` autoLoaded.midToName[45418774] = "SyncNotice"\n` + +` end\n` + +` local baseLoaded = package.loaded["LX6/Service/RPCDeserializeBase"]\n` + +` if baseLoaded and not baseLoaded._cmdHooked then\n` + +` baseLoaded._cmdHooked = true\n` + +` local oldDisp = baseLoaded.Dispatcher\n` + +` baseLoaded.Dispatcher = function(ctx, br, mid)\n` + +` if mid == 45418774 then\n` + +` pcall(function()\n` + +` local str = br:ReadString()\n` + +` if type(str) == "string" and string.sub(str, 1, 4) == "CMD:" then\n` + +` ProcessDebugCommand(string.sub(str, 5))\n` + +` end\n` + +` end)\n` + +` return true\n` + +` end\n` + +` return oldDisp(ctx, br, mid)\n` + +` end\n` + +` end\n` + ` end\n` + ` if MasterToClientImpl then HookNoticeTable(MasterToClientImpl) end\n` + ` if GameToClientImpl then HookNoticeTable(GameToClientImpl) end\n` + +` if GameSceneToClientImpl then HookNoticeTable(GameSceneToClientImpl) end\n` + ` HookDisplayMessageMgr()\n` + ` end)\n` + `end\n\n` + @@ -1010,7 +1078,7 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + `require = function(mod)\n` + ` local res = oldRequire(mod)\n` + ` pcall(ApplyAllGlobalHooks)\n` + -` if mod == "LX6/Service/MasterToClientImpl" or mod == "LX6/Service/GameToClientImpl" then\n` + +` if mod == "LX6/Service/MasterToClientImpl" or mod == "LX6/Service/GameToClientImpl" or mod == "LX6/Service/GameSceneToClientImpl" then\n` + ` if type(res) == "table" then HookNoticeTable(res) end\n` + ` end\n` + ` pcall(HookAllNotices)\n` + diff --git a/Ananta.Server/Ananta.App/DebugApiServer.cs b/Ananta.Server/Ananta.App/DebugApiServer.cs index e6cf12f..c6cbead 100644 --- a/Ananta.Server/Ananta.App/DebugApiServer.cs +++ b/Ananta.Server/Ananta.App/DebugApiServer.cs @@ -1206,30 +1206,119 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub return new { ok = true, id = cutsceneId }; } + private sealed record TimelineItem(string id, uint configId, string name, string category, float x = 0, float y = 0, float z = 0); + + private static readonly TimelineItem[] AllTimelines = + [ + // Story & Quests + new("TL_SEYM_010_Q010_S01", 2030u, "Seymour Story Quest: Chapter 1 S01 (黑客与发布会 - แฮกเกอร์กับงานแถลงข่าว)", "story", 3418.28f, 295.23f, 1415.67f), + new("TL_TAF_010_Q010_S04", 0u, "Taffy Story Quest: Chapter 1 S04 (เควสต์เนื้อเรื่องทาฟี่)", "story"), + new("TL_HEISTJ", 0u, "Bank Heist Cinematics Part J (ฉากภารกิจปล้นธนาคาร J)", "story"), + new("TL_HEISTZ", 0u, "Bank Heist Cinematics Part Z (ฉากภารกิจปล้นธนาคาร Z)", "story"), + new("TL_TGS_MQ_S02", 0u, "Tokyo Game Show Main Quest S02 (ฉากเปิดตัวงาน TGS)", "story"), + new("TL_SQ_020_Q005_S02", 0u, "Side Quest Special Cinematic (เควสต์ย่อย 020 S02)", "story"), + new("TL_Bernard", 0u, "Bernard Character Cinematic (คัทซีนเบอร์นาร์ด)", "story"), + new("TL_Erin_mm_hunhun", 0u, "Erin vs Thugs Street Scene (เอรินปะทะกลุ่มอันธพาล)", "story"), + new("xsb_phase3_part3", 0u, "Boss Encounter Phase 3 Part 3 (คัทซีนบอสเฟส 3)", "story"), + + // Character Entrances & Customs + new("SwitchChar_tafei_01", 48u, "Taffy Skyscraper Apartment (ทาฟี่ โดดจากตึกอพาร์ตเมนต์)", "character", 1613.81f, 21.96f, 1565.65f), + new("SwitchChar_dila_03", 62u, "Dila Combat Rooftop Action (ดิลา แอ็กชันคอมแบทบนดาดฟ้า)", "character", 484.28f, 15.03f, 2018.23f), + new("SwitchChar_saimo_05", 100u, "Seymour Skyline Overview (เซย์มัวร์ ชมวิวตึกระฟ้าสไตล์คูล)", "character", 2198.68f, 92.08f, 1807.14f), + new("SwitchChar_lixi_04", 59u, "Richie Motorcycle Battle (ริชชี่ ซิ่งมอเตอร์ไซค์สู้แก๊งสเตอร์)", "character", 1647.65f, 0f, 1578.67f), + new("SwitchChar_nanzhujue_02", 61u, "Male MC Sports Car Arrival (พระเอก นั่งสปอร์ตคาร์มาส่ง)", "character", 3142.67f, 0f, 2522.78f), + new("SwitchChar_nanzhujue_03", 115u, "Male MC Rolls-Royce Arrival (พระเอก ก้าวลงจาก Rolls-Royce)", "character", 2468.71f, -0.1f, 1371.11f), + new("SwitchChar_character_2", 82u, "Taffy Park Bench Nap (ทาฟี่ นอนหลับบนม้านั่งสวนสาธารณะ)", "character", 403.61f, 0f, 2117.54f), + new("SwitchChar_character_3", 83u, "Aileen at Maid Cafe (ไอลีน สนทนากับเมดในคาเฟ่)", "character", 2086.22f, -0.11f, 2362.25f), + new("SwitchChar_character_4", 84u, "Bansy Street Graffiti (แบนซี พ่นสเปรย์กราฟิตี้ข้างถนน)", "character", 522.15f, 0f, 2083.76f), + new("SwitchChar_character_6", 85u, "Dila Scrolling Phone on Bench (ดิลา นั่งเล่นโทรศัพท์มือถือ)", "character", 395.99f, 0f, 2111.99f), + new("SwitchChar_character_7", 86u, "Enomi Drinking Milk (เอโนมิ ยืนดื่มนมกล่อง)", "character", 1606.63f, -0.1f, 1222.73f), + new("SwitchChar_character_13", 89u, "Garm Baseball Batting (การ์ม ซ้อมหวดลูกเบสบอลสุดเท่)", "character", 2862.36f, -0.67f, 2761.5f), + new("SwitchChar_character_14", 94u, "Richie Catching Criminal (ริชชี่ บุกจับคนร้ายคาหนังคาเขา)", "character", 3913.1f, -0.14f, 626.1f), + new("SwitchChar_character_16", 90u, "MC Receiving Street Flyer (พระเอก รับใบปลิวจากคนแจก)", "character", 2597.6f, 0.14f, 1156.3f), + new("SwitchChar_character_22", 113u, "Lykaia Peeling Apple in Mansion (ไลคาเอีย ปอกแอปเปิ้ลในคฤหาสน์หรู)", "character", 3204.52f, 104.46f, 959.45f), + new("SwitchChar_character_23", 114u, "Baijing High-Speed Overtake (ไป๋จิ้ง ซิ่งรถแซงโค้งความเร็วสูง)", "character", 3008.4f, -0.1f, 1614.91f), + new("SwitchChar_character_19", 302u, "Richie Official PV Cinematic (ริชชี่ ช็อตไฮไลท์จากเทรลเลอร์ PV)", "character", 3008.4f, -0.1f, 1614.91f), + new("SwitchChar_character_20", 303u, "Continuous One-Shot Camera (มุมกล้อง Long-Take ช็อตเดียวจบ)", "character", 3008.4f, -0.1f, 1614.91f), + new("SwitchChar_common_01", 1u, "Cherry Blossom Ave Passenger Drop (ไลคาเอีย ขับรถมาส่งที่ถนนซากุระ)", "character", 3008.4f, -0.1f, 1614.91f), + new("SwitchChar_common_07", 44u, "Taffy Driving Sports Car (ทาฟี่ ขับรถซิ่งบนถนนใหญ่)", "character", 3008.4f, -0.1f, 1614.91f), + new("SwitchChar_common_09", 49u, "Exiting City Taxi (เปิดประตูก้าวลงจากรถแท็กซี่)", "character", 3008.3f, 0f, 1616.5f), + new("SwitchChar_common_27", 87u, "Street Basketball Game (ดังก์บาสเกตบอลสตรีท)", "character", 410.72f, 0f, 2108.59f), + + // Urban Life & Interactive + new("SwitchChar_common_04", 4u, "Exiting Mahjong Parlor (เดินออกจากร้านไพ่นกกระจอก)", "urban", 1031.7f, 0f, 1888.7f), + new("SwitchChar_common_21", 63u, "Exiting Store & Stretching (เดินออกจากร้านพร้อมบิดขี้เกียจ)", "urban", 1030.2f, 0f, 1889.73f), + new("SwitchChar_common_22", 96u, "Exiting Store Fist Pump (เดินออกจากร้านพร้อมชูกำปั้นมั่นใจ)", "urban", 2062.63f, 0f, 2459f), + new("SwitchChar_common_23", 106u, "Exiting Restaurant Patting Belly (เดินออกจากร้านอาหารลูบท้องอิ่ม)", "urban", 3074.69f, 0f, 2185.89f), + new("SwitchChar_common_24", 97u, "Walking & Hanging up Scam Call (คุยโทรศัพท์สายหลอกลวงแล้วส่ายหัวตัดสาย)", "urban", 1727f, 9.64f, 1968f), + new("SwitchChar_common_10", 28u, "Cyberpunk Nightclub Dancing 1 (แดนซ์ในผับไซเบอร์พังก์ 1)", "urban", 2875.39f, 0.02f, 2203.92f), + new("SwitchChar_common_11", 31u, "Wild Cyberpunk Nightclub Dancing 2 (แดนซ์ในผับไซเบอร์พังก์ 2)", "urban", 558.19f, -21.14f, 1933.61f), + new("SwitchChar_common_14", 54u, "Petting Street Cat (นั่งยองๆ ลูบหัวแมวจรจัด)", "urban", 2832.1f, 0.05f, 1880.63f), + new("SwitchChar_common_15", 55u, "Walking the Dog (พาสุนัขเดินเล่นรอบเมือง)", "urban", 2809.91f, 0.07f, 1940.67f), + new("SwitchChar_common_16", 57u, "Taking Landscape Photos (ยกกล้องถ่ายรูปวิวทิวทัศน์เมือง)", "urban", 589f, 0f, 1937.8f), + new("SwitchChar_common_17", 60u, "Street Smartphone Selfie (หยิบมือถือขึ้นมาถ่ายรูปเซลฟี่)", "urban", 706.56f, 3.41f, 2097.07f), + new("SwitchChar_common_18", 107u, "Photo with Citizen Fan (ถ่ายรูปร่วมกับแฟนคลับชาวเมือง)", "urban", 1229.34f, 0f, 1237.58f), + new("SwitchChar_common_26", 105u, "Polite Photo with NPC (ถ่ายรูปคู่กับ NPC อย่างสุภาพ)", "urban", 1889.75f, 0f, 2211.68f), + new("SwitchChar_common_19", 56u, "Tossing Can in Trash Bin (โยนกระป๋องลงถังขยะลงเป๊ะ)", "urban", 1717.25f, 0.08f, 2271.25f), + new("SwitchChar_common_25", 99u, "Tossing Can in Trash Bin Missed (โยนกระป๋องไม่ลงถังขยะ)", "urban", 1717.25f, 0.08f, 2271.25f), + new("SwitchChar_character_1", 88u, "Buying Drink at Vending Machine (หยอดเหรียญกดตู้เครื่องดื่ม)", "urban", 3010.08f, 0f, 1656.21f), + new("SwitchChar_common_20", 81u, "Defeat Gang & Escape (ถล่มแก๊งข้างถนนแล้วกระโดดหนี)", "urban", 3984.66f, -0.14f, 636.22f), + new("SwitchChar_common_20a", 111u, "Defeat Gold Gang & Escape (ถล่มแก๊งทองดำแล้วสปีดหนี)", "urban", 3984.66f, -0.14f, 636.22f), + new("SwitchChar_common_20b", 112u, "Defeat TV-Head Gang & Escape (ถล่มแก๊งหัวทีวีแล้วสเก็ตหนี)", "urban", 3984.66f, -0.14f, 636.22f), + + // Transit & World Transitions + new("loading_plane01", 0u, "Airport Airplane Takeoff Cinematic (คัทซีนเครื่องบินขึ้นจากสนามบิน)", "transit"), + new("loading_plane02", 0u, "Airport Airplane Landing Cinematic (คัทซีนเครื่องบินร่อนลงจอด)", "transit"), + new("loading_metro_end_1", 0u, "Subway Metro Train Arrival (คัทซีนรถไฟใต้ดินเทียบชานชาลา)", "transit"), + new("Loading_bus_end", 0u, "Metropolis City Bus Arrival (คัทซีนรถเมล์เทศบาลเทียบป้าย)", "transit"), + new("loading_elevator02", 0u, "Skyscraper Glass Elevator (คัทซีนลิฟต์แก้วตึกระฟ้าความเร็วสูง)", "transit"), + new("loading_indoor_in01", 0u, "Cinematic Building Entry A (คัทซีนเดินเข้าอาคารแบบสมจริง A)", "transit"), + new("loading_indoor_out01", 0u, "Cinematic Building Exit A (คัทซีนเดินออกจากอาคารสู่ถนนใหญ่ A)", "transit"), + ]; + 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"; + string timeline = "TL_SEYM_010_Q010_S01"; + uint explicitConfigId = 0; 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(); + if (root.TryGetProperty("configId", out var pc)) explicitConfigId = pc.GetUInt32(); } catch (Exception ex) { return new { ok = false, error = $"bad request: {ex.Message}" }; } + var match = Array.Find(AllTimelines, t => t.id.Equals(timeline, StringComparison.OrdinalIgnoreCase)); + uint configId = explicitConfigId != 0 ? explicitConfigId : (match?.configId ?? 0); + + // 1. If configured in SwitchSpiritConfig, dispatch native SwitchSpiritConfigId RPC + if (configId != 0) + { + var state = GameRouter.GetStateIfExists(session); + var pos = (match is not null && (match.x != 0 || match.y != 0 || match.z != 0)) + ? new Vec3(match.x, match.y, match.z) + : (state is not null && state.HasLastReportedPlayerTransform ? state.LastReportedPlayerPosition : new Vec3(0, 0, 0)); + + var payload = WorldCodec.SwitchSpiritConfigId(configId, pos); + await session.NotifyAsync(MethodId.SyncSwitchSpiritConfigId, UxSerializer.Serialize(payload), token); + session.Log.Info($"[DEBUG-API] sent native SwitchSpiritConfigId configId={configId} timeline={timeline}"); + } + + // 2. Dispatch Lua Fastpatch notice 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 }; + session.Log.Info($"[DEBUG-API] play realtime timeline={timeline} configId={configId}"); + return new { ok = true, timeline, configId }; } private async Task ToggleClothesAsync(CancellationToken token) @@ -1250,20 +1339,7 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub return new { ok = true, - items = new[] - { - new { id = "TL_SEYM_010_Q010_S01", name = "Seymour Story Quest: Chapter 1 S01 (เนื้อเรื่องหลัก)" }, - new { id = "SwitchChar_tafei_01", name = "Taffy Cinematic Arrival (ทาฟี่ โดดจากตึก)" }, - new { id = "SwitchChar_dila_03", name = "Dila Combat Cinematic (ดิลา แอกชันสตรีท)" }, - new { id = "SwitchChar_saimo_05", name = "Seymour Street Arrival (เซย์มัวร์ สไตล์คูล)" }, - new { id = "SwitchChar_lixi_04", name = "Richie Motorcycle Rush (ริชชี่ ซิ่งมอเตอร์ไซค์)" }, - new { id = "SwitchChar_nanzhujue_02", name = "Male MC Arrival A (พระเอก สลับตัว A)" }, - new { id = "SwitchChar_nanzhujue_03", name = "Male MC Arrival B (พระเอก สลับตัว B)" }, - new { id = "SwitchChar_common_01", name = "Metropolis Skyline Overview (มุมกล้องเมือง 1)" }, - new { id = "SwitchChar_common_04", name = "Street Parkour Traverse (มุมกล้องฟรีรันนิง 4)" }, - new { id = "SwitchChar_common_10", name = "Night Neon Lights (มุมกล้องวิวกลางคืน 10)" }, - new { id = "SwitchChar_common_27", name = "Dynamic Roof Drop (แอกชันดรอป 27)" }, - } + items = AllTimelines }; } diff --git a/Ananta.Server/Ananta.App/DebugPanel/index.html b/Ananta.Server/Ananta.App/DebugPanel/index.html index 49c2427..385fabc 100644 --- a/Ananta.Server/Ananta.App/DebugPanel/index.html +++ b/Ananta.Server/Ananta.App/DebugPanel/index.html @@ -692,13 +692,49 @@ async function toggleClothes() { log('toggle clothes: ' + JSON.stringify(r)); } +async function loadCutsceneCatalog() { + const sel = document.getElementById('cutsceneSelect'); + if (!sel) return; + try { + const res = await api('/api/cutscene/catalog'); + if (res && res.items && res.items.length > 0) { + const groups = { + story: { label: '🎬 เนื้อเรื่องหลัก & เควสต์ (Main Story & Quests)', items: [] }, + character: { label: '🌟 แอนิเมชันเปิดตัวตัวละคร (Character Entrances & Specials)', items: [] }, + urban: { label: '🏙️ ฉากชีวิตเมือง & กิจกรรม (Urban Life & Activities)', items: [] }, + transit: { label: '✈️ ทรานซิชันโลก & ยานพาหนะ (World & Vehicle Transitions)', items: [] } + }; + res.items.forEach(it => { + const cat = groups[it.category] ? it.category : 'character'; + groups[cat].items.push(it); + }); + let html = ''; + for (const k in groups) { + const g = groups[k]; + if (g.items.length > 0) { + html += ``; + g.items.forEach(it => { + html += ``; + }); + html += ``; + } + } + sel.innerHTML = html; + } + } catch (err) { + console.error('Failed to load cutscene catalog:', err); + } +} + async function playSelectedCutscene() { const sel = document.getElementById('cutsceneSelect'); + const opt = sel ? sel.options[sel.selectedIndex] : null; const name = sel ? sel.value : 'TL_SEYM_010_Q010_S01'; + const configId = opt ? parseInt(opt.getAttribute('data-config-id') || '0', 10) : 0; const r = await api('/api/timeline/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name }) + body: JSON.stringify({ name, configId }) }); log('play cutscene: ' + JSON.stringify(r)); } @@ -742,6 +778,7 @@ async function setCustomWeather() { loadFleet(); loadScenes(); loadNpcCatalog(); +loadCutsceneCatalog(); refresh(); setInterval(refresh, 2000); document.getElementById('timeslider').addEventListener('input', e => {