Files
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

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
}