pre cw with probe and a bunch script to read game behavior
This commit is contained in:
1 parent
5120219ae2
commit
da8a32f60d
310 files changed
+219693
-872
No files matched your search
@@ -0,0 +1,306 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
goproto "gac-hkrpg-go/protocol/proto"
|
||||
)
|
||||
|
||||
type inventoryItemResource struct {
|
||||
ItemIDs []uint32 `json:"item_ids"`
|
||||
Lightcones []struct {
|
||||
ID uint32 `json:"id"`
|
||||
MaxRank uint32 `json:"max_rank"`
|
||||
} `json:"lightcones"`
|
||||
Relics []struct {
|
||||
RelicID uint32 `json:"relic_id"`
|
||||
RelicSetID uint32 `json:"relic_set_id"`
|
||||
MainAffixID uint32 `json:"main_affix_id"`
|
||||
SubAffixes []struct {
|
||||
SubAffixID uint32 `json:"sub_affix_id"`
|
||||
Count uint32 `json:"count"`
|
||||
Step uint32 `json:"step"`
|
||||
} `json:"sub_affixes"`
|
||||
} `json:"relics"`
|
||||
}
|
||||
|
||||
var inventoryItemsOnce sync.Once
|
||||
var inventoryItems []uint32
|
||||
var inventoryCatalog inventoryItemResource
|
||||
var inventoryRelicIDs map[uint32]struct{}
|
||||
var inventoryItemsErr error
|
||||
|
||||
func loadInventoryItems() ([]uint32, error) {
|
||||
inventoryItemsOnce.Do(func() {
|
||||
raw, err := common.ReadResourceFile("resources/InventoryItemConfig.json", "InventoryItemConfig.json")
|
||||
if err != nil {
|
||||
inventoryItemsErr = err
|
||||
return
|
||||
}
|
||||
var resource inventoryItemResource
|
||||
if err := json.Unmarshal(raw, &resource); err != nil {
|
||||
inventoryItemsErr = err
|
||||
return
|
||||
}
|
||||
if len(resource.ItemIDs) == 0 || len(resource.Lightcones) == 0 || len(resource.Relics) == 0 {
|
||||
inventoryItemsErr = fmt.Errorf("inventory item resource is incomplete")
|
||||
return
|
||||
}
|
||||
seen := make(map[uint32]bool, len(resource.ItemIDs))
|
||||
for _, id := range resource.ItemIDs {
|
||||
if id == 0 || seen[id] {
|
||||
inventoryItemsErr = fmt.Errorf("invalid or duplicate inventory item id %d", id)
|
||||
return
|
||||
}
|
||||
seen[id] = true
|
||||
}
|
||||
seenLightcones := make(map[uint32]bool, len(resource.Lightcones))
|
||||
for _, lc := range resource.Lightcones {
|
||||
if lc.ID == 0 || lc.MaxRank == 0 || seenLightcones[lc.ID] {
|
||||
inventoryItemsErr = fmt.Errorf("invalid or duplicate Light Cone %d in inventory item resource", lc.ID)
|
||||
return
|
||||
}
|
||||
seenLightcones[lc.ID] = true
|
||||
}
|
||||
seenRelics := make(map[uint32]struct{}, len(resource.Relics))
|
||||
for _, relic := range resource.Relics {
|
||||
if relic.RelicID == 0 || relic.RelicSetID == 0 || relic.MainAffixID == 0 || len(relic.SubAffixes) != 4 {
|
||||
inventoryItemsErr = fmt.Errorf("invalid or duplicate Relic %d in inventory item resource", relic.RelicID)
|
||||
return
|
||||
}
|
||||
if _, exists := seenRelics[relic.RelicID]; exists {
|
||||
inventoryItemsErr = fmt.Errorf("invalid or duplicate Relic %d in inventory item resource", relic.RelicID)
|
||||
return
|
||||
}
|
||||
seenRelics[relic.RelicID] = struct{}{}
|
||||
}
|
||||
inventoryCatalog = resource
|
||||
inventoryRelicIDs = seenRelics
|
||||
inventoryItems = resource.ItemIDs
|
||||
})
|
||||
if inventoryItemsErr != nil {
|
||||
return nil, fmt.Errorf("load inventory item resource: %w", inventoryItemsErr)
|
||||
}
|
||||
return inventoryItems, nil
|
||||
}
|
||||
|
||||
func supportedRelicIDs() (map[uint32]struct{}, error) {
|
||||
if _, err := loadInventoryItems(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inventoryRelicIDs, nil
|
||||
}
|
||||
|
||||
func filterSupportedRelics(relics []*common.Relic, supported map[uint32]struct{}) []*common.Relic {
|
||||
filtered := make([]*common.Relic, 0, len(relics))
|
||||
for _, relic := range relics {
|
||||
if relic == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := supported[relic.RelicID]; ok {
|
||||
filtered = append(filtered, relic)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func supportedRelicsForAvatar(player *common.FreesrData, avatarID uint32) ([]*common.Relic, error) {
|
||||
supported, err := supportedRelicIDs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return filterSupportedRelics(player.FindRelicsFor(avatarID), supported), nil
|
||||
}
|
||||
|
||||
func supportedProtoRelics(relics []common.Relic) ([]*goproto.Relic, int, error) {
|
||||
supported, err := supportedRelicIDs()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
result := make([]*goproto.Relic, 0, len(relics))
|
||||
omitted := 0
|
||||
for i := range relics {
|
||||
if _, ok := supported[relics[i].RelicID]; !ok {
|
||||
omitted++
|
||||
continue
|
||||
}
|
||||
result = append(result, relics[i].ToProtoRelic())
|
||||
}
|
||||
return result, omitted, nil
|
||||
}
|
||||
|
||||
// ensureCatalogLightcones makes each client-configured Light Cone available as
|
||||
// an unassigned level-1 copy while keeping existing copies and their ownership.
|
||||
func ensureCatalogLightcones(player *common.FreesrData) error {
|
||||
if _, err := loadInventoryItems(); err != nil {
|
||||
return err
|
||||
}
|
||||
usedItems := make(map[uint32]bool, len(player.Lightcones))
|
||||
usedUIDs := make(map[uint32]bool, len(player.Lightcones))
|
||||
var nextUID uint32
|
||||
for _, item := range player.Lightcones {
|
||||
usedItems[item.ItemID] = true
|
||||
usedUIDs[item.InternalUID] = true
|
||||
if item.InternalUID >= nextUID {
|
||||
if item.InternalUID == ^uint32(0) {
|
||||
return fmt.Errorf("cannot allocate a Light Cone inventory UID")
|
||||
}
|
||||
nextUID = item.InternalUID + 1
|
||||
}
|
||||
}
|
||||
var additions []common.Lightcone
|
||||
for _, item := range inventoryCatalog.Lightcones {
|
||||
if usedItems[item.ID] {
|
||||
continue
|
||||
}
|
||||
for usedUIDs[nextUID] {
|
||||
if nextUID == ^uint32(0) {
|
||||
return fmt.Errorf("cannot allocate a Light Cone inventory UID")
|
||||
}
|
||||
nextUID++
|
||||
}
|
||||
additions = append(additions, common.Lightcone{
|
||||
ItemID: item.ID, InternalUID: nextUID, Level: 1, Promotion: 0, Rank: 1,
|
||||
})
|
||||
usedItems[item.ID] = true
|
||||
usedUIDs[nextUID] = true
|
||||
if nextUID == ^uint32(0) {
|
||||
return fmt.Errorf("cannot allocate a Light Cone inventory UID")
|
||||
}
|
||||
nextUID++
|
||||
}
|
||||
if err := common.SaveNewLightcones(additions); err != nil {
|
||||
return err
|
||||
}
|
||||
player.Lightcones = append(player.Lightcones, additions...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureCatalogRelics makes each Client-configured Relic type available as an
|
||||
// unassigned instance with affixes validated against its own config groups.
|
||||
func ensureCatalogRelics(player *common.FreesrData) error {
|
||||
if _, err := loadInventoryItems(); err != nil {
|
||||
return err
|
||||
}
|
||||
usedRelics := make(map[uint32]bool, len(player.Relics))
|
||||
usedUIDs := make(map[uint32]bool, len(player.Relics))
|
||||
var nextUID uint32
|
||||
for _, item := range player.Relics {
|
||||
usedRelics[item.RelicID] = true
|
||||
usedUIDs[item.InternalUID] = true
|
||||
if item.InternalUID >= nextUID {
|
||||
if item.InternalUID == ^uint32(0) {
|
||||
return fmt.Errorf("cannot allocate a Relic inventory UID")
|
||||
}
|
||||
nextUID = item.InternalUID + 1
|
||||
}
|
||||
}
|
||||
var additions []common.Relic
|
||||
for _, item := range inventoryCatalog.Relics {
|
||||
if usedRelics[item.RelicID] {
|
||||
continue
|
||||
}
|
||||
for usedUIDs[nextUID] {
|
||||
if nextUID == ^uint32(0) {
|
||||
return fmt.Errorf("cannot allocate a Relic inventory UID")
|
||||
}
|
||||
nextUID++
|
||||
}
|
||||
relic := common.Relic{
|
||||
RelicID: item.RelicID, RelicSetID: item.RelicSetID,
|
||||
MainAffixID: item.MainAffixID, InternalUID: nextUID,
|
||||
SubAffixes: make([]common.SubAffix, 0, len(item.SubAffixes)),
|
||||
}
|
||||
for _, sub := range item.SubAffixes {
|
||||
relic.SubAffixes = append(relic.SubAffixes, common.SubAffix{
|
||||
SubAffixID: sub.SubAffixID, Count: sub.Count, Step: sub.Step,
|
||||
})
|
||||
}
|
||||
additions = append(additions, relic)
|
||||
usedRelics[item.RelicID] = true
|
||||
usedUIDs[nextUID] = true
|
||||
if nextUID == ^uint32(0) {
|
||||
return fmt.Errorf("cannot allocate a Relic inventory UID")
|
||||
}
|
||||
nextUID++
|
||||
}
|
||||
if err := common.SaveNewRelics(additions); err != nil {
|
||||
return err
|
||||
}
|
||||
player.Relics = append(player.Relics, additions...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func bagMaterials(player *common.FreesrData) ([]*goproto.Material, error) {
|
||||
itemIDs, err := loadInventoryItems()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
counts := make(map[uint32]uint32, len(itemIDs)+64)
|
||||
for _, id := range itemIDs {
|
||||
counts[id] = 1
|
||||
}
|
||||
counts[1] = 9999999 // Stellar Jade
|
||||
counts[2] = 99999999 // Credit
|
||||
counts[101] = 999999 // Normal Pass
|
||||
counts[102] = 999999 // Special Pass
|
||||
|
||||
eidolon, err := eidolonMaterials(player)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range eidolon {
|
||||
// A completed rank/EX grant is zero, including when the general
|
||||
// inventory catalog also contains this item.
|
||||
counts[item.Tid] = item.Num
|
||||
}
|
||||
foods, err := techniqueMaterials(player)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range foods {
|
||||
if item.Num > 0 {
|
||||
counts[item.Tid] = item.Num
|
||||
}
|
||||
}
|
||||
cosmetics, err := common.LoadPlayerCosmetics()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range cosmetics.OutfitItems {
|
||||
if item.OwnedCount > 0 {
|
||||
counts[item.ItemID] = item.OwnedCount
|
||||
}
|
||||
}
|
||||
// Explicit inventory edits from GaC Srtool are authoritative over generated
|
||||
// defaults and progression grants. Keeping zero values lets users remove an
|
||||
// item from the bag without deleting its catalog entry.
|
||||
allowed := make(map[uint32]bool, len(itemIDs)+2)
|
||||
allowed[1], allowed[2] = true, true
|
||||
for _, id := range itemIDs {
|
||||
allowed[id] = true
|
||||
}
|
||||
for _, item := range eidolon {
|
||||
allowed[item.Tid] = true
|
||||
}
|
||||
for id, count := range player.Materials {
|
||||
if allowed[id] {
|
||||
counts[id] = count
|
||||
}
|
||||
}
|
||||
|
||||
ids := make([]uint32, 0, len(counts))
|
||||
for id := range counts {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
||||
materials := make([]*goproto.Material, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
materials = append(materials, &goproto.Material{Tid: id, Num: counts[id]})
|
||||
}
|
||||
return materials, nil
|
||||
}
|
||||
Reference in new issue
Block a user