mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-30 07:06:18 -04:00
c6235411b7
* Add basic movement * Calculate step length based on tick time between updates, teleport to target if within one step. * Smooth camera, hero movement removed float to int conversions in Render and IsoToScreen functions * Render hero in the center of the screen (assuming 800x600 resolution) * Revert changing Render() parameters type * Render hero in the tile loop hero will naturally render in front of the walls of the current tile but behind the walls of the tile below * Smoother steps near target coordinates remove jitter from trying to get one step away from target on both axis
303 lines
10 KiB
Go
303 lines
10 KiB
Go
package d2mapengine
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
|
|
|
"github.com/OpenDiablo2/D2Shared/d2helper"
|
|
|
|
"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"
|
|
)
|
|
|
|
type RegionTile struct {
|
|
tileX, tileY int // tile coordinates
|
|
offX, offY int // world space coordinates of tile origin
|
|
}
|
|
|
|
type EngineRegion struct {
|
|
Rect d2common.Rectangle
|
|
Region *Region
|
|
Tiles []RegionTile
|
|
}
|
|
|
|
type Engine struct {
|
|
soundManager *d2audio.Manager
|
|
gameState *d2core.GameState
|
|
fileProvider d2interface.FileProvider
|
|
regions []EngineRegion
|
|
OffsetX float64
|
|
OffsetY float64
|
|
ShowTiles int
|
|
Hero *d2core.Hero
|
|
}
|
|
|
|
func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager, fileProvider d2interface.FileProvider) *Engine {
|
|
result := &Engine{
|
|
gameState: gameState,
|
|
soundManager: soundManager,
|
|
fileProvider: fileProvider,
|
|
regions: make([]EngineRegion, 0),
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (v *Engine) GetRegion(regionIndex int) *EngineRegion {
|
|
return &v.regions[regionIndex]
|
|
}
|
|
|
|
func (v *Engine) CenterCameraOn(x, y float64) {
|
|
v.OffsetX = -(x - 400)
|
|
v.OffsetY = -(y - 300)
|
|
}
|
|
|
|
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,
|
|
})
|
|
|
|
for i, _ := range v.regions {
|
|
v.GenTiles(&v.regions[i])
|
|
v.GenTilesCache(&v.regions[i])
|
|
}
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
sx, sy := d2helper.IsoToScreen(region.StartX, region.StartY, 0, 0)
|
|
v.OffsetX = sx - 400
|
|
v.OffsetY = sy - 300
|
|
}
|
|
|
|
func (v *Engine) GetRegionAt(x, y int) *EngineRegion {
|
|
if v.regions == nil {
|
|
return nil
|
|
}
|
|
for _, region := range v.regions {
|
|
if !region.Rect.IsInRect(x, y) {
|
|
continue
|
|
}
|
|
return ®ion
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (v *Engine) Render(target *ebiten.Image) {
|
|
for _, region := range v.regions {
|
|
// X position of leftmost point of region
|
|
left := (region.Rect.Left - region.Rect.Bottom()) * 80
|
|
// Y position of top of region
|
|
top := (region.Rect.Left + region.Rect.Top) * 40
|
|
// X of right
|
|
right := (region.Rect.Right() - region.Rect.Top) * 80
|
|
// Y of bottom
|
|
bottom := (region.Rect.Right() + region.Rect.Bottom()) * 40
|
|
|
|
if -v.OffsetX+800 > float64(left) && -v.OffsetX < float64(right) && -v.OffsetY+600 > float64(top) && -v.OffsetY < float64(bottom) {
|
|
v.RenderRegion(region, target)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 := ®ion.Tiles[tileIdx]
|
|
if t.tileY < len(region.Region.DS1.Tiles) && t.tileX < len(region.Region.DS1.Tiles[t.tileY]) {
|
|
tile := ®ion.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 {
|
|
sx, sy := d2helper.IsoToScreen(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top), v.OffsetX, v.OffsetY)
|
|
if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 {
|
|
region.Region.UpdateAnimations()
|
|
v.RenderPass1(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
|
|
if v.ShowTiles > 0 {
|
|
v.DrawTileLines(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
|
|
}
|
|
|
|
}
|
|
}
|
|
for tileIdx := range region.Tiles {
|
|
sx, sy := d2helper.IsoToScreen(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top), v.OffsetX, v.OffsetY)
|
|
if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 {
|
|
v.RenderPass2(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
|
|
}
|
|
}
|
|
for tileIdx := range region.Tiles {
|
|
sx, sy := d2helper.IsoToScreen(float64(region.Tiles[tileIdx].tileX+region.Rect.Left), float64(region.Tiles[tileIdx].tileY+region.Rect.Top), v.OffsetX, v.OffsetY)
|
|
if sx > -160 && sy > -380 && sx <= 880 && sy <= 1240 {
|
|
v.RenderPass3(region.Region, region.Tiles[tileIdx].offX, region.Tiles[tileIdx].offY, region.Tiles[tileIdx].tileX, region.Tiles[tileIdx].tileY, target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (v *Engine) RenderPass1(region *Region, offX, offY, 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(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target)
|
|
}
|
|
|
|
for i := range tile.Floors {
|
|
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
|
|
continue
|
|
}
|
|
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeFloors, i, target)
|
|
}
|
|
for i := range tile.Shadows {
|
|
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
|
|
continue
|
|
}
|
|
region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeShadows, i, target)
|
|
}
|
|
|
|
}
|
|
|
|
func (v *Engine) RenderPass2(region *Region, offX, offY, 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(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target)
|
|
}
|
|
|
|
for _, obj := range region.AnimationEntities {
|
|
if int(math.Floor(obj.LocationX)) == x && int(math.Floor(obj.LocationY)) == y {
|
|
obj.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
|
}
|
|
}
|
|
for _, npc := range region.NPCs {
|
|
if int(math.Floor(npc.AnimatedEntity.LocationX)) == x && int(math.Floor(npc.AnimatedEntity.LocationY)) == y {
|
|
npc.Render(target, offX+int(v.OffsetX), offY+int(v.OffsetY))
|
|
}
|
|
}
|
|
if v.Hero != nil && int(v.Hero.AnimatedEntity.LocationX) == x && int(v.Hero.AnimatedEntity.LocationY) == y {
|
|
v.Hero.Render(target, 400, 300)
|
|
}
|
|
}
|
|
|
|
func (v *Engine) RenderPass3(region *Region, offX, offY, 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(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, d2enum.RegionLayerTypeWalls, i, target)
|
|
}
|
|
}
|
|
|
|
func (v *Engine) DrawTileLines(region *Region, offX, offY, x, y int, target *ebiten.Image) {
|
|
if v.ShowTiles > 0 {
|
|
subtileColor := color.RGBA{80, 80, 255, 100}
|
|
tileColor := color.RGBA{255, 255, 255, 255}
|
|
|
|
ebitenutil.DrawLine(target, float64(offX)+v.OffsetX, float64(offY)+v.OffsetY, float64(offX)+v.OffsetX+80, float64(offY)+v.OffsetY+40, tileColor)
|
|
ebitenutil.DrawLine(target, float64(offX)+v.OffsetX, float64(offY)+v.OffsetY, float64(offX)+v.OffsetX-80, float64(offY)+v.OffsetY+40, tileColor)
|
|
|
|
coords := fmt.Sprintf("%v,%v", x, y)
|
|
ebitenutil.DebugPrintAt(target, coords, offX+int(v.OffsetX)-10, offY+int(v.OffsetY)+10)
|
|
|
|
if v.ShowTiles > 1 {
|
|
for i := 1; i <= 4; i++ {
|
|
x := (16 * i)
|
|
y := (8 * i)
|
|
ebitenutil.DrawLine(target, float64(offX-x)+v.OffsetX, float64(offY+y)+v.OffsetY,
|
|
float64(offX-x)+v.OffsetX+80, float64(offY+y)+v.OffsetY+40, subtileColor)
|
|
ebitenutil.DrawLine(target, float64(offX+x)+v.OffsetX, float64(offY+y)+v.OffsetY,
|
|
float64(offX+x)+v.OffsetX-80, float64(offY+y)+v.OffsetY+40, subtileColor)
|
|
}
|
|
|
|
tile := region.DS1.Tiles[y][x]
|
|
for i := range tile.Floors {
|
|
floorSpec := fmt.Sprintf("f: %v-%v", tile.Floors[i].Style, tile.Floors[i].Sequence)
|
|
ebitenutil.DebugPrintAt(target, floorSpec, offX+int(v.OffsetX)-20, offY+int(v.OffsetY)+10+((i+1)*14))
|
|
}
|
|
// wallSpec := fmt.Sprintf("w: %v-%v", tile.Walls[0].Style, tile.Walls[0].Sequence)
|
|
// ebitenutil.DebugPrintAt(target, wallSpec, offX+int(v.OffsetX)-20, offY+int(v.OffsetY)+34)
|
|
}
|
|
}
|
|
}
|