服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
115 lines
3.0 KiB
Go
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",
|
|
)
|
|
}
|