DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go): - GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面 - GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新 - 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新) - 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍 - 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0) - 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算 启动器(Python/JS): - 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭 - GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐) - 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
commit
645b64fe21
4388 files changed
+1405457
No files matched your search
@@ -0,0 +1,532 @@
|
||||
package worldmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTopologyResolverRequired = errors.New("dnf dungeon topology resolver is required")
|
||||
ErrTopologyEmpty = errors.New("dnf dungeon topology has no rooms")
|
||||
)
|
||||
|
||||
type RoomDirection string
|
||||
|
||||
const (
|
||||
RoomDirectionUp RoomDirection = "up"
|
||||
RoomDirectionRight RoomDirection = "right"
|
||||
RoomDirectionDown RoomDirection = "down"
|
||||
RoomDirectionLeft RoomDirection = "left"
|
||||
)
|
||||
|
||||
type RoomCoordinate struct {
|
||||
X int64 `json:"x"`
|
||||
Y int64 `json:"y"`
|
||||
}
|
||||
|
||||
func (c RoomCoordinate) String() string {
|
||||
return fmt.Sprintf("%d:%d", c.X, c.Y)
|
||||
}
|
||||
|
||||
type RoomNeighbor struct {
|
||||
Direction RoomDirection `json:"direction"`
|
||||
Coordinate RoomCoordinate `json:"coordinate"`
|
||||
}
|
||||
|
||||
type DungeonRoom struct {
|
||||
Coordinate RoomCoordinate `json:"coordinate"`
|
||||
Start bool `json:"start,omitempty"`
|
||||
Boss bool `json:"boss,omitempty"`
|
||||
Map *ResolvedMap `json:"map,omitempty"`
|
||||
ResolutionError string `json:"resolution_error,omitempty"`
|
||||
Neighbors []RoomNeighbor `json:"neighbors,omitempty"`
|
||||
}
|
||||
|
||||
type TopologyDiagnostic struct {
|
||||
Code string `json:"code"`
|
||||
Coordinate *RoomCoordinate `json:"coordinate,omitempty"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type DungeonTopology struct {
|
||||
DungeonID int64 `json:"dungeon_id"`
|
||||
MazeIndex int `json:"maze_index"`
|
||||
Width OptionalInt `json:"width"`
|
||||
Height OptionalInt `json:"height"`
|
||||
Start *RoomCoordinate `json:"start,omitempty"`
|
||||
Bosses []RoomCoordinate `json:"bosses,omitempty"`
|
||||
AllowMoveBeforeClear bool `json:"allow_move_before_clear,omitempty"`
|
||||
Diagnostics []TopologyDiagnostic `json:"diagnostics,omitempty"`
|
||||
|
||||
rooms map[coordinateKey]DungeonRoom
|
||||
resolutionErrors map[coordinateKey]error
|
||||
warpTransitions map[topologyTransition]struct{}
|
||||
resolver *Resolver
|
||||
}
|
||||
|
||||
type topologyTransition struct {
|
||||
from coordinateKey
|
||||
to coordinateKey
|
||||
}
|
||||
|
||||
type topologyDelta struct {
|
||||
direction RoomDirection
|
||||
dx int64
|
||||
dy int64
|
||||
mask uint8
|
||||
opposite uint8
|
||||
}
|
||||
|
||||
var topologyDeltas = [...]topologyDelta{
|
||||
{direction: RoomDirectionUp, dx: 0, dy: -1, mask: 0x02, opposite: 0x08},
|
||||
{direction: RoomDirectionRight, dx: 1, dy: 0, mask: 0x01, opposite: 0x04},
|
||||
{direction: RoomDirectionDown, dx: 0, dy: 1, mask: 0x08, opposite: 0x02},
|
||||
{direction: RoomDirectionLeft, dx: -1, dy: 0, mask: 0x04, opposite: 0x01},
|
||||
}
|
||||
|
||||
func BuildDungeonTopology(resolver *Resolver, dungeonID int64, mazeIndex int) (*DungeonTopology, error) {
|
||||
if resolver == nil || resolver.table == nil {
|
||||
return nil, ErrTopologyResolverRequired
|
||||
}
|
||||
dungeonIndex, ok := resolver.byDungeon[dungeonID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: dungeon=%d", ErrDungeonNotIndexed, dungeonID)
|
||||
}
|
||||
if mazeIndex < 0 || mazeIndex >= len(dungeonIndex.mazes) {
|
||||
return nil, fmt.Errorf("%w: dungeon=%d maze=%d", ErrMazeNotFound, dungeonID, mazeIndex)
|
||||
}
|
||||
|
||||
dungeon := resolver.table.dungeons[dungeonIndex.dungeonPos]
|
||||
maze := dungeon.Mazes[mazeIndex]
|
||||
topology := &DungeonTopology{
|
||||
DungeonID: dungeonID, MazeIndex: mazeIndex,
|
||||
Width: maze.Width, Height: maze.Height,
|
||||
AllowMoveBeforeClear: dungeon.Metadata.Flags["move map even enemy"],
|
||||
rooms: make(map[coordinateKey]DungeonRoom),
|
||||
resolutionErrors: make(map[coordinateKey]error),
|
||||
warpTransitions: make(map[topologyTransition]struct{}),
|
||||
resolver: resolver,
|
||||
}
|
||||
cells := make(map[coordinateKey]struct{})
|
||||
greedMasks, structuredGreed := addGreedCells(topology, maze, cells)
|
||||
addSpecificationCells(cells, maze.MapSpecifications)
|
||||
addSpecificationCells(cells, maze.BossSpecifications)
|
||||
addSpecificationCells(cells, maze.LayeredSpecifications)
|
||||
|
||||
if maze.Start != nil {
|
||||
start := RoomCoordinate{X: maze.Start.X, Y: maze.Start.Y}
|
||||
topology.Start = &start
|
||||
addExplicitCell(topology, maze, cells, start, "start_out_of_bounds")
|
||||
}
|
||||
for _, boss := range mazeBossCoordinates(maze.Boss) {
|
||||
topology.Bosses = append(topology.Bosses, boss)
|
||||
addExplicitCell(topology, maze, cells, boss, "boss_out_of_bounds")
|
||||
}
|
||||
warpConditions := maze.WarpMapConditions
|
||||
if len(warpConditions) == 0 {
|
||||
// Preserve programmatic/legacy tables that predate per-maze ownership.
|
||||
warpConditions = dungeon.WarpMapConditions
|
||||
}
|
||||
// A boss-entrance warp does not authorize travel to its destination
|
||||
// coordinate. Its source remains the logical room while the destination only
|
||||
// supplies a replacement map template after the condition is complete.
|
||||
if maze.BossRoomEntranceCondition == nil {
|
||||
addWarpTransitions(topology, maze, cells, warpConditions)
|
||||
}
|
||||
|
||||
if !structuredGreed {
|
||||
// The resolver already limits these paths to directories owned by this maze's
|
||||
// explicit specifications. Coordinates are data, not a map-selection fallback.
|
||||
for key := range dungeonIndex.mazes[mazeIndex].byCoordinate {
|
||||
coordinate := RoomCoordinate{X: key.x, Y: key.y}
|
||||
if withinMazeBounds(maze, coordinate) {
|
||||
cells[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cells) == 0 {
|
||||
return nil, fmt.Errorf("%w: dungeon=%d maze=%d", ErrTopologyEmpty, dungeonID, mazeIndex)
|
||||
}
|
||||
|
||||
bosses := make(map[coordinateKey]struct{}, len(topology.Bosses))
|
||||
for _, boss := range topology.Bosses {
|
||||
bosses[coordinateKey{x: boss.X, y: boss.Y}] = struct{}{}
|
||||
}
|
||||
for key := range cells {
|
||||
coordinate := RoomCoordinate{X: key.x, Y: key.y}
|
||||
room := DungeonRoom{Coordinate: coordinate}
|
||||
room.Start = topology.Start != nil && topology.Start.X == key.x && topology.Start.Y == key.y
|
||||
_, room.Boss = bosses[key]
|
||||
resolved, err := resolver.Resolve(ResolveRequest{
|
||||
DungeonID: dungeonID, MazeIndex: mazeIndex, X: key.x, Y: key.y,
|
||||
})
|
||||
if err != nil {
|
||||
room.ResolutionError = err.Error()
|
||||
topology.resolutionErrors[key] = err
|
||||
topology.addDiagnostic("room_map_unresolved", &coordinate, err.Error())
|
||||
} else {
|
||||
room.Map = &resolved
|
||||
}
|
||||
topology.rooms[key] = room
|
||||
}
|
||||
for key, room := range topology.rooms {
|
||||
for _, delta := range topologyDeltas {
|
||||
neighborKey := coordinateKey{x: key.x + delta.dx, y: key.y + delta.dy}
|
||||
if _, ok := topology.rooms[neighborKey]; !ok {
|
||||
continue
|
||||
}
|
||||
if structuredGreed {
|
||||
currentMask, currentInGrid := greedMasks[key]
|
||||
neighborMask, neighborInGrid := greedMasks[neighborKey]
|
||||
if !currentInGrid || !neighborInGrid || currentMask&delta.mask == 0 || neighborMask&delta.opposite == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
room.Neighbors = append(room.Neighbors, RoomNeighbor{
|
||||
Direction: delta.direction,
|
||||
Coordinate: RoomCoordinate{X: neighborKey.x, Y: neighborKey.y},
|
||||
})
|
||||
}
|
||||
topology.rooms[key] = room
|
||||
}
|
||||
return topology, nil
|
||||
}
|
||||
|
||||
func (t *DungeonTopology) allowsWarpTransition(from, to coordinateKey) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := t.warpTransitions[topologyTransition{from: from, to: to}]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (t *DungeonTopology) Room(coordinate RoomCoordinate) (DungeonRoom, bool) {
|
||||
if t == nil {
|
||||
return DungeonRoom{}, false
|
||||
}
|
||||
room, ok := t.rooms[coordinateKey{x: coordinate.X, y: coordinate.Y}]
|
||||
if !ok {
|
||||
return DungeonRoom{}, false
|
||||
}
|
||||
return cloneDungeonRoom(room), true
|
||||
}
|
||||
|
||||
// ReplaceRoomMap updates the resolved map for an existing coordinate while
|
||||
// preserving topology identity and adjacency.
|
||||
func (t *DungeonTopology) ReplaceRoomMap(coordinate RoomCoordinate, resolved ResolvedMap) error {
|
||||
if t == nil {
|
||||
return ErrTopologyEmpty
|
||||
}
|
||||
key := coordinateKey{x: coordinate.X, y: coordinate.Y}
|
||||
room, ok := t.rooms[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: room=%s", ErrMapUnresolved, coordinate)
|
||||
}
|
||||
if resolved.Map.ID <= 0 {
|
||||
return fmt.Errorf("%w: room=%s map=%d", ErrMapReferenceMissing, coordinate, resolved.Map.ID)
|
||||
}
|
||||
copy := resolved
|
||||
copy.Map = cloneMap(resolved.Map)
|
||||
room.Map = ©
|
||||
room.ResolutionError = ""
|
||||
t.rooms[key] = room
|
||||
delete(t.resolutionErrors, key)
|
||||
filtered := t.Diagnostics[:0]
|
||||
for _, diagnostic := range t.Diagnostics {
|
||||
if diagnostic.Code == "room_map_unresolved" && diagnostic.Coordinate != nil && *diagnostic.Coordinate == coordinate {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, diagnostic)
|
||||
}
|
||||
t.Diagnostics = filtered
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DungeonTopology) Rooms() []DungeonRoom {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
rooms := make([]DungeonRoom, 0, len(t.rooms))
|
||||
for _, room := range t.rooms {
|
||||
rooms = append(rooms, cloneDungeonRoom(room))
|
||||
}
|
||||
sort.Slice(rooms, func(i, j int) bool {
|
||||
if rooms[i].Coordinate.Y != rooms[j].Coordinate.Y {
|
||||
return rooms[i].Coordinate.Y < rooms[j].Coordinate.Y
|
||||
}
|
||||
return rooms[i].Coordinate.X < rooms[j].Coordinate.X
|
||||
})
|
||||
return rooms
|
||||
}
|
||||
|
||||
func (t *DungeonTopology) prepareRoom(room DungeonRoom) DungeonRoom {
|
||||
return cloneDungeonRoom(room)
|
||||
}
|
||||
|
||||
func (t *DungeonTopology) UnresolvedRooms() []DungeonRoom {
|
||||
rooms := t.Rooms()
|
||||
unresolved := rooms[:0]
|
||||
for _, room := range rooms {
|
||||
if room.Map == nil {
|
||||
unresolved = append(unresolved, room)
|
||||
}
|
||||
}
|
||||
return unresolved
|
||||
}
|
||||
|
||||
func (t *DungeonTopology) addDiagnostic(code string, coordinate *RoomCoordinate, message string) {
|
||||
var copied *RoomCoordinate
|
||||
if coordinate != nil {
|
||||
value := *coordinate
|
||||
copied = &value
|
||||
}
|
||||
t.Diagnostics = append(t.Diagnostics, TopologyDiagnostic{Code: code, Coordinate: copied, Message: message})
|
||||
}
|
||||
|
||||
func addGreedCells(
|
||||
topology *DungeonTopology,
|
||||
maze Maze,
|
||||
cells map[coordinateKey]struct{},
|
||||
) (map[coordinateKey]uint8, bool) {
|
||||
if maze.Greed == "" {
|
||||
return nil, false
|
||||
}
|
||||
if !maze.Width.Set || !maze.Height.Set || maze.Width.Value <= 0 || maze.Height.Value <= 0 {
|
||||
topology.addDiagnostic("maze_size_invalid", nil, "[greed] requires positive [size] width and height")
|
||||
return nil, false
|
||||
}
|
||||
rows := normalizedGreedRows(maze.Greed)
|
||||
if len(rows) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
paired := greedRowsArePaired(rows, maze.Width.Value)
|
||||
if int64(len(rows)) < maze.Height.Value {
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_rows_short", nil,
|
||||
fmt.Sprintf("[greed] has %d rows, [size] declares %d; omitted rows are empty", len(rows), maze.Height.Value),
|
||||
)
|
||||
} else if int64(len(rows)) > maze.Height.Value {
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_rows_extra", nil,
|
||||
fmt.Sprintf("[greed] has %d rows, [size] declares %d", len(rows), maze.Height.Value),
|
||||
)
|
||||
}
|
||||
masks := make(map[coordinateKey]uint8)
|
||||
rowCount := int64(len(rows))
|
||||
if rowCount > maze.Height.Value {
|
||||
rowCount = maze.Height.Value
|
||||
}
|
||||
for y := int64(0); y < rowCount; y++ {
|
||||
row := rows[y]
|
||||
unitWidth := int64(1)
|
||||
if paired {
|
||||
unitWidth = 2
|
||||
if len(row)%2 != 0 {
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_row_odd", &RoomCoordinate{X: int64(len(row) / 2), Y: y},
|
||||
fmt.Sprintf("[greed] row %d has an unmatched paired symbol", y),
|
||||
)
|
||||
}
|
||||
}
|
||||
logicalWidth := int64(len(row)) / unitWidth
|
||||
if logicalWidth < maze.Width.Value {
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_row_short", &RoomCoordinate{X: logicalWidth, Y: y},
|
||||
fmt.Sprintf("[greed] row %d has %d cells, [size] declares %d; omitted cells are empty", y, logicalWidth, maze.Width.Value),
|
||||
)
|
||||
} else if logicalWidth > maze.Width.Value {
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_row_extra", &RoomCoordinate{X: maze.Width.Value, Y: y},
|
||||
fmt.Sprintf("[greed] row %d has %d cells, [size] declares %d", y, logicalWidth, maze.Width.Value),
|
||||
)
|
||||
logicalWidth = maze.Width.Value
|
||||
}
|
||||
for x := int64(0); x < logicalWidth; x++ {
|
||||
valueIndex := x * unitWidth
|
||||
if paired {
|
||||
if valueIndex+1 >= int64(len(row)) {
|
||||
break
|
||||
}
|
||||
if row[valueIndex] != row[valueIndex+1] {
|
||||
coordinate := RoomCoordinate{X: x, Y: y}
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_pair_mismatch", &coordinate,
|
||||
fmt.Sprintf("[greed] pair %q%q does not repeat one room code", row[valueIndex], row[valueIndex+1]),
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
mask, ok := greedRoomMask(row[valueIndex])
|
||||
if !ok {
|
||||
coordinate := RoomCoordinate{X: x, Y: y}
|
||||
topology.addDiagnostic(
|
||||
"maze_grid_code_unknown", &coordinate,
|
||||
fmt.Sprintf("[greed] room code %q is unknown", row[valueIndex]),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if mask != 0 {
|
||||
key := coordinateKey{x: x, y: y}
|
||||
cells[key] = struct{}{}
|
||||
masks[key] = mask
|
||||
}
|
||||
}
|
||||
}
|
||||
return masks, true
|
||||
}
|
||||
|
||||
func normalizedGreedRows(value string) [][]rune {
|
||||
lines := strings.Split(strings.ReplaceAll(value, "\r\n", "\n"), "\n")
|
||||
rows := make([][]rune, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
row := make([]rune, 0, len(line))
|
||||
for _, symbol := range line {
|
||||
if unicode.IsSpace(symbol) || symbol == '`' || symbol == ',' {
|
||||
continue
|
||||
}
|
||||
row = append(row, symbol)
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
for len(rows) > 0 && len(rows[0]) == 0 {
|
||||
rows = rows[1:]
|
||||
}
|
||||
for len(rows) > 0 && len(rows[len(rows)-1]) == 0 {
|
||||
rows = rows[:len(rows)-1]
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func greedRowsArePaired(rows [][]rune, width int64) bool {
|
||||
allRepeated := true
|
||||
hasSymbols := false
|
||||
for _, row := range rows {
|
||||
if int64(len(row)) > width {
|
||||
return true
|
||||
}
|
||||
if len(row) == 0 {
|
||||
continue
|
||||
}
|
||||
hasSymbols = true
|
||||
if len(row)%2 != 0 {
|
||||
allRepeated = false
|
||||
continue
|
||||
}
|
||||
for index := 0; index < len(row); index += 2 {
|
||||
if row[index] != row[index+1] {
|
||||
allRepeated = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasSymbols && allRepeated
|
||||
}
|
||||
|
||||
func greedRoomMask(value rune) (uint8, bool) {
|
||||
if value >= 'a' && value <= 'p' {
|
||||
value -= 'a' - 'A'
|
||||
}
|
||||
if value >= 'A' && value <= 'P' {
|
||||
return uint8(value - 'A'), true
|
||||
}
|
||||
switch value {
|
||||
case '0', '.', 'x', 'X':
|
||||
return 0, true
|
||||
case '1':
|
||||
return 0x0f, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func addSpecificationCells(cells map[coordinateKey]struct{}, specifications []MapSpecification) {
|
||||
for _, specification := range specifications {
|
||||
cells[coordinateKey{x: specification.Coordinate.X, y: specification.Coordinate.Y}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func addWarpTransitions(
|
||||
topology *DungeonTopology,
|
||||
maze Maze,
|
||||
cells map[coordinateKey]struct{},
|
||||
conditions []WarpMapCondition,
|
||||
) {
|
||||
for _, condition := range conditions {
|
||||
source := RoomCoordinate{X: condition.Source.X, Y: condition.Source.Y}
|
||||
destination := RoomCoordinate{X: condition.Destination.X, Y: condition.Destination.Y}
|
||||
// Negative source coordinates are PVF mechanism sentinels. They are not
|
||||
// client-requestable maze rooms and must never become travel origins.
|
||||
if source.X < 0 || source.Y < 0 {
|
||||
continue
|
||||
}
|
||||
if !withinMazeBounds(maze, source) {
|
||||
topology.addDiagnostic(
|
||||
"warp_source_out_of_bounds", &source,
|
||||
fmt.Sprintf("warp source %s is outside declared maze size", source),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if destination.X < 0 || destination.Y < 0 || !withinMazeBounds(maze, destination) {
|
||||
topology.addDiagnostic(
|
||||
"warp_destination_out_of_bounds", &destination,
|
||||
fmt.Sprintf("warp destination %s is outside declared maze size", destination),
|
||||
)
|
||||
continue
|
||||
}
|
||||
sourceKey := coordinateKey{x: source.X, y: source.Y}
|
||||
destinationKey := coordinateKey{x: destination.X, y: destination.Y}
|
||||
cells[sourceKey] = struct{}{}
|
||||
cells[destinationKey] = struct{}{}
|
||||
topology.warpTransitions[topologyTransition{from: sourceKey, to: destinationKey}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func addExplicitCell(
|
||||
topology *DungeonTopology,
|
||||
maze Maze,
|
||||
cells map[coordinateKey]struct{},
|
||||
coordinate RoomCoordinate,
|
||||
diagnosticCode string,
|
||||
) {
|
||||
if !withinMazeBounds(maze, coordinate) {
|
||||
topology.addDiagnostic(
|
||||
diagnosticCode, &coordinate,
|
||||
fmt.Sprintf("coordinate %s is outside declared maze size", coordinate),
|
||||
)
|
||||
}
|
||||
cells[coordinateKey{x: coordinate.X, y: coordinate.Y}] = struct{}{}
|
||||
}
|
||||
|
||||
func withinMazeBounds(maze Maze, coordinate RoomCoordinate) bool {
|
||||
if maze.Width.Set && maze.Width.Value > 0 && (coordinate.X < 0 || coordinate.X >= maze.Width.Value) {
|
||||
return false
|
||||
}
|
||||
if maze.Height.Set && maze.Height.Value > 0 && (coordinate.Y < 0 || coordinate.Y >= maze.Height.Value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func mazeBossCoordinates(point *MazePoint) []RoomCoordinate {
|
||||
if point == nil {
|
||||
return nil
|
||||
}
|
||||
coordinates := []RoomCoordinate{{X: point.X, Y: point.Y}}
|
||||
for index := 0; index+1 < len(point.Params); index += 2 {
|
||||
coordinates = append(coordinates, RoomCoordinate{X: point.Params[index], Y: point.Params[index+1]})
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
|
||||
func cloneDungeonRoom(room DungeonRoom) DungeonRoom {
|
||||
room.Neighbors = append([]RoomNeighbor(nil), room.Neighbors...)
|
||||
if room.Map != nil {
|
||||
resolved := *room.Map
|
||||
resolved.Map = cloneMap(resolved.Map)
|
||||
room.Map = &resolved
|
||||
}
|
||||
return room
|
||||
}
|
||||
Reference in new issue
Block a user