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,106 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
dnfguild "longheng.io/server/internal/modules/dnf/guild"
|
||||
)
|
||||
|
||||
type currentGuildBuff struct {
|
||||
index uint32
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type currentGuildBuffProjection struct {
|
||||
mu sync.Mutex
|
||||
characterID uint16
|
||||
buffs [2]currentGuildBuff
|
||||
}
|
||||
|
||||
// The caller has committed op24 and owns townMu. Recreate the guild buffs on
|
||||
// dungeon return: sub_18ADDA0 updates parameters but retains the original tick.
|
||||
func (s *Service) sendCurrentGuildBuffProjection(session *gameSession, source string) error {
|
||||
guild, err := s.currentGuildForSelectedCharacter(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.writeCurrentGuildBuffProjection(session, guild, true, source)
|
||||
}
|
||||
|
||||
// Called only after the selected actor and scene transition exist.
|
||||
func (s *Service) writeCurrentGuildBuffProjection(session *gameSession, guild *dnfguild.Guild, force bool, source string) error {
|
||||
if session == nil || session.selectedCharacterID == 0 {
|
||||
return nil
|
||||
}
|
||||
now := s.gameplayNow().UTC()
|
||||
var desired [2]currentGuildBuff
|
||||
if guild != nil {
|
||||
if _, member := guild.Members[strconv.Itoa(int(session.selectedCharacterID))]; member {
|
||||
// Current Script.pvf: Guild_Status / Guild_Status1, both icon 206.
|
||||
// Other content values (EXP percentage, teleport) are not buff IDs.
|
||||
for i, kind := range [...]byte{3, 5} {
|
||||
benefit, found := guild.Benefits[kind]
|
||||
if found && benefit.Value == uint32(167+i*2) && benefit.Active(now) {
|
||||
desired[i] = currentGuildBuff{index: benefit.Value, expiresAt: benefit.ExpiresAt}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state := &session.guildBuffProjection
|
||||
state.mu.Lock()
|
||||
defer state.mu.Unlock()
|
||||
force = force || state.characterID != session.selectedCharacterID
|
||||
// 2026-10-08:重放时**先把两份都移除、再按登录顺序把它们加回来**。
|
||||
// 原先每个槽位是"移除 A→加 A→移除 B→加 B"交替下发;两个公会属性 buff 共用同一个
|
||||
// 图标(206),交替 churn 之后客户端只剩最后加进去的那一个的加成效力 —— 现场就是
|
||||
// "两个公会属性(限时167/永久169,各+60四维)进图回城后只回来 60 点"。
|
||||
// 拆成两趟之后,添加顺序与登录时(167 然后 169,实测 +120 正常)完全一致。
|
||||
var pending [2]currentGuildBuff
|
||||
for i, buff := range desired {
|
||||
previous := state.buffs[i]
|
||||
if !force && previous.index == buff.index && previous.expiresAt.Equal(buff.expiresAt) {
|
||||
pending[i] = previous
|
||||
continue
|
||||
}
|
||||
if previous.index != 0 {
|
||||
var body packetWriter
|
||||
body.writeByte(1)
|
||||
body.writeUint32(previous.index)
|
||||
// sub_18AE7D0 removes this exact VariousBuff and its HUD entry.
|
||||
if err := s.sendGameUpperRawClass(session, 488, body.bytes(), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
state.buffs[i] = currentGuildBuff{}
|
||||
}
|
||||
pending[i] = buff
|
||||
}
|
||||
for i, buff := range pending {
|
||||
if buff.index == 0 {
|
||||
continue
|
||||
}
|
||||
seconds := uint32(0)
|
||||
if !buff.expiresAt.IsZero() {
|
||||
seconds = uint32(math.Min(math.Ceil(buff.expiresAt.Sub(now).Seconds()), math.MaxInt32))
|
||||
}
|
||||
var body packetWriter
|
||||
body.writeByte(1)
|
||||
body.writeUint32(buff.index)
|
||||
body.writeUint32(0) // expire_time parameter 0, as defined in PVF
|
||||
body.writeUint32(seconds) // parameter 1; zero means permanent
|
||||
body.writeUint32(0) // auxiliary value, unused by Guild_Status groups
|
||||
// sub_18AE360 -> sub_18AD260 -> sub_18A9060 -> sub_1F8B060.
|
||||
if err := s.sendGameUpperRawClass(session, 487, body.bytes(), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
state.buffs[i] = buff
|
||||
s.logPacketEvent("game-guild-various-buff-send",
|
||||
"conn_id", session.connID, "char_id", session.selectedCharacterID,
|
||||
"source", source, "buff_index", buff.index, "remaining_seconds", seconds,
|
||||
"sequence", "class0/op487-various-buff")
|
||||
}
|
||||
state.characterID = session.selectedCharacterID
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user