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,99 @@
package hellparty
import (
"errors"
"fmt"
"longheng.io/server/internal/modules/dnf/worldmap"
)
var (
ErrRoomUnavailable = errors.New("dnf hell party room is unavailable")
ErrRoomInvalid = errors.New("dnf hell party room is invalid")
)
type WorldSource interface {
FindDungeon(id int64) (worldmap.Dungeon, bool)
FindMap(id int64) (worldmap.Map, bool)
}
type Pillar struct {
ObjectID int64
X int64
Y int64
Flags int64
}
type Room struct {
Coordinate worldmap.RoomCoordinate
HellMap worldmap.Map
NormalMap worldmap.Map
Pillar Pillar
Entries []MapEntry
}
func ResolveRoom(source WorldSource, topology *worldmap.DungeonTopology, dungeonID int64, mazeIndex int) (Room, error) {
if source == nil || topology == nil {
return Room{}, fmt.Errorf("%w: source or topology is nil", ErrRoomUnavailable)
}
dungeon, ok := source.FindDungeon(dungeonID)
if !ok || mazeIndex < 0 || mazeIndex >= len(dungeon.Mazes) {
return Room{}, fmt.Errorf("%w: dungeon=%d maze=%d", ErrRoomUnavailable, dungeonID, mazeIndex)
}
maze := dungeon.Mazes[mazeIndex]
if !maze.SealDoorMapIndex.Set || maze.SealDoorMapIndex.Value <= 0 || len(maze.SealDoorPosition) != 2 {
return Room{}, fmt.Errorf("%w: dungeon=%d maze=%d has no exact seal-door map/position", ErrRoomUnavailable, dungeonID, mazeIndex)
}
coordinate := worldmap.RoomCoordinate{X: maze.SealDoorPosition[0], Y: maze.SealDoorPosition[1]}
topologyRoom, ok := topology.Room(coordinate)
if !ok || topologyRoom.Map == nil {
return Room{}, fmt.Errorf("%w: seal-door room=%s is absent or unresolved", ErrRoomInvalid, coordinate)
}
if topologyRoom.Boss {
return Room{}, fmt.Errorf("%w: seal-door room=%s is a boss room", ErrRoomInvalid, coordinate)
}
hellMap, ok := source.FindMap(maze.SealDoorMapIndex.Value)
if !ok {
return Room{}, fmt.Errorf("%w: seal-door map=%d is absent", ErrRoomInvalid, maze.SealDoorMapIndex.Value)
}
room := Room{Coordinate: coordinate, HellMap: hellMap, NormalMap: topologyRoom.Map.Map}
for _, object := range hellMap.SpecialPassiveObjects {
if len(object.HellParty) == 0 {
continue
}
if room.Pillar.ObjectID == 0 {
room.Pillar = Pillar{
ObjectID: object.ObjectID,
X: object.X,
Y: object.Y,
Flags: object.Flags,
}
}
for _, entry := range object.HellParty {
if entry.GroupID <= 0 || entry.GroupID > int64(^uint32(0)) ||
entry.Rate < 0 || entry.Rate > int64(^uint32(0)) ||
entry.Order <= 0 || entry.Order > int64(^uint32(0)) {
return Room{}, fmt.Errorf("%w: map=%d hellparty entry=%+v", ErrRoomInvalid, hellMap.ID, entry)
}
room.Entries = append(room.Entries, MapEntry{
GroupID: uint32(entry.GroupID),
Rate: uint32(entry.Rate),
Order: uint32(entry.Order),
})
}
}
if room.Pillar.ObjectID == 0 || len(room.Entries) == 0 {
return Room{}, fmt.Errorf("%w: seal-door map=%d has no [hellparty] pillar", ErrRoomInvalid, hellMap.ID)
}
return room, nil
}
func ApplyRoom(topology *worldmap.DungeonTopology, room Room) error {
if topology == nil || room.HellMap.ID <= 0 {
return fmt.Errorf("%w: topology or Hell map is empty", ErrRoomInvalid)
}
return topology.ReplaceRoomMap(room.Coordinate, worldmap.ResolvedMap{
Map: room.HellMap,
Source: worldmap.ResolutionHellParty,
})
}