Files
90CNServer/server/internal/services/dnfbridge/guild_dungeon_repeat_bonus_test.go
T
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

111 lines
4.5 KiB
Go

package dnfbridge
import (
"testing"
dnfguild "longheng.io/server/internal/modules/dnf/guild"
)
// 运营规则(2026-09-15 起):每通关 10 次公会地下城发 1 个公会地下城奖励礼盒,
// 没有上限,也就是每通关一次涨 10%。(此前是 20 次 / 5%。)
func TestCurrentGuildDungeonRepeatBonusOwed(t *testing.T) {
for _, row := range []struct {
name string
before int64
after int64
want int64
}{
{name: "第一次通关", before: 0, after: 1, want: 0},
{name: "第 9 次仍未满", before: 8, after: 9, want: 0},
{name: "第 10 次发一个", before: 9, after: 10, want: 1},
{name: "第 11 次不再发", before: 10, after: 11, want: 0},
{name: "第 20 次再发一个(无上限)", before: 19, after: 20, want: 1},
{name: "一次跨过两个整块就补两个", before: 9, after: 21, want: 2},
{name: "计数没动就不发", before: 10, after: 10, want: 0},
{name: "重放(after 落后)不发", before: 40, after: 20, want: 0},
{name: "负数计数按 0 起算", before: -5, after: 1, want: 0},
} {
t.Run(row.name, func(t *testing.T) {
if got := currentGuildDungeonRepeatBonusOwed(row.before, row.after); got != row.want {
t.Fatalf("owed(%d -> %d) = %d, want %d", row.before, row.after, got, row.want)
}
})
}
}
func TestCurrentGuildDungeonRepeatBonusPercent(t *testing.T) {
for _, row := range []struct {
count int64
want int64
}{
{count: 0, want: 0},
{count: 1, want: 10},
{count: 2, want: 20},
{count: 5, want: 50},
{count: 9, want: 90},
{count: 10, want: 0},
{count: 11, want: 10},
{count: 21, want: 10},
} {
if got := currentGuildDungeonRepeatBonusPercent(row.count); got != row.want {
t.Fatalf("percent(%d) = %d, want %d", row.count, got, row.want)
}
}
}
// 百分比必须是从次数阈值算出来的,不是另写的一个常量:改阈值时两者不能脱节。
func TestCurrentGuildDungeonRepeatBonusPercentTracksTheThreshold(t *testing.T) {
step := 100 / currentGuildDungeonRepeatClearsPerBonus
for clears := int64(1); clears < currentGuildDungeonRepeatClearsPerBonus; clears++ {
if got, want := currentGuildDungeonRepeatBonusPercent(clears), clears*step; got != want {
t.Fatalf("percent(%d) = %d, want %d", clears, got, want)
}
}
if got := currentGuildDungeonRepeatBonusPercent(currentGuildDungeonRepeatClearsPerBonus); got != 0 {
t.Fatalf("a completed block reads %d, want 0", got)
}
}
// 礼盒与净化槽必须是两套独立的东西:槽只在 99%/100% 发公会贡献证书。
func TestCurrentGuildDungeonRepeatBonusIsNotThePurificationGauge(t *testing.T) {
if currentGuildDungeonRepeatClearBonusItem == 10154439 {
t.Fatal("repeat-clear bonus must not be the contribution certificate")
}
if currentGuildDungeonRepeatClearBonusItem != 10156320 {
t.Fatalf("repeat-clear bonus item = %d, want 10156320 公会地下城奖励礼盒",
currentGuildDungeonRepeatClearBonusItem)
}
if currentGuildDungeonRepeatClearsPerBonus != 10 {
t.Fatalf("clears per bonus = %d, want 10", currentGuildDungeonRepeatClearsPerBonus)
}
}
// 净化率翻倍只能在入账侧做:客户端的 clears * [gauge on clear] / [gauge limit]
// 在它自己的 PVF 里,服务端改不动。一次通关记 2 份槽,除回来就是 2 次,8500 的
// 1.5% 因此显示成 3%。
func TestCurrentGuildDungeonGaugeOnClearIsDoubled(t *testing.T) {
stage := dnfguild.DungeonStage{GaugeOnClear: 150, GaugeLimit: 10000}
if got := currentGuildDungeonGaugeOnClear(stage); got != 300 {
t.Fatalf("gauge on clear = %d, want 300 (原版 150 的两倍)", got)
}
// 除回来给客户端的次数必须跟着翻倍,否则显示不会变。
if got := currentGuildDungeonClears(stage, currentGuildDungeonGaugeOnClear(stage)); got != 2 {
t.Fatalf("clears after one run = %d, want 2 so the client draws 3%%", got)
}
// 满槽仍然封顶在 100%,不会因为翻倍而画过头。
if got := currentGuildDungeonClears(stage, 999999); got != stage.GaugeLimit/stage.GaugeOnClear {
t.Fatalf("over-credited gauge rendered %d clears, want the full-bar clamp", got)
}
}
func TestCurrentGuildDungeonGaugeOnClearHandlesDegenerateStages(t *testing.T) {
if got := currentGuildDungeonGaugeOnClear(dnfguild.DungeonStage{}); got != 0 {
t.Fatalf("a stage with no gauge credited %d, want 0", got)
}
// 翻倍不能溢出成一个更小的数。
huge := dnfguild.DungeonStage{GaugeOnClear: ^uint32(0) - 1}
if got := currentGuildDungeonGaugeOnClear(huge); got != huge.GaugeOnClear {
t.Fatalf("overflowing stage credited %d, want the un-doubled %d", got, huge.GaugeOnClear)
}
}