fix(scene): unlock all teleports across maps, merge resource configs, fix scenary not load error, and add quick farm battle handlers

This commit is contained in:
CAtlas0784 committed 2026-10-16 20:08:13 +07:00
1 parent 9aab62414f
commit 163d4433e6
47 files changed
+2025067 -6082

No files matched your search

+146 -38
View File
@@ -20,16 +20,36 @@ import (
// Rust iterated a HashMap arbitrarily; we pick the lexicographically first
// name for determinism.
func firstSceneOfEntry(res *common.JsonConfig, entryID uint32) (string, *common.LevelOutputConfig, bool) {
m, ok := res.LevelOutputConfigs[entryID]
if !ok || len(m) == 0 {
return "", nil, false
if m, ok := res.LevelOutputConfigs[entryID]; ok && len(m) > 0 {
names := make([]string, 0, len(m))
for k := range m {
names = append(names, k)
}
sort.Strings(names)
return names[0], m[names[0]], true
}
names := make([]string, 0, len(m))
for k := range m {
names = append(names, k)
if entryID != 0 {
name := fmt.Sprintf("P%d_F%d", entryID/100, entryID*10+1)
cfg := &common.LevelOutputConfig{
IsEnteredSceneInfo: true,
Scenes: map[uint32]common.SceneData{
1: {
Teleports: map[uint32]common.TeleportInfo{
entryID: {Pos: common.Vector{X: 0, Y: 0, Z: 0}},
1: {Pos: common.Vector{X: 0, Y: 0, Z: 0}},
},
},
},
}
if res.LevelOutputConfigs == nil {
res.LevelOutputConfigs = map[uint32]map[string]*common.LevelOutputConfig{}
}
res.LevelOutputConfigs[entryID] = map[string]*common.LevelOutputConfig{
name: cfg,
}
return name, cfg, true
}
sort.Strings(names)
return names[0], m[names[0]], true
return "", nil, false
}
// parsePlaneFloor splits "P<plane>_F<floor>".
@@ -65,19 +85,24 @@ func init() {
return nil
}
// recover from a stale challenge arena back to the Parlor Car
// recover from a stale challenge arena, broken map, or void coordinate back to Belobog Admin District
entryID := player.Scene.EntryID
if entryID >= 3000000 && entryID < 4000000 {
entryID = 100000104
if (entryID >= 3000000 && entryID < 4000000) || entryID == 100000104 || entryID == 100000304 || entryID == 1030402 || entryID == 0 || player.Position.Y < -50000 {
entryID = 102020107
player.Scene.EntryID = 102020107
player.Scene.PlaneID = 10202
player.Scene.FloorID = 10202001
player.Position = common.Position{X: -92, Y: 4278, Z: 68083, RotY: 155566}
player.SavePersistent()
}
defaultScene := &goproto.SceneInfo{
GameModeType: 1,
EntryId: entryID,
}
if entryID == 100000104 {
defaultScene.PlaneId = 10000
defaultScene.FloorId = 10000000
if entryID == 102020107 {
defaultScene.PlaneId = 10202
defaultScene.FloorId = 10202001
} else {
defaultScene.PlaneId = player.Scene.PlaneID
defaultScene.FloorId = player.Scene.FloorID
@@ -103,10 +128,10 @@ func init() {
}
player := s.JSON()
currentEntry := func() uint32 {
if player != nil {
if player != nil && player.Scene.EntryID != 0 && player.Scene.EntryID != 100000104 && player.Scene.EntryID != 100000304 {
return player.Scene.EntryID
}
return 100000104
return 102020107
}
interactID := uint32(req.InteractId)
@@ -115,17 +140,17 @@ func init() {
sceneIdent = proto.Clone(req.SceneIdentifier).(*goproto.SceneIdentifier)
}
// resolve target entry (mirrors the Rust resolution chain)
// resolve target entry (mirrors the Rust resolution chain, with cascading fallbacks)
resolvedEntry := uint32(0)
switch {
case req.EntryId != 0 && mapHasEntry(res, req.EntryId):
if req.EntryId != 0 && mapHasEntry(res, req.EntryId) {
resolvedEntry = req.EntryId
case sceneIdent != nil && sceneIdent.FloorId != 0:
} else if 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
}
if resolvedEntry == 0 && req.EntryId != 0 {
resolvedEntry = findEntryForFloor(res, req.EntryId)
case req.TeleportId != 0:
}
if resolvedEntry == 0 && req.TeleportId != 0 {
floorID := uint32(0)
if sceneIdent != nil {
floorID = sceneIdent.FloorId
@@ -133,21 +158,26 @@ func init() {
if entry := findEntryByTeleportInFloor(res, req.TeleportId, floorID); entry != 0 {
resolvedEntry = entry
} else {
resolvedEntry = findEntryByTeleport(res, req.TeleportId, currentEntry())
resolvedEntry = findEntryByTeleport(res, req.TeleportId, 0)
}
case sceneIdent != nil && sceneIdent.TeleportNigger != nil:
}
if resolvedEntry == 0 && 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 + floor arrive via scene_identifier
tpID := t.MDAIDPPKOPO.PLIDNBMCIJH
if entry := findEntryByTeleportInFloor(res, tpID, sceneIdent.FloorId); entry != 0 {
resolvedEntry = entry
} else {
resolvedEntry = findEntryByTeleport(res, tpID, currentEntry())
resolvedEntry = findEntryByTeleport(res, tpID, 0)
}
}
case interactID != 0:
resolvedEntry = findEntryByInteract(res, interactID, currentEntry())
default:
}
if resolvedEntry == 0 && interactID != 0 {
resolvedEntry = findEntryByInteract(res, interactID, 0)
}
if resolvedEntry == 0 && req.EntryId != 0 {
resolvedEntry = req.EntryId
}
if resolvedEntry == 0 {
resolvedEntry = currentEntry()
}
@@ -215,6 +245,10 @@ func init() {
// a wrong state (like 1100) breaks interactions and UI entry points
state := uint32(1)
if p, ok := s.PropEntities[req.PropEntityId]; ok {
if p.State == 0 {
p.State = 1
s.PropEntities[req.PropEntityId] = p
}
state = p.State
}
@@ -354,7 +388,6 @@ func init() {
}
}
}
player.SavePersistent()
return nil
})
@@ -396,31 +429,106 @@ func init() {
// cmd 1432 -> 1421: unlock every teleport in the game
handle("GetUnlockTeleport", func(s *Session, req *goproto.GetUnlockTeleportCsReq, rsp *goproto.GetUnlockTeleportScRsp) error {
rsp.Retcode = 0
unlocks := getMissionUnlocks()
seen := map[uint32]bool{}
var tps []uint32
// 1. All IDs from mission_unlocks.json
unlocks := getMissionUnlocks()
for _, id := range unlocks.TeleportIDs {
if id != 0 && !seen[id] {
seen[id] = true
tps = append(tps, id)
}
}
if len(tps) == 0 {
for _, m := range common.Teleports() {
for id := range m {
if id != 0 && !seen[id] {
seen[id] = true
tps = append(tps, id)
// 2. All IDs from teleports DB across all floors
for _, m := range common.Teleports() {
for id := range m {
if id != 0 && !seen[id] {
seen[id] = true
tps = append(tps, id)
}
}
}
// 3. All teleport IDs from LevelOutputConfigs
if res, err := common.GameRes(); err == nil {
for _, m := range res.LevelOutputConfigs {
for _, cfg := range m {
for _, sc := range cfg.Scenes {
for tid := range sc.Teleports {
if tid != 0 && !seen[tid] {
seen[tid] = true
tps = append(tps, tid)
}
}
}
}
}
}
// 4. Any entry IDs requested by client or in EntranceIDs
for _, eid := range req.EntryIdList {
if eid != 0 && !seen[eid] {
seen[eid] = true
tps = append(tps, eid)
}
}
for _, id := range unlocks.EntranceIDs {
if id != 0 && !seen[id] {
seen[id] = true
tps = append(tps, id)
}
}
sort.Slice(tps, func(i, j int) bool { return tps[i] < tps[j] })
rsp.UnlockedTeleportList = tps
SceneDebugLog("GET_UNLOCK_TELEPORT req_entries=%d teleports_sent=%d", len(req.EntryIdList), len(rsp.UnlockedTeleportList))
return nil
})
// cmd 1438 -> 1409: handle entity teleport within scene
handle("SceneEntityTeleport", func(s *Session, req *goproto.SceneEntityTeleportCsReq, rsp *goproto.SceneEntityTeleportScRsp) error {
rsp.Retcode = 0
rsp.EntityMotion = req.EntityMotion
if req.EntityMotion != nil && req.EntityMotion.Motion != nil {
motion := req.EntityMotion.Motion
player := s.JSON()
if player != nil {
if motion.Pos != nil {
player.Position.X = motion.Pos.X
player.Position.Y = motion.Pos.Y
player.Position.Z = motion.Pos.Z
}
if motion.Rot != nil {
player.Position.RotY = motion.Rot.Y
}
player.SavePersistent()
}
}
SceneDebugLog("SCENE_ENTITY_TELEPORT entry_id=%d", req.EntryId)
return nil
})
// cmd 1267 -> 1263: teleport to mission reset point
handle("TeleportToMissionResetPoint", func(s *Session, req *goproto.TeleportToMissionResetPointCsReq, rsp *goproto.TeleportToMissionResetPointScRsp) error {
rsp.Retcode = 0
player := s.JSON()
if player != nil {
rsp.Motion = &goproto.MotionInfo{
Pos: &goproto.Vector{
X: player.Position.X,
Y: player.Position.Y,
Z: player.Position.Z,
},
Rot: &goproto.Vector{
Y: player.Position.RotY,
},
}
}
return nil
})
}
// findEntryForFloor resolves an entry id for a floor WITHOUT relying on