Files
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

115 lines
3.0 KiB
Go

package dnfbridge
import (
"fmt"
"golang.org/x/text/encoding/simplifiedchinese"
)
const currentActivityCalendarMsgID uint16 = 0x006c
type activityCalendarRecord struct {
eventID uint16
field0 uint32
baseText0 []byte
baseText1 []byte
extension bool
text3 string
}
func currentActivityCalendarRecords() []activityCalendarRecord {
return []activityCalendarRecord{
{eventID: 181, field0: 0xff6e00ff, extension: true, text3: "ui/event/calendar/2awakeninglevel.xui/2/107&1"},
{eventID: 192},
{eventID: 200},
{eventID: 145},
{eventID: 143},
{eventID: 202},
{eventID: 457},
}
}
func writeCurrentActivityCalendarRecord(writer *packetWriter, record activityCalendarRecord) error {
if writer == nil {
return fmt.Errorf("activity calendar writer is nil")
}
writer.writeUint16(record.eventID)
writer.writeUint32(record.field0)
writer.writeRawDstr(record.baseText0)
writer.writeRawDstr(record.baseText1)
if !record.extension {
writer.writeByte(0)
return nil
}
text3, err := encodeCurrentActivityCalendarText(record.text3)
if err != nil {
return err
}
writer.writeByte(1)
writer.writeByte(0)
writer.writeByte(0)
writer.writeRawDstr(nil)
writer.writeRawDstr(nil)
writer.writeRawDstr(nil)
writer.writeUint32(0)
writer.writeUint32(0)
writer.writeRawDstr(text3)
writer.writeRawDstr(nil)
writer.writeByte(0)
return nil
}
func encodeCurrentActivityCalendarText(value string) ([]byte, error) {
if value == "" {
return nil, nil
}
encoded, err := simplifiedchinese.GB18030.NewEncoder().Bytes([]byte(value))
if err != nil {
return nil, fmt.Errorf("encode activity calendar text %q as GB18030: %w", value, err)
}
return encoded, nil
}
func buildCurrentActivityCalendarBody() ([]byte, error) {
records := currentActivityCalendarRecords()
var writer packetWriter
writer.writeUint16(uint16(len(records)))
for _, record := range records {
if err := writeCurrentActivityCalendarRecord(&writer, record); err != nil {
return nil, err
}
}
writer.writeByte(0)
return writer.bytes(), nil
}
func (s *Service) sendCurrentActivityCalendar(session *gameSession, source string) error {
if s == nil || s.options.pvfPath == "" {
// Unit/test-only services without a PVF source do not own the client
// event table. Production always supplies env.PVF or a configured path.
return nil
}
body, err := buildCurrentActivityCalendarBody()
if err != nil {
return err
}
if len(body) != 184 {
return fmt.Errorf("activity calendar body length=%d want=184", len(body))
}
// The calendar has its own class-0/op0x006C owner. Keep it separate from
// compressed op108, whose parser installs cumulative-online event state.
s.logGameEvent(session, "game-activity-calendar-standalone-send",
"source", source,
"msg_id", currentActivityCalendarMsgID,
"record_count", len(currentActivityCalendarRecords()),
"body_len", len(body),
"transport", "class0_protected_marker1_plaintext")
return s.sendCurrentProtectedClass0Packet(
session,
currentActivityCalendarMsgID,
body,
"current_activity_calendar_plaintext",
"activity_calendar",
)
}