2026-10-17 23:44:36 +07:00
|
|
|
package gameserver
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"gac-hkrpg-go/internal/common"
|
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
|
|
goproto "gac-hkrpg-go/protocol/proto"
|
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type challengeRoom struct {
|
|
|
|
|
Entrance, Group, Monster, Event uint32
|
|
|
|
|
Avatars []uint32
|
|
|
|
|
Buffs []uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PearlSR 005d2c12 challenge_mgr.zig defines the three Tierce nodes 0..2.
|
|
|
|
|
const tierceNodeCount = 3
|
|
|
|
|
|
|
|
|
|
func rememberTierceTeams(s *Session, id uint32, teams [][]uint32, buffs []uint32, single bool, node uint32) error {
|
|
|
|
|
if s.TierceTeams == nil {
|
|
|
|
|
s.TierceTeams = map[uint32][][]uint32{}
|
|
|
|
|
}
|
|
|
|
|
if s.TierceBuffs == nil {
|
|
|
|
|
s.TierceBuffs = map[uint32][]uint32{}
|
|
|
|
|
}
|
|
|
|
|
p := s.JSON()
|
|
|
|
|
if p == nil {
|
|
|
|
|
return fmt.Errorf("player data is unavailable")
|
|
|
|
|
}
|
|
|
|
|
if p.TierceProgress == nil {
|
|
|
|
|
p.TierceProgress = map[uint32]*common.TierceProgress{}
|
|
|
|
|
}
|
|
|
|
|
saved := p.TierceProgress[id]
|
|
|
|
|
if saved == nil {
|
|
|
|
|
saved = &common.TierceProgress{Nodes: map[uint32]common.TierceNodeProgress{}}
|
|
|
|
|
p.TierceProgress[id] = saved
|
|
|
|
|
}
|
|
|
|
|
if s.TierceTeams[id] == nil {
|
|
|
|
|
s.TierceTeams[id] = saved.Teams
|
|
|
|
|
}
|
|
|
|
|
if s.TierceBuffs[id] == nil {
|
|
|
|
|
s.TierceBuffs[id] = saved.Buffs
|
|
|
|
|
}
|
|
|
|
|
if len(s.TierceTeams[id]) != tierceNodeCount {
|
|
|
|
|
s.TierceTeams[id] = make([][]uint32, tierceNodeCount)
|
|
|
|
|
}
|
|
|
|
|
if len(s.TierceBuffs[id]) != tierceNodeCount {
|
|
|
|
|
s.TierceBuffs[id] = make([]uint32, tierceNodeCount)
|
|
|
|
|
}
|
|
|
|
|
for index, team := range teams {
|
|
|
|
|
target := index
|
|
|
|
|
if single && len(teams) == 1 {
|
|
|
|
|
target = int(node)
|
|
|
|
|
}
|
|
|
|
|
if target >= tierceNodeCount {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if len(team) > 0 {
|
|
|
|
|
s.TierceTeams[id][target] = append([]uint32(nil), dedupeTruncate4(team)...)
|
|
|
|
|
}
|
|
|
|
|
if index < len(buffs) {
|
|
|
|
|
s.TierceBuffs[id][target] = buffs[index]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
saved.Teams, saved.Buffs = s.TierceTeams[id], s.TierceBuffs[id]
|
|
|
|
|
return p.SavePersistentChecked()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A run owns its teams, score and room independently of Freesr and other
|
|
|
|
|
// sessions. Rooms come from the installed ChallengeMaze/Tierce/Peak tables.
|
|
|
|
|
type challengeRun struct {
|
|
|
|
|
ID uint32
|
|
|
|
|
Tierce, Peak, Single, PendingNext, Finished bool
|
|
|
|
|
Node uint32
|
|
|
|
|
Rooms []challengeRoom
|
|
|
|
|
Scores []uint32
|
|
|
|
|
Rounds uint32
|
|
|
|
|
RoundCosts []uint32
|
|
|
|
|
Cur *goproto.CurChallenge
|
|
|
|
|
TierceCur *goproto.CurTierceChallenge
|
|
|
|
|
PeakBuff uint32
|
|
|
|
|
Results []*goproto.PMMOFOBCINL
|
|
|
|
|
Win bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resourceRoom(stage ChallengeStageData, second bool, avatars []uint32, buff uint32) challengeRoom {
|
|
|
|
|
r := challengeRoom{Entrance: stage.Entrance, Group: stage.Group1, Monster: stage.Monster1, Event: stage.Event1}
|
|
|
|
|
if second {
|
|
|
|
|
r.Entrance, r.Group, r.Monster, r.Event = stage.Entrance2, stage.Group2, stage.Monster2, stage.Event2
|
|
|
|
|
}
|
|
|
|
|
r.Avatars = append([]uint32(nil), dedupeTruncate4(avatars)...)
|
|
|
|
|
r.Buffs = []uint32{buff, stage.Buff}
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (run *challengeRun) lineupType() goproto.ExtraLineupType {
|
|
|
|
|
// Each Tierce node has its own protocol team. Updating team 1 while the
|
|
|
|
|
// client switches to team 3 leaves an empty TeamData and leader slot -1.
|
|
|
|
|
if run.Tierce {
|
|
|
|
|
switch run.Node {
|
|
|
|
|
case 1:
|
|
|
|
|
return goproto.ExtraLineupType_ExtraLineupType_LineupChallenge2
|
|
|
|
|
case 2:
|
|
|
|
|
return goproto.ExtraLineupType_ExtraLineupType_LineupChallenge3
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !run.Tierce && !run.Peak && run.Node > 0 {
|
|
|
|
|
return goproto.ExtraLineupType_ExtraLineupType_LineupChallenge2
|
|
|
|
|
}
|
|
|
|
|
return goproto.ExtraLineupType_ExtraLineupType_LineupChallenge
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (run *challengeRun) activeLineup(plane uint32) *goproto.LineupInfo {
|
|
|
|
|
return lineupInfoFromAvatars(run.Rooms[run.Node].Avatars, plane, run.lineupType(), 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func challengeCurBuffs(id uint32, buffs []uint32) *goproto.ChallengeCurBuffInfo {
|
|
|
|
|
var valid []uint32
|
|
|
|
|
seen := map[uint32]bool{}
|
|
|
|
|
for _, buff := range buffs {
|
|
|
|
|
if buff != 0 && !seen[buff] {
|
|
|
|
|
valid = append(valid, buff)
|
|
|
|
|
seen[buff] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
switch {
|
|
|
|
|
case id > 20000 && id < 30000:
|
|
|
|
|
return &goproto.ChallengeCurBuffInfo{CurStoryBuffs: &goproto.ChallengeStoryBuffList{BuffList: valid}}
|
|
|
|
|
case id > 30000:
|
|
|
|
|
return &goproto.ChallengeCurBuffInfo{CurBossBuffs: &goproto.ChallengeBossBuffList{BuffList: valid, ChallengeBossConst: 1}}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func advanceChallengeRoom(s *Session) (*goproto.SceneInfo, error) {
|
|
|
|
|
run := s.ChallengeRun
|
|
|
|
|
if run == nil || !run.PendingNext || int(run.Node)+1 >= len(run.Rooms) {
|
|
|
|
|
return nil, fmt.Errorf("challenge has no pending next room")
|
|
|
|
|
}
|
|
|
|
|
next := run.Node + 1
|
|
|
|
|
r := run.Rooms[next]
|
|
|
|
|
if len(r.Avatars) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("challenge %d node %d has no saved team", run.ID, next)
|
|
|
|
|
}
|
|
|
|
|
previousConfig := s.ChallengeBattleConfig
|
|
|
|
|
if err := setChallengeBattleConfig(s, r.Avatars, run.ID, r.Event, r.Buffs); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-10-28 18:54:43 +07:00
|
|
|
scene, _, _, err := loadChallengeScene(s, r.Entrance, r.Group, r.Monster, r.Event, r.Avatars)
|
2026-10-17 23:44:36 +07:00
|
|
|
if err != nil {
|
|
|
|
|
s.ChallengeBattleConfig = previousConfig
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
run.Node, run.PendingNext = next, false
|
|
|
|
|
if run.Cur != nil {
|
|
|
|
|
run.Cur.ExtraLineupType = run.lineupType()
|
|
|
|
|
run.Cur.StageInfo = challengeCurBuffs(run.ID, r.Buffs)
|
|
|
|
|
}
|
|
|
|
|
if run.TierceCur != nil {
|
|
|
|
|
run.TierceCur.FINOCEGDBDI = next
|
|
|
|
|
run.TierceCur.LineupList = []*goproto.LineupInfo{run.activeLineup(scene.PlaneId)}
|
|
|
|
|
}
|
|
|
|
|
SceneDebugLog("[CHALLENGE] advanced id=%d tierce=%t node=%d entry=%d stage=%d team=%v", run.ID, run.Tierce, next, r.Entrance, r.Event, r.Avatars)
|
|
|
|
|
return scene, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sendChallengeRoom(s *Session, scene *goproto.SceneInfo) error {
|
|
|
|
|
run := s.ChallengeRun
|
|
|
|
|
lineup := run.activeLineup(scene.PlaneId)
|
|
|
|
|
if err := s.Send(&goproto.SyncLineupNotify{Lineup: lineup}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if !run.Tierce && !run.Peak {
|
|
|
|
|
if err := s.Send(&goproto.ChallengeLineupNotify{ExtraLineupType: run.lineupType()}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return s.Send(&goproto.EnterSceneByServerScNotify{Scene: scene, Lineup: lineup})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
handle("EnterChallengeNextPhase", func(s *Session, _ *goproto.EnterChallengeNextPhaseCsReq, rsp *goproto.EnterChallengeNextPhaseScRsp) error {
|
|
|
|
|
if run := s.ChallengeRun; run == nil || run.Tierce || run.Peak {
|
|
|
|
|
rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
scene, err := advanceChallengeRoom(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
rsp.Scene = scene
|
|
|
|
|
return s.Send(&goproto.SyncLineupNotify{Lineup: s.ChallengeRun.activeLineup(scene.PlaneId)})
|
|
|
|
|
})
|
|
|
|
|
// PearlSR 005d2c12 retains obfuscated command names. Client callback
|
|
|
|
|
// metadata confirms <StartNext>b__25_0(LHAKNKHBLBI) and
|
|
|
|
|
// <Restart>b__21_0(JMPJCAPKNGI); resolve IDs by these table names.
|
|
|
|
|
rawNamed("MHJNOBBOFOC", func(s *Session, _ []byte) error {
|
|
|
|
|
if run := s.ChallengeRun; run == nil || !run.Tierce {
|
|
|
|
|
return s.Send(&goproto.LHAKNKHBLBI{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
|
|
|
}
|
|
|
|
|
scene, err := advanceChallengeRoom(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return s.Send(&goproto.LHAKNKHBLBI{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
|
|
|
}
|
|
|
|
|
if err := s.Send(&goproto.SyncLineupNotify{Lineup: s.ChallengeRun.activeLineup(scene.PlaneId)}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return s.Send(&goproto.LHAKNKHBLBI{Scene: scene, CurTierceChallenge: s.ChallengeRun.TierceCur})
|
|
|
|
|
})
|
|
|
|
|
rawNamed("PKKFNODINEM", func(s *Session, _ []byte) error {
|
|
|
|
|
scene, err := restartChallengeRoom(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return s.Send(&goproto.JMPJCAPKNGI{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)})
|
|
|
|
|
}
|
|
|
|
|
return s.Send(&goproto.JMPJCAPKNGI{Scene: scene})
|
|
|
|
|
})
|
|
|
|
|
rawNamed("MENLHHCJNCE", func(s *Session, _ []byte) error { return s.Send(&goproto.OKEDFNJJGHB{}) })
|
|
|
|
|
handle("RestartChallengePhase", func(s *Session, _ *goproto.RestartChallengePhaseCsReq, rsp *goproto.RestartChallengePhaseScRsp) error {
|
|
|
|
|
scene, err := restartChallengeRoom(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)
|
|
|
|
|
} else {
|
|
|
|
|
rsp.Scene = scene
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func restartChallengeRoom(s *Session) (*goproto.SceneInfo, error) {
|
|
|
|
|
run := s.ChallengeRun
|
|
|
|
|
if run == nil {
|
|
|
|
|
return nil, fmt.Errorf("no challenge to restart")
|
|
|
|
|
}
|
|
|
|
|
r := run.Rooms[run.Node]
|
2026-10-21 20:40:13 +07:00
|
|
|
configure := setChallengeBattleConfig
|
|
|
|
|
if run.Peak {
|
|
|
|
|
configure = setPeakBattleConfig
|
|
|
|
|
}
|
|
|
|
|
if err := configure(s, r.Avatars, run.ID, r.Event, r.Buffs); err != nil {
|
2026-10-17 23:44:36 +07:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-10-28 18:54:43 +07:00
|
|
|
scene, _, _, err := loadChallengeScene(s, r.Entrance, r.Group, r.Monster, r.Event, r.Avatars)
|
2026-10-17 23:44:36 +07:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
run.Finished, run.PendingNext = false, false
|
|
|
|
|
run.Win = false
|
|
|
|
|
run.Scores[run.Node] = 0
|
|
|
|
|
if len(run.RoundCosts) > int(run.Node) {
|
|
|
|
|
run.RoundCosts[run.Node] = 0
|
|
|
|
|
}
|
|
|
|
|
run.Rounds = 0
|
|
|
|
|
for _, rounds := range run.RoundCosts {
|
|
|
|
|
run.Rounds += rounds
|
|
|
|
|
}
|
|
|
|
|
var results []*goproto.PMMOFOBCINL
|
|
|
|
|
for _, result := range run.Results {
|
|
|
|
|
if result.FINOCEGDBDI != run.Node {
|
|
|
|
|
results = append(results, result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
run.Results = results
|
|
|
|
|
if run.Cur != nil {
|
|
|
|
|
run.Cur.Status = goproto.ChallengeStatus_CHALLENGE_DOING
|
|
|
|
|
run.Cur.RoundCount = run.Rounds
|
|
|
|
|
run.Cur.ScoreId = run.Scores[0]
|
|
|
|
|
if len(run.Scores) > 1 {
|
|
|
|
|
run.Cur.ScoreTwo = run.Scores[1]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CURRENT_BATTLE.Delete(s.Token)
|
|
|
|
|
return scene, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func finishTierceNode(s *Session, req *goproto.PVEBattleResultCsReq) error {
|
|
|
|
|
run := s.ChallengeRun
|
|
|
|
|
lineup := &goproto.GLLFJFJBIBC{}
|
|
|
|
|
for slot, id := range run.Rooms[run.Node].Avatars {
|
|
|
|
|
avatar := s.JSON().Avatars[id]
|
|
|
|
|
if avatar == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
info := &goproto.CLDOIEGCNLD{AvatarId: id, AvatarType: uint32(goproto.AvatarType_AVATAR_FORMAL_TYPE), Slot: uint32(slot), Level: avatar.Level, PAGPNOKLNOM: &goproto.ChallengeBossEquipmentInfo{}, OKCGHKDMFAM: &goproto.ChallengeBossAvatarRelicInfo{}}
|
|
|
|
|
if cone := s.JSON().FindLightconeFor(id); cone != nil {
|
|
|
|
|
info.PAGPNOKLNOM = &goproto.ChallengeBossEquipmentInfo{UniqueId: cone.InternalUID, Tid: cone.ItemID, Level: cone.Level, Promotion: cone.Promotion, Rank: cone.Rank}
|
|
|
|
|
}
|
|
|
|
|
lineup.MNABNJOEHLF = append(lineup.MNABNJOEHLF, info)
|
|
|
|
|
}
|
|
|
|
|
result := &goproto.PMMOFOBCINL{FINOCEGDBDI: run.Node, ScoreId: run.Scores[run.Node], EndStatus: req.EndStatus, Lineup: lineup}
|
|
|
|
|
if req.Stt != nil {
|
|
|
|
|
for _, targets := range req.Stt.BattleTargetInfo {
|
|
|
|
|
result.BattleTargetList = append(result.BattleTargetList, targets.GetBattleTargetList()...)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
run.Results = append(run.Results, result)
|
|
|
|
|
if err := saveTierceResult(s, result); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var total uint32
|
|
|
|
|
for _, score := range run.Scores {
|
|
|
|
|
total += score
|
|
|
|
|
}
|
|
|
|
|
allWon := len(run.Results) == tierceNodeCount
|
|
|
|
|
for _, nodeResult := range run.Results {
|
|
|
|
|
if nodeResult.EndStatus != goproto.BattleEndStatus_BATTLE_END_WIN {
|
|
|
|
|
allWon = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var finishedTargets []uint32
|
|
|
|
|
if run.Finished && allWon {
|
|
|
|
|
finishedTargets = tierceTargets(run.ID, total)
|
|
|
|
|
}
|
|
|
|
|
win := req.EndStatus == goproto.BattleEndStatus_BATTLE_END_WIN
|
|
|
|
|
if run.Finished {
|
|
|
|
|
win = win && allWon
|
|
|
|
|
}
|
|
|
|
|
if run.Finished && getChallengeData().Tierce[run.ID].ClearScore > 0 {
|
|
|
|
|
win = win && total >= getChallengeData().Tierce[run.ID].ClearScore
|
|
|
|
|
}
|
|
|
|
|
run.Win = win
|
|
|
|
|
// Verified Client events: this oneof is OnStageSettle; the field-8
|
|
|
|
|
// alternative is OnMedal and must not be used to switch stages.
|
|
|
|
|
if err := s.Send(&goproto.DOPHNDHILPA{MMCGBGDJIPN: &goproto.DOPHNDHILPA_IANBCDJPDJF{IANBCDJPDJF: &goproto.JCKGBFJJFFC{PAIAEPJIDPE: result, IsWin: win, FinishedTargetList: finishedTargets}}}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if run.Finished {
|
|
|
|
|
sort.Slice(run.Results, func(i, j int) bool { return run.Results[i].FINOCEGDBDI < run.Results[j].FINOCEGDBDI })
|
|
|
|
|
var score uint32
|
|
|
|
|
for _, value := range run.Scores {
|
|
|
|
|
score += value
|
|
|
|
|
}
|
|
|
|
|
if err := s.Send(&goproto.DOPHNDHILPA{MMCGBGDJIPN: &goproto.DOPHNDHILPA_DFNNKENBOEN{DFNNKENBOEN: &goproto.GEKNAKDAMJF{ChallengeId: run.ID, IsWin: win, FinishedTargetList: finishedTargets, EGEHLOPFELK: &goproto.ChallengeTierceRecord{TotalScore: score}, GBMBAHHDONN: run.Results}}}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return errResponseAlreadySent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Scores are supplied by the Client; Tierce targets are evaluated against the
|
|
|
|
|
// installed target table. A win alone does not grant score targets or rewards.
|
|
|
|
|
func finishChallengeBattle(s *Session, req *goproto.PVEBattleResultCsReq, rsp *goproto.PVEBattleResultScRsp, active bool) error {
|
|
|
|
|
run := s.ChallengeRun
|
|
|
|
|
if !active || run == nil || run.Finished || run.PendingNext || req.StageId != run.Rooms[run.Node].Event {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if req.EndStatus == goproto.BattleEndStatus_BATTLE_END_QUIT {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if req.Stt != nil {
|
|
|
|
|
run.Scores[run.Node] = req.Stt.ChallengeScore
|
|
|
|
|
if len(run.RoundCosts) != len(run.Rooms) {
|
|
|
|
|
run.RoundCosts = make([]uint32, len(run.Rooms))
|
|
|
|
|
}
|
|
|
|
|
run.RoundCosts[run.Node] = req.Stt.RoundCnt
|
|
|
|
|
run.Rounds = 0
|
|
|
|
|
for _, rounds := range run.RoundCosts {
|
|
|
|
|
run.Rounds += rounds
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if run.Cur != nil {
|
|
|
|
|
run.Cur.RoundCount = run.Rounds
|
|
|
|
|
run.Cur.ScoreId = run.Scores[0]
|
|
|
|
|
if len(run.Scores) > 1 {
|
|
|
|
|
run.Cur.ScoreTwo = run.Scores[1]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SceneDebugLog("[CHALLENGE] result id=%d node=%d status=%s stage=%d scores=%v rounds=%d", run.ID, run.Node, req.EndStatus, req.StageId, run.Scores, run.Rounds)
|
|
|
|
|
if err := s.Send(rsp); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
win := req.EndStatus == goproto.BattleEndStatus_BATTLE_END_WIN
|
|
|
|
|
run.Win = win
|
|
|
|
|
if win && !run.Single && int(run.Node)+1 < len(run.Rooms) {
|
|
|
|
|
run.PendingNext = true
|
|
|
|
|
if run.Tierce {
|
|
|
|
|
return finishTierceNode(s, req)
|
|
|
|
|
}
|
|
|
|
|
scene, err := advanceChallengeRoom(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := sendChallengeRoom(s, scene); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return errResponseAlreadySent
|
|
|
|
|
}
|
|
|
|
|
run.Finished = true
|
|
|
|
|
if run.Tierce {
|
|
|
|
|
return finishTierceNode(s, req)
|
|
|
|
|
}
|
|
|
|
|
if run.Peak {
|
|
|
|
|
if err := s.Send(&goproto.ChallengePeakSettleScNotify{PeakId: run.ID, IsWin: win, CyclesUsed: run.Rounds}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return errResponseAlreadySent
|
|
|
|
|
}
|
|
|
|
|
if run.Cur != nil {
|
|
|
|
|
run.Cur.Status = goproto.ChallengeStatus_CHALLENGE_FAILED
|
|
|
|
|
if win {
|
|
|
|
|
run.Cur.Status = goproto.ChallengeStatus_CHALLENGE_FINISH
|
|
|
|
|
}
|
|
|
|
|
if err := s.Send(&goproto.ChallengeSettleNotify{ChallengeId: run.ID, IsWin: win, CurChallenge: proto.Clone(run.Cur).(*goproto.CurChallenge), Reward: &goproto.ItemList{}, ChallengeScore: run.Cur.ScoreId, ScoreTwo: run.Cur.ScoreTwo}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return errResponseAlreadySent
|
|
|
|
|
}
|