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,143 @@
|
||||
// Package cerashop:代币券 / 欢乐代币券的"入钱包"语义。
|
||||
//
|
||||
// 背景(现场 2026-09-19):代币券和欢乐代币券在道具 PVF 里是普通
|
||||
// `[stackable type] [material]`,既没有 `[action type]` 也没有任何"使用后转成点券"
|
||||
// 的定义 —— 也就是说**客户端自己根本无法把它们变成钱包余额**。任何把它们当普通道具
|
||||
// 发进背包的路径,玩家拿到的都是一件永远用不掉的"材料",这正是现场的抱怨。
|
||||
//
|
||||
// 所以转换 100% 由服务端负责:发放时识别券的道具号,直接累加到账号钱包,不占背包格。
|
||||
//
|
||||
// 道具号来源(真机 runtime/data/dnf/Script.pvf):
|
||||
//
|
||||
// stackable/cash/chn_Trade_Gold/trade_point.stk id 2681762 `代币券`
|
||||
// stackable/cash/chn_Trade_Gold/trade_point_event.stk id 2681917 `欢乐代币券`
|
||||
package cerashop
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
// TokenCeraVoucherItemID 是 `代币券`(trade_point.stk)。
|
||||
TokenCeraVoucherItemID int64 = 2681762
|
||||
// HappyTokenCeraVoucherItemID 是 `欢乐代币券`(trade_point_event.stk)。
|
||||
HappyTokenCeraVoucherItemID int64 = 2681917
|
||||
)
|
||||
|
||||
// MaxWalletBalance 是三个钱包字段的上限。
|
||||
//
|
||||
// 客户端把这三个值按 **有符号 32 位** 读进字段(反编译 `class0/0x0035` 的 handler
|
||||
// `FUN_01d83c20` 读的是 i32),所以上限必须是 MaxInt32 而不是 MaxInt64:
|
||||
// 超过就会在客户端显示成负数。所有入账都必须在这里挡住。
|
||||
const MaxWalletBalance int64 = math.MaxInt32
|
||||
|
||||
// ErrWalletOverflow 表示入账会让钱包超过 MaxWalletBalance。
|
||||
// 调用方应该把它映射成自己模块的"请求非法"错误,而不是静默截断。
|
||||
var ErrWalletOverflow = errors.New("cera shop: wallet balance would exceed the client limit")
|
||||
|
||||
// VoucherKind 是券对应的钱包字段。
|
||||
type VoucherKind int
|
||||
|
||||
const (
|
||||
// VoucherNone 表示这件道具不是券。
|
||||
VoucherNone VoucherKind = iota
|
||||
// VoucherTokenCera 是代币券,入账到 Wallet.TokenCera。
|
||||
VoucherTokenCera
|
||||
// VoucherHappyTokenCera 是欢乐代币券,入账到 Wallet.HappyTokenCera。
|
||||
VoucherHappyTokenCera
|
||||
)
|
||||
|
||||
// VoucherKindOf 判断一件道具是不是券。不是券返回 VoucherNone。
|
||||
func VoucherKindOf(itemID int64) VoucherKind {
|
||||
switch itemID {
|
||||
case TokenCeraVoucherItemID:
|
||||
return VoucherTokenCera
|
||||
case HappyTokenCeraVoucherItemID:
|
||||
return VoucherHappyTokenCera
|
||||
default:
|
||||
return VoucherNone
|
||||
}
|
||||
}
|
||||
|
||||
// IsVoucher 是上面的布尔简写,给"这批奖励里有没有券"这类判断用。
|
||||
func IsVoucher(itemID int64) bool {
|
||||
return VoucherKindOf(itemID) != VoucherNone
|
||||
}
|
||||
|
||||
// VoucherItemID 返回某类券的道具号(VoucherNone 返回 0)。
|
||||
func VoucherItemID(kind VoucherKind) int64 {
|
||||
switch kind {
|
||||
case VoucherTokenCera:
|
||||
return TokenCeraVoucherItemID
|
||||
case VoucherHappyTokenCera:
|
||||
return HappyTokenCeraVoucherItemID
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// VoucherItemIDs 返回全部券道具号,供"发放前先摘出来"的过滤使用。
|
||||
func VoucherItemIDs() []int64 {
|
||||
return []int64{TokenCeraVoucherItemID, HappyTokenCeraVoucherItemID}
|
||||
}
|
||||
|
||||
// VoucherCounts 是一批待入账的券数量。
|
||||
type VoucherCounts struct {
|
||||
TokenCera int64
|
||||
HappyTokenCera int64
|
||||
}
|
||||
|
||||
// IsZero 表示没有任何券需要入账。
|
||||
func (c VoucherCounts) IsZero() bool {
|
||||
return c.TokenCera == 0 && c.HappyTokenCera == 0
|
||||
}
|
||||
|
||||
// Add 累加另一种券的数量。
|
||||
func (c VoucherCounts) Add(other VoucherCounts) VoucherCounts {
|
||||
return VoucherCounts{
|
||||
TokenCera: c.TokenCera + other.TokenCera,
|
||||
HappyTokenCera: c.HappyTokenCera + other.HappyTokenCera,
|
||||
}
|
||||
}
|
||||
|
||||
// CreditVoucherCounts 把券数量累加到账号钱包。
|
||||
//
|
||||
// 语义与既有邮件领取路径完全一致(那里已经这么做了),本函数只是把它提取成共享实现,
|
||||
// 让任务奖励、礼盒开启、GM 补发都走同一条路,避免各写一套溢出判断。
|
||||
//
|
||||
// - **全有或全无**:任何一项溢出都不写任何字段,账号记录保持原样,返回 ErrWalletOverflow。
|
||||
// - 调用方负责保存 account 与它的 UpdatedAt。
|
||||
// - 负数一律当 0:券数量为负只可能来自上游 bug,不该把余额扣下去。
|
||||
func CreditVoucherCounts(account *dnfrepo.AccountRecord, counts VoucherCounts) (Wallet, error) {
|
||||
if account == nil {
|
||||
return Wallet{}, errors.New("cera shop: account is required")
|
||||
}
|
||||
tokenCera := counts.TokenCera
|
||||
if tokenCera < 0 {
|
||||
tokenCera = 0
|
||||
}
|
||||
happyTokenCera := counts.HappyTokenCera
|
||||
if happyTokenCera < 0 {
|
||||
happyTokenCera = 0
|
||||
}
|
||||
if tokenCera == 0 && happyTokenCera == 0 {
|
||||
return WalletBalance(*account), nil
|
||||
}
|
||||
wallet := WalletBalance(*account)
|
||||
if tokenCera > MaxWalletBalance-wallet.TokenCera {
|
||||
return wallet, ErrWalletOverflow
|
||||
}
|
||||
if happyTokenCera > MaxWalletBalance-wallet.HappyTokenCera {
|
||||
return wallet, ErrWalletOverflow
|
||||
}
|
||||
if tokenCera > 0 {
|
||||
SetTokenCeraBalance(account, wallet.TokenCera+tokenCera)
|
||||
}
|
||||
if happyTokenCera > 0 {
|
||||
SetHappyTokenCeraBalance(account, wallet.HappyTokenCera+happyTokenCera)
|
||||
}
|
||||
return WalletBalance(*account), nil
|
||||
}
|
||||
Reference in new issue
Block a user