Simple LOS pathfinding without walkmesh (#610)

* Reorganize MapEngine

This is already turning into a mess...

* Map engine selects tile index to use

Still very ugly

* Fix subtile flag combination

* Prepare randomly generated base tiles

* Restore collision viewer

* Movement works again, searches for straight paths

Paths are now d2vector slices

* Fix LOS calculation

* Fix test (I think)
This commit is contained in:
Ziemas 2020-07-21 14:50:45 +02:00 committed by GitHub
parent aadfa35e6b
commit d0c6cd61dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 286 additions and 321 deletions

View File

@ -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
}

View File

@ -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}

View File

@ -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 := ""

View File

@ -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]]
}

View File

@ -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()

View File

@ -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]
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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) {

View File

@ -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

View File

@ -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()

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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() {