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,213 @@
|
||||
package dnfbridge
|
||||
|
||||
import (
|
||||
"longheng.io/server/internal/modules/dnf/dungeoncmd"
|
||||
"longheng.io/server/internal/modules/dnf/worldmap"
|
||||
)
|
||||
|
||||
// completeValidatedDungeonBossDieCheckLocked owns the clear decision after the
|
||||
// request target has been proven defeated by the authoritative room runtime.
|
||||
func (s *Service) completeValidatedDungeonBossDieCheckLocked(
|
||||
session *gameSession,
|
||||
runtime *runtimeDungeonState,
|
||||
request dungeoncmd.BossDieCheckRequest,
|
||||
scene worldmap.DungeonRoomScene,
|
||||
tutorialScene bool,
|
||||
targetReference worldmap.HostileReference,
|
||||
targetIsOrdinaryMonster bool,
|
||||
targetObjectKey uint32,
|
||||
) error {
|
||||
completionSource := "cleared_ordinary_runtime_boss_room"
|
||||
if tutorialScene {
|
||||
completionSource = "cleared_tutorial_boss_room"
|
||||
}
|
||||
storyAIBossGate, storyAIBossGateActive := currentDungeonStoryAIBossDeathGate(runtime, scene)
|
||||
if !tutorialScene && storyAIBossGateActive && !storyAIBossGate.Ready {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"maze_index", runtime.MazeIndex,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"dummy_boss_object_keys", storyAIBossGate.DummyBossObjectKeys,
|
||||
"AI_boss_object_keys", storyAIBossGate.AIBossObjectKeys,
|
||||
"reason", "story_ai_boss_and_dummy_boss_require_separate_authoritative_op39")
|
||||
return nil
|
||||
}
|
||||
pvfMultiBossGate, pvfMultiBossGateActive := currentDungeonPVFMultiBossDeathGate(runtime, scene)
|
||||
if !tutorialScene && pvfMultiBossGateActive && !pvfMultiBossGate.Ready {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"maze_index", runtime.MazeIndex,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"boss_object_keys", pvfMultiBossGate.BossObjectKeys,
|
||||
"reason", "current_pvf_multi_boss_rows_require_separate_authoritative_op39")
|
||||
return nil
|
||||
}
|
||||
scriptEvidence := dungeonTutorialScriptEvidence{}
|
||||
remainingScriptedMonsterIndexes := []int(nil)
|
||||
if !scene.Cleared {
|
||||
targetIsBossActor, targetBossActorType, targetBossActorSource := currentDungeonRuntimeObjectBossActor(
|
||||
runtime,
|
||||
targetObjectKey,
|
||||
runtimeDungeonMonsterDefeated,
|
||||
)
|
||||
if !tutorialScene && targetIsBossActor && !storyAIBossGateActive &&
|
||||
(!pvfMultiBossGateActive || pvfMultiBossGate.Ready) {
|
||||
forcedDefeated, forcedVisual, forcedCleared, forceErr := s.forceCurrentDungeonRemainingHostilesForCombatEndLocked(
|
||||
session,
|
||||
runtime,
|
||||
targetObjectKey,
|
||||
"op117_clear_decision_after_boss_actor_defeated",
|
||||
)
|
||||
if forceErr != nil {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-force-clear-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"boss_actor_type", targetBossActorType,
|
||||
"boss_actor_source", targetBossActorSource,
|
||||
"forced_defeated_count", forcedDefeated,
|
||||
"forced_visual_death_count", forcedVisual,
|
||||
"reason", "boss_death_clear_decision_force_clear_failed",
|
||||
"error", forceErr)
|
||||
return nil
|
||||
}
|
||||
snapshot := runtime.Session.Snapshot()
|
||||
scene = snapshot.Scene
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-force-clear-applied",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"boss_actor_type", targetBossActorType,
|
||||
"boss_actor_source", targetBossActorSource,
|
||||
"forced_defeated_count", forcedDefeated,
|
||||
"forced_visual_death_count", forcedVisual,
|
||||
"forced_cleared", forcedCleared,
|
||||
"room_cleared", scene.Cleared,
|
||||
"source", "boss_clear_decision_retires_remaining_hostiles")
|
||||
}
|
||||
if !tutorialScene {
|
||||
if scene.Cleared {
|
||||
goto bossRoomClearReady
|
||||
}
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"reason", "ordinary_boss_room_not_authoritatively_cleared")
|
||||
return nil
|
||||
}
|
||||
if !targetIsOrdinaryMonster || targetReference.Kind != worldmap.HostileMonster {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"target_kind", targetReference.Kind,
|
||||
"reason", "uncleared_final_target_not_ordinary_scripted_monster")
|
||||
return nil
|
||||
}
|
||||
catalog, catalogReady := s.dungeonTutorialScriptCatalog()
|
||||
if !catalogReady {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"reason", "tutorial_script_catalog_unavailable")
|
||||
return nil
|
||||
}
|
||||
var remainingOwned bool
|
||||
remainingScriptedMonsterIndexes, remainingOwned = currentDungeonRemainingBlockingMonsterIndexes(scene)
|
||||
if !remainingOwned {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"reason", "uncleared_final_blocking_monster_runtime_ownership_incomplete")
|
||||
return nil
|
||||
}
|
||||
var scripted bool
|
||||
scriptEvidence, scripted = catalog.FindMonsterDestroyCovering(
|
||||
scene.Map.Map.ID,
|
||||
targetReference.Index,
|
||||
remainingScriptedMonsterIndexes,
|
||||
)
|
||||
if !scripted {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"target_monster_index", targetReference.Index,
|
||||
"remaining_blocking_monster_indexes", remainingScriptedMonsterIndexes,
|
||||
"reason", "no_single_pvf_cmt_covers_destroy_target_and_all_remaining_blockers")
|
||||
return nil
|
||||
}
|
||||
completionSource = "pvf_cmt_destroyed_tutorial_final_actor"
|
||||
}
|
||||
|
||||
bossRoomClearReady:
|
||||
if !tutorialScene {
|
||||
pendingLayer, nextLayerIndex, nextLayerMapID, layerErr := s.currentDungeonPendingLayer(runtime, scene)
|
||||
if layerErr != nil {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-blocked",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"maze_index", runtime.MazeIndex,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"reason", "pending_layer_resolution_failed",
|
||||
"error", layerErr)
|
||||
return nil
|
||||
}
|
||||
if pendingLayer {
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-deferred",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"maze_index", runtime.MazeIndex,
|
||||
"room", scene.Coordinate.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"next_layer_index", nextLayerIndex,
|
||||
"next_layer_map_id", nextLayerMapID,
|
||||
"reason", "explicit_pvf_layer_must_be_consumed_before_settlement")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := runtime.Session.CompleteCurrentRoom(); err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.bossDieCheckAccepted = true
|
||||
runtime.tutorialFinalRoomClearPending = false
|
||||
runtime.bossDieCheckRelatedActorObjectKey = request.RelatedActorObjectKey
|
||||
runtime.bossDieCheckTargetObjectKey = request.TargetObjectKey
|
||||
runtime.bossDieCheckPending = false
|
||||
runtime.bossDieCheckPendingRequest = dungeoncmd.BossDieCheckRequest{}
|
||||
completed := runtime.Session.Snapshot()
|
||||
s.logGameEvent(session, "game-dungeon-boss-die-check-committed",
|
||||
"dungeon_id", runtime.Dungeon.ID,
|
||||
"maze_index", runtime.MazeIndex,
|
||||
"room", completed.Run.Current.String(),
|
||||
"map_id", scene.Map.Map.ID,
|
||||
"target_object_key", request.TargetObjectKey,
|
||||
"completion_source", completionSource,
|
||||
"cinematic_id", scriptEvidence.CinematicID,
|
||||
"cinematic_path", scriptEvidence.CinematicPath,
|
||||
"remaining_blocking_monster_indexes", remainingScriptedMonsterIndexes,
|
||||
"cinematic_monster_actor_indexes", scriptEvidence.MonsterActorIndexes,
|
||||
"cinematic_destroy_monster_indexes", scriptEvidence.DestroyMonsterIndexes,
|
||||
"run_status", completed.Run.Status,
|
||||
"defeated_actor_count", len(completed.Scene.DefeatedObjects),
|
||||
"remaining_actor_deaths_fabricated", false,
|
||||
"runtime_retained", true,
|
||||
"op115_sent", runtime.bossDieCheckResponseSent,
|
||||
"next_stage", "persist_completion_then_reply_op115_then_bodyless_settlement_op31")
|
||||
return s.completeCurrentDungeonBossDieCheckLocked(session, runtime, "op117_completion")
|
||||
}
|
||||
Reference in new issue
Block a user