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,229 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
dnfdrops "longheng.io/server/internal/modules/dnf/drop"
|
||||
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
|
||||
)
|
||||
|
||||
const (
|
||||
currentDungeonDropDenominator = 10000
|
||||
currentDungeonExplicitPoolRate = 1000
|
||||
)
|
||||
|
||||
var currentDungeonDropDifficultyBonus = [...]float64{1.0, 1.2, 1.4, 1.6, 1.8}
|
||||
|
||||
var (
|
||||
errCurrentDungeonDropRulesUnavailable = errors.New("current dungeon drop PVF rules are unavailable")
|
||||
errCurrentDungeonClearRewardRulesUnavailable = errors.New("current dungeon clear-reward PVF rules are unavailable")
|
||||
errCurrentDungeonGoldReferenceInvalid = errors.New("current dungeon gold PVF reference is invalid")
|
||||
errCurrentDungeonMapDropCountInvalid = errors.New("current dungeon map drop count is invalid")
|
||||
)
|
||||
|
||||
func currentDungeonMapDropAttempts(value int64, kind string) (int, error) {
|
||||
if value < 0 || value > math.MaxUint8 {
|
||||
return 0, fmt.Errorf("%w: kind=%s count=%d", errCurrentDungeonMapDropCountInvalid, kind, value)
|
||||
}
|
||||
return int(value), nil
|
||||
}
|
||||
|
||||
// currentDungeonDropLCG is the room PRNG algorithm used by the verified C#
|
||||
// domain implementation. Its state is persisted in the room visit, not
|
||||
// reconstructed from a monster key, so sequential kills consume the same
|
||||
// deterministic stream as the room run.
|
||||
type currentDungeonDropLCG struct {
|
||||
seed uint32
|
||||
}
|
||||
|
||||
func newCurrentDungeonDropLCG(seed uint32) *currentDungeonDropLCG {
|
||||
return ¤tDungeonDropLCG{seed: seed}
|
||||
}
|
||||
|
||||
func (rng *currentDungeonDropLCG) Seed() uint32 {
|
||||
if rng == nil {
|
||||
return 0
|
||||
}
|
||||
return rng.seed
|
||||
}
|
||||
|
||||
func (rng *currentDungeonDropLCG) NextUint32() uint32 {
|
||||
if rng == nil {
|
||||
return 0
|
||||
}
|
||||
v2 := rng.seed*1103515245 + 12345
|
||||
v3 := v2*1103515245 + 12345
|
||||
v4 := v3*1103515245 + 12345
|
||||
rng.seed = v4
|
||||
hi2 := (v2 >> 16) & 0x7ff
|
||||
hi3 := (v3 >> 16) & 0x3ff
|
||||
hi4 := (v4 >> 16) & 0x3ff
|
||||
return (((hi2 << 10) ^ hi3) << 10) | hi4
|
||||
}
|
||||
|
||||
func (rng *currentDungeonDropLCG) Next(max int) int {
|
||||
value := rng.NextUint32()
|
||||
if max <= 0 {
|
||||
return int(value)
|
||||
}
|
||||
return int(value % uint32(max))
|
||||
}
|
||||
|
||||
type currentDungeonGoldReference struct {
|
||||
Level int64
|
||||
Base int64
|
||||
VariancePct int64
|
||||
}
|
||||
|
||||
type currentDungeonGoldReferenceCacheEntry struct {
|
||||
byLevel map[int64]currentDungeonGoldReference
|
||||
err error
|
||||
}
|
||||
|
||||
type currentDungeonMonsterDropRulesCacheEntry struct {
|
||||
rules *dnfdrops.MonsterRules
|
||||
err error
|
||||
}
|
||||
|
||||
type currentDungeonClearRewardRulesCacheEntry struct {
|
||||
rules *dnfdrops.ClearRewardRules
|
||||
err error
|
||||
}
|
||||
|
||||
var currentDungeonGoldReferencesByCatalog sync.Map
|
||||
var currentDungeonMonsterDropRulesByCatalog sync.Map
|
||||
var currentDungeonClearRewardRulesByCatalog sync.Map
|
||||
|
||||
func currentDungeonMonsterDropRules(catalog *pvfDungeonMonsterCatalog) (*dnfdrops.MonsterRules, error) {
|
||||
if catalog == nil || catalog.source == nil {
|
||||
return nil, errCurrentDungeonDropRulesUnavailable
|
||||
}
|
||||
if cached, found := currentDungeonMonsterDropRulesByCatalog.Load(catalog); found {
|
||||
entry := cached.(currentDungeonMonsterDropRulesCacheEntry)
|
||||
return entry.rules, entry.err
|
||||
}
|
||||
rules, err := dnfdrops.LoadMonsterRules(catalog.source)
|
||||
entry := currentDungeonMonsterDropRulesCacheEntry{rules: rules, err: err}
|
||||
actual, _ := currentDungeonMonsterDropRulesByCatalog.LoadOrStore(catalog, entry)
|
||||
entry = actual.(currentDungeonMonsterDropRulesCacheEntry)
|
||||
if entry.err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errCurrentDungeonDropRulesUnavailable, entry.err)
|
||||
}
|
||||
return entry.rules, nil
|
||||
}
|
||||
|
||||
func currentDungeonClearRewardRules(catalog *pvfDungeonMonsterCatalog) (*dnfdrops.ClearRewardRules, error) {
|
||||
if catalog == nil || catalog.source == nil {
|
||||
return nil, errCurrentDungeonClearRewardRulesUnavailable
|
||||
}
|
||||
if cached, found := currentDungeonClearRewardRulesByCatalog.Load(catalog); found {
|
||||
entry := cached.(currentDungeonClearRewardRulesCacheEntry)
|
||||
return entry.rules, entry.err
|
||||
}
|
||||
rules, err := dnfdrops.LoadClearRewardRules(catalog.source)
|
||||
entry := currentDungeonClearRewardRulesCacheEntry{rules: rules, err: err}
|
||||
actual, _ := currentDungeonClearRewardRulesByCatalog.LoadOrStore(catalog, entry)
|
||||
entry = actual.(currentDungeonClearRewardRulesCacheEntry)
|
||||
if entry.err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errCurrentDungeonClearRewardRulesUnavailable, entry.err)
|
||||
}
|
||||
return entry.rules, nil
|
||||
}
|
||||
|
||||
func currentDungeonGoldReferences(catalog *pvfDungeonMonsterCatalog) (map[int64]currentDungeonGoldReference, error) {
|
||||
if catalog == nil || catalog.source == nil {
|
||||
return nil, errCurrentDungeonGoldReferenceInvalid
|
||||
}
|
||||
if cached, found := currentDungeonGoldReferencesByCatalog.Load(catalog); found {
|
||||
entry := cached.(currentDungeonGoldReferenceCacheEntry)
|
||||
return entry.byLevel, entry.err
|
||||
}
|
||||
entry := currentDungeonGoldReferenceCacheEntry{}
|
||||
text, err := catalog.source.ReadText(dungeonCardGoldReferencePath)
|
||||
if err == nil {
|
||||
var document *dnfpvf.Document
|
||||
document, err = dnfpvf.Parse(dungeonCardGoldReferencePath, text)
|
||||
if err == nil {
|
||||
values, parseErr := dungeonCardPVFSectionInts(document, "gold drop ref table")
|
||||
if parseErr != nil {
|
||||
err = parseErr
|
||||
} else if len(values)%3 != 0 {
|
||||
err = fmt.Errorf("%w: rows=%d", errCurrentDungeonGoldReferenceInvalid, len(values))
|
||||
} else {
|
||||
entry.byLevel = make(map[int64]currentDungeonGoldReference, len(values)/3)
|
||||
for index := 0; index < len(values); index += 3 {
|
||||
row := currentDungeonGoldReference{Level: values[index], Base: values[index+1], VariancePct: values[index+2]}
|
||||
if row.Level <= 0 || row.Base <= 0 || row.VariancePct < 0 || row.VariancePct > 100 || entry.byLevel[row.Level].Level != 0 {
|
||||
err = fmt.Errorf("%w: row=%+v", errCurrentDungeonGoldReferenceInvalid, row)
|
||||
break
|
||||
}
|
||||
entry.byLevel[row.Level] = row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
entry.err = fmt.Errorf("%w: %v", errCurrentDungeonGoldReferenceInvalid, err)
|
||||
}
|
||||
actual, _ := currentDungeonGoldReferencesByCatalog.LoadOrStore(catalog, entry)
|
||||
entry = actual.(currentDungeonGoldReferenceCacheEntry)
|
||||
return entry.byLevel, entry.err
|
||||
}
|
||||
|
||||
func currentDungeonDropRate(base int64, typeBonus float64, difficultyBonus float64) int {
|
||||
if base <= 0 || math.IsNaN(typeBonus) || math.IsInf(typeBonus, 0) || typeBonus <= 0 ||
|
||||
math.IsNaN(difficultyBonus) || math.IsInf(difficultyBonus, 0) || difficultyBonus <= 0 {
|
||||
return 0
|
||||
}
|
||||
value := math.Floor(float64(base) * typeBonus * difficultyBonus)
|
||||
if value <= 0 {
|
||||
return 0
|
||||
}
|
||||
if value >= currentDungeonDropDenominator {
|
||||
return currentDungeonDropDenominator
|
||||
}
|
||||
return int(value)
|
||||
}
|
||||
|
||||
func currentDungeonDropDifficultyRate(index int) float64 {
|
||||
if index < 0 || index >= len(currentDungeonDropDifficultyBonus) {
|
||||
return 1.0
|
||||
}
|
||||
return currentDungeonDropDifficultyBonus[index]
|
||||
}
|
||||
|
||||
func currentDungeonDropRarity(rng *currentDungeonDropLCG, rules *dnfdrops.MonsterRules) int64 {
|
||||
if rng == nil || rules == nil {
|
||||
return 0
|
||||
}
|
||||
thresholds := rules.RawTables().RarityDecision[0]
|
||||
roll := int64(rng.Next(1000000)) + 1
|
||||
for rarity, threshold := range thresholds {
|
||||
if roll <= threshold {
|
||||
return int64(rarity)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func currentDungeonGoldAmount(rng *currentDungeonDropLCG, reference currentDungeonGoldReference, difficulty float64) uint32 {
|
||||
if rng == nil || reference.Base <= 0 || reference.VariancePct < 0 || difficulty <= 0 {
|
||||
return 0
|
||||
}
|
||||
variance := int64(0)
|
||||
if reference.VariancePct > 0 {
|
||||
rangeSize := reference.VariancePct*2 + 1
|
||||
variance = (int64(rng.Next(int(rangeSize))) - reference.VariancePct) * reference.Base / 100
|
||||
}
|
||||
value := int64(math.Floor(float64(reference.Base+variance) * difficulty))
|
||||
if value < 1 {
|
||||
value = 1
|
||||
}
|
||||
if value > math.MaxUint32 {
|
||||
return math.MaxUint32
|
||||
}
|
||||
return uint32(value)
|
||||
}
|
||||
Reference in new issue
Block a user