524 lines
18 KiB
Go
524 lines
18 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"gac-hkrpg-go/internal/common"
|
|
goproto "gac-hkrpg-go/protocol/proto"
|
|
)
|
|
|
|
// Challenge flows use the generated request and response layouts. Command
|
|
// numbers are resolved through the current protocol table at registration/send.
|
|
|
|
type decodedChallengeStart struct {
|
|
challengeID uint32
|
|
stageIndex uint32
|
|
isSingleStage bool
|
|
firstAvatars []uint32
|
|
secondAvatars []uint32
|
|
thirdAvatars []uint32
|
|
stageInfoList [][]uint32
|
|
buffID uint32
|
|
buffIDs []uint32
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// protobuf wire helpers (mirroring the Rust prost decoding loops)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func skipWireField(wireType uint32, buf *[]byte) {
|
|
b := *buf
|
|
switch wireType {
|
|
case 0:
|
|
_, n := readUvarint(b)
|
|
if n == 0 {
|
|
*buf = nil
|
|
} else {
|
|
*buf = b[n:]
|
|
}
|
|
case 1:
|
|
if len(b) >= 8 {
|
|
*buf = b[8:]
|
|
} else {
|
|
*buf = nil
|
|
}
|
|
case 2:
|
|
l, n := readUvarint(b)
|
|
if n == 0 || int(l) > len(b[n:]) {
|
|
*buf = nil
|
|
} else {
|
|
*buf = b[n+int(l):]
|
|
}
|
|
case 5:
|
|
if len(b) >= 4 {
|
|
*buf = b[4:]
|
|
} else {
|
|
*buf = nil
|
|
}
|
|
default:
|
|
*buf = nil
|
|
}
|
|
}
|
|
|
|
func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
|
|
var pbReq goproto.StartChallengeCsReq
|
|
if err := proto.Unmarshal(payload, &pbReq); err != nil {
|
|
return decodedChallengeStart{}
|
|
}
|
|
req := decodedChallengeStart{challengeID: pbReq.ChallengeId,
|
|
firstAvatars: pbReq.FirstLineup, secondAvatars: pbReq.SecondLineup}
|
|
if len(req.firstAvatars) == 0 {
|
|
for _, avatar := range pbReq.AvatarLineupFirst {
|
|
if avatar.Id != 0 {
|
|
req.firstAvatars = append(req.firstAvatars, avatar.Id)
|
|
}
|
|
}
|
|
}
|
|
if len(req.secondAvatars) == 0 {
|
|
for _, avatar := range pbReq.AvatarLineupSecond {
|
|
if avatar.Id != 0 {
|
|
req.secondAvatars = append(req.secondAvatars, avatar.Id)
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
|
|
var pbReq goproto.StartChallengeTierceCsReq
|
|
if err := proto.Unmarshal(payload, &pbReq); err != nil {
|
|
return 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 {
|
|
avatars = append(avatars, avatar.Id)
|
|
}
|
|
}
|
|
req.stageInfoList = append(req.stageInfoList, avatars)
|
|
if uint32(index) == req.stageIndex {
|
|
req.buffID = stage.BuffId
|
|
}
|
|
}
|
|
if len(req.stageInfoList) > 0 {
|
|
req.firstAvatars = req.stageInfoList[0]
|
|
}
|
|
if len(req.stageInfoList) > 1 {
|
|
req.secondAvatars = req.stageInfoList[1]
|
|
}
|
|
if len(req.stageInfoList) > 2 {
|
|
req.thirdAvatars = req.stageInfoList[2]
|
|
}
|
|
return req
|
|
}
|
|
|
|
func decodeSetChallengeTierceLineupReq(payload []byte) (uint32, [][]uint32, []uint32) {
|
|
var req goproto.SetChallengeTierceLineupCsReq
|
|
if err := proto.Unmarshal(payload, &req); err != 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 {
|
|
avatars = append(avatars, avatar.Id)
|
|
}
|
|
}
|
|
stages = append(stages, avatars)
|
|
}
|
|
return req.ChallengeId, stages, buffs
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// config setup shared by start handlers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// 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)
|
|
}
|
|
config := common.BattleConfig{StageID: eventID, CustomBattleLineup: map[uint32]uint32{}}
|
|
for i, id := range avatars {
|
|
config.CustomBattleLineup[uint32(i)] = id
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
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 {
|
|
custom := map[uint32]uint32{}
|
|
for i, aid := range avatars {
|
|
custom[uint32(i)] = aid
|
|
}
|
|
lineup := common.ToLineupInfo(custom)
|
|
lineup.PlaneId = planeID
|
|
lineup.ExtraLineupType = extraType
|
|
lineup.Index = index
|
|
return lineup
|
|
}
|
|
|
|
func challengeAvatarsFallback(s *Session) []uint32 {
|
|
if player := s.JSON(); player != nil {
|
|
var out []uint32
|
|
for _, slot := range player.SortedLineupKeys() {
|
|
if v := player.Lineups[slot]; v != 0 {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func dedupeTruncate4(in []uint32) []uint32 {
|
|
seen := map[uint32]bool{}
|
|
var out []uint32
|
|
for _, v := range in {
|
|
if !seen[v] {
|
|
seen[v] = true
|
|
out = append(out, v)
|
|
}
|
|
if len(out) == 4 {
|
|
break
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// handleStartChallenge ports handle_start_challenge: respond cmd 1787 with
|
|
// retcode(3) + cur_challenge(5) + lineup_list(6) + scene(10) + stage_info(13).
|
|
func handleStartChallenge(s *Session, payload []byte) error {
|
|
req := decodeStartChallengeReq(payload)
|
|
if req.challengeID == 0 {
|
|
return s.Send(&goproto.StartChallengeScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
}
|
|
Log("handle_start_challenge: challenge_id=" + itoa(int(req.challengeID)))
|
|
|
|
chosenAvatars := req.firstAvatars
|
|
if len(chosenAvatars) == 0 {
|
|
chosenAvatars = challengeAvatarsFallback(s)
|
|
}
|
|
// cap team at 4 (client TeamManager IndexOutOfRange otherwise)
|
|
chosenAvatars = dedupeTruncate4(chosenAvatars)
|
|
|
|
baseID := req.challengeID
|
|
cd := getChallengeData()
|
|
stage, ok := cd.Challenges[baseID]
|
|
if !ok {
|
|
return s.Send(&goproto.StartChallengeScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
}
|
|
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)))
|
|
|
|
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 {
|
|
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
|
|
lineups = append(lineups, lineupInfoFromAvatars(chosenAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), 0))
|
|
|
|
secondAvatars := req.secondAvatars
|
|
if len(secondAvatars) == 0 {
|
|
secondAvatars = chosenAvatars
|
|
}
|
|
secondAvatars = dedupeTruncate4(secondAvatars)
|
|
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: 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,
|
|
CurChallenge: curChallenge,
|
|
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)
|
|
}
|
|
|
|
// 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 := req.challengeID
|
|
Log("handle_start_challenge_tierce: challenge_id=" + itoa(int(req.challengeID)) + " (mapped " + itoa(int(baseID)) + ")")
|
|
|
|
if err := rememberTierceTeams(s, req.challengeID, req.stageInfoList, req.buffIDs, req.isSingleStage, req.stageIndex); err != nil {
|
|
return err
|
|
}
|
|
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 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 {
|
|
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_LINEUP_IS_EMPTY)})
|
|
}
|
|
|
|
cd := getChallengeData()
|
|
// 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)})
|
|
}
|
|
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
|
|
}
|
|
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 {
|
|
s.ChallengeBattleConfig = previousConfig
|
|
SceneDebugLog("[CHALLENGE] tierce scene rejected: %v", err)
|
|
return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
}
|
|
|
|
// 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{
|
|
Retcode: 0,
|
|
Scene: sceneInfo,
|
|
CurTierceChallenge: &goproto.CurTierceChallenge{
|
|
LineupList: tierceLineupList,
|
|
ChallengeId: req.challengeID,
|
|
FINOCEGDBDI: req.stageIndex,
|
|
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, buffs := decodeSetChallengeTierceLineupReq(payload)
|
|
if challengeID == 0 {
|
|
return s.Send(&goproto.SetChallengeTierceLineupScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
}
|
|
if err := rememberTierceTeams(s, challengeID, stages, buffs, false, 0); err != nil {
|
|
return err
|
|
}
|
|
return s.Send(&goproto.SetChallengeTierceLineupScRsp{})
|
|
}
|
|
|
|
func handleLeaveChallenge(s *Session, _ []byte) error {
|
|
return leaveChallengeScene(s, &goproto.LeaveChallengeScRsp{})
|
|
}
|
|
|
|
func handleLeaveChallengeTierce(s *Session, _ []byte) error {
|
|
return leaveChallengeScene(s, &goproto.LeaveChallengeTierceScRsp{})
|
|
}
|
|
|
|
func leaveChallengeScene(s *Session, response proto.Message) error {
|
|
player := s.JSON()
|
|
if player == nil || player.Scene.EntryID == 0 {
|
|
return fmt.Errorf("challenge return scene is unavailable")
|
|
}
|
|
// loadChallengeScene preserves the player's saved overworld scene.
|
|
entryID := player.Scene.EntryID
|
|
CURRENT_BATTLE.Delete(s.Token)
|
|
if err := s.Send(&goproto.QuitBattleScNotify{}); err != nil {
|
|
return err
|
|
}
|
|
if _, err := loadScene(s, entryID, true, true, nil, nil); err != nil {
|
|
return err
|
|
}
|
|
if err := refreshLineup(s); err != nil {
|
|
return err
|
|
}
|
|
return s.Send(response)
|
|
}
|
|
|
|
func handleTakeChallengeReward(s *Session, payload []byte) error {
|
|
var req goproto.TakeChallengeRewardCsReq
|
|
if err := proto.Unmarshal(payload, &req); err != nil {
|
|
return err
|
|
}
|
|
return s.Send(&goproto.TakeChallengeRewardScRsp{GroupId: req.GroupId})
|
|
}
|
|
|
|
// 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]
|
|
}
|
|
rsp.ChallengeTierceGroups = append(rsp.ChallengeTierceGroups, buildTierceGroup(id, saved))
|
|
}
|
|
return rsp
|
|
}
|