fix: sync current handlers, repair relic data and restore visible launcher consoles

This commit is contained in:
CAtlas0784 committed 2026-10-16 22:19:52 +07:00
1 parent 163d4433e6
commit 74f3c4411b
46 files changed
+4599 -1402

No files matched your search

+99 -19
View File
@@ -52,6 +52,57 @@ func firstSceneOfEntry(res *common.JsonConfig, entryID uint32) (string, *common.
return "", nil, false
}
// sceneOfEntryForTeleport selects the floor config that actually owns the
// requested teleport. Some resource sets put several floors under one entry;
// choosing the lexicographically first config can then send a valid scene
// transition with the wrong floor and spawn position.
func sceneOfEntryForTeleport(res *common.JsonConfig, entryID, floorID, teleportID uint32) (string, *common.LevelOutputConfig, bool) {
configs := res.LevelOutputConfigs[entryID]
names := make([]string, 0, len(configs))
for name := range configs {
names = append(names, name)
}
sort.Strings(names)
if floorID != 0 {
want := fmt.Sprintf("_F%d", floorID)
for _, name := range names {
cfg := configs[name]
if len(name) > len(want) && name[len(name)-len(want):] == want && sceneHasTeleport(cfg, teleportID) {
return name, cfg, true
}
}
}
if teleportID != 0 {
for _, name := range names {
cfg := configs[name]
if sceneHasTeleport(cfg, teleportID) {
return name, cfg, true
}
}
}
if floorID != 0 {
want := fmt.Sprintf("_F%d", floorID)
for _, name := range names {
if len(name) > len(want) && name[len(name)-len(want):] == want {
return name, configs[name], true
}
}
}
return firstSceneOfEntry(res, entryID)
}
func sceneHasTeleport(cfg *common.LevelOutputConfig, teleportID uint32) bool {
if cfg == nil || teleportID == 0 {
return false
}
for _, group := range cfg.Scenes {
if _, ok := group.Teleports[teleportID]; ok {
return true
}
}
return false
}
// parsePlaneFloor splits "P<plane>_F<floor>".
func parsePlaneFloor(name string) (uint32, uint32, error) {
if len(name) < 2 || name[0] != 'P' {
@@ -108,7 +159,7 @@ func init() {
defaultScene.FloorId = player.Scene.FloorID
}
scene, err := loadScene(s, entryID, false, nil, nil)
scene, err := loadScene(s, entryID, false, false, nil, nil)
rsp.Retcode = 0
if err == nil {
rsp.Scene = scene
@@ -120,7 +171,7 @@ func init() {
handle("EnterScene", func(s *Session, req *goproto.EnterSceneCsReq, rsp *goproto.EnterSceneScRsp) error {
Log(fmt.Sprintf("on_enter_scene_cs_req: entry_id=%d, teleport_id=%d, interact_id=%d", req.EntryId, req.TeleportId, req.InteractId))
SceneDebugLog("ENTER_SCENE entry_id=%d teleport_id=%d interact_id=%d", req.EntryId, req.TeleportId, req.InteractId)
SceneDebugLog("ENTER_SCENE entry_id=%d teleport_id=%d interact_id=%d scene_identifier=%v", req.EntryId, req.TeleportId, req.InteractId, req.SceneIdentifier)
res, err := common.GameRes()
if err != nil {
@@ -222,7 +273,7 @@ func init() {
teleportID = &v
}
sceneInfo, err := loadScene(s, resolvedEntry, true, teleportID, sceneIdent)
sceneInfo, err := loadScene(s, resolvedEntry, true, false, teleportID, sceneIdent)
if err != nil {
SceneDebugLog("ENTER_SCENE_FAILED requested_entry=%d resolved_entry=%d err=%v", req.EntryId, resolvedEntry, err)
Log(fmt.Sprintf("Failed to enter scene %d: %v", resolvedEntry, err))
@@ -230,11 +281,21 @@ func init() {
return nil
}
SceneDebugLog("ENTER_SCENE_RESOLVED requested_entry=%d resolved_entry=%d", req.EntryId, resolvedEntry)
SceneDebugLog("ENTER_SCENE_RESOLVED requested_entry=%d resolved_entry=%d floor_id=%d plane_id=%d teleport_id=%d", req.EntryId, resolvedEntry, sceneInfo.FloorId, sceneInfo.PlaneId, teleportIDValue(teleportID))
rsp.Retcode = 0
rsp.SceneIdentifier = sceneInfo.SceneIdentifier
rsp.IsOverMap = true
rsp.IsCloseMap = req.IsCloseMap
return nil
if err := s.Send(rsp); err != nil {
return err
}
if err := s.Send(&goproto.EnterSceneByServerScNotify{
Scene: sceneInfo,
Lineup: common.ToLineupInfo(player.Lineups),
}); err != nil {
return err
}
return errResponseAlreadySent
})
handle("InteractProp", func(s *Session, req *goproto.InteractPropCsReq, rsp *goproto.InteractPropScRsp) error {
@@ -268,14 +329,14 @@ func init() {
return nil
})
// decodeSceneIdentifiersLenient: the 4.6.51 client's cmd-1461 payload can
// decodeSceneIdentifiersLenient: some Client scene-map payloads can
// fail strict proto parsing (field layout drift between builds) — walk the
// wire format manually and pull floor_id/content_id out of field 2.
// GetSceneMapInfo as a raw handler: the 4.6.51 client's cmd-1461 payload
// GetSceneMapInfo as a raw handler: some Client scene-map payloads
// can fail strict proto parsing (layout drift), so fall back to a manual
// wire walk instead of dropping the request (the map UI stalls without
// this reply - no pillars, "navigation loading").
rawHandlers[1461] = func(s *Session, payload []byte) error {
rawNamed("GetSceneMapInfoCsReq", func(s *Session, payload []byte) error {
res, err := common.GameRes()
if err != nil {
return err
@@ -352,7 +413,7 @@ func init() {
}
return s.Send(rsp)
}
})
handle("SceneEntityMove", func(s *Session, req *goproto.SceneEntityMoveCsReq, rsp *goproto.SceneEntityMoveScRsp) error {
player := s.JSON()
@@ -696,9 +757,9 @@ func sortedU32Keys2[V any](m map[uint32]V) []uint32 {
func sortedU32Keys[V any](m map[uint32]V) []uint32 { return sortedU32Keys2(m) }
// loadScene is the Go port of load_scene. When isEnterScene is true it also
// sends EnterSceneByServerScNotify and persists the new position.
func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32, reqSceneIdent *goproto.SceneIdentifier) (*goproto.SceneInfo, error) {
// loadScene is the Go port of load_scene. When isEnterScene is true it updates
// the player's scene; sendNotify controls whether it also emits the notify.
func loadScene(s *Session, entryID uint32, isEnterScene, sendNotify bool, teleportID *uint32, reqSceneIdent *goproto.SceneIdentifier) (*goproto.SceneInfo, error) {
player := s.JSON()
if player == nil {
return nil, fmt.Errorf("data is not set!")
@@ -708,7 +769,15 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32
return nil, err
}
name, scene, ok := firstSceneOfEntry(res, entryID)
floorHint := uint32(0)
if reqSceneIdent != nil {
floorHint = reqSceneIdent.FloorId
}
requestedTeleport := uint32(0)
if teleportID != nil {
requestedTeleport = *teleportID
}
name, scene, ok := sceneOfEntryForTeleport(res, entryID, floorHint, requestedTeleport)
if !ok {
Log(fmt.Sprintf("Map Entrance Not Found %d", entryID))
return nil, fmt.Errorf("Map Entrance Not Found %d", entryID)
@@ -750,6 +819,7 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32
}
}
}
SceneDebugLog("LOAD_SCENE_TELEPORT entry_id=%d config=%s floor_id=%d teleport_id=%d matched=%t pos=(%d,%d,%d)", entryID, name, floorID, *teleportID, found, jsonPos.X, jsonPos.Y, jsonPos.Z)
} else if isEnterScene || jsonPos.Y < -5000 {
// first teleport in the scene, else the teleport DB, else spawn near the first prop
var firstTP *common.TeleportInfo
@@ -861,7 +931,7 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32
for _, groupID := range sortedU32Keys2(scene.Scenes) {
group := scene.Scenes[groupID]
groupInfo := &goproto.SceneEntityGroupInfo{GroupId: groupID}
groupInfo := &goproto.SceneEntityGroupInfo{State: 1, GroupId: groupID}
// props
for i := range group.Props {
@@ -924,6 +994,7 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32
})
}
sceneInfo.EntityList = append(sceneInfo.EntityList, groupInfo.EntityList...)
if len(group.Props) == 0 && len(group.Npcs) == 0 && len(group.Monsters) == 0 {
continue
}
@@ -959,17 +1030,26 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32
player.Scene.PlaneID = planeID
player.Position = jsonPos
if err := s.Send(&goproto.EnterSceneByServerScNotify{
Scene: sceneInfo,
Lineup: common.ToLineupInfo(player.Lineups),
}); err != nil {
return nil, err
if sendNotify {
if err := s.Send(&goproto.EnterSceneByServerScNotify{
Scene: sceneInfo,
Lineup: common.ToLineupInfo(player.Lineups),
}); err != nil {
return nil, err
}
}
}
return sceneInfo, nil
}
func teleportIDValue(id *uint32) uint32 {
if id == nil {
return 0
}
return *id
}
func hasAvatar(player *common.FreesrData, id uint32) bool {
_, ok := player.Avatars[id]
return ok