DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

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

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

No files matched your search

@@ -0,0 +1,108 @@
package quest
import (
"testing"
dnfcerashop "longheng.io/server/internal/modules/dnf/cerashop"
)
// 任务奖励里的代币券 / 欢乐代币券处理(纯函数部分)。
//
// 券在道具 PVF 里是普通 `[material]`、**没有 `[action type]`**,客户端根本没有任何
// 途径把它们换成钱包余额;当普通道具发下去,玩家只会拿到一件永远用不掉的"材料"。
// 所以发放前先把它们从奖励规则里摘出来,改成入账号钱包。
func finishVoucherTestRule(itemID, count int64) RewardItemRule {
return RewardItemRule{ItemID: itemID, Count: count}
}
// 拆分必须只摘出券,并且**保持非券规则的原有顺序**——顺序变了会改变落格结果,
// 那是"修了 A 坏了 B"的典型来源。
func TestSplitFinishVoucherRulesKeepsOrdinaryRulesInOrder(t *testing.T) {
rules := []RewardItemRule{
finishVoucherTestRule(100050, 1),
finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 100),
finishVoucherTestRule(100056, 3),
finishVoucherTestRule(dnfcerashop.HappyTokenCeraVoucherItemID, 7),
finishVoucherTestRule(100057, 2),
}
vouchers, items := splitFinishVoucherRules(rules)
if len(vouchers) != 2 || len(items) != 3 {
t.Fatalf("vouchers=%+v items=%+v", vouchers, items)
}
for index, want := range []int64{100050, 100056, 100057} {
if items[index].ItemID != want {
t.Fatalf("非券规则顺序被改变: items[%d]=%d want=%d", index, items[index].ItemID, want)
}
}
if vouchers[0].ItemID != dnfcerashop.TokenCeraVoucherItemID ||
vouchers[1].ItemID != dnfcerashop.HappyTokenCeraVoucherItemID {
t.Fatalf("券的顺序被改变: %+v", vouchers)
}
}
// 没有券时必须原样返回(不得产生任何副作用)。
func TestSplitFinishVoucherRulesLeavesVoucherFreeRewardsUntouched(t *testing.T) {
if vouchers, items := splitFinishVoucherRules([]RewardItemRule{finishVoucherTestRule(1, 1)}); len(vouchers) != 0 || len(items) != 1 {
t.Fatalf("无券时不该有变化: vouchers=%+v items=%+v", vouchers, items)
}
if vouchers, items := splitFinishVoucherRules(nil); len(vouchers) != 0 || len(items) != 0 {
t.Fatalf("空输入: vouchers=%+v items=%+v", vouchers, items)
}
}
func TestFinishVoucherCountsSumsPerKind(t *testing.T) {
counts, err := finishVoucherCounts([]RewardItemRule{
finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 100),
finishVoucherTestRule(dnfcerashop.HappyTokenCeraVoucherItemID, 7),
finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 5),
})
if err != nil {
t.Fatal(err)
}
if counts.TokenCera != 105 || counts.HappyTokenCera != 7 {
t.Fatalf("counts=%+v want token=105 happy=7", counts)
}
}
// 坏值必须被挡住:券既然不走落格路径,就没有别的东西替我们校验这些字段了
// (allocateFinishRewardItems 的那套校验对券不再执行)。
func TestFinishVoucherCountsRejectsMalformedRules(t *testing.T) {
cases := map[string]RewardItemRule{
"zero_count": finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 0),
"negative": finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, -1),
"non_voucher": finishVoucherTestRule(100050, 1),
"zero_item": finishVoucherTestRule(0, 1),
"oversized": finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 1<<33),
}
for name, rule := range cases {
t.Run(name, func(t *testing.T) {
if _, err := finishVoucherCounts([]RewardItemRule{rule}); err == nil {
t.Fatalf("坏值必须报错: %+v", rule)
}
})
}
}
// 汇总后超过客户端可读范围(i32)时必须报错,而不是让它截断成负数。
func TestFinishVoucherCountsRejectsPerKindOverflow(t *testing.T) {
if _, err := finishVoucherCounts([]RewardItemRule{
finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 1<<31-1),
finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 1),
}); err == nil {
t.Fatal("代币券汇总溢出必须报错")
}
if _, err := finishVoucherCounts([]RewardItemRule{
finishVoucherTestRule(dnfcerashop.HappyTokenCeraVoucherItemID, 1<<31-1),
finishVoucherTestRule(dnfcerashop.HappyTokenCeraVoucherItemID, 1),
}); err == nil {
t.Fatal("欢乐代币券汇总溢出必须报错")
}
// 刚好到上限是允许的。
counts, err := finishVoucherCounts([]RewardItemRule{
finishVoucherTestRule(dnfcerashop.TokenCeraVoucherItemID, 1<<31-1),
})
if err != nil || counts.TokenCera != 1<<31-1 {
t.Fatalf("刚好到上限应当允许: err=%v counts=%+v", err, counts)
}
}