Save endgame progress, team presets, technique support and camera controls
This commit is contained in:
1 parent
a681affdb3
commit
639cef7d19
52 files changed
+11940
-766
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user