DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

@@ -0,0 +1,166 @@
package dnfbridge
import (
"golang.org/x/text/encoding/simplifiedchinese"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
)
func (r *csharpLegacyUserInfoReader) buildCurrentUserInfoMode1StatBlob(row dnfrepo.LegacyUserInfoRow, character dnfrepo.CharacterRecord) []byte {
var w packetWriter
/*
// MCP: 女鬼剑 actor 虚函数 sub_24A5B20 读取当前 EXE 的 92 字节块:
// +0/+4 为 dword,+8..+14 为 4 个 u16,+16 起 4 个 s16,+24 起 18 个 s16,后段固定读取 +60/+64/+66/+68/+72/+74/+76/+78/+80/+84/+85。
hpMax := rowStatU32NonZero(row, character, "stat_hp_max", 11600)
*/
// Current EXE sub_3841230 expands this 92-byte actor state and sub_1D1EDE0
// maps +8..+14 to strength, vitality, intelligence, and spirit. The next
// four s16 fields are elemental resistances, followed by 18 status
// resistances and the inventory/speed/weight fields.
hpMax := r.rowStatU32Real(row, character, "stat_hp_max")
mpMax := r.rowStatU32Real(row, character, "stat_mp_max")
w.writeUint32(hpMax)
w.writeUint32(mpMax)
for _, value := range []int{
r.rowStatIntReal(row, character, "stat_strength"),
r.rowStatIntReal(row, character, "stat_vitality"),
r.rowStatIntReal(row, character, "stat_intelligence"),
r.rowStatIntReal(row, character, "stat_spirit"),
r.rowStatIntReal(row, character, "stat_fire_resistance"),
r.rowStatIntReal(row, character, "stat_water_resistance"),
r.rowStatIntReal(row, character, "stat_dark_resistance"),
r.rowStatIntReal(row, character, "stat_light_resistance"),
} {
writeLegacyInt16(&w, currentActorStatS16Wire(value))
}
for idx := 0; idx < 18; idx++ {
value := int(rowStatU16(row, character, "active_status_resistance_"+twoDigit(idx), 0))
writeLegacyInt16(&w, currentActorStatS16Wire(value))
}
w.writeUint32(r.rowStatU32Real(row, character, "stat_inventory_limit"))
w.writeUint16(r.rowStatU16Real(row, character, "stat_hp_regen_speed"))
w.writeUint16(r.rowStatU16Real(row, character, "stat_mp_regen_speed"))
w.writeUint32(r.rowStatU32Real(row, character, "stat_move_speed"))
w.writeUint16(r.rowStatU16Real(row, character, "stat_attack_speed"))
w.writeUint16(r.rowStatU16Real(row, character, "stat_cast_speed"))
w.writeUint16(r.rowStatU16Real(row, character, "stat_hit_recovery"))
w.writeUint16(r.rowStatU16Real(row, character, "stat_jump_power"))
w.writeUint32(r.rowStatU32Real(row, character, "stat_weight"))
// Current NoPack sub_3841230 copies +0x54 to actor+0x5F04. sub_24D5640
// consumes it as the base-stat percentage: four dimensions use
// percentage*wire/1000 and current HP uses percentage*maxHP/100. The
// neutral protocol value is 100; actor level comes from the separate mode0
// level byte propagated into ability context 3.
w.writeByte(currentActorBaseStatScalePercent)
// Current NoPack sub_3841230 copies source +0x55 into the actor state, and
// sub_24A5B20 consumes it as a float scaled by 0.1. It is not the durable
// ex_equip_slot_stat byte carried by the selected-character subtype1 body.
// Keep this unresolved runtime float neutral instead of leaking slot bits.
w.writeUint32(0)
out := w.bytes()
switch {
case len(out) < currentMode1StatBlobWireSize:
padded := make([]byte, currentMode1StatBlobWireSize)
copy(padded, out)
return padded
case len(out) > currentMode1StatBlobWireSize:
return append([]byte(nil), out[:currentMode1StatBlobWireSize]...)
default:
return out
}
}
func currentActorStatS16Wire(value int) int {
scaled := value * 10
switch {
case scaled > 32767:
return 32767
case scaled < -32768:
return -32768
default:
return scaled
}
}
func clampUint16ForUserInfo(value uint32) uint16 {
if value > 0xffff {
return 0xffff
}
return uint16(value)
}
func csharpUserInfoSubtype1Columns() []string {
columns := []string{
"stat_hp_max", "stat_mp_max", "stat_strength", "stat_intelligence", "stat_vitality", "stat_spirit",
"stat_physical_attack", "stat_physical_defense", "stat_magical_attack", "stat_magical_defense",
"stat_independent_attack", "stat_fire_resistance", "stat_water_resistance", "stat_dark_resistance", "stat_light_resistance",
}
for idx := 0; idx < 17; idx++ {
columns = append(columns, "active_status_resistance_"+twoDigit(idx))
}
columns = append(columns,
"stat_inventory_limit", "stat_hp_regen_speed", "stat_mp_regen_speed", "stat_move_speed",
"stat_attack_speed", "stat_cast_speed", "stat_hit_recovery", "stat_jump_power", "stat_weight",
"stat_level", "name_tag_item_id", "name_tag_expire_time", "skill_tree_index",
"equipped_creature_level", "equip_list_trailing", "manage_level", "flag_byte",
"guild_power_war", "server_timestamp", "quest_shop_count", "progress1", "progress2")
return columns
}
func writeClientNameDstr(w *packetWriter, name string) {
if encoded, err := simplifiedchinese.GB18030.NewEncoder().Bytes([]byte(name)); err == nil {
w.writeRawDstr(encoded)
return
}
w.writeRawDstr([]byte(name))
}
func writeFixedBytes(w *packetWriter, data []byte, size int) {
for idx := 0; idx < size; idx++ {
var value byte
if idx < len(data) {
value = data[idx]
}
w.writeByte(value)
}
}
func writeLegacyInt16(w *packetWriter, value int) {
w.writeUint16(uint16(int16(value)))
}
func (r *csharpLegacyUserInfoReader) rowStatU16Real(row dnfrepo.LegacyUserInfoRow, character dnfrepo.CharacterRecord, column string) uint16 {
value, ok := r.realStatInt64(row, character, column)
if !ok || value <= 0 {
return 0
}
if value > 0xffff {
return 0xffff
}
return uint16(value)
}
func (r *csharpLegacyUserInfoReader) rowStatU32Real(row dnfrepo.LegacyUserInfoRow, character dnfrepo.CharacterRecord, column string) uint32 {
value, ok := r.realStatInt64(row, character, column)
if !ok || value <= 0 {
return 0
}
if value > 0xffffffff {
return 0xffffffff
}
return uint32(value)
}
func (r *csharpLegacyUserInfoReader) rowStatIntReal(row dnfrepo.LegacyUserInfoRow, character dnfrepo.CharacterRecord, column string) int {
value, ok := r.realStatInt64(row, character, column)
if !ok {
return 0
}
if value > int64(^uint(0)>>1) {
return int(^uint(0) >> 1)
}
if value < -int64(^uint(0)>>1)-1 {
return -int(^uint(0)>>1) - 1
}
return int(value)
}