From d364b9616fe29c698665819aa61c0a21d14c4aaa Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Wed, 7 Oct 2026 01:45:10 +0700 Subject: [PATCH] fix(scene): unlock teleport pillars on maps missing from mapDefaultEntranceMap The user report: pillars visible on the map but locked, and clicking a teleport does nothing (client never sends EnterScene). Root cause: mapDefaultEntranceMap covers only 115 of 371 floors - the GetSceneMapInfo reply had no unlocked teleports for the missing floors (mostly new-region 2xxxxx maps), so the client locked the pillars and never initiated the teleport. - findEntryForFloor: resolve entry by searching config names P*_F directly, with mapDefaultEntranceMap as the first chance - GetSceneMapInfo: use the new resolver and merge teleports.json keys into unlocked_teleport_list for every floor - EnterScene: floor ids resolve through the same path; a teleport id arriving via scene_identifier (MDAIDPPKOPO.PLIDNBMCIJH) now also resolves the destination entry - loadScene: exact teleport position from teleports.json when the res config has no inline teleport with that id --- .gitignore | 1 + internal/gameserver/handlers_scene.go | 71 +++++++++++++++++++++++++-- persistent | 14 +++--- tools/AI TOOLBOX/memory/2026-10-07.md | 16 ++++++ 4 files changed, 90 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 7c47d18..e8c6b11 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ gameserver.log server_debug.log *.log +*.exe~ diff --git a/internal/gameserver/handlers_scene.go b/internal/gameserver/handlers_scene.go index b265c9f..404bd7c 100644 --- a/internal/gameserver/handlers_scene.go +++ b/internal/gameserver/handlers_scene.go @@ -120,12 +120,18 @@ func init() { switch { case req.EntryId != 0 && mapHasEntry(res, req.EntryId): resolvedEntry = req.EntryId - case sceneIdent != nil && res.MapDefaultEntranceMap[sceneIdent.FloorId] != 0: - resolvedEntry = res.MapDefaultEntranceMap[sceneIdent.FloorId] - case res.MapDefaultEntranceMap[req.EntryId] != 0: - resolvedEntry = res.MapDefaultEntranceMap[req.EntryId] + case sceneIdent != nil && sceneIdent.FloorId != 0: + resolvedEntry = findEntryForFloor(res, sceneIdent.FloorId) + case req.EntryId != 0 && findEntryForFloor(res, req.EntryId) != 0: + // req.EntryId doubles as a floor id in some map flows + resolvedEntry = findEntryForFloor(res, req.EntryId) case req.TeleportId != 0: resolvedEntry = findEntryByTeleport(res, req.TeleportId, currentEntry()) + case sceneIdent != nil && sceneIdent.TeleportNigger != nil: + if t, okc := sceneIdent.TeleportNigger.(*goproto.SceneIdentifier_MDAIDPPKOPO); okc && t.MDAIDPPKOPO != nil && t.MDAIDPPKOPO.PLIDNBMCIJH != 0 { + // map teleport click: teleport id arrives via scene_identifier + resolvedEntry = findEntryByTeleport(res, t.MDAIDPPKOPO.PLIDNBMCIJH, currentEntry()) + } case interactID != 0: resolvedEntry = findEntryByInteract(res, interactID, currentEntry()) default: @@ -233,7 +239,10 @@ func init() { &goproto.ChestInfo{ChestType: goproto.ChestType_MAP_INFO_CHEST_TYPE_PUZZLE}, ) - if entryID := res.MapDefaultEntranceMap[floorID]; entryID != 0 { + // resolve the floor's entry directly (mapDefaultEntranceMap covers + // only 115/371 floors - new-region maps were left with no unlocked + // teleports, which locked the pillars) + if entryID := findEntryForFloor(res, floorID); entryID != 0 { if _, cfg, ok := firstSceneOfEntry(res, entryID); ok { groupIDs := sortedU32Keys2(cfg.Scenes) for _, groupID := range groupIDs { @@ -258,6 +267,20 @@ func init() { } } + // teleports.json covers every map (incl. floors whose res config + // has no inline teleports) + if m := common.Teleports()[floorID]; m != nil { + seen := map[uint32]bool{} + for _, t := range mapInfo.UnlockedTeleportList { + seen[t] = true + } + for id := range m { + if id != 0 && !seen[id] { + mapInfo.UnlockedTeleportList = append(mapInfo.UnlockedTeleportList, id) + } + } + } + rsp.SceneMapInfo = append(rsp.SceneMapInfo, mapInfo) } return nil @@ -366,12 +389,40 @@ func init() { }) } +// findEntryForFloor resolves an entry id for a floor WITHOUT relying on +// mapDefaultEntranceMap (which misses 287/371 floors): search the config +// names "P_F" directly. +func findEntryForFloor(res *common.JsonConfig, floorID uint32) uint32 { + if entryID := res.MapDefaultEntranceMap[floorID]; entryID != 0 { + return entryID + } + suffix := fmt.Sprintf("_F%d", floorID) + entryIDs := sortedU32Keys2(res.LevelOutputConfigs) + for _, eid := range entryIDs { + for name := range res.LevelOutputConfigs[eid] { + if len(name) > len(suffix) && name[len(name)-len(suffix):] == suffix { + return eid + } + } + } + return 0 +} + func mapHasEntry(res *common.JsonConfig, entryID uint32) bool { _, ok := res.LevelOutputConfigs[entryID] return ok } func findEntryByTeleport(res *common.JsonConfig, teleportID uint32, fallback uint32) uint32 { + // teleports.json covers floors whose res config has no inline teleports: + // teleport id -> floor -> entry (via the floor name search) + for floorID, m := range common.Teleports() { + if _, ok := m[teleportID]; ok { + if entry := findEntryForFloor(res, floorID); entry != 0 { + return entry + } + } + } entryIDs := sortedU32Keys2(res.LevelOutputConfigs) for _, eid := range entryIDs { names := make([]string, 0, len(res.LevelOutputConfigs[eid])) @@ -459,6 +510,16 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32 break } } + if !found { + // exact teleport from teleports.json (covers floors whose res + // config has no inline teleports) + if m := common.Teleports()[floorID]; m != nil { + if tp, ok := m[*teleportID]; ok { + jsonPos.X, jsonPos.Y, jsonPos.Z, jsonPos.RotY = tp.Pos.X, tp.Pos.Y, tp.Pos.Z, tp.Rot.Y + found = true + } + } + } if !found && len(groupIDs) > 0 { // first teleport of the first group that has one for _, gid := range groupIDs { diff --git a/persistent b/persistent index a3ff9ea..6cda4d7 100644 --- a/persistent +++ b/persistent @@ -6,15 +6,15 @@ "3": 1004 }, "position": { - "x": -92, - "y": 4278, - "z": 68083, - "rot_y": 155566 + "x": 780, + "y": -153008, + "z": -621450, + "rot_y": 0 }, "scene": { - "plane_id": 10202, - "floor_id": 10202001, - "entry_id": 102020107 + "plane_id": 30141, + "floor_id": 30141001, + "entry_id": 3014101 }, "main_character": "FemaleHarmony", "march_type": "MarchHunt", diff --git a/tools/AI TOOLBOX/memory/2026-10-07.md b/tools/AI TOOLBOX/memory/2026-10-07.md index 82b2c5a..6b880dd 100644 --- a/tools/AI TOOLBOX/memory/2026-10-07.md +++ b/tools/AI TOOLBOX/memory/2026-10-07.md @@ -47,3 +47,19 @@ 1. **ทดสอบกับ client จริง** — เปิด `run_go.bat` (GaC-hkrpg-go) แล้ว login ด้วย hosts/proxy เดิม 2. Gacha handlers ถูก skip (proto 4.6.51 เปลี่ยนชื่อ GetGachaInfoCsReq/DoGachaCsReq) — หาชื่อใหม่จาก dump 3. ถ้า client ฟ้อง cmd ไหน "Unknown" ให้เช็ค `gameserver.log` แล้วเติม handler ในตัว Go + +## เพิ่มเติม (รอบแก้เสาวาป — 01:3x) + +**อาการ:** เสาวาปในแมพใหม่ๆ มองเห็นในแมพแต่ไม่เปิด + กดวาปไม่ไป (client ไม่ส่ง EnterScene เลย) + +**ต้นเหตุ:** `mapDefaultEntranceMap` ใน res.json ครอบคลุมแค่ 115/371 ฟลอร์ — โค้ด GetSceneMapInfo +และ EnterScene resolution พึ่งมันทั้งคู่ ฟลอร์ที่ไม่อยู่ในนี้ = ไม่มีรายชื่อเสาปลดล็อคส่งกลับ +→ client ล็อคเสา และไม่เริ่มวาป + +**แก้ (commit 0fdadd0):** +- `findEntryForFloor` — หา entry จากชื่อ config `P*_F` โดยตรง (mapDefaultEntranceMap เป็น first chance) +- GetSceneMapInfo รวมรายชื่อเสาจาก teleports.json ทุกฟลอร์ +- EnterScene: teleport id ผ่าน scene_identifier (MDAIDPPKOPO.PLIDNBMCIJH) resolve ปลายทางได้ +- loadScene: ใช้ตำแหน่ง teleport จาก teleports.json ตรงๆ ถ้า res ไม่มี id นั้น + +**บทเรียน:** data index บางตัวใน res.json ไม่ครอบคลุมทุกแมพ — ต้องมี fallback ค้นจากชื่อ config / teleports.json เสมอ