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,194 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user