The synthetic 1000-60000 ranges filled the Travel Log with entries the client cannot resolve. Replaced with the 147 real (activityId, panelId) rows dumped from the March7th PS ActivityConfig (matches the 4.6.51 client), keeping the Apocalyptic Shadow panel 21001 block that the AS tab requires
313 lines
8.3 KiB
Go
313 lines
8.3 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"sync"
|
|
|
|
goproto "gac-hkrpg-go/protocol/proto"
|
|
)
|
|
|
|
// ChallengeDataConfig mirrors challenge_data.json (Rust: ChallengeConfigData).
|
|
type ChallengeDataConfig struct {
|
|
StagesList []uint32 `json:"stages_list"`
|
|
GroupsList []uint32 `json:"groups_list"`
|
|
Challenges map[uint32]ChallengeStageData `json:"challenges"`
|
|
Tierce map[uint32]ChallengeTierceStageData `json:"tierce"`
|
|
Stages map[uint32]StageBattleData `json:"stages"`
|
|
Peak map[string]PeakStageData `json:"peak"`
|
|
}
|
|
|
|
type ChallengeStageData struct {
|
|
GroupID uint32 `json:"group_id"`
|
|
Entrance uint32 `json:"entrance"`
|
|
Entrance2 uint32 `json:"entrance2"`
|
|
Group1 uint32 `json:"group1"`
|
|
Group2 uint32 `json:"group2"`
|
|
Monster1 uint32 `json:"monster1"`
|
|
Monster2 uint32 `json:"monster2"`
|
|
Event1 uint32 `json:"event1"`
|
|
Event2 uint32 `json:"event2"`
|
|
Buff uint32 `json:"buff"`
|
|
}
|
|
|
|
type ChallengeTierceStageData struct {
|
|
Entrance uint32 `json:"entrance"`
|
|
Group uint32 `json:"group"`
|
|
Monster uint32 `json:"monster"`
|
|
Event uint32 `json:"event"`
|
|
Targets []uint32 `json:"targets"`
|
|
Score uint32 `json:"score"`
|
|
}
|
|
|
|
type StageBattleData struct {
|
|
Level uint32 `json:"level"`
|
|
Monsters [][]uint32 `json:"monsters"`
|
|
}
|
|
|
|
type PeakStageData struct {
|
|
EventID uint32 `json:"event_id"`
|
|
HardEventID uint32 `json:"hard_event_id"`
|
|
MonsterID uint32 `json:"monster_id"`
|
|
DefaultBuff uint32 `json:"default_buff"`
|
|
IsBoss bool `json:"is_boss"`
|
|
Targets []uint32 `json:"targets"`
|
|
HardTarget uint32 `json:"hard_target"`
|
|
}
|
|
|
|
var (
|
|
challengeOnce sync.Once
|
|
challengeData ChallengeDataConfig
|
|
)
|
|
|
|
func getChallengeData() *ChallengeDataConfig {
|
|
challengeOnce.Do(func() {
|
|
raw, err := os.ReadFile("challenge_data.json")
|
|
if err != nil {
|
|
Log("challenge_data.json not found!")
|
|
return
|
|
}
|
|
if err := json.Unmarshal(raw, &challengeData); err != nil {
|
|
Log("Failed to parse challenge_data.json: " + err.Error())
|
|
challengeData = ChallengeDataConfig{}
|
|
}
|
|
})
|
|
return &challengeData
|
|
}
|
|
|
|
func getChallengeStagesList() []uint32 {
|
|
cd := getChallengeData()
|
|
if len(cd.StagesList) > 0 {
|
|
return cd.StagesList
|
|
}
|
|
var stages []uint32
|
|
for id := range cd.Challenges {
|
|
stages = append(stages, id)
|
|
}
|
|
for id := range cd.Tierce {
|
|
stages = append(stages, id)
|
|
}
|
|
sortU32Slice(stages)
|
|
// dedup
|
|
out := stages[:0]
|
|
var last uint32 = ^uint32(0)
|
|
for _, id := range stages {
|
|
if id != last {
|
|
out = append(out, id)
|
|
last = id
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func getChallengeGroups() []uint32 {
|
|
cd := getChallengeData()
|
|
if len(cd.GroupsList) > 0 {
|
|
return cd.GroupsList
|
|
}
|
|
var groups []uint32
|
|
for _, c := range cd.Challenges {
|
|
if c.GroupID != 0 {
|
|
groups = append(groups, c.GroupID)
|
|
}
|
|
}
|
|
sortU32Slice(groups)
|
|
out := groups[:0]
|
|
var last uint32 = ^uint32(0)
|
|
for _, g := range groups {
|
|
if g != last {
|
|
out = append(out, g)
|
|
last = g
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func groupTotalStars(groupID uint32) uint64 {
|
|
cd := getChallengeData()
|
|
n := uint64(0)
|
|
for _, c := range cd.Challenges {
|
|
if c.GroupID == groupID {
|
|
n++
|
|
}
|
|
}
|
|
return n * 3
|
|
}
|
|
|
|
func challengeScore(id uint32) uint32 {
|
|
switch {
|
|
case id >= 20000 && id < 30000:
|
|
return 80000
|
|
case id >= 30000 || (id >= 5000 && id < 6000):
|
|
return 4000
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func init() {
|
|
handle("GetChallenge", func(s *Session, req *goproto.GetChallengeCsReq, rsp *goproto.GetChallengeScRsp) error {
|
|
for _, groupID := range getChallengeGroups() {
|
|
rsp.ChallengeGroupList = append(rsp.ChallengeGroupList, &goproto.ChallengeGroup{
|
|
GroupId: groupID,
|
|
TakenStarsCountReward: groupTotalStars(groupID),
|
|
})
|
|
}
|
|
for _, id := range getChallengeStagesList() {
|
|
rsp.ChallengeList = append(rsp.ChallengeList, &goproto.Challenge{
|
|
ChallengeId: id,
|
|
Star: 7,
|
|
TakenReward: 7,
|
|
RecordId: 1,
|
|
HGPKMHFPMBJ: false,
|
|
ScoreTwo: challengeScore(id),
|
|
ScoreId: challengeScore(id),
|
|
})
|
|
}
|
|
|
|
addMaxLevel := func(level, rType uint32, unlocked bool) {
|
|
rsp.MaxLevelList = append(rsp.MaxLevelList, &goproto.ChallengeHistoryMaxLevel{
|
|
Level: level,
|
|
HNHCFJJNJCE: unlocked,
|
|
RewardDisplayType: rType,
|
|
})
|
|
}
|
|
addMaxLevel(15, 1, true) // Jarilo
|
|
addMaxLevel(15, 1, false)
|
|
addMaxLevel(6, 2, true) // Luofu
|
|
addMaxLevel(6, 2, false)
|
|
addMaxLevel(12, 3, true) // MoC
|
|
addMaxLevel(12, 3, false)
|
|
addMaxLevel(4, 4, true) // Pure Fiction
|
|
addMaxLevel(4, 4, false)
|
|
addMaxLevel(4, 5, true) // Apocalyptic Shadow
|
|
addMaxLevel(4, 5, false)
|
|
addMaxLevel(6, 6, true) // Anomaly Arbitration
|
|
addMaxLevel(6, 6, false)
|
|
|
|
for r := uint32(101001); r <= 101015; r++ {
|
|
addMaxLevel(15, r, true)
|
|
}
|
|
for r := uint32(101016); r <= 101021; r++ {
|
|
addMaxLevel(6, r, true)
|
|
}
|
|
for r := uint32(101201); r <= 101212; r++ {
|
|
addMaxLevel(12, r, true)
|
|
}
|
|
for r := uint32(101401); r <= 101404; r++ {
|
|
addMaxLevel(4, r, true)
|
|
}
|
|
for _, r := range []uint32{101713, 101913, 102113} {
|
|
addMaxLevel(4, r, true)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
handle("GetCurChallenge", func(s *Session, req *goproto.GetCurChallengeCsReq, rsp *goproto.GetCurChallengeScRsp) error {
|
|
rsp.Retcode = 0
|
|
return nil
|
|
})
|
|
|
|
handle("GetChallengePeakData", func(s *Session, req *goproto.GetChallengePeakDataCsReq, rsp *goproto.GetChallengePeakDataScRsp) error {
|
|
rsp.Retcode = 0
|
|
rsp.CurrentPeakGroupId = 1
|
|
|
|
cd := getChallengeData()
|
|
type stageEntry struct {
|
|
id uint32
|
|
stage PeakStageData
|
|
}
|
|
groups := map[uint32][]stageEntry{}
|
|
for k, stage := range cd.Peak {
|
|
var id uint32
|
|
if _, err := fmt.Sscanf(k, "%d", &id); err != nil {
|
|
continue
|
|
}
|
|
g := id / 100
|
|
groups[g] = append(groups[g], stageEntry{id, stage})
|
|
}
|
|
|
|
if len(groups) == 0 {
|
|
peakGroup := &goproto.ChallengePeakGroup{
|
|
PeakGroupId: 1,
|
|
ObtainedStars: 9,
|
|
CountOfPeaks: 3,
|
|
DisableHardMode: false,
|
|
TakenStarRewards: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
|
}
|
|
for peakID := uint32(1); peakID <= 3; peakID++ {
|
|
peakGroup.Peaks = append(peakGroup.Peaks, &goproto.ChallengePeak{
|
|
PeakId: peakID,
|
|
HasPassed: true,
|
|
FinishedTargetList: []uint32{1, 2, 3, 4, 5},
|
|
})
|
|
}
|
|
rsp.ChallengePeakGroups = append(rsp.ChallengePeakGroups, peakGroup)
|
|
return nil
|
|
}
|
|
|
|
groupIDs := sortedU32Keys2(groups)
|
|
for _, groupID := range groupIDs {
|
|
stages := groups[groupID]
|
|
sort.Slice(stages, func(i, j int) bool { return stages[i].id < stages[j].id })
|
|
|
|
peakGroup := &goproto.ChallengePeakGroup{
|
|
PeakGroupId: groupID,
|
|
CountOfPeaks: uint32(len(stages)),
|
|
}
|
|
for _, entry := range stages {
|
|
stars := uint32(len(entry.stage.Targets))
|
|
peakGroup.ObtainedStars += stars
|
|
for t := uint32(1); t <= stars; t++ {
|
|
peakGroup.TakenStarRewards = append(peakGroup.TakenStarRewards, peakGroup.ObtainedStars-stars+t)
|
|
}
|
|
peakGroup.Peaks = append(peakGroup.Peaks, &goproto.ChallengePeak{
|
|
PeakId: entry.id,
|
|
HasPassed: true,
|
|
FinishedTargetList: entry.stage.Targets,
|
|
})
|
|
}
|
|
rsp.ChallengePeakGroups = append(rsp.ChallengePeakGroups, peakGroup)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
handle("GetCurChallengePeak", func(s *Session, req *goproto.GetCurChallengePeakCsReq, rsp *goproto.GetCurChallengePeakScRsp) error {
|
|
// reverted to the state of the build that reached Pom-Pom:
|
|
// empty peak_id (a fabricated peak_id:1 crashed CreateMemberEntityLeader)
|
|
rsp.Retcode = 0
|
|
rsp.HasPassed = true
|
|
rsp.PeakId = 1
|
|
rsp.BossBuffId = 0
|
|
return nil
|
|
})
|
|
|
|
handle("GetActivityScheduleConfig", func(s *Session, req *goproto.GetActivityScheduleConfigCsReq, rsp *goproto.GetActivityScheduleConfigScRsp) error {
|
|
rsp.Retcode = 0
|
|
add := func(activityID, panelID uint32) {
|
|
rsp.ScheduleData = append(rsp.ScheduleData, &goproto.ActivityScheduleData{
|
|
ActivityId: activityID,
|
|
PanelId: panelID,
|
|
BeginTime: 0,
|
|
EndTime: 1924992000,
|
|
})
|
|
}
|
|
// real activity ids from the 4.6.51 client (March7th ActivityConfig
|
|
// dump) - synthetic ids the client doesn't know just fill the Travel
|
|
// Log with blank entries
|
|
for _, e := range realActivitySchedule {
|
|
add(e.activityID, e.panelID)
|
|
}
|
|
|
|
// Apocalyptic Shadow tab requires an active schedule with panel 21001
|
|
for actID := uint32(21001); actID <= 21030; actID++ {
|
|
add(actID, 21001)
|
|
}
|
|
for i := uint32(1); i <= 8; i++ {
|
|
add(2100000+i*100+1, 21001)
|
|
}
|
|
return nil
|
|
})
|
|
}
|