Files
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

195 lines
6.7 KiB
Go

package dnfbridge
import (
"encoding/binary"
"errors"
"strconv"
"longheng.io/server/internal/modules/dnf/dnfenum"
dnfguild "longheng.io/server/internal/modules/dnf/guild"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
const (
currentGuildGuardianStateMsgID = uint16(1297)
currentGuildGuardianStateSize = 2174
)
func buildCurrentGuildGuardianStateBody(guild *dnfguild.Guild) []byte {
body := make([]byte, currentGuildGuardianStateSize)
body[1] = 0xff
binary.LittleEndian.PutUint32(body[2166:], 65537)
if guild == nil {
return body
}
binary.LittleEndian.PutUint16(body[2166:], guild.WarShopLevel(0))
binary.LittleEndian.PutUint16(body[2168:], guild.WarShopLevel(1))
// sub_E88380 reads AP/GP at guild raw11804/11808, relative to11781.
body[2] = guild.AreaLevel
binary.LittleEndian.PutUint32(body[23:], guild.GuardianAP)
binary.LittleEndian.PutUint32(body[27:], guild.GuardianGP)
for i, guardian := range guild.Guardians {
if i >= 5 {
break
}
off := 2040 + i*18
binary.LittleEndian.PutUint32(body[off:], guardian.ID)
body[off+4] = guardian.Level
copy(body[off+5:off+10], guardian.Skills[:])
binary.LittleEndian.PutUint32(body[off+10:], guardian.APExp)
binary.LittleEndian.PutUint32(body[off+14:], guardian.GPExp)
if guardian.ID == guild.ActiveGuardian {
// sub_E92E00 uses this ID to replace the base's guardian NPC.
binary.LittleEndian.PutUint32(body[68:], guardian.ID)
}
}
return body
}
func parseCurrentGuildGuardianRequest(body []byte) (byte, uint32, bool) {
if len(body) != 16 {
return 0, 0, false
}
action := binary.LittleEndian.Uint32(body)
id := binary.LittleEndian.Uint32(body[4:])
valid := action <= 8 && id != 0
if action >= 7 {
valid = valid && binary.LittleEndian.Uint64(body[8:]) == 0
} else {
valid = valid && binary.LittleEndian.Uint32(body[12:]) != 0
}
return byte(action), id, valid
}
func (s *Service) handleCurrentGuildGuardian(session *gameSession, body []byte) error {
opcode := uint16(dnfenum.CmdPacketAgitWarGuardian)
action, guardianID, ok := parseCurrentGuildGuardianRequest(body)
ready, _ := s.currentSceneTransitionReadyForState(session)
if !ok || !ready {
return s.sendGameUpperFailure(session, opcode, 3)
}
items, err := s.currentPVFItemCatalog()
if err != nil {
return s.sendGameUpperFailure(session, opcode, 1)
}
costs, err := dnfguild.LoadGuardianCosts(items.source)
if err != nil {
return s.sendGameUpperFailure(session, opcode, 1)
}
var growth dnfguild.GuardianGrowth
if action < 7 {
growth, err = dnfguild.LoadGuardianGrowth(items.source)
if err != nil {
return s.sendGameUpperFailure(session, opcode, 1)
}
}
ctx, cancel := s.currentGuildContext()
defer cancel()
var selected *dnfguild.Guild
err = s.transactCurrentGuildAssets(ctx, session, func(_ dnfrepo.Group, registry *dnfguild.Registry, character *dnfrepo.CharacterRecord) error {
var applyErr error
if action < 7 {
applyErr = registry.GrowGuardian(character.CharacterID, action, guardianID, binary.LittleEndian.Uint32(body[8:]), binary.LittleEndian.Uint32(body[12:]), growth, s.gameplayNow())
} else {
applyErr = registry.SetGuardian(character.CharacterID, action, guardianID, costs, s.gameplayNow())
}
if applyErr != nil {
return applyErr
}
selected, _, _ = registry.GuildForCharacter(character.CharacterID)
return nil
})
if err != nil {
s.logGameEvent(session, "game-guild-guardian-rejected", "action", action, "guardian_id", guardianID, "error", err)
return s.sendGameUpperFailure(session, opcode, currentGuildGuardianError(err))
}
if err := s.sendGameUpperSuccess(session, opcode, nil); err != nil {
return err
}
s.logGameEvent(session, "game-guild-guardian-saved", "action", action, "guardian_id", guardianID)
return s.publishCurrentGuildGuardianState(session, selected)
}
func currentGuildGuardianError(err error) byte {
if errors.Is(err, dnfguild.ErrInsufficientGuardian) {
return 22
}
if errors.Is(err, dnfguild.ErrNotMember) {
return 100
}
if errors.Is(err, dnfguild.ErrRepositoryUnavailable) {
return 1
}
return 3
}
func (s *Service) handleCurrentGuildAreaExtend(session *gameSession, body []byte) error {
opcode := uint16(dnfenum.CmdPacketAgitWarExtend)
ready, _ := s.currentSceneTransitionReadyForState(session)
if len(body) != 1 || !ready {
return s.sendGameUpperFailure(session, opcode, 3)
}
items, err := s.currentPVFItemCatalog()
if err != nil {
return s.sendGameUpperFailure(session, opcode, 3)
}
costs, err := dnfguild.LoadAreaCosts(items.source)
if err != nil {
return s.sendGameUpperFailure(session, opcode, 3)
}
ctx, cancel := s.currentGuildContext()
defer cancel()
var selected *dnfguild.Guild
err = s.transactCurrentGuildAssets(ctx, session, func(_ dnfrepo.Group, registry *dnfguild.Registry, character *dnfrepo.CharacterRecord) error {
if err := registry.ExtendArea(character.CharacterID, body[0], costs, s.gameplayNow()); err != nil {
return err
}
selected, _, _ = registry.GuildForCharacter(character.CharacterID)
return nil
})
if err != nil {
s.logGameEvent(session, "game-guild-area-extend-rejected", "level", body[0], "error", err)
return s.sendGameUpperFailure(session, opcode, currentGuildGuardianError(err))
}
if err := s.sendGameUpperSuccess(session, opcode, []byte{selected.AreaLevel}); err != nil {
return err
}
s.logGameEvent(session, "game-guild-area-extend-saved", "level", selected.AreaLevel)
return s.publishCurrentGuildGuardianState(session, selected)
}
func (s *Service) writeCurrentGuildGuardianState(session *gameSession, guild *dnfguild.Guild) error {
if guild != nil {
if repos, ok := s.repositoryGroup(); ok && repos.Settings != nil && repos.Character != nil {
ctx, cancel := s.currentGuildContext()
defer cancel()
var body []byte
err := s.readCurrentGuild(ctx, func(_ dnfrepo.Group, r dnfguild.Registry) error {
body = buildCurrentGuildWarOverviewBody(guild, r)
return nil
})
if err != nil {
return err
}
return s.sendGameUpperRawClass(session, currentGuildGuardianStateMsgID, body, 0)
}
}
return s.sendGameUpperRawClass(session, currentGuildGuardianStateMsgID, buildCurrentGuildGuardianStateBody(guild), 0)
}
func (s *Service) publishCurrentGuildGuardianState(session *gameSession, guild *dnfguild.Guild) error {
for id := range guild.Members {
if id == strconv.Itoa(int(session.selectedCharacterID)) {
continue
}
if target, found := s.onlineGameSessionCharacterSnapshot(uint16(currentGuildNumericID(id))); found {
if ready, _ := s.currentSceneTransitionReadyForState(target.session); ready {
if err := s.writeCurrentGuildGuardianState(target.session, guild); err != nil {
s.logGameEvent(target.session, "game-guild-guardian-refresh-failed", "error", err)
}
}
}
}
return s.writeCurrentGuildGuardianState(session, guild)
}