mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-10 06:16:27 -05:00
88326b5278
* Casting a skill now plays the corresponding overlay(if any). * Prevent a crash caused by nil pointer in HeroSkill deserialization, happening when unmarshalling HeroSkill from packets as a remote client. * Add PlayerAnimationModeNone to handle some of the Skills(e.g. Paladin auras) having "" as animation mode. * Joining a game as remote client now waits for map generation to finish before rendering map or processing map entities. This is temporary hack to prevent the game from crashing due to concurrent map read & write exception. * Send CastSkill packet to other clients. Co-authored-by: Presiyan Ivanov <presiyan-ivanov@users.noreply.github.com>
646 lines
20 KiB
Go
646 lines
20 KiB
Go
package d2maprenderer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image/color"
|
|
"log"
|
|
"math"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
|
|
)
|
|
|
|
const (
|
|
screenMiddleX = 400
|
|
two = 2
|
|
|
|
dbgOffsetXY = 40
|
|
dbgBoxWidth = 220
|
|
dbgBoxHeight = 60
|
|
dbgBoxPadding = 10
|
|
|
|
dbgCollisionSize = 5
|
|
dbgCollisionOffsetX = -3
|
|
dbgCollisionOffsetY = 4
|
|
|
|
whiteHalfOpacity = 0xffffff7f
|
|
blackQuarterOpacity = 0x00000040
|
|
lightGreenFullOpacity = 0x40ff00ff
|
|
magentaFullOpacity = 0xff00ffff
|
|
yellowFullOpacity = 0xffff00ff
|
|
lightBlueQuarterOpacity = 0x5050ff32
|
|
whiteQuarterOpacity = 0xffffff64
|
|
redQuarterOpacity = 0x74000064
|
|
|
|
subtilesPerTile = 5
|
|
orthoSubTileWidth = 16
|
|
orthoSubTileHeight = 8
|
|
orthoTileWidth = subtilesPerTile * orthoSubTileWidth
|
|
orthoTileHeight = subtilesPerTile * orthoSubTileHeight
|
|
)
|
|
|
|
// MapRenderer manages the game viewport and Camera. It requests tile and entity data from MapEngine and renders it.
|
|
type MapRenderer struct {
|
|
asset *d2asset.AssetManager
|
|
renderer d2interface.Renderer // Used for drawing operations
|
|
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
|
|
palette d2interface.Palette // The palette used for this map
|
|
viewport *Viewport // Used for rendering offsets
|
|
Camera Camera // Used to determine where on the map we are rendering
|
|
imageCacheRecords map[uint32]d2interface.Surface
|
|
mapDebugVisLevel int // Map debug visibility index (0=none, 1=tiles, 2=sub-tiles)
|
|
entityDebugVisLevel int // Entity Debug visibility index (0=none, 1=vectors)
|
|
lastFrameTime float64 // The last time the map was rendered
|
|
currentFrame int // Current render frame (for animations)
|
|
}
|
|
|
|
// CreateMapRenderer creates a new MapRenderer, sets the required fields and returns a pointer to it.
|
|
func CreateMapRenderer(asset *d2asset.AssetManager, renderer d2interface.Renderer,
|
|
mapEngine *d2mapengine.MapEngine,
|
|
term d2interface.Terminal, startX, startY float64) *MapRenderer {
|
|
result := &MapRenderer{
|
|
asset: asset,
|
|
renderer: renderer,
|
|
mapEngine: mapEngine,
|
|
viewport: NewViewport(0, 0, 800, 600),
|
|
}
|
|
|
|
result.Camera = Camera{}
|
|
rx, ry := result.WorldToOrtho(startX, startY)
|
|
startPosition := d2vector.NewPosition(rx, ry)
|
|
result.Camera.position = &startPosition
|
|
result.viewport.SetCamera(&result.Camera)
|
|
|
|
var err error
|
|
err = term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) {
|
|
result.mapDebugVisLevel = level
|
|
})
|
|
|
|
if err != nil {
|
|
fmt.Printf("could not bind the mapdebugvis action, err: %v\n", err)
|
|
}
|
|
|
|
err = term.BindAction("entitydebugvis", "set entity debug visualization level", func(level int) {
|
|
result.entityDebugVisLevel = level
|
|
})
|
|
|
|
if err != nil {
|
|
fmt.Printf("could not bind the entitydebugvis action, err: %v\n", err)
|
|
}
|
|
|
|
if mapEngine.LevelType().ID != 0 {
|
|
result.generateTileCache()
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// RegenerateTileCache calls MapRenderer.generateTileCache().
|
|
func (mr *MapRenderer) RegenerateTileCache() {
|
|
mr.generateTileCache()
|
|
}
|
|
|
|
// SetMapEngine sets the MapEngine this renderer is rendering.
|
|
func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) {
|
|
mr.mapEngine = mapEngine
|
|
mr.generateTileCache()
|
|
}
|
|
|
|
// Render determines the width and height of map tiles that should be rendered. The following four render passes are
|
|
// made in succession:
|
|
//
|
|
// Pass 1: Lower wall tiles, tile shadows and floor tiles.
|
|
//
|
|
// Pass 2: Entities below walls.
|
|
//
|
|
// Pass 3: Upper wall tiles and entities above walls.
|
|
//
|
|
// Pass 4: Roof tiles.
|
|
func (mr *MapRenderer) Render(target d2interface.Surface) {
|
|
// TODO:(temp hack) should not render before the map has been fully generated -
|
|
// Prevents concurrent map read & write exceptions that otherwise occur when we join a TCP game
|
|
// as a remote client, due to rendering before we have handled the GenerateMapPacket.
|
|
if mr.mapEngine.IsLoading {
|
|
return
|
|
}
|
|
mapSize := mr.mapEngine.Size()
|
|
|
|
stxf, styf := mr.viewport.ScreenToWorld(screenMiddleX, -200)
|
|
etxf, etyf := mr.viewport.ScreenToWorld(screenMiddleX, 1050)
|
|
|
|
startX := int(math.Max(0, math.Floor(stxf)))
|
|
startY := int(math.Max(0, math.Floor(styf)))
|
|
|
|
endX := int(math.Min(float64(mapSize.Width), math.Ceil(etxf)))
|
|
endY := int(math.Min(float64(mapSize.Height), math.Ceil(etyf)))
|
|
|
|
mr.renderPass1(target, startX, startY, endX, endY)
|
|
mr.renderPass2(target, startX, startY, endX, endY)
|
|
|
|
if mr.mapDebugVisLevel > 0 {
|
|
mr.renderMapDebug(mr.mapDebugVisLevel, target, startX, startY, endX, endY)
|
|
}
|
|
|
|
mr.renderPass3(target, startX, startY, endX, endY)
|
|
mr.renderPass4(target, startX, startY, endX, endY)
|
|
|
|
if mr.entityDebugVisLevel > 0 {
|
|
mr.renderEntityDebug(target)
|
|
}
|
|
}
|
|
|
|
// MoveCameraTo sets the position of the Camera to the given x and y coordinates.
|
|
func (mr *MapRenderer) MoveCameraTo(position *d2vector.Position) {
|
|
mr.Camera.MoveTo(position)
|
|
}
|
|
|
|
// MoveCameraBy adds the given vector to the current position of the Camera.
|
|
func (mr *MapRenderer) MoveCameraBy(vector *d2vector.Vector) {
|
|
mr.Camera.MoveBy(vector)
|
|
}
|
|
|
|
// MoveCameraTargetBy adds the given vector to the current position of the Camera.
|
|
func (mr *MapRenderer) MoveCameraTargetBy(vector *d2vector.Vector) {
|
|
mr.Camera.MoveTargetBy(vector)
|
|
}
|
|
|
|
// ScreenToWorld returns the world position for the given screen (pixel) position.
|
|
func (mr *MapRenderer) ScreenToWorld(x, y int) (worldX, worldY float64) {
|
|
return mr.viewport.ScreenToWorld(x, y)
|
|
}
|
|
|
|
// ScreenToOrtho returns the orthogonal position, without accounting for the isometric angle, for the given screen
|
|
// (pixel) position.
|
|
func (mr *MapRenderer) ScreenToOrtho(x, y int) (orthoX, orthoY float64) {
|
|
return mr.viewport.ScreenToOrtho(x, y)
|
|
}
|
|
|
|
// WorldToOrtho returns the orthogonal position for the given isometric world position.
|
|
func (mr *MapRenderer) WorldToOrtho(x, y float64) (orthoX, orthoY float64) {
|
|
return mr.viewport.WorldToOrtho(x, y)
|
|
}
|
|
|
|
// Lower wall tiles, tile shadows and floor tiles.
|
|
func (mr *MapRenderer) renderPass1(target d2interface.Surface, startX, startY, endX, endY int) {
|
|
for tileY := startY; tileY < endY; tileY++ {
|
|
for tileX := startX; tileX < endX; tileX++ {
|
|
tile := mr.mapEngine.TileAt(tileX, tileY)
|
|
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
|
|
mr.renderTilePass1(tile, target)
|
|
mr.viewport.PopTranslation()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Entities below walls.
|
|
func (mr *MapRenderer) renderPass2(target d2interface.Surface, startX, startY, endX, endY int) {
|
|
for tileY := startY; tileY < endY; tileY++ {
|
|
for tileX := startX; tileX < endX; tileX++ {
|
|
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
|
|
|
|
tileEnt := make([]d2interface.MapEntity, 0)
|
|
|
|
// TODO: Do not loop over every entity every frame
|
|
for _, mapEntity := range mr.mapEngine.Entities() {
|
|
pos := mapEntity.GetPosition()
|
|
vec := pos.World()
|
|
entityX, entityY := vec.X(), vec.Y()
|
|
|
|
if mapEntity.GetLayer() != 1 {
|
|
continue
|
|
}
|
|
|
|
if (int(entityX) != tileX) || (int(entityY) != tileY) {
|
|
continue
|
|
}
|
|
|
|
tileEnt = append(tileEnt, mapEntity)
|
|
}
|
|
|
|
for subY := 0; subY < 5; subY++ {
|
|
for subX := 0; subX < 5; subX++ {
|
|
for _, mapEntity := range tileEnt {
|
|
pos := mapEntity.GetPosition()
|
|
if (int(pos.SubTileOffset().X()) != subX) || (int(pos.SubTileOffset().Y()) != subY) {
|
|
continue
|
|
}
|
|
|
|
target.PushTranslation(mr.viewport.GetTranslationScreen())
|
|
mapEntity.Render(target)
|
|
target.Pop()
|
|
}
|
|
}
|
|
}
|
|
|
|
mr.viewport.PopTranslation()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Upper wall tiles and entities above walls.
|
|
func (mr *MapRenderer) renderPass3(target d2interface.Surface, startX, startY, endX, endY int) {
|
|
for tileY := startY; tileY < endY; tileY++ {
|
|
for tileX := startX; tileX < endX; tileX++ {
|
|
tile := mr.mapEngine.TileAt(tileX, tileY)
|
|
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
|
|
mr.renderTilePass2(tile, target)
|
|
|
|
tileEnt := make([]d2interface.MapEntity, 0)
|
|
|
|
// TODO: Do not loop over every entity every frame
|
|
for _, mapEntity := range mr.mapEngine.Entities() {
|
|
pos := mapEntity.GetPosition()
|
|
vec := pos.World()
|
|
entityX, entityY := vec.X(), vec.Y()
|
|
|
|
if mapEntity.GetLayer() == 1 {
|
|
continue
|
|
}
|
|
|
|
if (int(entityX) != tileX) || (int(entityY) != tileY) {
|
|
continue
|
|
}
|
|
|
|
tileEnt = append(tileEnt, mapEntity)
|
|
}
|
|
|
|
for subY := 0; subY < 5; subY++ {
|
|
for subX := 0; subX < 5; subX++ {
|
|
for _, mapEntity := range tileEnt {
|
|
pos := mapEntity.GetPosition()
|
|
if (int(pos.SubTileOffset().X()) != subX) || (int(pos.SubTileOffset().Y()) != subY) {
|
|
continue
|
|
}
|
|
|
|
target.PushTranslation(mr.viewport.GetTranslationScreen())
|
|
mapEntity.Render(target)
|
|
target.Pop()
|
|
}
|
|
}
|
|
}
|
|
|
|
mr.viewport.PopTranslation()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Roof tiles.
|
|
func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, endX, endY int) {
|
|
for tileY := startY; tileY < endY; tileY++ {
|
|
for tileX := startX; tileX < endX; tileX++ {
|
|
tile := mr.mapEngine.TileAt(tileX, tileY)
|
|
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
|
|
mr.renderTilePass3(tile, target)
|
|
mr.viewport.PopTranslation()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2interface.Surface) {
|
|
for _, wall := range tile.Components.Walls {
|
|
if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() {
|
|
mr.renderWall(wall, mr.viewport, target)
|
|
}
|
|
}
|
|
|
|
for _, floor := range tile.Components.Floors {
|
|
if !floor.Hidden && floor.Prop1 != 0 {
|
|
mr.renderFloor(floor, target)
|
|
}
|
|
}
|
|
|
|
for _, shadow := range tile.Components.Shadows {
|
|
if !shadow.Hidden && shadow.Prop1 != 0 {
|
|
mr.renderShadow(shadow, target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderTilePass2(tile *d2mapengine.MapTile, target d2interface.Surface) {
|
|
for _, wall := range tile.Components.Walls {
|
|
if !wall.Hidden && wall.Type.UpperWall() {
|
|
mr.renderWall(wall, mr.viewport, target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderTilePass3(tile *d2mapengine.MapTile, target d2interface.Surface) {
|
|
for _, wall := range tile.Components.Walls {
|
|
if wall.Type == d2enum.TileRoof {
|
|
mr.renderWall(wall, mr.viewport, target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2interface.Surface) {
|
|
var img d2interface.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()
|
|
|
|
if err := target.Render(img); err != nil {
|
|
fmt.Printf("failed to render the floor, err: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2interface.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))
|
|
defer viewport.PopTranslation()
|
|
|
|
target.PushTranslation(viewport.GetTranslationScreen())
|
|
defer target.Pop()
|
|
|
|
if err := target.Render(img); err != nil {
|
|
fmt.Printf("failed to render the wall, err: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2interface.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())
|
|
defer target.Pop()
|
|
|
|
target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160}) //nolint:gomnd // Not a magic number...
|
|
defer target.Pop()
|
|
|
|
if err := target.Render(img); err != nil {
|
|
fmt.Printf("failed to render the shadow, err: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func (mr *MapRenderer) renderMapDebug(mapDebugVisLevel int, target d2interface.Surface, startX, startY, endX, endY int) {
|
|
for tileY := startY; tileY < endY; tileY++ {
|
|
for tileX := startX; tileX < endX; tileX++ {
|
|
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
|
|
mr.renderTileDebug(tileX, tileY, mapDebugVisLevel, target)
|
|
mr.viewport.PopTranslation()
|
|
}
|
|
}
|
|
}
|
|
|
|
//nolint:funlen // doesn't make sense to split this function
|
|
func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) {
|
|
entities := mr.mapEngine.Entities()
|
|
|
|
for idx := range entities {
|
|
e := entities[idx]
|
|
pos := e.GetPosition()
|
|
world := pos
|
|
x, y := world.X()/subtilesPerTile, world.Y()/subtilesPerTile
|
|
velocity := e.GetVelocity()
|
|
velocity = *velocity.Clone()
|
|
vx, vy := mr.viewport.WorldToOrtho(velocity.X(), velocity.Y())
|
|
screenX, screenY := mr.viewport.WorldToScreen(x, y)
|
|
|
|
offX, offY := dbgOffsetXY, -dbgOffsetXY
|
|
|
|
entScreenXf, entScreenYf := mr.WorldToScreenF(e.GetPositionF())
|
|
entScreenX := int(math.Floor(entScreenXf))
|
|
entScreenY := int(math.Floor(entScreenYf))
|
|
entityWidth, entityHeight := e.GetSize()
|
|
halfWidth, halfHeight := entityWidth/two, entityHeight/two
|
|
l, r := entScreenX-halfWidth, entScreenX+halfWidth
|
|
t, b := entScreenY-halfHeight, entScreenY+halfHeight
|
|
mx, my := mr.renderer.GetCursorPos()
|
|
xWithin := (l <= mx) && (r >= mx)
|
|
yWithin := (t <= my) && (b >= my)
|
|
within := xWithin && yWithin
|
|
|
|
boxLineColor := d2util.Color(magentaFullOpacity)
|
|
boxHoverColor := d2util.Color(yellowFullOpacity)
|
|
|
|
boxColor := boxLineColor
|
|
|
|
if within {
|
|
boxColor = boxHoverColor
|
|
}
|
|
|
|
stack := 0
|
|
// box
|
|
mr.viewport.PushTranslationWorld(x, y)
|
|
|
|
target.PushTranslation(screenX, screenY)
|
|
stack++
|
|
|
|
target.PushTranslation(-halfWidth, -halfHeight)
|
|
stack++
|
|
|
|
target.DrawLine(0, entityHeight, boxColor)
|
|
target.DrawLine(entityWidth, 0, boxColor)
|
|
|
|
target.PushTranslation(entityWidth, entityHeight)
|
|
stack++
|
|
|
|
target.DrawLine(-entityWidth, 0, boxColor)
|
|
target.DrawLine(0, -entityHeight, boxColor)
|
|
target.PopN(stack)
|
|
mr.viewport.PopTranslation()
|
|
|
|
// hover
|
|
if within {
|
|
mr.viewport.PushTranslationWorld(x, y)
|
|
target.PushTranslation(screenX, screenY)
|
|
target.DrawLine(offX, offY, d2util.Color(whiteHalfOpacity))
|
|
target.PushTranslation(offX+dbgBoxPadding, offY-dbgBoxPadding*two)
|
|
target.PushTranslation(-dbgOffsetXY, -dbgOffsetXY)
|
|
target.DrawRect(dbgBoxWidth, dbgBoxHeight, d2util.Color(blackQuarterOpacity))
|
|
target.Pop()
|
|
target.DrawTextf("World (%.2f, %.2f)\nVelocity (%.2f, %.2f)", x, y, vx, vy)
|
|
target.Pop()
|
|
target.DrawLine(int(vx), int(vy), d2util.Color(lightGreenFullOpacity))
|
|
target.Pop()
|
|
mr.viewport.PopTranslation()
|
|
}
|
|
}
|
|
}
|
|
|
|
// WorldToScreen returns the screen (pixel) position for the given isometric world position as two ints.
|
|
func (mr *MapRenderer) WorldToScreen(x, y float64) (screenX, screenY int) {
|
|
return mr.viewport.WorldToScreen(x, y)
|
|
}
|
|
|
|
// WorldToScreenF returns the screen (pixel) position for the given isometric world position as two float64s.
|
|
func (mr *MapRenderer) WorldToScreenF(x, y float64) (screenX, screenY float64) {
|
|
return mr.viewport.WorldToScreenF(x, y)
|
|
}
|
|
|
|
func (mr *MapRenderer) renderTileDebug(ax, ay, debugVisLevel int, target d2interface.Surface) {
|
|
subTileColor := d2util.Color(lightBlueQuarterOpacity)
|
|
tileColor := d2util.Color(whiteQuarterOpacity)
|
|
tileCollisionColor := d2util.Color(redQuarterOpacity)
|
|
|
|
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.DrawTextf("%v, %v", ax, ay)
|
|
target.Pop()
|
|
|
|
if debugVisLevel > 1 {
|
|
for i := 1; i <= 4; i++ {
|
|
x2 := i * orthoSubTileWidth
|
|
y2 := i * orthoSubTileHeight
|
|
|
|
target.PushTranslation(-x2, y2)
|
|
target.DrawLine(orthoTileWidth, orthoTileHeight, subTileColor)
|
|
target.Pop()
|
|
|
|
target.PushTranslation(x2, y2)
|
|
target.DrawLine(-orthoTileWidth, orthoTileHeight, subTileColor)
|
|
target.Pop()
|
|
}
|
|
|
|
tile := mr.mapEngine.TileAt(ax, ay)
|
|
|
|
for i, wall := range tile.Components.Walls {
|
|
if wall.Type.Special() {
|
|
target.PushTranslation(-20, 10+(i+1)*14) // what are these magic numbers??
|
|
target.DrawTextf("s: %v-%v", wall.Style, wall.Sequence)
|
|
target.Pop()
|
|
}
|
|
}
|
|
|
|
for yy := 0; yy < 5; yy++ {
|
|
for xx := 0; xx < 5; xx++ {
|
|
isoX := (xx - yy) * orthoSubTileWidth
|
|
isoY := (xx + yy) * orthoSubTileHeight
|
|
|
|
blocked := tile.GetSubTileFlags(xx, yy).BlockWalk
|
|
|
|
if blocked {
|
|
target.PushTranslation(isoX+dbgCollisionOffsetX, isoY+dbgCollisionOffsetY)
|
|
target.DrawRect(dbgCollisionSize, dbgCollisionSize, tileCollisionColor)
|
|
target.Pop()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advance is called once per frame and maintains the MapRenderer's record previous render timestamp and current frame.
|
|
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
|
|
}
|
|
|
|
mr.Camera.Advance(elapsed)
|
|
}
|
|
|
|
func (mr *MapRenderer) loadPaletteForAct(levelType d2enum.RegionIdType) (d2interface.Palette,
|
|
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
|
|
case d2enum.RegionAct2Town, d2enum.RegionAct2Sewer, d2enum.RegionAct2Harem, d2enum.RegionAct2Basement,
|
|
d2enum.RegionAct2Desert, d2enum.RegionAct2Tomb, d2enum.RegionAct2Lair, d2enum.RegionAct2Arcane:
|
|
palettePath = d2resource.PaletteAct2
|
|
case d2enum.RegionAct3Town, d2enum.RegionAct3Jungle, d2enum.RegionAct3Kurast, d2enum.RegionAct3Spider,
|
|
d2enum.RegionAct3Dungeon, d2enum.RegionAct3Sewer:
|
|
palettePath = d2resource.PaletteAct3
|
|
case d2enum.RegionAct4Town, d2enum.RegionAct4Mesa, d2enum.RegionAct4Lava, d2enum.RegionAct5Lava:
|
|
palettePath = d2resource.PaletteAct4
|
|
case d2enum.RegonAct5Town, d2enum.RegionAct5Siege, d2enum.RegionAct5Barricade, d2enum.RegionAct5Temple,
|
|
d2enum.RegionAct5IceCaves, d2enum.RegionAct5Baal:
|
|
palettePath = d2resource.PaletteAct5
|
|
default:
|
|
return nil, errors.New("failed to find palette for region")
|
|
}
|
|
|
|
return mr.asset.LoadPalette(palettePath)
|
|
}
|
|
|
|
// ViewportToLeft moves the viewport to the left.
|
|
func (mr *MapRenderer) ViewportToLeft() {
|
|
mr.viewport.toLeft()
|
|
}
|
|
|
|
// ViewportToRight moves the viewport to the right.
|
|
func (mr *MapRenderer) ViewportToRight() {
|
|
mr.viewport.toRight()
|
|
}
|
|
|
|
// ViewportDefault resets the viewport to it's default position.
|
|
func (mr *MapRenderer) ViewportDefault() {
|
|
mr.viewport.resetAlign()
|
|
}
|
|
|
|
// SetCameraTarget sets the Camera target
|
|
func (mr *MapRenderer) SetCameraTarget(position *d2vector.Position) {
|
|
mr.Camera.SetTarget(position)
|
|
}
|
|
|
|
// SetCameraPosition sets the Camera position
|
|
func (mr *MapRenderer) SetCameraPosition(position *d2vector.Position) {
|
|
mr.Camera.MoveTo(position)
|
|
}
|
|
|
|
// InvalidateImageCache the global region image cache. Call this when you are changing regions.
|
|
func (mr *MapRenderer) InvalidateImageCache() {
|
|
mr.imageCacheRecords = nil
|
|
}
|
|
|
|
func (mr *MapRenderer) getImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) d2interface.Surface {
|
|
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
|
|
return mr.imageCacheRecords[lookupIndex]
|
|
}
|
|
|
|
func (mr *MapRenderer) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image d2interface.Surface) {
|
|
lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex)
|
|
|
|
if mr.imageCacheRecords == nil {
|
|
mr.imageCacheRecords = make(map[uint32]d2interface.Surface)
|
|
}
|
|
|
|
mr.imageCacheRecords[lookupIndex] = image
|
|
}
|