Files
90CNServer/server/internal/services/dnfbridge/scene_actor_skill_resources.go
T
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

87 lines
2.6 KiB
Go

package dnfbridge
import (
"context"
"fmt"
"sort"
)
type currentActorSkillResource struct {
id uint16
rank byte
}
func (r *csharpLegacyUserInfoReader) currentActorSkillResources() ([]currentActorSkillResource, error) {
if r == nil || r.service == nil || r.characterID == "" {
return nil, nil
}
repos, ready := r.service.repositoryGroup()
if !ready || repos.Skill == nil {
return nil, nil
}
ctx := r.ctx
if ctx == nil {
ctx = context.Background()
}
record, found, err := repos.Skill.Load(ctx, r.characterID)
if err != nil {
return nil, err
}
if !found {
return nil, nil
}
rows := make([]currentActorSkillResource, 0, len(record.Skills))
for id, state := range record.Skills {
if !state.Enabled || state.Level <= 0 {
continue
}
if id < 0 || id > 0xffff || state.Level > 0xff {
return nil, fmt.Errorf("actor skill resource exceeds native u16/u8 range: skill=%d rank=%d", id, state.Level)
}
rows = append(rows, currentActorSkillResource{id: uint16(id), rank: byte(state.Level)})
}
if len(rows) > 0xff {
return nil, fmt.Errorf("actor skill resource count exceeds native u8 range: %d", len(rows))
}
sort.Slice(rows, func(i, j int) bool { return rows[i].id < rows[j].id })
return rows, nil
}
func (r *csharpLegacyUserInfoReader) writeCurrentActorSkillResources(w *packetWriter) {
if r != nil && r.suppressActorSkillResources {
w.writeByte(0xff)
for tree := 0; tree < currentSkillInfoTreeCount; tree++ {
w.writeByte(0)
}
if r.service != nil {
r.service.logGameEvent(r.session,
"game-actor-skill-resources-suppressed",
"character_id", r.characterID,
"tree_count", currentSkillInfoTreeCount,
"reason", "existing_actor_context_rebind_must_not_replay_learned_skill_resources")
}
return
}
rows, err := r.currentActorSkillResources()
if err != nil {
if r.service != nil {
r.service.logGameEvent(r.session, "game-actor-skill-resources-unavailable", "character_id", r.characterID, "error", err)
}
rows = nil
}
if len(rows) > 0 && r.service != nil {
r.service.logGameEvent(r.session, "game-actor-skill-resources-built", "character_id", r.characterID, "tree_count", currentSkillInfoTreeCount, "skill_ranks", fmt.Sprint(rows), "body_source", "mode1_native_u16_skill_u8_persisted_rank")
}
w.writeByte(0xff)
// Current EXE sub_2003420 consumes both groups even for the selected
// actor, but applies the u16 ID/u8 rank rows only to remote actors. As in
// op19, both current skill pages share this persisted learned-skill set.
for tree := 0; tree < currentSkillInfoTreeCount; tree++ {
w.writeByte(byte(len(rows)))
for _, row := range rows {
w.writeUint16(row.id)
w.writeByte(row.rank)
}
}
}