DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,669 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user