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,283 @@
|
||||
package companion
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
configSchemaKey = "schema_version"
|
||||
configRevisionKey = "revision"
|
||||
configEnabledKey = "enabled"
|
||||
configBalanceKey = "balance_enabled"
|
||||
configProgressionKey = "progression_enabled"
|
||||
configRandomEquipmentKey = "random_equipment_enabled"
|
||||
configAISchemaKey = "ai_schema_version"
|
||||
configAIDefaultsKey = "ai_defaults"
|
||||
configAIProfilesKey = "ai_profiles"
|
||||
)
|
||||
|
||||
func ConfigFromSettings(
|
||||
record repository.SettingsRecord,
|
||||
ownerCharacterID string,
|
||||
) (Config, error) {
|
||||
scope, err := SettingsScope(ownerCharacterID)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return configFromSettingsScope(record, scope)
|
||||
}
|
||||
|
||||
func ConfigFromAccountSettings(
|
||||
record repository.SettingsRecord,
|
||||
accountID string,
|
||||
) (Config, error) {
|
||||
scope, err := AccountSettingsScope(accountID)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return configFromSettingsScope(record, scope)
|
||||
}
|
||||
|
||||
func configFromSettingsScope(
|
||||
record repository.SettingsRecord,
|
||||
expectedScope string,
|
||||
) (Config, error) {
|
||||
if strings.TrimSpace(record.Scope) != expectedScope {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
values := record.Values
|
||||
if values == nil {
|
||||
return DefaultConfig(), nil
|
||||
}
|
||||
|
||||
source := strings.TrimSpace(values[sourceCharacterSettingKey])
|
||||
sourcesRaw, hasSources := values[sourceCharactersSettingKey]
|
||||
_, hasSchema := values[configSchemaKey]
|
||||
_, hasRevision := values[configRevisionKey]
|
||||
_, hasEnabled := values[configEnabledKey]
|
||||
|
||||
// Phase-1 rows stored only source_character_id. Keep them readable so the
|
||||
// phase-2 account-scope migration does not invalidate accepted setups.
|
||||
if !hasSchema && !hasRevision && !hasEnabled {
|
||||
if source == "" {
|
||||
return DefaultConfig(), nil
|
||||
}
|
||||
cfg := Config{
|
||||
SchemaVersion: 1,
|
||||
Revision: 0,
|
||||
Enabled: true,
|
||||
SourceCharacterID: source,
|
||||
SourceCharacterIDs: []string{source},
|
||||
}
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
schema, err := parseCanonicalUint(values[configSchemaKey], 32)
|
||||
if err != nil || schema != 1 {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
revision, err := parseCanonicalUint(values[configRevisionKey], 64)
|
||||
if err != nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
enabled, err := strconv.ParseBool(strings.TrimSpace(values[configEnabledKey]))
|
||||
if err != nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
sources := []string{}
|
||||
if hasSources {
|
||||
if strings.TrimSpace(sourcesRaw) != sourcesRaw {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
if sourcesRaw != "" {
|
||||
var err error
|
||||
sources, err = canonicalSourceCharacters(strings.Split(sourcesRaw, ","))
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
}
|
||||
} else if source != "" {
|
||||
sources = []string{source}
|
||||
}
|
||||
if len(sources) != 0 {
|
||||
if source == "" {
|
||||
source = sources[0]
|
||||
} else if source != sources[0] {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
}
|
||||
balanceEnabled := false
|
||||
if raw, exists := values[configBalanceKey]; exists {
|
||||
balanceEnabled, err = strconv.ParseBool(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
}
|
||||
progressionEnabled := false
|
||||
if raw, exists := values[configProgressionKey]; exists {
|
||||
progressionEnabled, err = strconv.ParseBool(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
}
|
||||
randomEquipmentEnabled := false
|
||||
if raw, exists := values[configRandomEquipmentKey]; exists {
|
||||
randomEquipmentEnabled, err = strconv.ParseBool(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
}
|
||||
// Old rows may still contain the former distance/timing ai_* keys. They
|
||||
// are intentionally ignored; those values never had a verified action
|
||||
// owner. Schema 2 stores only the high-level profile and its bounded tuning
|
||||
// object, never raw Lua/AIC instructions.
|
||||
ai := AIConfig{}
|
||||
aiSchemaV2 := false
|
||||
if raw, exists := values[configAISchemaKey]; exists && strings.TrimSpace(raw) != "2" {
|
||||
return Config{}, ErrSelection
|
||||
} else if exists {
|
||||
aiSchemaV2 = true
|
||||
}
|
||||
if raw, exists := values[configAIDefaultsKey]; exists {
|
||||
if strings.TrimSpace(raw) == "" || json.Unmarshal([]byte(raw), &ai) != nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
if err := ai.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
ai = NormalizeAIConfig(ai)
|
||||
}
|
||||
profiles := map[string]AIProfile(nil)
|
||||
if raw, exists := values[configAIProfilesKey]; exists {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw), &profiles); err != nil || profiles == nil {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
for characterID, profile := range profiles {
|
||||
if !validCharacterID(characterID) {
|
||||
return Config{}, ErrSelection
|
||||
}
|
||||
profile = NormalizeAIProfile(profile)
|
||||
if !aiSchemaV2 {
|
||||
// Old profiles had no protection field; preserve the new safe
|
||||
// default when they are first read.
|
||||
profile.ProtectLeader = true
|
||||
}
|
||||
profiles[characterID] = profile
|
||||
if err := profile.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg := Config{
|
||||
SchemaVersion: int(schema),
|
||||
Revision: revision,
|
||||
Enabled: enabled,
|
||||
BalanceEnabled: balanceEnabled,
|
||||
ProgressionEnabled: progressionEnabled,
|
||||
RandomEquipmentEnabled: randomEquipmentEnabled,
|
||||
AI: ai,
|
||||
AIProfiles: profiles,
|
||||
SourceCharacterID: source,
|
||||
SourceCharacterIDs: sources,
|
||||
}
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func ConfigSettingsRecord(
|
||||
ownerCharacterID string,
|
||||
cfg Config,
|
||||
now time.Time,
|
||||
) (repository.SettingsRecord, error) {
|
||||
scope, err := SettingsScope(ownerCharacterID)
|
||||
if err != nil {
|
||||
return repository.SettingsRecord{}, err
|
||||
}
|
||||
return configSettingsRecordScope(scope, cfg, now)
|
||||
}
|
||||
|
||||
func AccountConfigSettingsRecord(
|
||||
accountID string,
|
||||
cfg Config,
|
||||
now time.Time,
|
||||
) (repository.SettingsRecord, error) {
|
||||
scope, err := AccountSettingsScope(accountID)
|
||||
if err != nil {
|
||||
return repository.SettingsRecord{}, err
|
||||
}
|
||||
return configSettingsRecordScope(scope, cfg, now)
|
||||
}
|
||||
|
||||
func configSettingsRecordScope(
|
||||
scope string,
|
||||
cfg Config,
|
||||
now time.Time,
|
||||
) (repository.SettingsRecord, error) {
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return repository.SettingsRecord{}, err
|
||||
}
|
||||
if now.IsZero() {
|
||||
return repository.SettingsRecord{}, ErrSelection
|
||||
}
|
||||
values := map[string]string{
|
||||
configSchemaKey: strconv.Itoa(cfg.SchemaVersion),
|
||||
configRevisionKey: strconv.FormatUint(cfg.Revision, 10),
|
||||
configEnabledKey: strconv.FormatBool(cfg.Enabled),
|
||||
configBalanceKey: strconv.FormatBool(cfg.BalanceEnabled),
|
||||
configProgressionKey: strconv.FormatBool(cfg.ProgressionEnabled),
|
||||
configRandomEquipmentKey: strconv.FormatBool(cfg.RandomEquipmentEnabled),
|
||||
}
|
||||
if !cfg.AI.IsZero() {
|
||||
values[configAISchemaKey] = "2"
|
||||
encoded, err := json.Marshal(NormalizeAIConfig(cfg.AI))
|
||||
if err != nil {
|
||||
return repository.SettingsRecord{}, ErrSelection
|
||||
}
|
||||
values[configAIDefaultsKey] = string(encoded)
|
||||
}
|
||||
if cfg.AIProfiles != nil {
|
||||
values[configAISchemaKey] = "2"
|
||||
profiles := cloneAIProfiles(cfg.AIProfiles)
|
||||
encoded, err := json.Marshal(profiles)
|
||||
if err != nil {
|
||||
return repository.SettingsRecord{}, ErrSelection
|
||||
}
|
||||
values[configAIProfilesKey] = string(encoded)
|
||||
}
|
||||
sources := cfg.Sources()
|
||||
if len(sources) != 0 {
|
||||
values[sourceCharacterSettingKey] = sources[0]
|
||||
values[sourceCharactersSettingKey] = strings.Join(sources, ",")
|
||||
}
|
||||
return repository.SettingsRecord{
|
||||
Scope: scope,
|
||||
Values: values,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCanonicalUint(value string, bits int) (uint64, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return 0, ErrSelection
|
||||
}
|
||||
parsed, err := strconv.ParseUint(value, 10, bits)
|
||||
if err != nil || strconv.FormatUint(parsed, 10) != value {
|
||||
return 0, ErrSelection
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
Reference in new issue
Block a user