feat: port GaC-hkrpg-ps to Go with SceneCastSkill camera-detach fix
- mihoyo-variant KCP in pure Go (28-byte conv+token header), UDP gateway with 0xFF/0x145/0x194 handshake, ms-precision KCP clock + retransmit ticker - protobuf for 4.6.51 generated from the AstralOS StarRail.proto (4932 messages) plus CmdID map from // CmdID: comments (2114 ids) - sdkserver port: query_dispatch/query_gateway (Gateserver pb + base64), hotfix fetch, auth endpoints, SRTools freesr-data.json upload - gameserver handlers: login flow, scene load/spawn with real monster entity tracking, lineup, avatar/skin, missions, tutorials (1249+855), bag/equip, challenge incl. StartChallenge 1787 layout + Tierce 8973 + peak 8948, all raw tierce replies - SceneCastSkill camera bug fix: only real scene monsters (tracked per session) trigger battle; attacking props/NPCs/air replies retcode 0 without battle_info so the camera stays locked on the character - end-to-end smoke test simulating a client over real UDP: handshake, PlayerGetToken, and both SceneCastSkill paths - reuses the Rust PS data files unchanged (freesr-data.json, persistent, res.json, teleports.json, challenge_data.json, mission_unlocks.json)
This commit is contained in:
commit
59ea97b1a6
45 files changed
+1963868
No files matched your search
@@ -0,0 +1,997 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
goproto "gac-hkrpg-go/protocol/proto"
|
||||
)
|
||||
|
||||
// Full Go port of challenge.rs: StartChallenge / Tierce / Peak flows with the
|
||||
// hand-built 4.6.51 response layouts (cmd 1787 / 8973).
|
||||
|
||||
type decodedChallengeStart struct {
|
||||
challengeID uint32
|
||||
stageIndex uint32
|
||||
isSingleStage bool
|
||||
firstAvatars []uint32
|
||||
secondAvatars []uint32
|
||||
thirdAvatars []uint32
|
||||
stageInfoList [][]uint32
|
||||
buffID 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 mapChallengeStageID(id uint32) uint32 {
|
||||
switch {
|
||||
case id >= 5400 && id <= 5600: // 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:
|
||||
cd := getChallengeData()
|
||||
if _, ok := cd.Challenges[id]; !ok {
|
||||
return id - 1
|
||||
}
|
||||
return id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func decodeStartChallengeReq(payload []byte) decodedChallengeStart {
|
||||
var req decodedChallengeStart
|
||||
buf := payload
|
||||
|
||||
for len(buf) > 0 {
|
||||
tag, n := readUvarint(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n:]
|
||||
fieldNumber := uint32(tag >> 3)
|
||||
wireType := uint32(tag & 0x7)
|
||||
|
||||
switch {
|
||||
case (fieldNumber == 14 || fieldNumber == 12) && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
if req.challengeID == 0 {
|
||||
req.challengeID = uint32(v)
|
||||
}
|
||||
case fieldNumber == 6 && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
req.firstAvatars = append(req.firstAvatars, uint32(v))
|
||||
case fieldNumber == 6 && wireType == 2:
|
||||
sub, ok := readLenDelim(buf)
|
||||
if !ok {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[len(buf)-len(sub):] // consumed below
|
||||
buf = buf[len(sub):]
|
||||
for len(sub) > 0 {
|
||||
v, n3 := readUvarint(sub)
|
||||
if n3 == 0 {
|
||||
break
|
||||
}
|
||||
sub = sub[n3:]
|
||||
req.firstAvatars = append(req.firstAvatars, uint32(v))
|
||||
}
|
||||
case fieldNumber == 8 && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
req.secondAvatars = append(req.secondAvatars, uint32(v))
|
||||
case fieldNumber == 8 && wireType == 2:
|
||||
sub, ok := readLenDelim(buf)
|
||||
if !ok {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[len(sub):]
|
||||
for len(sub) > 0 {
|
||||
v, n3 := readUvarint(sub)
|
||||
if n3 == 0 {
|
||||
break
|
||||
}
|
||||
sub = sub[n3:]
|
||||
req.secondAvatars = append(req.secondAvatars, uint32(v))
|
||||
}
|
||||
case fieldNumber == 4 && wireType == 2:
|
||||
// AvatarLineup: id = tag 1
|
||||
sub, ok := readLenDelim(buf)
|
||||
if !ok {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[len(sub):]
|
||||
for len(sub) > 0 {
|
||||
stag, n3 := readUvarint(sub)
|
||||
if n3 == 0 {
|
||||
break
|
||||
}
|
||||
sfn := uint32(stag >> 3)
|
||||
swt := uint32(stag & 0x7)
|
||||
if sfn == 1 && swt == 0 {
|
||||
v, n4 := readUvarint(sub[n3:])
|
||||
if n4 == 0 {
|
||||
break
|
||||
}
|
||||
sub = sub[n3+n4:]
|
||||
req.firstAvatars = append(req.firstAvatars, uint32(v))
|
||||
} else {
|
||||
skipWireField(swt, &sub)
|
||||
}
|
||||
}
|
||||
case fieldNumber == 10 && wireType == 2:
|
||||
// ChallengeBuffInfo { story_info = 9, boss_info = 10 }
|
||||
sub, ok := readLenDelim(buf)
|
||||
if !ok {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[len(sub):]
|
||||
for len(sub) > 0 {
|
||||
stag, n3 := readUvarint(sub)
|
||||
if n3 == 0 {
|
||||
break
|
||||
}
|
||||
sfn := uint32(stag >> 3)
|
||||
swt := uint32(stag & 0x7)
|
||||
sub2 := sub[n3:]
|
||||
if swt == 2 && (sfn == 9 || sfn == 10) {
|
||||
inner, ok2 := readLenDelim(sub2)
|
||||
if !ok2 {
|
||||
break
|
||||
}
|
||||
for len(inner) > 0 {
|
||||
t2, n4 := readUvarint(inner)
|
||||
if n4 == 0 {
|
||||
break
|
||||
}
|
||||
f2 := uint32(t2 >> 3)
|
||||
w2 := uint32(t2 & 0x7)
|
||||
if w2 == 0 && (f2 == 10 || f2 == 15) {
|
||||
v, n5 := readUvarint(inner[n4:])
|
||||
if n5 == 0 {
|
||||
break
|
||||
}
|
||||
inner = inner[n4+n5:]
|
||||
if req.buffID == 0 {
|
||||
req.buffID = uint32(v)
|
||||
}
|
||||
} else {
|
||||
skipWireField(w2, &inner)
|
||||
}
|
||||
}
|
||||
sub = sub2
|
||||
// skip the whole len-delimited field
|
||||
_, n6 := readUvarint(sub2)
|
||||
if n6 == 0 {
|
||||
sub = nil
|
||||
} else {
|
||||
sub = sub2[n6:]
|
||||
}
|
||||
} else {
|
||||
skipWireField(swt, &sub)
|
||||
}
|
||||
}
|
||||
default:
|
||||
skipWireField(wireType, &buf)
|
||||
}
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func readLenDelim(buf []byte) ([]byte, bool) {
|
||||
l, n := readUvarint(buf)
|
||||
if n == 0 || int(l) > len(buf[n:]) {
|
||||
return nil, false
|
||||
}
|
||||
return buf[n : n+int(l)], true
|
||||
}
|
||||
|
||||
type tierceStageEntry struct {
|
||||
buff uint32
|
||||
lineup []uint32
|
||||
}
|
||||
|
||||
func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart {
|
||||
var req decodedChallengeStart
|
||||
buf := payload
|
||||
var stagesAvatars []tierceStageEntry
|
||||
|
||||
for len(buf) > 0 {
|
||||
tag, n := readUvarint(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n:]
|
||||
fieldNumber := uint32(tag >> 3)
|
||||
wireType := uint32(tag & 0x7)
|
||||
|
||||
switch {
|
||||
case (fieldNumber == 10 || fieldNumber == 1) && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
req.stageIndex = uint32(v)
|
||||
case (fieldNumber == 15 || fieldNumber == 3) && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
req.challengeID = uint32(v)
|
||||
case (fieldNumber == 5 || fieldNumber == 13) && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
req.isSingleStage = v != 0
|
||||
case (fieldNumber == 12 || fieldNumber == 14) && wireType == 2:
|
||||
sub, ok := readLenDelim(buf)
|
||||
if !ok {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[len(sub):]
|
||||
|
||||
var stageBuff uint32
|
||||
var stageLineup []uint32
|
||||
for len(sub) > 0 {
|
||||
subTag, n2 := readUvarint(sub)
|
||||
if n2 == 0 {
|
||||
break
|
||||
}
|
||||
subFn := uint32(subTag >> 3)
|
||||
subWt := uint32(subTag & 0x7)
|
||||
switch {
|
||||
case (subFn == 6 || subFn == 5) && subWt == 0:
|
||||
v, n3 := readUvarint(sub[n2:])
|
||||
if n3 == 0 {
|
||||
sub = nil
|
||||
break
|
||||
}
|
||||
sub = sub[n2+n3:]
|
||||
stageBuff = uint32(v)
|
||||
case (subFn == 4 || subFn == 9) && subWt == 2:
|
||||
abuf, ok2 := readLenDelim(sub[n2:])
|
||||
if !ok2 {
|
||||
sub = nil
|
||||
break
|
||||
}
|
||||
sub = sub[n2+len(abuf):]
|
||||
var aid uint32
|
||||
for len(abuf) > 0 {
|
||||
atag, n4 := readUvarint(abuf)
|
||||
if n4 == 0 {
|
||||
break
|
||||
}
|
||||
afn := uint32(atag >> 3)
|
||||
awt := uint32(atag & 0x7)
|
||||
if (afn == 7 || afn == 9) && awt == 0 {
|
||||
v, n5 := readUvarint(abuf[n4:])
|
||||
if n5 == 0 {
|
||||
break
|
||||
}
|
||||
abuf = abuf[n4+n5:]
|
||||
aid = uint32(v)
|
||||
} else {
|
||||
skipWireField(awt, &abuf)
|
||||
}
|
||||
}
|
||||
if aid != 0 {
|
||||
stageLineup = append(stageLineup, aid)
|
||||
}
|
||||
default:
|
||||
skipWireField(subWt, &sub)
|
||||
}
|
||||
}
|
||||
stagesAvatars = append(stagesAvatars, tierceStageEntry{buff: stageBuff, lineup: stageLineup})
|
||||
default:
|
||||
skipWireField(wireType, &buf)
|
||||
}
|
||||
}
|
||||
|
||||
for _, st := range stagesAvatars {
|
||||
req.stageInfoList = append(req.stageInfoList, st.lineup)
|
||||
}
|
||||
if len(stagesAvatars) > 0 {
|
||||
req.firstAvatars = stagesAvatars[0].lineup
|
||||
if req.stageIndex == 0 && stagesAvatars[0].buff != 0 {
|
||||
req.buffID = stagesAvatars[0].buff
|
||||
}
|
||||
}
|
||||
if len(stagesAvatars) > 1 {
|
||||
req.secondAvatars = stagesAvatars[1].lineup
|
||||
if req.stageIndex == 1 && stagesAvatars[1].buff != 0 {
|
||||
req.buffID = stagesAvatars[1].buff
|
||||
}
|
||||
}
|
||||
if len(stagesAvatars) > 2 {
|
||||
req.thirdAvatars = stagesAvatars[2].lineup
|
||||
if req.stageIndex == 2 && stagesAvatars[2].buff != 0 {
|
||||
req.buffID = stagesAvatars[2].buff
|
||||
}
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func decodeSetChallengeTierceLineupReq(payload []byte) (uint32, [][]uint32) {
|
||||
var challengeID uint32
|
||||
var stages [][]uint32
|
||||
buf := payload
|
||||
|
||||
for len(buf) > 0 {
|
||||
tag, n := readUvarint(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n:]
|
||||
fieldNumber := uint32(tag >> 3)
|
||||
wireType := uint32(tag & 0x7)
|
||||
|
||||
switch {
|
||||
case fieldNumber == 7 && wireType == 0:
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
challengeID = uint32(v)
|
||||
case fieldNumber == 12 && wireType == 2:
|
||||
sub, ok := readLenDelim(buf)
|
||||
if !ok {
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = buf[len(sub):]
|
||||
var stageLineup []uint32
|
||||
for len(sub) > 0 {
|
||||
subTag, n2 := readUvarint(sub)
|
||||
if n2 == 0 {
|
||||
break
|
||||
}
|
||||
subFn := uint32(subTag >> 3)
|
||||
subWt := uint32(subTag & 0x7)
|
||||
if subFn == 9 && subWt == 2 {
|
||||
abuf, ok2 := readLenDelim(sub[n2:])
|
||||
if !ok2 {
|
||||
sub = nil
|
||||
break
|
||||
}
|
||||
sub = sub[n2+len(abuf):]
|
||||
var aid uint32
|
||||
for len(abuf) > 0 {
|
||||
atag, n4 := readUvarint(abuf)
|
||||
if n4 == 0 {
|
||||
break
|
||||
}
|
||||
afn := uint32(atag >> 3)
|
||||
awt := uint32(atag & 0x7)
|
||||
if afn == 9 && awt == 0 {
|
||||
v, n5 := readUvarint(abuf[n4:])
|
||||
if n5 == 0 {
|
||||
break
|
||||
}
|
||||
abuf = abuf[n4+n5:]
|
||||
aid = uint32(v)
|
||||
} else {
|
||||
skipWireField(awt, &abuf)
|
||||
}
|
||||
}
|
||||
if aid != 0 {
|
||||
stageLineup = append(stageLineup, aid)
|
||||
}
|
||||
} else {
|
||||
skipWireField(subWt, &sub)
|
||||
}
|
||||
}
|
||||
stages = append(stages, stageLineup)
|
||||
default:
|
||||
skipWireField(wireType, &buf)
|
||||
}
|
||||
}
|
||||
|
||||
return challengeID, stages
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// battle 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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
player.BattleConfig.Monsters = waves
|
||||
}
|
||||
|
||||
if buff != 0 {
|
||||
player.BattleConfig.Blessings = []common.BattleBuffJson{{
|
||||
ID: buff,
|
||||
Level: 1,
|
||||
}}
|
||||
}
|
||||
|
||||
player.SavePersistent()
|
||||
}
|
||||
|
||||
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 []uint32{1304, 1313, 1406, 1004}
|
||||
}
|
||||
|
||||
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).
|
||||
func handleStartChallenge(s *Session, payload []byte) error {
|
||||
req := decodeStartChallengeReq(payload)
|
||||
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 := mapChallengeStageID(req.challengeID)
|
||||
cd := getChallengeData()
|
||||
entrance, group, monster, event, buff := uint32(3014101), 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
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to the standard arena when the mapped one is missing from res
|
||||
if res, err := common.GameRes(); err == nil {
|
||||
if !mapHasEntry(res, entrance) {
|
||||
Log("[CHALLENGE] Arena entrance missing in res.json, fallback to 3000101")
|
||||
entrance = 3000101
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
sceneInfo, _, err := loadChallengeScene(s, entrance, group, monster, event, chosenAvatars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
custom := map[uint32]uint32{}
|
||||
for i, aid := range chosenAvatars {
|
||||
custom[uint32(i)] = aid
|
||||
}
|
||||
lineupInfo := common.ToLineupInfo(custom)
|
||||
|
||||
curChallenge := &goproto.CurChallenge{
|
||||
ChallengeId: req.challengeID,
|
||||
Status: 1, // CHALLENGE_DOING
|
||||
RoundCount: 0,
|
||||
ScoreId: scoreIDFor(req.challengeID),
|
||||
ScoreTwo: 0,
|
||||
ExtraLineupType: goproto.ExtraLineupType(1),
|
||||
}
|
||||
|
||||
chalBuf, err := proto.Marshal(curChallenge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lineupBuf, err := proto.Marshal(lineupInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sceneBuf, err := proto.Marshal(sceneInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body = append(body, 0x18, 0x00) // tag 3: retcode = 0
|
||||
body = append(body, 0x2A) // tag 5: cur_challenge
|
||||
body = appendUvarint(body, uint64(len(chalBuf)))
|
||||
body = append(body, chalBuf...)
|
||||
body = append(body, 0x32) // tag 6: lineup_list
|
||||
body = appendUvarint(body, uint64(len(lineupBuf)))
|
||||
body = append(body, lineupBuf...)
|
||||
body = append(body, 0x52) // tag 10: scene
|
||||
body = appendUvarint(body, uint64(len(sceneBuf)))
|
||||
body = append(body, sceneBuf...)
|
||||
|
||||
return s.SendRaw(&NetPacket{CmdType: 1787, Body: body})
|
||||
}
|
||||
|
||||
func scoreIDFor(challengeID uint32) uint32 {
|
||||
if challengeID >= 20000 && challengeID < 30000 {
|
||||
return 40000
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// handleStartChallengeTierce ports handle_start_challenge_tierce -> cmd 8973.
|
||||
func handleStartChallengeTierce(s *Session, payload []byte) error {
|
||||
req := decodeStartChallengeTierceReq(payload)
|
||||
baseID := mapChallengeStageID(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
|
||||
}
|
||||
tierceLineupsMu.Unlock()
|
||||
|
||||
var chosenAvatars []uint32
|
||||
switch req.stageIndex {
|
||||
case 0:
|
||||
chosenAvatars = req.firstAvatars
|
||||
case 1:
|
||||
chosenAvatars = req.secondAvatars
|
||||
case 2:
|
||||
chosenAvatars = req.thirdAvatars
|
||||
}
|
||||
|
||||
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 len(chosenAvatars) == 0 {
|
||||
chosenAvatars = challengeAvatarsFallback(s)
|
||||
}
|
||||
|
||||
cd := getChallengeData()
|
||||
entrance, group, monster, event, buff := uint32(3014101), uint32(5), uint32(4033010), uint32(30124011), uint32(0)
|
||||
switch {
|
||||
case req.stageIndex == 2:
|
||||
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.Entrance2, c.Group2, c.Monster2, c.Event2
|
||||
if req.buffID != 0 {
|
||||
buff = req.buffID
|
||||
} else {
|
||||
buff = c.Buff
|
||||
}
|
||||
}
|
||||
case req.stageIndex == 1:
|
||||
if c, ok := cd.Challenges[baseID]; ok {
|
||||
entrance, group, monster, event = c.Entrance2, c.Group2, c.Monster2, c.Event2
|
||||
if req.buffID != 0 {
|
||||
buff = req.buffID
|
||||
} else {
|
||||
buff = c.Buff
|
||||
}
|
||||
} else {
|
||||
entrance, group, monster, event = 3014101, 6, 8013010, 30124012
|
||||
}
|
||||
default:
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if player := s.JSON(); player != nil {
|
||||
setChallengeBattleConfig(player, chosenAvatars, req.challengeID, event, buff)
|
||||
}
|
||||
|
||||
sceneInfo, _, err := loadChallengeScene(s, entrance, group, monster, event, chosenAvatars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// ChallengeTierceChallengeInfo: stage_index=4, challenge_id=6, is_single_stage=14, lineup_list=15
|
||||
var tierceInfo []byte
|
||||
tierceInfo = append(tierceInfo, 0x20) // tag 4: stage_index
|
||||
tierceInfo = appendUvarint(tierceInfo, uint64(req.stageIndex))
|
||||
tierceInfo = append(tierceInfo, 0x30) // tag 6: challenge_id
|
||||
tierceInfo = appendUvarint(tierceInfo, uint64(req.challengeID))
|
||||
if req.isSingleStage {
|
||||
tierceInfo = append(tierceInfo, 0x70, 0x01) // tag 14: true
|
||||
}
|
||||
|
||||
for stIdx, stageAvatars := range allLineups {
|
||||
lineup := lineupInfoFromAvatars(stageAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), uint32(stIdx))
|
||||
lineupBuf, err := proto.Marshal(lineup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tierceInfo = append(tierceInfo, 0x7A) // tag 15: lineup_list
|
||||
tierceInfo = appendUvarint(tierceInfo, uint64(len(lineupBuf)))
|
||||
tierceInfo = append(tierceInfo, lineupBuf...)
|
||||
}
|
||||
|
||||
sceneBuf, err := proto.Marshal(sceneInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// StartChallengeTierceScRsp cmd 8973: retcode=12, challenge_tierce_info=4, scene=9
|
||||
var body []byte
|
||||
body = append(body, 0x60, 0x00) // tag 12: retcode = 0
|
||||
body = append(body, 0x22) // tag 4
|
||||
body = appendUvarint(body, uint64(len(tierceInfo)))
|
||||
body = append(body, tierceInfo...)
|
||||
body = append(body, 0x4A) // tag 9: scene
|
||||
body = appendUvarint(body, uint64(len(sceneBuf)))
|
||||
body = append(body, sceneBuf...)
|
||||
|
||||
return s.SendRaw(&NetPacket{CmdType: 8973, Body: body})
|
||||
}
|
||||
|
||||
func handleSetChallengeTierceLineup(s *Session, payload []byte) error {
|
||||
challengeID, stages := decodeSetChallengeTierceLineupReq(payload)
|
||||
if len(stages) > 0 {
|
||||
tierceLineupsMu.Lock()
|
||||
tierceLineups[challengeID] = stages
|
||||
tierceLineupsMu.Unlock()
|
||||
}
|
||||
if err := s.SendRaw(&NetPacket{CmdType: 8999, Body: []byte{0x08, 0x00}}); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = s.SendRaw(&NetPacket{CmdType: 8995, Body: []byte{0x60, 0x00}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLeaveChallenge(s *Session, _ []byte) error {
|
||||
if player := s.JSON(); player != nil {
|
||||
player.BattleConfig.CustomBattleLineup = nil
|
||||
player.SavePersistent()
|
||||
}
|
||||
if err := s.SendRaw(&NetPacket{CmdType: 1716, Body: []byte{0x20, 0x00}}); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = s.SendRaw(&NetPacket{CmdType: 1781, Body: []byte{0x40, 0x00}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLeaveChallengeTierce(s *Session, _ []byte) error {
|
||||
if player := s.JSON(); player != nil {
|
||||
player.BattleConfig.CustomBattleLineup = nil
|
||||
player.SavePersistent()
|
||||
}
|
||||
if err := s.SendRaw(&NetPacket{CmdType: 8997, Body: []byte{0x38, 0x00}}); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = s.SendRaw(&NetPacket{CmdType: 8982, Body: []byte{0x68, 0x00}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleTakeChallengeReward(s *Session, payload []byte) error {
|
||||
var groupID uint32
|
||||
buf := payload
|
||||
for len(buf) > 0 {
|
||||
tag, n := readUvarint(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n:]
|
||||
fnNum := uint32(tag >> 3)
|
||||
wt := uint32(tag & 0x7)
|
||||
if fnNum == 4 && wt == 0 {
|
||||
v, n2 := readUvarint(buf)
|
||||
if n2 == 0 {
|
||||
break
|
||||
}
|
||||
buf = buf[n2:]
|
||||
groupID = uint32(v)
|
||||
break
|
||||
}
|
||||
skipWireField(wt, &buf)
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body = append(body, 0x10) // tag 2: group_id
|
||||
body = appendUvarint(body, uint64(groupID))
|
||||
body = append(body, 0x30, 0x00) // tag 6: retcode = 0
|
||||
|
||||
return s.SendRaw(&NetPacket{CmdType: 1704, Body: body})
|
||||
}
|
||||
|
||||
// buildGetChallengeTierceDataScRsp — verbatim port of the Rust builder.
|
||||
func buildGetChallengeTierceDataScRspReal() []byte {
|
||||
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}
|
||||
}
|
||||
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 rsp []byte
|
||||
rsp = append(rsp, 0x40, 0x00) // tag 8: retcode = 0
|
||||
rsp = append(rsp, 0x60, 0x00) // legacy tag 12: retcode = 0
|
||||
|
||||
for _, e := range tierceStages {
|
||||
var tierceData []byte
|
||||
|
||||
// tag 3: challenge_id
|
||||
tierceData = append(tierceData, 0x18)
|
||||
tierceData = appendUvarint(tierceData, uint64(e.id))
|
||||
// tag 6: is_passed = true
|
||||
tierceData = append(tierceData, 0x30, 0x01)
|
||||
// tag 7: finished_target_list (packed)
|
||||
var targetsBuf []byte
|
||||
for _, t := range e.targets {
|
||||
targetsBuf = appendUvarint(targetsBuf, uint64(t))
|
||||
}
|
||||
tierceData = append(tierceData, 0x3A)
|
||||
tierceData = appendUvarint(tierceData, uint64(len(targetsBuf)))
|
||||
tierceData = append(tierceData, targetsBuf...)
|
||||
|
||||
// tag 4: result_list (3 stages)
|
||||
for stageIdx := uint32(0); stageIdx < 3; stageIdx++ {
|
||||
var result []byte
|
||||
result = append(result, 0x18) // tag 3: score_id
|
||||
result = appendUvarint(result, uint64(e.score))
|
||||
result = append(result, 0x48) // tag 9: stage_index
|
||||
result = appendUvarint(result, uint64(stageIdx))
|
||||
result = append(result, 0x50, 0x01) // tag 10: end_status = WIN
|
||||
|
||||
tierceData = append(tierceData, 0x22)
|
||||
tierceData = appendUvarint(tierceData, uint64(len(result)))
|
||||
tierceData = append(tierceData, result...)
|
||||
}
|
||||
|
||||
// tag 8: stage_info_list (3 stages)
|
||||
for stageIdx := uint32(0); stageIdx < 3; stageIdx++ {
|
||||
var sinfo []byte
|
||||
sinfo = append(sinfo, 0x70) // tag 14: stage_index
|
||||
sinfo = appendUvarint(sinfo, uint64(stageIdx))
|
||||
|
||||
tierceData = append(tierceData, 0x42)
|
||||
tierceData = appendUvarint(tierceData, uint64(len(sinfo)))
|
||||
tierceData = append(tierceData, sinfo...)
|
||||
}
|
||||
|
||||
// tag 4: challenge_info_list (4.5.52)
|
||||
rsp = append(rsp, 0x22)
|
||||
rsp = appendUvarint(rsp, uint64(len(tierceData)))
|
||||
rsp = append(rsp, tierceData...)
|
||||
|
||||
// tag 10: challenge_info_list (legacy)
|
||||
rsp = append(rsp, 0x52)
|
||||
rsp = appendUvarint(rsp, uint64(len(tierceData)))
|
||||
rsp = append(rsp, tierceData...)
|
||||
}
|
||||
|
||||
return rsp
|
||||
}
|
||||
Reference in new issue
Block a user