From 8470c3d413e1cff46496f82e53a1e0ae9fa9af26 Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Fri, 18 Sep 2026 16:11:34 +0700 Subject: [PATCH] fix(world): resolve traffic freeze, player vehicle kick, mob npcs, void falls, subway fast-travel, and timeline playback --- .../proxy/public/fastpatch_4229938.zip | Bin 11966 -> 12090 bytes .../tools/generate_client_config_patch.js | 38 ++-- Ananta.Server/Ananta.App/DebugApiServer.cs | 173 ++++++++++-------- .../Ananta.App/DebugPanel/index.html | 92 +++++++--- .../Ananta.Gameplay/Crowd/UrbanCrowdEngine.cs | 29 ++- .../Traffic/CityTrafficEngine.cs | 15 +- .../Game/GameRouter.VehicleStory.cs | 1 - .../Game/GameRouter.Vehicles.cs | 1 + .../Game/TrafficAndCrowdDriver.cs | 7 +- 9 files changed, 233 insertions(+), 123 deletions(-) diff --git a/Ananta.Proxy/proxy/public/fastpatch_4229938.zip b/Ananta.Proxy/proxy/public/fastpatch_4229938.zip index 61ef2224648ce34e36d84c8464086625ab55f61c..bd1503a4a5a8025244290723c821d82793b7d803 100644 GIT binary patch delta 10146 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 YNdnIclh7_40Wg#8E=LBmEdT%j0IIuW(EtDd diff --git a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js index 1f1f5b0..cb09b8a 100644 --- a/Ananta.Proxy/proxy/tools/generate_client_config_patch.js +++ b/Ananta.Proxy/proxy/tools/generate_client_config_patch.js @@ -718,6 +718,7 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` end)\n` + ` elseif string.sub(cmd, 1, 14) == "PLAY_TIMELINE:" then\n` + ` local arg = string.sub(cmd, 15)\n` + +` local tlName = string.lower(tostring(arg or ""))\n` + ` pcall(function()\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` + @@ -731,31 +732,46 @@ gameSwitchDefaults.map((name) => `M.${name} = true\n`).join("") + ` 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` + +` if tlData then\n` + +` if myPos then tlData.pos = myPos end\n` + +` if myRot then tlData.rot = myRot end\n` + +` pcall(function() tlData.ignoreLoadingState = true end)\n` + +` pcall(function() tlData.owner = unit end)\n` + +` pcall(function() tlData.loadFromLocal = true end)\n` + ` end\n` + ` end\n` + ` if gTimelineManager.Timeline_LoadAndPlay then\n` + -` pcall(function() gTimelineManager:Timeline_LoadAndPlay(arg, tlData) played = true end)\n` + +` pcall(function() gTimelineManager:Timeline_LoadAndPlay(tlName, tlData) played = true end)\n` + +` if not played and tlName ~= arg then\n` + +` pcall(function() gTimelineManager:Timeline_LoadAndPlay(arg, tlData) played = true end)\n` + +` end\n` + ` end\n` + ` if not played and gTimelineManager.PlayTimeline then\n` + -` pcall(function() gTimelineManager:PlayTimeline(arg) played = true end)\n` + +` pcall(function() gTimelineManager:PlayTimeline(tlName) played = true end)\n` + +` if not played and tlName ~= arg then\n` + +` pcall(function() gTimelineManager:PlayTimeline(arg) played = true end)\n` + +` 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` + +` if tlData then\n` + +` if myPos then tlData.pos = myPos end\n` + +` if myRot then tlData.rot = myRot end\n` + +` pcall(function() tlData.ignoreLoadingState = true end)\n` + +` pcall(function() tlData.owner = unit end)\n` + +` pcall(function() tlData.loadFromLocal = true end)\n` + +` end\n` + +` pcall(function() cm:LoadAndPlay(tlName, tlData) played = true end)\n` + +` if not played and tlName ~= arg then\n` + +` pcall(function() cm:LoadAndPlay(arg, tlData) played = true end)\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` + +` pcall(function() gCS.LuaUtils.PlayTimeline(tlName) end)\n` + ` end\n` + -` local tip = "Playing Timeline: " .. tostring(arg)\n` + +` local tip = "Playing Timeline: " .. tostring(tlName)\n` + ` if gDisplayMessageMgr and gDisplayMessageMgr.ShowMessageContent then\n` + ` gDisplayMessageMgr:ShowMessageContent(tip)\n` + ` end\n` + diff --git a/Ananta.Server/Ananta.App/DebugApiServer.cs b/Ananta.Server/Ananta.App/DebugApiServer.cs index fa4ca6a..3e90b87 100644 --- a/Ananta.Server/Ananta.App/DebugApiServer.cs +++ b/Ananta.Server/Ananta.App/DebugApiServer.cs @@ -1229,69 +1229,47 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub 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"), + new("tl_seymour01", 1288u, "Seymour Story Chapter 1 (赛默 - 计划外响应 01)", "story"), + new("tl_seymour02", 1289u, "Seymour Story Chapter 2 (赛默 - 计划外响应 02)", "story"), + new("tl_seymour03", 1290u, "Seymour Story Chapter 3 (赛默 - 计划外响应 03)", "story"), + new("tl_seym_010_q010_s01", 1270u, "Seymour Story Quest S01 (面具之下,监视之中)", "story", 3418.28f, 295.23f, 1415.67f), + new("tl_taffy01", 1353u, "Taffy Story: High-Speed Moment 01 (塔菲 - 极速时刻 01)", "story"), + new("tl_taffy02", 1354u, "Taffy Story: High-Speed Moment 02 (塔菲 - 极速时刻 02)", "story"), + new("tl_richiet01", 1208u, "Richie Story: Holiday Syndrome 01 (里希 - 假期综合征 01)", "story"), + new("ananta_openning", 3u, "Ananta Main Opening Cinematic (ฉากเปิดตัว Ananta โลกใหม่)", "story"), + new("alienfight", 2u, "Alien Combat Cinematic (ฉากต่อสู้เอเลี่ยนสุดอลังการ)", "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), + 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_22", 113u, "Lykaia Peeling Apple in Mansion (ไลคาเอีย ปอกแอปเปิ้ลในคฤหาสน์หรู)", "character", 3204.52f, 104.46f, 959.45f), + 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), - // 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"), + // Urban Life & Transitions + 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_10", 28u, "Cyberpunk Nightclub Dancing 1 (แดนซ์ในผับไซเบอร์พังก์ 1)", "urban", 2875.39f, 0.02f, 2203.92f), + 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_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_character_1", 88u, "Buying Drink at Vending Machine (หยอดเหรียญกดตู้เครื่องดื่ม)", "urban", 3010.08f, 0f, 1656.21f), ]; private async Task PlayTimelineAsync(string json, CancellationToken token) @@ -1300,15 +1278,15 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub if (session is null) return new { ok = false, error = "no live game session (is the client in the world?)" }; - string timeline = "TL_SEYM_010_Q010_S01"; - uint explicitConfigId = 0; + string timeline = "tl_seymour01"; + bool teleport = false; 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(); + if (root.TryGetProperty("teleport", out var pt)) teleport = pt.GetBoolean(); } catch (Exception ex) { @@ -1316,27 +1294,31 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub } var match = Array.Find(AllTimelines, t => t.id.Equals(timeline, StringComparison.OrdinalIgnoreCase)); - uint configId = explicitConfigId != 0 ? explicitConfigId : (match?.configId ?? 0); + if (match is not null) + timeline = match.id; - // 1. If configured in SwitchSpiritConfig, dispatch native SwitchSpiritConfigId RPC - if (configId != 0) + // If explicit teleport requested and coordinates exist: + if (teleport && match is not null && (match.x != 0 || match.y != 0 || match.z != 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}"); + ulong unitId = Profile.InitialUnitId; + if (session.Items.TryGetValue(GameRouter.WorldStateKey, out var raw) && raw is WorldEntryState state) + { + lock (state.SyncRoot) + { + if (state.ActiveSpiritUnitId != 0) + unitId = state.ActiveSpiritUnitId; + } + } + await SendPlayerTeleportAsync(session, unitId, match.x, match.y, match.z, 0, "timeline-teleport", token); + await Task.Delay(500, token); } - // 2. Dispatch Lua Fastpatch notice + // Dispatch Lua Fastpatch notice to play timeline directly var cmd = $"CMD:PLAY_TIMELINE:{timeline}"; await session.NotifyAsync(MethodId.SyncNotice, UxSerializer.Serialize(cmd), token); - session.Log.Info($"[DEBUG-API] play realtime timeline={timeline} configId={configId}"); - return new { ok = true, timeline, configId }; + session.Log.Info($"[DEBUG-API] play timeline={timeline}"); + return new { ok = true, timeline }; } private async Task ToggleClothesAsync(CancellationToken token) @@ -1361,6 +1343,8 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub }; } + private ulong _activeInstrumentEntityId = 0; + private async Task LaunchMinigameAsync(string json, CancellationToken token) { var session = hub.Current; @@ -1377,6 +1361,33 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub } catch { } + // Spawn instrument prop if musical minigame + if (game.Equals("PIANO", StringComparison.OrdinalIgnoreCase)) + { + if (_activeInstrumentEntityId != 0) + { + await GameRouter.DestroyDirectAsync(session, _activeInstrumentEntityId, "instrument-replace"); + _activeInstrumentEntityId = 0; + } + + var (hasPos, px, py, pz, pyaw, _) = TrafficAndCrowdDriver.Instance.GetPlayerPosition(session); + if (hasPos) + { + var yawRad = pyaw * MathF.PI / 180f; + var spawnX = px + MathF.Sin(yawRad) * 1.6f; + var spawnZ = pz + MathF.Cos(yawRad) * 1.6f; + var facingOpposite = (pyaw + 180f) % 360f; + + var res = await GameRouter.SpawnDirectAsync( + session, 80000370, new Vec3(spawnX, py, spawnZ), facingOpposite, rightOffset: 0f, sourceType: 2, reason: "piano-minigame"); + if (res.Ok) + { + _activeInstrumentEntityId = res.EntityId; + session.Log.Info($"[MINIGAME] spawned grand piano prop entity={_activeInstrumentEntityId} in front of player"); + } + } + } + var cmd = $"CMD:LAUNCH_MINIGAME:{game.ToUpperInvariant()}"; await session.NotifyAsync(MethodId.SyncNotice, UxSerializer.Serialize(cmd), token); @@ -1390,6 +1401,13 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub if (session is null) return new { ok = false, error = "no live game session (is the client in the world?)" }; + if (_activeInstrumentEntityId != 0) + { + await GameRouter.DestroyDirectAsync(session, _activeInstrumentEntityId, "instrument-close"); + _activeInstrumentEntityId = 0; + session.Log.Info("[MINIGAME] removed spawned instrument prop"); + } + var cmd = "CMD:CLOSE_MINIGAME"; await session.NotifyAsync(MethodId.SyncNotice, UxSerializer.Serialize(cmd), token); @@ -1406,7 +1424,6 @@ internal sealed class DebugApiServer(PrivateServerConfig config, GameSessionHub { new { id = "KOF97", name = "🕹️ Arcade: The King of Fighters '97 (ตู้เกม KOF '97)", category = "arcade" }, new { id = "METALSLUG", name = "🕹️ Arcade: Metal Slug (ตู้เกม Metal Slug ลุยด่าน)", category = "arcade" }, - new { id = "FIGHTER", name = "🥊 3D Arcade Fighter (มินิเกมต่อสู้ 3D)", category = "arcade" }, new { id = "PIANO", name = "🎹 Grand Piano Interactive (เล่นเปียโนสด)", category = "music" }, new { id = "DRUMKIT", name = "🥁 Drumkit Interactive (ตีกลองชุดสด)", category = "music" }, new { id = "BOWLING", name = "🎳 Bowling Alley (โยนโบว์ลิ่ง)", category = "sports" }, diff --git a/Ananta.Server/Ananta.App/DebugPanel/index.html b/Ananta.Server/Ananta.App/DebugPanel/index.html index 7d39f98..ec90e42 100644 --- a/Ananta.Server/Ananta.App/DebugPanel/index.html +++ b/Ananta.Server/Ananta.App/DebugPanel/index.html @@ -233,30 +233,64 @@ -
-

👗 Outfit / Appearance (สลับชุด / ซ่อนเสื้อผ้า)

-
- - กดปุ่ม F8 ในเกมได้ +
+

🚇 สถานีรถไฟ & รถไฟใต้ดิน (Subway & Metro Fast Travel)

+
🏙️ Nova City Metro (12 สถานี):
+
+ + + + + + + + + + + +
-
สลับการแสดงผลเสื้อผ้าและ Dynamic Bone ของชุดชั้นนอกอย่างหมดจด โดยยังคงโมเดลบอดี้เดิมไว้ 100% ไร้ชิ้นส่วนลอยค้าง
+
🏮 Longqi Metro (13 สถานี):
+
+ + + + + + + + + + + + + +
+
วาร์ปไปยังสถานีรถไฟใต้ดิน (Subway / Metro Stations) ทั่วทั้ง Nova City และ Longqi ทันที

🎬 3D Cinematics & Story Timelines (ฉากคัทซีนเนื้อเรื่อง)

@@ -270,29 +304,28 @@ - - + - +
- +
-
เปิดตู้เกมอาร์เคด (KOF '97, Metal Slug) หรือมินิเกมดนตรี/กีฬาในโลกได้ทันที พร้อมปุ่มควบคุมแบบเต็มหน้าจอ
+
เปิดตู้เกมอาร์เคด (KOF '97, Metal Slug), มินิเกมดนตรีสด (เสกโมเดลเปียโนตรงหน้า), หรือวาร์ปไปลานโบว์ลิ่งจริงใน Nova City
@@ -854,6 +887,21 @@ async function launchArcadeMinigame() { await launchSelectedMinigame(); } +async function warpSubway(x, y, z, facing) { + const r = await api('/api/player/teleport', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ x: parseFloat(x), y: parseFloat(y), z: parseFloat(z), facing: parseFloat(facing || 0) }) + }); + log(`🚇 Warp to Subway (${x}, ${y}, ${z}): ` + JSON.stringify(r)); + refresh(); +} + +async function teleportToBowling() { + await warpSubway(1090.8, 6.5, 2083.7, 0); + log('🎳 Warped to Bowling Alley at (1090.8, 6.5, 2083.7)'); +} + async function setTimePreset(hour, minute) { const slider = document.getElementById('timeslider'); if (slider) { diff --git a/Ananta.Server/Ananta.Gameplay/Crowd/UrbanCrowdEngine.cs b/Ananta.Server/Ananta.Gameplay/Crowd/UrbanCrowdEngine.cs index f819599..0cc8c5a 100644 --- a/Ananta.Server/Ananta.Gameplay/Crowd/UrbanCrowdEngine.cs +++ b/Ananta.Server/Ananta.Gameplay/Crowd/UrbanCrowdEngine.cs @@ -97,12 +97,23 @@ public sealed class UrbanCrowdEngine public int TargetPedestrianDensity { get; set; } = 15; public bool PopulateShops { get; set; } = true; - // Verified civilian NPCs from Camp 2 with models + // Verified authentic civilian, shopkeeper, barista, and bar patron NPCs private static readonly uint[] CitizenFormworkPool = [ - 40120883, 40120884, 40120885, 40120886, 40120887, - 40120888, 40120889, 40120890, 40120891, 40120892, - 40120893, 40120894, 40120895, 40120896, 40120897 + 40130101, // Emily Sato - Lively female shopkeeper + 40130102, // Sato Saori - Gentle female shopkeeper + 40130117, // Emma - Store greeter + 40130118, // Owen King - Store manager + 40130121, // Hunter Davis - Store clerk + 40130122, // Linda Wilson - Professional female clerk + 40130128, // Eiko Anderson - Boutique clerk + 40131542, // Yang Zhiyuan - Cafe Barista + 40131591, // Cheng Mosheng - Kiosk clerk + 40130852, // Mizuki Mei - Bar & Nightclub patron + 40650080, // Mizuki Mei - Bar patron + 40651232, // Eugene Price - Cafe patron/customer + 40969501, // Daphne Shelby - Fashion blogger pedestrian + 40968790, // Kaneko Mei - Pedestrian ]; // Common idle / chatter / phone POI action IDs @@ -234,6 +245,10 @@ public sealed class UrbanCrowdEngine var sy = shop.Pos[1]; var sz = shop.Pos[2]; + // Ground elevation clamping: align indoor floor Y with player elevation to prevent falling into void + if (MathF.Abs(sy - py) > 2.5f) + sy = py; + var dx = sx - px; var dz = sz - pz; var distSq = dx * dx + dz * dz; @@ -313,12 +328,14 @@ public sealed class UrbanCrowdEngine if (candidateWaypoints.Count > 0) { var wp = candidateWaypoints[_rng.Next(candidateWaypoints.Count)]; + // Ground elevation clamping: ensure sidewalk pedestrian Y is on mesh floor + var spawnY = MathF.Abs(wp.Y - py) > 2.5f ? py : wp.Y; var formworkId = CitizenFormworkPool[_rng.Next(CitizenFormworkPool.Length)]; var poiAction = AmbientPoiActions[_rng.Next(AmbientPoiActions.Length)]; var facing = _rng.Next(0, 360); var (ok, ids) = await _driver.SpawnCrowdPedestriansAsync( - session, formworkId, poiAction, wp.X, wp.Y, wp.Z, facing); + session, formworkId, poiAction, wp.X, spawnY, wp.Z, facing); if (ok && ids.Length > 0) { @@ -327,7 +344,7 @@ public sealed class UrbanCrowdEngine EntityId = ids[0], FormworkId = formworkId, X = wp.X, - Y = wp.Y, + Y = spawnY, Z = wp.Z, SpawnedAt = DateTime.UtcNow }; diff --git a/Ananta.Server/Ananta.Gameplay/Traffic/CityTrafficEngine.cs b/Ananta.Server/Ananta.Gameplay/Traffic/CityTrafficEngine.cs index f096e7f..6158799 100644 --- a/Ananta.Server/Ananta.Gameplay/Traffic/CityTrafficEngine.cs +++ b/Ananta.Server/Ananta.Gameplay/Traffic/CityTrafficEngine.cs @@ -26,19 +26,28 @@ public sealed class CityTrafficEngine public float MaxSpawnDistance { get; set; } = 160f; public float DespawnDistance { get; set; } = 220f; - // Pool of verified vehicle configs with full models and multi-seat support + // Pool of verified vehicle configs with full models and multi-seat support (no damaged/wrecked models) private static readonly uint[] TrafficVehiclePool = [ 81001001, // Sunset Skywing (Sedan) 81001002, // Korou RV6 (SUV) 81001003, // Kazama Voyage (Sedan) 81000007, // Sunset GT-X Specter (Sports Car) - 81000002, // Korou Transporter M (Truck) - 81000005, // Erebos Black Box (Van) + 81004001, // Kazama CRN6 (Taxi) + 81004027, // Kazama Sandstorm (Police Cruiser) ]; private readonly Random _rng = new(); + public void NotifyVehicleBoarding(ulong entityId) + { + if (_simulatedVehicles.TryRemove(entityId, out var v)) + { + v.HijackedByPlayer = true; + Console.WriteLine($"[TRAFFIC] Vehicle entity={entityId} boarded by player. Detached from traffic loop."); + } + } + public sealed class SimulatedVehicle { public ulong EntityId { get; set; } diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.VehicleStory.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.VehicleStory.cs index a375f6c..2cc466a 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.VehicleStory.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.VehicleStory.cs @@ -487,7 +487,6 @@ internal sealed partial class GameRouter new SceneMethods.SyncChangeVehicleController { VehicleInstanceId = vehicleId, ControllerPid = pid }), ct); await session.NotifyAsync(MethodId.SyncPlayerFinishEnterOrExitVehic, UxSerializer.Serialize(drive), ct); await SendStoryAsync(session, StoryS011Codec4229938.BuildEnterChildBootstrap(VehicleStoryEnterNid, vehicleId, seat)); - await SendStoryAsync(session, StoryS011Codec4229938.BuildDeleteNode(VehicleStoryEnterNid)); session.Log.Info($"[VEHICLE-STORY] force enter vehicle={vehicleId} seat={seat} unit={unitId} status=4"); return (true, $"seated in vehicle {vehicleId} (seat 0) — drive with normal keys, exit with F or the Exit button"); } diff --git a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Vehicles.cs b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Vehicles.cs index 982d6ac..b1a5f47 100644 --- a/Ananta.Server/Ananta.Handlers/Game/GameRouter.Vehicles.cs +++ b/Ananta.Server/Ananta.Handlers/Game/GameRouter.Vehicles.cs @@ -379,6 +379,7 @@ internal sealed partial class GameRouter conn.Log.Info($"[VEHICLE] AskPlayerStartEnterOrExitVehicle vehicle={drive.VehicleEntityId} enter={drive.EnterOrLeave} seat={drive.SeatIndex}"); if (drive.EnterOrLeave) { + Ananta.Server.Gameplay.Traffic.CityTrafficEngine.Instance.NotifyVehicleBoarding(drive.VehicleEntityId); await ForceEnterVehicleAsync(conn.Session, drive.VehicleEntityId); } else diff --git a/Ananta.Server/Ananta.Handlers/Game/TrafficAndCrowdDriver.cs b/Ananta.Server/Ananta.Handlers/Game/TrafficAndCrowdDriver.cs index 537e1d3..bcad87e 100644 --- a/Ananta.Server/Ananta.Handlers/Game/TrafficAndCrowdDriver.cs +++ b/Ananta.Server/Ananta.Handlers/Game/TrafficAndCrowdDriver.cs @@ -25,9 +25,12 @@ internal sealed class TrafficAndCrowdDriver : ITrafficVehicleDriver, IUrbanCrowd await GameRouter.DestroyDirectAsync(session, entityId, "traffic-despawn"); } + private readonly System.Collections.Concurrent.ConcurrentDictionary _moveTokens = new(); + public async Task SendVehicleMoveAsync( TcpSession session, ulong entityId, float x, float y, float z, float yaw, float vx, float vz) { + var token = _moveTokens.AddOrUpdate(entityId, 1, (_, cur) => unchecked(cur + 1)); var moveData = new SceneMethods.RaidVehicleSyncData { Id = entityId, @@ -35,8 +38,8 @@ internal sealed class TrafficAndCrowdDriver : ITrafficVehicleDriver, IUrbanCrowd FacingDirection = yaw, EulerAngles = new SceneMethods.UxVector3(0f, yaw, 0f), Velocity = new SceneMethods.UxVector3(vx, 0f, vz), - Bits = [], - MoveToken = 0, + Bits = [1, 0, 0, 0], + MoveToken = token, }; await session.NotifyAsync(MethodId.SyncVehicleMove, UxSerializer.Serialize(moveData), CancellationToken.None); GameRouter.TrackVehicleMove(entityId, x, y, z, yaw);