Files
90CNServer/server/internal/services/dnfbridge/companion_start_map_switch.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

103 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dnfbridge
import (
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// 本文件实现支援兵自建 APC 的**开关**。
//
// ⚠️ 默认必须保持关闭(环境变量与标记文件都不设)。这条通道已在零售客户端上
// 实测**导致客户端闪退**(op29 含自建 APC 时 0xC0000005 空指针),只有官方
// 配套的补丁客户端才能安全使用。完整结论见
// companion_dungeon_p0_start_map.go 顶部的说明。
//
// 为什么还要有文件开关(而不是只用环境变量)
// 环境变量必须在服务端**进程启动前**就设好,而本部署的启动链路是
// D:\90server\登录游戏.bat → DNF90Launcher.exe → DNF90Server.exe
// 只要服务端已经在跑,登录器就会复用它,改环境变量不会生效 ——
// 表现为"改了半天开关,行为一点没变",极难排查。
//
// 文件开关在**每次进副本时**读一次,所以:
// - 服务端已经跑着也能开/关
// - 登录器重建服务端也不受影响
// - 开关状态可被 GM 工具/一行命令直接写
//
// 两种开关是**或**的关系,任一成立即启用;都关=行为与移植前完全一致。
const (
// 环境开关(兼容 Server-90 的既有名字)。
currentDungeonCompanionP0StartMapEnv = "DNF_COMPANION_P0_STARTMAP_APC"
// 文件开关:内容为 "1"(或文件存在且内容非 0)即启用。
currentDungeonCompanionP0StartMapFlagFile = "companion_start_map.enabled"
// 文件开关的读取缓存时长。进副本是低频操作,这个粒度足够,
// 又不会在每次房间切换时都打系统调用。
currentDungeonCompanionP0StartMapFlagTTL = 5 * time.Second
)
var (
companionStartMapFlagMu sync.Mutex
companionStartMapFlagCache bool
companionStartMapFlagChecked time.Time
// companionStartMapFlagPathOverride 让测试把开关文件指向临时目录。
//
// 没有这个钩子时,测试会去读**部署目录**里的真实开关文件
// (测试进程的工作目录同样是 runtime\),于是"用户打开开关"会
// 连带把一批与支援兵无关的测试弄失败 —— 那是环境耦合,不是被
// 测代码的问题。
companionStartMapFlagPathOverride string
)
func currentDungeonCompanionP0StartMapEnabledValue(value string) bool {
return value == "1"
}
// currentDungeonCompanionP0StartMapFlagPath 返回文件开关的绝对路径。
// 服务端的工作目录是 runtime\,所以默认落在 runtime\ 下。
func currentDungeonCompanionP0StartMapFlagPath() string {
if override := strings.TrimSpace(companionStartMapFlagPathOverride); override != "" {
return override
}
if dir, err := os.Getwd(); err == nil && strings.TrimSpace(dir) != "" {
return filepath.Join(dir, currentDungeonCompanionP0StartMapFlagFile)
}
return currentDungeonCompanionP0StartMapFlagFile
}
// currentDungeonCompanionP0StartMapFlagFileEnabled 读文件开关(带 TTL 缓存)。
//
// 语义:文件存在且内容不是 "0"/"false" 即为开。这样 `echo 1 > 文件` 和
// 直接 `type nul > 文件` 都能开,符合"放个标记文件"的直觉。
func currentDungeonCompanionP0StartMapFlagFileEnabled() bool {
companionStartMapFlagMu.Lock()
defer companionStartMapFlagMu.Unlock()
now := time.Now()
if !companionStartMapFlagChecked.IsZero() &&
now.Sub(companionStartMapFlagChecked) < currentDungeonCompanionP0StartMapFlagTTL {
return companionStartMapFlagCache
}
companionStartMapFlagChecked = now
companionStartMapFlagCache = false
raw, err := os.ReadFile(currentDungeonCompanionP0StartMapFlagPath())
if err != nil {
return false
}
switch strings.ToLower(strings.TrimSpace(string(raw))) {
case "0", "false", "off", "no":
return false
}
companionStartMapFlagCache = true
return true
}
// currentDungeonCompanionP0StartMapEnabled 是这条通道的总开关。
func currentDungeonCompanionP0StartMapEnabled() bool {
if currentDungeonCompanionP0StartMapEnabledValue(os.Getenv(currentDungeonCompanionP0StartMapEnv)) {
return true
}
return currentDungeonCompanionP0StartMapFlagFileEnabled()
}