1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-18 05:05:23 +00:00

Rename Paperdoll to Composite (#265)

Make AnimatedEntity use Composite
This commit is contained in:
Alex Yatskov 2019-12-23 22:48:45 -08:00 committed by Tim Sarbin
parent 1b03e691b9
commit 0ee937f01b
17 changed files with 513 additions and 681 deletions

View File

@ -68,6 +68,11 @@ The engine is configured via the `config.json` file. By default, the configurati
expansion via the official Blizzard Diablo2 installers using the default file paths. If you are not on Windows, or have installed
the game in a different location, the base path may have to be adjusted.
## Roadmap
There is an in-progress [project roadmap](https://docs.google.com/document/d/156sWiuk-XBfomVxZ3MD-ijxnwM1X66KTHo2AcWIy8bE/edit?usp=sharing),
which will be updated over time with new requirements.
## Screenshots
![Main Menu](docs/MainMenuSS.png)

View File

@ -3,8 +3,11 @@ package d2asset
import (
"errors"
"image/color"
"math"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
@ -37,6 +40,7 @@ type Animation struct {
frameIndex int
directionIndex int
lastFrameTime float64
playedCount int
compositeMode ebiten.CompositeMode
colorMod color.Color
@ -46,19 +50,81 @@ type Animation struct {
playLoop bool
}
func createAnimationFromDCC(dcc *d2dcc.DCC, palette *d2datadict.PaletteRec, transparency int) (*Animation, error) {
animation := &Animation{
playLength: 1.0,
playLoop: true,
}
for directionIndex, dccDirection := range dcc.Directions {
for _, dccFrame := range dccDirection.Frames {
minX, minY := math.MaxInt32, math.MaxInt32
maxX, maxY := math.MinInt32, math.MinInt32
for _, dccFrame := range dccDirection.Frames {
minX = d2helper.MinInt(minX, dccFrame.Box.Left)
minY = d2helper.MinInt(minY, dccFrame.Box.Top)
maxX = d2helper.MaxInt(maxX, dccFrame.Box.Right())
maxY = d2helper.MaxInt(maxY, dccFrame.Box.Bottom())
}
frameWidth := maxX - minX
frameHeight := maxY - minY
pixels := make([]byte, frameWidth*frameHeight*4)
for y := 0; y < frameHeight; y++ {
for x := 0; x < frameWidth; x++ {
if paletteIndex := dccFrame.PixelData[y*frameWidth+x]; paletteIndex != 0 {
color := palette.Colors[paletteIndex]
offset := (x + y*frameWidth) * 4
pixels[offset] = color.R
pixels[offset+1] = color.G
pixels[offset+2] = color.B
pixels[offset+3] = byte(transparency)
}
}
}
image, err := ebiten.NewImage(frameWidth, frameHeight, ebiten.FilterNearest)
if err != nil {
return nil, err
}
if err := image.ReplacePixels(pixels); err != nil {
return nil, err
}
if directionIndex >= len(animation.directions) {
animation.directions = append(animation.directions, new(animationDirection))
}
direction := animation.directions[directionIndex]
direction.frames = append(direction.frames, &animationFrame{
width: int(dccFrame.Width),
height: int(dccFrame.Height),
offsetX: minX,
offsetY: minY,
image: image,
})
}
}
return animation, nil
}
func createAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) {
animation := &Animation{
playLength: 1.0,
playLoop: true,
}
for frameIndex, frame := range dc6.Frames {
image, err := ebiten.NewImage(int(frame.Width), int(frame.Height), ebiten.FilterNearest)
for frameIndex, dc6Frame := range dc6.Frames {
image, err := ebiten.NewImage(int(dc6Frame.Width), int(dc6Frame.Height), ebiten.FilterNearest)
if err != nil {
return nil, err
}
if err := image.ReplacePixels(frame.ColorData()); err != nil {
if err := image.ReplacePixels(dc6Frame.ColorData()); err != nil {
return nil, err
}
@ -69,10 +135,10 @@ func createAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) {
direction := animation.directions[directionIndex]
direction.frames = append(direction.frames, &animationFrame{
width: int(frame.Width),
height: int(frame.Height),
offsetX: int(frame.OffsetX),
offsetY: int(frame.OffsetY),
width: int(dc6Frame.Width),
height: int(dc6Frame.Height),
offsetX: int(dc6Frame.OffsetX),
offsetY: int(dc6Frame.OffsetY),
image: image,
})
}
@ -101,6 +167,7 @@ func (a *Animation) Advance(elapsed float64) error {
case playModeForward:
a.frameIndex++
if a.frameIndex >= frameCount {
a.playedCount++
if a.playLoop {
a.frameIndex = 0
} else {
@ -111,6 +178,7 @@ func (a *Animation) Advance(elapsed float64) error {
case playModeBackward:
a.frameIndex--
if a.frameIndex < 0 {
a.playedCount++
if a.playLoop {
a.frameIndex = frameCount - 1
} else {
@ -233,6 +301,10 @@ func (a *Animation) SetPlayLoop(loop bool) {
a.playLoop = true
}
func (a *Animation) SetPlaySpeed(playSpeed float64) {
a.SetPlayLength(playSpeed * float64(a.GetFrameCount()))
}
func (a *Animation) SetPlayLength(playLength float64) {
a.playLength = playLength
a.lastFrameTime = 0
@ -246,6 +318,14 @@ func (a *Animation) SetColorMod(color color.Color) {
a.colorMod = color
}
func (a *Animation) GetPlayedCount() int {
return a.playedCount
}
func (a *Animation) ResetPlayedCount() {
a.playedCount = 0
}
func (a *Animation) SetBlend(blend bool) {
if blend {
a.compositeMode = ebiten.CompositeModeLighter

View File

@ -1,5 +1,12 @@
package d2asset
import (
"errors"
"fmt"
"path/filepath"
"strings"
)
type animationManager struct {
cache *cache
}
@ -8,22 +15,47 @@ func createAnimationManager() *animationManager {
return &animationManager{cache: createCache(AnimationBudget)}
}
func (sm *animationManager) loadAnimation(animationPath, palettePath string) (*Animation, error) {
cachePath := animationPath + palettePath
func (sm *animationManager) loadAnimation(animationPath, palettePath string, transparency int) (*Animation, error) {
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, transparency)
if animation, found := sm.cache.retrieve(cachePath); found {
return animation.(*Animation).clone(), nil
}
dc6, err := loadDC6(animationPath, palettePath)
if err != nil {
var animation *Animation
switch strings.ToLower(filepath.Ext(animationPath)) {
case ".dc6":
dc6, err := loadDC6(animationPath, palettePath)
if err != nil {
return nil, err
}
animation, err = createAnimationFromDC6(dc6)
if err != nil {
return nil, err
}
case ".dcc":
dcc, err := loadDCC(animationPath)
if err != nil {
return nil, err
}
palette, err := loadPalette(palettePath)
if err != nil {
return nil, err
}
animation, err = createAnimationFromDCC(dcc, palette, transparency)
if err != nil {
return nil, err
}
default:
return nil, errors.New("unknown animation format")
}
if err := sm.cache.insert(cachePath, animation.clone(), 1); err != nil {
return nil, err
}
animation, err := createAnimationFromDC6(dc6)
if err != nil {
return nil, err
}
sm.cache.insert(cachePath, animation.clone(), 1)
return animation, err
return animation, nil
}

View File

@ -18,7 +18,6 @@ const (
// In counts
PaletteBudget = 64
PaperdollBudget = 64
AnimationBudget = 64
)
@ -31,7 +30,6 @@ type assetManager struct {
archiveManager *archiveManager
fileManager *fileManager
paletteManager *paletteManager
paperdollManager *paperdollManager
animationManager *animationManager
}
@ -46,7 +44,6 @@ func Initialize(config *d2corecommon.Configuration) error {
archiveManager = createArchiveManager(config)
fileManager = createFileManager(config, archiveManager)
paletteManager = createPaletteManager()
paperdollManager = createPaperdollManager()
animationManager = createAnimationManager()
)
@ -54,7 +51,6 @@ func Initialize(config *d2corecommon.Configuration) error {
archiveManager,
fileManager,
paletteManager,
paperdollManager,
animationManager,
}
@ -78,29 +74,19 @@ func LoadFile(filePath string) ([]byte, error) {
}
func LoadAnimation(animationPath, palettePath string) (*Animation, error) {
return LoadAnimationWithTransparency(animationPath, palettePath, 255)
}
func LoadAnimationWithTransparency(animationPath, palettePath string, transparency int) (*Animation, error) {
if singleton == nil {
return nil, ErrNoInit
}
return singleton.animationManager.loadAnimation(animationPath, palettePath)
return singleton.animationManager.loadAnimation(animationPath, palettePath, transparency)
}
func LoadPaperdoll(object *d2datadict.ObjectLookupRecord, palettePath string) (*Paperdoll, error) {
if singleton == nil {
return nil, ErrNoInit
}
return singleton.paperdollManager.loadPaperdoll(object, palettePath)
}
// TODO: remove transitional usage pattern
func MustLoadFile(filePath string) []byte {
data, err := LoadFile(filePath)
if err != nil {
return []byte{}
}
return data
func LoadComposite(object *d2datadict.ObjectLookupRecord, palettePath string) (*Composite, error) {
return createComposite(object, palettePath), nil
}
func loadPalette(palettePath string) (*d2datadict.PaletteRec, error) {
@ -130,7 +116,7 @@ func loadDC6(dc6Path, palettePath string) (*d2dc6.DC6File, error) {
return &dc6, nil
}
func LoadDCC(dccPath string) (*d2dcc.DCC, error) {
func loadDCC(dccPath string) (*d2dcc.DCC, error) {
dccData, err := LoadFile(dccPath)
if err != nil {
return nil, err
@ -139,7 +125,7 @@ func LoadDCC(dccPath string) (*d2dcc.DCC, error) {
return d2dcc.LoadDCC(dccData)
}
func LoadCOF(cofPath string) (*d2cof.COF, error) {
func loadCOF(cofPath string) (*d2cof.COF, error) {
cofData, err := LoadFile(cofPath)
if err != nil {
return nil, err

259
d2asset/composite.go Normal file
View File

@ -0,0 +1,259 @@
package d2asset
import (
"errors"
"fmt"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/hajimehoshi/ebiten"
)
type Composite struct {
object *d2datadict.ObjectLookupRecord
palettePath string
mode *compositeMode
}
func createComposite(object *d2datadict.ObjectLookupRecord, palettePath string) *Composite {
return &Composite{object: object, palettePath: palettePath}
}
func (c *Composite) Advance(elapsed float64) error {
if c.mode == nil {
return nil
}
c.mode.lastFrameTime += elapsed
framesToAdd := int(c.mode.lastFrameTime / c.mode.animationSpeed)
c.mode.lastFrameTime -= float64(framesToAdd) * c.mode.animationSpeed
c.mode.frameIndex += framesToAdd
c.mode.playedCount += c.mode.frameIndex / c.mode.frameCount
c.mode.frameIndex %= c.mode.frameCount
for _, layer := range c.mode.layers {
if layer != nil {
if err := layer.Advance(elapsed); err != nil {
return err
}
}
}
return nil
}
func (c *Composite) Render(target *ebiten.Image, offsetX, offsetY int) error {
if c.mode == nil {
return nil
}
for _, layerIndex := range c.mode.drawOrder[c.mode.frameIndex] {
layer := c.mode.layers[layerIndex]
if layer != nil {
if err := layer.Render(target, offsetX, offsetY); err != nil {
return err
}
}
}
return nil
}
func (c *Composite) SetMode(animationMode, weaponClass string, direction int) error {
if c.mode != nil && c.mode.animationMode == animationMode && c.mode.weaponClass == weaponClass && c.mode.direction == direction {
return nil
}
mode, err := c.createMode(animationMode, weaponClass, direction)
if err != nil {
return err
}
c.mode = mode
return nil
}
func (c *Composite) GetDirectionCount() int {
if c.mode == nil {
return 0
}
return c.mode.directionCount
}
func (c *Composite) GetPlayedCount() int {
if c.mode == nil {
return 0
}
return c.mode.playedCount
}
func (c *Composite) ResetPlayedCount() {
if c.mode != nil {
c.mode.playedCount = 0
}
}
type compositeMode struct {
animationMode string
weaponClass string
direction int
directionCount int
playedCount int
layers []*Animation
drawOrder [][]d2enum.CompositeType
frameCount int
frameIndex int
animationSpeed float64
lastFrameTime float64
}
func (c *Composite) createMode(animationMode, weaponClass string, direction int) (*compositeMode, error) {
cof, err := loadCOF(fmt.Sprintf("%s/%s/COF/%s%s%s.COF", c.object.Base, c.object.Token, c.object.Token, animationMode, weaponClass))
if err != nil {
return nil, err
}
if direction >= cof.NumberOfDirections {
return nil, errors.New("invalid direction")
}
animationKey := strings.ToLower(c.object.Token + animationMode + weaponClass)
animationData := d2data.AnimationData[animationKey]
if len(animationData) == 0 {
return nil, errors.New("could not find animation data")
}
mode := &compositeMode{
animationMode: animationMode,
weaponClass: weaponClass,
direction: direction,
directionCount: cof.NumberOfDirections,
layers: make([]*Animation, d2enum.CompositeTypeMax),
frameCount: animationData[0].FramesPerDirection,
animationSpeed: 1.0 / ((float64(animationData[0].AnimationSpeed) * 25.0) / 256.0),
}
mode.drawOrder = make([][]d2enum.CompositeType, mode.frameCount)
for frame := 0; frame < mode.frameCount; frame++ {
mode.drawOrder[frame] = cof.Priority[direction][frame]
}
var layerDirection int
switch cof.NumberOfDirections {
case 4:
layerDirection = d2dcc.CofToDir4[mode.direction]
case 8:
layerDirection = d2dcc.CofToDir8[mode.direction]
case 16:
layerDirection = d2dcc.CofToDir16[mode.direction]
case 32:
layerDirection = d2dcc.CofToDir32[mode.direction]
}
for _, cofLayer := range cof.CofLayers {
var layerKey, layerValue string
switch cofLayer.Type {
case d2enum.CompositeTypeHead:
layerKey = "HD"
layerValue = c.object.HD
case d2enum.CompositeTypeTorso:
layerKey = "TR"
layerValue = c.object.TR
case d2enum.CompositeTypeLegs:
layerKey = "LG"
layerValue = c.object.LG
case d2enum.CompositeTypeRightArm:
layerKey = "RA"
layerValue = c.object.RA
case d2enum.CompositeTypeLeftArm:
layerKey = "LA"
layerValue = c.object.LA
case d2enum.CompositeTypeRightHand:
layerKey = "RH"
layerValue = c.object.RH
case d2enum.CompositeTypeLeftHand:
layerKey = "LH"
layerValue = c.object.LH
case d2enum.CompositeTypeShield:
layerKey = "SH"
layerValue = c.object.SH
case d2enum.CompositeTypeSpecial1:
layerKey = "S1"
layerValue = c.object.S1
case d2enum.CompositeTypeSpecial2:
layerKey = "S2"
layerValue = c.object.S2
case d2enum.CompositeTypeSpecial3:
layerKey = "S3"
layerValue = c.object.S3
case d2enum.CompositeTypeSpecial4:
layerKey = "S4"
layerValue = c.object.S4
case d2enum.CompositeTypeSpecial5:
layerKey = "S5"
layerValue = c.object.S5
case d2enum.CompositeTypeSpecial6:
layerKey = "S6"
layerValue = c.object.S6
case d2enum.CompositeTypeSpecial7:
layerKey = "S7"
layerValue = c.object.S7
case d2enum.CompositeTypeSpecial8:
layerKey = "S8"
layerValue = c.object.S8
default:
return nil, errors.New("unknown layer type")
}
blend := false
transparency := 255
if cofLayer.Transparent {
switch cofLayer.DrawEffect {
case d2enum.DrawEffectPctTransparency25:
transparency = 64
case d2enum.DrawEffectPctTransparency50:
transparency = 128
case d2enum.DrawEffectPctTransparency75:
transparency = 192
case d2enum.DrawEffectModulate:
blend = true
}
}
layer, err := loadCompositeLayer(c.object, layerKey, layerValue, animationMode, weaponClass, c.palettePath, transparency)
if err == nil {
layer.SetPlaySpeed(mode.animationSpeed)
layer.PlayForward()
layer.SetBlend(blend)
layer.SetDirection(layerDirection)
mode.layers[cofLayer.Type] = layer
}
}
return mode, nil
}
func loadCompositeLayer(object *d2datadict.ObjectLookupRecord, layerKey, layerValue, animationMode, weaponClass, palettePath string, transparency int) (*Animation, error) {
animationPaths := []string{
fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", object.Base, object.Token, layerKey, object.Token, layerKey, layerValue, animationMode, weaponClass),
fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", object.Base, object.Token, layerKey, object.Token, layerKey, layerValue, animationMode, "HTH"),
fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dc6", object.Base, object.Token, layerKey, object.Token, layerKey, layerValue, animationMode, weaponClass),
fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dc6", object.Base, object.Token, layerKey, object.Token, layerKey, layerValue, animationMode, "HTH"),
}
for _, animationPath := range animationPaths {
animation, err := LoadAnimationWithTransparency(animationPath, palettePath, transparency)
if err == nil {
return animation, nil
}
}
return nil, errors.New("animation not found")
}

View File

@ -1,302 +0,0 @@
package d2asset
import (
"errors"
"fmt"
"image"
"math"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/hajimehoshi/ebiten"
)
type paperdollCacheEntry struct {
sheetImage *ebiten.Image
compositeMode ebiten.CompositeMode
width int
height int
offsetX int
offsetY int
}
type Paperdoll struct {
object *d2datadict.ObjectLookupRecord
palette *d2datadict.PaletteRec
mode *paperdollMode
}
func createPaperdoll(object *d2datadict.ObjectLookupRecord, palette *d2datadict.PaletteRec) *Paperdoll {
return &Paperdoll{object: object, palette: palette}
}
func (p *Paperdoll) Render(target *ebiten.Image, offsetX, offsetY int) {
if p.mode == nil {
return
}
if p.mode.animationSpeed > 0 {
frameTime := d2helper.Now()
framesToAdd := int(math.Floor((frameTime - p.mode.lastFrameTime) / p.mode.animationSpeed))
if framesToAdd > 0 {
p.mode.lastFrameTime += p.mode.animationSpeed * float64(framesToAdd)
p.mode.currentFrame = (p.mode.currentFrame + framesToAdd) % p.mode.frameCount
}
}
for _, layerIndex := range p.mode.drawOrder[p.mode.currentFrame] {
cacheEntry := p.mode.layerCache[layerIndex]
x := float64(offsetX) + float64(p.mode.layerCache[layerIndex].offsetX)
y := float64(offsetY) + float64(p.mode.layerCache[layerIndex].offsetY)
sheetOffset := cacheEntry.width * p.mode.currentFrame
sheetRect := image.Rect(sheetOffset, 0, sheetOffset+cacheEntry.width, cacheEntry.height)
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(x, y)
opts.CompositeMode = cacheEntry.compositeMode
target.DrawImage(cacheEntry.sheetImage.SubImage(sheetRect).(*ebiten.Image), opts)
}
}
func (p *Paperdoll) SetMode(animationMode, weaponClass string, direction int) error {
mode, err := p.createMode(animationMode, weaponClass, direction)
if err != nil {
return err
}
p.mode = mode
return nil
}
type paperdollMode struct {
animationMode string
weaponClass string
direction int
layers []*d2dcc.DCC
layerCache []*paperdollCacheEntry
drawOrder [][]d2enum.CompositeType
frameCount int
animationSpeed float64
currentFrame int
lastFrameTime float64
}
func (p *Paperdoll) createMode(animationMode, weaponClass string, direction int) (*paperdollMode, error) {
mode := &paperdollMode{
animationMode: animationMode,
weaponClass: weaponClass,
direction: direction,
}
cofPath := fmt.Sprintf(
"%s/%s/COF/%s%s%s.COF",
p.object.Base,
p.object.Token,
p.object.Token,
mode.animationMode,
mode.weaponClass,
)
cof, err := LoadCOF(cofPath)
if err != nil {
return nil, err
}
if mode.direction >= cof.NumberOfDirections {
return nil, errors.New("invalid direction")
}
mode.layers = make([]*d2dcc.DCC, d2enum.CompositeTypeMax)
for _, cofLayer := range cof.CofLayers {
var layerKey, layerValue string
switch cofLayer.Type {
case d2enum.CompositeTypeHead:
layerKey = "HD"
layerValue = p.object.HD
case d2enum.CompositeTypeTorso:
layerKey = "TR"
layerValue = p.object.TR
case d2enum.CompositeTypeLegs:
layerKey = "LG"
layerValue = p.object.LG
case d2enum.CompositeTypeRightArm:
layerKey = "RA"
layerValue = p.object.RA
case d2enum.CompositeTypeLeftArm:
layerKey = "LA"
layerValue = p.object.LA
case d2enum.CompositeTypeRightHand:
layerKey = "RH"
layerValue = p.object.RH
case d2enum.CompositeTypeLeftHand:
layerKey = "LH"
layerValue = p.object.LH
case d2enum.CompositeTypeShield:
layerKey = "SH"
layerValue = p.object.SH
case d2enum.CompositeTypeSpecial1:
layerKey = "S1"
layerValue = p.object.S1
case d2enum.CompositeTypeSpecial2:
layerKey = "S2"
layerValue = p.object.S2
case d2enum.CompositeTypeSpecial3:
layerKey = "S3"
layerValue = p.object.S3
case d2enum.CompositeTypeSpecial4:
layerKey = "S4"
layerValue = p.object.S4
case d2enum.CompositeTypeSpecial5:
layerKey = "S5"
layerValue = p.object.S5
case d2enum.CompositeTypeSpecial6:
layerKey = "S6"
layerValue = p.object.S6
case d2enum.CompositeTypeSpecial7:
layerKey = "S7"
layerValue = p.object.S7
case d2enum.CompositeTypeSpecial8:
layerKey = "S8"
layerValue = p.object.S8
default:
return nil, errors.New("unknown layer type")
}
layerPath := fmt.Sprintf(
"%s/%s/%s/%s%s%s%s%s.dcc",
p.object.Base,
p.object.Token,
layerKey,
p.object.Token,
layerKey,
layerValue,
mode.animationMode,
mode.weaponClass,
)
dcc, err := LoadDCC(layerPath)
if err != nil {
return nil, err
}
mode.layers[cofLayer.Type] = dcc
}
animationKey := strings.ToLower(p.object.Token + mode.animationMode + mode.weaponClass)
animationData := d2data.AnimationData[animationKey]
if len(animationData) == 0 {
return nil, errors.New("could not find animation data")
}
mode.animationSpeed = 1.0 / ((float64(animationData[0].AnimationSpeed) * 25.0) / 256.0)
mode.lastFrameTime = d2helper.Now()
mode.frameCount = animationData[0].FramesPerDirection
var dccDirection int
switch cof.NumberOfDirections {
case 4:
dccDirection = d2dcc.CofToDir4[mode.direction]
case 8:
dccDirection = d2dcc.CofToDir8[mode.direction]
case 16:
dccDirection = d2dcc.CofToDir16[mode.direction]
case 32:
dccDirection = d2dcc.CofToDir32[mode.direction]
}
mode.drawOrder = make([][]d2enum.CompositeType, mode.frameCount)
for frame := 0; frame < mode.frameCount; frame++ {
mode.drawOrder[frame] = cof.Priority[direction][frame]
}
mode.layerCache = make([]*paperdollCacheEntry, d2enum.CompositeTypeMax)
for _, cofLayer := range cof.CofLayers {
layer := mode.layers[cofLayer.Type]
minX, minY := math.MaxInt32, math.MaxInt32
maxX, maxY := math.MinInt32, math.MinInt32
for _, frame := range layer.Directions[dccDirection].Frames {
minX = d2helper.MinInt(minX, frame.Box.Left)
minY = d2helper.MinInt(minY, frame.Box.Top)
maxX = d2helper.MaxInt(maxX, frame.Box.Right())
maxY = d2helper.MaxInt(maxY, frame.Box.Bottom())
}
cacheEntry := &paperdollCacheEntry{
offsetX: minX,
offsetY: minY,
width: maxX - minX,
height: maxY - minY,
}
if cacheEntry.width <= 0 || cacheEntry.height <= 0 {
return nil, errors.New("invalid animation size")
}
var transparency int
if cofLayer.Transparent {
switch cofLayer.DrawEffect {
case d2enum.DrawEffectPctTransparency25:
transparency = 64
case d2enum.DrawEffectPctTransparency50:
transparency = 128
case d2enum.DrawEffectPctTransparency75:
transparency = 192
case d2enum.DrawEffectModulate:
cacheEntry.compositeMode = ebiten.CompositeModeLighter
default:
transparency = 255
}
}
pixels := make([]byte, mode.frameCount*cacheEntry.width*cacheEntry.height*4)
for i := 0; i < mode.frameCount; i++ {
direction := layer.Directions[dccDirection]
if i >= len(direction.Frames) {
return nil, errors.New("invalid animation index")
}
sheetOffset := cacheEntry.width * i
sheetWidth := cacheEntry.height * mode.frameCount
frame := direction.Frames[i]
for y := 0; y < direction.Box.Height; y++ {
for x := 0; x < direction.Box.Width; x++ {
if paletteIndex := frame.PixelData[x+(y*direction.Box.Width)]; paletteIndex != 0 {
color := p.palette.Colors[paletteIndex]
frameX := (x + direction.Box.Left) - minX
frameY := (y + direction.Box.Top) - minY
offset := (sheetOffset + frameX + (frameY * sheetWidth)) * 4
pixels[offset] = color.R
pixels[offset+1] = color.G
pixels[offset+2] = color.B
pixels[offset+3] = byte(transparency)
}
}
}
}
cacheEntry.sheetImage, err = ebiten.NewImage(cacheEntry.width*mode.frameCount, cacheEntry.height, ebiten.FilterNearest)
if err != nil {
return nil, err
}
if err := cacheEntry.sheetImage.ReplacePixels(pixels); err != nil {
return nil, err
}
mode.layerCache[cofLayer.Type] = cacheEntry
}
return mode, nil
}

View File

@ -1,22 +0,0 @@
package d2asset
import (
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
)
type paperdollManager struct {
cache *cache
}
func createPaperdollManager() *paperdollManager {
return &paperdollManager{cache: createCache(PaperdollBudget)}
}
func (pm *paperdollManager) loadPaperdoll(object *d2datadict.ObjectLookupRecord, palettePath string) (*Paperdoll, error) {
palette, err := loadPalette(palettePath)
if err != nil {
return nil, err
}
return createPaperdoll(object, palette), nil
}

View File

@ -45,7 +45,10 @@ func (v *Manager) PlayBGM(song string) {
log.Panic(err)
}
}
audioData := d2asset.MustLoadFile(song)
audioData, err := d2asset.LoadFile(song)
if err != nil {
panic(err)
}
d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData))
if err != nil {
log.Fatal(err)

View File

@ -24,7 +24,12 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *Soun
} else {
soundFile = sfx
}
audioData := d2asset.MustLoadFile(soundFile)
audioData, err := d2asset.LoadFile(soundFile)
if err != nil {
panic(err)
}
d, err := wav.Decode(context, audio.BytesReadSeekCloser(audioData))
if err != nil {
log.Fatal(err)

View File

@ -21,7 +21,10 @@ func CreateBlizzardIntro(sceneProvider d2coreinterface.SceneProvider) *BlizzardI
func (v *BlizzardIntro) Load() []func() {
return []func(){
func() {
videoBytes := d2asset.MustLoadFile("/data/local/video/BlizNorth640x480.bik")
videoBytes, err := d2asset.LoadFile("/data/local/video/BlizNorth640x480.bik")
if err != nil {
panic(err)
}
v.videoDecoder = d2video.CreateBinkDecoder(videoBytes)
},
}

View File

@ -87,8 +87,12 @@ func (v *Credits) Load() []func() {
v.uiManager.AddWidget(&v.exitButton)
},
func() {
fileData, _ := dh.Utf16BytesToString(d2asset.MustLoadFile(d2resource.CreditsText)[2:])
v.creditsText = strings.Split(fileData, "\r\n")
fileData, err := d2asset.LoadFile(d2resource.CreditsText)
if err != nil {
panic(err)
}
creditData, _ := dh.Utf16BytesToString(fileData[2:])
v.creditsText = strings.Split(creditData, "\r\n")
for i := range v.creditsText {
v.creditsText[i] = strings.Trim(v.creditsText[i], " ")
}

View File

@ -2,41 +2,42 @@ package d2core
import (
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/hajimehoshi/ebiten"
)
type Hero struct {
AnimatedEntity d2render.AnimatedEntity
AnimatedEntity *d2render.AnimatedEntity
Equipment CharacterEquipment
mode d2enum.AnimationMode
direction int
}
func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment CharacterEquipment) *Hero {
result := &Hero{
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, &d2datadict.ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: heroType.GetToken(),
Class: equipment.RightHand.GetWeaponClass(),
SH: equipment.Shield.GetItemCode(),
// TODO: Offhand class?
HD: equipment.Head.GetArmorClass(),
TR: equipment.Torso.GetArmorClass(),
LG: equipment.Legs.GetArmorClass(),
RA: equipment.RightArm.GetArmorClass(),
LA: equipment.LeftArm.GetArmorClass(),
RH: equipment.RightHand.GetItemCode(),
LH: equipment.LeftHand.GetItemCode(),
},
d2enum.Units,
),
Equipment: equipment,
mode: d2enum.AnimationModePlayerTownNeutral,
direction: direction,
object := &d2datadict.ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerNeutral.String(),
Base: "/data/global/chars",
Token: heroType.GetToken(),
Class: equipment.RightHand.GetWeaponClass(),
SH: equipment.Shield.GetItemCode(),
// TODO: Offhand class?
HD: equipment.Head.GetArmorClass(),
TR: equipment.Torso.GetArmorClass(),
LG: equipment.Legs.GetArmorClass(),
RA: equipment.RightArm.GetArmorClass(),
LA: equipment.LeftArm.GetArmorClass(),
RH: equipment.RightHand.GetItemCode(),
LH: equipment.LeftHand.GetItemCode(),
}
entity, err := d2render.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits)
if err != nil {
panic(err)
}
result := &Hero{AnimatedEntity: entity, Equipment: equipment, mode: d2enum.AnimationModePlayerTownNeutral, direction: direction}
result.AnimatedEntity.SetMode(result.mode.String(), equipment.RightHand.GetWeaponClass(), direction)
return result
}
@ -47,6 +48,8 @@ func (v *Hero) Advance(tickTime float64) {
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
v.AnimatedEntity.Step(tickTime)
}
v.AnimatedEntity.Advance(tickTime)
}
func (v *Hero) Render(target *ebiten.Image, offsetX, offsetY int) {

View File

@ -2,24 +2,26 @@ package d2core
import (
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/hajimehoshi/ebiten"
)
type NPC struct {
AnimatedEntity d2render.AnimatedEntity
AnimatedEntity *d2render.AnimatedEntity
HasPaths bool
Paths []d2common.Path
path int
}
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, direction int) *NPC {
result := &NPC{
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, d2enum.Units),
HasPaths: false,
entity, err := d2render.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits)
if err != nil {
panic(err)
}
result := &NPC{AnimatedEntity: entity, HasPaths: false}
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
return result
}
@ -68,4 +70,6 @@ func (v *NPC) Advance(tickTime float64) {
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
v.AnimatedEntity.Step(tickTime)
}
v.AnimatedEntity.Advance(tickTime)
}

View File

@ -1,326 +1,83 @@
package d2render
import (
"errors"
"fmt"
"image"
"log"
"math"
"math/rand"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2cof"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/hajimehoshi/ebiten"
)
var DccLayerNames = []string{"HD", "TR", "LG", "RA", "LA", "RH", "LH", "SH", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"}
type LayerCacheEntry struct {
frameSheet *ebiten.Image
frameWidth int
frameHeight int
compositeMode ebiten.CompositeMode
offsetX, offsetY int32
}
// AnimatedEntity represents an entity on the map that can be animated
type AnimatedEntity struct {
LocationX float64
LocationY float64
TileX, TileY int // Coordinates of the tile the unit is within
subcellX, subcellY float64 // Subcell coordinates within the current tile
dccLayers map[string]*d2dcc.DCC
Cof *d2cof.COF
palette d2enum.PaletteType
base string
token string
animationMode string
weaponClass string
lastFrameTime float64
framesToAnimate int
animationSpeed float64
direction int
currentFrame int
offsetX, offsetY int32
object *d2datadict.ObjectLookupRecord
layerCache []LayerCacheEntry
drawOrder [][]d2enum.CompositeType
TargetX float64
TargetY float64
action int32
repetitions int32
repetitions int
composite *d2asset.Composite
}
// CreateAnimatedEntity creates an instance of AnimatedEntity
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, palette d2enum.PaletteType) AnimatedEntity {
result := AnimatedEntity{
base: object.Base,
token: object.Token,
object: object,
palette: palette,
layerCache: make([]LayerCacheEntry, d2enum.CompositeTypeMax),
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, palettePath string) (*AnimatedEntity, error) {
composite, err := d2asset.LoadComposite(object, palettePath)
if err != nil {
return nil, err
}
result.dccLayers = make(map[string]*d2dcc.DCC)
result.LocationX = float64(x)
result.LocationY = float64(y)
result.TargetX = result.LocationX
result.TargetY = result.LocationY
result.TileX = int(result.LocationX / 5)
result.TileY = int(result.LocationY / 5)
result.subcellX = 1 + math.Mod(result.LocationX, 5)
result.subcellY = 1 + math.Mod(result.LocationY, 5)
entity := &AnimatedEntity{composite: composite}
entity.LocationX = float64(x)
entity.LocationY = float64(y)
entity.TargetX = entity.LocationX
entity.TargetY = entity.LocationY
return result
entity.TileX = int(entity.LocationX / 5)
entity.TileY = int(entity.LocationY / 5)
entity.subcellX = 1 + math.Mod(entity.LocationX, 5)
entity.subcellY = 1 + math.Mod(entity.LocationY, 5)
return entity, nil
}
// SetMode changes the graphical mode of this animated entity
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int) {
cofPath := fmt.Sprintf("%s/%s/COF/%s%s%s.COF", v.base, v.token, v.token, animationMode, weaponClass)
var err error
if v.Cof, err = d2asset.LoadCOF(cofPath); err != nil {
return
}
if v.Cof.NumberOfDirections == 0 || v.Cof.NumberOfLayers == 0 || v.Cof.FramesPerDirection == 0 {
return
}
resetAnimation := v.animationMode != animationMode || v.weaponClass != weaponClass
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int) error {
v.animationMode = animationMode
v.weaponClass = weaponClass
v.direction = direction
if v.direction >= v.Cof.NumberOfDirections {
v.direction = v.Cof.NumberOfDirections - 1
}
v.dccLayers = make(map[string]*d2dcc.DCC)
for _, cofLayer := range v.Cof.CofLayers {
layerName := DccLayerNames[cofLayer.Type]
if v.dccLayers[layerName], err = v.LoadLayer(layerName); err != nil {
continue
}
}
v.updateFrameCache(resetAnimation)
}
func (v *AnimatedEntity) LoadLayer(layer string) (*d2dcc.DCC, error) {
layerName := "TR"
switch strings.ToUpper(layer) {
case "HD": // Head
layerName = v.object.HD
case "TR": // Torso
layerName = v.object.TR
case "LG": // Legs
layerName = v.object.LG
case "RA": // RightArm
layerName = v.object.RA
case "LA": // LeftArm
layerName = v.object.LA
case "RH": // RightHand
layerName = v.object.RH
case "LH": // LeftHand
layerName = v.object.LH
case "SH": // Shield
layerName = v.object.SH
case "S1": // Special1
layerName = v.object.S1
case "S2": // Special2
layerName = v.object.S2
case "S3": // Special3
layerName = v.object.S3
case "S4": // Special4
layerName = v.object.S4
case "S5": // Special5
layerName = v.object.S5
case "S6": // Special6
layerName = v.object.S6
case "S7": // Special7
layerName = v.object.S7
case "S8": // Special8
layerName = v.object.S8
}
if len(layerName) == 0 {
return nil, errors.New("invalid layer")
}
dccPath := fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, v.weaponClass)
result, err := d2asset.LoadDCC(dccPath)
err := v.composite.SetMode(animationMode, weaponClass, direction)
if err != nil {
dccPath = fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, "HTH")
result, err = d2asset.LoadDCC(dccPath)
if err != nil {
return nil, err
}
err = v.composite.SetMode(animationMode, "HTH", direction)
}
return result, nil
return err
}
// If an npc has a path to pause at each location.
// Waits for animation to end and all repetitions to be exhausted.
func (v AnimatedEntity) Wait() bool {
// currentFrame might skip the final frame if framesToAdd doesn't match up,
// bail immediately after the last repetition if that happens.
return v.repetitions < 0 || (v.repetitions == 0 && v.currentFrame >= v.framesToAnimate-1)
return v.composite.GetPlayedCount() > v.repetitions
}
// Render draws this animated entity onto the target
func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) {
if v.animationSpeed > 0 {
now := d2helper.Now()
framesToAdd := math.Floor((now - v.lastFrameTime) / v.animationSpeed)
if framesToAdd > 0 {
v.lastFrameTime += v.animationSpeed * framesToAdd
v.currentFrame += int(math.Floor(framesToAdd))
for v.currentFrame >= v.framesToAnimate {
v.currentFrame -= v.framesToAnimate
v.repetitions = d2helper.MinInt32(-1, v.repetitions-1)
}
}
}
localX := (v.subcellX - v.subcellY) * 16
localY := ((v.subcellX + v.subcellY) * 8) - 5
if v.drawOrder == nil {
return
}
for _, layerIdx := range v.drawOrder[v.currentFrame] {
if v.currentFrame < 0 || v.layerCache[layerIdx].frameSheet == nil || v.currentFrame >= v.framesToAnimate {
continue
}
opts := &ebiten.DrawImageOptions{}
layer := v.layerCache[layerIdx]
x := float64(v.offsetX) + float64(offsetX) + localX + float64(v.layerCache[layerIdx].offsetX)
y := float64(v.offsetY) + float64(offsetY) + localY + float64(v.layerCache[layerIdx].offsetY)
opts.GeoM.Translate(x, y)
opts.CompositeMode = v.layerCache[layerIdx].compositeMode
xOffset := layer.frameWidth * v.currentFrame
sheetIndex := image.Rect(xOffset, 0, xOffset+layer.frameWidth, layer.frameHeight)
if err := target.DrawImage(layer.frameSheet.SubImage(sheetIndex).(*ebiten.Image), opts); err != nil {
log.Panic(err.Error())
}
}
}
func (v *AnimatedEntity) updateFrameCache(resetAnimation bool) {
if resetAnimation {
v.currentFrame = 0
}
// TODO: This animation data madness is incorrect, yet tasty
animDataTemp := d2data.AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)]
if animDataTemp == nil {
return
}
animationData := animDataTemp[0]
v.animationSpeed = 1.0 / ((float64(animationData.AnimationSpeed) * 25.0) / 256.0)
v.framesToAnimate = animationData.FramesPerDirection
v.lastFrameTime = d2helper.Now()
v.drawOrder = make([][]d2enum.CompositeType, v.framesToAnimate)
var dccDirection int
switch v.Cof.NumberOfDirections {
case 4:
dccDirection = d2dcc.CofToDir4[v.direction]
case 8:
dccDirection = d2dcc.CofToDir8[v.direction]
case 16:
dccDirection = d2dcc.CofToDir16[v.direction]
case 32:
dccDirection = d2dcc.CofToDir32[v.direction]
default:
dccDirection = 0
}
for frame := 0; frame < v.framesToAnimate; frame++ {
v.drawOrder[frame] = v.Cof.Priority[v.direction][frame]
}
for cofLayerIdx := range v.Cof.CofLayers {
layerType := v.Cof.CofLayers[cofLayerIdx].Type
layerName := DccLayerNames[layerType]
dccLayer := v.dccLayers[layerName]
if dccLayer == nil {
continue
}
minX := int32(10000)
minY := int32(10000)
maxX := int32(-10000)
maxY := int32(-10000)
for frameIdx := range dccLayer.Directions[dccDirection].Frames {
minX = d2helper.MinInt32(minX, int32(dccLayer.Directions[dccDirection].Frames[frameIdx].Box.Left))
minY = d2helper.MinInt32(minY, int32(dccLayer.Directions[dccDirection].Frames[frameIdx].Box.Top))
maxX = d2helper.MaxInt32(maxX, int32(dccLayer.Directions[dccDirection].Frames[frameIdx].Box.Right()))
maxY = d2helper.MaxInt32(maxY, int32(dccLayer.Directions[dccDirection].Frames[frameIdx].Box.Bottom()))
}
v.layerCache[layerType].offsetX = minX
v.layerCache[layerType].offsetY = minY
actualWidth := maxX - minX
actualHeight := maxY - minY
if (actualWidth <= 0) || (actualHeight < 0) {
log.Printf("Animated entity created with an invalid size of (%d, %d)", actualWidth, actualHeight)
return
}
transparency := byte(255)
if v.Cof.CofLayers[cofLayerIdx].Transparent {
switch v.Cof.CofLayers[cofLayerIdx].DrawEffect {
//Lets pick whatever we have that's closest.
case d2enum.DrawEffectPctTransparency25:
transparency = byte(64)
case d2enum.DrawEffectPctTransparency50:
transparency = byte(128)
case d2enum.DrawEffectPctTransparency75:
transparency = byte(192)
case d2enum.DrawEffectModulate:
v.layerCache[layerType].compositeMode = ebiten.CompositeModeLighter
case d2enum.DrawEffectBurn:
// Flies in tal rasha's tomb use this
case d2enum.DrawEffectNormal:
}
}
pixels := make([]byte, int32(v.framesToAnimate)*(actualWidth*actualHeight*4))
for animationIdx := 0; animationIdx < v.framesToAnimate; animationIdx++ {
if animationIdx >= len(dccLayer.Directions[dccDirection].Frames) {
log.Printf("Invalid animation index of %d for animated entity", animationIdx)
continue
}
sheetOffset := int(actualWidth) * animationIdx
combinedWidth := int(actualWidth) * v.framesToAnimate
frame := dccLayer.Directions[dccDirection].Frames[animationIdx]
for y := 0; y < dccLayer.Directions[dccDirection].Box.Height; y++ {
for x := 0; x < dccLayer.Directions[dccDirection].Box.Width; x++ {
paletteIndex := frame.PixelData[x+(y*dccLayer.Directions[dccDirection].Box.Width)]
if paletteIndex == 0 {
continue
}
color := d2datadict.Palettes[v.palette].Colors[paletteIndex]
actualX := (x + dccLayer.Directions[dccDirection].Box.Left) - int(minX)
actualY := (y + dccLayer.Directions[dccDirection].Box.Top) - int(minY)
idx := (sheetOffset + actualX + ((actualY) * combinedWidth)) * 4
pixels[idx] = color.R
pixels[idx+1] = color.G
pixels[idx+2] = color.B
pixels[idx+3] = transparency
}
}
}
v.layerCache[layerType].frameSheet, _ = ebiten.NewImage(int(actualWidth)*v.framesToAnimate, int(actualHeight), ebiten.FilterNearest)
_ = v.layerCache[layerType].frameSheet.ReplacePixels(pixels)
v.layerCache[layerType].frameWidth = int(actualWidth)
v.layerCache[layerType].frameHeight = int(actualHeight)
}
v.composite.Render(
target,
int(v.offsetX)+offsetX+int(localX),
int(v.offsetY)+offsetY+int(localY),
)
}
func (v AnimatedEntity) GetDirection() int {
@ -365,8 +122,7 @@ func (v *AnimatedEntity) Step(tickTime float64) {
v.TileY = int(v.LocationY / 5)
if v.LocationX == v.TargetX && v.LocationY == v.TargetY {
v.repetitions = 3 + rand.Int31n(5)
v.repetitions = 3 + rand.Intn(5)
newAnimationMode := d2enum.AnimationModeObjectNeutral
// TODO: Figure out what 1-3 are for, 4 is correct.
switch v.action {
@ -381,10 +137,10 @@ func (v *AnimatedEntity) Step(tickTime float64) {
v.repetitions = 0
}
v.composite.ResetPlayedCount()
if v.animationMode != newAnimationMode.String() {
v.SetMode(newAnimationMode.String(), v.weaponClass, v.direction)
}
}
}
@ -405,7 +161,7 @@ func (v *AnimatedEntity) SetTarget(tx, ty float64, action int32) {
newAnimationMode = d2enum.AnimationModeMonsterWalk.String()
}
newDirection := angleToDirection(float64(angle), v.Cof.NumberOfDirections)
newDirection := angleToDirection(float64(angle), v.composite.GetDirectionCount())
if newDirection != v.GetDirection() || newAnimationMode != v.animationMode {
v.SetMode(newAnimationMode, v.weaponClass, newDirection)
}
@ -428,8 +184,8 @@ func angleToDirection(angle float64, numberOfDirections int) int {
return newDirection
}
func (v *AnimatedEntity) Advance(tickTime float64) {
func (v *AnimatedEntity) Advance(elapsed float64) {
v.composite.Advance(elapsed)
}
func (v *AnimatedEntity) GetPosition() (float64, float64) {

View File

@ -1,8 +1,9 @@
package d2render
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAngleToDirection_16Directions(t *testing.T) {
@ -61,7 +62,6 @@ func TestAngleToDirection_1Direction(t *testing.T) {
}
}
func TestAngleToDirection_0Directions(t *testing.T) {
angle := 0.0
for i := 0; i < 120; i++ {

View File

@ -13,6 +13,7 @@ import (
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2ds1"
"github.com/OpenDiablo2/D2Shared/d2data/d2dt1"
@ -54,7 +55,12 @@ func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.Regio
for _, levelTypeDt1 := range region.levelType.Files {
if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" {
dt1 := d2dt1.LoadDT1(d2asset.MustLoadFile("/data/global/tiles/" + levelTypeDt1))
fileData, err := d2asset.LoadFile("/data/global/tiles/" + levelTypeDt1)
if err != nil {
panic(err)
}
dt1 := d2dt1.LoadDT1(fileData)
region.tiles = append(region.tiles, dt1.Tiles...)
}
}
@ -72,7 +78,11 @@ func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.Regio
}
region.regionPath = levelFilesToPick[levelIndex]
region.ds1 = d2ds1.LoadDS1(d2asset.MustLoadFile("/data/global/tiles/" + region.regionPath))
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,
@ -133,9 +143,12 @@ func (mr *MapRegion) loadEntities() []MapEntity {
}
case d2datadict.ObjectTypeItem:
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
entity := d2render.CreateAnimatedEntity(int32(worldX), int32(worldY), object.Lookup, d2enum.Units)
entity, err := d2render.CreateAnimatedEntity(int32(worldX), int32(worldY), object.Lookup, d2resource.PaletteUnits)
if err != nil {
panic(err)
}
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
entities = append(entities, &entity)
entities = append(entities, entity)
}
}
}

View File

@ -51,7 +51,10 @@ func CreateFont(font string, palettePath string) *Font {
// bug: performance issue when using CJK fonts, because ten thousand frames will be rendered PER font
result.fontSprite, _ = d2render.LoadSprite(font+".dc6", palettePath)
woo := "Woo!\x01"
fontData := d2asset.MustLoadFile(font + ".tbl")
fontData, err := d2asset.LoadFile(font + ".tbl")
if err != nil {
panic(err)
}
if string(fontData[0:5]) != woo {
panic("No woo :(")
}