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,97 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/worldmap"
|
||||
)
|
||||
|
||||
// 深渊派对入场消耗(邀请函数量)的取值规则。
|
||||
//
|
||||
// 客户端副本提示里的 "进入条件 ·消耗N个深渊派对邀请函 (0)" 是客户端自己算的,
|
||||
// N 来自副本自己的 [minimum required level]:
|
||||
//
|
||||
// N = (40*Lv - 1800) / 100 + 10 // 整数除法,向零截断
|
||||
//
|
||||
// 实测(2026-09-26,角色 测试x Lv90,客户端内逐个悬停读数):
|
||||
//
|
||||
// 86 列车上的海贼 Lv71 -> 20 87 夺回西部线 Lv72 -> 20
|
||||
// 92 雾都赫伊斯 Lv73 -> 21 93 阿登高地 Lv74 -> 21
|
||||
// 193 能源熔炉 Lv86 -> 26
|
||||
//
|
||||
// 五个读数全部命中该式,而且区间边界也吻合(71/72 同为 20,73/74 同为 21)。
|
||||
// 换用 [basis level](74/75/76/77/89)会得到 21/22/22/22/27,全部不符;换用角色
|
||||
// 等级也不符(同一个角色读到 20/20/21/21 四个不同的数)。旧树 changlog 里
|
||||
// "(40·90−1800)/100+10 = 28" 是同一个式子在 Lv90 上的取值。
|
||||
//
|
||||
// 真 PVF 里只有 11/205 个深渊区域副本写了 [hell party required item count];剩下 194
|
||||
// 个没写,其中 187 个的所属区域有恰好一条 [item condition](就是 3330 邀请函),
|
||||
// 7 个(镜像阿拉德 8000 与伤城 8500 一系)连 [item condition] 都没有。没有声明值时
|
||||
// 必须按上面这条等级式推导:否则这些副本的入场计划建立不起来,玩家点下"深渊派对"
|
||||
// 之后服务端给不出任何回执(2026-09-26 现场:193 号副本卡在选择页 26 秒)。
|
||||
const (
|
||||
currentHellAdmissionLevelScale = 40
|
||||
currentHellAdmissionLevelOffset = 1800
|
||||
currentHellAdmissionLevelDivisor = 100
|
||||
currentHellAdmissionBaseCount = 10
|
||||
currentHellAdmissionMinCount = 1
|
||||
)
|
||||
|
||||
// currentHellAdmissionCostSource* 记录这次消耗数量是从哪儿来的,只进日志:
|
||||
// 有 PVF 声明值和按等级式推导,出问题时必须能一眼分清。
|
||||
const (
|
||||
currentHellAdmissionCostSourceDeclared = "dungeon_hell_party_required_item_count"
|
||||
currentHellAdmissionCostSourceLevel = "dungeon_minimum_required_level_formula"
|
||||
)
|
||||
|
||||
// currentHellAdmissionInvitationCountForLevel is the client's own invitation
|
||||
// count rule: (40*Lv-1800)/100+10 with C-style truncating division. A level that
|
||||
// would price the entry at zero or below still costs one invitation.
|
||||
func currentHellAdmissionInvitationCountForLevel(level int64) int64 {
|
||||
if level <= 0 {
|
||||
return 0
|
||||
}
|
||||
count := (currentHellAdmissionLevelScale*level-currentHellAdmissionLevelOffset)/
|
||||
currentHellAdmissionLevelDivisor + currentHellAdmissionBaseCount
|
||||
if count < currentHellAdmissionMinCount {
|
||||
count = currentHellAdmissionMinCount
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// currentDungeonHellAdmissionInvitationCount returns how many invitations a hell
|
||||
// entry into this dungeon consumes, preferring the dungeon's own PVF value
|
||||
// ([hell party required item count]) and otherwise deriving it from
|
||||
// [minimum required level] exactly as the client tooltip does. The second result
|
||||
// names which of the two was used.
|
||||
func currentDungeonHellAdmissionInvitationCount(dungeon worldmap.Dungeon) (int64, string, error) {
|
||||
if declared := dungeon.Metadata.HellPartyRequiredItemCount; declared.Set && declared.Value > 0 {
|
||||
return declared.Value, currentHellAdmissionCostSourceDeclared, nil
|
||||
}
|
||||
level := dungeon.Metadata.MinimumRequiredLevel
|
||||
if !level.Set || level.Value <= 0 {
|
||||
return 0, "", fmt.Errorf(
|
||||
"dungeon %d has neither [hell party required item count] nor [minimum required level]",
|
||||
dungeon.ID,
|
||||
)
|
||||
}
|
||||
return currentHellAdmissionInvitationCountForLevel(level.Value), currentHellAdmissionCostSourceLevel, nil
|
||||
}
|
||||
|
||||
// currentDungeonHellEntryRequiresInvitations reports whether this entry's
|
||||
// admission item is the hell area's [item condition] -- the two branches the
|
||||
// admission plan prices with the invitation count above. Those are exactly the
|
||||
// entries whose client text lives in the op16 failure reason-27 block
|
||||
// (0x01CFBD8B: "队伍中有队员的%s不足,无法进入深渊派对。" / 27156
|
||||
// "部分队员邀请函不足,无法进入该地下城。"), so they get reason 27 instead of the
|
||||
// generic reason 17. Particular-hell (魔界裂缝) entries are excluded: their plan
|
||||
// comes from the area's particular-hell free pass, not from the item condition.
|
||||
func currentDungeonHellEntryRequiresInvitations(runtime *runtimeDungeonState) bool {
|
||||
if runtime == nil {
|
||||
return false
|
||||
}
|
||||
if runtime.Dungeon.Metadata.InfernoDungeon {
|
||||
return true
|
||||
}
|
||||
return currentDungeonExplicitHellPartyRequest(runtime.Request, runtime.Dungeon, runtime.MazeIndex)
|
||||
}
|
||||
Reference in new issue
Block a user