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,142 @@
package dnfbridge
import "fmt"
const (
currentCeraPackageGraphMaxDepth = 8
currentCeraPackageGraphMaxNodes = 256
currentCeraPackageGraphMaxRows = 512
)
type currentCeraPackageGraphBudget struct {
nodes int
rows int
}
// validateCurrentCeraPackageRewardGraph proves that the deterministic Cera
// package subgraph is finite and bounded before a source item is consumed.
// Fixed rows are additive; repeated selection groups contribute the largest
// possible branch, while every candidate is still visited for cycle and shape
// validation. Random/selection booster boxes are deliberate op160 boundaries
// and stay as one terminal item here, preserving their native roll/choice.
func validateCurrentCeraPackageRewardGraph(catalog *pvfDungeonDropCatalog, sourceItemID uint32) error {
if catalog == nil || catalog.source == nil || sourceItemID == 0 {
return errCurrentCeraPackagePVFInvalid
}
budget := &currentCeraPackageGraphBudget{}
units, err := currentCeraPackageExpandedUnits(catalog, sourceItemID, 1, 0, make(map[uint32]struct{}), budget)
if err != nil {
return err
}
if units == 0 || units > currentCeraPackageMaxRewardAmount {
return fmt.Errorf("%w: source item=%d expanded_units=%d", errCurrentCeraPackagePVFInvalid, sourceItemID, units)
}
return nil
}
func currentCeraPackageExpandedUnits(
catalog *pvfDungeonDropCatalog,
itemID uint32,
count uint64,
depth int,
path map[uint32]struct{},
budget *currentCeraPackageGraphBudget,
) (uint64, error) {
if itemID == 0 || count == 0 || count > currentCeraPackageMaxRewardAmount || budget == nil {
return 0, errCurrentCeraPackagePVFInvalid
}
definition, err := catalog.ResolveItem(itemID)
if err != nil {
return 0, fmt.Errorf("%w: graph reward item=%d: %v", errCurrentCeraPackagePVFInvalid, itemID, err)
}
packageDefinition, recognized, err := resolveCurrentCeraPackageDefinitionForSource(catalog, definition)
if err != nil {
return 0, err
}
if !recognized {
// Every non-Cera item, including [booster], [booster selection],
// [cera booster] and legacy random boxes, is granted as one terminal
// row. Its independent use protocol owns any later random or choice.
return count, nil
}
if depth >= currentCeraPackageGraphMaxDepth {
return 0, fmt.Errorf("%w: package graph depth exceeds %d at item=%d", errCurrentCeraPackagePVFInvalid, currentCeraPackageGraphMaxDepth, itemID)
}
if _, cycle := path[itemID]; cycle {
return 0, fmt.Errorf("%w: package graph cycle at item=%d", errCurrentCeraPackagePVFInvalid, itemID)
}
budget.nodes++
if budget.nodes > currentCeraPackageGraphMaxNodes {
return 0, fmt.Errorf("%w: package graph nodes exceed %d", errCurrentCeraPackagePVFInvalid, currentCeraPackageGraphMaxNodes)
}
rows := len(packageDefinition.Rewards)
for _, group := range packageDefinition.SelectionGroups {
rows += len(group)
}
budget.rows += rows
if budget.rows > currentCeraPackageGraphMaxRows {
return 0, fmt.Errorf("%w: package graph rows exceed %d", errCurrentCeraPackagePVFInvalid, currentCeraPackageGraphMaxRows)
}
nextPath := make(map[uint32]struct{}, len(path)+1)
for parent := range path {
nextPath[parent] = struct{}{}
}
nextPath[itemID] = struct{}{}
var perPackage uint64
addReward := func(reward currentCeraPackageReward) (uint64, error) {
multiplier, ok := currentCeraPackageBoundedProduct(count, uint64(reward.Count))
if !ok {
return 0, fmt.Errorf("%w: package graph item=%d reward=%d count overflow", errCurrentCeraPackagePVFInvalid, itemID, reward.Definition.ItemID)
}
return currentCeraPackageExpandedUnits(catalog, reward.Definition.ItemID, multiplier, depth+1, nextPath, budget)
}
for _, reward := range packageDefinition.Rewards {
units, rewardErr := addReward(reward)
if rewardErr != nil {
return 0, rewardErr
}
var ok bool
perPackage, ok = currentCeraPackageBoundedSum(perPackage, units)
if !ok {
return 0, fmt.Errorf("%w: package graph source=%d fixed rewards expand beyond limit", errCurrentCeraPackagePVFInvalid, itemID)
}
}
for groupIndex, group := range packageDefinition.SelectionGroups {
if len(group) == 0 {
return 0, fmt.Errorf("%w: package graph source=%d group=%d is empty", errCurrentCeraPackagePVFInvalid, itemID, groupIndex)
}
var largest uint64
for _, candidate := range group {
units, candidateErr := addReward(candidate)
if candidateErr != nil {
return 0, candidateErr
}
if units > largest {
largest = units
}
}
var ok bool
perPackage, ok = currentCeraPackageBoundedSum(perPackage, largest)
if !ok {
return 0, fmt.Errorf("%w: package graph source=%d selected rewards expand beyond limit", errCurrentCeraPackagePVFInvalid, itemID)
}
}
return perPackage, nil
}
func currentCeraPackageBoundedProduct(left, right uint64) (uint64, bool) {
if left == 0 || right == 0 || left > currentCeraPackageMaxRewardAmount || right > currentCeraPackageMaxRewardAmount ||
left > currentCeraPackageMaxRewardAmount/right {
return 0, false
}
return left * right, true
}
func currentCeraPackageBoundedSum(left, right uint64) (uint64, bool) {
if left > currentCeraPackageMaxRewardAmount || right > currentCeraPackageMaxRewardAmount ||
left > currentCeraPackageMaxRewardAmount-right {
return 0, false
}
return left + right, true
}