Update GaCy-GO
This commit is contained in:
1 parent
da8a32f60d
commit
0cbe460e49
345 files changed
+98202
-77559
No files matched your search
@@ -0,0 +1,279 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// MonsterSettingsCatalog is a read-only preview of challenge stages plus the
|
||||
// resource-backed ID/level suggestions used by the Custom Enemy editor.
|
||||
type MonsterSettingsCatalog struct {
|
||||
Modes map[string][]MonsterSettingsGroup `json:"modes"`
|
||||
MonsterIDs []uint32 `json:"monster_ids"`
|
||||
Monsters map[uint32]MonsterSettingsMonsterMetadata `json:"monsters"`
|
||||
Levels []uint32 `json:"levels"`
|
||||
Sources []string `json:"sources"`
|
||||
}
|
||||
|
||||
type MonsterSettingsGroup struct {
|
||||
ID uint32 `json:"id"`
|
||||
Names map[string]string `json:"names,omitempty"`
|
||||
Stages []MonsterSettingsEncounter `json:"stages"`
|
||||
}
|
||||
|
||||
type MonsterSettingsEncounter struct {
|
||||
ID uint32 `json:"id"`
|
||||
IsBoss bool `json:"is_boss,omitempty"`
|
||||
DefaultBuffID uint32 `json:"default_buff_id,omitempty"`
|
||||
MonsterIDs []uint32 `json:"monster_ids,omitempty"`
|
||||
Battles []MonsterSettingsBattle `json:"battles"`
|
||||
}
|
||||
|
||||
type MonsterSettingsMonsterMetadata struct {
|
||||
Names map[string]string `json:"names"`
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
}
|
||||
|
||||
type MonsterSettingsBattle struct {
|
||||
StageID uint32 `json:"stage_id"`
|
||||
Level uint32 `json:"level"`
|
||||
Waves [][]uint32 `json:"waves"`
|
||||
}
|
||||
|
||||
type monsterSettingStageData struct {
|
||||
Level uint32 `json:"level"`
|
||||
Monsters [][]uint32 `json:"monsters"`
|
||||
}
|
||||
|
||||
type monsterSettingsAccumulator struct {
|
||||
groups map[uint32]map[uint32]*MonsterSettingsEncounter
|
||||
}
|
||||
|
||||
// LoadMonsterSettingsCatalog uses the same installed challenge and stage
|
||||
// resources as the game-server battle handlers. It never supplies a battle
|
||||
// override; custom waves are separately restricted to Client Calyx rows.
|
||||
func LoadMonsterSettingsCatalog() (MonsterSettingsCatalog, error) {
|
||||
challengeRaw, err := ReadResourceFile("challenge_data.json")
|
||||
if err != nil {
|
||||
return MonsterSettingsCatalog{}, fmt.Errorf("read challenge catalog: %w", err)
|
||||
}
|
||||
stageRaw, err := ReadResourceFile("resources/StageConfig.json")
|
||||
if err != nil {
|
||||
return MonsterSettingsCatalog{}, fmt.Errorf("read Client stage catalog: %w", err)
|
||||
}
|
||||
|
||||
var challenges struct {
|
||||
Challenges map[string]struct {
|
||||
GroupID uint32 `json:"groupId"`
|
||||
Monster1 uint32 `json:"monster1"`
|
||||
Monster2 uint32 `json:"monster2"`
|
||||
Event1 uint32 `json:"event1"`
|
||||
Event2 uint32 `json:"event2"`
|
||||
} `json:"challenges"`
|
||||
Tierce map[string]struct {
|
||||
Group uint32 `json:"group"`
|
||||
Event uint32 `json:"event"`
|
||||
} `json:"tierce"`
|
||||
Stages map[string]monsterSettingStageData `json:"stages"`
|
||||
Peak struct {
|
||||
Stages map[string]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"`
|
||||
} `json:"stages"`
|
||||
Groups map[string]struct {
|
||||
PreLevelIDs []uint32 `json:"pre_level_ids"`
|
||||
BossLevelID uint32 `json:"boss_level_id"`
|
||||
} `json:"groups"`
|
||||
} `json:"peak"`
|
||||
}
|
||||
if err := json.Unmarshal(challengeRaw, &challenges); err != nil {
|
||||
return MonsterSettingsCatalog{}, fmt.Errorf("decode challenge catalog: %w", err)
|
||||
}
|
||||
var stageTable struct {
|
||||
Entries []struct {
|
||||
StageID uint32 `json:"StageID"`
|
||||
Level uint32 `json:"Level"`
|
||||
MonsterList [][]uint32 `json:"MonsterList"`
|
||||
} `json:"stage_config"`
|
||||
}
|
||||
if err := json.Unmarshal(stageRaw, &stageTable); err != nil {
|
||||
return MonsterSettingsCatalog{}, fmt.Errorf("decode Client stage catalog: %w", err)
|
||||
}
|
||||
stageByID := make(map[uint32]monsterSettingStageData, len(stageTable.Entries))
|
||||
monsterSet := map[uint32]struct{}{}
|
||||
levelSet := map[uint32]struct{}{}
|
||||
for _, row := range stageTable.Entries {
|
||||
stageByID[row.StageID] = monsterSettingStageData{Level: row.Level, Monsters: row.MonsterList}
|
||||
if row.Level != 0 {
|
||||
levelSet[row.Level] = struct{}{}
|
||||
}
|
||||
for _, wave := range row.MonsterList {
|
||||
for _, id := range wave {
|
||||
if id != 0 {
|
||||
monsterSet[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(stageByID) == 0 {
|
||||
return MonsterSettingsCatalog{}, fmt.Errorf("Client StageConfig has no stage rows")
|
||||
}
|
||||
|
||||
acc := map[string]*monsterSettingsAccumulator{
|
||||
"moc": {groups: map[uint32]map[uint32]*MonsterSettingsEncounter{}},
|
||||
"pf": {groups: map[uint32]map[uint32]*MonsterSettingsEncounter{}},
|
||||
"as": {groups: map[uint32]map[uint32]*MonsterSettingsEncounter{}},
|
||||
"aa": {groups: map[uint32]map[uint32]*MonsterSettingsEncounter{}},
|
||||
}
|
||||
addBattle := func(entry *MonsterSettingsEncounter, eventID uint32) {
|
||||
stage, ok := stageByID[eventID]
|
||||
if !ok || len(stage.Monsters) == 0 {
|
||||
return
|
||||
}
|
||||
for _, existing := range entry.Battles {
|
||||
if existing.StageID == eventID {
|
||||
return
|
||||
}
|
||||
}
|
||||
entry.Battles = append(entry.Battles, MonsterSettingsBattle{StageID: eventID, Level: stage.Level, Waves: stage.Monsters})
|
||||
}
|
||||
addEntry := func(mode string, groupID uint32, entryID uint32, monsterIDs []uint32, events ...uint32) {
|
||||
bucket, ok := acc[mode]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if bucket.groups[groupID] == nil {
|
||||
bucket.groups[groupID] = map[uint32]*MonsterSettingsEncounter{}
|
||||
}
|
||||
entry := bucket.groups[groupID][entryID]
|
||||
if entry == nil {
|
||||
entry = &MonsterSettingsEncounter{ID: entryID}
|
||||
bucket.groups[groupID][entryID] = entry
|
||||
}
|
||||
for _, monsterID := range monsterIDs {
|
||||
if monsterID == 0 || slices.Contains(entry.MonsterIDs, monsterID) {
|
||||
continue
|
||||
}
|
||||
entry.MonsterIDs = append(entry.MonsterIDs, monsterID)
|
||||
}
|
||||
for _, eventID := range events {
|
||||
if eventID != 0 {
|
||||
addBattle(entry, eventID)
|
||||
}
|
||||
}
|
||||
}
|
||||
modeForChallengeID := func(id uint32) string {
|
||||
// These are the same challenge-ID namespaces used by
|
||||
// setChallengeBattleConfigWithLimit in the game server.
|
||||
switch {
|
||||
case id >= 30000:
|
||||
return "as"
|
||||
case id >= 20000:
|
||||
return "pf"
|
||||
default:
|
||||
return "moc"
|
||||
}
|
||||
}
|
||||
for rawID, row := range challenges.Challenges {
|
||||
id64, err := strconv.ParseUint(rawID, 10, 32)
|
||||
if err != nil || id64 == 0 {
|
||||
continue
|
||||
}
|
||||
addEntry(modeForChallengeID(uint32(id64)), row.GroupID, uint32(id64), []uint32{row.Monster1, row.Monster2}, row.Event1, row.Event2)
|
||||
}
|
||||
for rawID, row := range challenges.Tierce {
|
||||
id64, err := strconv.ParseUint(rawID, 10, 32)
|
||||
if err != nil || id64 == 0 {
|
||||
continue
|
||||
}
|
||||
addEntry(modeForChallengeID(uint32(id64)), row.Group, uint32(id64), nil, row.Event)
|
||||
}
|
||||
|
||||
peakGroupForStage := map[uint32]uint32{}
|
||||
for rawGroupID, group := range challenges.Peak.Groups {
|
||||
groupID64, err := strconv.ParseUint(rawGroupID, 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
groupID := uint32(groupID64)
|
||||
for _, stageID := range group.PreLevelIDs {
|
||||
peakGroupForStage[stageID] = groupID
|
||||
}
|
||||
if group.BossLevelID != 0 {
|
||||
peakGroupForStage[group.BossLevelID] = groupID
|
||||
}
|
||||
}
|
||||
for rawStageID, row := range challenges.Peak.Stages {
|
||||
stageID64, err := strconv.ParseUint(rawStageID, 10, 32)
|
||||
if err != nil || stageID64 == 0 {
|
||||
continue
|
||||
}
|
||||
stageID := uint32(stageID64)
|
||||
groupID := peakGroupForStage[stageID]
|
||||
if groupID == 0 {
|
||||
continue
|
||||
}
|
||||
bucket := acc["aa"].groups
|
||||
if bucket[groupID] == nil {
|
||||
bucket[groupID] = map[uint32]*MonsterSettingsEncounter{}
|
||||
}
|
||||
entry := &MonsterSettingsEncounter{ID: stageID, IsBoss: row.IsBoss, DefaultBuffID: row.DefaultBuff, MonsterIDs: []uint32{row.MonsterID}}
|
||||
addBattle(entry, row.EventID)
|
||||
addBattle(entry, row.HardEventID)
|
||||
if len(entry.Battles) != 0 {
|
||||
bucket[groupID][stageID] = entry
|
||||
}
|
||||
}
|
||||
|
||||
result := MonsterSettingsCatalog{
|
||||
Modes: map[string][]MonsterSettingsGroup{},
|
||||
Sources: []string{"challenge_data.json", "resources/StageConfig.json"},
|
||||
}
|
||||
for _, mode := range []string{"moc", "pf", "as", "aa"} {
|
||||
for groupID, stages := range acc[mode].groups {
|
||||
group := MonsterSettingsGroup{ID: groupID}
|
||||
for _, stage := range stages {
|
||||
if len(stage.Battles) == 0 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(stage.Battles, func(i, j int) bool { return stage.Battles[i].StageID < stage.Battles[j].StageID })
|
||||
if len(stage.MonsterIDs) == 0 {
|
||||
for _, wave := range stage.Battles[0].Waves {
|
||||
for _, monsterID := range wave {
|
||||
if monsterID != 0 && !slices.Contains(stage.MonsterIDs, monsterID) {
|
||||
stage.MonsterIDs = append(stage.MonsterIDs, monsterID)
|
||||
if len(stage.MonsterIDs) == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(stage.MonsterIDs) == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
group.Stages = append(group.Stages, *stage)
|
||||
}
|
||||
if len(group.Stages) != 0 {
|
||||
sort.Slice(group.Stages, func(i, j int) bool { return group.Stages[i].ID < group.Stages[j].ID })
|
||||
result.Modes[mode] = append(result.Modes[mode], group)
|
||||
}
|
||||
}
|
||||
sort.Slice(result.Modes[mode], func(i, j int) bool { return result.Modes[mode][i].ID < result.Modes[mode][j].ID })
|
||||
}
|
||||
for id := range monsterSet {
|
||||
result.MonsterIDs = append(result.MonsterIDs, id)
|
||||
}
|
||||
for level := range levelSet {
|
||||
result.Levels = append(result.Levels, level)
|
||||
}
|
||||
sort.Slice(result.MonsterIDs, func(i, j int) bool { return result.MonsterIDs[i] < result.MonsterIDs[j] })
|
||||
sort.Slice(result.Levels, func(i, j int) bool { return result.Levels[i] < result.Levels[j] })
|
||||
return result, nil
|
||||
}
|
||||
Reference in new issue
Block a user