Save endgame progress, team presets, technique support and camera controls

This commit is contained in:
CAtlas0784 committed 2026-10-17 23:44:36 +07:00
1 parent a681affdb3
commit 639cef7d19
52 files changed
+11940 -766

No files matched your search

+191 -296
View File
@@ -2,7 +2,6 @@ package gameserver
import (
"fmt"
"sort"
"google.golang.org/protobuf/proto"
@@ -22,6 +21,7 @@ type decodedChallengeStart struct {
thirdAvatars []uint32
stageInfoList [][]uint32
buffID uint32
buffIDs []uint32
}
// ---------------------------------------------------------------------------
@@ -62,51 +62,6 @@ func skipWireField(wireType uint32, buf *[]byte) {
}
}
func mapChallengeStageID(id uint32) uint32 {
cd := getChallengeData()
if _, ok := cd.Challenges[id]; ok {
return id
}
if _, ok := cd.Tierce[id]; ok {
return id
}
switch {
case id >= 5400 && id <= 5700: // MoC 1035/1036 -> 5301..5312
rem := id % 100
if rem < 1 {
rem = 1
}
if rem > 12 {
rem = 12
}
return 5300 + rem
case id >= 20200 && id <= 20300: // Pure Fiction -> 20251..20254
rem := id % 10
if rem < 1 {
rem = 1
}
if rem > 4 {
rem = 4
}
return 20250 + rem
case id >= 30200 && id <= 30300: // Apocalyptic Shadow -> 30201..30204
rem := id % 10
if rem < 1 {
rem = 1
}
if rem > 4 {
rem = 4
}
return 30200 + rem
case id > 1:
if _, ok := cd.Challenges[id]; !ok {
return id - 1
}
return id
}
return id
}
func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
var pbReq goproto.StartChallengeCsReq
if err := proto.Unmarshal(payload, &pbReq); err != nil {
@@ -131,9 +86,11 @@ func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
if pbReq.StageInfo != nil {
if info := pbReq.StageInfo.BossInfo; info != nil {
req.buffID = info.BuffOne
req.buffIDs = []uint32{info.BuffOne, info.BuffTwo}
}
if info := pbReq.StageInfo.StoryInfo; info != nil && req.buffID == 0 {
req.buffID = info.BuffOne
req.buffIDs = []uint32{info.BuffOne, info.BuffTwo}
}
}
return req
@@ -147,6 +104,7 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
req := decodedChallengeStart{challengeID: pbReq.ChallengeId,
stageIndex: pbReq.FINOCEGDBDI, isSingleStage: pbReq.MPKMIKBFEHG}
for index, stage := range pbReq.LineupTierceList {
req.buffIDs = append(req.buffIDs, stage.BuffId)
var avatars []uint32
for _, avatar := range stage.TierceAvatarIdList {
if avatar.Id != 0 {
@@ -170,13 +128,15 @@ func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
return req
}
func decodeSetChallengeTierceLineupReq(payload []byte) (uint32, [][]uint32) {
func decodeSetChallengeTierceLineupReq(payload []byte) (uint32, [][]uint32, []uint32) {
var req goproto.SetChallengeTierceLineupCsReq
if err := proto.Unmarshal(payload, &req); err != nil {
return 0, nil
return 0, nil, nil
}
var stages [][]uint32
var buffs []uint32
for _, stage := range req.StageInfoList {
buffs = append(buffs, stage.BuffId)
var avatars []uint32
for _, avatar := range stage.TierceAvatarIdList {
if avatar.Id != 0 {
@@ -185,54 +145,70 @@ func decodeSetChallengeTierceLineupReq(payload []byte) (uint32, [][]uint32) {
}
stages = append(stages, avatars)
}
return req.ChallengeId, stages
return req.ChallengeId, stages, buffs
}
// ---------------------------------------------------------------------------
// config setup shared by start handlers
// ---------------------------------------------------------------------------
func setChallengeBattleConfig(player *common.FreesrData, avatars []uint32, challengeID uint32, eventID uint32, buff uint32) {
customLineup := map[uint32]uint32{}
for i, aid := range avatars {
customLineup[uint32(i)] = aid
// Build challenge configuration from the selected resource stage, never from
// Freesr custom monsters. Only the current session owns this configuration.
func setChallengeBattleConfig(s *Session, avatars []uint32, challengeID, eventID uint32, buffs []uint32) error {
stage, ok := getChallengeData().Stages[eventID]
if !ok || len(stage.Monsters) == 0 {
return fmt.Errorf("challenge stage %d has no resource waves", eventID)
}
player.BattleConfig.CustomBattleLineup = customLineup
player.BattleConfig.StageID = eventID
isPF := challengeID >= 20000 && challengeID < 30000
isAS := challengeID >= 30000
if isPF {
player.BattleConfig.CycleCount = 4
player.BattleConfig.BattleType = common.BattleTypePF
} else if isAS {
player.BattleConfig.CycleCount = 30
player.BattleConfig.BattleType = common.BattleTypeAS
} else {
player.BattleConfig.CycleCount = 30
player.BattleConfig.BattleType = common.BattleTypeDefault
config := common.BattleConfig{StageID: eventID, CustomBattleLineup: map[uint32]uint32{}}
for i, id := range avatars {
config.CustomBattleLineup[uint32(i)] = id
}
if sb, ok := getChallengeData().Stages[eventID]; ok {
var waves [][]common.Monster
for _, wave := range sb.Monsters {
var ms []common.Monster
for _, mid := range wave {
ms = append(ms, common.Monster{Level: sb.Level, MonsterID: mid})
}
waves = append(waves, ms)
switch {
case challengeID >= 20000 && challengeID < 30000:
config.BattleType = common.BattleTypePF
case challengeID >= 30000:
config.BattleType = common.BattleTypeAS
default:
config.BattleType = common.BattleTypeDefault
}
// ChallengeCountDown is supplied by the installed challenge table.
configID := challengeID
if tierce, ok := getChallengeData().Tierce[challengeID]; ok {
configID = tierce.PreChallengeID
}
config.CycleCount = getChallengeData().Countdowns[configID]
if config.CycleCount == 0 {
// AS/PF rows omit ChallengeCountDown. Their limits come from the
// sourced challenge rules, not the user's custom battle configuration.
rules, err := loadBattleRules()
if err != nil {
return err
}
config.CycleCount = rules.ChallengeRoundLimits[config.BattleType]
if config.CycleCount == 0 {
return fmt.Errorf("missing challenge round limit for %s", config.BattleType)
}
player.BattleConfig.Monsters = waves
}
if buff != 0 {
player.BattleConfig.Blessings = []common.BattleBuffJson{{
ID: buff,
Level: 1,
}}
for _, wave := range stage.Monsters {
if len(wave) == 0 {
return fmt.Errorf("challenge stage %d has an empty wave", eventID)
}
monsters := make([]common.Monster, 0, len(wave))
for _, id := range wave {
monsters = append(monsters, common.Monster{Level: stage.Level, MonsterID: id})
}
config.Monsters = append(config.Monsters, monsters)
}
player.SavePersistent()
seen := map[uint32]bool{}
for _, id := range buffs {
if id != 0 && !seen[id] {
config.Blessings = append(config.Blessings, common.BattleBuffJson{ID: id, Level: 1})
seen[id] = true
}
}
s.ChallengeBattleConfig = &config
SceneDebugLog("[CHALLENGE] battle source=resource challenge=%d stage=%d waves=%v buffs=%v", challengeID, eventID, stage.Monsters, buffs)
return nil
}
func lineupInfoFromAvatars(avatars []uint32, planeID uint32, extraType goproto.ExtraLineupType, index uint32) *goproto.LineupInfo {
@@ -257,7 +233,7 @@ func challengeAvatarsFallback(s *Session) []uint32 {
}
return out
}
return []uint32{1304, 1313, 1406, 1004}
return nil
}
func dedupeTruncate4(in []uint32) []uint32 {
@@ -291,36 +267,32 @@ func handleStartChallenge(s *Session, payload []byte) error {
// cap team at 4 (client TeamManager IndexOutOfRange otherwise)
chosenAvatars = dedupeTruncate4(chosenAvatars)
baseID := mapChallengeStageID(req.challengeID)
baseID := req.challengeID
cd := getChallengeData()
entrance, group, monster, event, buff := uint32(3000101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
if c, ok := cd.Challenges[baseID]; ok {
entrance, group, monster, event = c.Entrance, c.Group1, c.Monster1, c.Event1
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
} else if t, ok := cd.Tierce[baseID]; ok {
entrance, group, monster, event = t.Entrance, t.Group, t.Monster, t.Event
buff = req.buffID
stage, ok := cd.Challenges[baseID]
if !ok {
return s.Send(&goproto.StartChallengeScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
Log("[CHALLENGE] Arena entrance missing in res.json, fallback to 3000101")
entrance = 3000101
entrance, group, monster, event := stage.Entrance, stage.Group1, stage.Monster1, stage.Event1
buff := req.buffID
if buff == 0 {
buff = stage.Buff
}
Log("[CHALLENGE] Start: challenge_id=" + itoa(int(req.challengeID)) + " (mapped " + itoa(int(baseID)) + ")")
Log("[CHALLENGE] Arena: entrance=" + itoa(int(entrance)) + " group=" + itoa(int(group)) + " monster=" + itoa(int(monster)) + " event=" + itoa(int(event)))
if player := s.JSON(); player != nil {
setChallengeBattleConfig(player, chosenAvatars, req.challengeID, event, buff)
previousConfig := s.ChallengeBattleConfig
if err := setChallengeBattleConfig(s, chosenAvatars, req.challengeID, event, []uint32{buff, cd.Challenges[baseID].Buff}); err != nil {
SceneDebugLog("[CHALLENGE] start rejected: %v", err)
return s.Send(&goproto.StartChallengeScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
sceneInfo, _, err := loadChallengeScene(s, entrance, group, monster, event, chosenAvatars)
if err != nil {
return err
s.ChallengeBattleConfig = previousConfig
SceneDebugLog("[CHALLENGE] scene rejected: %v", err)
return s.Send(&goproto.StartChallengeScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
var lineups []*goproto.LineupInfo
@@ -331,16 +303,45 @@ func handleStartChallenge(s *Session, payload []byte) error {
secondAvatars = chosenAvatars
}
secondAvatars = dedupeTruncate4(secondAvatars)
lineups = append(lineups, lineupInfoFromAvatars(secondAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), 1))
lineups = append(lineups, lineupInfoFromAvatars(secondAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType_ExtraLineupType_LineupChallenge2, 0))
curChallenge := &goproto.CurChallenge{
ChallengeId: req.challengeID,
Status: goproto.ChallengeStatus_CHALLENGE_DOING,
RoundCount: 0,
ScoreId: scoreIDFor(req.challengeID),
ScoreId: 0,
ScoreTwo: 0,
ExtraLineupType: goproto.ExtraLineupType(1),
}
// PearlSR 005d2c12 challenge_mgr.zig creates this mode-specific state
// before the Client recovers its challenge instance. Omitting it causes
// ChallengeBossInstance._SyncExtraInfo to dereference a missing message.
mazeBuff := cd.Challenges[baseID].Buff
buffs := make([]uint32, 0, 2)
for _, id := range []uint32{req.buffID, mazeBuff} {
if id != 0 {
buffs = append(buffs, id)
}
}
switch {
case req.challengeID > 20000 && req.challengeID < 30000:
curChallenge.StageInfo = &goproto.ChallengeCurBuffInfo{
CurStoryBuffs: &goproto.ChallengeStoryBuffList{BuffList: buffs},
}
case req.challengeID > 30000:
curChallenge.StageInfo = &goproto.ChallengeCurBuffInfo{
CurBossBuffs: &goproto.ChallengeBossBuffList{BuffList: buffs, ChallengeBossConst: 1},
}
}
rooms := []challengeRoom{resourceRoom(stage, false, chosenAvatars, req.buffID)}
if stage.Entrance2 != 0 && stage.Event2 != 0 {
var secondBuff uint32
if len(req.buffIDs) > 1 {
secondBuff = req.buffIDs[1]
}
rooms = append(rooms, resourceRoom(stage, true, secondAvatars, secondBuff))
}
s.ChallengeRun = &challengeRun{ID: req.challengeID, Rooms: rooms, Scores: make([]uint32, len(rooms)), Cur: curChallenge}
rsp := &goproto.StartChallengeScRsp{
Retcode: 0,
@@ -348,171 +349,68 @@ func handleStartChallenge(s *Session, payload []byte) error {
LineupList: lineups,
Scene: sceneInfo,
}
SceneDebugLog("[CHALLENGE] start id=%d mapped=%d entry=%d floor=%d world=%d mode=%d buff=%v storyState=%t bossState=%t", req.challengeID, baseID, sceneInfo.EntryId, sceneInfo.FloorId, sceneInfo.WorldId, sceneInfo.GameModeType, buffs, curChallenge.GetStageInfo().GetCurStoryBuffs() != nil, curChallenge.GetStageInfo().GetCurBossBuffs() != nil)
return s.Send(rsp)
}
func scoreIDFor(challengeID uint32) uint32 {
if challengeID >= 20000 && challengeID < 30000 {
return 40000
}
return 0
}
// handleStartChallengeTierce uses the current Tierce request schema.
func handleStartChallengeTierce(s *Session, payload []byte) error {
req := decodeStartChallengeTierceReq(payload)
if req.challengeID == 0 {
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
baseID := mapChallengeStageID(req.challengeID)
baseID := req.challengeID
Log("handle_start_challenge_tierce: challenge_id=" + itoa(int(req.challengeID)) + " (mapped " + itoa(int(baseID)) + ")")
tierceLineupsMu.Lock()
if len(req.stageInfoList) > 0 {
tierceLineups[req.challengeID] = req.stageInfoList
if err := rememberTierceTeams(s, req.challengeID, req.stageInfoList, req.buffIDs, req.isSingleStage, req.stageIndex); err != nil {
return err
}
tierceLineupsMu.Unlock()
var chosenAvatars []uint32
switch req.stageIndex {
case 0:
chosenAvatars = req.firstAvatars
case 1:
if len(req.secondAvatars) > 0 {
chosenAvatars = req.secondAvatars
} else {
chosenAvatars = req.firstAvatars
}
case 2:
if len(req.thirdAvatars) > 0 {
chosenAvatars = req.thirdAvatars
} else {
chosenAvatars = req.firstAvatars
}
req.stageInfoList = s.TierceTeams[req.challengeID]
req.buffIDs = s.TierceBuffs[req.challengeID]
if int(req.stageIndex) < len(req.buffIDs) {
req.buffID = req.buffIDs[req.stageIndex]
}
if len(chosenAvatars) == 0 {
tierceLineupsMu.Lock()
if list, ok := tierceLineups[req.challengeID]; ok && int(req.stageIndex) < len(list) {
chosenAvatars = list[req.stageIndex]
}
tierceLineupsMu.Unlock()
if req.stageIndex >= tierceNodeCount {
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
chosenAvatars := req.stageInfoList[req.stageIndex]
if len(chosenAvatars) == 0 {
chosenAvatars = challengeAvatarsFallback(s)
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_LINEUP_IS_EMPTY)})
}
cd := getChallengeData()
entrance, group, monster, event, buff := uint32(3000101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
if req.stageIndex == 0 {
if t, ok := cd.Tierce[baseID]; ok {
entrance, group, monster, event, buff = t.Entrance, t.Group, t.Monster, t.Event, req.buffID
} else if c, ok2 := cd.Challenges[baseID]; ok2 {
entrance, group, monster, event = c.Entrance, c.Group1, c.Monster1, c.Event1
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
}
} else if req.stageIndex == 1 {
// Stage 2 (Node 2)
if c, ok := cd.Challenges[baseID-1]; ok && c.Entrance2 != 0 {
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
} else if c, ok2 := cd.Challenges[baseID]; ok2 && c.Entrance2 != 0 {
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
if req.buffID != 0 {
buff = req.buffID
} else {
buff = c.Buff
}
} else if t, ok3 := cd.Tierce[baseID]; ok3 {
entrance, group, monster, event, buff = t.Entrance, t.Group, t.Monster, t.Event, req.buffID
}
} else {
if t, ok := cd.Tierce[baseID]; ok {
entrance, group, monster, event, buff = t.Entrance, t.Group, t.Monster, t.Event, req.buffID
}
// PearlSR challenge_mgr.zig: Tierce nodes 0/1 use the preceding
// ChallengeMazeConfig's first/second node; node 2 uses Tierce config.
tierce, tierceOK := cd.Tierce[req.challengeID]
base, baseOK := cd.Challenges[tierce.PreChallengeID]
if !tierceOK || !baseOK || req.stageIndex > 2 {
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
if res, err := common.GameRes(); err == nil && !mapHasEntry(res, entrance) {
Log("[CHALLENGE] Tierce arena entrance missing in res.json, fallback to 3000101")
entrance = 3000101
entrance, group, monster, event := base.Entrance, base.Group1, base.Monster1, base.Event1
switch req.stageIndex {
case 1:
entrance, group, monster, event = base.Entrance2, base.Group2, base.Monster2, base.Event2
case 2:
entrance, group, monster, event = tierce.Entrance, tierce.Group, tierce.Monster, tierce.Event
}
if player := s.JSON(); player != nil {
setChallengeBattleConfig(player, chosenAvatars, req.challengeID, event, buff)
previousConfig := s.ChallengeBattleConfig
if err := setChallengeBattleConfig(s, chosenAvatars, req.challengeID, event, []uint32{req.buffID, base.Buff}); err != nil {
SceneDebugLog("[CHALLENGE] tierce start rejected: %v", err)
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
sceneInfo, _, err := loadChallengeScene(s, entrance, group, monster, event, chosenAvatars)
if err != nil {
return err
s.ChallengeBattleConfig = previousConfig
SceneDebugLog("[CHALLENGE] tierce scene rejected: %v", err)
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
// build all lineups for tierce_info
var allLineups [][]uint32
firstLineup := req.firstAvatars
if len(firstLineup) == 0 && len(req.stageInfoList) > 0 {
firstLineup = req.stageInfoList[0]
}
if len(firstLineup) == 0 {
tierceLineupsMu.Lock()
if list, ok := tierceLineups[req.challengeID]; ok && len(list) > 0 {
firstLineup = list[0]
}
tierceLineupsMu.Unlock()
}
if len(firstLineup) == 0 {
firstLineup = chosenAvatars
}
allLineups = append(allLineups, firstLineup)
if !req.isSingleStage {
secondLineup := req.secondAvatars
if len(secondLineup) == 0 && len(req.stageInfoList) > 1 {
secondLineup = req.stageInfoList[1]
}
if len(secondLineup) == 0 {
tierceLineupsMu.Lock()
if list, ok := tierceLineups[req.challengeID]; ok && len(list) > 1 {
secondLineup = list[1]
}
tierceLineupsMu.Unlock()
}
if len(secondLineup) == 0 {
secondLineup = firstLineup
}
allLineups = append(allLineups, secondLineup)
thirdLineup := req.thirdAvatars
if len(thirdLineup) == 0 && len(req.stageInfoList) > 2 {
thirdLineup = req.stageInfoList[2]
}
if len(thirdLineup) == 0 {
tierceLineupsMu.Lock()
if list, ok := tierceLineups[req.challengeID]; ok && len(list) > 2 {
thirdLineup = list[2]
}
tierceLineupsMu.Unlock()
}
if len(thirdLineup) > 0 || req.stageIndex == 2 {
if len(thirdLineup) == 0 {
thirdLineup = firstLineup
}
allLineups = append(allLineups, thirdLineup)
}
}
var tierceLineupList []*goproto.LineupInfo
for stIdx, stageAvatars := range allLineups {
tierceLineupList = append(tierceLineupList, lineupInfoFromAvatars(stageAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), uint32(stIdx)))
}
// The response carries the active node's lineup, as upstream does.
// Other nodes are cached for subsequent requests, not installed as active teams.
tierceLineupList := []*goproto.LineupInfo{lineupInfoFromAvatars(chosenAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), 0)}
SceneDebugLog("[CHALLENGE] tierce id=%d node=%d entry=%d floor=%d group=%d event=%d avatars=%v", req.challengeID, req.stageIndex, entrance, sceneInfo.FloorId, group, event, chosenAvatars)
// Generated response resolves its command ID through Session.Send.
rsp := &goproto.StartChallengeTierceScRsp{
@@ -525,18 +423,52 @@ func handleStartChallengeTierce(s *Session, payload []byte) error {
MPKMIKBFEHG: req.isSingleStage,
},
}
teams := req.stageInfoList
if len(teams) == 0 {
teams = s.TierceTeams[req.challengeID]
}
rooms := make([]challengeRoom, tierceNodeCount)
for node := range rooms {
var team []uint32
if node < len(teams) {
team = teams[node]
}
if uint32(node) == req.stageIndex {
team = chosenAvatars
}
var selectedBuff uint32
if node < len(req.buffIDs) {
selectedBuff = req.buffIDs[node]
}
rooms[node] = resourceRoom(base, node == 1, team, selectedBuff)
if node == 2 {
rooms[node].Entrance, rooms[node].Group, rooms[node].Monster, rooms[node].Event = tierce.Entrance, tierce.Group, tierce.Monster, tierce.Event
}
}
s.ChallengeRun = &challengeRun{ID: req.challengeID, Tierce: true, Single: req.isSingleStage, Node: req.stageIndex, Rooms: rooms, Scores: make([]uint32, len(rooms)), TierceCur: rsp.CurTierceChallenge}
// A single-node retry keeps the other nodes' measured best results.
if req.isSingleStage {
if saved := s.JSON().TierceProgress[req.challengeID]; saved != nil {
for node, result := range saved.Nodes {
if node < tierceNodeCount && node != req.stageIndex {
s.ChallengeRun.Scores[node] = result.Score
s.ChallengeRun.Results = append(s.ChallengeRun.Results, tierceNodeResult(node, result, s.TierceTeams[req.challengeID][node]))
}
}
}
}
rsp.CurTierceChallenge.LineupList = []*goproto.LineupInfo{s.ChallengeRun.activeLineup(sceneInfo.PlaneId)}
return s.Send(rsp)
}
func handleSetChallengeTierceLineup(s *Session, payload []byte) error {
challengeID, stages := decodeSetChallengeTierceLineupReq(payload)
challengeID, stages, buffs := decodeSetChallengeTierceLineupReq(payload)
if challengeID == 0 {
return s.Send(&goproto.SetChallengeTierceLineupScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
}
if len(stages) > 0 {
tierceLineupsMu.Lock()
tierceLineups[challengeID] = stages
tierceLineupsMu.Unlock()
if err := rememberTierceTeams(s, challengeID, stages, buffs, false, 0); err != nil {
return err
}
return s.Send(&goproto.SetChallengeTierceLineupScRsp{})
}
@@ -556,7 +488,6 @@ func leaveChallengeScene(s *Session, response proto.Message) error {
}
// loadChallengeScene preserves the player's saved overworld scene.
entryID := player.Scene.EntryID
player.BattleConfig.CustomBattleLineup = nil
CURRENT_BATTLE.Delete(s.Token)
if err := s.Send(&goproto.QuitBattleScNotify{}); err != nil {
return err
@@ -578,51 +509,15 @@ func handleTakeChallengeReward(s *Session, payload []byte) error {
return s.Send(&goproto.TakeChallengeRewardScRsp{GroupId: req.GroupId})
}
// buildGetChallengeTierceDataScRspReal builds the generated current response.
func buildGetChallengeTierceDataScRspReal() *goproto.GetChallengeTierceDataScRsp {
cd := getChallengeData()
type tierceEntry struct {
id uint32
targets []uint32
score uint32
}
var tierceStages []tierceEntry
for id, data := range cd.Tierce {
var targets []uint32
switch {
case len(data.Targets) > 0:
targets = data.Targets
case id >= 30000:
targets = []uint32{5001, 5002, 5003, 5000}
case id >= 20000:
targets = []uint32{4001, 4002, 4003, 4000}
default:
targets = []uint32{601, 602, 603, 600}
// Return saved teams and measured scores, including genuinely unplayed stages.
func buildGetChallengeTierceDataScRspReal(s *Session) *goproto.GetChallengeTierceDataScRsp {
rsp := &goproto.GetChallengeTierceDataScRsp{}
for _, id := range sortedU32Keys2(getChallengeData().Tierce) {
var saved *common.TierceProgress
if p := s.JSON(); p != nil {
saved = p.TierceProgress[id]
}
score := data.Score
if score == 0 {
if id >= 20000 && id < 30000 {
score = 40000
} else {
score = 4000
}
}
tierceStages = append(tierceStages, tierceEntry{id, targets, score})
}
sort.Slice(tierceStages, func(i, j int) bool { return tierceStages[i].id < tierceStages[j].id })
var groups []*goproto.ChallengeTierceGroup
for _, e := range tierceStages {
groups = append(groups, &goproto.ChallengeTierceGroup{
ChallengeId: e.id,
IsWin: true,
FinishedTargetList: e.targets,
})
}
return &goproto.GetChallengeTierceDataScRsp{
ChallengeTierceGroups: groups,
Retcode: 0,
rsp.ChallengeTierceGroups = append(rsp.ChallengeTierceGroups, buildTierceGroup(id, saved))
}
return rsp
}