1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-09 09:20:44 +00:00

Engine and region reorganization in preperation for spatial partitioning (#249)

* Engine and region reorg
* Remove unnecessary file diff
* Remove extra render of debug mesh
This commit is contained in:
Alex Yatskov 2019-12-12 21:33:11 -08:00 committed by Tim Sarbin
parent 5b6da2434f
commit a194913609
8 changed files with 674 additions and 695 deletions

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
@ -27,7 +26,8 @@ type Game struct {
pentSpinLeft d2render.Sprite
pentSpinRight d2render.Sprite
testLabel d2ui.Label
mapEngine *d2mapengine.Engine
mapEngine *d2mapengine.MapEngine
hero *d2core.Hero
}
func CreateGame(
@ -72,25 +72,23 @@ func (v *Game) Load() []func() {
},
func() {
v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager, v.fileProvider)
// TODO: This needs to be different depending on the act of the player
v.mapEngine.GenerateMap(d2enum.RegionAct1Town, 1, 0)
v.mapEngine.SetRegion(0)
region := v.mapEngine.Region()
rx, ry := d2helper.IsoToScreen(region.Region.StartX, region.Region.StartY, 0, 0)
v.mapEngine.CenterCameraOn(rx, ry)
v.mapEngine.Hero = d2core.CreateHero(
int32((region.Region.StartX*5)+3),
int32((region.Region.StartY*5)+3),
startX, startY := v.mapEngine.GetStartPosition()
v.hero = d2core.CreateHero(
int32(startX*5)+3,
int32(startY*5)+3,
0,
v.gameState.HeroType,
v.gameState.Equipment,
v.fileProvider)
v.fileProvider,
)
v.mapEngine.AddEntity(v.hero)
},
}
}
func (v *Game) Unload() {
}
func (v Game) Render(screen *ebiten.Image) {
@ -99,40 +97,13 @@ func (v Game) Render(screen *ebiten.Image) {
}
func (v *Game) Update(tickTime float64) {
// TODO: Pathfinding
v.mapEngine.Advance(tickTime)
if v.mapEngine.Hero.AnimatedEntity.LocationX != v.mapEngine.Hero.AnimatedEntity.TargetX ||
v.mapEngine.Hero.AnimatedEntity.LocationY != v.mapEngine.Hero.AnimatedEntity.TargetY {
v.mapEngine.Hero.AnimatedEntity.Step(tickTime)
}
for _, npc := range v.mapEngine.Region().Region.NPCs {
if npc.HasPaths &&
npc.AnimatedEntity.LocationX == npc.AnimatedEntity.TargetX &&
npc.AnimatedEntity.LocationY == npc.AnimatedEntity.TargetY &&
npc.AnimatedEntity.Wait() {
// If at the target, set target to the next path.
path := npc.NextPath()
npc.AnimatedEntity.SetTarget(
float64(path.X),
float64(path.Y),
path.Action,
)
}
if npc.AnimatedEntity.LocationX != npc.AnimatedEntity.TargetX ||
npc.AnimatedEntity.LocationY != npc.AnimatedEntity.TargetY {
npc.AnimatedEntity.Step(tickTime)
}
}
rx, ry := v.mapEngine.WorldToOrtho(v.hero.AnimatedEntity.LocationX/5, v.hero.AnimatedEntity.LocationY/5)
v.mapEngine.MoveCameraTo(rx, ry)
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
px, py := v.mapEngine.ScreenToIso(ebiten.CursorPosition())
v.mapEngine.Hero.AnimatedEntity.SetTarget(px*5, py*5, 1)
px, py := v.mapEngine.ScreenToWorld(ebiten.CursorPosition())
v.hero.AnimatedEntity.SetTarget(px*5, py*5, 1)
}
rx, ry := d2helper.IsoToScreen(v.mapEngine.Hero.AnimatedEntity.LocationX/5, v.mapEngine.Hero.AnimatedEntity.LocationY/5, 0, 0)
v.mapEngine.CenterCameraOn(rx, ry)
}

View File

@ -5,8 +5,6 @@ import (
"math"
"os"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
@ -88,7 +86,7 @@ type MapEngineTest struct {
fileProvider d2interface.FileProvider
sceneProvider d2coreinterface.SceneProvider
gameState *d2core.GameState
mapEngine *d2mapengine.Engine
mapEngine *d2mapengine.MapEngine
//TODO: this is region specific properties, should be refactored for multi-region rendering
currentRegion int
@ -96,6 +94,7 @@ type MapEngineTest struct {
fileIndex int
regionSpec RegionSpec
filesCount int
debugVisLevel int
}
func CreateMapEngineTest(
@ -149,9 +148,8 @@ func (v *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) {
v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager, v.fileProvider) // necessary for map name update
v.mapEngine.GenerateMap(d2enum.RegionIdType(n), levelPreset, fileIndex)
}
isox, isoy := d2helper.IsoToScreen(float64(v.mapEngine.GetRegion(0).Rect.Width)/2,
float64(v.mapEngine.GetRegion(0).Rect.Height)/2, 0, 0)
v.mapEngine.CenterCameraOn(isox, isoy)
v.mapEngine.MoveCameraTo(v.mapEngine.WorldToOrtho(v.mapEngine.GetCenterPosition()))
}
func (v *MapEngineTest) Load() []func() {
@ -172,32 +170,36 @@ func (v *MapEngineTest) Unload() {
func (v *MapEngineTest) Render(screen *ebiten.Image) {
v.mapEngine.Render(screen)
actualX := v.uiManager.CursorX
actualY := v.uiManager.CursorY
tileX, tileY := v.mapEngine.ScreenToIso(actualX, actualY)
subtileX := int(math.Ceil(math.Mod((tileX*10), 10))) / 2
subtileY := int(math.Ceil(math.Mod((tileY*10), 10))) / 2
curRegion := v.mapEngine.GetRegionAt(int(tileX), int(tileY))
screenX := v.uiManager.CursorX
screenY := v.uiManager.CursorY
worldX, worldY := v.mapEngine.ScreenToWorld(screenX, screenY)
subtileX := int(math.Ceil(math.Mod((worldX*10), 10))) / 2
subtileY := int(math.Ceil(math.Mod((worldY*10), 10))) / 2
curRegion := v.mapEngine.GetRegionAtTile(int(worldX), int(worldY))
if curRegion == nil {
return
}
tileRect := curRegion.GetTileRect()
line := fmt.Sprintf("%d, %d (Tile %d.%d, %d.%d)",
actualX,
actualY,
int(math.Floor(tileX))-curRegion.Rect.Left,
screenX,
screenY,
int(math.Floor(worldX))-tileRect.Left,
subtileX,
int(math.Floor(tileY))-curRegion.Rect.Top,
int(math.Floor(worldY))-tileRect.Top,
subtileY,
)
levelFilesToPick := make([]string, 0)
fileIndex := v.fileIndex
for n, fileRecord := range curRegion.Region.LevelPreset.Files {
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 == curRegion.Region.RegionPath {
if fileRecord == regionPath {
fileIndex = n
}
}
@ -206,14 +208,16 @@ func (v *MapEngineTest) Render(screen *ebiten.Image) {
}
v.filesCount = len(levelFilesToPick)
ebitenutil.DebugPrintAt(screen, line, 5, 5)
ebitenutil.DebugPrintAt(screen, "Map: "+curRegion.Region.LevelType.Name, 5, 17)
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("%v: %v/%v [%v, %v]", curRegion.Region.RegionPath, fileIndex+1, v.filesCount, v.currentRegion, v.levelPreset), 5, 29)
ebitenutil.DebugPrintAt(screen, "Map: "+curRegion.GetLevelType().Name, 5, 17)
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("%v: %v/%v [%v, %v]", regionPath, fileIndex+1, v.filesCount, v.currentRegion, v.levelPreset), 5, 29)
ebitenutil.DebugPrintAt(screen, "N - next region, P - previous region", 5, 41)
ebitenutil.DebugPrintAt(screen, "Shift+N - next preset, Shift+P - previous preset", 5, 53)
ebitenutil.DebugPrintAt(screen, "Ctrl+N - next file, Ctrl+P - previous file", 5, 65)
}
func (v *MapEngineTest) Update(tickTime float64) {
v.mapEngine.Advance(tickTime)
ctrlPressed := v.uiManager.KeyPressed(ebiten.KeyControl)
shiftPressed := v.uiManager.KeyPressed(ebiten.KeyShift)
@ -236,11 +240,13 @@ func (v *MapEngineTest) Update(tickTime float64) {
}
if inpututil.IsKeyJustPressed(ebiten.KeyF7) {
if v.mapEngine.ShowTiles < 2 {
v.mapEngine.ShowTiles++
if v.debugVisLevel < 2 {
v.debugVisLevel++
} else {
v.mapEngine.ShowTiles = 0
v.debugVisLevel = 0
}
v.mapEngine.SetDebugVisLevel(v.debugVisLevel)
}
if v.uiManager.KeyPressed(ebiten.KeyEscape) {

View File

@ -43,6 +43,18 @@ func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment Chara
return result
}
func (v *Hero) Advance(tickTime float64) {
// TODO: Pathfinding
if v.AnimatedEntity.LocationX != v.AnimatedEntity.TargetX ||
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
v.AnimatedEntity.Step(tickTime)
}
}
func (v *Hero) Render(target *ebiten.Image, offsetX, offsetY int) {
v.AnimatedEntity.Render(target, offsetX, offsetY)
}
func (v *Hero) GetPosition() (float64, float64) {
return v.AnimatedEntity.GetPosition()
}

View File

@ -46,3 +46,27 @@ func (v *NPC) SetPaths(paths []d2common.Path) {
func (v *NPC) Render(target *ebiten.Image, offsetX, offsetY int) {
v.AnimatedEntity.Render(target, offsetX, offsetY)
}
func (v *NPC) GetPosition() (float64, float64) {
return v.AnimatedEntity.GetPosition()
}
func (v *NPC) Advance(tickTime float64) {
if v.HasPaths &&
v.AnimatedEntity.LocationX == v.AnimatedEntity.TargetX &&
v.AnimatedEntity.LocationY == v.AnimatedEntity.TargetY &&
v.AnimatedEntity.Wait() {
// If at the target, set target to the next path.
path := v.NextPath()
v.AnimatedEntity.SetTarget(
float64(path.X),
float64(path.Y),
path.Action,
)
}
if v.AnimatedEntity.LocationX != v.AnimatedEntity.TargetX ||
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
v.AnimatedEntity.Step(tickTime)
}
}

View File

@ -2,6 +2,11 @@ package d2render
import (
"fmt"
"log"
"math"
"math/rand"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2data"
@ -10,10 +15,6 @@ import (
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/hajimehoshi/ebiten"
"log"
"math"
"math/rand"
"strings"
)
var DccLayerNames = []string{"HD", "TR", "LG", "RA", "LA", "RH", "LH", "SH", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"}
@ -416,3 +417,11 @@ func angleToDirection(angle float64, numberOfDirections int) int {
return newDirection
}
func (v *AnimatedEntity) Advance(tickTime float64) {
}
func (v *AnimatedEntity) GetPosition() (float64, float64) {
return float64(v.TileX), float64(v.TileY)
}

View File

@ -1,52 +1,40 @@
package d2mapengine
import (
"fmt"
"image/color"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
)
type RegionTile struct {
tileX, tileY int // tile coordinates
offX, offY int // world space coordinates of tile origin
type MapEntity interface {
Render(target *ebiten.Image, screenX, screenY int)
GetPosition() (float64, float64)
Advance(tickTime float64)
}
type EngineRegion struct {
Rect d2common.Rectangle
Region *Region
Tiles []RegionTile
}
type Engine struct {
type MapEngine struct {
soundManager *d2audio.Manager
gameState *d2core.GameState
fileProvider d2interface.FileProvider
region int
regions []EngineRegion
ShowTiles int
Hero *d2core.Hero
debugVisLevel int
regions []*MapRegion
entities []MapEntity
viewport *Viewport
camera Camera
}
func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager, fileProvider d2interface.FileProvider) *Engine {
engine := &Engine{
func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager, fileProvider d2interface.FileProvider) *MapEngine {
engine := &MapEngine{
gameState: gameState,
soundManager: soundManager,
fileProvider: fileProvider,
regions: make([]EngineRegion, 0),
viewport: NewViewport(0, 0, 800, 600),
}
@ -54,315 +42,108 @@ func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager,
return engine
}
func (v *Engine) Region() *EngineRegion {
return &v.regions[v.region]
func (me *MapEngine) GetStartPosition() (float64, float64) {
var startX, startY float64
if len(me.regions) > 0 {
region := me.regions[0]
startX, startY = region.getStartTilePosition()
}
return startX, startY
}
func (v *Engine) SetRegion(region int) {
v.region = region
func (me *MapEngine) GetCenterPosition() (float64, float64) {
var centerX, centerY float64
if len(me.regions) > 0 {
region := me.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 (v *Engine) GetRegion(regionIndex int) *EngineRegion {
return &v.regions[regionIndex]
func (me *MapEngine) MoveCameraTo(x, y float64) {
me.camera.MoveTo(x, y)
}
func (v *Engine) CenterCameraOn(x, y float64) {
v.camera.MoveTo(x, y)
func (me *MapEngine) MoveCameraBy(x, y float64) {
me.camera.MoveBy(x, y)
}
func (v *Engine) MoveCameraBy(x, y float64) {
v.camera.MoveBy(x, y)
func (me *MapEngine) ScreenToWorld(x, y int) (float64, float64) {
return me.viewport.ScreenToWorld(x, y)
}
func (v *Engine) ScreenToIso(x, y int) (float64, float64) {
return v.viewport.ScreenToIso(x, y)
func (me *MapEngine) ScreenToOrtho(x, y int) (float64, float64) {
return me.viewport.ScreenToOrtho(x, y)
}
func (v *Engine) ScreenToWorld(x, y int) (float64, float64) {
return v.viewport.ScreenToWorld(x, y)
func (me *MapEngine) WorldToOrtho(x, y float64) (float64, float64) {
return me.viewport.WorldToOrtho(x, y)
}
func (v *Engine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int) {
region := LoadRegion(v.gameState.Seed, regionType, levelPreset, v.fileProvider, fileIndex)
fmt.Printf("Loading region: %v\n", region.RegionPath)
v.regions = append(v.regions, EngineRegion{
Rect: d2common.Rectangle{0, 0, int(region.TileWidth), int(region.TileHeight)},
Region: region,
})
func (me *MapEngine) SetDebugVisLevel(debugVisLevel int) {
me.debugVisLevel = debugVisLevel
}
for i, _ := range v.regions {
v.GenTiles(&v.regions[i])
v.GenTilesCache(&v.regions[i])
func (me *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int) {
region, entities := loadRegion(me.gameState.Seed, 0, 0, regionType, levelPreset, me.fileProvider, fileIndex)
me.regions = append(me.regions, region)
me.entities = append(me.entities, entities...)
}
func (me *MapEngine) GenerateAct1Overworld() {
me.soundManager.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here
region, entities := loadRegion(me.gameState.Seed, 0, 0, d2enum.RegionAct1Town, 1, me.fileProvider, -1)
me.regions = append(me.regions, region)
me.entities = append(me.entities, entities...)
if strings.Contains(region.regionPath, "E1") {
region, entities := loadRegion(me.gameState.Seed, int(region.tileRect.Width-1), 0, d2enum.RegionAct1Town, 2, me.fileProvider, -1)
me.regions = append(me.regions, region)
me.entities = append(me.entities, entities...)
} else if strings.Contains(region.regionPath, "S1") {
region, entities := loadRegion(me.gameState.Seed, 0, int(region.tileRect.Height-1), d2enum.RegionAct1Town, 3, me.fileProvider, -1)
me.regions = append(me.regions, region)
me.entities = append(me.entities, entities...)
}
}
func (v *Engine) GenerateAct1Overworld() {
v.soundManager.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here
region := LoadRegion(v.gameState.Seed, d2enum.RegionAct1Town, 1, v.fileProvider, -1)
v.regions = append(v.regions, EngineRegion{
Rect: d2common.Rectangle{0, 0, int(region.TileWidth), int(region.TileHeight)},
Region: region,
})
if strings.Contains(region.RegionPath, "E1") {
region2 := LoadRegion(v.gameState.Seed, d2enum.RegionAct1Town, 2, v.fileProvider, -1)
v.regions = append(v.regions, EngineRegion{
Rect: d2common.Rectangle{int(region.TileWidth - 1), 0, int(region2.TileWidth), int(region2.TileHeight)},
Region: region2,
})
} else if strings.Contains(region.RegionPath, "S1") {
region2 := LoadRegion(v.gameState.Seed, d2enum.RegionAct1Town, 3, v.fileProvider, -1)
v.regions = append(v.regions, EngineRegion{
Rect: d2common.Rectangle{0, int(region.TileHeight - 1), int(region2.TileWidth), int(region2.TileHeight)},
Region: region2,
})
}
for i, _ := range v.regions {
v.GenTiles(&v.regions[i])
v.GenTilesCache(&v.regions[i])
}
v.camera.MoveTo(v.viewport.IsoToWorld(region.StartX, region.StartY))
}
func (v *Engine) GetRegionAt(x, y int) *EngineRegion {
for _, region := range v.regions {
if region.Rect.IsInRect(x, y) {
return &region
func (me *MapEngine) GetRegionAtTile(x, y int) *MapRegion {
for _, region := range me.regions {
if region.tileRect.IsInRect(x, y) {
return region
}
}
return nil
}
func (v *Engine) Render(target *ebiten.Image) {
for _, region := range v.regions {
// X position of leftmost point of region
left := float64((region.Rect.Left - region.Rect.Bottom()) * 80)
// Y position of top of region
top := float64((region.Rect.Left + region.Rect.Top) * 40)
// X of right
right := float64((region.Rect.Right() - region.Rect.Top) * 80)
// Y of bottom
bottom := float64((region.Rect.Right() + region.Rect.Bottom()) * 40)
func (me *MapEngine) AddEntity(entity MapEntity) {
me.entities = append(me.entities, entity)
}
if v.viewport.IsWorldRectVisible(left, top, right, bottom) {
v.RenderRegion(region, target)
func (me *MapEngine) Advance(tickTime float64) {
for _, region := range me.regions {
if region.isVisbile(me.viewport) {
region.advance(tickTime)
}
}
for _, entity := range me.entities {
entity.Advance(tickTime)
}
}
func (v *Engine) GenTiles(region *EngineRegion) {
for y := 0; y < int(region.Region.TileHeight); y++ {
offX := -((y + region.Rect.Top) * 80) + (region.Rect.Left * 80)
offY := ((y + region.Rect.Top) * 40) + (region.Rect.Left * 40)
for x := 0; x < int(region.Region.TileWidth); x++ {
region.Tiles = append(region.Tiles, RegionTile{
tileX: x,
tileY: y,
offX: offX,
offY: offY,
})
offX += 80
offY += 40
}
}
}
func (v *Engine) GenTilesCache(region *EngineRegion) {
for tileIdx := range region.Tiles {
t := &region.Tiles[tileIdx]
if t.tileY < len(region.Region.DS1.Tiles) && t.tileX < len(region.Region.DS1.Tiles[t.tileY]) {
tile := &region.Region.DS1.Tiles[t.tileY][t.tileX]
for i := range tile.Floors {
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
continue
}
region.Region.generateFloorCache(&tile.Floors[i], t.tileX, t.tileY)
}
for i := range tile.Shadows {
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
continue
}
region.Region.generateShadowCache(&tile.Shadows[i], t.tileX, t.tileY)
}
for i := range tile.Walls {
if tile.Walls[i].Hidden || tile.Walls[i].Prop1 == 0 {
continue
}
region.Region.generateWallCache(&tile.Walls[i], t.tileX, t.tileY)
}
}
}
}
func (v *Engine) RenderRegion(region EngineRegion, target *ebiten.Image) {
for tileIdx := range region.Tiles {
if v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) {
region.Region.UpdateAnimations()
v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY))
v.RenderPass1(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
v.DrawTileLines(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
v.viewport.PopTranslation()
}
}
for tileIdx := range region.Tiles {
if v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) {
v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY))
v.RenderPass2(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
v.viewport.PopTranslation()
}
}
for tileIdx := range region.Tiles {
if v.viewport.IsWorldTileVisbile(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top)) {
v.viewport.PushTranslation(float64(region.Tiles[tileIdx].offX), float64(region.Tiles[tileIdx].offY))
v.RenderPass3(region.Region, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
v.viewport.PopTranslation()
}
}
}
func (v *Engine) RenderPass1(region *Region, x, y int, target *ebiten.Image) {
tile := region.DS1.Tiles[y][x]
// Draw lower walls
for i := range tile.Walls {
if !tile.Walls[i].Type.LowerWall() || tile.Walls[i].Prop1 == 0 || tile.Walls[i].Hidden {
continue
}
region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeWalls, i, target)
}
for i := range tile.Floors {
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
continue
}
region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeFloors, i, target)
}
for i := range tile.Shadows {
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
continue
}
region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeShadows, i, target)
}
}
func (v *Engine) RenderPass2(region *Region, x, y int, target *ebiten.Image) {
tile := region.DS1.Tiles[y][x]
// Draw upper walls
for i := range tile.Walls {
if !tile.Walls[i].Type.UpperWall() || tile.Walls[i].Hidden {
continue
}
region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeWalls, i, target)
}
screenX, screenY := v.viewport.WorldToScreen(v.viewport.GetTranslation())
for _, obj := range region.AnimationEntities {
if obj.TileX == x && obj.TileY == y {
obj.Render(target, screenX, screenY)
}
}
for _, npc := range region.NPCs {
if npc.AnimatedEntity.TileX == x && npc.AnimatedEntity.TileY == y {
npc.Render(target, screenX, screenY)
}
}
if v.Hero != nil && v.Hero.AnimatedEntity.TileX == x && v.Hero.AnimatedEntity.TileY == y {
v.Hero.Render(target, screenX, screenY)
}
}
func (v *Engine) RenderPass3(region *Region, x, y int, target *ebiten.Image) {
tile := region.DS1.Tiles[y][x]
// Draw ceilings
for i := range tile.Walls {
if tile.Walls[i].Type != d2enum.Roof {
continue
}
region.RenderTile(v.viewport, x, y, d2enum.RegionLayerTypeWalls, i, target)
}
}
func (v *Engine) DrawTileLines(region *Region, x, y int, target *ebiten.Image) {
if v.ShowTiles > 0 {
subtileColor := color.RGBA{80, 80, 255, 100}
tileColor := color.RGBA{255, 255, 255, 255}
screenX1, screenY1 := v.viewport.IsoToScreen(float64(x), float64(y))
screenX2, screenY2 := v.viewport.IsoToScreen(float64(x+1), float64(y))
screenX3, screenY3 := v.viewport.IsoToScreen(float64(x), float64(y+1))
ebitenutil.DrawLine(
target,
float64(screenX1),
float64(screenY1),
float64(screenX2),
float64(screenY2),
tileColor,
)
ebitenutil.DrawLine(
target,
float64(screenX1),
float64(screenY1),
float64(screenX3),
float64(screenY3),
tileColor,
)
ebitenutil.DebugPrintAt(
target,
fmt.Sprintf("%v,%v", x, y),
screenX1-10,
screenY1+10,
)
if v.ShowTiles > 1 {
for i := 1; i <= 4; i++ {
x := i * 16
y := i * 8
ebitenutil.DrawLine(
target,
float64(screenX1-x),
float64(screenY1+y),
float64(screenX1-x+80),
float64(screenY1+y+40),
subtileColor,
)
ebitenutil.DrawLine(
target,
float64(screenX1+x),
float64(screenY1+y),
float64(screenX1+x-80),
float64(screenY1+y+40),
subtileColor,
)
}
tile := region.DS1.Tiles[y][x]
for i := range tile.Floors {
ebitenutil.DebugPrintAt(
target,
fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence),
screenX1-20,
screenY1+10+(i+1)*14,
)
}
func (me *MapEngine) Render(target *ebiten.Image) {
for _, region := range me.regions {
if region.isVisbile(me.viewport) {
region.renderPass1(me.viewport, target)
region.renderDebug(me.debugVisLevel, me.viewport, target)
region.renderPass2(me.entities, me.viewport, target)
region.renderPass3(me.viewport, target)
}
}
}

View File

@ -1,173 +1,171 @@
package d2mapengine
import (
"fmt"
"image/color"
"log"
"math"
"math/rand"
"strconv"
"github.com/OpenDiablo2/D2Shared/d2data/d2dt1"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2ds1"
"github.com/OpenDiablo2/D2Shared/d2data/d2dt1"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/hajimehoshi/ebiten"
)
type Region struct {
RegionPath string
LevelType d2datadict.LevelTypeRecord
LevelPreset d2datadict.LevelPresetRecord
TileWidth int32
TileHeight int32
Tiles []d2dt1.Tile
DS1 d2ds1.DS1
Palette d2datadict.PaletteRec
AnimationEntities []d2render.AnimatedEntity
NPCs []*d2core.NPC
StartX float64
StartY float64
type MapRegion struct {
tileRect d2common.Rectangle
regionPath string
levelType d2datadict.LevelTypeRecord
levelPreset d2datadict.LevelPresetRecord
tiles []d2dt1.Tile
ds1 d2ds1.DS1
palette d2datadict.PaletteRec
startX float64
startY float64
imageCacheRecords map[uint32]*ebiten.Image
seed int64
currentFrame byte
lastFrameTime float64
}
func LoadRegion(seed int64, levelType d2enum.RegionIdType, levelPreset int, fileProvider d2interface.FileProvider, fileIndex int) *Region {
result := &Region{
LevelType: d2datadict.LevelTypes[levelType],
LevelPreset: d2datadict.LevelPresets[levelPreset],
Tiles: make([]d2dt1.Tile, 0),
func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.RegionIdType, levelPreset int, fileProvider d2interface.FileProvider, fileIndex int) (*MapRegion, []MapEntity) {
region := &MapRegion{
levelType: d2datadict.LevelTypes[levelType],
levelPreset: d2datadict.LevelPresets[levelPreset],
imageCacheRecords: map[uint32]*ebiten.Image{},
seed: seed,
}
result.Palette = d2datadict.Palettes[d2enum.PaletteType("act"+strconv.Itoa(int(result.LevelType.Act)))]
// Temp hack
region.palette = d2datadict.Palettes[d2enum.PaletteType("act"+strconv.Itoa(int(region.levelType.Act)))]
if levelType == d2enum.RegionAct5Lava {
result.Palette = d2datadict.Palettes[d2enum.PaletteType("act4")]
region.palette = d2datadict.Palettes[d2enum.PaletteType("act4")]
}
//bm := result.levelPreset.Dt1Mask
for _, levelTypeDt1 := range result.LevelType.Files {
/*
if bm&1 == 0 {
bm >>= 1
continue
}
bm >>= 1
*/
if len(levelTypeDt1) == 0 || levelTypeDt1 == "" || levelTypeDt1 == "0" {
continue
for _, levelTypeDt1 := range region.levelType.Files {
if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" {
dt1 := d2dt1.LoadDT1("/data/global/tiles/"+levelTypeDt1, fileProvider)
region.tiles = append(region.tiles, dt1.Tiles...)
}
dt1 := d2dt1.LoadDT1("/data/global/tiles/"+levelTypeDt1, fileProvider)
result.Tiles = append(result.Tiles, dt1.Tiles...)
}
levelFilesToPick := make([]string, 0)
for _, fileRecord := range result.LevelPreset.Files {
if len(fileRecord) == 0 || fileRecord == "" || fileRecord == "0" {
continue
var levelFilesToPick []string
for _, fileRecord := range region.levelPreset.Files {
if len(fileRecord) != 0 && fileRecord != "" && fileRecord != "0" {
levelFilesToPick = append(levelFilesToPick, fileRecord)
}
levelFilesToPick = append(levelFilesToPick, fileRecord)
}
levelIndex := int(math.Round(float64(len(levelFilesToPick)-1) * rand.Float64()))
if fileIndex >= 0 && fileIndex < len(levelFilesToPick) {
levelIndex = fileIndex
}
levelFile := levelFilesToPick[levelIndex]
result.RegionPath = levelFile
result.DS1 = d2ds1.LoadDS1("/data/global/tiles/"+levelFile, fileProvider)
result.TileWidth = result.DS1.Width
result.TileHeight = result.DS1.Height
result.currentFrame = 0
result.loadObjects(fileProvider)
result.loadSpecials()
return result
region.regionPath = levelFilesToPick[levelIndex]
region.ds1 = d2ds1.LoadDS1("/data/global/tiles/"+region.regionPath, fileProvider)
region.tileRect = d2common.Rectangle{
Left: tileOffsetX,
Top: tileOffsetY,
Width: int(region.ds1.Width),
Height: int(region.ds1.Height),
}
entities := region.loadEntities(fileProvider)
region.loadSpecials()
region.generateTileCache()
return region, entities
}
func (v *Region) loadSpecials() {
for y := range v.DS1.Tiles {
for x := range v.DS1.Tiles[y] {
for _, wall := range v.DS1.Tiles[y][x].Walls {
if wall.Type != 10 {
continue
}
if wall.Style == 30 && wall.Sequence == 0 {
v.StartX = float64(x) + 0.5
v.StartY = float64(y) + 0.5
log.Printf("Starting location: %d, %d", x, y)
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, mr.startY = mr.getTileWorldPosition(tileX, tileY)
mr.startX += 0.5
mr.startY += 0.5
return
}
}
}
}
}
func (v *Region) loadObjects(fileProvider d2interface.FileProvider) {
v.AnimationEntities = make([]d2render.AnimatedEntity, 0)
v.NPCs = make([]*d2core.NPC, 0)
for _, object := range v.DS1.Objects {
func (mr *MapRegion) loadEntities(fileProvider d2interface.FileProvider) []MapEntity {
var entities []MapEntity
for _, object := range mr.ds1.Objects {
worldX, worldY := mr.getTileWorldPosition(int(object.X), int(object.Y))
switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter:
// Temp code, maybe..
if object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" {
continue
if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" {
npc := d2core.CreateNPC(int32(worldX), int32(worldY), object.Lookup, fileProvider, 0)
npc.SetPaths(object.Paths)
entities = append(entities, npc)
}
npc := d2core.CreateNPC(object.X, object.Y, object.Lookup, fileProvider, 1)
npc.SetPaths(object.Paths)
v.NPCs = append(v.NPCs, npc)
case d2datadict.ObjectTypeItem:
if object.ObjectInfo == nil || !object.ObjectInfo.Draw || object.Lookup.Base == "" || object.Lookup.Token == "" {
continue
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
entity := d2render.CreateAnimatedEntity(int32(worldX), int32(worldY), object.Lookup, fileProvider, d2enum.Units)
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
entities = append(entities, &entity)
}
entity := d2render.CreateAnimatedEntity(object.X, object.Y, object.Lookup, fileProvider, d2enum.Units)
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
v.AnimationEntities = append(v.AnimationEntities, entity)
}
}
return entities
}
func (v *Region) UpdateAnimations() {
func (mr *MapRegion) updateAnimations() {
now := d2helper.Now()
framesToAdd := math.Floor((now - v.lastFrameTime) / 0.1)
framesToAdd := math.Floor((now - mr.lastFrameTime) / 0.1)
if framesToAdd > 0 {
v.lastFrameTime += 0.1 * framesToAdd
v.currentFrame += byte(math.Floor(framesToAdd))
if v.currentFrame > 9 {
v.currentFrame = 0
mr.lastFrameTime += 0.1 * framesToAdd
mr.currentFrame += byte(math.Floor(framesToAdd))
if mr.currentFrame > 9 {
mr.currentFrame = 0
}
}
}
func (v *Region) RenderTile(viewport *Viewport, tileX, tileY int, layerType d2enum.RegionLayerType, layerIndex int, target *ebiten.Image) {
switch layerType {
case d2enum.RegionLayerTypeFloors:
v.renderFloor(v.DS1.Tiles[tileY][tileX].Floors[layerIndex], viewport, target, tileX, tileY)
case d2enum.RegionLayerTypeWalls:
v.renderWall(v.DS1.Tiles[tileY][tileX].Walls[layerIndex], viewport, target, tileX, tileY)
case d2enum.RegionLayerTypeShadows:
v.renderShadow(v.DS1.Tiles[tileY][tileX].Shadows[layerIndex], viewport, target, tileX, tileY)
}
func (mr *MapRegion) getStartTilePosition() (float64, float64) {
return float64(mr.tileRect.Left) + mr.startX, float64(mr.tileRect.Top) + mr.startY
}
func (v *Region) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) byte {
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(v.LevelType.Id)
tileSeed *= uint64(y) + uint64(mr.levelType.Id)
tileSeed ^= tileSeed << 13
tileSeed ^= tileSeed >> 17
@ -196,9 +194,9 @@ func (v *Region) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) byte {
return 0
}
func (v *Region) getTiles(style, sequence, tileType int32, x, y int, seed int64) []d2dt1.Tile {
func (mr *MapRegion) getTiles(style, sequence, tileType int32, x, y int, seed int64) []d2dt1.Tile {
var tiles []d2dt1.Tile
for _, tile := range v.Tiles {
for _, tile := range mr.tiles {
if tile.Style != style || tile.Sequence != sequence || tile.Type != tileType {
continue
}
@ -211,50 +209,146 @@ func (v *Region) getTiles(style, sequence, tileType int32, x, y int, seed int64)
return tiles
}
func (v *Region) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image, tileX, tileY int) {
func (mr *MapRegion) isVisbile(viewport *Viewport) bool {
return viewport.IsTileRectVisible(mr.tileRect)
}
func (mr *MapRegion) advance(tickTime float64) {
mr.updateAnimations()
}
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 *ebiten.Image) {
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 []MapEntity, viewport *Viewport, target *ebiten.Image) {
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 _, entity := range entities {
entWorldX, entWorldY := entity.GetPosition()
if entWorldX == worldX && entWorldY == worldY {
screenX, screenY := viewport.GetTranslationScreen()
entity.Render(target, screenX, screenY)
}
}
viewport.PopTranslation()
}
}
}
}
func (mr *MapRegion) renderPass3(viewport *Viewport, target *ebiten.Image) {
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 *ebiten.Image) {
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 *ebiten.Image) {
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 *ebiten.Image) {
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 *ebiten.Image) {
var img *ebiten.Image
if !tile.Animated {
img = v.GetImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
} else {
img = v.GetImageCacheRecord(tile.Style, tile.Sequence, 0, v.currentFrame)
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, mr.currentFrame)
}
if img == nil {
log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)
return
}
viewport.PushTranslation(-80, float64(tile.YAdjust))
screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation())
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
screenX, screenY := viewport.GetTranslationScreen()
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(screenX), float64(screenY))
target.DrawImage(img, opts)
viewport.PopTranslation()
}
func (v *Region) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target *ebiten.Image, tileX, tileY int) {
img := v.GetImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex)
func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target *ebiten.Image) {
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.PushTranslation(-80, float64(tile.YAdjust))
screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation())
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
screenX, screenY := viewport.GetTranslationScreen()
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(screenX), float64(screenY))
target.DrawImage(img, opts)
viewport.PopTranslation()
}
func (v *Region) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image, tileX, tileY int) {
img := v.GetImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex)
func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *ebiten.Image) {
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.PushTranslation(-80, float64(tile.YAdjust))
screenX, screenY := viewport.WorldToScreen(viewport.GetTranslation())
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
screenX, screenY := viewport.GetTranslationScreen()
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(screenX), float64(screenY))
opts.ColorM = d2corehelper.ColorToColorM(color.RGBA{255, 255, 255, 160})
@ -262,7 +356,233 @@ func (v *Region) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport,
viewport.PopTranslation()
}
func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOffset int32, tileWidth int32) {
func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target *ebiten.Image) {
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 *ebiten.Image) {
if debugVisLevel > 0 {
subtileColor := color.RGBA{80, 80, 255, 100}
tileColor := color.RGBA{255, 255, 255, 255}
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))
ebitenutil.DrawLine(target, float64(screenX1), float64(screenY1), float64(screenX2), float64(screenY2), tileColor)
ebitenutil.DrawLine(target, float64(screenX1), float64(screenY1), float64(screenX3), float64(screenY3), tileColor)
ebitenutil.DebugPrintAt(target, fmt.Sprintf("%v,%v", x, y), screenX1-10, screenY1+10)
if debugVisLevel > 1 {
for i := 1; i <= 4; i++ {
x := i * 16
y := i * 8
ebitenutil.DrawLine(target, float64(screenX1-x), float64(screenY1+y), float64(screenX1-x+80), float64(screenY1+y+40), subtileColor)
ebitenutil.DrawLine(target, float64(screenX1+x), float64(screenY1+y), float64(screenX1+x-80), float64(screenY1+y+40), subtileColor)
}
tile := mr.ds1.Tiles[y][x]
for i := range tile.Floors {
ebitenutil.DebugPrintAt(target, fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence), screenX1-20, screenY1+10+(i+1)*14)
}
}
}
}
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) *ebiten.Image {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
return mr.imageCacheRecords[lookupIndex]
}
func (mr *MapRegion) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image *ebiten.Image) {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
mr.imageCacheRecords[lookupIndex] = image
}
func (mr *MapRegion) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := mr.getTiles(int32(tile.Style), int32(tile.Sequence), 0, tileX, tileY, mr.seed)
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.Animated {
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.Animated {
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 = d2helper.MinInt32(tileYMinimum, int32(block.Y))
}
tileYOffset := d2helper.AbsInt32(tileYMinimum)
tileHeight := d2helper.AbsInt32(tileData[i].Height)
image, _ := ebiten.NewImage(int(tileData[i].Width), int(tileHeight), ebiten.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, tileX, tileY, mr.seed)
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
tileMinY := int32(0)
tileMaxY := int32(0)
for _, block := range tileData.Blocks {
tileMinY = d2helper.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2helper.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, _ := ebiten.NewImage(int(tileData.Width), int(tileHeight), ebiten.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), tileX, tileY, mr.seed)
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), tileX, tileY, mr.seed)
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 = d2helper.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2helper.MaxInt32(tileMaxY, int32(block.Y+32))
}
realHeight := d2helper.MaxInt32(d2helper.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, _ := ebiten.NewImage(160, int(realHeight), ebiten.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
@ -281,7 +601,7 @@ func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOf
for n > 0 {
colorIndex := block.EncodedData[idx]
if colorIndex != 0 {
pixelColor := v.Palette.Colors[colorIndex]
pixelColor := mr.palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
@ -317,7 +637,7 @@ func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOf
for b2 > 0 {
colorIndex := block.EncodedData[idx]
if colorIndex != 0 {
pixelColor := v.Palette.Colors[colorIndex]
pixelColor := mr.palette.Colors[colorIndex]
offset := 4 * (((blockY + y + tileYOffset) * tileWidth) + (blockX + x))
(*pixels)[offset] = pixelColor.R
(*pixels)[offset+1] = pixelColor.G
@ -333,162 +653,3 @@ func (v *Region) decodeTileGfxData(blocks []d2dt1.Block, pixels *[]byte, tileYOf
}
}
}
func (v *Region) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := v.getTiles(int32(tile.Style), int32(tile.Sequence), 0, tileX, tileY, v.seed)
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.Animated {
tileIndex = v.getRandomTile(tileOptions, tileX, tileY, v.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.Animated {
tile.RandomIndex = tileIndex
} else {
tileIndex = byte(tileData[i].RarityFrameIndex)
}
cachedImage := v.GetImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex)
if cachedImage != nil {
return
}
tileYMinimum := int32(0)
for _, block := range tileData[i].Blocks {
tileYMinimum = d2helper.MinInt32(tileYMinimum, int32(block.Y))
}
tileYOffset := d2helper.AbsInt32(tileYMinimum)
tileHeight := d2helper.AbsInt32(tileData[i].Height)
image, _ := ebiten.NewImage(int(tileData[i].Width), int(tileHeight), ebiten.FilterNearest)
pixels := make([]byte, 4*tileData[i].Width*tileHeight)
v.decodeTileGfxData(tileData[i].Blocks, &pixels, tileYOffset, tileData[i].Width)
image.ReplacePixels(pixels)
v.SetImageCacheRecord(tile.Style, tile.Sequence, 0, tileIndex, image)
}
}
func (v *Region) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) {
tileOptions := v.getTiles(int32(tile.Style), int32(tile.Sequence), 13, tileX, tileY, v.seed)
var tileIndex byte
var tileData *d2dt1.Tile
if tileOptions == nil {
return
} else {
tileIndex = v.getRandomTile(tileOptions, tileX, tileY, v.seed)
tileData = &tileOptions[tileIndex]
}
tile.RandomIndex = tileIndex
tileMinY := int32(0)
tileMaxY := int32(0)
for _, block := range tileData.Blocks {
tileMinY = d2helper.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2helper.MaxInt32(tileMaxY, int32(block.Y+32))
}
tileYOffset := -tileMinY
tileHeight := int(tileMaxY - tileMinY)
tile.YAdjust = int(tileMinY + 80)
cachedImage := v.GetImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex)
if cachedImage != nil {
return
}
image, _ := ebiten.NewImage(int(tileData.Width), int(tileHeight), ebiten.FilterNearest)
pixels := make([]byte, 4*tileData.Width*int32(tileHeight))
v.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, tileData.Width)
image.ReplacePixels(pixels)
v.SetImageCacheRecord(tile.Style, tile.Sequence, 13, tileIndex, image)
}
func (v *Region) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY int) {
tileOptions := v.getTiles(int32(tile.Style), int32(tile.Sequence), int32(tile.Type), tileX, tileY, v.seed)
var tileIndex byte
var tileData *d2dt1.Tile
if tileOptions == nil {
return
} else {
tileIndex = v.getRandomTile(tileOptions, tileX, tileY, v.seed)
tileData = &tileOptions[tileIndex]
}
tile.RandomIndex = tileIndex
var newTileData *d2dt1.Tile = nil
if tile.Type == 3 {
newTileOptions := v.getTiles(int32(tile.Style), int32(tile.Sequence), int32(4), tileX, tileY, v.seed)
newTileIndex := v.getRandomTile(newTileOptions, tileX, tileY, v.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 = d2helper.MinInt32(tileMinY, int32(block.Y))
tileMaxY = d2helper.MaxInt32(tileMaxY, int32(block.Y+32))
}
realHeight := d2helper.MaxInt32(d2helper.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 := v.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, _ := ebiten.NewImage(160, int(realHeight), ebiten.FilterNearest)
pixels := make([]byte, 4*160*realHeight)
v.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, 160)
if newTileData != nil {
v.decodeTileGfxData(newTileData.Blocks, &pixels, tileYOffset, 160)
}
if err := image.ReplacePixels(pixels); err != nil {
log.Panicf(err.Error())
}
v.SetImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tileIndex, image)
}
func (v *Region) GetImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) *ebiten.Image {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
return v.imageCacheRecords[lookupIndex]
}
func (v *Region) SetImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image *ebiten.Image) {
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
v.imageCacheRecords[lookupIndex] = image
}

View File

@ -33,67 +33,82 @@ func (v *Viewport) SetCamera(camera *Camera) {
v.camera = camera
}
func (v *Viewport) IsoToScreen(x, y float64) (int, int) {
return v.WorldToScreen(v.IsoToWorld(x, y))
}
func (v *Viewport) ScreenToIso(x, y int) (float64, float64) {
return v.WorldToIso(v.ScreenToWorld(x, y))
}
func (v *Viewport) WorldToIso(x, y float64) (float64, float64) {
isoX := (x/80 + y/40) / 2
isoY := (y/40 - x/80) / 2
return isoX, isoY
}
func (v *Viewport) IsoToWorld(x, y float64) (float64, float64) {
worldX := (x - y) * 80
worldY := (x + y) * 40
return worldX, worldY
func (v *Viewport) WorldToScreen(x, y float64) (int, int) {
return v.OrthoToScreen(v.WorldToOrtho(x, y))
}
func (v *Viewport) ScreenToWorld(x, y int) (float64, float64) {
return v.OrthoToWorld(v.ScreenToOrtho(x, y))
}
func (v *Viewport) OrthoToWorld(x, y float64) (float64, float64) {
worldX := (x/80 + y/40) / 2
worldY := (y/40 - x/80) / 2
return worldX, worldY
}
func (v *Viewport) WorldToOrtho(x, y float64) (float64, float64) {
orthoX := (x - y) * 80
orthoY := (x + y) * 40
return orthoX, orthoY
}
func (v *Viewport) ScreenToOrtho(x, y int) (float64, float64) {
camX, camY := v.getCameraOffset()
screenX := float64(x) + camX - float64(v.screenRect.Left)
screenY := float64(y) + camY - float64(v.screenRect.Top)
return screenX, screenY
}
func (v *Viewport) WorldToScreen(x, y float64) (int, int) {
camX, camY := v.getCameraOffset()
worldX := int(math.Floor(x - camX + float64(v.screenRect.Left)))
worldY := int(math.Floor(y - camY + float64(v.screenRect.Top)))
return worldX, worldY
func (v *Viewport) OrthoToScreen(x, y float64) (int, int) {
camOrthoX, camOrthoY := v.getCameraOffset()
orthoX := int(math.Floor(x - camOrthoX + float64(v.screenRect.Left)))
orthoY := int(math.Floor(y - camOrthoY + float64(v.screenRect.Top)))
return orthoX, orthoY
}
func (v *Viewport) IsWorldTileVisbile(x, y float64) bool {
worldX1, worldY1 := v.IsoToWorld(x-2, y)
worldX2, worldY2 := v.IsoToWorld(x+2, y)
return v.IsWorldRectVisible(worldX1, worldY1, worldX2, worldY2)
func (v *Viewport) IsTileVisible(x, y float64) bool {
orthoX1, orthoY1 := v.WorldToOrtho(x-3, y)
orthoX2, orthoY2 := v.WorldToOrtho(x+3, y)
return v.IsOrthoRectVisible(orthoX1, orthoY1, orthoX2, orthoY2)
}
func (v *Viewport) IsWorldPointVisible(x, y float64) bool {
screenX, screenY := v.WorldToScreen(x, y)
return screenX >= 0 && screenX < v.screenRect.Width && screenY >= 0 && screenY < v.screenRect.Height
func (v *Viewport) IsTileRectVisible(rect d2common.Rectangle) bool {
left := float64((rect.Left - rect.Bottom()) * 80)
top := float64((rect.Left + rect.Top) * 40)
right := float64((rect.Right() - rect.Top) * 80)
bottom := float64((rect.Right() + rect.Bottom()) * 40)
return v.IsOrthoRectVisible(left, top, right, bottom)
}
func (v *Viewport) IsWorldRectVisible(x1, y1, x2, y2 float64) bool {
screenX1, screenY1 := v.WorldToScreen(x1, y1)
screenX2, screenY2 := v.WorldToScreen(x2, y2)
func (v *Viewport) IsOrthoRectVisible(x1, y1, x2, y2 float64) bool {
screenX1, screenY1 := v.OrthoToScreen(x1, y1)
screenX2, screenY2 := v.OrthoToScreen(x2, y2)
return !(screenX1 >= v.screenRect.Width || screenX2 < 0 || screenY1 >= v.screenRect.Height || screenY2 < 0)
}
func (v *Viewport) GetTranslation() (float64, float64) {
func (v *Viewport) GetTranslationOrtho() (float64, float64) {
return v.transCurrent.x, v.transCurrent.y
}
func (v *Viewport) PushTranslation(x, y float64) {
func (v *Viewport) GetTranslationScreen() (int, int) {
return v.OrthoToScreen(v.transCurrent.x, v.transCurrent.y)
}
func (v *Viewport) PushTranslationOrtho(x, y float64) {
v.transStack = append(v.transStack, v.transCurrent)
v.transCurrent.x += x
v.transCurrent.y += y
}
func (v *Viewport) PushTranslationWorld(x, y float64) {
v.PushTranslationOrtho(v.WorldToOrtho(x, y))
}
func (v *Viewport) PushTranslationScreen(x, y int) {
v.PushTranslationOrtho(v.ScreenToOrtho(x, y))
}
func (v *Viewport) PopTranslation() {
count := len(v.transStack)
if count == 0 {