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,166 @@
|
||||
package drop
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
platformpvf "longheng.io/server/internal/platform/pvf"
|
||||
)
|
||||
|
||||
func TestLoadClearRewardRulesPreservesPVFRowsAndResolvesCurrentFormula(t *testing.T) {
|
||||
rules, err := LoadClearRewardRules(clearRewardRuleTestSource{ClearRewardRulePath: validClearRewardRuleText()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if snapshot := rules.Snapshot(); snapshot != (ClearRewardRuleSnapshot{
|
||||
MapRateRows: 5, DifficultyBonusRows: 5, RarityThresholdRows: 7,
|
||||
PartyBonusRows: 4, GoldDifficultyRows: 5, GoldCardCostRows: 2, ItemReferenceRows: 2,
|
||||
}) {
|
||||
t.Fatalf("snapshot=%+v", snapshot)
|
||||
}
|
||||
plan := rules.PlanSource(60, 1, 2, 5)
|
||||
if !plan.ItemRateFound || !plan.DifficultyFound || !plan.PartyBonusFound ||
|
||||
!plan.GoldDifficultyFound || !plan.GoldCardCostFound || !plan.ItemReferenceFound ||
|
||||
plan.ItemRate != 6000 || plan.DifficultyBonus != 600 || plan.PartyBonus != 1.2 ||
|
||||
plan.GoldDifficultyRate != 1.38 || plan.GoldCardCost != 7180 ||
|
||||
plan.ItemReference != (ClearRewardItemReference{LookupKey: 60, ValueA: 7, ValueB: 3}) {
|
||||
t.Fatalf("plan=%+v", plan)
|
||||
}
|
||||
missing := rules.PlanSource(59, 5, 5, 6)
|
||||
if missing.ItemRateFound || missing.DifficultyFound || missing.PartyBonusFound ||
|
||||
missing.GoldDifficultyFound || missing.GoldCardCostFound || missing.ItemReferenceFound {
|
||||
t.Fatalf("missing lookup fabricated rows: %+v", missing)
|
||||
}
|
||||
raw := rules.RawTables()
|
||||
if raw.ItemRateByMapCount[5] != 6000 || raw.DifficultyDropBonus != ([5]int64{0, 600, 1000, 1400, 1200}) ||
|
||||
raw.RarityDecision != ([7]int64{500000, 799900, 999900, 1000000, 1000001, 1000002, 1000003}) ||
|
||||
raw.PartyMemberDropBonus != ([4]float64{1, 1.2, 1.4, 1.6}) ||
|
||||
raw.GoldDifficultyDropBonus != ([5]float64{1.02, 1.38, 1.6, 1.9, 2}) || raw.GoldCardCostByLevel[60] != 7180 {
|
||||
t.Fatalf("raw=%+v", raw)
|
||||
}
|
||||
raw.ItemRateByMapCount[5] = 1
|
||||
raw.GoldCardCostByLevel[60] = 1
|
||||
again := rules.RawTables()
|
||||
if again.ItemRateByMapCount[5] != 6000 || again.GoldCardCostByLevel[60] != 7180 {
|
||||
t.Fatalf("rules leaked mutable tables: %+v", again)
|
||||
}
|
||||
resolved, err := rules.Resolve(60, 1, 2, 9)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resolved.EquipmentRate != 10_000 || resolved.GradeDown != 7 || resolved.GradeUp != 3 ||
|
||||
resolved.RarityThresholds != ([7]uint32{500000, 799900, 999900, 1000000, 1000001, 1000002, 1000003}) ||
|
||||
resolved.RarityScale != 1000003 || resolved.PaidCardCost != 7180 ||
|
||||
resolved.GoldDifficultyRate != 1.38 || resolved.MapCount != 5 {
|
||||
t.Fatalf("resolved=%+v", resolved)
|
||||
}
|
||||
if _, err := rules.Resolve(59, 0, 1, 1); !errors.Is(err, ErrClearRewardRuleLookupMissing) {
|
||||
t.Fatalf("missing resolved lookup error=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadClearRewardRulesRejectsMalformedPVFShapes(t *testing.T) {
|
||||
if _, err := LoadClearRewardRules(nil); !errors.Is(err, ErrClearRewardRuleSourceRequired) {
|
||||
t.Fatalf("nil source error=%v", err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(string) string
|
||||
}{
|
||||
{
|
||||
name: "map counts must be complete and ordered",
|
||||
mutate: func(text string) string {
|
||||
return replaceOnce(t, text, "1 2000 2 3000", "1 2000 3 3000")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "difficulty rows must remain five",
|
||||
mutate: func(text string) string {
|
||||
return replaceOnce(t, text, "0 600 1000 1400 1200", "0 600 1000 1400")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rarity thresholds cannot decrease",
|
||||
mutate: func(text string) string {
|
||||
return replaceOnce(t, text, "500000 799900 999900", "500000 400000 999900")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "party bonus must remain positive",
|
||||
mutate: func(text string) string {
|
||||
return replaceOnce(t, text, "1.00 1.20 1.40 1.60", "1.00 0 1.40 1.60")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gold card level keys must increase",
|
||||
mutate: func(text string) string {
|
||||
return replaceOnce(t, text, "1 100 60 7180", "60 7180 1 100")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "item references require nonnegative values",
|
||||
mutate: func(text string) string {
|
||||
return replaceOnce(t, text, "60 7 3", "60 -1 3")
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := LoadClearRewardRules(clearRewardRuleTestSource{ClearRewardRulePath: test.mutate(validClearRewardRuleText())})
|
||||
if !errors.Is(err, ErrClearRewardRuleSectionShape) {
|
||||
t.Fatalf("error=%v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealScriptPVFClearRewardRulesPreserveCurrentSourceTables(t *testing.T) {
|
||||
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
|
||||
if pvfPath == "" {
|
||||
pvfPath = os.Getenv("DNF_WORLDMAP_REAL_PVF_SMOKE")
|
||||
}
|
||||
if pvfPath == "" {
|
||||
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to run real clear-reward PVF smoke")
|
||||
}
|
||||
archive, err := platformpvf.Open(pvfPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rules, err := LoadClearRewardRules(archive)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshot := rules.Snapshot()
|
||||
if snapshot.MapRateRows != 5 || snapshot.DifficultyBonusRows != 5 || snapshot.RarityThresholdRows != 7 ||
|
||||
snapshot.PartyBonusRows != 4 || snapshot.GoldDifficultyRows != 5 || snapshot.GoldCardCostRows == 0 || snapshot.ItemReferenceRows == 0 {
|
||||
t.Fatalf("real snapshot=%+v", snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
func validClearRewardRuleText() string {
|
||||
return `[reward item rate per map max count]
|
||||
1 2000 2 3000 3 4000 4 5000 5 6000
|
||||
[dungeon difficulty drop bonusrate]
|
||||
0 600 1000 1400 1200
|
||||
[basis of rarity dicision]
|
||||
500000 799900 999900 1000000 1000001 1000002 1000003
|
||||
[party member drop bonusrate]
|
||||
1.00 1.20 1.40 1.60
|
||||
[dungeon difficulty gold drop bonusrate]
|
||||
1.02 1.38 1.60 1.90 2.00
|
||||
[gold card cost table]
|
||||
1 100 60 7180
|
||||
[item drop ref table]
|
||||
1 0 3 60 7 3
|
||||
`
|
||||
}
|
||||
|
||||
type clearRewardRuleTestSource map[string]string
|
||||
|
||||
func (source clearRewardRuleTestSource) ReadText(path string) (string, error) {
|
||||
if text, found := source[path]; found {
|
||||
return text, nil
|
||||
}
|
||||
return "", errors.New("missing test PVF path: " + path)
|
||||
}
|
||||
Reference in new issue
Block a user