Added object support (#93)

* Fixed LevelTypes load
* Update ResourcePaths.go
* Added DCC loading support
* Added animation data. Fixed bitshift version compile issue.
* Fixed another go build error
* Initial support for object rendering
This commit is contained in:
Tim Sarbin 2019-11-06 18:25:19 -05:00 committed by GitHub
parent 2ec5dd2d85
commit 01a48d8720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 641 additions and 105 deletions

125
Common/AnimatedEntity.go Normal file
View File

@ -0,0 +1,125 @@
package Common
import (
"fmt"
"strings"
"time"
"github.com/OpenDiablo2/OpenDiablo2/PaletteDefs"
"github.com/hajimehoshi/ebiten"
)
type AnimatedEntity struct {
dcc *DCC
cof *Cof
palette PaletteDefs.PaletteType
base string
token string
tr string
animationMode string
weaponClass string
lastFrameTime time.Time
framesToAnimate int
animationSpeed int
direction int
currentFrame int
LocationX float64
LocationY float64
frames []*ebiten.Image
frameLocations []Rectangle
}
func CreateAnimatedEntity(base, token, tr string, palette PaletteDefs.PaletteType) *AnimatedEntity {
result := &AnimatedEntity{
base: base,
token: token,
tr: tr,
palette: palette,
}
return result
}
var DirectionLookup = []int{3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14}
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int, provider FileProvider) {
dccPath := fmt.Sprintf("%s/%s/tr/%str%s%s%s.dcc", v.base, v.token, v.token, v.tr, animationMode, weaponClass)
v.dcc = LoadDCC(dccPath, provider)
cofPath := fmt.Sprintf("%s/%s/cof/%s%s%s.cof", v.base, v.token, v.token, animationMode, weaponClass)
v.cof = LoadCof(cofPath, provider)
v.animationMode = animationMode
v.weaponClass = weaponClass
v.direction = direction
v.cacheFrames()
}
func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) {
for v.lastFrameTime.Add(time.Millisecond * time.Duration(v.animationSpeed)).Before(time.Now()) {
v.lastFrameTime = v.lastFrameTime.Add(time.Millisecond * time.Duration(v.animationSpeed))
v.currentFrame++
if v.currentFrame >= v.framesToAnimate {
v.currentFrame = 0
}
}
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(v.frameLocations[v.currentFrame].Left+offsetX), float64(v.frameLocations[v.currentFrame].Top+offsetY+40))
target.DrawImage(v.frames[v.currentFrame], opts)
}
func (v *AnimatedEntity) cacheFrames() {
animationData := AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)][v.direction]
v.animationSpeed = int((float64(animationData.AnimationSpeed) / 255.0) * 0.04 * 1000.0)
v.framesToAnimate = animationData.FramesPerDirection
v.lastFrameTime = time.Now()
minX := int32(2147483647)
minY := int32(2147483647)
maxX := int32(-2147483648)
maxY := int32(-2147483648)
for _, layer := range v.dcc.Directions {
minX = MinInt32(minX, int32(layer.Box.Left))
minY = MinInt32(minY, int32(layer.Box.Top))
maxX = MaxInt32(maxX, int32(layer.Box.Right()))
maxY = MaxInt32(maxY, int32(layer.Box.Bottom()))
}
frameW := maxX - minX
frameH := maxY - minY
v.frames = make([]*ebiten.Image, v.framesToAnimate)
v.frameLocations = make([]Rectangle, v.framesToAnimate)
for frameIndex := range v.frames {
v.frames[frameIndex], _ = ebiten.NewImage(int(frameW), int(frameH), ebiten.FilterNearest)
priorityBase := (v.direction * animationData.FramesPerDirection * v.cof.NumberOfLayers) + (frameIndex * v.cof.NumberOfLayers)
for layerIdx := 0; layerIdx < v.cof.NumberOfLayers; layerIdx++ {
comp := v.cof.Priority[priorityBase+layerIdx]
if _, found := v.cof.CompositeLayers[comp]; !found {
continue
}
direction := v.dcc.Directions[v.direction]
frame := direction.Frames[frameIndex]
pixelData := make([]byte, 4*frameW*frameH)
for y := 0; y < direction.Box.Height; y++ {
for x := 0; x < direction.Box.Width; x++ {
paletteIndex := frame.PixelData[x+(y*direction.Box.Width)]
if paletteIndex == 0 {
continue
}
color := Palettes[v.palette].Colors[paletteIndex]
actualX := x + direction.Box.Left - int(minX)
actualY := y + direction.Box.Top - int(minY)
pixelData[(actualX*4)+(actualY*int(frameW)*4)] = color.R
pixelData[(actualX*4)+(actualY*int(frameW)*4)+1] = color.G
pixelData[(actualX*4)+(actualY*int(frameW)*4)+2] = color.B
pixelData[(actualX*4)+(actualY*int(frameW)*4)+3] = 255
}
}
v.frames[frameIndex].ReplacePixels(pixelData)
v.frameLocations[frameIndex] = Rectangle{
Left: int(minX),
Top: int(minY),
Width: int(frameW),
Height: int(frameH),
}
}
}
}

244
Common/Cof.go Normal file
View File

@ -0,0 +1,244 @@
package Common
import "strings"
type AnimationMode int
const (
AnimationModePlayerDeath AnimationMode = 0
AnimationModePlayerNeutral AnimationMode = 1
AnimationModePlayerWalk AnimationMode = 2
AnimationModePlayerRun AnimationMode = 3
AnimationModePlayerGetHit AnimationMode = 4
AnimationModePlayerTownNeutral AnimationMode = 5
AnimationModePlayerTownWalk AnimationMode = 6
AnimationModePlayerAttack1 AnimationMode = 7
AnimationModePlayerAttack2 AnimationMode = 8
AnimationModePlayerBlock AnimationMode = 9
AnimationModePlayerCast AnimationMode = 10
AnimationModePlayerThrow AnimationMode = 11
AnimationModePlayerKick AnimationMode = 12
AnimationModePlayerSkill1 AnimationMode = 13
AnimationModePlayerSkill2 AnimationMode = 14
AnimationModePlayerSkill3 AnimationMode = 15
AnimationModePlayerSkill4 AnimationMode = 16
AnimationModePlayerDead AnimationMode = 17
AnimationModePlayerSequence AnimationMode = 18
AnimationModePlayerKnockBack AnimationMode = 19
AnimationModeMonsterDeath AnimationMode = 20
AnimationModeMonsterNeutral AnimationMode = 21
AnimationModeMonsterWalk AnimationMode = 22
AnimationModeMonsterGetHit AnimationMode = 23
AnimationModeMonsterAttack1 AnimationMode = 24
AnimationModeMonsterAttack2 AnimationMode = 25
AnimationModeMonsterBlock AnimationMode = 26
AnimationModeMonsterCast AnimationMode = 27
AnimationModeMonsterSkill1 AnimationMode = 28
AnimationModeMonsterSkill2 AnimationMode = 29
AnimationModeMonsterSkill3 AnimationMode = 30
AnimationModeMonsterSkill4 AnimationMode = 31
AnimationModeMonsterDead AnimationMode = 32
AnimationModeMonsterKnockback AnimationMode = 33
AnimationModeMonsterSequence AnimationMode = 34
AnimationModeMonsterRun AnimationMode = 35
AnimationModeObjectNeutral AnimationMode = 36
AnimationModeObjectOperating AnimationMode = 37
AnimationModeObjectOpened AnimationMode = 38
AnimationModeObjectSpecial1 AnimationMode = 39
AnimationModeObjectSpecial2 AnimationMode = 40
AnimationModeObjectSpecial3 AnimationMode = 41
AnimationModeObjectSpecial4 AnimationMode = 42
AnimationModeObjectSpecial5 AnimationMode = 43
)
var AnimationModeStr = map[AnimationMode]string{
AnimationModePlayerDeath: "DT",
AnimationModePlayerNeutral: "NU",
AnimationModePlayerWalk: "WL",
AnimationModePlayerRun: "RN",
AnimationModePlayerGetHit: "GH",
AnimationModePlayerTownNeutral: "TN",
AnimationModePlayerTownWalk: "TW",
AnimationModePlayerAttack1: "A1",
AnimationModePlayerAttack2: "A2",
AnimationModePlayerBlock: "BL",
AnimationModePlayerCast: "SC",
AnimationModePlayerThrow: "TH",
AnimationModePlayerKick: "KK",
AnimationModePlayerSkill1: "S1",
AnimationModePlayerSkill2: "S2",
AnimationModePlayerSkill3: "S3",
AnimationModePlayerSkill4: "S4",
AnimationModePlayerDead: "DD",
AnimationModePlayerSequence: "GH",
AnimationModePlayerKnockBack: "GH",
AnimationModeMonsterDeath: "DT",
AnimationModeMonsterNeutral: "NU",
AnimationModeMonsterWalk: "WL",
AnimationModeMonsterGetHit: "GH",
AnimationModeMonsterAttack1: "A1",
AnimationModeMonsterAttack2: "A2",
AnimationModeMonsterBlock: "BL",
AnimationModeMonsterCast: "SC",
AnimationModeMonsterSkill1: "S1",
AnimationModeMonsterSkill2: "S2",
AnimationModeMonsterSkill3: "S3",
AnimationModeMonsterSkill4: "S4",
AnimationModeMonsterDead: "DD",
AnimationModeMonsterKnockback: "GH",
AnimationModeMonsterSequence: "xx",
AnimationModeMonsterRun: "RN",
AnimationModeObjectNeutral: "NU",
AnimationModeObjectOperating: "OP",
AnimationModeObjectOpened: "ON",
AnimationModeObjectSpecial1: "S1",
AnimationModeObjectSpecial2: "S2",
AnimationModeObjectSpecial3: "S3",
AnimationModeObjectSpecial4: "S4",
AnimationModeObjectSpecial5: "S5",
}
type CompositeType int
const (
CompositeTypeHead CompositeType = 0
CompositeTypeTorso CompositeType = 1
CompositeTypeLegs CompositeType = 2
CompositeTypeRightArm CompositeType = 3
CompositeTypeLeftArm CompositeType = 4
CompositeTypeRightHand CompositeType = 5
CompositeTypeLeftHand CompositeType = 6
CompositeTypeShield CompositeType = 7
CompositeTypeSpecial1 CompositeType = 8
CompositeTypeSpecial2 CompositeType = 9
CompositeTypeSpecial3 CompositeType = 10
CompositeTypeSpecial4 CompositeType = 11
CompositeTypeSpecial5 CompositeType = 12
CompositeTypeSpecial6 CompositeType = 13
CompositeTypeSpecial7 CompositeType = 14
CompositeTypeSpecial8 CompositeType = 15
CompositeTypeMax CompositeType = 16
)
type DrawEffect int
const (
DrawEffectPctTransparency75 = 0 //75 % transparency (colormaps 561-816 in a .pl2)
DrawEffectPctTransparency50 = 1 //50 % transparency (colormaps 305-560 in a .pl2)
DrawEffectPctTransparency25 = 2 //25 % transparency (colormaps 49-304 in a .pl2)
DrawEffectScreen = 3 //Screen (colormaps 817-1072 in a .pl2)
DrawEffectLuminance = 4 //luminance (colormaps 1073-1328 in a .pl2)
DrawEffectBringAlphaBlending = 5 //bright alpha blending (colormaps 1457-1712 in a .pl2)
)
type WeaponClass int
const (
WeaponClassNone WeaponClass = 0
WeaponClassHandToHand WeaponClass = 1
WeaponClassBow WeaponClass = 2
WeaponClassOneHandSwing WeaponClass = 3
WeaponClassOneHandThrust WeaponClass = 4
WeaponClassStaff WeaponClass = 5
WeaponClassTwoHandSwing WeaponClass = 6
WeaponClassTwoHandThrust WeaponClass = 7
WeaponClassCrossbow WeaponClass = 8
WeaponClassLeftJabRightSwing WeaponClass = 9
WeaponClassLeftJabRightThrust WeaponClass = 10
WeaponClassLeftSwingRightSwing WeaponClass = 11
WeaponClassLeftSwingRightThrust WeaponClass = 12
WeaponClassOneHandToHand WeaponClass = 13
WeaponClassTwoHandToHand WeaponClass = 14
)
var WeaponClassStr = map[WeaponClass]string{
WeaponClassNone: "",
WeaponClassHandToHand: "hth",
WeaponClassBow: "bow",
WeaponClassOneHandSwing: "1hs",
WeaponClassOneHandThrust: "1ht",
WeaponClassStaff: "stf",
WeaponClassTwoHandSwing: "2hs",
WeaponClassTwoHandThrust: "2ht",
WeaponClassCrossbow: "xbw",
WeaponClassLeftJabRightSwing: "1js",
WeaponClassLeftJabRightThrust: "1jt",
WeaponClassLeftSwingRightSwing: "1ss",
WeaponClassLeftSwingRightThrust: "1st",
WeaponClassOneHandToHand: "ht1",
WeaponClassTwoHandToHand: "ht2",
}
func GetWeaponClass(val string) WeaponClass {
for weaponClass, weaponStr := range WeaponClassStr {
if val != weaponStr {
continue
}
return weaponClass
}
return WeaponClassNone
}
type AnimationFrame int
const (
AnimationFrameNoEvent AnimationFrame = 0
AnimationFrameAttack AnimationFrame = 1
AnimationFrameMissile AnimationFrame = 2
AnimationFrameSound AnimationFrame = 3
AnimationFrameSkill AnimationFrame = 4
)
type CofLayer struct {
Type CompositeType
Shadow byte
Transparent bool
DrawEffect DrawEffect
WeaponClass WeaponClass
}
type Cof struct {
NumberOfDirections int
FramesPerDirection int
NumberOfLayers int
CofLayers []*CofLayer
CompositeLayers map[CompositeType]int
AnimationFrames []AnimationFrame
Priority []CompositeType
}
func LoadCof(fileName string, fileProvider FileProvider) *Cof {
result := &Cof{}
fileData := fileProvider.LoadFile(fileName)
streamReader := CreateStreamReader(fileData)
result.NumberOfLayers = int(streamReader.GetByte())
result.FramesPerDirection = int(streamReader.GetByte())
result.NumberOfDirections = int(streamReader.GetByte())
streamReader.SkipBytes(25) // Skip 25 unknown bytes...
result.CofLayers = make([]*CofLayer, 0)
result.CompositeLayers = make(map[CompositeType]int, 0)
for i := 0; i < result.NumberOfLayers; i++ {
layer := &CofLayer{}
layer.Type = CompositeType(streamReader.GetByte())
layer.Shadow = streamReader.GetByte()
streamReader.SkipBytes(1) // Unknown
layer.Transparent = streamReader.GetByte() != 0
layer.DrawEffect = DrawEffect(streamReader.GetByte())
weaponClassStr, _ := streamReader.ReadBytes(4)
layer.WeaponClass = GetWeaponClass(strings.TrimSpace(strings.ReplaceAll(string(weaponClassStr), string(0), "")))
result.CofLayers = append(result.CofLayers, layer)
result.CompositeLayers[layer.Type] = i
}
animationFrameBytes, _ := streamReader.ReadBytes(result.FramesPerDirection)
result.AnimationFrames = make([]AnimationFrame, result.FramesPerDirection)
for i := range animationFrameBytes {
result.AnimationFrames[i] = AnimationFrame(animationFrameBytes[i])
}
priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers
result.Priority = make([]CompositeType, priorityLen)
priorityBytes, _ := streamReader.ReadBytes(priorityLen)
for i := range priorityBytes {
result.Priority[i] = CompositeType(priorityBytes[i])
}
return result
}

View File

@ -513,5 +513,6 @@ func LoadDCC(path string, fileProvider FileProvider) *DCC {
for i := 0; i < result.NumberOfDirections; i++ {
result.Directions[i] = CreateDCCDirection(CreateBitMuncher(fileData, directionOffsets[i]*8), result)
}
return result
}

View File

@ -58,3 +58,9 @@ func IsoToScreen(isoX, isoY, modX, modY int) (int, int) {
screenY := (isoX + isoY) * 40
return screenX + modX, screenY + modY
}
func ScreenToIso(sx, sy float64) (float64, float64) {
x := (sx/80 + sy/40) / 2
y := (sy/40 - (sx / 80)) / 2
return x, y
}

View File

@ -2,7 +2,6 @@ package Common
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/PaletteDefs"
)
@ -34,15 +33,15 @@ func CreatePalette(name PaletteDefs.PaletteType, data []byte) PaletteRec {
return result
}
func LoadPalettes(mpqFiles map[string]*MpqFileRecord, fileProvider FileProvider) {
func LoadPalettes(mpqFiles map[string]string, fileProvider FileProvider) {
Palettes = make(map[PaletteDefs.PaletteType]PaletteRec)
for file := range mpqFiles {
if strings.Index(file, "/data/global/palette/") != 0 || strings.Index(file, ".dat") != len(file)-4 {
continue
}
nameParts := strings.Split(file, `/`)
paletteName := PaletteDefs.PaletteType(nameParts[len(nameParts)-2])
palette := CreatePalette(paletteName, fileProvider.LoadFile(file))
for _, pal := range []string{
"act1", "act2", "act3", "act4", "act5", "endgame", "endgame2", "fechar", "loading",
"menu0", "menu1", "menu2", "menu3", "menu4", "sky", "static", "trademark", "units",
} {
filePath := `data\global\palette\` + pal + `\pal.dat`
paletteName := PaletteDefs.PaletteType(pal)
palette := CreatePalette(paletteName, fileProvider.LoadFile(filePath))
Palettes[paletteName] = palette
}
log.Printf("Loaded %d palettes", len(Palettes))

View File

@ -14,3 +14,7 @@ func (v *Rectangle) Bottom() int {
func (v *Rectangle) Right() int {
return v.Left + v.Width
}
func (v *Rectangle) IsInRect(x, y int) bool {
return x >= v.Left && x < v.Left+v.Width && y >= v.Top && y < v.Top+v.Height
}

View File

@ -2,7 +2,6 @@ package Core
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math"
@ -11,12 +10,13 @@ import (
"strings"
"sync"
"github.com/OpenDiablo2/OpenDiablo2/MPQ"
"github.com/OpenDiablo2/OpenDiablo2/PaletteDefs"
"github.com/OpenDiablo2/OpenDiablo2/Sound"
"github.com/OpenDiablo2/OpenDiablo2/Common"
"github.com/OpenDiablo2/OpenDiablo2/MPQ"
"github.com/OpenDiablo2/OpenDiablo2/ResourcePaths"
"github.com/OpenDiablo2/OpenDiablo2/Scenes"
"github.com/OpenDiablo2/OpenDiablo2/UI"
@ -42,17 +42,17 @@ type EngineConfig struct {
// Engine is the core OpenDiablo2 engine
type Engine struct {
Settings *EngineConfig // Engine configuration settings from json file
Files map[string]*Common.MpqFileRecord // Map that defines which files are in which MPQs
CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that.
LoadingSprite *Common.Sprite // The sprite shown when loading stuff
loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
stepLoadingSize float64 // The size for each loading step
CurrentScene Scenes.Scene // The current scene being rendered
UIManager *UI.Manager // The UI manager
SoundManager *Sound.Manager // The sound manager
nextScene Scenes.Scene // The next scene to be loaded at the end of the game loop
fullscreenKey bool // When true, the fullscreen toggle is still being pressed
Settings *EngineConfig // Engine configuration settings from json file
Files map[string]string // Map that defines which files are in which MPQs
CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that.
LoadingSprite *Common.Sprite // The sprite shown when loading stuff
loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
stepLoadingSize float64 // The size for each loading step
CurrentScene Scenes.Scene // The current scene being rendered
UIManager *UI.Manager // The UI manager
SoundManager *Sound.Manager // The sound manager
nextScene Scenes.Scene // The next scene to be loaded at the end of the game loop
fullscreenKey bool // When true, the fullscreen toggle is still being pressed
}
// CreateEngine creates and instance of the OpenDiablo2 engine
@ -115,6 +115,11 @@ func (v *Engine) loadConfigurationFile() {
}
}
func (v *Engine) mapMpqFiles() {
v.Files = make(map[string]string)
}
/*
func (v *Engine) mapMpqFiles() {
log.Println("mapping mpq file structure")
v.Files = make(map[string]*Common.MpqFileRecord)
@ -229,6 +234,37 @@ func (v *Engine) LoadFile(fileName string) []byte {
mutex.Unlock()
return result
}
*/
var mutex sync.Mutex
func (v *Engine) LoadFile(fileName string) []byte {
fileName = strings.ReplaceAll(fileName, "{LANG}", ResourcePaths.LanguageCode)
fileName = strings.ToLower(fileName)
fileName = strings.ReplaceAll(fileName, `/`, "\\")
if fileName[0] == '\\' {
fileName = fileName[1:]
}
mutex.Lock()
defer mutex.Unlock()
// TODO: May want to cache some things if performance becomes an issue
cachedMpqFile, cacheExists := v.Files[fileName]
if cacheExists {
mpq, _ := MPQ.Load(cachedMpqFile)
result, _ := mpq.ReadFile(fileName)
return result
}
for _, mpqFile := range v.Settings.MpqLoadOrder {
mpq, _ := MPQ.Load(path.Join(v.Settings.MpqPath, mpqFile))
if !mpq.FileExists(fileName) {
continue
}
v.Files[fileName] = path.Join(v.Settings.MpqPath, mpqFile)
result, _ := mpq.ReadFile(fileName)
return result
}
log.Fatalf("Could not load %s from MPQs", fileName)
return []byte{}
}
// IsLoading returns true if the engine is currently in a loading state
func (v *Engine) IsLoading() bool {

View File

@ -242,7 +242,6 @@ func (v *MPQ) Close() {
}
func (v MPQ) FileExists(fileName string) bool {
fileName = strings.ReplaceAll(fileName, "{LANG}", ResourcePaths.LanguageCode)
_, err := v.getFileHashEntry(fileName)
return err == nil
}

View File

@ -1,7 +1,7 @@
package Map
import (
"image"
"math"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/Common"
@ -10,7 +10,7 @@ import (
)
type EngineRegion struct {
Rect image.Rectangle
Rect Common.Rectangle
Region *Region
}
@ -37,76 +37,116 @@ func (v *Engine) GenerateMap(regionType RegionIdType, levelPreset int) {
randomSource := rand.NewSource(v.gameState.Seed)
region := LoadRegion(randomSource, regionType, levelPreset, v.fileProvider)
v.regions = append(v.regions, EngineRegion{
Rect: image.Rectangle{image.Point{0, 0}, image.Point{int(region.TileWidth), int(region.TileHeight)}},
Rect: Common.Rectangle{0, 0, int(region.TileWidth), int(region.TileHeight)},
Region: region,
})
v.soundManager.PlayBGM("/data/global/music/Act1/tristram.wav") // TODO: Temp stuff here
v.soundManager.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here
}
func (v *Engine) GetRegionAt(x, y int) *Region {
if v.regions == nil {
return nil
}
for _, region := range v.regions {
if !region.Rect.IsInRect(x, y) {
continue
}
return region.Region
}
return nil
}
func (v *Engine) Render(target *ebiten.Image) {
for y := 0; y < int(v.regions[0].Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(v.regions[0].Region.TileWidth); x++ {
sx, sy := Common.IsoToScreen(x, y, int(v.OffsetX), int(v.OffsetY))
if sx > -160 && sy > -160 && sx <= 800 && sy <= 1000 {
tile := v.regions[0].Region.DS1.Tiles[y][x]
for i := range tile.Floors {
if !tile.Floors[i].Hidden && tile.Floors[i].Prop1 != 0 {
v.regions[0].Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeFloors, i, target)
for _, region := range v.regions {
for y := 0; y < int(region.Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(region.Region.TileWidth); x++ {
sx, sy := Common.IsoToScreen(x, y, int(v.OffsetX), int(v.OffsetY))
if sx > -160 && sy > -160 && sx <= 880 && sy <= 1000 {
tile := region.Region.DS1.Tiles[y][x]
for i := range tile.Floors {
if tile.Floors[i].Hidden || tile.Floors[i].Prop1 == 0 {
continue
}
region.Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeFloors, i, target)
}
}
offX += 80
offY += 40
}
offX += 80
offY += 40
}
}
for y := 0; y < int(v.regions[0].Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(v.regions[0].Region.TileWidth); x++ {
sx, sy := Common.IsoToScreen(x, y, int(v.OffsetX), int(v.OffsetY))
if sx > -160 && sy > -160 && sx <= 800 && sy <= 1000 {
tile := v.regions[0].Region.DS1.Tiles[y][x]
for i := range tile.Shadows {
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
for _, region := range v.regions {
for y := 0; y < int(region.Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(region.Region.TileWidth); x++ {
sx, sy := Common.IsoToScreen(x, y, int(v.OffsetX), int(v.OffsetY))
if sx > -160 && sy > -160 && sx <= 880 && sy <= 1000 {
tile := region.Region.DS1.Tiles[y][x]
for i := range tile.Shadows {
if tile.Shadows[i].Hidden || tile.Shadows[i].Prop1 == 0 {
continue
}
region.Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeShadows, i, target)
}
}
offX += 80
offY += 40
}
}
}
for _, region := range v.regions {
for y := 0; y < int(region.Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(region.Region.TileWidth); x++ {
tile := region.Region.DS1.Tiles[y][x]
for i := range tile.Walls {
if tile.Walls[i].Hidden || tile.Walls[i].Orientation == 15 || tile.Walls[i].Orientation == 10 || tile.Walls[i].Orientation == 11 || tile.Walls[i].Orientation == 0 {
continue
}
v.regions[0].Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeShadows, i, target)
region.Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeWalls, i, target)
}
offX += 80
offY += 40
}
offX += 80
offY += 40
}
}
for y := 0; y < int(v.regions[0].Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(v.regions[0].Region.TileWidth); x++ {
tile := v.regions[0].Region.DS1.Tiles[y][x]
for i := range tile.Walls {
if tile.Walls[i].Hidden || tile.Walls[i].Orientation == 15 || tile.Walls[i].Orientation == 10 || tile.Walls[i].Orientation == 11 || tile.Walls[i].Orientation == 0 {
continue
for _, region := range v.regions {
for y := 0; y < int(region.Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(region.Region.TileWidth); x++ {
tile := region.Region.DS1.Tiles[y][x]
for i := range tile.Walls {
if tile.Walls[i].Hidden || tile.Walls[i].Orientation != 15 {
continue
}
region.Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeWalls, i, target)
}
v.regions[0].Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeWalls, i, target)
offX += 80
offY += 40
}
offX += 80
offY += 40
}
}
for y := 0; y < int(v.regions[0].Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(v.regions[0].Region.TileWidth); x++ {
tile := v.regions[0].Region.DS1.Tiles[y][x]
for i := range tile.Walls {
if tile.Walls[i].Hidden || tile.Walls[i].Orientation != 15 {
continue
for _, region := range v.regions {
for y := 0; y < int(region.Region.TileHeight); y++ {
offX := -(y * 80)
offY := y * 40
for x := 0; x < int(region.Region.TileWidth); x++ {
sx, sy := Common.IsoToScreen(x, y, int(v.OffsetX), int(v.OffsetY))
if sx > -160 && sy > -160 && sx <= 880 && sy <= 1000 {
for _, obj := range region.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))
}
}
}
v.regions[0].Region.RenderTile(offX+int(v.OffsetX), offY+int(v.OffsetY), x, y, RegionLayerTypeWalls, i, target)
offX += 80
offY += 40
}
offX += 80
offY += 40
}
}
}

View File

@ -6,7 +6,7 @@ import (
"math"
"math/rand"
"strconv"
"strings"
"sync"
"github.com/OpenDiablo2/OpenDiablo2/PaletteDefs"
@ -22,16 +22,17 @@ type TileCacheRecord struct {
}
type Region struct {
levelType Common.LevelTypeRecord
levelPreset *Common.LevelPresetRecord
TileWidth int32
TileHeight int32
Tiles []Tile
DS1 *DS1
Palette Common.PaletteRec
FloorCache map[uint32]*TileCacheRecord
ShadowCache map[uint32]*TileCacheRecord
WallCache map[uint32]*TileCacheRecord
LevelType Common.LevelTypeRecord
levelPreset *Common.LevelPresetRecord
TileWidth int32
TileHeight int32
Tiles []Tile
DS1 *DS1
Palette Common.PaletteRec
FloorCache map[uint32]*TileCacheRecord
ShadowCache map[uint32]*TileCacheRecord
WallCache map[uint32]*TileCacheRecord
AnimationEntities []*Common.AnimatedEntity
}
type RegionLayerType int
@ -84,16 +85,16 @@ const (
func LoadRegion(seed rand.Source, levelType RegionIdType, levelPreset int, fileProvider Common.FileProvider) *Region {
result := &Region{
levelType: Common.LevelTypes[levelType],
LevelType: Common.LevelTypes[levelType],
levelPreset: Common.LevelPresets[levelPreset],
Tiles: make([]Tile, 0),
FloorCache: make(map[uint32]*TileCacheRecord),
ShadowCache: make(map[uint32]*TileCacheRecord),
WallCache: make(map[uint32]*TileCacheRecord),
}
result.Palette = Common.Palettes[PaletteDefs.PaletteType("act"+strconv.Itoa(int(result.levelType.Act)))]
result.Palette = Common.Palettes[PaletteDefs.PaletteType("act"+strconv.Itoa(int(result.LevelType.Act)))]
//\bm := result.levelPreset.Dt1Mask
for _, levelTypeDt1 := range result.levelType.Files {
for _, levelTypeDt1 := range result.LevelType.Files {
/*
if bm&1 == 0 {
bm >>= 1
@ -120,22 +121,32 @@ func LoadRegion(seed rand.Source, levelType RegionIdType, levelPreset int, fileP
result.DS1 = LoadDS1("/data/global/tiles/"+levelFile, fileProvider)
result.TileWidth = result.DS1.Width
result.TileHeight = result.DS1.Height
var wg sync.WaitGroup
wg.Add(len(result.DS1.Objects))
result.AnimationEntities = make([]*Common.AnimatedEntity, 0)
for _, object := range result.DS1.Objects {
switch object.Lookup.Type {
case Common.ObjectTypeCharacter:
case Common.ObjectTypeItem:
if object.Lookup.Base == "" {
continue
go func(object Object) {
defer wg.Done()
switch object.Lookup.Type {
case Common.ObjectTypeCharacter:
case Common.ObjectTypeItem:
if object.Lookup.Base == "" || object.Lookup.Token == "" || object.Lookup.TR == "" {
return
}
animEntity := Common.CreateAnimatedEntity(object.Lookup.Base, object.Lookup.Token, object.Lookup.TR, PaletteDefs.Units)
animEntity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0, fileProvider)
animEntity.LocationX = math.Floor(float64(object.X) / 5)
animEntity.LocationY = math.Floor(float64(object.Y) / 5)
result.AnimationEntities = append(result.AnimationEntities, animEntity)
}
objPath := strings.ToLower(object.Lookup.Base + "/" + object.Lookup.Token + "/tr/" + object.Lookup.Token + "tr" + object.Lookup.TR +
object.Lookup.Mode + object.Lookup.Class + ".dcc")
_ = Common.LoadDCC(objPath, fileProvider) // This is where the magic will happen
}
}(object)
}
wg.Wait()
return result
}
func (v *Region) RenderTile(offsetX, offsetY, tileX, tileY int, layerType RegionLayerType, layerIndex int, target *ebiten.Image) {
offsetX -= 80
switch layerType {
case RegionLayerTypeFloors:
v.renderFloor(v.DS1.Tiles[tileY][tileX].Floors[layerIndex], offsetX, offsetY, target)

View File

@ -1,11 +1,15 @@
package Scenes
import (
"fmt"
"math"
"github.com/OpenDiablo2/OpenDiablo2/Common"
"github.com/OpenDiablo2/OpenDiablo2/Map"
"github.com/OpenDiablo2/OpenDiablo2/Sound"
"github.com/OpenDiablo2/OpenDiablo2/UI"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
)
type MapEngineTest struct {
@ -43,16 +47,16 @@ func (v *MapEngineTest) Load() []func() {
v.mapEngine.GenerateMap(Map.RegionAct1Town, 1)
//v.mapEngine.GenerateMap(Map.RegionAct1Tristram, 300)
//v.mapEngine.GenerateMap(Map.RegionAct1Cathedral, 257)
//v.mapEngine.GenerateMap(Map.RegionAct2Town, 301) // Broken rendering
//v.mapEngine.GenerateMap(Map.RegionAct2Harem, 353)
//v.mapEngine.GenerateMap(Map.RegionAct2Town, 301)
//v.mapEngine.GenerateMap(Map.RegionAct2Harem, 353) // Crashes on dcc load
//v.mapEngine.GenerateMap(Map.RegionAct3Town, 529)
//v.mapEngine.GenerateMap(Map.RegionAct3Jungle, 574)
//v.mapEngine.GenerateMap(Map.RegionAct4Town, 797)
//v.mapEngine.GenerateMap(Map.RegonAct5Town, 863)
//v.mapEngine.GenerateMap(Map.RegionAct5IceCaves, 1038)
//v.mapEngine.GenerateMap(Map.RegionAct5Siege, 879)
//v.mapEngine.GenerateMap(Map.RegionAct5Lava, 1057) // PALETTE ISSUE
//v.mapEngine.GenerateMap(Map.RegionAct5Barricade, 880)
//v.mapEngine.GenerateMap(Map.RegionAct4Town, 797) // Broken height of large objects
//v.mapEngine.GenerateMap(Map.RegonAct5Town, 863) // Completely broken!!
//v.mapEngine.GenerateMap(Map.RegionAct5IceCaves, 1038) // Completely broken!
//v.mapEngine.GenerateMap(Map.RegionAct5Siege, 879) // Completely broken!
//v.mapEngine.GenerateMap(Map.RegionAct5Lava, 1057) // Broken
//v.mapEngine.GenerateMap(Map.RegionAct5Barricade, 880) // Broken
},
}
@ -64,6 +68,18 @@ func (v *MapEngineTest) Unload() {
func (v *MapEngineTest) Render(screen *ebiten.Image) {
v.mapEngine.Render(screen)
actualX := float64(v.uiManager.CursorX) - v.mapEngine.OffsetX
actualY := float64(v.uiManager.CursorY) - v.mapEngine.OffsetY
tileX, tileY := Common.ScreenToIso(actualX, actualY)
subtileX := int(math.Ceil(math.Mod((tileX*10), 10))) / 2
subtileY := int(math.Ceil(math.Mod((tileY*10), 10))) / 2
line := fmt.Sprintf("%d, %d (Tile %d.%d, %d.%d)", int(math.Ceil(actualX)), int(math.Ceil(actualY)), int(math.Ceil(tileX)), subtileX, int(math.Ceil(tileY)), subtileY)
ebitenutil.DebugPrintAt(screen, line, 5, 5)
curRegion := v.mapEngine.GetRegionAt(int(tileX), int(tileY))
if curRegion == nil {
return
}
ebitenutil.DebugPrintAt(screen, "Map: "+curRegion.LevelType.Name, 5, 17)
}
func (v *MapEngineTest) Update(tickTime float64) {

7
go.mod
View File

@ -5,6 +5,11 @@ go 1.13
require (
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0
github.com/giorgisio/goav v0.1.0
github.com/hajimehoshi/ebiten v1.9.3
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de // indirect
github.com/gopherjs/gopherwasm v1.1.0 // indirect
github.com/hajimehoshi/ebiten v1.10.0
github.com/mitchellh/go-homedir v1.1.0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 // indirect
golang.org/x/mobile v0.0.0-20191031020345-0945064e013a // indirect
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd // indirect
)

50
go.sum
View File

@ -1,14 +1,20 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 h1:tDnuU0igiBiQFjsvq1Bi7DpoUjqI76VVvW045vpeFeM=
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0/go.mod h1:h/5OEGj4G+fpYxluLjSMZbFY011ZxAntO98nCl8mrCs=
github.com/giorgisio/goav v0.1.0 h1:ZyfG3NfX7PMSimv4ulhmnQJf/XeHpMdGCn+afRmY5Oc=
github.com/giorgisio/goav v0.1.0/go.mod h1:RtH8HyxLRLU1iY0pjfhWBKRhnbsnmfoI+FxMwb5bfEo=
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM=
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ=
@ -16,12 +22,19 @@ github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWG
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
github.com/hajimehoshi/bitmapfont v1.1.1/go.mod h1:Hamfxgney7tDSmVOSDh2AWzoDH70OaC+P24zc02Gum4=
github.com/hajimehoshi/bitmapfont v1.2.0/go.mod h1:h9QrPk6Ktb2neObTlAbma6Ini1xgMjbJ3w7ysmD7IOU=
github.com/hajimehoshi/ebiten v1.9.3 h1:YijWGMBwH2XA1ZytUQFy33UDHeCSS6d4JZKH1wq38O0=
github.com/hajimehoshi/ebiten v1.9.3/go.mod h1:XxiJ4Eltvb1KmcD0i6F81eIB1asJhK47y5DC+FPkyso=
github.com/hajimehoshi/ebiten v1.10.0 h1:ADLOUI/7aaTOP7GRlQBHVI1Qtvfdt9M6XQqeULSK7Uo=
github.com/hajimehoshi/ebiten v1.10.0/go.mod h1:8BJhIws+Jkol+z7hSGP/WFsaDAPTtRQ+ELBPPQetq2w=
github.com/hajimehoshi/go-mp3 v0.2.0/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
github.com/hajimehoshi/go-mp3 v0.2.1/go.mod h1:Rr+2P46iH6PwTPVgSsEwBkon0CK5DxCAeX/Rp65DCTE=
github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
github.com/hajimehoshi/oto v0.3.3 h1:Wi7VVtxe9sF2rbDBIJtVXnpFWhRfK57hw0JY7tR2qXM=
github.com/hajimehoshi/oto v0.3.3/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
github.com/hajimehoshi/oto v0.3.4/go.mod h1:PgjqsBJff0efqL2nlMJidJgVJywLn6M4y8PI4TfeWfA=
github.com/hajimehoshi/oto v0.5.2 h1:5FEPlejAsR2PVRqiW7h2PIwp9UWR+8zxj2And102YU4=
github.com/hajimehoshi/oto v0.5.2/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/jakecoffman/cp v0.1.0/go.mod h1:a3xPx9N8RyFAACD644t2dj/nK4SuLg1v+jL61m2yVo4=
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
@ -30,18 +43,55 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs=
golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20180926015637-991ec62608f3/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 h1:FNSSV4jv1PrPsiM2iKGpqLPPgYACqh9Muav7Pollk1k=
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190127143845-a42111704963 h1:2HSxAhImj2OpXsNjXSqfnv1xtqeCpDjwPB3o1DnQqKM=
golang.org/x/mobile v0.0.0-20190127143845-a42111704963/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20191025110607-73ccc5ba0426/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ=
golang.org/x/mobile v0.0.0-20191031020345-0945064e013a h1:CrJ8+QyIm2tcw/zt9Rp/vGFsey+jndL1y5EnFwzgGOg=
golang.org/x/mobile v0.0.0-20191031020345-0945064e013a/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190203050204-7ae0202eb74c h1:YeMXU0KQqExdpG959DFhAhfpY8myIsnfqj8lhNFRzzE=
golang.org/x/sys v0.0.0-20190203050204-7ae0202eb74c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190202235157-7414d4c1f71c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191026034945-b2104f82a97d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=