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,142 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
dnfguild "longheng.io/server/internal/modules/dnf/guild"
|
||||
)
|
||||
|
||||
// 公会勋章 / 守护珠的合成候选表。
|
||||
//
|
||||
// 合成的产物是"下一档里的随机一件",所以需要按稀有度分组的完整候选集。这里不写
|
||||
// 死 ID 段:itemRefs / stackableIDs / equipmentIDs 是从 PVF 的 .lst 全量枚举出来
|
||||
// 的,逐个 ResolveItem 之后按 [flag] / [flag gem] 归类,和 list-38 的归位规则
|
||||
// (currentMailboxPVFPlacement)用的是同一个判据。
|
||||
//
|
||||
// 结果排序后缓存:同一档里选哪一件是随机的,但候选顺序必须稳定,否则测试和日志
|
||||
// 都没法复现。
|
||||
type currentGuildCompoundCandidates struct {
|
||||
once sync.Once
|
||||
err error
|
||||
byKey map[currentGuildCompoundKey][]uint32
|
||||
}
|
||||
|
||||
type currentGuildCompoundKey struct {
|
||||
Kind dnfguild.CompoundKind
|
||||
Rarity uint32
|
||||
}
|
||||
|
||||
func (s *Service) currentGuildCompoundCandidates(
|
||||
kind dnfguild.CompoundKind,
|
||||
rarity uint32,
|
||||
) ([]uint32, error) {
|
||||
catalog, err := s.currentPVFItemCatalog()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.guildCompoundCandidates.once.Do(func() {
|
||||
s.guildCompoundCandidates.byKey, s.guildCompoundCandidates.err =
|
||||
buildCurrentGuildCompoundCandidates(catalog)
|
||||
})
|
||||
if s.guildCompoundCandidates.err != nil {
|
||||
return nil, s.guildCompoundCandidates.err
|
||||
}
|
||||
return s.guildCompoundCandidates.byKey[currentGuildCompoundKey{Kind: kind, Rarity: rarity}], nil
|
||||
}
|
||||
|
||||
func buildCurrentGuildCompoundCandidates(
|
||||
catalog *pvfDungeonDropCatalog,
|
||||
) (map[currentGuildCompoundKey][]uint32, error) {
|
||||
out := make(map[currentGuildCompoundKey][]uint32, 16)
|
||||
add := func(kind dnfguild.CompoundKind, itemID uint32, rarity int64) {
|
||||
if rarity < 0 {
|
||||
return
|
||||
}
|
||||
key := currentGuildCompoundKey{Kind: kind, Rarity: uint32(rarity)}
|
||||
out[key] = append(out[key], itemID)
|
||||
}
|
||||
for _, itemID := range catalog.stackableIDs {
|
||||
if !currentGuildCompoundPathMayHold(catalog, itemID) {
|
||||
continue
|
||||
}
|
||||
definition, err := catalog.ResolveItem(itemID)
|
||||
if err != nil || definition.Kind != dungeonDropItemStackable {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(normalizeDungeonDropStackableType(definition.StackableType), "[flag gem]") {
|
||||
continue
|
||||
}
|
||||
add(dnfguild.CompoundGem, itemID, definition.Rarity)
|
||||
}
|
||||
for _, itemID := range catalog.equipmentIDs {
|
||||
if !currentGuildCompoundPathMayHold(catalog, itemID) {
|
||||
continue
|
||||
}
|
||||
definition, err := catalog.ResolveItem(itemID)
|
||||
if err != nil || definition.Kind != dungeonDropItemEquipment {
|
||||
continue
|
||||
}
|
||||
if normalizeEquipmentPlacementPVFType(definition.EquipmentType) != "[flag]" {
|
||||
continue
|
||||
}
|
||||
add(dnfguild.CompoundFlag, itemID, definition.Rarity)
|
||||
}
|
||||
for key := range out {
|
||||
sort.Slice(out[key], func(i, j int) bool { return out[key][i] < out[key][j] })
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// currentGuildCompoundPathMayHold 是建索引时的**廉价预筛**。
|
||||
//
|
||||
// 没有它,建一次索引要对 PVF 里**每一件**可堆叠物与装备跑一次 ResolveItem(各自
|
||||
// 都要解析一份文档)—— 实测第一次合成因此卡了 **8.66 秒**,玩家看到的就是"合成
|
||||
// 失败会卡住一段时间"。
|
||||
//
|
||||
// 预筛只看已经加载好的引用路径,不读任何文档:守护珠在 stackable/flaggem/ 下,
|
||||
// 勋章在 equipment/character/common/flag/ 下,两者的路径里都带 "flag"。判据故意
|
||||
// 放宽成子串匹配而不是精确前缀 —— 宁可多解析几件,也不要因为路径换了目录而悄悄
|
||||
// 丢掉候选。真正的归类判据仍然是 PVF 的 [flag] / [flag gem],没有放松。
|
||||
func currentGuildCompoundPathMayHold(catalog *pvfDungeonDropCatalog, itemID uint32) bool {
|
||||
reference, found := catalog.itemRefs[itemID]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(strings.ToLower(reference.path), "flag")
|
||||
}
|
||||
|
||||
// currentGuildCompoundGemFamilySuccessor 是同属性守护珠的下一档 ID。
|
||||
//
|
||||
// 守护珠的 ID 是极其规整的 900<属性2位><档位1位>:90000..90003 是物理防御力的
|
||||
// 0..3 档,90010..90013 是魔法防御力的 0..3 档,一直到 90120..90123,13 个属性
|
||||
// 各 4 档。所以"同属性升一档"就是 ID + 1。
|
||||
//
|
||||
// 只有在四件材料是**同一个 ID** 时才走这条路(守护珠是可堆叠的,正常操作就是从
|
||||
// 同一摞里拖四个出来,实测请求也确实是四个槽位都指向同一格)。材料属性不一致时
|
||||
// 没有"同属性"可言,退回随机。
|
||||
//
|
||||
// 这条规则是设计判断而非反汇编证据:这张配方表不在客户端里,服务端此前也没有实现,
|
||||
// 两端都不存在可以证明它的代码。挑它是因为属性对玩家有意义,随机掉属性会让合成
|
||||
// 变成纯赌博。勋章没有这种对应关系(稀有度 0 的"古老/破旧/耀眼"和稀有度 1 的
|
||||
// "简陋/破旧/寒酸"不是同一组词),所以勋章只能随机。
|
||||
func currentGuildCompoundGemFamilySuccessor(materials []uint32, nextRarity uint32, candidates []uint32) (uint32, bool) {
|
||||
if len(materials) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
first := materials[0]
|
||||
for _, material := range materials[1:] {
|
||||
if material != first {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
successor := first + 1
|
||||
for _, candidate := range candidates {
|
||||
if candidate == successor {
|
||||
return successor, true
|
||||
}
|
||||
}
|
||||
_ = nextRarity
|
||||
return 0, false
|
||||
}
|
||||
Reference in new issue
Block a user