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,175 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"longheng.io/server/internal/modules/dnf/dnfenum"
|
||||
dnfexpertjob "longheng.io/server/internal/modules/dnf/expertjob"
|
||||
dnfproto "longheng.io/server/internal/modules/dnf/protocol"
|
||||
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
currentExpertJobGiveUpInsufficientGold byte = 10
|
||||
currentExpertJobGiveUpInvalidState byte = 19
|
||||
currentExpertJobGiveUpStateStat = "expert_job_give_up_state"
|
||||
)
|
||||
|
||||
type currentExpertJobGiveUpResult struct {
|
||||
FinalGold int64
|
||||
State byte
|
||||
Cost int64
|
||||
}
|
||||
|
||||
func (s *Service) handleCurrentExpertJobGiveUp(session *gameSession, body []byte) error {
|
||||
opcode := uint16(dnfenum.CmdPacketGiveupExpertJob)
|
||||
if len(body) != 0 || s == nil || session == nil || session.selectedCharacterID == 0 || currentExpertJobSessionInDungeon(session) {
|
||||
return s.sendGameUpperFailure(session, opcode, currentExpertJobGiveUpInvalidState)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), createWriteTimeout)
|
||||
result, err := s.commitCurrentExpertJobGiveUp(ctx, session)
|
||||
cancel()
|
||||
if err != nil {
|
||||
code := currentExpertJobGiveUpInvalidState
|
||||
if errors.Is(err, dnfexpertjob.ErrInsufficientGold) {
|
||||
code = currentExpertJobGiveUpInsufficientGold
|
||||
}
|
||||
s.logGameEvent(session, "game-expert-job-give-up-rejected", "reason", err, "error_code", code)
|
||||
return s.sendGameUpperFailure(session, opcode, code)
|
||||
}
|
||||
|
||||
if store := s.removeCurrentExpertJobStore(session.selectedCharacterID, session); store != nil {
|
||||
s.broadcastCurrentExpertJobStoreClose(store, true)
|
||||
}
|
||||
if err := s.sendGameUpperRawClass(session, opcode, buildCurrentExpertJobGiveUpSuccessBody(result), dnfproto.DefaultChannelClassification); err != nil {
|
||||
return err
|
||||
}
|
||||
// Clear the actor record's sub-profession field before restating the cleared
|
||||
// op205 state. The client only re-reads that field from a mode-0 record, so
|
||||
// without this refresh the profession row stays visible until a relogin - and,
|
||||
// worse, a later re-acquisition would not be a real 0 -> N transition.
|
||||
refreshCtx, refreshCancel := context.WithTimeout(context.Background(), createWriteTimeout)
|
||||
refreshErr := s.currentSendExpertJobActorRefresh(refreshCtx, session, dnfrepo.CharacterRecord{})
|
||||
refreshCancel()
|
||||
if refreshErr != nil {
|
||||
s.logGameEvent(session, "game-expert-job-give-up-actor-refresh-failed",
|
||||
"char_id", session.selectedCharacterID,
|
||||
"error", refreshErr)
|
||||
}
|
||||
if err := s.sendCurrentExpertJobClearedInfo(session, result.State); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.sendCurrentActiveQuestSnapshotForSession(session, "current_exe_op239_after_atomic_give_up"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.sendCurrentAcceptableQuestListOnlyForSession(session, "current_exe_op239_after_active_snapshot"); err != nil {
|
||||
return err
|
||||
}
|
||||
s.logGameEvent(session, "game-expert-job-give-up-committed",
|
||||
"final_gold", result.FinalGold,
|
||||
"give_up_state", result.State,
|
||||
"cost", result.Cost,
|
||||
"quest_source", "runtime_pvf_job_change_quest_20",
|
||||
"body_source", "current_exe_op239_success_exact")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) commitCurrentExpertJobGiveUp(ctx context.Context, session *gameSession) (currentExpertJobGiveUpResult, error) {
|
||||
if s == nil || session == nil || session.selectedCharacterID == 0 {
|
||||
return currentExpertJobGiveUpResult{}, dnfexpertjob.ErrOwnerUnavailable
|
||||
}
|
||||
catalog, err := s.currentExpertJobCatalog()
|
||||
if err != nil {
|
||||
return currentExpertJobGiveUpResult{}, err
|
||||
}
|
||||
questCatalog, err := s.loadQuestCatalog(ctx)
|
||||
if err != nil {
|
||||
return currentExpertJobGiveUpResult{}, err
|
||||
}
|
||||
transitionQuestIDs := questCatalog.ExpertJobTransitionQuestIDs()
|
||||
if len(transitionQuestIDs) == 0 {
|
||||
return currentExpertJobGiveUpResult{}, dnfexpertjob.ErrJobUnsupported
|
||||
}
|
||||
repositories, ok := s.repositoryGroup()
|
||||
if !ok {
|
||||
return currentExpertJobGiveUpResult{}, dnfexpertjob.ErrOwnerUnavailable
|
||||
}
|
||||
owner, err := dnfexpertjob.NewOwner(repositories)
|
||||
if err != nil {
|
||||
return currentExpertJobGiveUpResult{}, err
|
||||
}
|
||||
accountID := strings.TrimSpace(s.accountIDForSession(session))
|
||||
characterID := strconv.FormatUint(uint64(session.selectedCharacterID), 10)
|
||||
if accountID == "" || characterID == "0" {
|
||||
return currentExpertJobGiveUpResult{}, dnfexpertjob.ErrOwnerUnavailable
|
||||
}
|
||||
|
||||
var committed currentExpertJobGiveUpResult
|
||||
err = owner.GiveUp(ctx, dnfexpertjob.Command{
|
||||
AccountID: accountID, CharacterID: characterID, UpdatedAt: s.gameplayNow(),
|
||||
Project: func(assets *dnfexpertjob.Assets) (dnfexpertjob.Changes, error) {
|
||||
jobType, _, stateErr := currentExpertJobCharacterState(assets.Character)
|
||||
if stateErr != nil {
|
||||
return dnfexpertjob.Changes{}, stateErr
|
||||
}
|
||||
costs, found := catalog.GiveUpCosts(jobType)
|
||||
if !found {
|
||||
return dnfexpertjob.Changes{}, dnfexpertjob.ErrJobUnsupported
|
||||
}
|
||||
gold, goldErr := currentExpertJobWalletGold(assets.Character)
|
||||
if goldErr != nil {
|
||||
return dnfexpertjob.Changes{}, goldErr
|
||||
}
|
||||
state := assets.Character.Stats[currentExpertJobGiveUpStateStat]
|
||||
plan, planErr := dnfexpertjob.PlanGiveUp(gold, state, costs)
|
||||
if planErr != nil {
|
||||
return dnfexpertjob.Changes{}, planErr
|
||||
}
|
||||
if plan.FinalGold < 0 || plan.FinalGold > math.MaxUint32 {
|
||||
return dnfexpertjob.Changes{}, dnfexpertjob.ErrMachineInvalid
|
||||
}
|
||||
currentExpertJobSetWalletGold(assets.Character, plan.FinalGold)
|
||||
if assets.Character.Stats == nil {
|
||||
assets.Character.Stats = make(map[string]int64)
|
||||
}
|
||||
assets.Character.Stats["expert_job_type"] = 0
|
||||
assets.Character.Stats["expert_job_exp"] = 0
|
||||
assets.Character.Stats[currentExpertJobGiveUpStateStat] = int64(plan.FinalState)
|
||||
delete(assets.Character.Stats, currentExpertJobMachineGradeStat)
|
||||
delete(assets.Character.Stats, currentExpertJobMachineEnduranceStat)
|
||||
for key := range assets.Character.Stats {
|
||||
if strings.HasPrefix(key, "expert_job_recipe_") {
|
||||
delete(assets.Character.Stats, key)
|
||||
}
|
||||
}
|
||||
questChanged := false
|
||||
for _, questID := range transitionQuestIDs {
|
||||
if _, exists := assets.Quest.States[questID]; exists {
|
||||
delete(assets.Quest.States, questID)
|
||||
questChanged = true
|
||||
}
|
||||
if _, exists := assets.Quest.Progress[questID]; exists {
|
||||
delete(assets.Quest.Progress, questID)
|
||||
questChanged = true
|
||||
}
|
||||
}
|
||||
committed = currentExpertJobGiveUpResult{FinalGold: plan.FinalGold, State: plan.FinalState, Cost: plan.Cost}
|
||||
return dnfexpertjob.Changes{Character: true, Inventory: true, Quest: questChanged}, nil
|
||||
},
|
||||
})
|
||||
return committed, err
|
||||
}
|
||||
|
||||
func buildCurrentExpertJobGiveUpSuccessBody(result currentExpertJobGiveUpResult) []byte {
|
||||
w := packetWriter{}
|
||||
w.writeByte(1)
|
||||
w.writeUint32(uint32(result.FinalGold))
|
||||
w.writeByte(result.State)
|
||||
w.writeByte(0)
|
||||
return w.bytes()
|
||||
}
|
||||
Reference in new issue
Block a user