diff --git a/d2common/d2data/d2datadict/levels.go b/d2common/d2data/d2datadict/levels.go index bb11679d..280d45e6 100644 --- a/d2common/d2data/d2datadict/levels.go +++ b/d2common/d2data/d2datadict/levels.go @@ -377,6 +377,16 @@ type LevelDetailsRecord struct { var LevelDetails map[int]*LevelDetailsRecord +func GetLevelDetails(id int) *LevelDetailsRecord { + for i := 0; i < len(LevelDetails); i++ { + if LevelDetails[i].Id == id { + return LevelDetails[i] + } + } + + return nil +} + func LoadLevelDetails(file []byte) { dict := d2common.LoadDataDictionary(string(file)) numRecords := len(dict.Data) diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 8c693c75..e38624bc 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -2,6 +2,7 @@ package d2mapengine import ( "log" + "strings" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" @@ -28,6 +29,7 @@ type MapEngine struct { walkMesh []d2common.PathTile // The walk mesh startSubTileX int // The starting X position startSubTileY int // The starting Y position + dt1Files []string // The list of DS1 strings } // Creates a new instance of the map engine @@ -52,21 +54,55 @@ func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) { m.tiles = make([]d2ds1.TileRecord, width*height) m.dt1TileData = make([]d2dt1.Tile, 0) m.walkMesh = make([]d2common.PathTile, width*height*25) + m.dt1Files = make([]string, 0) for _, dtFileName := range m.levelType.Files { - if len(dtFileName) == 0 || dtFileName == "0" { - continue - } - fileData, err := d2asset.LoadFile("/data/global/tiles/" + dtFileName) - if err != nil { - panic(err) - } - dt1, _ := d2dt1.LoadDT1(fileData) - m.dt1TileData = append(m.dt1TileData, dt1.Tiles...) + m.addDT1(dtFileName) } } +func (m *MapEngine) addDT1(fileName string) { + if len(fileName) == 0 || fileName == "0" { + return + } + fileName = strings.ToLower(fileName) + for i := 0; i < len(m.dt1Files); i++ { + if m.dt1Files[i] == fileName { + return + } + } + + fileData, err := d2asset.LoadFile("/data/global/tiles/" + fileName) + if err != nil { + panic(err) + } + dt1, _ := d2dt1.LoadDT1(fileData) + m.dt1TileData = append(m.dt1TileData, dt1.Tiles...) + m.dt1Files = append(m.dt1Files, fileName) +} + +func (m *MapEngine) AddDS1(fileName string) { + if len(fileName) == 0 || fileName == "0" { + return + } + + fileData, err := d2asset.LoadFile("/data/global/tiles/" + fileName) + if err != nil { + panic(err) + } + ds1, _ := d2ds1.LoadDS1(fileData) + for _, dt1File := range ds1.Files { + dt1File := strings.ToLower(dt1File) + if strings.Contains(dt1File, ".tg1") { + continue + } + dt1File = strings.Replace(dt1File, "c:", "", -1) // Yes they did... + dt1File = strings.Replace(dt1File, "\\d2\\data\\global\\tiles\\", "", -1) + m.addDT1(strings.Replace(dt1File, "\\", "/", -1)) + } +} + func (m *MapEngine) FindTile(style, sequence, tileType int32) d2dt1.Tile { for _, tile := range m.dt1TileData { if tile.Style == style && tile.Sequence == sequence && tile.Type == tileType { diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index e7e5025b..4f539f5a 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -3,35 +3,140 @@ package d2mapgen import ( "log" "math/rand" + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen/d2wilderness" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp" ) +var wildernessGrass = d2ds1.FloorShadowRecord{Prop1: 1, Style: 0, Sequence: 0} + +type TownDirection int + +const ( + TownDirectionNorth TownDirection = iota + TownDirectionSouth + TownDirectionEast + TownDirectionWest +) + +func loadPreset(mapEngine *d2mapengine.MapEngine, id, index int) *d2mapstamp.Stamp { + for _, file := range d2datadict.LevelPresets[id].Files { + mapEngine.AddDS1(file) + } + return d2mapstamp.LoadStamp(d2enum.RegionAct1Wilderness, id, index) +} + func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) { - log.Printf("Map seed: %d", mapEngine.Seed()) + rand.Seed(mapEngine.Seed()) - townStamp := d2mapstamp.LoadStamp(d2enum.RegionAct1Town, 1, -1) - townSize := townStamp.Size() - mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff - - wildernessGrass := d2ds1.FloorShadowRecord{ - Prop1: 195, - Style: 0, - } - + wilderness1Details := d2datadict.GetLevelDetails(2) + mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150) mapWidth := mapEngine.Size().Width - for y := 0; y < 100; y++ { - for x := 0; x < 100; x++ { - (*mapEngine.Tiles())[x+(y*mapWidth)].RegionType = d2enum.RegionAct1Wilderness - (*mapEngine.Tiles())[x+(y*mapWidth)].Floors = []d2ds1.FloorShadowRecord{wildernessGrass} - } - } + mapHeight := mapEngine.Size().Height - mapEngine.PlaceStamp(townStamp, 50-(townSize.Width/2), 50-(townSize.Height/2)) + townStamp := d2mapstamp.LoadStamp(d2enum.RegionAct1Town, 1, -1) + townStamp.RegionPath() + townSize := townStamp.Size() + var townDirection TownDirection + + log.Printf("Region Path: %s", townStamp.RegionPath()) + if strings.Contains(townStamp.RegionPath(), "E1") { + // East Exit + townDirection = TownDirectionEast + mapEngine.PlaceStamp(townStamp, 0, 0) + + } else if strings.Contains(townStamp.RegionPath(), "S1") { + // South Exit + townDirection = TownDirectionSouth + mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0) + rightWaterBorderStamp := d2mapstamp.LoadStamp(d2enum.RegionAct1Wilderness, d2wilderness.WaterBorderEast, 0) + rightWaterBorderStamp2 := d2mapstamp.LoadStamp(d2enum.RegionAct1Wilderness, d2wilderness.WaterBorderWest, 0) + // Place the water on the right side of the map + for y := townSize.Height; y < mapHeight-9; y += 9 { + mapEngine.PlaceStamp(rightWaterBorderStamp, mapWidth-17, y) + mapEngine.PlaceStamp(rightWaterBorderStamp2, mapWidth-9, y) + } + generateWilderness1(mapEngine, mapWidth-wilderness1Details.SizeXNormal-14, townSize.Height, townDirection) + + } else if strings.Contains(townStamp.RegionPath(), "W1") { + // West Exit + townDirection = TownDirectionWest + mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) + + } else { + // North Exit + townDirection = TownDirectionNorth + mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) + + } mapEngine.RegenerateWalkPaths() } + +func generateWilderness1(mapEngine *d2mapengine.MapEngine, startX, startY int, townDirection TownDirection) { + levelDetails := d2datadict.GetLevelDetails(2) + mapWidth := mapEngine.Size().Width + + fenceNorthStamp := []*d2mapstamp.Stamp{ + loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0), + loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1), + loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2), + } + + fenceWestStamp := []*d2mapstamp.Stamp{ + loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0), + loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1), + loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2), + } + + fenceSouthStamp := []*d2mapstamp.Stamp{ + loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0), + loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1), + loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2), + } + + fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0) + fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0) + fenceWaterBorderSouthEast := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 1) + + // Fill in the grass + for y := 0; y < levelDetails.SizeYNormal; y++ { + for x := 0; x < levelDetails.SizeXNormal; x++ { + (*mapEngine.Tiles())[startX+x+((startY+y)*mapWidth)].RegionType = d2enum.RegionIdType(levelDetails.LevelType) + (*mapEngine.Tiles())[startX+x+((startY+y)*mapWidth)].Floors = []d2ds1.FloorShadowRecord{wildernessGrass} + } + } + + // Draw the north fence + if townDirection == TownDirectionSouth { + for i := 0; i < 4; i++ { + mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)+5, startY-6) + } + } + + // Draw the west fence + if townDirection == TownDirectionSouth { + for i := 0; i < 8; i++ { + mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)+3) + } + } + + // Draw the south fence + if townDirection == TownDirectionSouth { + for i := 1; i < 9; i++ { + mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(8*9)+3) + } + } + + mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-6) + mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+(8*9)+3) + mapEngine.PlaceStamp(fenceWaterBorderSouthEast, startX+(9*9)-4, startY+(8*9)+1) +} diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 6a4f0cf1..cd7e1960 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -4,6 +4,7 @@ import ( "errors" "image/color" "log" + "math" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" @@ -58,12 +59,20 @@ func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) { } func (mr *MapRenderer) Render(target d2render.Surface) { - mr.renderPass1(mr.viewport, target) + mapSize := mr.mapEngine.Size() + stxf, styf := mr.viewport.ScreenToWorld(400, -200) + etxf, etyf := mr.viewport.ScreenToWorld(400, 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) if mr.debugVisLevel > 0 { - mr.renderDebug(mr.debugVisLevel, mr.viewport, target) + mr.renderDebug(mr.debugVisLevel, target, startX, startY, endX, endY) } - mr.renderPass2(mr.viewport, target) - mr.renderPass3(mr.viewport, target) + mr.renderPass2(target, startX, startY, endX, endY) + mr.renderPass3(target, startX, startY, endX, endY) } func (mr *MapRenderer) MoveCameraTo(x, y float64) { @@ -86,59 +95,47 @@ func (mr *MapRenderer) WorldToOrtho(x, y float64) (float64, float64) { return mr.viewport.WorldToOrtho(x, y) } -func (mr *MapRenderer) renderPass1(viewport *Viewport, target d2render.Surface) { - mapSize := mr.mapEngine.Size() - // TODO: Render based on visible area - for tileY := 0; tileY < mapSize.Height; tileY++ { - for tileX := 0; tileX < mapSize.Width; tileX++ { +func (mr *MapRenderer) renderPass1(target d2render.Surface, startX, startY, endX, endY int) { + for tileY := startY; tileY < endY; tileY++ { + for tileX := startX; tileX < endX; tileX++ { tile := mr.mapEngine.TileAt(tileX, tileY) - if viewport.IsTileVisible(float64(tileX), float64(tileY)) { - viewport.PushTranslationWorld(float64(tileX), float64(tileY)) - mr.renderTilePass1(tile, target) - viewport.PopTranslation() - } + mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY)) + mr.renderTilePass1(tile, target) + mr.viewport.PopTranslation() } } } -func (mr *MapRenderer) renderPass2(viewport *Viewport, target d2render.Surface) { - mapSize := mr.mapEngine.Size() - - // TODO: Render based on visible area - for tileY := 0; tileY < mapSize.Height; tileY++ { - for tileX := 0; tileX < mapSize.Width; tileX++ { +func (mr *MapRenderer) renderPass2(target d2render.Surface, startX, startY, endX, endY int) { + for tileY := startY; tileY < endY; tileY++ { + for tileX := startX; tileX < endX; tileX++ { tile := mr.mapEngine.TileAt(tileX, tileY) - if viewport.IsTileVisible(float64(tileX), float64(tileY)) { - viewport.PushTranslationWorld(float64(tileX), float64(tileY)) - mr.renderTilePass2(tile, target) + mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY)) + mr.renderTilePass2(tile, target) - // TODO: Do not loop over every entity every frame - for _, mapEntity := range *mr.mapEngine.Entities() { - entityX, entityY := mapEntity.GetPosition() - if (int(entityX) != tileX) || (int(entityY) != tileY) { - continue - } - target.PushTranslation(viewport.GetTranslationScreen()) - mapEntity.Render(target) - target.Pop() + // TODO: Do not loop over every entity every frame + for _, mapEntity := range *mr.mapEngine.Entities() { + entityX, entityY := mapEntity.GetPosition() + if (int(entityX) != tileX) || (int(entityY) != tileY) { + continue } - viewport.PopTranslation() + target.PushTranslation(mr.viewport.GetTranslationScreen()) + mapEntity.Render(target) + target.Pop() } + mr.viewport.PopTranslation() } } } -func (mr *MapRenderer) renderPass3(viewport *Viewport, target d2render.Surface) { - mapSize := mr.mapEngine.Size() - // TODO: Render based on visible area - for tileY := 0; tileY < mapSize.Height; tileY++ { - for tileX := 0; tileX < mapSize.Width; tileX++ { +func (mr *MapRenderer) renderPass3(target d2render.Surface, startX, startY, endX, endY int) { + for tileY := startY; tileY < endY; tileY++ { + for tileX := startX; tileX < endX; tileX++ { tile := mr.mapEngine.TileAt(tileX, tileY) - if viewport.IsTileVisible(float64(tileX), float64(tileY)) { - viewport.PushTranslationWorld(float64(tileX), float64(tileY)) - mr.renderTilePass3(tile, target) - viewport.PopTranslation() - } + mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY)) + mr.renderTilePass3(tile, target) + mr.viewport.PopTranslation() + } } @@ -233,16 +230,12 @@ func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2rende target.Render(img) } -func (mr *MapRenderer) renderDebug(debugVisLevel int, viewport *Viewport, target d2render.Surface) { - mapSize := mr.mapEngine.Size() - // TODO: Render based on visible area - for tileY := 0; tileY < mapSize.Height; tileY++ { - for tileX := 0; tileX < mapSize.Width; tileX++ { - if viewport.IsTileVisible(float64(tileX), float64(tileY)) { - viewport.PushTranslationWorld(float64(tileX), float64(tileY)) - mr.renderTileDebug(tileX, tileY, debugVisLevel, target) - viewport.PopTranslation() - } +func (mr *MapRenderer) renderDebug(debugVisLevel int, target d2render.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, debugVisLevel, target) + mr.viewport.PopTranslation() } } } diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index 5e2278ce..1ae5f3e2 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -1,6 +1,7 @@ package d2gamescreen import ( + "log" "os" "time" @@ -104,6 +105,7 @@ func CreateMapEngineTest(currentRegion int, levelPreset int) *MapEngineTest { } func (met *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) { + log.Printf("Loaded region: Type(%d) LevelPreset(%d) FileIndex(%d)", n, levelPreset, fileIndex) d2maprenderer.InvalidateImageCache() for _, spec := range regions { if spec.regionType == d2enum.RegionIdType(n) { @@ -158,8 +160,6 @@ func (met *MapEngineTest) OnUnload() error { func (met *MapEngineTest) Render(screen d2render.Surface) error { met.mapRenderer.Render(screen) - //screenX, screenY := d2render.GetCursorPos() - //worldX, worldY := met.mapRenderer.ScreenToWorld(screenX, screenY) // //levelFilesToPick := make([]string, 0) //fileIndex := met.fileIndex @@ -179,11 +179,6 @@ func (met *MapEngineTest) Render(screen d2render.Surface) error { //} //met.filesCount = len(levelFilesToPick) // - //tileX := int(math.Floor(worldX)) - //tileY := int(math.Floor(worldY)) - // - //subtileX := int((worldX - float64(int(worldX))) * 5) - //subtileY := int((worldY - float64(int(worldY))) * 5) // //regionWidth, regionHeight := curRegion.GetTileSize() //if tileX >= 0 && tileY >= 0 && tileX < regionWidth && tileY < regionHeight { @@ -305,7 +300,8 @@ func (met *MapEngineTest) OnKeyDown(event d2input.KeyEvent) bool { if event.Key == d2input.KeyN { if event.KeyMod == d2input.KeyModControl { - met.fileIndex = increment(met.fileIndex, 0, met.filesCount-1) + //met.fileIndex = increment(met.fileIndex, 0, met.filesCount-1) + met.fileIndex++ d2screen.SetNextScreen(met) } else if event.KeyMod == d2input.KeyModShift { met.levelPreset = increment(met.levelPreset, met.regionSpec.startPresetIndex, met.regionSpec.endPresetIndex) @@ -320,7 +316,8 @@ func (met *MapEngineTest) OnKeyDown(event d2input.KeyEvent) bool { if event.Key == d2input.KeyP { if event.KeyMod == d2input.KeyModControl { - met.fileIndex = decrement(met.fileIndex, 0, met.filesCount-1) + //met.fileIndex = decrement(met.fileIndex, 0, met.filesCount-1) + met.fileIndex-- d2screen.SetNextScreen(met) } else if event.KeyMod == d2input.KeyModShift { met.levelPreset = decrement(met.levelPreset, met.regionSpec.startPresetIndex, met.regionSpec.endPresetIndex)