Merge pull request #1078 from gucio321/anim-data-encoder

animation data engine + encoder
This commit is contained in:
gravestench 2021-02-26 12:09:08 -08:00 committed by GitHub
commit fa4276156c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 192 additions and 135 deletions

View File

@ -5,7 +5,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/golang:1.15.8
- image: circleci/golang:1.15.6
working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}
steps:
- checkout

View File

@ -6,7 +6,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2animdata"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
@ -150,12 +150,12 @@ func (a *App) initAnimationData(path string) error {
a.Debugf(fmtLoadAnimData, path)
animData, err := d2data.LoadAnimationData(animDataBytes)
animData, err := d2animdata.Load(animDataBytes)
if err != nil {
a.Error(err.Error())
}
a.Infof("Loaded %d animation data records", len(animData))
a.Infof("Loaded %d animation data records", animData.GetRecordsCount())
a.asset.Records.Animation.Data = animData

View File

@ -1,83 +0,0 @@
package d2data
import (
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
const (
numCofNameBytes = 8
numFlagBytes = 144
)
// AnimationDataRecord represents a single entry in the animation data dictionary file
type AnimationDataRecord struct {
// COFName is the name of the COF file used for this animation
COFName string
// FramesPerDirection specifies how many frames are in each direction
FramesPerDirection int
// AnimationSpeed represents a value of X where the rate is a ration of (x/255) at 25FPS
AnimationSpeed int
// Flags are used in keyframe triggers
Flags []byte
}
// AnimationData represents all of the animation data records, mapped by the COF index
type AnimationData map[string][]*AnimationDataRecord
// LoadAnimationData loads the animation data table into the global AnimationData dictionary
func LoadAnimationData(rawData []byte) (AnimationData, error) {
animdata := make(AnimationData)
streamReader := d2datautils.CreateStreamReader(rawData)
for !streamReader.EOF() {
var dataCount int
b, err := streamReader.ReadInt32()
if err != nil {
return nil, err
}
dataCount = int(b)
for i := 0; i < dataCount; i++ {
cofNameBytes, err := streamReader.ReadBytes(numCofNameBytes)
if err != nil {
return nil, err
}
fpd, err := streamReader.ReadInt32()
if err != nil {
return nil, err
}
animSpeed, err := streamReader.ReadInt32()
if err != nil {
return nil, err
}
data := &AnimationDataRecord{
COFName: strings.ReplaceAll(string(cofNameBytes), string(byte(0)), ""),
FramesPerDirection: int(fpd),
AnimationSpeed: int(animSpeed),
}
flagBytes, err := streamReader.ReadBytes(numFlagBytes)
if err != nil {
return nil, err
}
data.Flags = flagBytes
cofIndex := strings.ToLower(data.COFName)
if _, found := animdata[cofIndex]; !found {
animdata[cofIndex] = make([]*AnimationDataRecord, 0)
}
animdata[cofIndex] = append(animdata[cofIndex], data)
}
}
return animdata, nil
}

View File

@ -56,6 +56,11 @@ func (ad *AnimationData) GetRecords(name string) []*AnimationDataRecord {
return ad.entries[name]
}
// GetRecordsCount returns number of animation data records
func (ad *AnimationData) GetRecordsCount() int {
return len(ad.entries)
}
// Load loads the data into an AnimationData struct
//nolint:gocognit,funlen // can't reduce
func Load(data []byte) (*AnimationData, error) {
@ -143,8 +148,92 @@ func Load(data []byte) (*AnimationData, error) {
}
if reader.Position() != uint64(len(data)) {
return nil, errors.New("unable to parse animation data")
return nil, fmt.Errorf("unable to parse animation data: %d != %d", reader.Position(), len(data))
}
return animdata, nil
}
// Marshal encodes animation data back into byte slice
// basing on AnimationData.records
func (ad *AnimationData) Marshal() []byte {
sw := d2datautils.CreateStreamWriter()
// keys - all entries in animationData
keys := make([]string, len(ad.entries))
// we must manually add index
idx := 0
for i := range ad.entries {
keys[idx] = i
idx++
}
// name terminates current name
name := 0
// recordIdx determinates current record index
recordIdx := 0
// numberOfEntries is a number of entries in all map indexes
var numberOfEntries int = 0
for i := 0; i < len(keys); i++ {
numberOfEntries += len(ad.entries[keys[i]])
}
for idx := 0; idx < numBlocks; idx++ {
// number of records (max is maxRecordsPerObject)
l := 0
switch {
// first condition: end up with all this and push 0 to dhe end
case numberOfEntries == 0:
sw.PushUint32(0)
continue
case numberOfEntries < maxRecordsPerBlock:
// second condition - if number of entries left is smaller than
// maxRecordsPerBlock, push...
l = numberOfEntries
sw.PushUint32(uint32(l))
default:
// else use maxRecordsPerBlock
l = maxRecordsPerBlock
sw.PushUint32(maxRecordsPerBlock)
}
for currentRecordIdx := 0; currentRecordIdx < l; currentRecordIdx++ {
numberOfEntries--
if recordIdx == len(ad.entries[keys[name]]) {
recordIdx = 0
name++
}
animationRecord := ad.entries[keys[name]][recordIdx]
recordIdx++
name := animationRecord.name
missingZeroBytes := byteCountName - len(name)
sw.PushBytes([]byte(name)...)
for i := 0; i < missingZeroBytes; i++ {
sw.PushBytes(0)
}
sw.PushUint32(animationRecord.framesPerDirection)
sw.PushUint16(animationRecord.speed)
for i := 0; i < byteCountSpeedPadding; i++ {
sw.PushBytes(0)
}
for event := 0; event < numEvents; event++ {
sw.PushBytes(byte(animationRecord.events[event]))
}
}
}
return sw.GetBytes()
}

View File

@ -154,3 +154,58 @@ func TestAnimationDataRecord_FPS(t *testing.T) {
t.Error("incorrect fps")
}
}
func TestAnimationDataRecord_Marshal(t *testing.T) {
file, fileErr := os.Open("testdata/AnimData.d2")
if fileErr != nil {
t.Error("cannot open test data file")
return
}
data := make([]byte, 0)
buf := make([]byte, 16)
for {
numRead, err := file.Read(buf)
data = append(data, buf[:numRead]...)
if err != nil {
break
}
}
ad, err := Load(data)
if err != nil {
t.Error(err)
}
newData := ad.Marshal()
newAd, err := Load(newData)
if err != nil {
t.Error(err)
}
keys1 := make([]string, 0)
for i := range ad.entries {
keys1 = append(keys1, i)
}
keys2 := make([]string, 0)
for i := range newAd.entries {
keys2 = append(keys2, i)
}
if len(keys1) != len(keys2) {
t.Fatalf("unexpected length of keys in first and second dict: %d, %d", len(keys1), len(keys2))
}
for key := range newAd.entries {
for n, i := range newAd.entries[key] {
if i.speed != ad.entries[key][n].speed {
t.Fatal("unexpected record set")
}
}
}
}

View File

@ -8,6 +8,16 @@ type AnimationDataRecord struct {
events map[int]AnimationEvent
}
// FramesPerDirection returns frames per direction value
func (r *AnimationDataRecord) FramesPerDirection() int {
return int(r.framesPerDirection)
}
// Speed returns animation's speed
func (r *AnimationDataRecord) Speed() int {
return int(r.speed)
}
// FPS returns the frames per second for this animation record
func (r *AnimationDataRecord) FPS() float64 {
speedf := float64(r.speed)

View File

@ -265,9 +265,9 @@ func (c *Composite) createMode(animationMode animationMode, weaponClass string)
return nil, err
}
animationKey := strings.ToLower(c.token + animationMode.String() + weaponClass)
animationKey := strings.ToUpper(c.token + animationMode.String() + weaponClass)
animationData := c.Records.Animation.Data[animationKey]
animationData := c.Records.Animation.Data.GetRecords(animationKey)
if len(animationData) == 0 {
return nil, errors.New("could not find Animation data")
}
@ -277,8 +277,8 @@ func (c *Composite) createMode(animationMode animationMode, weaponClass string)
animationMode: animationMode,
weaponClass: weaponClass,
layers: make([]d2interface.Animation, d2enum.CompositeTypeMax),
frameCount: animationData[0].FramesPerDirection,
animationSpeed: 1.0 / (float64(animationData[0].AnimationSpeed) * speedUnit), //nolint:gomnd // taking inverse
frameCount: animationData[0].FramesPerDirection(),
animationSpeed: 1.0 / (float64(animationData[0].Speed()) * speedUnit), //nolint:gomnd // taking inverse
}
for _, cofLayer := range cof.CofLayers {

View File

@ -108,8 +108,8 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT
result.mapEntity.uuid = id
result.SetSpeed(baseWalkSpeed)
result.mapEntity.directioner = result.rotate
err = composite.SetMode(d2enum.PlayerAnimationModeTownNeutral, equipment.RightHand.GetWeaponClass())
err = composite.SetMode(d2enum.PlayerAnimationModeTownNeutral, equipment.RightHand.GetWeaponClass())
if err != nil {
panic(err)
}

View File

@ -5,9 +5,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2animdata"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
@ -39,7 +38,7 @@ type RecordManager struct {
*d2util.Logger
boundLoaders map[string][]recordLoader // there can be more than one loader bound for a file
Animation struct {
Data d2data.AnimationData
Data *d2animdata.AnimationData
Token struct {
Player PlayerTypes
Composite CompositeTypes

17
go.mod
View File

@ -4,19 +4,18 @@ go 1.14
require (
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d
github.com/go-restruct/restruct v1.2.0-alpha
github.com/google/uuid v1.2.0
github.com/gravestench/akara v0.0.0-20210116041952-513d453f9ca8
github.com/hajimehoshi/ebiten/v2 v2.0.6
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/google/uuid v1.1.2
github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c
github.com/hajimehoshi/ebiten/v2 v2.0.2
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.5.0
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
github.com/stretchr/testify v1.4.0
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f // indirect
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 // indirect
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 // indirect
golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
golang.org/x/sys v0.0.0-20201028215240-c5abc1b1d397 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
)

48
go.sum
View File

@ -1,39 +1,35 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2 h1:Ac1OEHHkbAZ6EUnJahF0GKcU0FjPc/V8F1DvjhKngFE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265 h1:BcbKYUZo/TKPsiSh7LymK3p+TNAJJW3OfGO/21sBbiA=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc=
github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gravestench/akara v0.0.0-20210116041952-513d453f9ca8 h1:3MOT9gUiXETmJJ3UsHgVa4+okRSdUVW+eSvPD4C+ZnQ=
github.com/gravestench/akara v0.0.0-20210116041952-513d453f9ca8/go.mod h1:uxAqdO3YanpyVIUcFAJyMfpTHbYveWgm0MbJW1JBtMM=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c h1:WopE590cKxkcKXcOee4gPXHqtzwbarLClCaWNCdLqgI=
github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ=
github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
github.com/hajimehoshi/ebiten/v2 v2.0.6 h1:sHNymgI+q80xasP69oFyrpup6r2qCNsKxqwsGEh6PWE=
github.com/hajimehoshi/ebiten/v2 v2.0.6/go.mod h1:uS3OjMW3f2DRDMtWoIF7yMMmrMkv+fZ6pXcwR1pfA0Y=
github.com/hajimehoshi/ebiten/v2 v2.0.2 h1:t8HXO9hJfKlS9tNhht8Ov6xecag0gRl7AkfKgC9hcLE=
github.com/hajimehoshi/ebiten/v2 v2.0.2/go.mod h1:AbHP/SS226aFTex/izULVwW0D2AuGyqC4AVwilmRjOg=
github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.6.8 h1:yRb3EJQ4lAkBgZYheqmdH6Lr77RV9nSWFsK/jwWdTNY=
github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -50,10 +46,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac h1:kYPjbEN6YPYWWHI6ky1J813KzIq/8+Wg4TO4xU7A/KU=
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -65,21 +57,18 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f h1:GrkO5AtFUU9U/1f5ctbIBXtBGeSJbWwIYfIsTcFMaX4=
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4=
golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7 h1:2/QncOxxpPAdiH+E00abYw/SaQG353gltz79Nl1zrYE=
golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw=
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-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -99,11 +88,10 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201028215240-c5abc1b1d397 h1:YZ169h3kkKEXsueizzMwOT9AaiffbOa6oXSmUFJ4vxM=
golang.org/x/sys v0.0.0-20201028215240-c5abc1b1d397/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=