服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
670 lines
20 KiB
Go
670 lines
20 KiB
Go
package player
|
|
|
|
// Code generated by profilegen; DO NOT EDIT.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type Profile struct {
|
|
AccountID string `json:"account_id"`
|
|
RoleID string `json:"role_id"`
|
|
Name string `json:"name"`
|
|
Level int `json:"level"`
|
|
State string `json:"state"`
|
|
LoadedAt time.Time `json:"loaded_at,omitempty"`
|
|
SavedAt time.Time `json:"saved_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
Version int64 `json:"version"`
|
|
Currencies map[string]int64 `json:"currencies,omitempty"`
|
|
Inventory map[string]int64 `json:"inventory,omitempty"`
|
|
Quests map[string]string `json:"quests,omitempty"`
|
|
Mailbox []MailRecord `json:"mailbox,omitempty"`
|
|
}
|
|
|
|
const (
|
|
ProfileFieldBase ProfileField = "base"
|
|
ProfileFieldRuntime ProfileField = "runtime"
|
|
ProfileFieldMeta ProfileField = "meta"
|
|
ProfileFieldCurrency ProfileField = "currency"
|
|
ProfileFieldInventory ProfileField = "inventory"
|
|
ProfileFieldQuest ProfileField = "quest"
|
|
ProfileFieldMail ProfileField = "mail"
|
|
)
|
|
|
|
var orderedFields = []ProfileField{
|
|
ProfileFieldBase,
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldCurrency,
|
|
ProfileFieldInventory,
|
|
ProfileFieldQuest,
|
|
ProfileFieldMail,
|
|
}
|
|
|
|
const (
|
|
ProfileModuleIdentity = "identity"
|
|
ProfileModuleRuntime = "runtime"
|
|
ProfileModuleMeta = "meta"
|
|
ProfileModuleWallet = "wallet"
|
|
ProfileModuleInventory = "inventory"
|
|
ProfileModuleQuest = "quest"
|
|
ProfileModuleMailbox = "mailbox"
|
|
)
|
|
|
|
type profileBaseRecord struct {
|
|
AccountID string `json:"account_id"`
|
|
RoleID string `json:"role_id"`
|
|
Name string `json:"name"`
|
|
Level int `json:"level"`
|
|
}
|
|
|
|
type profileRuntimeRecord struct {
|
|
State string `json:"state"`
|
|
LoadedAt time.Time `json:"loaded_at,omitempty"`
|
|
SavedAt time.Time `json:"saved_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type profileMetaRecord struct {
|
|
Version int64 `json:"version"`
|
|
}
|
|
|
|
type currencyRecord struct {
|
|
Currencies map[string]int64 `json:"currencies,omitempty"`
|
|
}
|
|
|
|
type inventoryRecord struct {
|
|
Inventory map[string]int64 `json:"inventory,omitempty"`
|
|
}
|
|
|
|
type profileQuestRecord struct {
|
|
Quests map[string]string `json:"quests,omitempty"`
|
|
}
|
|
|
|
type profileMailRecord struct {
|
|
Mailbox []MailRecord `json:"mailbox,omitempty"`
|
|
}
|
|
|
|
const (
|
|
redisBaseField = "base"
|
|
redisRuntimeField = "runtime"
|
|
redisMetaField = "meta"
|
|
redisCurrencyField = "currency"
|
|
redisInventoryField = "inventory"
|
|
redisQuestField = "quest"
|
|
redisMailField = "mail"
|
|
)
|
|
|
|
var genFieldLookup = map[ProfileField]struct{}{
|
|
ProfileFieldBase: {},
|
|
ProfileFieldRuntime: {},
|
|
ProfileFieldMeta: {},
|
|
ProfileFieldCurrency: {},
|
|
ProfileFieldInventory: {},
|
|
ProfileFieldQuest: {},
|
|
ProfileFieldMail: {},
|
|
}
|
|
|
|
var genModuleFields = map[string]ProfileField{
|
|
ProfileModuleIdentity: ProfileFieldBase,
|
|
ProfileModuleRuntime: ProfileFieldRuntime,
|
|
ProfileModuleMeta: ProfileFieldMeta,
|
|
ProfileModuleWallet: ProfileFieldCurrency,
|
|
ProfileModuleInventory: ProfileFieldInventory,
|
|
ProfileModuleQuest: ProfileFieldQuest,
|
|
ProfileModuleMailbox: ProfileFieldMail,
|
|
}
|
|
|
|
var genReqFields = []ProfileField{
|
|
ProfileFieldBase,
|
|
}
|
|
|
|
var genBackfillFields = []ProfileField{
|
|
ProfileFieldCurrency,
|
|
ProfileFieldInventory,
|
|
ProfileFieldQuest,
|
|
ProfileFieldMail,
|
|
}
|
|
|
|
func AllProfileFields() []ProfileField {
|
|
return append([]ProfileField(nil), orderedFields...)
|
|
}
|
|
|
|
func reqProfileFields() []ProfileField {
|
|
return append([]ProfileField(nil), genReqFields...)
|
|
}
|
|
|
|
func backfillFields() []ProfileField {
|
|
return append([]ProfileField(nil), genBackfillFields...)
|
|
}
|
|
|
|
func isProfileField(field ProfileField) bool {
|
|
_, ok := genFieldLookup[field]
|
|
return ok
|
|
}
|
|
|
|
func fieldByModule(module string) (ProfileField, bool) {
|
|
field, ok := genModuleFields[module]
|
|
return field, ok
|
|
}
|
|
|
|
func dirtyFieldsByModule(module string) []ProfileField {
|
|
field, ok := fieldByModule(module)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return profileDirtyFields(field)
|
|
}
|
|
|
|
func normProfileFields(fields []ProfileField) []ProfileField {
|
|
if len(fields) == 0 {
|
|
return nil
|
|
}
|
|
seen := make(map[ProfileField]struct{}, len(fields))
|
|
for _, field := range fields {
|
|
if isProfileField(field) {
|
|
seen[field] = struct{}{}
|
|
}
|
|
}
|
|
out := make([]ProfileField, 0, len(seen))
|
|
for _, field := range orderedFields {
|
|
if _, ok := seen[field]; ok {
|
|
out = append(out, field)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mergeProfileFields(left, right []ProfileField) []ProfileField {
|
|
if len(left) == 0 {
|
|
return normProfileFields(right)
|
|
}
|
|
if len(right) == 0 {
|
|
return normProfileFields(left)
|
|
}
|
|
merged := make([]ProfileField, 0, len(left)+len(right))
|
|
merged = append(merged, left...)
|
|
merged = append(merged, right...)
|
|
return normProfileFields(merged)
|
|
}
|
|
|
|
func profileDirtyFields(field ProfileField) []ProfileField {
|
|
switch field {
|
|
case ProfileFieldBase:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldBase,
|
|
})
|
|
case ProfileFieldRuntime:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
})
|
|
case ProfileFieldMeta:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldMeta,
|
|
})
|
|
case ProfileFieldCurrency:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldCurrency,
|
|
})
|
|
case ProfileFieldInventory:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldInventory,
|
|
})
|
|
case ProfileFieldQuest:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldQuest,
|
|
})
|
|
case ProfileFieldMail:
|
|
return normProfileFields([]ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldMail,
|
|
})
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
const (
|
|
ProfileOperationAddCurrency ProfileOperation = "add_currency"
|
|
ProfileOperationSetInventoryItem ProfileOperation = "set_inventory_item"
|
|
ProfileOperationSetQuestState ProfileOperation = "set_quest_state"
|
|
)
|
|
|
|
var genProfileDescs = []ProfileFieldDescriptor{
|
|
{Field: ProfileFieldBase, Module: ProfileModuleIdentity, HashField: redisBaseField, Record: "profileBaseRecord", Required: true, Backfill: false, DirtyFields: []ProfileField{
|
|
ProfileFieldBase,
|
|
}, Description: "Account, role, name and level identity data.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "AccountID", Type: "string", JSONName: "account_id", Profile: "AccountID"},
|
|
{Name: "RoleID", Type: "string", JSONName: "role_id", Profile: "RoleID"},
|
|
{Name: "Name", Type: "string", JSONName: "name", Profile: "Name"},
|
|
{Name: "Level", Type: "int", JSONName: "level", Profile: "Level"},
|
|
}},
|
|
{Field: ProfileFieldRuntime, Module: ProfileModuleRuntime, HashField: redisRuntimeField, Record: "profileRuntimeRecord", Required: false, Backfill: false, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
}, Description: "Online state and profile timestamps.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "State", Type: "string", JSONName: "state", Profile: "State"},
|
|
{Name: "LoadedAt", Type: "time.Time", JSONName: "loaded_at", Profile: "LoadedAt"},
|
|
{Name: "SavedAt", Type: "time.Time", JSONName: "saved_at", Profile: "SavedAt"},
|
|
{Name: "UpdatedAt", Type: "time.Time", JSONName: "updated_at", Profile: "UpdatedAt"},
|
|
}},
|
|
{Field: ProfileFieldMeta, Module: ProfileModuleMeta, HashField: redisMetaField, Record: "profileMetaRecord", Required: false, Backfill: false, DirtyFields: []ProfileField{
|
|
ProfileFieldMeta,
|
|
}, Description: "Version and optimistic metadata.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "Version", Type: "int64", JSONName: "version", Profile: "Version"},
|
|
}},
|
|
{Field: ProfileFieldCurrency, Module: ProfileModuleWallet, HashField: redisCurrencyField, Record: "currencyRecord", Required: false, Backfill: true, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldCurrency,
|
|
}, Description: "Wallet balances such as gold and diamond.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "Currencies", Type: "map[string]int64", JSONName: "currencies", Profile: "Currencies"},
|
|
}},
|
|
{Field: ProfileFieldInventory, Module: ProfileModuleInventory, HashField: redisInventoryField, Record: "inventoryRecord", Required: false, Backfill: true, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldInventory,
|
|
}, Description: "Item quantities keyed by item id.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "Inventory", Type: "map[string]int64", JSONName: "inventory", Profile: "Inventory"},
|
|
}},
|
|
{Field: ProfileFieldQuest, Module: ProfileModuleQuest, HashField: redisQuestField, Record: "profileQuestRecord", Required: false, Backfill: true, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldQuest,
|
|
}, Description: "Quest state keyed by quest id.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "Quests", Type: "map[string]string", JSONName: "quests", Profile: "Quests"},
|
|
}},
|
|
{Field: ProfileFieldMail, Module: ProfileModuleMailbox, HashField: redisMailField, Record: "profileMailRecord", Required: false, Backfill: true, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldMail,
|
|
}, Description: "Mailbox records and read states.", Columns: []ProfileRecordFieldDescriptor{
|
|
{Name: "Mailbox", Type: "[]MailRecord", JSONName: "mailbox", Profile: "Mailbox"},
|
|
}},
|
|
}
|
|
|
|
func cloneProfileDesc(descriptor ProfileFieldDescriptor) ProfileFieldDescriptor {
|
|
descriptor.DirtyFields = append([]ProfileField(nil), descriptor.DirtyFields...)
|
|
descriptor.Columns = append([]ProfileRecordFieldDescriptor(nil), descriptor.Columns...)
|
|
return descriptor
|
|
}
|
|
|
|
func ProfileFieldDescriptors() []ProfileFieldDescriptor {
|
|
out := make([]ProfileFieldDescriptor, len(genProfileDescs))
|
|
for i := range out {
|
|
out[i] = cloneProfileDesc(genProfileDescs[i])
|
|
}
|
|
return out
|
|
}
|
|
|
|
func profileDBDesc(field ProfileField) (ProfileFieldDescriptor, bool) {
|
|
switch field {
|
|
case ProfileFieldBase:
|
|
return cloneProfileDesc(genProfileDescs[0]), true
|
|
case ProfileFieldRuntime:
|
|
return cloneProfileDesc(genProfileDescs[1]), true
|
|
case ProfileFieldMeta:
|
|
return cloneProfileDesc(genProfileDescs[2]), true
|
|
case ProfileFieldCurrency:
|
|
return cloneProfileDesc(genProfileDescs[3]), true
|
|
case ProfileFieldInventory:
|
|
return cloneProfileDesc(genProfileDescs[4]), true
|
|
case ProfileFieldQuest:
|
|
return cloneProfileDesc(genProfileDescs[5]), true
|
|
case ProfileFieldMail:
|
|
return cloneProfileDesc(genProfileDescs[6]), true
|
|
default:
|
|
return ProfileFieldDescriptor{}, false
|
|
}
|
|
}
|
|
|
|
func profileDBDescByHash(hashField string) (ProfileFieldDescriptor, bool) {
|
|
switch hashField {
|
|
case redisBaseField:
|
|
return cloneProfileDesc(genProfileDescs[0]), true
|
|
case redisRuntimeField:
|
|
return cloneProfileDesc(genProfileDescs[1]), true
|
|
case redisMetaField:
|
|
return cloneProfileDesc(genProfileDescs[2]), true
|
|
case redisCurrencyField:
|
|
return cloneProfileDesc(genProfileDescs[3]), true
|
|
case redisInventoryField:
|
|
return cloneProfileDesc(genProfileDescs[4]), true
|
|
case redisQuestField:
|
|
return cloneProfileDesc(genProfileDescs[5]), true
|
|
case redisMailField:
|
|
return cloneProfileDesc(genProfileDescs[6]), true
|
|
default:
|
|
return ProfileFieldDescriptor{}, false
|
|
}
|
|
}
|
|
|
|
func profileDBHashField(field ProfileField) (string, bool) {
|
|
switch field {
|
|
case ProfileFieldBase:
|
|
return redisBaseField, true
|
|
case ProfileFieldRuntime:
|
|
return redisRuntimeField, true
|
|
case ProfileFieldMeta:
|
|
return redisMetaField, true
|
|
case ProfileFieldCurrency:
|
|
return redisCurrencyField, true
|
|
case ProfileFieldInventory:
|
|
return redisInventoryField, true
|
|
case ProfileFieldQuest:
|
|
return redisQuestField, true
|
|
case ProfileFieldMail:
|
|
return redisMailField, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
var genProfileOps = []ProfileOperationDescriptor{
|
|
{Operation: ProfileOperationAddCurrency, Module: ProfileModuleWallet, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldCurrency,
|
|
}, Description: "Adds currency to wallet balances and marks wallet dirty."},
|
|
{Operation: ProfileOperationSetInventoryItem, Module: ProfileModuleInventory, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldInventory,
|
|
}, Description: "Sets or removes one inventory item quantity."},
|
|
{Operation: ProfileOperationSetQuestState, Module: ProfileModuleQuest, DirtyFields: []ProfileField{
|
|
ProfileFieldRuntime,
|
|
ProfileFieldMeta,
|
|
ProfileFieldQuest,
|
|
}, Description: "Updates one quest state entry."},
|
|
}
|
|
|
|
func cloneProfileOp(descriptor ProfileOperationDescriptor) ProfileOperationDescriptor {
|
|
descriptor.DirtyFields = append([]ProfileField(nil), descriptor.DirtyFields...)
|
|
return descriptor
|
|
}
|
|
|
|
func ProfileOperationDescriptors() []ProfileOperationDescriptor {
|
|
out := make([]ProfileOperationDescriptor, len(genProfileOps))
|
|
for i := range out {
|
|
out[i] = cloneProfileOp(genProfileOps[i])
|
|
}
|
|
return out
|
|
}
|
|
|
|
func opDesc(operation ProfileOperation) (ProfileOperationDescriptor, bool) {
|
|
switch operation {
|
|
case ProfileOperationAddCurrency:
|
|
return cloneProfileOp(genProfileOps[0]), true
|
|
case ProfileOperationSetInventoryItem:
|
|
return cloneProfileOp(genProfileOps[1]), true
|
|
case ProfileOperationSetQuestState:
|
|
return cloneProfileOp(genProfileOps[2]), true
|
|
default:
|
|
return ProfileOperationDescriptor{}, false
|
|
}
|
|
}
|
|
|
|
func dirtyFieldsByOp(operation ProfileOperation) []ProfileField {
|
|
descriptor, ok := opDesc(operation)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return descriptor.DirtyFields
|
|
}
|
|
|
|
func mutateProfileOp(ctx context.Context, module *Module, accountID string, operation ProfileOperation, mutate func(*Profile)) (Profile, error) {
|
|
if module == nil {
|
|
return Profile{}, fmt.Errorf("profile module is nil")
|
|
}
|
|
if mutate == nil {
|
|
return Profile{}, fmt.Errorf("profile operation %s mutation is required", operation)
|
|
}
|
|
dirtyFields := dirtyFieldsByOp(operation)
|
|
if len(dirtyFields) == 0 {
|
|
return Profile{}, fmt.Errorf("unsupported profile operation %s", operation)
|
|
}
|
|
return module.mutateLoadedProfile(ctx, accountID, func(profile *Profile) []ProfileField {
|
|
mutate(profile)
|
|
return dirtyFields
|
|
})
|
|
}
|
|
|
|
func mutateAddCurrency(ctx context.Context, module *Module, accountID string, mutate func(*Profile)) (Profile, error) {
|
|
return mutateProfileOp(ctx, module, accountID, ProfileOperationAddCurrency, mutate)
|
|
}
|
|
|
|
func mutateSetItem(ctx context.Context, module *Module, accountID string, mutate func(*Profile)) (Profile, error) {
|
|
return mutateProfileOp(ctx, module, accountID, ProfileOperationSetInventoryItem, mutate)
|
|
}
|
|
|
|
func mutateSetQuest(ctx context.Context, module *Module, accountID string, mutate func(*Profile)) (Profile, error) {
|
|
return mutateProfileOp(ctx, module, accountID, ProfileOperationSetQuestState, mutate)
|
|
}
|
|
|
|
func encodeProfileField(profile Profile, field ProfileField) ([]byte, error) {
|
|
switch field {
|
|
case ProfileFieldBase:
|
|
return json.Marshal(profileBaseRecord{
|
|
AccountID: profile.AccountID,
|
|
RoleID: profile.RoleID,
|
|
Name: profile.Name,
|
|
Level: profile.Level,
|
|
})
|
|
case ProfileFieldRuntime:
|
|
return json.Marshal(profileRuntimeRecord{
|
|
State: profile.State,
|
|
LoadedAt: profile.LoadedAt,
|
|
SavedAt: profile.SavedAt,
|
|
UpdatedAt: profile.UpdatedAt,
|
|
})
|
|
case ProfileFieldMeta:
|
|
return json.Marshal(profileMetaRecord{
|
|
Version: profile.Version,
|
|
})
|
|
case ProfileFieldCurrency:
|
|
return json.Marshal(currencyRecord{
|
|
Currencies: profile.Currencies,
|
|
})
|
|
case ProfileFieldInventory:
|
|
return json.Marshal(inventoryRecord{
|
|
Inventory: profile.Inventory,
|
|
})
|
|
case ProfileFieldQuest:
|
|
return json.Marshal(profileQuestRecord{
|
|
Quests: profile.Quests,
|
|
})
|
|
case ProfileFieldMail:
|
|
return json.Marshal(profileMailRecord{
|
|
Mailbox: profile.Mailbox,
|
|
})
|
|
default:
|
|
return nil, fmt.Errorf("unsupported profile field %q", field)
|
|
}
|
|
}
|
|
|
|
func encodeProfileFields(profile Profile, fields []ProfileField) (map[string][]byte, error) {
|
|
fields = normProfileFields(fields)
|
|
if len(fields) == 0 {
|
|
return nil, nil
|
|
}
|
|
out := make(map[string][]byte, len(fields))
|
|
for _, field := range fields {
|
|
hashField, ok := profileDBHashField(field)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unsupported profile field %q", field)
|
|
}
|
|
data, err := encodeProfileField(profile, field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[hashField] = data
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func decodeProfileField(profile *Profile, field ProfileField, data []byte) error {
|
|
if profile == nil {
|
|
return fmt.Errorf("profile is nil")
|
|
}
|
|
switch field {
|
|
case ProfileFieldBase:
|
|
if len(data) == 0 {
|
|
return fmt.Errorf("profile field %s is empty", field)
|
|
}
|
|
var record profileBaseRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.AccountID = record.AccountID
|
|
profile.RoleID = record.RoleID
|
|
profile.Name = record.Name
|
|
profile.Level = record.Level
|
|
return nil
|
|
case ProfileFieldRuntime:
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var record profileRuntimeRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.State = record.State
|
|
profile.LoadedAt = record.LoadedAt
|
|
profile.SavedAt = record.SavedAt
|
|
profile.UpdatedAt = record.UpdatedAt
|
|
return nil
|
|
case ProfileFieldMeta:
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var record profileMetaRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.Version = record.Version
|
|
return nil
|
|
case ProfileFieldCurrency:
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var record currencyRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.Currencies = record.Currencies
|
|
return nil
|
|
case ProfileFieldInventory:
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var record inventoryRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.Inventory = record.Inventory
|
|
return nil
|
|
case ProfileFieldQuest:
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var record profileQuestRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.Quests = record.Quests
|
|
return nil
|
|
case ProfileFieldMail:
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
var record profileMailRecord
|
|
if err := json.Unmarshal(data, &record); err != nil {
|
|
return fmt.Errorf("decode profile field %s: %w", field, err)
|
|
}
|
|
profile.Mailbox = record.Mailbox
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unsupported profile field %q", field)
|
|
}
|
|
}
|
|
|
|
func backfillProfileField(profile *Profile, field ProfileField, now time.Time) bool {
|
|
if profile == nil {
|
|
return false
|
|
}
|
|
switch field {
|
|
case ProfileFieldCurrency:
|
|
return backfillCurrency(profile, now)
|
|
case ProfileFieldInventory:
|
|
return backfillInventory(profile, now)
|
|
case ProfileFieldQuest:
|
|
return backfillProfileQuest(profile, now)
|
|
case ProfileFieldMail:
|
|
return backfillProfileMail(profile, now)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func backfillModules(profile *Profile, now time.Time) []ProfileField {
|
|
if profile == nil {
|
|
return nil
|
|
}
|
|
dirty := make([]ProfileField, 0, len(genBackfillFields))
|
|
for _, field := range genBackfillFields {
|
|
if backfillProfileField(profile, field, now) {
|
|
dirty = append(dirty, field)
|
|
}
|
|
}
|
|
return normProfileFields(dirty)
|
|
}
|
|
|
|
func redisModuleFields() []string {
|
|
return []string{
|
|
redisBaseField,
|
|
redisRuntimeField,
|
|
redisMetaField,
|
|
redisCurrencyField,
|
|
redisInventoryField,
|
|
redisQuestField,
|
|
redisMailField,
|
|
}
|
|
}
|
|
|
|
func profileToModules(profile Profile) (map[string][]byte, error) {
|
|
out := make(map[string][]byte, len(genProfileDescs))
|
|
for _, descriptor := range genProfileDescs {
|
|
data, err := encodeProfileField(profile, descriptor.Field)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[descriptor.HashField] = data
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func profileFromRedis(accountID string, modules map[string][]byte) (Profile, error) {
|
|
var profile Profile
|
|
for _, descriptor := range genProfileDescs {
|
|
if err := decodeProfileField(&profile, descriptor.Field, modules[descriptor.HashField]); err != nil {
|
|
return Profile{}, fmt.Errorf("decode redis profile %s: %w", accountID, err)
|
|
}
|
|
}
|
|
if profile.AccountID == "" {
|
|
profile.AccountID = accountID
|
|
}
|
|
return profile, nil
|
|
}
|