diff --git a/d2common/d2fileformats/d2ds1/tile_record.go b/d2common/d2fileformats/d2ds1/tile_record.go index e8699f8e..1e560f92 100644 --- a/d2common/d2fileformats/d2ds1/tile_record.go +++ b/d2common/d2fileformats/d2ds1/tile_record.go @@ -1,16 +1,9 @@ package d2ds1 -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - // TileRecord represents a tile record in a DS1 file. type TileRecord struct { Floors []FloorShadowRecord // Collection of floor records Walls []WallRecord // Collection of wall records Shadows []FloorShadowRecord // Collection of shadow records Substitutions []SubstitutionRecord // Collection of substitutions - - // This is set and used internally by the engine to determine what region this map is from - RegionType d2enum.RegionIdType } diff --git a/d2core/d2map/d2maprenderer/gfx_decode.go b/d2common/d2fileformats/d2dt1/gfx_decode.go similarity index 81% rename from d2core/d2map/d2maprenderer/gfx_decode.go rename to d2common/d2fileformats/d2dt1/gfx_decode.go index eb65a6b9..acf78645 100644 --- a/d2core/d2map/d2maprenderer/gfx_decode.go +++ b/d2common/d2fileformats/d2dt1/gfx_decode.go @@ -1,10 +1,8 @@ -package d2maprenderer +package d2dt1 -import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1" - -func (mr *MapRenderer) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) { +func DecodeTileGfxData(blocks []Block, pixels *[]byte, tileYOffset int32, tileWidth int32) { for _, block := range blocks { - if block.Format == d2dt1.BlockFormatIsometric { + if block.Format == BlockFormatIsometric { // 3D isometric decoding xjump := []int32{14, 12, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 12, 14} nbpix := []int32{4, 8, 12, 16, 20, 24, 28, 32, 28, 24, 20, 16, 12, 8, 4} diff --git a/d2common/d2fileformats/d2dt1/subtile.go b/d2common/d2fileformats/d2dt1/subtile.go index 6a98ddb1..1e048837 100644 --- a/d2common/d2fileformats/d2dt1/subtile.go +++ b/d2common/d2fileformats/d2dt1/subtile.go @@ -12,6 +12,18 @@ type SubTileFlags struct { Unknown3 bool } +// Combine combines a second set of flags into the current one +func (s *SubTileFlags) Combine(f SubTileFlags) { + s.BlockWalk = s.BlockWalk || f.BlockWalk + s.BlockLOS = s.BlockLOS || f.BlockLOS + s.BlockJump = s.BlockJump || f.BlockJump + s.BlockPlayerWalk = s.BlockPlayerWalk || f.BlockPlayerWalk + s.Unknown1 = s.Unknown1 || f.Unknown1 + s.BlockLight = s.BlockLight || f.BlockLight + s.Unknown2 = s.Unknown2 || f.Unknown2 + s.Unknown3 = s.Unknown3 || f.Unknown3 +} + // DebugString returns the debug string func (s *SubTileFlags) DebugString() string { result := "" diff --git a/d2common/d2fileformats/d2dt1/tile.go b/d2common/d2fileformats/d2dt1/tile.go index 0b85f297..f0b61c3a 100644 --- a/d2common/d2fileformats/d2dt1/tile.go +++ b/d2common/d2fileformats/d2dt1/tile.go @@ -16,16 +16,3 @@ type Tile struct { blockHeaderSize int32 Blocks []Block } - -// GetSubTileFlags returns the tile flags for the given subtile -func (t *Tile) GetSubTileFlags(x, y int) *SubTileFlags { - var subtileLookup = [5][5]int{ - {20, 21, 22, 23, 24}, - {15, 16, 17, 18, 19}, - {10, 11, 12, 13, 14}, - {5, 6, 7, 8, 9}, - {0, 1, 2, 3, 4}, - } - - return &t.SubTileFlags[subtileLookup[y][x]] -} diff --git a/d2common/d2math/d2vector/position.go b/d2common/d2math/d2vector/position.go index 437d9c5e..75795cb7 100644 --- a/d2common/d2math/d2vector/position.go +++ b/d2common/d2math/d2vector/position.go @@ -71,7 +71,7 @@ func (p *Position) RenderOffset() *Vector { return p.subTileOffset().AddScalar(1) } -// SubTileOffset is the offset from the current map tile in sub tiles. +// subTileOffset is the offset from the current map tile in sub tiles. func (p *Position) subTileOffset() *Vector { t := p.Tile().Scale(subTilesPerTile) c := p.Clone() diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index a1bca0b6..1d2813af 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -20,13 +20,12 @@ import ( // MapEngine loads the tiles which make up the isometric map and the entities type MapEngine struct { - seed int64 // The map seed - entities []d2interface.MapEntity // Entities on the map - tiles []d2ds1.TileRecord // Map tiles + seed int64 // The map seed + entities []d2interface.MapEntity // Entities on the map + tiles []MapTile size d2common.Size // Size of the map, in tiles levelType d2datadict.LevelTypeRecord // Level type of this map dt1TileData []d2dt1.Tile // DT1 tile data - walkMesh []d2common.PathTile // Sub tiles representing the walkable map area startSubTileX int // Starting X position startSubTileY int // Starting Y position dt1Files []string // List of DS1 strings @@ -39,11 +38,6 @@ func CreateMapEngine() *MapEngine { return engine } -// WalkMesh returns a pointer to a slice with the map's PathTiles. -func (m *MapEngine) WalkMesh() *[]d2common.PathTile { - return &m.walkMesh -} - // GetStartingPosition returns the starting position on the map in // sub-tiles. func (m *MapEngine) GetStartingPosition() (int, int) { @@ -56,9 +50,8 @@ func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) { m.entities = make([]d2interface.MapEntity, 0) m.levelType = d2datadict.LevelTypes[levelType] m.size = d2common.Size{Width: width, Height: height} - m.tiles = make([]d2ds1.TileRecord, width*height) + m.tiles = make([]MapTile, width*height) m.dt1TileData = make([]d2dt1.Tile, 0) - m.walkMesh = make([]d2common.PathTile, width*height*25) m.dt1Files = make([]string, 0) for idx := range m.levelType.Files { @@ -67,7 +60,7 @@ func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) { } func (m *MapEngine) addDT1(fileName string) { - if len(fileName) == 0 || fileName == "0" { + if fileName == "" || fileName == "0" { return } @@ -81,8 +74,8 @@ func (m *MapEngine) addDT1(fileName string) { fileData, err := d2asset.LoadFile("/data/global/tiles/" + fileName) if err != nil { log.Printf("Could not load /data/global/tiles/%s", fileName) - return // panic(err) + return } dt1, _ := d2dt1.LoadDT1(fileData) @@ -94,7 +87,7 @@ func (m *MapEngine) addDT1(fileName string) { // appends the tile data and files to MapEngine.dt1TileData and // MapEngine.dt1Files. func (m *MapEngine) AddDS1(fileName string) { - if len(fileName) == 0 || fileName == "0" { + if fileName == "" || fileName == "0" { return } @@ -108,24 +101,13 @@ func (m *MapEngine) AddDS1(fileName string) { for idx := range ds1.Files { dt1File := ds1.Files[idx] dt1File = strings.ToLower(dt1File) - dt1File = strings.Replace(dt1File, "c:", "", -1) // Yes they did... - dt1File = strings.Replace(dt1File, ".tg1", ".dt1", -1) // Yes they did... - dt1File = strings.Replace(dt1File, "\\d2\\data\\global\\tiles\\", "", -1) - m.addDT1(strings.Replace(dt1File, "\\", "/", -1)) + dt1File = strings.ReplaceAll(dt1File, "c:", "") // Yes they did... + dt1File = strings.ReplaceAll(dt1File, ".tg1", ".dt1") // Yes they did... + dt1File = strings.ReplaceAll(dt1File, "\\d2\\data\\global\\tiles\\", "") + m.addDT1(strings.ReplaceAll(dt1File, "\\", "/")) } } -// FindTile returns the tile of given stye, sequence and tileType. -func (m *MapEngine) FindTile(style, sequence, tileType int32) d2dt1.Tile { - for idx := range m.dt1TileData { - if m.dt1TileData[idx].Style == style && m.dt1TileData[idx].Sequence == sequence && m.dt1TileData[idx].Type == tileType { - return m.dt1TileData[idx] - } - } - - panic("Could not find the requested tile!") -} - // LevelType returns the level type of this map. func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord { return m.levelType @@ -144,13 +126,13 @@ func (m *MapEngine) Size() d2common.Size { // Tile returns the TileRecord containing the data // for a single map tile. -func (m *MapEngine) Tile(x, y int) *d2ds1.TileRecord { +func (m *MapEngine) Tile(x, y int) *MapTile { return &m.tiles[x+(y*m.size.Width)] } // Tiles returns a pointer to a slice contaning all // map tile data. -func (m *MapEngine) Tiles() *[]d2ds1.TileRecord { +func (m *MapEngine) Tiles() *[]MapTile { return &m.tiles } @@ -178,7 +160,9 @@ func (m *MapEngine) PlaceStamp(stamp *d2mapstamp.Stamp, tileOffsetX, tileOffsetY for x := 0; x < stampW; x++ { targetTileIndex := m.tileCoordinateToIndex((x + xMin), (y + yMin)) stampTile := *stamp.Tile(x, y) - m.tiles[targetTileIndex] = stampTile + m.tiles[targetTileIndex].RegionType = stamp.RegionID() + m.tiles[targetTileIndex].Components = stampTile + m.tiles[targetTileIndex].PrepareTile(x, y, m) } } @@ -196,9 +180,16 @@ func (m *MapEngine) tileIndexToCoordinate(index int) (int, int) { return (index % m.size.Width), (index / m.size.Width) } +// SubTileAt gets the flags for the given subtile +func (m *MapEngine) SubTileAt(subX, subY int) *d2dt1.SubTileFlags { + tile := m.TileAt(subX/5, subY/5) + + return tile.GetSubTileFlags(subX%5, subY%5) +} + // TileAt returns a pointer to the data for the map tile at the given // x and y index. -func (m *MapEngine) TileAt(tileX, tileY int) *d2ds1.TileRecord { +func (m *MapEngine) TileAt(tileX, tileY int) *MapTile { idx := m.tileCoordinateToIndex(tileX, tileY) if idx < 0 || idx >= len(m.tiles) { return nil @@ -232,11 +223,12 @@ func (m *MapEngine) RemoveEntity(entity d2interface.MapEntity) { // GetTiles returns a slice of all tiles matching the given style, // sequence and tileType. -func (m *MapEngine) GetTiles(style, sequence, tileType int32) []d2dt1.Tile { +func (m *MapEngine) GetTiles(style, sequence, tileType int) []d2dt1.Tile { var tiles []d2dt1.Tile for idx := range m.dt1TileData { - if m.dt1TileData[idx].Style != style || m.dt1TileData[idx].Sequence != sequence || m.dt1TileData[idx].Type != tileType { + if m.dt1TileData[idx].Style != int32(style) || m.dt1TileData[idx].Sequence != int32(sequence) || + m.dt1TileData[idx].Type != int32(tileType) { continue } @@ -255,7 +247,7 @@ func (m *MapEngine) GetTiles(style, sequence, tileType int32) []d2dt1.Tile { func (m *MapEngine) GetStartPosition() (float64, float64) { for tileY := 0; tileY < m.size.Height; tileY++ { for tileX := 0; tileX < m.size.Width; tileX++ { - tile := m.tiles[tileX+(tileY*m.size.Width)] + tile := m.tiles[tileX+(tileY*m.size.Width)].Components for idx := range tile.Walls { if tile.Walls[idx].Type.Special() && tile.Walls[idx].Style == 30 { return float64(tileX) + 0.5, float64(tileY) + 0.5 @@ -285,7 +277,7 @@ func (m *MapEngine) TileExists(tileX, tileY int) bool { tileIndex := m.tileCoordinateToIndex(tileX, tileY) if valid := (tileIndex >= 0) && (tileIndex <= len(m.tiles)); valid { - tile := m.tiles[tileIndex] + tile := m.tiles[tileIndex].Components numFeatures := len(tile.Floors) numFeatures += len(tile.Shadows) numFeatures += len(tile.Walls) @@ -305,10 +297,11 @@ func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, m.PlaceStamp(region, 0, 0) } -// GetTileData returns the tile with the given style, sequence and tileType. -func (m *MapEngine) GetTileData(style int32, sequence int32, tileType d2enum.TileType) *d2dt1.Tile { +// GetTileData returns the tile with the given style, sequence, tileType and index. +func (m *MapEngine) GetTileData(style, sequence int, tileType d2enum.TileType, index byte) *d2dt1.Tile { for idx := range m.dt1TileData { - if m.dt1TileData[idx].Style == style && m.dt1TileData[idx].Sequence == sequence && m.dt1TileData[idx].Type == int32(tileType) { + if m.dt1TileData[idx].Style == int32(style) && m.dt1TileData[idx].Sequence == int32(sequence) && + m.dt1TileData[idx].Type == int32(tileType) && m.dt1TileData[idx].RarityFrameIndex == int32(index) { return &m.dt1TileData[idx] } } diff --git a/d2core/d2map/d2mapengine/map_tile.go b/d2core/d2map/d2mapengine/map_tile.go new file mode 100644 index 00000000..26e5aee7 --- /dev/null +++ b/d2core/d2map/d2mapengine/map_tile.go @@ -0,0 +1,118 @@ +package d2mapengine + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1" +) + +// MapTile is a tile placed on the map +type MapTile struct { + Components d2ds1.TileRecord + RegionType d2enum.RegionIdType + SubTiles [25]d2dt1.SubTileFlags +} + +// GetSubTileFlags returns the tile flags for the given subtile +func (t *MapTile) GetSubTileFlags(x, y int) *d2dt1.SubTileFlags { + var subtileLookup = [5][5]int{ + {20, 21, 22, 23, 24}, + {15, 16, 17, 18, 19}, + {10, 11, 12, 13, 14}, + {5, 6, 7, 8, 9}, + {0, 1, 2, 3, 4}, + } + + return &t.SubTiles[subtileLookup[y][x]] +} + +// PrepareTile selects which graphic to use and updates the tiles subtileflags +func (t *MapTile) PrepareTile(x, y int, me *MapEngine) { + for wIdx := range t.Components.Walls { + wall := &t.Components.Walls[wIdx] + options := me.GetTiles(int(wall.Style), int(wall.Sequence), int(wall.Type)) + + if options == nil { + break + } + + wall.RandomIndex = getRandomTile(options, x, y, me.seed) + + for i := range t.SubTiles { + t.SubTiles[i].Combine(options[wall.RandomIndex].SubTileFlags[i]) + } + } + + for fIdx := range t.Components.Floors { + floor := &t.Components.Floors[fIdx] + options := me.GetTiles(int(floor.Style), int(floor.Sequence), 0) + + if options == nil { + break + } + + if options[0].MaterialFlags.Lava { + floor.Animated = true + floor.RandomIndex = 0 + } else { + floor.RandomIndex = getRandomTile(options, x, y, me.seed) + } + + for i := range t.SubTiles { + t.SubTiles[i].Combine(options[floor.RandomIndex].SubTileFlags[i]) + } + } + + for sIdx := range t.Components.Shadows { + shadow := &t.Components.Shadows[sIdx] + options := me.GetTiles(int(shadow.Style), int(shadow.Sequence), 13) + + if options == nil { + break + } + + shadow.RandomIndex = getRandomTile(options, x, y, me.seed) + + for i := range t.SubTiles { + t.SubTiles[i].Combine(options[shadow.RandomIndex].SubTileFlags[i]) + } + } +} + +// Selects a random tile from the slice, rest of args just used for seeding +func getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) byte { + /* Walker's Alias Method for weighted random selection + * with xorshifting for random numbers */ + + var tileSeed uint64 + tileSeed = uint64(seed) + uint64(x) + tileSeed *= uint64(y) + + tileSeed ^= tileSeed << 13 + tileSeed ^= tileSeed >> 17 + tileSeed ^= tileSeed << 5 + + weightSum := 0 + + for i := range tiles { + weightSum += int(tiles[i].RarityFrameIndex) + } + + if weightSum == 0 { + return 0 + } + + random := tileSeed % uint64(weightSum) + + sum := 0 + + for i := range tiles { + sum += int(tiles[i].RarityFrameIndex) + if sum >= int(random) { + return byte(i) + } + } + + // This return shouldn't be hit + return 0 +} diff --git a/d2core/d2map/d2mapengine/pathfind.go b/d2core/d2map/d2mapengine/pathfind.go new file mode 100644 index 00000000..47fa80cf --- /dev/null +++ b/d2core/d2map/d2mapengine/pathfind.go @@ -0,0 +1,47 @@ +package d2mapengine + +import ( + "math" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" +) + +func (m *MapEngine) PathFind(start, dest d2vector.Position) []d2vector.Position { + points := make([]d2vector.Position, 0) + _, point := m.checkLos(start, dest) + points = append(points, point) + + return points +} + +// checkLos finds out if there is a clear line of sight between two points +func (m *MapEngine) checkLos(start, end d2vector.Position) (bool, d2vector.Position) { + dv := d2vector.Position{Vector: end.Clone()} + dv.Subtract(&start.Vector) + dx := dv.X() + dy := dv.Y() + N := math.Max(math.Abs(dx), math.Abs(dy)) + + var divN float64 + if N == 0 { + divN = 0.0 + } else { + divN = 1.0 / N + } + + xstep := dx * divN + ystep := dy * divN + x := start.X() + y := start.Y() + + for i := 0; i <= int(N); i++ { + x += xstep + y += ystep + + if m.SubTileAt(int(math.Floor(x)), int(math.Floor(y))).BlockWalk { + return false, d2vector.NewPosition(x-xstep, y-ystep) + } + } + + return true, end +} diff --git a/d2core/d2map/d2mapengine/walk_mesh.go b/d2core/d2map/d2mapengine/walk_mesh.go deleted file mode 100644 index 93425b9c..00000000 --- a/d2core/d2map/d2mapengine/walk_mesh.go +++ /dev/null @@ -1,133 +0,0 @@ -package d2mapengine - -import ( - "math" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - - "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2astar" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - -// RegenerateWalkPaths based on current tile data. -func (m *MapEngine) RegenerateWalkPaths() { - for subTileY := 0; subTileY < m.size.Height*5; subTileY++ { - tileY := int(float64(subTileY) / 5.0) - - for subTileX := 0; subTileX < m.size.Width*5; subTileX++ { - tileX := int(float64(subTileX) / 5.0) - tile := m.TileAt(tileX, tileY) - isBlocked := false - - for _, floor := range tile.Floors { - tileData := m.GetTileData(int32(floor.Style), int32(floor.Sequence), d2enum.TileFloor) - if tileData == nil { - continue - } - - tileSubAttributes := tileData.GetSubTileFlags(subTileX%5, subTileY%5) - - isBlocked = isBlocked || tileSubAttributes.BlockWalk - if isBlocked { - break - } - } - - if !isBlocked { - for _, wall := range tile.Walls { - tileData := m.GetTileData(int32(wall.Style), int32(wall.Sequence), wall.Type) - if tileData == nil { - continue - } - - tileSubAttributes := tileData.GetSubTileFlags(subTileX%5, subTileY%5) - - isBlocked = isBlocked || tileSubAttributes.BlockWalk - if isBlocked { - break - } - } - } - - index := subTileX + (subTileY * m.size.Width * 5) - m.walkMesh[index] = d2common.PathTile{ - Walkable: !isBlocked, - Position: d2vector.NewPosition( - float64(subTileX), - float64(subTileY)), - } - - ySkew := m.size.Width * 5 - if !isBlocked && subTileY > 0 && m.walkMesh[index-ySkew].Walkable { - m.walkMesh[index].Up = &m.walkMesh[index-ySkew] - m.walkMesh[index-ySkew].Down = &m.walkMesh[index] - } - - if !isBlocked && subTileX > 0 && m.walkMesh[index-1].Walkable { - m.walkMesh[index].Left = &m.walkMesh[index-1] - m.walkMesh[index-1].Right = &m.walkMesh[index] - } - - if !isBlocked && subTileX > 0 && subTileY > 0 && m.walkMesh[(index-ySkew)-1].Walkable { - m.walkMesh[index].UpLeft = &m.walkMesh[(index-ySkew)-1] - m.walkMesh[(index-ySkew)-1].DownRight = &m.walkMesh[index] - } - - if !isBlocked && subTileY > 0 && subTileX < (m.size.Width*5)-1 && m.walkMesh[(index-ySkew)+1].Walkable { - m.walkMesh[index].UpRight = &m.walkMesh[(index-ySkew)+1] - m.walkMesh[(index-ySkew)+1].DownLeft = &m.walkMesh[index] - } - } - } -} - -// PathFind finds a walkable path between two points. -func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []d2astar.Pather, distance float64, found bool) { - startTileX := int(math.Floor(startX)) - startTileY := int(math.Floor(startY)) - - if !m.TileExists(startTileX, startTileY) { - return - } - - startSubtileX := int((startX - float64(int(startX))) * 5) - startSubtileY := int((startY - float64(int(startY))) * 5) - startNodeIndex := ((startSubtileY + (startTileY * 5)) * m.size.Width * 5) + startSubtileX + ((startTileX) * 5) - - if startNodeIndex < 0 || startNodeIndex >= len(m.walkMesh) { - return - } - - startNode := &m.walkMesh[startNodeIndex] - - endTileX := int(math.Floor(endX)) - endTileY := int(math.Floor(endY)) - - if !m.TileExists(endTileX, endTileY) { - return - } - - endSubtileX := int((endX - float64(int(endX))) * 5) - endSubtileY := int((endY - float64(int(endY))) * 5) - - endNodeIndex := ((endSubtileY + (endTileY * 5)) * m.size.Width * 5) + endSubtileX + ((endTileX) * 5) - if endNodeIndex < 0 || endNodeIndex >= len(m.walkMesh) { - return - } - - endNode := &m.walkMesh[endNodeIndex] - - path, distance, found = d2astar.Path(startNode, endNode, 80) - if path != nil { - // Reverse the path to fit what the game expects. - for i := len(path)/2 - 1; i >= 0; i-- { - opp := len(path) - 1 - i - path[i], path[opp] = path[opp], path[i] - } - - path = path[1:] - } - - return -} diff --git a/d2core/d2map/d2mapentity/map_entity.go b/d2core/d2map/d2mapentity/map_entity.go index fd42ca66..8d569084 100644 --- a/d2core/d2map/d2mapentity/map_entity.go +++ b/d2core/d2map/d2mapentity/map_entity.go @@ -3,8 +3,6 @@ package d2mapentity import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2astar" ) // mapEntity represents an entity on the map that can be animated @@ -14,7 +12,7 @@ type mapEntity struct { velocity d2vector.Vector Speed float64 - path []d2astar.Pather + path []d2vector.Position drawLayer int done func() @@ -38,7 +36,7 @@ func (m *mapEntity) GetLayer() int { // SetPath sets the entity movement path. done() is called when the entity reaches it's path destination. For example, // when the player entity reaches the point a player clicked. -func (m *mapEntity) SetPath(path []d2astar.Pather, done func()) { +func (m *mapEntity) SetPath(path []d2vector.Position, done func()) { m.path = path m.done = done m.nextPath() @@ -140,14 +138,14 @@ func (m *mapEntity) nextPath() { if m.hasPath() { // Set next path node m.setTarget( - m.path[0].(*d2common.PathTile).Position, + m.path[0], m.done, ) if len(m.path) > 1 { m.path = m.path[1:] } else { - m.path = []d2astar.Pather{} + m.path = []d2vector.Position{} } } else { // End of path. diff --git a/d2core/d2map/d2mapentity/map_entity_test.go b/d2core/d2map/d2mapentity/map_entity_test.go index 6839d467..2490c955 100644 --- a/d2core/d2map/d2mapentity/map_entity_test.go +++ b/d2core/d2map/d2mapentity/map_entity_test.go @@ -5,9 +5,6 @@ import ( "testing" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - - "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2astar" ) var stepEntity mapEntity @@ -38,8 +35,8 @@ func movingEntity() mapEntity { return e } -func path(length int, origin d2vector.Position) []d2astar.Pather { - path := make([]d2astar.Pather, length) +func path(length int, origin d2vector.Position) []d2vector.Position { + path := make([]d2vector.Position, length) for i := 0; i < length; i++ { origin.AddScalar(float64(i+1) / 5) @@ -50,8 +47,8 @@ func path(length int, origin d2vector.Position) []d2astar.Pather { return path } -func pathTile(x, y float64) *d2common.PathTile { - return &d2common.PathTile{Position: d2vector.NewPositionTile(x, y)} +func pathTile(x, y float64) d2vector.Position { + return d2vector.NewPositionTile(x, y) } func TestMapEntity_Step(t *testing.T) { diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index b47e43cc..e8a9b172 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -66,7 +66,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) { mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) } - mapEngine.RegenerateWalkPaths() + //mapEngine.RegenerateWalkPaths() } func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) { @@ -264,7 +264,8 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common for x := 0; x < rect.Width; x++ { tile := mapEngine.Tile(rect.Left+x, rect.Top+y) tile.RegionType = d2enum.RegionIdType(levelDetails.LevelType) - tile.Floors = []d2ds1.FloorShadowRecord{wildernessGrass} + tile.Components.Floors = []d2ds1.FloorShadowRecord{wildernessGrass} + tile.PrepareTile(x, y, mapEngine) } } @@ -320,11 +321,11 @@ func areaEmpty(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) bool { for y := rect.Top; y <= rect.Bottom(); y++ { for x := rect.Left; x <= rect.Right(); x++ { - if len(mapEngine.Tile(x, y).Floors) == 0 { + if len(mapEngine.Tile(x, y).Components.Floors) == 0 { continue } - floor := mapEngine.Tile(x, y).Floors[0] + floor := mapEngine.Tile(x, y).Components.Floors[0] if floor.Style != 0 || floor.Sequence != 0 || floor.Prop1 != 1 { return false diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index a4ed03f4..e23b4369 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -208,36 +208,36 @@ func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, e } } -func (mr *MapRenderer) renderTilePass1(tile *d2ds1.TileRecord, target d2interface.Surface) { - for _, wall := range tile.Walls { +func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2interface.Surface) { + for _, wall := range tile.Components.Walls { if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() { mr.renderWall(wall, mr.viewport, target) } } - for _, floor := range tile.Floors { + for _, floor := range tile.Components.Floors { if !floor.Hidden && floor.Prop1 != 0 { mr.renderFloor(floor, target) } } - for _, shadow := range tile.Shadows { + for _, shadow := range tile.Components.Shadows { if !shadow.Hidden && shadow.Prop1 != 0 { mr.renderShadow(shadow, target) } } } -func (mr *MapRenderer) renderTilePass2(tile *d2ds1.TileRecord, target d2interface.Surface) { - for _, wall := range tile.Walls { +func (mr *MapRenderer) renderTilePass2(tile *d2mapengine.MapTile, target d2interface.Surface) { + for _, wall := range tile.Components.Walls { if !wall.Hidden && wall.Type.UpperWall() { mr.renderWall(wall, mr.viewport, target) } } } -func (mr *MapRenderer) renderTilePass3(tile *d2ds1.TileRecord, target d2interface.Surface) { - for _, wall := range tile.Walls { +func (mr *MapRenderer) renderTilePass3(tile *d2mapengine.MapTile, target d2interface.Surface) { + for _, wall := range tile.Components.Walls { if wall.Type == d2enum.TileRoof { mr.renderWall(wall, mr.viewport, target) } @@ -359,7 +359,7 @@ func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2i target.Pop() }*/ - for i, wall := range tile.Walls { + for i, wall := range tile.Components.Walls { if wall.Type.Special() { target.PushTranslation(-20, 10+(i+1)*14) target.DrawTextf("s: %v-%v", wall.Style, wall.Sequence) @@ -372,9 +372,9 @@ func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2i isoX := (xx - yy) * 16 isoY := (xx + yy) * 8 - var walkableArea = (*mr.mapEngine.WalkMesh())[((yy+(ay*5))*mr.mapEngine.Size().Width*5)+xx+(ax*5)] + blocked := tile.GetSubTileFlags(xx, yy).BlockWalk - if !walkableArea.Walkable { + if blocked { target.PushTranslation(isoX-3, isoY+4) target.DrawRect(5, 5, tileCollisionColor) target.Pop() diff --git a/d2core/d2map/d2maprenderer/tile_cache.go b/d2core/d2map/d2maprenderer/tile_cache.go index 3d3f329c..9b0180cd 100644 --- a/d2core/d2map/d2maprenderer/tile_cache.go +++ b/d2core/d2map/d2maprenderer/tile_cache.go @@ -18,33 +18,31 @@ func (mr *MapRenderer) generateTileCache() { tileX := idx % mapEngineSize.Width tileY := (idx - tileX) / mapEngineSize.Width - for i := range tile.Floors { - if !tile.Floors[i].Hidden && tile.Floors[i].Prop1 != 0 { - mr.generateFloorCache(&tile.Floors[i], tileX, tileY) + for i := range tile.Components.Floors { + if !tile.Components.Floors[i].Hidden && tile.Components.Floors[i].Prop1 != 0 { + mr.generateFloorCache(&tile.Components.Floors[i], tileX, tileY) } } - for i := range tile.Shadows { - if !tile.Shadows[i].Hidden && tile.Shadows[i].Prop1 != 0 { - mr.generateShadowCache(&tile.Shadows[i], tileX, tileY) + for i := range tile.Components.Shadows { + if !tile.Components.Shadows[i].Hidden && tile.Components.Shadows[i].Prop1 != 0 { + mr.generateShadowCache(&tile.Components.Shadows[i], tileX, tileY) } } - for i := range tile.Walls { - if !tile.Walls[i].Hidden && tile.Walls[i].Prop1 != 0 { - mr.generateWallCache(&tile.Walls[i], tileX, tileY) + for i := range tile.Components.Walls { + if !tile.Components.Walls[i].Hidden && tile.Components.Walls[i].Prop1 != 0 { + mr.generateWallCache(&tile.Components.Walls[i], tileX, tileY) } } } } func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) { - tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 0) + tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), 0) var tileData []*d2dt1.Tile - var tileIndex byte - if tileOptions == nil { log.Printf("Could not locate tile Style:%d, Seq: %d, Type: %d\n", tile.Style, tile.Sequence, 0) @@ -53,8 +51,7 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileData[0].Height = 10 } else { if !tileOptions[0].MaterialFlags.Lava { - tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed()) - tileData = append(tileData, &tileOptions[tileIndex]) + tileData = append(tileData, &tileOptions[tile.RandomIndex]) } else { tile.Animated = true for i := range tileOptions { @@ -63,11 +60,13 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, } } + var tileIndex byte + for i := range tileData { - if !tileData[i].MaterialFlags.Lava { - tile.RandomIndex = tileIndex - } else { + if tileData[i].MaterialFlags.Lava { tileIndex = byte(tileData[i].RarityFrameIndex) + } else { + tileIndex = tile.RandomIndex } cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex) @@ -86,7 +85,7 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileHeight := d2common.AbsInt32(tileData[i].Height) image, _ := mr.renderer.NewSurface(int(tileData[i].Width), int(tileHeight), d2enum.FilterNearest) indexData := make([]byte, tileData[i].Width*tileHeight) - mr.decodeTileGfxData(tileData[i].Blocks, &indexData, tileYOffset, tileData[i].Width) + d2dt1.DecodeTileGfxData(tileData[i].Blocks, &indexData, tileYOffset, tileData[i].Width) pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette) _ = image.ReplacePixels(pixels) @@ -95,24 +94,20 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, } func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) { - tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 13) - - var tileIndex byte + tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), 13) var tileData *d2dt1.Tile if tileOptions == nil { return } else { - tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed()) - tileData = &tileOptions[tileIndex] + tileData = &tileOptions[tile.RandomIndex] } if tileData.Width == 0 || tileData.Height == 0 { return } - tile.RandomIndex = tileIndex tileMinY := int32(0) tileMaxY := int32(0) @@ -125,41 +120,35 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileHeight := int(tileMaxY - tileMinY) tile.YAdjust = int(tileMinY + 80) - cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex) + cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex) if cachedImage != nil { return } image, _ := mr.renderer.NewSurface(int(tileData.Width), tileHeight, d2enum.FilterNearest) indexData := make([]byte, tileData.Width*int32(tileHeight)) - mr.decodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileData.Width) + d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileData.Width) pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette) _ = image.ReplacePixels(pixels) - mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex, image) + mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex, image) } func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY int) { - tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), int32(tile.Type)) - - var tileIndex byte + tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), int(tile.Type)) var tileData *d2dt1.Tile if tileOptions == nil { return - } else { - tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed()) - tileData = &tileOptions[tileIndex] } - tile.RandomIndex = tileIndex + tileData = &tileOptions[tile.RandomIndex] var newTileData *d2dt1.Tile = nil if tile.Type == 3 { - newTileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), int32(4)) - newTileIndex := mr.getRandomTile(newTileOptions, tileX, tileY, mr.mapEngine.Seed()) - newTileData = &newTileOptions[newTileIndex] + newTileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), int(4)) + newTileData = &newTileOptions[tile.RandomIndex] } tileMinY := int32(0) @@ -186,7 +175,7 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY in tile.YAdjust = int(tileMinY) + 80 } - cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex) + cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex) if cachedImage != nil { return } @@ -199,10 +188,10 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY in image, _ := mr.renderer.NewSurface(160, int(realHeight), d2enum.FilterNearest) indexData := make([]byte, 160*realHeight) - mr.decodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, 160) + d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, 160) if newTileData != nil { - mr.decodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, 160) + d2dt1.DecodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, 160) } pixels := d2asset.ImgIndexToRGBA(indexData, mr.palette) @@ -211,42 +200,5 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY in log.Panicf(err.Error()) } - mr.setImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex, image) -} - -func (mr *MapRenderer) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) byte { - /* Walker's Alias Method for weighted random selection - * with xorshifting for random numbers */ - - var tileSeed uint64 - tileSeed = uint64(seed) + uint64(x) - tileSeed *= uint64(y) + uint64(mr.mapEngine.LevelType().ID) - - tileSeed ^= tileSeed << 13 - tileSeed ^= tileSeed >> 17 - tileSeed ^= tileSeed << 5 - - weightSum := 0 - - for _, tile := range tiles { - weightSum += int(tile.RarityFrameIndex) - } - - if weightSum == 0 { - return 0 - } - - random := tileSeed % uint64(weightSum) - - sum := 0 - - for i, tile := range tiles { - sum += int(tile.RarityFrameIndex) - if sum >= int(random) { - return byte(i) - } - } - - // This return shouldn't be hit - return 0 + mr.setImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex, image) } diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index f9875fb6..0524597d 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -21,7 +21,8 @@ import ( // Stamp represents a pre-fabricated map stamp that can be placed on a map. type Stamp struct { - regionPath string // The file path of the region + regionPath string // The file path of the region + regionID d2enum.RegionIdType levelType d2datadict.LevelTypeRecord // The level type id for this stamp levelPreset d2datadict.LevelPresetRecord // The level preset id for this stamp tiles []d2dt1.Tile // The tiles contained on this stamp @@ -31,6 +32,7 @@ type Stamp struct { // LoadStamp loads the Stamp data from file. func LoadStamp(levelType d2enum.RegionIdType, levelPreset, fileIndex int) *Stamp { stamp := &Stamp{ + regionID: levelType, levelType: d2datadict.LevelTypes[levelType], levelPreset: d2datadict.LevelPresets[levelPreset], } @@ -74,13 +76,6 @@ func LoadStamp(levelType d2enum.RegionIdType, levelPreset, fileIndex int) *Stamp stamp.ds1, _ = d2ds1.LoadDS1(fileData) - // Update the region info for the tiles - for rx := 0; rx < len(stamp.ds1.Tiles); rx++ { - for x := 0; x < len(stamp.ds1.Tiles[rx]); x++ { - stamp.ds1.Tiles[rx][x].RegionType = levelType - } - } - return stamp } @@ -99,6 +94,11 @@ func (mr *Stamp) LevelType() d2datadict.LevelTypeRecord { return mr.levelType } +// RegionPath returns the file path of the region. +func (mr *Stamp) RegionID() d2enum.RegionIdType { + return mr.regionID +} + // RegionPath returns the file path of the region. func (mr *Stamp) RegionPath() string { return mr.regionPath diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index def1ff39..0fe1ecf7 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -14,7 +14,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" @@ -92,7 +91,7 @@ type MapEngineTest struct { lastMouseX, lastMouseY int selX, selY int - selectedTile *d2ds1.TileRecord + selectedTile *d2mapengine.MapTile //TODO: this is region specific properties, should be refactored for multi-region rendering currentRegion int @@ -164,7 +163,7 @@ func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) { met.mapEngine = d2mapengine.CreateMapEngine() // necessary for map name update met.mapEngine.SetSeed(time.Now().UnixNano()) met.mapEngine.GenerateMap(d2enum.RegionIdType(n), levelPreset, fileIndex, true) - met.mapEngine.RegenerateWalkPaths() + //met.mapEngine.RegenerateWalkPaths() } met.mapRenderer.SetMapEngine(met.mapEngine) @@ -227,7 +226,7 @@ func (met *MapEngineTest) Render(screen d2interface.Surface) error { screen.PushTranslation(15, 16) screen.DrawTextf("Walls") tpop := 0 - for _, wall := range met.selectedTile.Walls { + for _, wall := range met.selectedTile.Components.Walls { screen.PushTranslation(0, 12) tpop++ tmpString := fmt.Sprintf("%#v", wall) @@ -245,7 +244,7 @@ func (met *MapEngineTest) Render(screen d2interface.Surface) error { screen.PushTranslation(170, 0) screen.DrawTextf("Floors") tpop = 0 - for _, floor := range met.selectedTile.Floors { + for _, floor := range met.selectedTile.Components.Floors { screen.PushTranslation(0, 12) tpop++ tmpString := fmt.Sprintf("%#v", floor) @@ -263,7 +262,7 @@ func (met *MapEngineTest) Render(screen d2interface.Surface) error { tpop = 0 screen.PushTranslation(170, 0) screen.DrawTextf("Shadows") - for _, shadow := range met.selectedTile.Shadows { + for _, shadow := range met.selectedTile.Components.Shadows { screen.PushTranslation(0, 12) tpop++ tmpString := fmt.Sprintf("%#v", shadow) @@ -281,7 +280,7 @@ func (met *MapEngineTest) Render(screen d2interface.Surface) error { tpop = 0 screen.PushTranslation(170, 0) screen.DrawTextf("Substitutions") - for _, subst := range met.selectedTile.Substitutions { + for _, subst := range met.selectedTile.Components.Substitutions { screen.PushTranslation(0, 12) tpop++ tmpString := fmt.Sprintf("%#v", subst) diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index 89638843..9ee41f5e 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -8,6 +8,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen" @@ -175,7 +176,9 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error { func (g *GameClient) handleMovePlayerPacket(packet d2netpacket.NetPacket) error { movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket) player := g.Players[movePlayer.PlayerID] - path, _, _ := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY) + start := d2vector.NewPositionTile(movePlayer.StartX, movePlayer.StartY) + dest := d2vector.NewPositionTile(movePlayer.DestX, movePlayer.DestY) + path := g.MapEngine.PathFind(start, dest) if len(path) > 0 { player.SetPath(path, func() {