chore: checkpoint stable battle and eidolon progression

This commit is contained in:
CAtlas0784 committed 2026-10-17 16:38:03 +07:00
1 parent 365e66ffe0
commit a681affdb3
23 files changed
+34511 -11310

No files matched your search

+41 -38
View File
@@ -67,52 +67,55 @@ func init() {
}
// handleExRankUnlock handles CmdID 400 (Affinitive Eidolon / Ex-Rank unlock request from client).
// The EX request remains obfuscated in PearlSR 005d2c1: field 5 is
// interpreted as an avatar, EX-rank or material ID only by an explicit mapping.
// Never unlock every avatar when an unknown value is received.
func handleExRankUnlock(s *Session, payload []byte) error {
Log(fmt.Sprintf("[EX-RANK] Received cmd 400 (len: %d) payload: %x", len(payload), payload))
var req goproto.HIGLJHLNODG
if err := proto.Unmarshal(payload, &req); err != nil {
return err
}
parsedVal := req.OJFIGABCKLG
value := req.OJFIGABCKLG
resources, err := loadEidolonResources()
if err != nil {
return err
}
var avatarID uint32
if parsedVal >= 100000 {
avatarID = parsedVal / 100 // e.g. 120401 -> 1204, 121301 -> 1213, 130801 -> 1308
} else if parsedVal >= 1000 && parsedVal < 10000 {
avatarID = parsedVal
}
Log(fmt.Sprintf("[EX-RANK] Target avatarID: %d (from val: %d)", avatarID, parsedVal))
player := s.JSON()
if player != nil {
if avatarID == 0 {
// Fallback: unlock for all avatars that have ExRank defined (1204, 1213, 1308)
for aid := range common.AvatarExRankMap {
if av, ok := player.Avatars[aid]; ok {
av.ExRankUnlocked = true
Log(fmt.Sprintf("[EX-RANK] Fallback set ExRankUnlocked=true for avatar %d", aid))
syncNotify := &goproto.PlayerSyncScNotify{}
buildSync(player, syncNotify, []uint32{aid}, nil, nil)
_ = s.Send(syncNotify)
}
}
} else {
if av, ok := player.Avatars[avatarID]; ok {
av.ExRankUnlocked = true
Log(fmt.Sprintf("[EX-RANK] Set ExRankUnlocked=true for avatar %d", avatarID))
}
syncNotify := &goproto.PlayerSyncScNotify{}
buildSync(player, syncNotify, []uint32{avatarID}, nil, nil)
_ = s.Send(syncNotify)
for id, exID := range common.AvatarExRankMap {
if value == id || value == exID || value == resources.ExMaterials[id].ItemID {
avatarID = id
break
}
player.SavePersistent()
}
// The matching response is declared by the current upstream schema.
return s.Send(&goproto.LPOLGDLAADM{})
rsp := &goproto.LPOLGDLAADM{}
player := s.JSON()
if player == nil || avatarID == 0 || player.Avatars[avatarID] == nil {
rsp.Retcode = uint32(goproto.Retcode_RET_AVATAR_NOT_EXIST)
Log(fmt.Sprintf("[EX-RANK] rejected unmapped value=%d", value))
return s.Send(rsp)
}
if _, active := CURRENT_BATTLE.Load(s.Token); active {
rsp.Retcode = uint32(goproto.Retcode_RET_AVATAR_RANK_NOT_MATCH)
return s.Send(rsp)
}
avatar := player.Avatars[avatarID]
previous := avatar.ExRankUnlocked
avatar.ExRankUnlocked = true
if err := player.SavePersistentChecked(); err != nil {
avatar.ExRankUnlocked = previous
return err
}
notify := &goproto.PlayerSyncScNotify{}
buildSync(player, notify, []uint32{avatarID}, nil, nil)
notify.MaterialList, err = eidolonMaterials(player)
if err != nil {
return err
}
if err := s.Send(notify); err != nil {
return err
}
Log(fmt.Sprintf("[EX-RANK] unlocked avatar=%d value=%d", avatarID, value))
return s.Send(rsp)
}
// challengeGroupStatsReply uses the current typed statistics response.