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