Update GaCy-GO
This commit is contained in:
1 parent
da8a32f60d
commit
0cbe460e49
345 files changed
+98202
-77559
No files matched your search
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -13,18 +15,27 @@ import (
|
||||
|
||||
type prydwenEndgameSnapshot struct {
|
||||
Source struct {
|
||||
DatasetVersion string `json:"dataset_version"`
|
||||
ChallengeVersion string `json:"challenge_version"`
|
||||
ChallengeName string `json:"challenge_name"`
|
||||
DatasetVersion string `json:"dataset_version"`
|
||||
CollectDate string `json:"collect_date"`
|
||||
} `json:"source"`
|
||||
AvatarAliases map[string]string `json:"avatar_aliases"`
|
||||
Mode string `json:"mode"`
|
||||
Floor uint32 `json:"floor"`
|
||||
Stages []struct {
|
||||
Stage uint32 `json:"stage"`
|
||||
Rank uint32 `json:"rank"`
|
||||
Characters []string `json:"characters"`
|
||||
} `json:"stages"`
|
||||
Protocol struct {
|
||||
RequestTypeRoutes map[string]string `json:"request_type_routes"`
|
||||
} `json:"protocol"`
|
||||
AvatarAliases map[string]string `json:"avatar_aliases"`
|
||||
Modes map[string]prydwenModeSnapshot `json:"modes"`
|
||||
}
|
||||
|
||||
type prydwenModeSnapshot struct {
|
||||
ChallengeVersion string `json:"challenge_version"`
|
||||
ChallengeName string `json:"challenge_name"`
|
||||
Floor uint32 `json:"floor"`
|
||||
Stages []prydwenStageSnapshot `json:"stages"`
|
||||
}
|
||||
|
||||
type prydwenStageSnapshot struct {
|
||||
Stage string `json:"stage"`
|
||||
Rank uint32 `json:"rank"`
|
||||
Characters []string `json:"characters"`
|
||||
}
|
||||
|
||||
type prydwenAvatarResource struct {
|
||||
@@ -33,10 +44,17 @@ type prydwenAvatarResource struct {
|
||||
ConfigPath string `json:"JsonPath"`
|
||||
}
|
||||
|
||||
type challengePeakGroupResource struct {
|
||||
ID uint32 `json:"ID"`
|
||||
RecommendID uint32 `json:"RecommendID"`
|
||||
PreLevelIDList []uint32 `json:"PreLevelIDList"`
|
||||
BossLevelID uint32 `json:"BossLevelID"`
|
||||
}
|
||||
|
||||
var (
|
||||
prydwenOnce sync.Once
|
||||
prydwenLineups []*goproto.ChallengeLineupList
|
||||
prydwenLoadErr error
|
||||
prydwenOnce sync.Once
|
||||
prydwenSnapshot prydwenEndgameSnapshot
|
||||
prydwenLoadErr error
|
||||
)
|
||||
|
||||
func normalizedAvatarName(name string) string {
|
||||
@@ -49,26 +67,38 @@ func normalizedAvatarName(name string) string {
|
||||
}, name)
|
||||
}
|
||||
|
||||
func loadPrydwenLineups() ([]*goproto.ChallengeLineupList, error) {
|
||||
func loadPrydwenSnapshot() (*prydwenEndgameSnapshot, error) {
|
||||
prydwenOnce.Do(func() {
|
||||
prydwenLineups, prydwenLoadErr = readPrydwenLineups()
|
||||
prydwenSnapshot, prydwenLoadErr = readPrydwenSnapshot()
|
||||
})
|
||||
return prydwenLineups, prydwenLoadErr
|
||||
if prydwenLoadErr != nil {
|
||||
return nil, prydwenLoadErr
|
||||
}
|
||||
return &prydwenSnapshot, nil
|
||||
}
|
||||
|
||||
func readPrydwenLineups() ([]*goproto.ChallengeLineupList, error) {
|
||||
func readPrydwenSnapshot() (prydwenEndgameSnapshot, error) {
|
||||
var snapshot prydwenEndgameSnapshot
|
||||
raw, err := common.ReadResourceFile("resources/PrydwenEndgameTeams.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read Prydwen endgame team snapshot: %w", err)
|
||||
return snapshot, fmt.Errorf("read Prydwen endgame team snapshot: %w", err)
|
||||
}
|
||||
var snapshot prydwenEndgameSnapshot
|
||||
if err := json.Unmarshal(raw, &snapshot); err != nil {
|
||||
return nil, fmt.Errorf("parse Prydwen endgame team snapshot: %w", err)
|
||||
return snapshot, fmt.Errorf("parse Prydwen endgame team snapshot: %w", err)
|
||||
}
|
||||
if snapshot.Mode != "moc" || snapshot.Floor == 0 || len(snapshot.Stages) == 0 {
|
||||
return nil, fmt.Errorf("Prydwen endgame team snapshot has no supported MoC floor")
|
||||
for _, mode := range []string{"moc", "pf", "as", "aa"} {
|
||||
entry, ok := snapshot.Modes[mode]
|
||||
if !ok || len(entry.Stages) == 0 {
|
||||
return snapshot, fmt.Errorf("Prydwen snapshot has no %s teams", mode)
|
||||
}
|
||||
}
|
||||
if len(snapshot.Protocol.RequestTypeRoutes) == 0 {
|
||||
return snapshot, fmt.Errorf("Prydwen snapshot has no request type routing table")
|
||||
}
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func readPrydwenAvatarIDs(aliases map[string]string) (map[string]uint32, error) {
|
||||
avatarRaw, err := common.ReadResourceFile("resources/AvatarConfig.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read Client AvatarConfig resource: %w", err)
|
||||
@@ -93,58 +123,213 @@ func readPrydwenLineups() ([]*goproto.ChallengeLineupList, error) {
|
||||
avatarIDs[pathName] = avatar.ID
|
||||
}
|
||||
}
|
||||
|
||||
lineups := make([]*goproto.ChallengeLineupList, 0, len(snapshot.Stages))
|
||||
seenStages := map[uint32]bool{}
|
||||
for _, stage := range snapshot.Stages {
|
||||
if stage.Stage == 0 || seenStages[stage.Stage] || len(stage.Characters) != 4 {
|
||||
return nil, fmt.Errorf("invalid Prydwen MoC stage %d team size/order", stage.Stage)
|
||||
for slug, clientTag := range aliases {
|
||||
if id := avatarIDs[normalizedAvatarName(clientTag)]; id != 0 {
|
||||
avatarIDs[normalizedAvatarName(slug)] = id
|
||||
}
|
||||
seenStages[stage.Stage] = true
|
||||
avatars := make([]*goproto.ChallengeAvatarInfo, 0, len(stage.Characters))
|
||||
}
|
||||
return avatarIDs, nil
|
||||
}
|
||||
|
||||
func prydwenStageLineup(stage prydwenStageSnapshot, avatarIDs map[string]uint32) (*goproto.ChallengeLineupList, error) {
|
||||
if stage.Stage == "" || len(stage.Characters) != 4 {
|
||||
return nil, fmt.Errorf("invalid Prydwen stage %q team size/order", stage.Stage)
|
||||
}
|
||||
avatars := make([]*goproto.ChallengeAvatarInfo, 0, len(stage.Characters))
|
||||
seen := make(map[uint32]bool, len(stage.Characters))
|
||||
for index, slug := range stage.Characters {
|
||||
id := avatarIDs[normalizedAvatarName(slug)]
|
||||
if id == 0 {
|
||||
return nil, fmt.Errorf("Prydwen avatar %q in stage %s has no ID in Client AvatarConfig", slug, stage.Stage)
|
||||
}
|
||||
if seen[id] {
|
||||
return nil, fmt.Errorf("Prydwen stage %s repeats AvatarID %d", stage.Stage, id)
|
||||
}
|
||||
seen[id] = true
|
||||
avatars = append(avatars, &goproto.ChallengeAvatarInfo{
|
||||
AvatarType: goproto.AvatarType_AVATAR_FORMAL_TYPE,
|
||||
Id: id,
|
||||
Index: uint32(index),
|
||||
})
|
||||
}
|
||||
return &goproto.ChallengeLineupList{AvatarList: avatars}, nil
|
||||
}
|
||||
|
||||
func buildPrydwenLineups(mode prydwenModeSnapshot, avatarIDs map[string]uint32) ([]*goproto.ChallengeLineupList, error) {
|
||||
lineups := make([]*goproto.ChallengeLineupList, 0, len(mode.Stages))
|
||||
stages := append([]prydwenStageSnapshot(nil), mode.Stages...)
|
||||
sort.SliceStable(stages, func(i, j int) bool { return stages[i].Stage < stages[j].Stage })
|
||||
seen := map[string]bool{}
|
||||
for _, stage := range stages {
|
||||
if seen[stage.Stage] {
|
||||
return nil, fmt.Errorf("Prydwen snapshot repeats stage %s", stage.Stage)
|
||||
}
|
||||
seen[stage.Stage] = true
|
||||
lineup, err := prydwenStageLineup(stage, avatarIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lineups = append(lineups, lineup)
|
||||
}
|
||||
return lineups, nil
|
||||
}
|
||||
|
||||
func readPrydwenLineups() ([]*goproto.ChallengeLineupList, error) {
|
||||
snapshot, err := loadPrydwenSnapshot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
avatarIDs, err := readPrydwenAvatarIDs(snapshot.AvatarAliases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildPrydwenLineups(snapshot.Modes["moc"], avatarIDs)
|
||||
}
|
||||
|
||||
func buildPrydwenPeakLineups(mode prydwenModeSnapshot, aliases map[string]string, requestKey uint32) (*goproto.MJMGDLJAJCM, error) {
|
||||
groupRaw, err := common.ReadResourceFile("resources/ChallengePeakGroupConfig.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read Client ChallengePeakGroupConfig resource: %w", err)
|
||||
}
|
||||
var groups struct {
|
||||
Entries []challengePeakGroupResource `json:"challenge_peak_group_config"`
|
||||
}
|
||||
if err := json.Unmarshal(groupRaw, &groups); err != nil {
|
||||
return nil, fmt.Errorf("parse Client ChallengePeakGroupConfig resource: %w", err)
|
||||
}
|
||||
if len(groups.Entries) == 0 {
|
||||
return nil, fmt.Errorf("Client ChallengePeakGroupConfig contains no groups")
|
||||
}
|
||||
|
||||
// The key semantics are not established in the obfuscated schema. Use an
|
||||
// exact Client resource match if present; otherwise pair the bundled
|
||||
// current-cycle snapshot with the newest group in this Client's resources.
|
||||
selected := groups.Entries[0]
|
||||
for _, group := range groups.Entries {
|
||||
if group.ID > selected.ID {
|
||||
selected = group
|
||||
}
|
||||
}
|
||||
matched := false
|
||||
for _, group := range groups.Entries {
|
||||
if requestKey != 0 && (requestKey == group.ID || requestKey == group.RecommendID || containsPeakLevel(group, requestKey)) {
|
||||
selected = group
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if requestKey != 0 && !matched {
|
||||
Log(fmt.Sprintf("[RECOMMEND] AA key=%d did not match a Client Peak resource; using latest configured group=%d", requestKey, selected.ID))
|
||||
}
|
||||
levelIDs := append([]uint32(nil), selected.PreLevelIDList...)
|
||||
if selected.BossLevelID != 0 {
|
||||
levelIDs = append(levelIDs, selected.BossLevelID)
|
||||
}
|
||||
if len(levelIDs) != len(mode.Stages) {
|
||||
return nil, fmt.Errorf("Prydwen AA has %d teams but Client Peak group %d has %d levels", len(mode.Stages), selected.ID, len(levelIDs))
|
||||
}
|
||||
|
||||
avatarIDs, err := readPrydwenAvatarIDs(aliases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stages := append([]prydwenStageSnapshot(nil), mode.Stages...)
|
||||
sort.SliceStable(stages, func(i, j int) bool { return stages[i].Stage < stages[j].Stage })
|
||||
peakLineups := make([]*goproto.OKFKPPNGCOD, 0, len(stages))
|
||||
for i, stage := range stages {
|
||||
if stage.Stage == "" || len(stage.Characters) != 4 {
|
||||
return nil, fmt.Errorf("invalid Prydwen AA stage %q team size/order", stage.Stage)
|
||||
}
|
||||
avatars := make([]*goproto.ONKDAPOKGAC, 0, len(stage.Characters))
|
||||
seen := map[uint32]bool{}
|
||||
for index, slug := range stage.Characters {
|
||||
resourceTag := snapshot.AvatarAliases[slug]
|
||||
if resourceTag == "" {
|
||||
resourceTag = slug
|
||||
}
|
||||
id := avatarIDs[normalizedAvatarName(resourceTag)]
|
||||
id := avatarIDs[normalizedAvatarName(slug)]
|
||||
if id == 0 {
|
||||
return nil, fmt.Errorf("Prydwen avatar %q has no ID in Client AvatarConfig", slug)
|
||||
return nil, fmt.Errorf("Prydwen avatar %q in AA stage %s has no ID in Client AvatarConfig", slug, stage.Stage)
|
||||
}
|
||||
avatars = append(avatars, &goproto.ChallengeAvatarInfo{
|
||||
if seen[id] {
|
||||
return nil, fmt.Errorf("Prydwen AA stage %s repeats AvatarID %d", stage.Stage, id)
|
||||
}
|
||||
seen[id] = true
|
||||
avatars = append(avatars, &goproto.ONKDAPOKGAC{
|
||||
AvatarType: goproto.AvatarType_AVATAR_FORMAL_TYPE,
|
||||
Id: id,
|
||||
Index: uint32(index),
|
||||
})
|
||||
}
|
||||
lineups = append(lineups, &goproto.ChallengeLineupList{AvatarList: avatars})
|
||||
peakLineups = append(peakLineups, &goproto.OKFKPPNGCOD{PeakId: levelIDs[i], AvatarList: avatars})
|
||||
}
|
||||
return lineups, nil
|
||||
return &goproto.MJMGDLJAJCM{DNIGKDKIPPJ: peakLineups}, nil
|
||||
}
|
||||
|
||||
func containsPeakLevel(group challengePeakGroupResource, key uint32) bool {
|
||||
if key == group.BossLevelID {
|
||||
return key != 0
|
||||
}
|
||||
for _, levelID := range group.PreLevelIDList {
|
||||
if key == levelID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func recommendationMode(snapshot *prydwenEndgameSnapshot, req *goproto.GetFriendRecommendLineupCsReq) (string, error) {
|
||||
typeKey := strconv.FormatInt(int64(req.GetType()), 10)
|
||||
mode := snapshot.Protocol.RequestTypeRoutes[typeKey]
|
||||
if mode == "" {
|
||||
return "", fmt.Errorf("unmapped GetFriendRecommendLineup type=%s", typeKey)
|
||||
}
|
||||
if _, ok := snapshot.Modes[mode]; !ok {
|
||||
return "", fmt.Errorf("GetFriendRecommendLineup type=%s routes to missing mode %q", typeKey, mode)
|
||||
}
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
func buildPrydwenRecommendResponse(req *goproto.GetFriendRecommendLineupCsReq) (*goproto.GetFriendRecommendLineupScRsp, error) {
|
||||
lineups, err := loadPrydwenLineups()
|
||||
snapshot, err := loadPrydwenSnapshot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &goproto.GetFriendRecommendLineupScRsp{
|
||||
Key: req.Key,
|
||||
Type: req.Type,
|
||||
MGKCKHIIICL: []*goproto.FPDCINMDJCE{{
|
||||
KJFEKHDEBIK: &goproto.FPDCINMDJCE_NHKCFOALAPD{
|
||||
NHKCFOALAPD: &goproto.OJJJDPNOKHG{LineupList: lineups},
|
||||
},
|
||||
}},
|
||||
}, nil
|
||||
modeName, err := recommendationMode(snapshot, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := &goproto.GetFriendRecommendLineupScRsp{Key: req.Key, Type: req.Type}
|
||||
if modeName == "aa" {
|
||||
peakLineups, err := buildPrydwenPeakLineups(snapshot.Modes[modeName], snapshot.AvatarAliases, req.GetKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.MGKCKHIIICL = []*goproto.FPDCINMDJCE{{
|
||||
KJFEKHDEBIK: &goproto.FPDCINMDJCE_LMHIOKFMDNE{LMHIOKFMDNE: peakLineups},
|
||||
}}
|
||||
return response, nil
|
||||
}
|
||||
avatarIDs, err := readPrydwenAvatarIDs(snapshot.AvatarAliases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lineups, err := buildPrydwenLineups(snapshot.Modes[modeName], avatarIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.MGKCKHIIICL = []*goproto.FPDCINMDJCE{{
|
||||
KJFEKHDEBIK: &goproto.FPDCINMDJCE_NHKCFOALAPD{
|
||||
NHKCFOALAPD: &goproto.OJJJDPNOKHG{LineupList: lineups},
|
||||
},
|
||||
}}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
handle("GetFriendRecommendLineup", func(_ *Session, req *goproto.GetFriendRecommendLineupCsReq, rsp *goproto.GetFriendRecommendLineupScRsp) error {
|
||||
// Preserve request context. The game UI asks this endpoint for the
|
||||
// recommendation panel; an empty auto-response left the panel blank.
|
||||
response, err := buildPrydwenRecommendResponse(req)
|
||||
if err != nil {
|
||||
Log(fmt.Sprintf("[RECOMMEND] Prydwen data unavailable type=%s key=%d: %v", req.Type, req.Key, err))
|
||||
// Still answer with request context so the UI does not wait forever.
|
||||
rsp.Key = req.Key
|
||||
rsp.Type = req.Type
|
||||
Log(fmt.Sprintf("[RECOMMEND] Prydwen data unavailable type=%d key=%d: %v", req.Type, req.Key, err))
|
||||
return nil
|
||||
}
|
||||
rsp.Key = response.Key
|
||||
@@ -152,7 +337,8 @@ func init() {
|
||||
rsp.MGKCKHIIICL = response.MGKCKHIIICL
|
||||
rsp.Retcode = response.Retcode
|
||||
rsp.ALDBIIIGLOM = response.ALDBIIIGLOM
|
||||
Log(fmt.Sprintf("[RECOMMEND] served Prydwen MoC teams type=%s key=%d stages=%d", req.Type, req.Key, len(response.MGKCKHIIICL[0].GetNHKCFOALAPD().LineupList)))
|
||||
mode, _ := recommendationMode(&prydwenSnapshot, req)
|
||||
Log(fmt.Sprintf("[RECOMMEND] served Prydwen %s teams type=%d key=%d groups=%d", strings.ToUpper(mode), req.Type, req.Key, len(response.MGKCKHIIICL)))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user