Re-tooled rendering engine (#379)

This commit is contained in:
Tim Sarbin 2020-06-21 18:40:37 -04:00 committed by GitHub
parent 6eb85d673a
commit 912aaf044c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1493 additions and 1720 deletions

View File

@ -3,6 +3,7 @@ package d2enum
type RegionIdType int
const (
RegionNone RegionIdType = 0
RegionAct1Town RegionIdType = 1
RegionAct1Wilderness RegionIdType = 2
RegionAct1Cave RegionIdType = 3

View File

@ -1,8 +1,15 @@
package d2ds1
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
type TileRecord struct {
Floors []FloorShadowRecord
Walls []WallRecord
Shadows []FloorShadowRecord
Substitutions []SubstitutionRecord
// This is set and used internally by the engine to determine what region this map is from
RegionType d2enum.RegionIdType
}

58
d2common/path_tile.go Normal file
View File

@ -0,0 +1,58 @@
package d2common
import "github.com/beefsack/go-astar"
type PathTile struct {
Walkable bool
Up, Down, Left, Right, UpLeft, UpRight, DownLeft, DownRight *PathTile
X, Y float64
}
func (t *PathTile) PathNeighbors() []astar.Pather {
result := make([]astar.Pather, 0)
if t.Up != nil {
result = append(result, t.Up)
}
if t.Right != nil {
result = append(result, t.Right)
}
if t.Down != nil {
result = append(result, t.Down)
}
if t.Left != nil {
result = append(result, t.Left)
}
if t.UpLeft != nil {
result = append(result, t.UpLeft)
}
if t.UpRight != nil {
result = append(result, t.UpRight)
}
if t.DownLeft != nil {
result = append(result, t.DownLeft)
}
if t.DownRight != nil {
result = append(result, t.DownRight)
}
return result
}
func (t *PathTile) PathNeighborCost(to astar.Pather) float64 {
return 1 // No cost specifics currently...
}
func (t *PathTile) PathEstimatedCost(to astar.Pather) float64 {
toT := to.(*PathTile)
absX := toT.X - t.X
if absX < 0 {
absX = -absX
}
absY := toT.Y - t.Y
if absY < 0 {
absY = -absY
}
r := absX + absY
return r
}

5
d2common/size.go Normal file
View File

@ -0,0 +1,5 @@
package d2common
type Size struct {
Width, Height int
}

View File

@ -1,66 +0,0 @@
package d2map
import (
"math/rand"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2wilderness"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
func (m *MapEngine) GenerateAct1Overworld(cacheTiles bool) {
rand.Seed(m.seed)
region, entities := loadRegion(m.seed, 0, 0, d2enum.RegionAct1Town, 1, -1, cacheTiles)
m.regions = append(m.regions, region)
m.entities.Add(entities...)
if strings.Contains(region.regionPath, "E1") {
region, entities := loadRegion(m.seed, region.tileRect.Width, 0, d2enum.RegionAct1Town, 2, -1, cacheTiles)
m.AppendRegion(region)
m.entities.Add(entities...)
} else if strings.Contains(region.regionPath, "S1") {
region.tileRect.Height -= 1 // For some reason, this has a duplciate wall tile strip...
mapWidthTiles := ((region.tileRect.Width - 18) / 9)
yOffset := region.tileRect.Height
waterXOffset := region.tileRect.Width - 17
region, entities := loadRegion(m.seed, 0, yOffset, d2enum.RegionAct1Town, 3, -1, cacheTiles)
m.AppendRegion(region)
m.entities.Add(entities...)
yOffset += region.tileRect.Height
var choices = [...]int{
d2wilderness.StoneFill1,
d2wilderness.StoneFill2,
d2wilderness.SwampFill1,
d2wilderness.Cottages1,
d2wilderness.Cottages2,
d2wilderness.Cottages3,
d2wilderness.CorralFill,
d2wilderness.FallenCamp1,
d2wilderness.FallenCamp2,
d2wilderness.Pond,
}
for i := 0; i < 6; i++ {
// West Border
region, entities = loadRegion(m.seed, 0, yOffset, d2enum.RegionAct1Wilderness, d2wilderness.TreeBorderWest, 0, cacheTiles)
m.AppendRegion(region)
m.entities.Add(entities...)
// East Border
region, entities = loadRegion(m.seed, waterXOffset, yOffset, d2enum.RegionAct1Wilderness, d2wilderness.WaterBorderEast, 0, cacheTiles)
m.AppendRegion(region)
m.entities.Add(entities...)
// Grass
for ix := 0; ix < mapWidthTiles; ix++ {
region, entities = loadRegion(m.seed, ((ix)*9)+7, yOffset, d2enum.RegionAct1Wilderness, choices[rand.Intn(len(choices))], 0, cacheTiles)
m.AppendRegion(region)
m.entities.Add(entities...)
}
yOffset += 9
}
}
}

View File

@ -0,0 +1,207 @@
package d2mapengine
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
)
// Represents the map data for a specific location
type MapEngine struct {
seed int64 // The map seed
entities []d2mapentity.MapEntity // Entities on the map
tiles []d2ds1.TileRecord // The map tiles
size d2common.Size // The size of the map, in tiles
levelType d2datadict.LevelTypeRecord // The level type of this map
dt1TileData []d2dt1.Tile // The DT1 tile data
walkMesh []d2common.PathTile // The walk mesh
startSubTileX int // The starting X position
startSubTileY int // The starting Y position
}
// Creates a new instance of the map engine
func CreateMapEngine() *MapEngine {
engine := &MapEngine{}
return engine
}
func (m *MapEngine) WalkMesh() *[]d2common.PathTile {
return &m.walkMesh
}
// Returns the starting position on the map in sub-tiles
func (m *MapEngine) GetStartingPosition() (int, int) {
return m.startSubTileX, m.startSubTileY
}
func (m *MapEngine) ResetMap(seed int64, levelType d2enum.RegionIdType, width, height int) {
m.seed = seed
m.entities = make([]d2mapentity.MapEntity, 0)
m.levelType = d2datadict.LevelTypes[levelType]
m.size = d2common.Size{Width: width, Height: height}
m.tiles = make([]d2ds1.TileRecord, width*height)
m.dt1TileData = make([]d2dt1.Tile, 0)
m.walkMesh = make([]d2common.PathTile, width*height*25)
for _, dtFileName := range m.levelType.Files {
if len(dtFileName) == 0 || dtFileName == "0" {
continue
}
fileData, err := d2asset.LoadFile("/data/global/tiles/" + dtFileName)
if err != nil {
panic(err)
}
dt1, _ := d2dt1.LoadDT1(fileData)
m.dt1TileData = append(m.dt1TileData, dt1.Tiles...)
}
}
// Returns the level type of this map
func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord {
return m.levelType
}
// Sets the seed of the map for generation
func (m *MapEngine) SetSeed(seed int64) {
log.Printf("Setting map engine seed to %d", seed)
m.seed = seed
}
// Returns the size of the map (in sub-tiles)
func (m *MapEngine) Size() d2common.Size {
return m.size
}
// Returns the map's tiles
func (m *MapEngine) Tiles() *[]d2ds1.TileRecord {
return &m.tiles
}
// Places a stamp at the specified location. Also adds any entities from the stamp to the map engine
func (m *MapEngine) PlaceStamp(stamp *d2mapstamp.Stamp, tileOffsetX, tileOffsetY int) {
stampSize := stamp.Size()
if (tileOffsetX < 0) || (tileOffsetY < 0) || ((tileOffsetX + stampSize.Width) > m.size.Width) || ((tileOffsetY + stampSize.Height) > m.size.Height) {
panic("Tried placing a stamp outside the bounds of the map")
}
// Copy over the map tile data
for y := 0; y < stampSize.Height; y++ {
for x := 0; x < stampSize.Width; x++ {
mapTileIdx := x + tileOffsetX + ((y + tileOffsetY) * stampSize.Width)
m.tiles[mapTileIdx] = *stamp.Tile(x, y)
}
}
// Copy over the entities
m.entities = append(m.entities, stamp.Entities()...)
}
// Returns a reference to a map tile based on the specified tile X and Y coordinate
func (m *MapEngine) TileAt(tileX, tileY int) *d2ds1.TileRecord {
idx := tileX + (tileY * m.size.Width)
if idx < 0 || idx >= len(m.tiles) {
return nil
}
return &m.tiles[idx]
}
// Returns a reference to the map entities
func (m *MapEngine) Entities() *[]d2mapentity.MapEntity {
return &m.entities
}
// Returns the map engine's seed
func (m *MapEngine) Seed() int64 {
return m.seed
}
// Adds an entity to the map engine
func (m *MapEngine) AddEntity(entity d2mapentity.MapEntity) {
m.entities = append(m.entities, entity)
}
// Removes an entity from the map engine
func (m *MapEngine) RemoveEntity(entity d2mapentity.MapEntity) {
if entity == nil {
return
}
panic("Removing entities is not currently implemented")
//m.entities.Remove(entity)
}
func (m *MapEngine) GetTiles(style, sequence, tileType int32) []d2dt1.Tile {
var tiles []d2dt1.Tile
for _, tile := range m.dt1TileData {
if tile.Style != style || tile.Sequence != sequence || tile.Type != tileType {
continue
}
tiles = append(tiles, tile)
}
if len(tiles) == 0 {
log.Printf("Unknown tile ID [%d %d %d]\n", style, sequence, tileType)
return nil
}
return tiles
}
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)]
for _, wall := range tile.Walls {
if wall.Type == 10 && (wall.Style == 10 || wall.Style == 31) {
return float64(tileX) + 0.5, float64(tileY) + 0.5
}
}
}
}
return m.GetCenterPosition()
}
// Returns the center of the map
func (m *MapEngine) GetCenterPosition() (float64, float64) {
return float64(m.size.Width) / 2.0, float64(m.size.Height) / 2.0
}
// Advances time on the map engine
func (m *MapEngine) Advance(tickTime float64) {
for _, entity := range m.entities {
entity.Advance(tickTime)
}
}
func (m *MapEngine) TileExists(tileX, tileY int) bool {
if tileX < 0 || tileX >= m.size.Width || tileY < 0 || tileY >= m.size.Height {
return false
}
tile := m.tiles[tileX+(tileY*m.size.Width)]
return len(tile.Floors) > 0 || len(tile.Shadows) > 0 || len(tile.Walls) > 0 || len(tile.Substitutions) > 0
}
func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) {
region := d2mapstamp.LoadStamp(m.seed, regionType, levelPreset, fileIndex)
regionSize := region.Size()
m.ResetMap(0, regionType, regionSize.Width, regionSize.Height)
m.PlaceStamp(region, 0, 0)
}
func (m *MapEngine) GetTileData(style int32, sequence int32, tileType d2enum.TileType) *d2dt1.Tile {
for _, tile := range m.dt1TileData {
if tile.Style == style && tile.Sequence == sequence && tile.Type == int32(tileType) {
return &tile
}
}
return nil
}

View File

@ -0,0 +1,105 @@
package d2mapengine
import (
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/beefsack/go-astar"
)
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.Floor)
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,
X: float64(subTileX) / 5.0,
Y: float64(subTileY) / 5.0,
}
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]
}
}
}
}
// Finds a walkable path between two points
func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []astar.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 = astar.Path(endNode, startNode)
if path != nil {
path = path[1:]
}
return
}

View File

@ -1,4 +1,4 @@
package d2map
package d2mapentity
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"

View File

@ -1,4 +1,4 @@
package d2map
package d2mapentity
import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"

View File

@ -1,12 +1,19 @@
package d2map
package d2mapentity
import (
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/beefsack/go-astar"
)
type MapEntity interface {
Render(target d2render.Surface)
Advance(tickTime float64)
GetPosition() (float64, float64)
}
// mapEntity represents an entity on the map that can be animated
type mapEntity struct {
LocationX float64
@ -92,7 +99,7 @@ func (m *mapEntity) Step(tickTime float64) {
if d2common.AlmostEqual(m.LocationX, m.TargetX, 0.01) && d2common.AlmostEqual(m.LocationY, m.TargetY, 0.01) {
if len(m.path) > 0 {
m.SetTarget(m.path[0].(*PathTile).X*5, m.path[0].(*PathTile).Y*5, m.done)
m.SetTarget(m.path[0].(*d2common.PathTile).X*5, m.path[0].(*d2common.PathTile).Y*5, m.done)
if len(m.path) > 1 {
m.path = m.path[1:]

View File

@ -1,4 +1,4 @@
package d2map
package d2mapentity
import (
"fmt"

View File

@ -1,4 +1,4 @@
package d2map
package d2mapentity
import (
"math/rand"

View File

@ -1,4 +1,4 @@
package d2map
package d2mapentity
import (
"image/color"

View File

@ -0,0 +1,72 @@
package d2mapgen
import (
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp"
)
func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
rand.Seed(mapEngine.Seed())
townStamp := d2mapstamp.LoadStamp(mapEngine.Seed(), d2enum.RegionAct1Town, 1, -1)
townSize := townStamp.Size()
mapEngine.ResetMap(0, d2enum.RegionAct1Town, townSize.Width, townSize.Height) // TODO: Mapgen - Needs levels.txt stuff
mapEngine.PlaceStamp(townStamp, 0, 0)
mapEngine.RegenerateWalkPaths()
//region, entities := LoadStamp(m.seed, 0, 0, d2enum.RegionAct1Town, 1, -1, cacheTiles)
//m.regions = append(m.regions, region)
//m.entities.Add(entities...)
//if strings.Contains(region.regionPath, "E1") {
// region, entities := LoadStamp(m.seed, region.tileRect.Width, 0, d2enum.RegionAct1Town, 2, -1, cacheTiles)
// m.AppendRegion(region)
// m.entities.Add(entities...)
//} else if strings.Contains(region.regionPath, "S1") {
// region.tileRect.Height -= 1 // For some reason, this has a duplciate wall tile strip...
// mapWidthTiles := ((region.tileRect.Width - 18) / 9)
// yOffset := region.tileRect.Height
// waterXOffset := region.tileRect.Width - 17
// region, entities := LoadStamp(m.seed, 0, yOffset, d2enum.RegionAct1Town, 3, -1, cacheTiles)
// m.AppendRegion(region)
// m.entities.Add(entities...)
// yOffset += region.tileRect.Height
//
// var choices = [...]int{
// d2wilderness.StoneFill1,
// d2wilderness.StoneFill2,
// d2wilderness.SwampFill1,
// d2wilderness.Cottages1,
// d2wilderness.Cottages2,
// d2wilderness.Cottages3,
// d2wilderness.CorralFill,
// d2wilderness.FallenCamp1,
// d2wilderness.FallenCamp2,
// d2wilderness.Pond,
// }
//
// for i := 0; i < 6; i++ {
// // West Border
// region, entities = LoadStamp(m.seed, 0, yOffset, d2enum.RegionAct1Wilderness, d2wilderness.TreeBorderWest, 0, cacheTiles)
// m.AppendRegion(region)
// m.entities.Add(entities...)
//
// // East Border
// region, entities = LoadStamp(m.seed, waterXOffset, yOffset, d2enum.RegionAct1Wilderness, d2wilderness.WaterBorderEast, 0, cacheTiles)
// m.AppendRegion(region)
// m.entities.Add(entities...)
//
// // Grass
// for ix := 0; ix < mapWidthTiles; ix++ {
// region, entities = LoadStamp(m.seed, ((ix)*9)+7, yOffset, d2enum.RegionAct1Wilderness, choices[rand.Intn(len(choices))], 0, cacheTiles)
// m.AppendRegion(region)
// m.entities.Add(entities...)
// }
//
// yOffset += 9
// }
//
//}
}

View File

@ -1,4 +1,4 @@
package d2map
package d2maprenderer
type Camera struct {
x float64

View File

@ -0,0 +1,76 @@
package d2maprenderer
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
func (mr *MapRenderer) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) {
for _, block := range blocks {
if block.Format == d2dt1.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}
blockX := int32(block.X)
blockY := int32(block.Y)
length := int32(256)
x := int32(0)
y := int32(0)
idx := 0
for length > 0 {
x = xjump[y]
n := nbpix[y]
length -= n
for n > 0 {
colorIndex := block.EncodedData[idx]
if colorIndex != 0 {
pixelColor := mr.palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
(*pixels)[offset+2] = pixelColor.B
(*pixels)[offset+3] = 255
}
x++
n--
idx++
}
y++
}
continue
}
// RLE Encoding
blockX := int32(block.X)
blockY := int32(block.Y)
x := int32(0)
y := int32(0)
idx := 0
length := block.Length
for length > 0 {
b1 := block.EncodedData[idx]
b2 := block.EncodedData[idx+1]
idx += 2
length -= 2
if (b1 | b2) == 0 {
x = 0
y++
continue
}
x += int32(b1)
length -= int32(b2)
for b2 > 0 {
colorIndex := block.EncodedData[idx]
if colorIndex != 0 {
pixelColor := mr.palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
(*pixels)[offset+2] = pixelColor.B
(*pixels)[offset+3] = 255
}
idx++
x++
b2--
}
}
}
}

View File

@ -0,0 +1,26 @@
package d2maprenderer
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
)
var imageCacheRecords map[uint32]d2render.Surface
// Invalidates the global region image cache. Call this when you are changing regions
func InvalidateImageCache() {
imageCacheRecords = nil
}
func (mr *MapRenderer) getImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) d2render.Surface {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
return imageCacheRecords[lookupIndex]
}
func (mr *MapRenderer) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image d2render.Surface) {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
if imageCacheRecords == nil {
imageCacheRecords = make(map[uint32]d2render.Surface)
}
imageCacheRecords[lookupIndex] = image
}

View File

@ -0,0 +1,346 @@
package d2maprenderer
import (
"errors"
"image/color"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2term"
)
// The map renderer, used to render the map
type MapRenderer struct {
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
palette *d2dat.DATPalette // The palette used for this map
viewport *Viewport // The viewport for the map renderer (used for rendering offsets)
camera Camera // The camera for this map renderer (used to determine where on the map we are rendering)
debugVisLevel int // Debug visibility index (0=none, 1=tiles, 2=sub-tiles)
lastFrameTime float64 // The last time the map was rendered
currentFrame int // The current render frame (for animations)
}
// Creates an instance of the map renderer
func CreateMapRenderer(mapEngine *d2mapengine.MapEngine) *MapRenderer {
result := &MapRenderer{
mapEngine: mapEngine,
viewport: NewViewport(0, 0, 800, 600),
}
result.viewport.SetCamera(&result.camera)
d2term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) {
result.debugVisLevel = level
})
if mapEngine.LevelType().Id != 0 {
result.generateTileCache()
}
return result
}
func (mr *MapRenderer) RegenerateTileCache() {
mr.generateTileCache()
}
func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) {
mr.mapEngine = mapEngine
mr.generateTileCache()
}
func (mr *MapRenderer) Render(target d2render.Surface) {
mr.renderPass1(mr.viewport, target)
if mr.debugVisLevel > 0 {
mr.renderDebug(mr.debugVisLevel, mr.viewport, target)
}
mr.renderPass2(mr.viewport, target)
mr.renderPass3(mr.viewport, target)
}
func (mr *MapRenderer) MoveCameraTo(x, y float64) {
mr.camera.MoveTo(x, y)
}
func (mr *MapRenderer) MoveCameraBy(x, y float64) {
mr.camera.MoveBy(x, y)
}
func (mr *MapRenderer) ScreenToWorld(x, y int) (float64, float64) {
return mr.viewport.ScreenToWorld(x, y)
}
func (mr *MapRenderer) ScreenToOrtho(x, y int) (float64, float64) {
return mr.viewport.ScreenToOrtho(x, y)
}
func (mr *MapRenderer) WorldToOrtho(x, y float64) (float64, float64) {
return mr.viewport.WorldToOrtho(x, y)
}
func (mr *MapRenderer) renderPass1(viewport *Viewport, target d2render.Surface) {
mapSize := mr.mapEngine.Size()
// TODO: Render based on visible area
for tileY := 0; tileY < mapSize.Height; tileY++ {
for tileX := 0; tileX < mapSize.Width; tileX++ {
tile := mr.mapEngine.TileAt(tileX, tileY)
if viewport.IsTileVisible(float64(tileX), float64(tileY)) {
viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTilePass1(tile, target)
viewport.PopTranslation()
}
}
}
}
func (mr *MapRenderer) renderPass2(viewport *Viewport, target d2render.Surface) {
mapSize := mr.mapEngine.Size()
// TODO: Render based on visible area
for tileY := 0; tileY < mapSize.Height; tileY++ {
for tileX := 0; tileX < mapSize.Width; tileX++ {
tile := mr.mapEngine.TileAt(tileX, tileY)
if viewport.IsTileVisible(float64(tileX), float64(tileY)) {
viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTilePass2(tile, target)
// TODO: Do not loop over every entity every frame
for _, mapEntity := range *mr.mapEngine.Entities() {
entityX, entityY := mapEntity.GetPosition()
if (int(entityX) != tileX) || (int(entityY) != tileY) {
continue
}
target.PushTranslation(viewport.GetTranslationScreen())
mapEntity.Render(target)
target.Pop()
}
viewport.PopTranslation()
}
}
}
}
func (mr *MapRenderer) renderPass3(viewport *Viewport, target d2render.Surface) {
mapSize := mr.mapEngine.Size()
// TODO: Render based on visible area
for tileY := 0; tileY < mapSize.Height; tileY++ {
for tileX := 0; tileX < mapSize.Width; tileX++ {
tile := mr.mapEngine.TileAt(tileX, tileY)
if viewport.IsTileVisible(float64(tileX), float64(tileY)) {
viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTilePass3(tile, target)
viewport.PopTranslation()
}
}
}
}
func (mr *MapRenderer) renderTilePass1(tile *d2ds1.TileRecord, target d2render.Surface) {
for _, wall := range tile.Walls {
if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() {
mr.renderWall(wall, mr.viewport, target)
}
}
for _, floor := range tile.Floors {
if !floor.Hidden && floor.Prop1 != 0 {
mr.renderFloor(floor, target)
}
}
for _, shadow := range tile.Shadows {
if !shadow.Hidden && shadow.Prop1 != 0 {
mr.renderShadow(shadow, target)
}
}
}
func (mr *MapRenderer) renderTilePass2(tile *d2ds1.TileRecord, target d2render.Surface) {
for _, wall := range tile.Walls {
if !wall.Hidden && wall.Type.UpperWall() {
mr.renderWall(wall, mr.viewport, target)
}
}
}
func (mr *MapRenderer) renderTilePass3(tile *d2ds1.TileRecord, target d2render.Surface) {
for _, wall := range tile.Walls {
if wall.Type == d2enum.Roof {
mr.renderWall(wall, mr.viewport, target)
}
}
}
func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2render.Surface) {
var img d2render.Surface
if !tile.Animated {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
} else {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, byte(mr.currentFrame))
}
if img == nil {
log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)
return
}
mr.viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
defer mr.viewport.PopTranslation()
target.PushTranslation(mr.viewport.GetTranslationScreen())
defer target.Pop()
target.Render(img)
}
func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2render.Surface) {
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex)
if img == nil {
log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type)
return
}
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)-8)
defer viewport.PopTranslation()
target.PushTranslation(viewport.GetTranslationScreen())
defer target.Pop()
target.Render(img)
}
func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2render.Surface) {
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex)
if img == nil {
log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence)
return
}
defer mr.viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)).PopTranslation()
target.PushTranslation(mr.viewport.GetTranslationScreen())
target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160})
defer target.PopN(2)
target.Render(img)
}
func (mr *MapRenderer) renderDebug(debugVisLevel int, viewport *Viewport, target d2render.Surface) {
mapSize := mr.mapEngine.Size()
// TODO: Render based on visible area
for tileY := 0; tileY < mapSize.Height; tileY++ {
for tileX := 0; tileX < mapSize.Width; tileX++ {
if viewport.IsTileVisible(float64(tileX), float64(tileY)) {
viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTileDebug(tileX, tileY, debugVisLevel, target)
viewport.PopTranslation()
}
}
}
}
func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2render.Surface) {
subTileColor := color.RGBA{R: 80, G: 80, B: 255, A: 50}
tileColor := color.RGBA{R: 255, G: 255, B: 255, A: 100}
tileCollisionColor := color.RGBA{R: 128, G: 0, B: 0, A: 100}
screenX1, screenY1 := mr.viewport.WorldToScreen(float64(ax), float64(ay))
screenX2, screenY2 := mr.viewport.WorldToScreen(float64(ax+1), float64(ay))
screenX3, screenY3 := mr.viewport.WorldToScreen(float64(ax), float64(ay+1))
target.PushTranslation(screenX1, screenY1)
defer target.Pop()
target.DrawLine(screenX2-screenX1, screenY2-screenY1, tileColor)
target.DrawLine(screenX3-screenX1, screenY3-screenY1, tileColor)
target.PushTranslation(-10, 10)
target.DrawText("%v, %v", ax, ay)
target.Pop()
if debugVisLevel > 1 {
for i := 1; i <= 4; i++ {
x2 := i * 16
y2 := i * 8
target.PushTranslation(-x2, y2)
target.DrawLine(80, 40, subTileColor)
target.Pop()
target.PushTranslation(x2, y2)
target.DrawLine(-80, 40, subTileColor)
target.Pop()
}
tile := mr.mapEngine.TileAt(ax, ay)
for i, floor := range tile.Floors {
target.PushTranslation(-20, 10+(i+1)*14)
target.DrawText("f: %v-%v", floor.Style, floor.Sequence)
target.Pop()
}
for yy := 0; yy < 5; yy++ {
for xx := 0; xx < 5; xx++ {
isoX := (xx - yy) * 16
isoY := (xx + yy) * 8
var walkableArea = (*mr.mapEngine.WalkMesh())[((yy+(ay*5))*mr.mapEngine.Size().Width*5)+xx+(ax*5)]
if !walkableArea.Walkable {
target.PushTranslation(isoX-3, isoY+4)
target.DrawRect(5, 5, tileCollisionColor)
target.Pop()
}
}
}
}
}
func (mr *MapRenderer) Advance(elapsed float64) {
frameLength := 0.1
mr.lastFrameTime += elapsed
framesAdvanced := int(mr.lastFrameTime / frameLength)
mr.lastFrameTime -= float64(framesAdvanced) * frameLength
mr.currentFrame += framesAdvanced
if mr.currentFrame > 9 {
mr.currentFrame = 0
}
}
func loadPaletteForAct(levelType d2enum.RegionIdType) (*d2dat.DATPalette, error) {
var palettePath string
switch levelType {
case d2enum.RegionAct1Town, d2enum.RegionAct1Wilderness, d2enum.RegionAct1Cave, d2enum.RegionAct1Crypt,
d2enum.RegionAct1Monestary, d2enum.RegionAct1Courtyard, d2enum.RegionAct1Barracks,
d2enum.RegionAct1Jail, d2enum.RegionAct1Cathedral, d2enum.RegionAct1Catacombs, d2enum.RegionAct1Tristram:
palettePath = d2resource.PaletteAct1
break
case d2enum.RegionAct2Town, d2enum.RegionAct2Sewer, d2enum.RegionAct2Harem, d2enum.RegionAct2Basement,
d2enum.RegionAct2Desert, d2enum.RegionAct2Tomb, d2enum.RegionAct2Lair, d2enum.RegionAct2Arcane:
palettePath = d2resource.PaletteAct2
break
case d2enum.RegionAct3Town, d2enum.RegionAct3Jungle, d2enum.RegionAct3Kurast, d2enum.RegionAct3Spider,
d2enum.RegionAct3Dungeon, d2enum.RegionAct3Sewer:
palettePath = d2resource.PaletteAct3
break
case d2enum.RegionAct4Town, d2enum.RegionAct4Mesa, d2enum.RegionAct4Lava, d2enum.RegionAct5Lava:
palettePath = d2resource.PaletteAct4
break
case d2enum.RegonAct5Town, d2enum.RegionAct5Siege, d2enum.RegionAct5Barricade, d2enum.RegionAct5Temple,
d2enum.RegionAct5IceCaves, d2enum.RegionAct5Baal:
palettePath = d2resource.PaletteAct5
break
default:
return nil, errors.New("failed to find palette for region")
}
return d2asset.LoadPalette(palettePath)
}

View File

@ -0,0 +1,225 @@
package d2maprenderer
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
)
func (mr *MapRenderer) generateTileCache() {
mr.palette, _ = loadPaletteForAct(d2enum.RegionIdType(mr.mapEngine.LevelType().Id))
mapEngineSize := mr.mapEngine.Size()
for idx, tile := range *mr.mapEngine.Tiles() {
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.Shadows {
if !tile.Shadows[i].Hidden && tile.Shadows[i].Prop1 != 0 {
mr.generateShadowCache(&tile.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)
}
}
}
}
func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(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)
tileData = append(tileData, &d2dt1.Tile{})
tileData[0].Width = 10
tileData[0].Height = 10
} else {
if !tileOptions[0].MaterialFlags.Lava {
tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed())
tileData = append(tileData, &tileOptions[tileIndex])
} else {
tile.Animated = true
for i := range tileOptions {
tileData = append(tileData, &tileOptions[i])
}
}
}
for i := range tileData {
if !tileData[i].MaterialFlags.Lava {
tile.RandomIndex = tileIndex
} else {
tileIndex = byte(tileData[i].RarityFrameIndex)
}
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex)
if cachedImage != nil {
return
}
tileYMinimum := int32(0)
for _, block := range tileData[i].Blocks {
tileYMinimum = d2common.MinInt32(tileYMinimum, int32(block.Y))
}
tileYOffset := d2common.AbsInt32(tileYMinimum)
tileHeight := d2common.AbsInt32(tileData[i].Height)
image, _ := d2render.NewSurface(int(tileData[i].Width), int(tileHeight), d2render.FilterNearest)
pixels := make([]byte, 4*tileData[i].Width*tileHeight)
mr.decodeTileGfxData(tileData[i].Blocks, &pixels, tileYOffset, tileData[i].Width)
image.ReplacePixels(pixels)
mr.setImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex, image)
}
}
func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := mr.mapEngine.GetTiles(int32(tile.Style), int32(tile.Sequence), 13)
var tileIndex byte
var tileData *d2dt1.Tile
if tileOptions == nil {
return
} else {
tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed())
tileData = &tileOptions[tileIndex]
}
if tileData.Width == 0 || tileData.Height == 0 {
return
}
tile.RandomIndex = tileIndex
tileMinY := int32(0)
tileMaxY := int32(0)
for _, block := range tileData.Blocks {
tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
}
tileYOffset := -tileMinY
tileHeight := int(tileMaxY - tileMinY)
tile.YAdjust = int(tileMinY + 80)
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex)
if cachedImage != nil {
return
}
image, _ := d2render.NewSurface(int(tileData.Width), tileHeight, d2render.FilterNearest)
pixels := make([]byte, 4*tileData.Width*int32(tileHeight))
mr.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, tileData.Width)
image.ReplacePixels(pixels)
mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex, 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
var tileData *d2dt1.Tile
if tileOptions == nil {
return
} else {
tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.mapEngine.Seed())
tileData = &tileOptions[tileIndex]
}
tile.RandomIndex = tileIndex
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]
}
tileMinY := int32(0)
tileMaxY := int32(0)
target := tileData
if newTileData != nil && newTileData.Height < tileData.Height {
target = newTileData
}
for _, block := range target.Blocks {
tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
}
realHeight := d2common.MaxInt32(d2common.AbsInt32(tileData.Height), tileMaxY-tileMinY)
tileYOffset := -tileMinY
//tileHeight := int(tileMaxY - tileMinY)
if tile.Type == 15 {
tile.YAdjust = -int(tileData.RoofHeight)
} else {
tile.YAdjust = int(tileMinY) + 80
}
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex)
if cachedImage != nil {
return
}
if realHeight == 0 {
log.Printf("Invalid 0 height for wall tile")
return
}
image, _ := d2render.NewSurface(160, int(realHeight), d2render.FilterNearest)
pixels := make([]byte, 4*160*realHeight)
mr.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, 160)
if newTileData != nil {
mr.decodeTileGfxData(newTileData.Blocks, &pixels, tileYOffset, 160)
}
if err := image.ReplacePixels(pixels); err != nil {
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
}

View File

@ -1,4 +1,4 @@
package d2map
package d2maprenderer
import (
"math"
@ -32,7 +32,7 @@ func NewViewport(x, y, width, height int) *Viewport {
Left: x,
Top: y,
Width: width,
Height: height,
Height: height + 200, // TODO: Temporary hack to prevent clipping
},
defaultScreenRect: d2common.Rectangle{
Left: x,
@ -109,10 +109,11 @@ func (v *Viewport) GetTranslationScreen() (int, int) {
return v.OrthoToScreen(v.transCurrent.x, v.transCurrent.y)
}
func (v *Viewport) PushTranslationOrtho(x, y float64) {
func (v *Viewport) PushTranslationOrtho(x, y float64) *Viewport {
v.transStack = append(v.transStack, v.transCurrent)
v.transCurrent.x += x
v.transCurrent.y += y
return v
}
func (v *Viewport) PushTranslationWorld(x, y float64) {

View File

@ -0,0 +1,163 @@
package d2mapstamp
import (
"math"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// Represents a pre-fabricated map stamp that can be placed on a map
type Stamp struct {
regionPath string // The file path of the region
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
ds1 *d2ds1.DS1 // The backing DS1 file for this stamp
}
// Loads a stamp based on the supplied parameters
func LoadStamp(seed int64, levelType d2enum.RegionIdType, levelPreset int, fileIndex int) *Stamp {
stamp := &Stamp{
levelType: d2datadict.LevelTypes[levelType],
levelPreset: d2datadict.LevelPresets[levelPreset],
}
//stamp.palette, _ = loadPaletteForAct(levelType)
for _, levelTypeDt1 := range stamp.levelType.Files {
if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" {
fileData, err := d2asset.LoadFile("/data/global/tiles/" + levelTypeDt1)
if err != nil {
panic(err)
}
dt1, _ := d2dt1.LoadDT1(fileData)
stamp.tiles = append(stamp.tiles, dt1.Tiles...)
}
}
var levelFilesToPick []string
for _, fileRecord := range stamp.levelPreset.Files {
if len(fileRecord) != 0 && fileRecord != "" && fileRecord != "0" {
levelFilesToPick = append(levelFilesToPick, fileRecord)
}
}
levelIndex := int(math.Round(float64(len(levelFilesToPick)-1) * rand.Float64()))
if fileIndex >= 0 && fileIndex < len(levelFilesToPick) {
levelIndex = fileIndex
}
if levelFilesToPick == nil {
panic("no level files to pick from")
}
stamp.regionPath = levelFilesToPick[levelIndex]
fileData, err := d2asset.LoadFile("/data/global/tiles/" + stamp.regionPath)
if err != nil {
panic(err)
}
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
}
}
//entities := stamp.loadEntities()
//stamp.loadSpecials()
return stamp
}
// Returns the size of the stamp, in tiles
func (mr *Stamp) Size() d2common.Size {
return d2common.Size{int(mr.ds1.Width), int(mr.ds1.Height)}
}
// Gets the level preset id
func (mr *Stamp) LevelPreset() d2datadict.LevelPresetRecord {
return mr.levelPreset
}
// Returns the level type id
func (mr *Stamp) LevelType() d2datadict.LevelTypeRecord {
return mr.levelType
}
// Gets the file path of the region
func (mr *Stamp) RegionPath() string {
return mr.regionPath
}
// Returns the specified tile
func (mr *Stamp) Tile(x, y int) *d2ds1.TileRecord {
return &mr.ds1.Tiles[y][x]
}
// Returns tile data based on the supplied paramters
func (mr *Stamp) TileData(style int32, sequence int32, tileType d2enum.TileType) *d2dt1.Tile {
for _, tile := range mr.tiles {
if tile.Style == style && tile.Sequence == sequence && tile.Type == int32(tileType) {
return &tile
}
}
return nil
}
func (mr *Stamp) Entities() []d2mapentity.MapEntity {
entities := make([]d2mapentity.MapEntity, 0)
for _, object := range mr.ds1.Objects {
switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter:
if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" {
npc := d2mapentity.CreateNPC(object.X, object.Y, object.Lookup, 0)
npc.SetPaths(object.Paths)
entities = append(entities, npc)
}
case d2datadict.ObjectTypeItem:
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
entity, err := d2mapentity.CreateAnimatedComposite(object.X, object.Y, object.Lookup, d2resource.PaletteUnits)
if err != nil {
panic(err)
}
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
entities = append(entities, entity)
}
}
}
return entities
}
//
//func (mr *Stamp) loadSpecials() {
// for tileY := range mr.ds1.Tiles {
// for tileX := range mr.ds1.Tiles[tileY] {
// for _, wall := range mr.ds1.Tiles[tileY][tileX].Walls {
// if wall.Type == 10 && wall.Style == 30 && wall.Sequence == 0 && mr.startX == 0 && mr.startY == 0 {
// mr.startX, mr.startY = mr.getTileWorldPosition(tileX, tileY)
// mr.startX += 0.5
// mr.startY += 0.5
// return
// }
// }
// }
// }
//}
//

View File

@ -1,234 +0,0 @@
package d2map
import (
"log"
"math"
"github.com/beefsack/go-astar"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
)
type MapEntity interface {
Render(target d2render.Surface)
Advance(tickTime float64)
GetPosition() (float64, float64)
}
// Represents the map data for a specific location
type MapEngine struct {
seed int64
regions []*MapRegion
entities MapEntitiesSearcher
}
// Creates a new instance of the map engine
func CreateMapEngine(seed int64) *MapEngine {
engine := &MapEngine{
seed: seed,
entities: NewRangeSearcher(),
}
return engine
}
// Sets the seed of the map for generation
func (m *MapEngine) SetSeed(seed int64) {
log.Printf("Setting map engine seed to %d", seed)
m.seed = seed
}
func (m *MapEngine) GetStartPosition() (float64, float64) {
var startX, startY float64
// TODO: Temporary code, only works for starting map
if len(m.regions) > 0 {
region := m.regions[0]
startX, startY = region.getStartTilePosition()
}
return startX, startY
}
// Returns the center of the map
func (m *MapEngine) GetCenterPosition() (float64, float64) {
var centerX, centerY float64
if len(m.regions) > 0 {
region := m.regions[0]
centerX = float64(region.tileRect.Left) + float64(region.tileRect.Width)/2
centerY = float64(region.tileRect.Top) + float64(region.tileRect.Height)/2
}
return centerX, centerY
}
func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) {
region, entities := loadRegion(m.seed, 0, 0, regionType, levelPreset, fileIndex, cacheTiles)
m.regions = append(m.regions, region)
m.entities.Add(entities...)
}
// Appends a region to the map
func (m *MapEngine) AppendRegion(region *MapRegion) {
m.regions = append(m.regions, region)
// Stitch together the walk map
// Top/Bottom
for x := 0; x < region.tileRect.Width*5; x++ {
otherRegion := m.GetRegionAtTile(region.tileRect.Left+(x/5), region.tileRect.Top-1)
if otherRegion == nil {
continue
}
xDiff := (region.tileRect.Left - otherRegion.tileRect.Left) * 5
sourceSubtile := &region.walkableArea[0][x]
if !sourceSubtile.Walkable {
continue
}
// North West
otherX := x + xDiff - 1
otherY := (otherRegion.tileRect.Height * 5) - 1
if otherX < 0 || otherX >= len(otherRegion.walkableArea[otherY]) {
continue
}
otherRegion.walkableArea[otherY][x+xDiff].DownRight = sourceSubtile
sourceSubtile.UpLeft = &otherRegion.walkableArea[otherY][x+xDiff]
// North
otherX++
if otherX < 0 || otherX >= len(otherRegion.walkableArea[otherY]) {
continue
}
otherRegion.walkableArea[otherY][x+xDiff].Down = sourceSubtile
sourceSubtile.Up = &otherRegion.walkableArea[otherY][x+xDiff]
// NorthEast
otherX++
if otherX < 0 || otherX >= len(otherRegion.walkableArea[otherY]) {
continue
}
otherRegion.walkableArea[otherY][x+xDiff].DownLeft = sourceSubtile
sourceSubtile.UpRight = &otherRegion.walkableArea[otherY][x+xDiff]
}
// West/East
for y := 0; y < region.tileRect.Height*5; y++ {
otherRegion := m.GetRegionAtTile(region.tileRect.Left-1, region.tileRect.Top+(y/5))
if otherRegion == nil {
continue
}
yDiff := (region.tileRect.Top - otherRegion.tileRect.Top) * 5
sourceSubtile := &region.walkableArea[y][0]
if !sourceSubtile.Walkable {
continue
}
// North West
otherX := (otherRegion.tileRect.Width * 5) - 1
otherY := y + yDiff - 1
if otherY < 0 || otherY >= len(otherRegion.walkableArea) {
continue
}
otherRegion.walkableArea[y+yDiff][otherX].DownRight = sourceSubtile
sourceSubtile.UpLeft = &otherRegion.walkableArea[y+yDiff][otherX]
// West
otherY++
if otherY < 0 || otherY >= len(otherRegion.walkableArea) {
continue
}
otherRegion.walkableArea[y+yDiff][otherX].Right = sourceSubtile
sourceSubtile.Left = &otherRegion.walkableArea[y+yDiff][otherX]
// South East
otherY++
if otherY < 0 || otherY >= len(otherRegion.walkableArea) {
continue
}
otherRegion.walkableArea[y+yDiff][otherX].UpRight = sourceSubtile
sourceSubtile.DownLeft = &otherRegion.walkableArea[y+yDiff][otherX]
}
}
// Returns the region located at the specified tile location
func (m *MapEngine) GetRegionAtTile(x, y int) *MapRegion {
// Read in reverse order as tiles can be placed over other tiles, and we prioritize the top level tiles
for i := len(m.regions) - 1; i >= 0; i-- {
region := m.regions[i]
if region.tileRect.IsInRect(x, y) {
return region
}
}
return nil
}
// Adds an entity to the map engine
func (m *MapEngine) AddEntity(entity MapEntity) {
m.entities.Add(entity)
}
// Removes an entity from the map engine
func (m *MapEngine) RemoveEntity(entity MapEntity) {
if entity == nil {
return
}
m.entities.Remove(entity)
}
// Advances time on the map engine
func (m *MapEngine) Advance(tickTime float64) {
for _, region := range m.regions {
//if region.isVisbile(m.viewport) {
region.advance(tickTime)
//}
}
for _, entity := range m.entities.All() {
entity.Advance(tickTime)
}
m.entities.Update()
}
// Finds a walkable path between two points
func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []astar.Pather, distance float64, found bool) {
startTileX := int(math.Floor(startX))
startTileY := int(math.Floor(startY))
startSubtileX := int((startX - float64(int(startX))) * 5)
startSubtileY := int((startY - float64(int(startY))) * 5)
startRegion := m.GetRegionAtTile(startTileX, startTileY)
if startRegion == nil {
return
}
startNode := &startRegion.walkableArea[startSubtileY+((startTileY-startRegion.tileRect.Top)*5)][startSubtileX+((startTileX-startRegion.tileRect.Left)*5)]
endTileX := int(math.Floor(endX))
endTileY := int(math.Floor(endY))
endSubtileX := int((endX - float64(int(endX))) * 5)
endSubtileY := int((endY - float64(int(endY))) * 5)
endRegion := m.GetRegionAtTile(endTileX, endTileY)
if endRegion == nil {
return
}
endNodeY := endSubtileY + ((endTileY - endRegion.tileRect.Top) * 5)
endNodeX := endSubtileX + ((endTileX - endRegion.tileRect.Left) * 5)
if endNodeY < 0 || endNodeY >= len(endRegion.walkableArea) {
return
}
if endNodeX < 0 || endNodeX >= len(endRegion.walkableArea[endNodeY]) {
return
}
endNode := &endRegion.walkableArea[endNodeY][endNodeX]
path, distance, found = astar.Path(endNode, startNode)
if path != nil {
path = path[1:]
}
return
}

View File

@ -1,149 +0,0 @@
package d2map
import (
"math"
"sort"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type MapEntitiesSearcher interface {
// Returns all map entities.
All() []MapEntity
// Add adds an entity to the index and re-sorts.
Add(entities ...MapEntity)
// Remove finds and removes the entity from the index.
Remove(entity MapEntity)
// SearchByRect get entities in a rectangle, results will be sorted top left to bottom right.
// Elements with equal Y will be sorted by X
SearchByRect(rect d2common.Rectangle) []MapEntity
// SearchByRadius get entities in a circle, results will be sorted top left to bottom right.
// Elements with equal Y will be sorted by X
SearchByRadius(originX, originY, radius float64) []MapEntity
// Update re-sorts the index, must be ran after each update.
Update()
}
// rangeSearcher a basic index of entity locations using a slice ordered by Y then X coordinates.
// Eventually this should be probably replaced with a proper spatial index.
type rangeSearcher struct {
entities []MapEntity
}
func NewRangeSearcher() MapEntitiesSearcher {
return &rangeSearcher{
entities: make([]MapEntity, 0, 64),
}
}
func (r *rangeSearcher) All() []MapEntity {
return r.entities
}
func (r *rangeSearcher) Add(entities ...MapEntity) {
r.entities = append(r.entities, entities...)
r.Update()
}
func (r *rangeSearcher) Remove(entity MapEntity) {
if entity == nil {
return
}
// In-place filter to remove the given entity.
n := 0
for _, check := range r.entities {
if check != entity {
r.entities[n] = check
n++
}
}
r.entities = r.entities[:n]
}
func (r *rangeSearcher) SearchByRect(rect d2common.Rectangle) []MapEntity {
left, top, right, bottom := float64(rect.Left), float64(rect.Top), float64(rect.Right()), float64(rect.Bottom())
topIndex := sort.Search(len(r.entities), func(i int) bool {
x, y := r.entities[i].GetPosition()
if y == top {
return x >= left
}
return y >= top
})
matches := make([]MapEntity, 0, 16)
for i := topIndex; i < len(r.entities); i++ {
x, y := r.entities[i].GetPosition()
if y > bottom {
break
}
if x >= left && x <= right {
matches = append(matches, r.entities[i])
}
}
return matches
}
func (r *rangeSearcher) SearchByRadius(originX, originY, radius float64) []MapEntity {
left, right := originX-radius, originX+radius
top, bottom := originY-radius, originY+radius
inRect := r.SearchByRect(d2common.Rectangle{
Left: int(left),
Top: int(top),
Width: int(right - left),
Height: int(bottom - top),
})
// In-place filter to remove entities outside the radius.
n := 0
for _, check := range inRect {
x, y := check.GetPosition()
if distance(originX, originY, x, y) <= radius {
inRect[n] = check
n++
}
}
return inRect[:n]
}
func distance(x1, y1, x2, y2 float64) float64 {
return math.Abs(math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2)))
}
// Re-sorts the index after entities have moved.
// Uses bubble sort to target O(n) sort time, in most cases no entities will be swapped.
func (r *rangeSearcher) Update() {
bubbleSort(r.entities, func(i, j int) bool {
ix, iy := r.entities[i].GetPosition()
jx, jy := r.entities[j].GetPosition()
if iy == jy {
return ix < jx
}
return iy < jy
})
}
func bubbleSort(items []MapEntity, less func(i, j int) bool) {
var (
n = len(items)
sorted = false
)
for !sorted {
swapped := false
for i := 0; i < n-1; i++ {
if less(i+1, i) {
items[i+1], items[i] = items[i], items[i+1]
swapped = true
}
}
if !swapped {
sorted = true
}
n = n - 1
}
}

View File

@ -1,125 +0,0 @@
package d2map
import (
"testing"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/stretchr/testify/assert"
)
type mockEntity struct {
x float64
y float64
}
func (m *mockEntity) Render(target d2render.Surface) {
panic("implement me")
}
func (m *mockEntity) Advance(tickTime float64) {
panic("implement me")
}
func (m *mockEntity) GetPosition() (float64, float64) {
return m.x, m.y
}
func newMockEntity(x, y float64) MapEntity {
return &mockEntity{
x: x,
y: y,
}
}
func TestRangeSearcher_Add(t *testing.T) {
searcher := &rangeSearcher{
entities: make([]MapEntity, 0, 64),
}
searcher.Add(
newMockEntity(0, 9),
newMockEntity(8, 1),
newMockEntity(1, 8),
newMockEntity(3, 6),
newMockEntity(5, 4),
newMockEntity(6, 3),
newMockEntity(9, 0),
newMockEntity(4, 5),
newMockEntity(2, 7),
newMockEntity(7, 2),
)
for i := 0; i <= 9; i++ {
_, pos := searcher.entities[i].GetPosition()
assert.Equal(t, float64(i), pos)
}
}
func TestRangeSearcher_SearchByRect(t *testing.T) {
searcher := &rangeSearcher{
entities: make([]MapEntity, 0, 64),
}
searcher.Add(
newMockEntity(0, 9),
newMockEntity(8, 1),
newMockEntity(1, 8),
newMockEntity(3, 6),
newMockEntity(5, 4),
newMockEntity(6, 3),
newMockEntity(9, 0),
newMockEntity(4, 5),
newMockEntity(2, 7),
newMockEntity(7, 2),
)
matches := searcher.SearchByRect(d2common.Rectangle{
Left: 3,
Top: 0,
Width: 4,
Height: 9,
})
valsX := make([]float64, 0)
for _, match := range matches {
x, _ := match.GetPosition()
valsX = append(valsX, x)
}
assert.ElementsMatch(t, []float64{3, 4, 5, 6, 7}, valsX)
matches = searcher.SearchByRect(d2common.Rectangle{
Left: 0,
Top: 1,
Width: 9,
Height: 4,
})
valsY := make([]float64, 0)
for _, match := range matches {
_, y := match.GetPosition()
valsY = append(valsY, y)
}
assert.ElementsMatch(t, []float64{1, 2, 3, 4, 5}, valsY)
matches = searcher.SearchByRect(d2common.Rectangle{
Left: 3,
Top: 3,
Width: 2,
Height: 2,
})
valsY = make([]float64, 0)
valsX = make([]float64, 0)
for _, match := range matches {
x, y := match.GetPosition()
valsX = append(valsX, x)
valsY = append(valsY, y)
}
assert.ElementsMatch(t, []float64{4, 5}, valsY)
assert.ElementsMatch(t, []float64{4, 5}, valsX)
}

View File

@ -1,890 +0,0 @@
package d2map
import (
"errors"
"image/color"
"log"
"math"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/beefsack/go-astar"
)
var imageCacheRecords map[uint32]d2render.Surface
type PathTile struct {
Walkable bool
Up, Down, Left, Right, UpLeft, UpRight, DownLeft, DownRight *PathTile
X, Y float64
}
func (t *PathTile) PathNeighbors() []astar.Pather {
result := make([]astar.Pather, 0)
if t.Up != nil {
result = append(result, t.Up)
}
if t.Right != nil {
result = append(result, t.Right)
}
if t.Down != nil {
result = append(result, t.Down)
}
if t.Left != nil {
result = append(result, t.Left)
}
if t.UpLeft != nil {
result = append(result, t.UpLeft)
}
if t.UpRight != nil {
result = append(result, t.UpRight)
}
if t.DownLeft != nil {
result = append(result, t.DownLeft)
}
if t.DownRight != nil {
result = append(result, t.DownRight)
}
return result
}
func (t *PathTile) PathNeighborCost(to astar.Pather) float64 {
return 1 // No cost specifics currently...
}
func (t *PathTile) PathEstimatedCost(to astar.Pather) float64 {
toT := to.(*PathTile)
absX := toT.X - t.X
if absX < 0 {
absX = -absX
}
absY := toT.Y - t.Y
if absY < 0 {
absY = -absY
}
r := absX + absY
return r
}
type MapRegion struct {
tileRect d2common.Rectangle
regionPath string
levelType d2datadict.LevelTypeRecord
levelPreset d2datadict.LevelPresetRecord
tiles []d2dt1.Tile
ds1 *d2ds1.DS1
palette *d2dat.DATPalette
startX float64
startY float64
seed int64
currentFrame int
lastFrameTime float64
walkableArea [][]PathTile
}
// Invalidates the global region image cache. Call this when you are changing regions
func InvalidateImageCache() {
imageCacheRecords = nil
}
func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) (*MapRegion, []MapEntity) {
region := &MapRegion{
levelType: d2datadict.LevelTypes[levelType],
levelPreset: d2datadict.LevelPresets[levelPreset],
seed: seed,
}
region.palette, _ = loadPaletteForAct(levelType)
for _, levelTypeDt1 := range region.levelType.Files {
if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" {
fileData, err := d2asset.LoadFile("/data/global/tiles/" + levelTypeDt1)
if err != nil {
panic(err)
}
dt1, _ := d2dt1.LoadDT1(fileData)
region.tiles = append(region.tiles, dt1.Tiles...)
}
}
var levelFilesToPick []string
for _, fileRecord := range region.levelPreset.Files {
if len(fileRecord) != 0 && fileRecord != "" && fileRecord != "0" {
levelFilesToPick = append(levelFilesToPick, fileRecord)
}
}
levelIndex := int(math.Round(float64(len(levelFilesToPick)-1) * rand.Float64()))
if fileIndex >= 0 && fileIndex < len(levelFilesToPick) {
levelIndex = fileIndex
}
if levelFilesToPick == nil {
panic("no level files to pick from")
}
region.regionPath = levelFilesToPick[levelIndex]
fileData, err := d2asset.LoadFile("/data/global/tiles/" + region.regionPath)
if err != nil {
panic(err)
}
region.ds1, _ = d2ds1.LoadDS1(fileData)
region.tileRect = d2common.Rectangle{
Left: tileOffsetX,
Top: tileOffsetY,
Width: int(region.ds1.Width),
Height: int(region.ds1.Height),
}
entities := region.loadEntities()
region.loadSpecials()
region.generateWalkableMatrix()
if cacheTiles {
region.generateTileCache()
}
return region, entities
}
func (mr *MapRegion) generateWalkableMatrix() {
mr.walkableArea = make([][]PathTile, mr.tileRect.Height*5)
for y := 0; y < mr.tileRect.Height*5; y++ {
mr.walkableArea[y] = make([]PathTile, mr.tileRect.Width*5)
ty := int(float64(y) / 5.0)
for x := 0; x < mr.tileRect.Width*5; x++ {
tx := int(float64(x) / 5.0)
tile := mr.GetTile(tx, ty)
isBlocked := false
for _, floor := range tile.Floors {
tileData := mr.GetTileData(int32(floor.Style), int32(floor.Sequence), d2enum.Floor)
tileSubAttrs := &d2dt1.SubTileFlags{}
if tileData != nil {
tileSubAttrs = tileData.GetSubTileFlags(x%5, y%5)
}
isBlocked = isBlocked || tileSubAttrs.BlockWalk
if isBlocked {
break
}
}
if !isBlocked {
for _, wall := range tile.Walls {
tileData := mr.GetTileData(int32(wall.Style), int32(wall.Sequence), d2enum.TileType(wall.Type))
tileSubAttrs := &d2dt1.SubTileFlags{}
if tileData != nil {
tileSubAttrs = tileData.GetSubTileFlags(x%5, y%5)
}
isBlocked = isBlocked || tileSubAttrs.BlockWalk
if isBlocked {
break
}
}
}
mr.walkableArea[y][x] = PathTile{
Walkable: !isBlocked,
X: (float64(x) / 5.0) + float64(mr.tileRect.Left),
Y: (float64(y) / 5.0) + float64(mr.tileRect.Top),
}
if !isBlocked && y > 0 && mr.walkableArea[y-1][x].Walkable {
mr.walkableArea[y][x].Up = &mr.walkableArea[y-1][x]
mr.walkableArea[y-1][x].Down = &mr.walkableArea[y][x]
}
if !isBlocked && x > 0 && mr.walkableArea[y][x-1].Walkable {
mr.walkableArea[y][x].Left = &mr.walkableArea[y][x-1]
mr.walkableArea[y][x-1].Right = &mr.walkableArea[y][x]
}
if !isBlocked && x > 0 && y > 0 && mr.walkableArea[y-1][x-1].Walkable {
mr.walkableArea[y][x].UpLeft = &mr.walkableArea[y-1][x-1]
mr.walkableArea[y-1][x-1].DownRight = &mr.walkableArea[y][x]
}
if !isBlocked && y > 0 && x < (mr.tileRect.Width*5)-1 && mr.walkableArea[y-1][x+1].Walkable {
mr.walkableArea[y][x].UpRight = &mr.walkableArea[y-1][x+1]
mr.walkableArea[y-1][x+1].DownLeft = &mr.walkableArea[y][x]
}
}
}
}
func (mr *MapRegion) GetTileRect() d2common.Rectangle {
return mr.tileRect
}
func (mr *MapRegion) GetLevelPreset() d2datadict.LevelPresetRecord {
return mr.levelPreset
}
func (mr *MapRegion) GetLevelType() d2datadict.LevelTypeRecord {
return mr.levelType
}
func (mr *MapRegion) GetPath() string {
return mr.regionPath
}
func (mr *MapRegion) loadSpecials() {
for tileY := range mr.ds1.Tiles {
for tileX := range mr.ds1.Tiles[tileY] {
for _, wall := range mr.ds1.Tiles[tileY][tileX].Walls {
if wall.Type == 10 && wall.Style == 30 && wall.Sequence == 0 && mr.startX == 0 && mr.startY == 0 {
mr.startX, mr.startY = mr.getTileWorldPosition(tileX, tileY)
mr.startX += 0.5
mr.startY += 0.5
return
}
}
}
}
}
func (mr *MapRegion) GetTile(x, y int) *d2ds1.TileRecord {
return &mr.ds1.Tiles[y][x]
}
func (mr *MapRegion) GetTileData(style int32, sequence int32, tileType d2enum.TileType) *d2dt1.Tile {
for _, tile := range mr.tiles {
if tile.Style == style && tile.Sequence == sequence && tile.Type == int32(tileType) {
return &tile
}
}
return nil
}
func (mr *MapRegion) GetTileSize() (int, int) {
return mr.tileRect.Width, mr.tileRect.Height
}
func (mr *MapRegion) loadEntities() []MapEntity {
var entities []MapEntity
for _, object := range mr.ds1.Objects {
worldX, worldY := mr.getSubTileWorldPosition(object.X, object.Y)
switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter:
if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" {
npc := CreateNPC(int(worldX), int(worldY), object.Lookup, 0)
npc.SetPaths(object.Paths)
entities = append(entities, npc)
}
case d2datadict.ObjectTypeItem:
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
entity, err := CreateAnimatedComposite(int(worldX), int(worldY), object.Lookup, d2resource.PaletteUnits)
if err != nil {
panic(err)
}
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
entities = append(entities, entity)
}
}
}
return entities
}
func (mr *MapRegion) getStartTilePosition() (float64, float64) {
return float64(mr.tileRect.Left) + mr.startX, float64(mr.tileRect.Top) + mr.startY
}
func (mr *MapRegion) 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.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
}
func (mr *MapRegion) getTiles(style, sequence, tileType int32) []d2dt1.Tile {
var tiles []d2dt1.Tile
for _, tile := range mr.tiles {
if tile.Style != style || tile.Sequence != sequence || tile.Type != tileType {
continue
}
tiles = append(tiles, tile)
}
if len(tiles) == 0 {
log.Printf("Unknown tile ID [%d %d %d]\n", style, sequence, tileType)
return nil
}
return tiles
}
func (mr *MapRegion) isVisbile(viewport *Viewport) bool {
return viewport.IsTileRectVisible(mr.tileRect)
}
func (mr *MapRegion) advance(elapsed float64) {
frameLength := 0.1
mr.lastFrameTime += elapsed
framesAdvanced := int(mr.lastFrameTime / frameLength)
mr.lastFrameTime -= float64(framesAdvanced) * frameLength
mr.currentFrame += framesAdvanced
if mr.currentFrame > 9 {
mr.currentFrame = 0
}
}
func (mr *MapRegion) getSubTileWorldPosition(subTileX, subTileY int) (float64, float64) {
return float64(subTileX + (mr.tileRect.Left * 5)), float64(subTileY + (mr.tileRect.Top * 5))
}
func (mr *MapRegion) getTileWorldPosition(tileX, tileY int) (float64, float64) {
return float64(tileX + mr.tileRect.Left), float64(tileY + mr.tileRect.Top)
}
func (mr *MapRegion) renderPass1(viewport *Viewport, target d2render.Surface) {
for tileY := range mr.ds1.Tiles {
for tileX, tile := range mr.ds1.Tiles[tileY] {
worldX, worldY := mr.getTileWorldPosition(tileX, tileY)
if viewport.IsTileVisible(worldX, worldY) {
viewport.PushTranslationWorld(worldX, worldY)
mr.renderTilePass1(tile, viewport, target)
viewport.PopTranslation()
}
}
}
}
func (mr *MapRegion) renderPass2(entities MapEntitiesSearcher, viewport *Viewport, target d2render.Surface) {
tileEntities := entities.SearchByRect(mr.tileRect)
nextIndex := 0
for tileY := range mr.ds1.Tiles {
for tileX, tile := range mr.ds1.Tiles[tileY] {
worldX, worldY := mr.getTileWorldPosition(tileX, tileY)
if viewport.IsTileVisible(worldX, worldY) {
viewport.PushTranslationWorld(worldX, worldY)
mr.renderTilePass2(tile, viewport, target)
for nextIndex < len(tileEntities) {
nextX, nextY := tileEntities[nextIndex].GetPosition()
if nextX == worldX && nextY == worldY {
target.PushTranslation(viewport.GetTranslationScreen())
tileEntities[nextIndex].Render(target)
target.Pop()
nextIndex++
} else if (nextY == worldY && nextX < worldX) || nextY < worldY {
nextIndex++
} else {
break
}
}
viewport.PopTranslation()
}
}
}
}
func (mr *MapRegion) renderPass3(viewport *Viewport, target d2render.Surface) {
for tileY := range mr.ds1.Tiles {
for tileX, tile := range mr.ds1.Tiles[tileY] {
worldX, worldY := mr.getTileWorldPosition(tileX, tileY)
if viewport.IsTileVisible(worldX, worldY) {
viewport.PushTranslationWorld(worldX, worldY)
mr.renderTilePass3(tile, viewport, target)
viewport.PopTranslation()
}
}
}
}
func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, target d2render.Surface) {
for _, wall := range tile.Walls {
if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() {
mr.renderWall(wall, viewport, target)
}
}
for _, floor := range tile.Floors {
if !floor.Hidden && floor.Prop1 != 0 {
mr.renderFloor(floor, viewport, target)
}
}
for _, shadow := range tile.Shadows {
if !shadow.Hidden && shadow.Prop1 != 0 {
mr.renderShadow(shadow, viewport, target)
}
}
}
func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, target d2render.Surface) {
for _, wall := range tile.Walls {
if !wall.Hidden && wall.Type.UpperWall() {
mr.renderWall(wall, viewport, target)
}
}
}
func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, target d2render.Surface) {
for _, wall := range tile.Walls {
if wall.Type == d2enum.Roof {
mr.renderWall(wall, viewport, target)
}
}
}
func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target d2render.Surface) {
var img d2render.Surface
if !tile.Animated {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
} else {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, byte(mr.currentFrame))
}
if img == nil {
log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)
return
}
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
defer viewport.PopTranslation()
target.PushTranslation(viewport.GetTranslationScreen())
defer target.Pop()
target.Render(img)
}
func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2render.Surface) {
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex)
if img == nil {
log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type)
return
}
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)-8)
defer viewport.PopTranslation()
target.PushTranslation(viewport.GetTranslationScreen())
defer target.Pop()
target.Render(img)
}
func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target d2render.Surface) {
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex)
if img == nil {
log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence)
return
}
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
defer viewport.PopTranslation()
target.PushTranslation(viewport.GetTranslationScreen())
target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160})
defer target.PopN(2)
target.Render(img)
}
func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target d2render.Surface) {
for tileY := range mr.ds1.Tiles {
for tileX := range mr.ds1.Tiles[tileY] {
worldX, worldY := mr.getTileWorldPosition(tileX, tileY)
if viewport.IsTileVisible(worldX, worldY) {
mr.renderTileDebug(int(worldX), int(worldY), debugVisLevel, viewport, target)
}
}
}
}
func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *Viewport, target d2render.Surface) {
ax := x - mr.tileRect.Left
ay := y - mr.tileRect.Top
if debugVisLevel > 0 {
if ay < 0 || ax < 0 || ay >= len(mr.ds1.Tiles) || ax >= len(mr.ds1.Tiles[ay]) {
return
}
subTileColor := color.RGBA{R: 80, G: 80, B: 255, A: 50}
tileColor := color.RGBA{R: 255, G: 255, B: 255, A: 100}
tileCollisionColor := color.RGBA{R: 128, G: 0, B: 0, A: 100}
screenX1, screenY1 := viewport.WorldToScreen(float64(x), float64(y))
screenX2, screenY2 := viewport.WorldToScreen(float64(x+1), float64(y))
screenX3, screenY3 := viewport.WorldToScreen(float64(x), float64(y+1))
target.PushTranslation(screenX1, screenY1)
defer target.Pop()
target.DrawLine(screenX2-screenX1, screenY2-screenY1, tileColor)
target.DrawLine(screenX3-screenX1, screenY3-screenY1, tileColor)
target.PushTranslation(-10, 10)
target.DrawText("%v, %v", x, y)
target.Pop()
if debugVisLevel > 1 {
for i := 1; i <= 4; i++ {
x2 := i * 16
y2 := i * 8
target.PushTranslation(-x2, y2)
target.DrawLine(80, 40, subTileColor)
target.Pop()
target.PushTranslation(x2, y2)
target.DrawLine(-80, 40, subTileColor)
target.Pop()
}
tile := mr.ds1.Tiles[ay][ax]
for i, floor := range tile.Floors {
target.PushTranslation(-20, 10+(i+1)*14)
target.DrawText("f: %v-%v", floor.Style, floor.Sequence)
target.Pop()
}
for yy := 0; yy < 5; yy++ {
for xx := 0; xx < 5; xx++ {
isoX := (xx - yy) * 16
isoY := (xx + yy) * 8
if !((len(mr.walkableArea) <= yy+(ay*5)) || (len(mr.walkableArea[yy+(ay*5)]) <= xx+(ax*5))) {
var walkableArea = mr.walkableArea[yy+(ay*5)][xx+(ax*5)]
if !walkableArea.Walkable {
target.PushTranslation(isoX-3, isoY+4)
target.DrawRect(5, 5, tileCollisionColor)
target.Pop()
}
}
}
}
}
}
}
func (mr *MapRegion) generateTileCache() {
for tileY := range mr.ds1.Tiles {
for tileX := range mr.ds1.Tiles[tileY] {
tile := mr.ds1.Tiles[tileY][tileX]
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.Shadows {
if !tile.Shadows[i].Hidden && tile.Shadows[i].Prop1 != 0 {
mr.generateShadowCache(&tile.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)
}
}
}
}
}
func (mr *MapRegion) getImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) d2render.Surface {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
return imageCacheRecords[lookupIndex]
}
func (mr *MapRegion) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image d2render.Surface) {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
if imageCacheRecords == nil {
imageCacheRecords = make(map[uint32]d2render.Surface)
}
imageCacheRecords[lookupIndex] = image
}
func (mr *MapRegion) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := mr.getTiles(int32(tile.Style), int32(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)
tileData = append(tileData, &d2dt1.Tile{})
tileData[0].Width = 10
tileData[0].Height = 10
} else {
if !tileOptions[0].MaterialFlags.Lava {
tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.seed)
tileData = append(tileData, &tileOptions[tileIndex])
} else {
tile.Animated = true
for i := range tileOptions {
tileData = append(tileData, &tileOptions[i])
}
}
}
for i := range tileData {
if !tileData[i].MaterialFlags.Lava {
tile.RandomIndex = tileIndex
} else {
tileIndex = byte(tileData[i].RarityFrameIndex)
}
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex)
if cachedImage != nil {
return
}
tileYMinimum := int32(0)
for _, block := range tileData[i].Blocks {
tileYMinimum = d2common.MinInt32(tileYMinimum, int32(block.Y))
}
tileYOffset := d2common.AbsInt32(tileYMinimum)
tileHeight := d2common.AbsInt32(tileData[i].Height)
image, _ := d2render.NewSurface(int(tileData[i].Width), int(tileHeight), d2render.FilterNearest)
pixels := make([]byte, 4*tileData[i].Width*tileHeight)
mr.decodeTileGfxData(tileData[i].Blocks, &pixels, tileYOffset, tileData[i].Width)
image.ReplacePixels(pixels)
mr.setImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex, image)
}
}
func (mr *MapRegion) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := mr.getTiles(int32(tile.Style), int32(tile.Sequence), 13)
var tileIndex byte
var tileData *d2dt1.Tile
if tileOptions == nil {
return
} else {
tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.seed)
tileData = &tileOptions[tileIndex]
}
if tileData.Width == 0 || tileData.Height == 0 {
return
}
tile.RandomIndex = tileIndex
tileMinY := int32(0)
tileMaxY := int32(0)
for _, block := range tileData.Blocks {
tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
}
tileYOffset := -tileMinY
tileHeight := int(tileMaxY - tileMinY)
tile.YAdjust = int(tileMinY + 80)
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex)
if cachedImage != nil {
return
}
image, _ := d2render.NewSurface(int(tileData.Width), tileHeight, d2render.FilterNearest)
pixels := make([]byte, 4*tileData.Width*int32(tileHeight))
mr.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, tileData.Width)
image.ReplacePixels(pixels)
mr.setImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex, image)
}
func (mr *MapRegion) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY int) {
tileOptions := mr.getTiles(int32(tile.Style), int32(tile.Sequence), int32(tile.Type))
var tileIndex byte
var tileData *d2dt1.Tile
if tileOptions == nil {
return
} else {
tileIndex = mr.getRandomTile(tileOptions, tileX, tileY, mr.seed)
tileData = &tileOptions[tileIndex]
}
tile.RandomIndex = tileIndex
var newTileData *d2dt1.Tile = nil
if tile.Type == 3 {
newTileOptions := mr.getTiles(int32(tile.Style), int32(tile.Sequence), int32(4))
newTileIndex := mr.getRandomTile(newTileOptions, tileX, tileY, mr.seed)
newTileData = &newTileOptions[newTileIndex]
}
tileMinY := int32(0)
tileMaxY := int32(0)
target := tileData
if newTileData != nil && newTileData.Height < tileData.Height {
target = newTileData
}
for _, block := range target.Blocks {
tileMinY = d2common.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2common.MaxInt32(tileMaxY, int32(block.Y+32))
}
realHeight := d2common.MaxInt32(d2common.AbsInt32(tileData.Height), tileMaxY-tileMinY)
tileYOffset := -tileMinY
//tileHeight := int(tileMaxY - tileMinY)
if tile.Type == 15 {
tile.YAdjust = -int(tileData.RoofHeight)
} else {
tile.YAdjust = int(tileMinY) + 80
}
cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex)
if cachedImage != nil {
return
}
if realHeight == 0 {
log.Printf("Invalid 0 height for wall tile")
return
}
image, _ := d2render.NewSurface(160, int(realHeight), d2render.FilterNearest)
pixels := make([]byte, 4*160*realHeight)
mr.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, 160)
if newTileData != nil {
mr.decodeTileGfxData(newTileData.Blocks, &pixels, tileYOffset, 160)
}
if err := image.ReplacePixels(pixels); err != nil {
log.Panicf(err.Error())
}
mr.setImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex, image)
}
func (mr *MapRegion) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) {
for _, block := range blocks {
if block.Format == d2dt1.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}
blockX := int32(block.X)
blockY := int32(block.Y)
length := int32(256)
x := int32(0)
y := int32(0)
idx := 0
for length > 0 {
x = xjump[y]
n := nbpix[y]
length -= n
for n > 0 {
colorIndex := block.EncodedData[idx]
if colorIndex != 0 {
pixelColor := mr.palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
(*pixels)[offset+2] = pixelColor.B
(*pixels)[offset+3] = 255
}
x++
n--
idx++
}
y++
}
} else {
// RLE Encoding
blockX := int32(block.X)
blockY := int32(block.Y)
x := int32(0)
y := int32(0)
idx := 0
length := block.Length
for length > 0 {
b1 := block.EncodedData[idx]
b2 := block.EncodedData[idx+1]
idx += 2
length -= 2
if (b1 | b2) == 0 {
x = 0
y++
continue
}
x += int32(b1)
length -= int32(b2)
for b2 > 0 {
colorIndex := block.EncodedData[idx]
if colorIndex != 0 {
pixelColor := mr.palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
(*pixels)[offset+2] = pixelColor.B
(*pixels)[offset+3] = 255
}
idx++
x++
b2--
}
}
}
}
}
func loadPaletteForAct(levelType d2enum.RegionIdType) (*d2dat.DATPalette, error) {
var palettePath string
switch levelType {
case d2enum.RegionAct1Town, d2enum.RegionAct1Wilderness, d2enum.RegionAct1Cave, d2enum.RegionAct1Crypt,
d2enum.RegionAct1Monestary, d2enum.RegionAct1Courtyard, d2enum.RegionAct1Barracks,
d2enum.RegionAct1Jail, d2enum.RegionAct1Cathedral, d2enum.RegionAct1Catacombs, d2enum.RegionAct1Tristram:
palettePath = d2resource.PaletteAct1
break
case d2enum.RegionAct2Town, d2enum.RegionAct2Sewer, d2enum.RegionAct2Harem, d2enum.RegionAct2Basement,
d2enum.RegionAct2Desert, d2enum.RegionAct2Tomb, d2enum.RegionAct2Lair, d2enum.RegionAct2Arcane:
palettePath = d2resource.PaletteAct2
break
case d2enum.RegionAct3Town, d2enum.RegionAct3Jungle, d2enum.RegionAct3Kurast, d2enum.RegionAct3Spider,
d2enum.RegionAct3Dungeon, d2enum.RegionAct3Sewer:
palettePath = d2resource.PaletteAct3
break
case d2enum.RegionAct4Town, d2enum.RegionAct4Mesa, d2enum.RegionAct4Lava, d2enum.RegionAct5Lava:
palettePath = d2resource.PaletteAct4
break
case d2enum.RegonAct5Town, d2enum.RegionAct5Siege, d2enum.RegionAct5Barricade, d2enum.RegionAct5Temple,
d2enum.RegionAct5IceCaves, d2enum.RegionAct5Baal:
palettePath = d2resource.PaletteAct5
break
default:
return nil, errors.New("failed to find palette for region")
}
return d2asset.LoadPalette(palettePath)
}

View File

@ -1,72 +0,0 @@
package d2map
import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2term"
)
type MapRenderer struct {
mapEngine *MapEngine
viewport *Viewport
camera Camera
debugVisLevel int
}
func CreateMapRenderer(mapEngine *MapEngine) *MapRenderer {
result := &MapRenderer{
mapEngine: mapEngine,
viewport: NewViewport(0, 0, 800, 600),
}
result.viewport.SetCamera(&result.camera)
d2term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) {
result.debugVisLevel = level
})
return result
}
func (m *MapRenderer) SetMapEngine(mapEngine *MapEngine) {
m.mapEngine = mapEngine
}
func (m *MapRenderer) Render(target d2render.Surface) {
for _, region := range m.mapEngine.regions {
if region.isVisbile(m.viewport) {
region.renderPass1(m.viewport, target)
region.renderDebug(m.debugVisLevel, m.viewport, target)
region.renderPass2(m.mapEngine.entities, m.viewport, target)
region.renderPass3(m.viewport, target)
}
}
}
func (m *MapRenderer) MoveCameraTo(x, y float64) {
m.camera.MoveTo(x, y)
}
func (m *MapRenderer) MoveCameraBy(x, y float64) {
m.camera.MoveBy(x, y)
}
func (m *MapRenderer) ScreenToWorld(x, y int) (float64, float64) {
return m.viewport.ScreenToWorld(x, y)
}
func (m *MapRenderer) ScreenToOrtho(x, y int) (float64, float64) {
return m.viewport.ScreenToOrtho(x, y)
}
func (m *MapRenderer) WorldToOrtho(x, y float64) (float64, float64) {
return m.viewport.WorldToOrtho(x, y)
}
func (m *MapRenderer) ViewportToLeft() {
m.viewport.toLeft()
}
func (m *MapRenderer) ViewportToRight() {
m.viewport.toRight()
}
func (m *MapRenderer) ViewportDefault() {
m.viewport.resetAlign()
}

View File

@ -208,7 +208,7 @@ func (v *Button) SetEnabled(enabled bool) {
v.enabled = enabled
}
// GetSize returns the size of the button
// Size returns the size of the button
func (v *Button) GetSize() (int, int) {
return v.width, v.height
}

View File

@ -94,7 +94,7 @@ func (v *Label) SetText(newText string) {
v.imageData = nil
}
// GetSize returns the size of the label
// Size returns the size of the label
func (v Label) GetSize() (width, height int) {
v.cacheImage()
width = v.Width

View File

@ -6,6 +6,8 @@ import (
"os"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client"
@ -20,7 +22,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
@ -43,7 +44,7 @@ type CharacterSelect struct {
characterNameLabel [8]d2ui.Label
characterStatsLabel [8]d2ui.Label
characterExpLabel [8]d2ui.Label
characterImage [8]*d2map.Player
characterImage [8]*d2mapentity.Player
gameStates []*d2player.PlayerState
selectedCharacter int
mouseButtonPressed bool
@ -165,7 +166,7 @@ func (v *CharacterSelect) updateCharacterBoxes() {
v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String())
v.characterExpLabel[i].SetText(expText)
// TODO: Generate or load the object from the actual player data...
v.characterImage[i] = d2map.CreatePlayer("", "", 0, 0, 0,
v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0,
v.gameStates[idx].HeroType,
d2inventory.HeroObjects[v.gameStates[idx].HeroType],
)

View File

@ -3,17 +3,15 @@ package d2gamescreen
import (
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
)
type Game struct {
@ -21,22 +19,23 @@ type Game struct {
//pentSpinRight *d2ui.Sprite
//testLabel d2ui.Label
gameClient *d2client.GameClient
mapRenderer *d2map.MapRenderer
mapRenderer *d2maprenderer.MapRenderer
gameControls *d2player.GameControls // TODO: Hack
localPlayer *d2map.Player
localPlayer *d2mapentity.Player
lastLevelType int
ticksSinceLevelCheck float64
}
func CreateGame(gameClient *d2client.GameClient) *Game {
return &Game{
result := &Game{
gameClient: gameClient,
gameControls: nil,
localPlayer: nil,
lastLevelType: -1,
ticksSinceLevelCheck: 0,
mapRenderer: d2map.CreateMapRenderer(gameClient.MapEngine),
mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
}
return result
}
func (v *Game) OnLoad() error {
@ -50,6 +49,10 @@ func (v *Game) OnUnload() error {
}
func (v *Game) Render(screen d2render.Surface) error {
if v.gameClient.RegenMap {
v.gameClient.RegenMap = false
v.mapRenderer.RegenerateTileCache()
}
screen.Clear(color.Black)
v.mapRenderer.Render(screen)
if v.gameControls != nil {
@ -71,19 +74,15 @@ func (v *Game) Advance(tickTime float64) error {
if v.ticksSinceLevelCheck > 1.0 {
v.ticksSinceLevelCheck = 0
if v.localPlayer != nil {
region := v.gameClient.MapEngine.GetRegionAtTile(v.localPlayer.TileX, v.localPlayer.TileY)
if region != nil {
levelType := region.GetLevelType().Id
if levelType != v.lastLevelType {
v.lastLevelType = levelType
switch levelType {
case 1: // Rogue encampent
v.localPlayer.SetIsInTown(true)
d2audio.PlayBGM("/data/global/music/Act1/town1.wav")
case 2: // Blood Moore
v.localPlayer.SetIsInTown(false)
d2audio.PlayBGM("/data/global/music/Act1/wild.wav")
}
tile := v.gameClient.MapEngine.TileAt(v.localPlayer.TileX, v.localPlayer.TileY)
if tile != nil {
switch tile.RegionType {
case 1: // Rogue encampent
v.localPlayer.SetIsInTown(true)
d2audio.PlayBGM("/data/global/music/Act1/town1.wav")
case 2: // Blood Moore
v.localPlayer.SetIsInTown(false)
d2audio.PlayBGM("/data/global/music/Act1/wild.wav")
}
}
}

View File

@ -1,20 +1,20 @@
package d2gamescreen
import (
"math"
"math/rand"
"os"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
)
type RegionSpec struct {
@ -80,8 +80,8 @@ var regions = []RegionSpec{
type MapEngineTest struct {
gameState *d2player.PlayerState
mapEngine *d2map.MapEngine
mapRenderer *d2map.MapRenderer
mapEngine *d2mapengine.MapEngine
mapRenderer *d2maprenderer.MapRenderer
//TODO: this is region specific properties, should be refactored for multi-region rendering
currentRegion int
@ -105,7 +105,7 @@ func CreateMapEngineTest(currentRegion int, levelPreset int) *MapEngineTest {
}
func (met *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) {
d2map.InvalidateImageCache()
d2maprenderer.InvalidateImageCache()
for _, spec := range regions {
if spec.regionType == d2enum.RegionIdType(n) {
met.regionSpec = spec
@ -131,20 +131,20 @@ func (met *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) {
if n == 0 {
rand.Seed(time.Now().UnixNano())
met.mapEngine.GenerateAct1Overworld(true)
d2mapgen.GenerateAct1Overworld(met.mapEngine)
} else {
met.mapEngine = d2map.CreateMapEngine(0) // necessary for map name update
met.mapEngine = d2mapengine.CreateMapEngine() // necessary for map name update
met.mapEngine.GenerateMap(d2enum.RegionIdType(n), levelPreset, fileIndex, true)
met.mapRenderer.SetMapEngine(met.mapEngine)
met.mapEngine.RegenerateWalkPaths()
}
met.mapRenderer.SetMapEngine(met.mapEngine)
met.mapRenderer.MoveCameraTo(met.mapRenderer.WorldToOrtho(met.mapEngine.GetCenterPosition()))
}
func (met *MapEngineTest) OnLoad() error {
d2input.BindHandler(met)
met.mapEngine = d2map.CreateMapEngine(0)
met.mapRenderer = d2map.CreateMapRenderer(met.mapEngine)
met.mapEngine = d2mapengine.CreateMapEngine()
met.mapRenderer = d2maprenderer.CreateMapRenderer(met.mapEngine)
met.LoadRegionByIndex(met.currentRegion, met.levelPreset, met.fileIndex)
return nil
@ -158,121 +158,113 @@ func (met *MapEngineTest) OnUnload() error {
func (met *MapEngineTest) Render(screen d2render.Surface) error {
met.mapRenderer.Render(screen)
screenX, screenY := d2render.GetCursorPos()
worldX, worldY := met.mapRenderer.ScreenToWorld(screenX, screenY)
//subtileX := int(math.Ceil(math.Mod(worldX*10, 10))) / 2
//subtileY := int(math.Ceil(math.Mod(worldY*10, 10))) / 2
curRegion := met.mapEngine.GetRegionAtTile(int(worldX), int(worldY))
if curRegion == nil {
return nil
}
tileRect := curRegion.GetTileRect()
levelFilesToPick := make([]string, 0)
fileIndex := met.fileIndex
levelPreset := curRegion.GetLevelPreset()
regionPath := curRegion.GetPath()
for n, fileRecord := range levelPreset.Files {
if len(fileRecord) == 0 || fileRecord == "" || fileRecord == "0" {
continue
}
levelFilesToPick = append(levelFilesToPick, fileRecord)
if fileRecord == regionPath {
fileIndex = n
}
}
if met.fileIndex == -1 {
met.fileIndex = fileIndex
}
met.filesCount = len(levelFilesToPick)
tileX := int(math.Floor(worldX)) - tileRect.Left
tileY := int(math.Floor(worldY)) - tileRect.Top
subtileX := int((worldX - float64(int(worldX))) * 5)
subtileY := int((worldY - float64(int(worldY))) * 5)
regionWidth, regionHeight := curRegion.GetTileSize()
if tileX >= 0 && tileY >= 0 && tileX < regionWidth && tileY < regionHeight {
tile := curRegion.GetTile(tileX, tileY)
screen.PushTranslation(5, 5)
screen.DrawText("%d, %d (Tile %d.%d, %d.%d)", screenX, screenY, tileX, subtileX, tileY, subtileY)
screen.PushTranslation(0, 16)
screen.DrawText("Map: " + curRegion.GetLevelType().Name)
screen.PushTranslation(0, 16)
screen.DrawText("%v: %v/%v [%v, %v]", regionPath, fileIndex+1, met.filesCount, met.currentRegion, met.levelPreset)
screen.PushTranslation(0, 16)
screen.DrawText("N - next region, P - previous region")
screen.PushTranslation(0, 16)
screen.DrawText("Shift+N - next preset, Shift+P - previous preset")
screen.PushTranslation(0, 16)
screen.DrawText("Ctrl+N - next file, Ctrl+P - previous file")
screen.PushTranslation(0, 16)
popN := 7
if len(tile.Floors) > 0 {
screen.PushTranslation(0, 16)
screen.DrawText("Floors:")
screen.PushTranslation(16, 0)
for idx, floor := range tile.Floors {
popN++
screen.PushTranslation(0, 16)
tileData := curRegion.GetTileData(int32(floor.Style), int32(floor.Sequence), d2enum.Floor)
tileSubAttrs := d2dt1.SubTileFlags{}
if tileData != nil {
tileSubAttrs = *tileData.GetSubTileFlags(subtileX, subtileY)
}
screen.DrawText("Floor %v: [ANI:%t] %s", idx, floor.Animated, tileSubAttrs.DebugString())
}
screen.PushTranslation(-16, 0)
popN += 3
}
if len(tile.Walls) > 0 {
screen.PushTranslation(0, 16)
screen.DrawText("Walls:")
screen.PushTranslation(16, 0)
for idx, wall := range tile.Walls {
popN++
screen.PushTranslation(0, 16)
tileData := curRegion.GetTileData(int32(wall.Style), int32(wall.Sequence), d2enum.Floor)
tileSubAttrs := d2dt1.SubTileFlags{}
if tileData != nil {
tileSubAttrs = *tileData.GetSubTileFlags(subtileX, subtileY)
}
screen.DrawText("Wall %v: [HID:%t] %s", idx, wall.Hidden, tileSubAttrs.DebugString())
}
screen.PushTranslation(-16, 0)
popN += 3
}
if len(tile.Walls) > 0 {
screen.PushTranslation(0, 16)
screen.DrawText("Shadows:")
screen.PushTranslation(16, 0)
for idx, shadow := range tile.Shadows {
popN++
screen.PushTranslation(0, 16)
tileData := curRegion.GetTileData(int32(shadow.Style), int32(shadow.Sequence), d2enum.Floor)
tileSubAttrs := d2dt1.SubTileFlags{}
if tileData != nil {
tileSubAttrs = *tileData.GetSubTileFlags(subtileX, subtileY)
}
screen.DrawText("Wall %v: [HID:%t] %s", idx, shadow.Hidden, tileSubAttrs.DebugString())
}
screen.PushTranslation(-16, 0)
popN += 3
}
screen.PopN(popN)
}
//screenX, screenY := d2render.GetCursorPos()
//worldX, worldY := met.mapRenderer.ScreenToWorld(screenX, screenY)
//
//levelFilesToPick := make([]string, 0)
//fileIndex := met.fileIndex
//levelPreset := curRegion.LevelPreset()
//regionPath := curRegion.RegionPath()
//for n, fileRecord := range levelPreset.Files {
// if len(fileRecord) == 0 || fileRecord == "" || fileRecord == "0" {
// continue
// }
// levelFilesToPick = append(levelFilesToPick, fileRecord)
// if fileRecord == regionPath {
// fileIndex = n
// }
//}
//if met.fileIndex == -1 {
// met.fileIndex = fileIndex
//}
//met.filesCount = len(levelFilesToPick)
//
//tileX := int(math.Floor(worldX))
//tileY := int(math.Floor(worldY))
//
//subtileX := int((worldX - float64(int(worldX))) * 5)
//subtileY := int((worldY - float64(int(worldY))) * 5)
//
//regionWidth, regionHeight := curRegion.GetTileSize()
//if tileX >= 0 && tileY >= 0 && tileX < regionWidth && tileY < regionHeight {
// tile := curRegion.Tile(tileX, tileY)
// screen.PushTranslation(5, 5)
// screen.DrawText("%d, %d (Tile %d.%d, %d.%d)", screenX, screenY, tileX, subtileX, tileY, subtileY)
// screen.PushTranslation(0, 16)
// screen.DrawText("Map: " + curRegion.LevelType().Name)
// screen.PushTranslation(0, 16)
// screen.DrawText("%v: %v/%v [%v, %v]", regionPath, fileIndex+1, met.filesCount, met.currentRegion, met.levelPreset)
// screen.PushTranslation(0, 16)
// screen.DrawText("N - next region, P - previous region")
// screen.PushTranslation(0, 16)
// screen.DrawText("Shift+N - next preset, Shift+P - previous preset")
// screen.PushTranslation(0, 16)
// screen.DrawText("Ctrl+N - next file, Ctrl+P - previous file")
// screen.PushTranslation(0, 16)
// popN := 7
// if len(tile.Floors) > 0 {
// screen.PushTranslation(0, 16)
// screen.DrawText("Floors:")
// screen.PushTranslation(16, 0)
// for idx, floor := range tile.Floors {
// popN++
// screen.PushTranslation(0, 16)
// tileData := curRegion.TileData(int32(floor.Style), int32(floor.Sequence), d2enum.Floor)
// tileSubAttrs := d2dt1.SubTileFlags{}
// if tileData != nil {
// tileSubAttrs = *tileData.GetSubTileFlags(subtileX, subtileY)
// }
// screen.DrawText("Floor %v: [ANI:%t] %s", idx, floor.Animated, tileSubAttrs.DebugString())
//
// }
// screen.PushTranslation(-16, 0)
// popN += 3
// }
// if len(tile.Walls) > 0 {
// screen.PushTranslation(0, 16)
// screen.DrawText("Walls:")
// screen.PushTranslation(16, 0)
// for idx, wall := range tile.Walls {
// popN++
// screen.PushTranslation(0, 16)
// tileData := curRegion.TileData(int32(wall.Style), int32(wall.Sequence), d2enum.Floor)
// tileSubAttrs := d2dt1.SubTileFlags{}
// if tileData != nil {
// tileSubAttrs = *tileData.GetSubTileFlags(subtileX, subtileY)
// }
// screen.DrawText("Wall %v: [HID:%t] %s", idx, wall.Hidden, tileSubAttrs.DebugString())
//
// }
// screen.PushTranslation(-16, 0)
// popN += 3
// }
// if len(tile.Walls) > 0 {
// screen.PushTranslation(0, 16)
// screen.DrawText("Shadows:")
// screen.PushTranslation(16, 0)
// for idx, shadow := range tile.Shadows {
// popN++
// screen.PushTranslation(0, 16)
// tileData := curRegion.TileData(int32(shadow.Style), int32(shadow.Sequence), d2enum.Floor)
// tileSubAttrs := d2dt1.SubTileFlags{}
// if tileData != nil {
// tileSubAttrs = *tileData.GetSubTileFlags(subtileX, subtileY)
// }
// screen.DrawText("Wall %v: [HID:%t] %s", idx, shadow.Hidden, tileSubAttrs.DebugString())
//
// }
// screen.PushTranslation(-16, 0)
// popN += 3
// }
// screen.PopN(popN)
//}
return nil
}
func (met *MapEngineTest) Advance(tickTime float64) error {
met.mapEngine.Advance(tickTime)
met.mapRenderer.Advance(tickTime)
return nil
}

View File

@ -6,7 +6,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2term"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
@ -23,9 +25,9 @@ type Panel interface {
var missileID = 59
type GameControls struct {
hero *d2map.Player
mapEngine *d2map.MapEngine
mapRenderer *d2map.MapRenderer
hero *d2mapentity.Player
mapEngine *d2mapengine.MapEngine
mapRenderer *d2maprenderer.MapRenderer
inventory *Inventory
heroStats *HeroStats
escapeMenu *EscapeMenu
@ -38,7 +40,7 @@ type GameControls struct {
skillIcon *d2ui.Sprite
}
func NewGameControls(hero *d2map.Player, mapEngine *d2map.MapEngine, mapRenderer *d2map.MapRenderer, inputListener InputCallbackListener) *GameControls {
func NewGameControls(hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine, mapRenderer *d2maprenderer.MapRenderer, inputListener InputCallbackListener) *GameControls {
d2term.BindAction("setmissile", "set missile id to summon on right click", func(id int) {
missileID = id
})
@ -91,7 +93,7 @@ func (g *GameControls) OnMouseButtonDown(event d2input.MouseEvent) bool {
}
if event.Button == d2input.MouseButtonRight {
missile, err := d2map.CreateMissile(
missile, err := d2mapentity.CreateMissile(
int(g.hero.AnimatedComposite.LocationX),
int(g.hero.AnimatedComposite.LocationY),
d2datadict.Missiles[missileID],

View File

@ -4,10 +4,13 @@ import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient"
@ -19,16 +22,17 @@ type GameClient struct {
clientConnection ClientConnection
connectionType d2clientconnectiontype.ClientConnectionType
GameState *d2player.PlayerState
MapEngine *d2map.MapEngine
MapEngine *d2mapengine.MapEngine
PlayerId string
Players map[string]*d2map.Player
Players map[string]*d2mapentity.Player
Seed int64
RegenMap bool
}
func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) {
result := &GameClient{
MapEngine: d2map.CreateMapEngine(0),
Players: make(map[string]*d2map.Player, 0),
MapEngine: d2mapengine.CreateMapEngine(), // TODO: Mapgen - Needs levels.txt stuff
Players: make(map[string]*d2mapentity.Player, 0),
connectionType: connectionType,
}
@ -64,8 +68,9 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
mapData := packet.PacketData.(d2netpacket.GenerateMapPacket)
switch mapData.RegionType {
case d2enum.RegionAct1Town:
g.MapEngine.GenerateAct1Overworld(true)
d2mapgen.GenerateAct1Overworld(g.MapEngine)
}
g.RegenMap = true
break
case d2netpackettype.UpdateServerInfo:
serverInfo := packet.PacketData.(d2netpacket.UpdateServerInfoPacket)
@ -76,7 +81,7 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
break
case d2netpackettype.AddPlayer:
player := packet.PacketData.(d2netpacket.AddPlayerPacket)
newPlayer := d2map.CreatePlayer(player.Id, player.Name, player.X, player.Y, 0, player.HeroType, player.Equipment)
newPlayer := d2mapentity.CreatePlayer(player.Id, player.Name, player.X, player.Y, 0, player.HeroType, player.Equipment)
g.Players[newPlayer.Id] = newPlayer
g.MapEngine.AddEntity(newPlayer)
break
@ -86,7 +91,13 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
path, _, found := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY)
if found {
player.AnimatedComposite.SetPath(path, func() {
if g.MapEngine.GetRegionAtTile(player.TileX, player.TileY).GetLevelType().Id == int(d2enum.RegionAct1Town) {
tile := g.MapEngine.TileAt(player.TileX, player.TileY)
if tile == nil {
return
}
regionType := tile.RegionType
if regionType == d2enum.RegionAct1Town {
player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerTownNeutral.String())
} else {
player.AnimatedComposite.SetAnimationMode(d2enum.AnimationModePlayerNeutral.String())

View File

@ -9,9 +9,13 @@ import (
"log"
"net"
"strings"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
@ -21,7 +25,7 @@ import (
type GameServer struct {
clientConnections map[string]ClientConnection
mapEngines []*d2map.MapEngine
mapEngines []*d2mapengine.MapEngine
scriptEngine *d2script.ScriptEngine
udpConnection *net.UDPConn
seed int64
@ -38,13 +42,14 @@ func Create(openNetworkServer bool) {
singletonServer = &GameServer{
clientConnections: make(map[string]ClientConnection),
mapEngines: make([]*d2map.MapEngine, 0),
mapEngines: make([]*d2mapengine.MapEngine, 0),
scriptEngine: d2script.CreateScriptEngine(),
seed: 1592539977884044000, //time.Now().UnixNano(),
seed: time.Now().UnixNano(),
}
mapEngine := d2map.CreateMapEngine(singletonServer.seed)
mapEngine.GenerateAct1Overworld(false)
mapEngine := d2mapengine.CreateMapEngine()
mapEngine.ResetMap(0, d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff
d2mapgen.GenerateAct1Overworld(mapEngine)
singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine)
singletonServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value {