diff --git a/d2core/d2asset/animation.go b/d2core/d2asset/animation.go index 8af714c3..1d903ca5 100644 --- a/d2core/d2asset/animation.go +++ b/d2core/d2asset/animation.go @@ -26,6 +26,8 @@ const ( playModeBackward ) +const defaultPlayLength = 1.0 + type animationFrame struct { width int height int @@ -59,9 +61,10 @@ type Animation struct { subEndingFrame int } +// CreateAnimationFromDCC creates an animation from d2dcc.DCC and d2dat.DATPalette func CreateAnimationFromDCC(dcc *d2dcc.DCC, palette *d2dat.DATPalette, transparency int) (*Animation, error) { animation := &Animation{ - playLength: 1.0, + playLength: defaultPlayLength, playLoop: true, } @@ -80,14 +83,14 @@ func CreateAnimationFromDCC(dcc *d2dcc.DCC, palette *d2dat.DATPalette, transpare frameWidth := maxX - minX frameHeight := maxY - minY - pixels := make([]byte, frameWidth*frameHeight*4) + const bytesPerPixel = 4 + pixels := make([]byte, frameWidth*frameHeight*bytesPerPixel) for y := 0; y < frameHeight; y++ { for x := 0; x < frameWidth; x++ { - if paletteIndex := dccFrame.PixelData[y*frameWidth+x]; paletteIndex != 0 { palColor := palette.Colors[paletteIndex] - offset := (x + y*frameWidth) * 4 + offset := (x + y*frameWidth) * bytesPerPixel pixels[offset] = palColor.R pixels[offset+1] = palColor.G pixels[offset+2] = palColor.B @@ -123,9 +126,10 @@ func CreateAnimationFromDCC(dcc *d2dcc.DCC, palette *d2dat.DATPalette, transpare return animation, nil } +// CreateAnimationFromDC6 creates an Animation from d2dc6.DC6 and d2dat.DATPalette func CreateAnimationFromDC6(dc6 *d2dc6.DC6, palette *d2dat.DATPalette) (*Animation, error) { animation := &Animation{ - playLength: 1.0, + playLength: defaultPlayLength, playLoop: true, originAtBottom: true, } @@ -170,17 +174,18 @@ func CreateAnimationFromDC6(dc6 *d2dc6.DC6, palette *d2dat.DATPalette) (*Animati } } - colorData := make([]byte, dc6Frame.Width*dc6Frame.Height*4) + bytesPerPixel := 4 + colorData := make([]byte, int(dc6Frame.Width)*int(dc6Frame.Height)*bytesPerPixel) for i := 0; i < int(dc6Frame.Width*dc6Frame.Height); i++ { if indexData[i] < 1 { // TODO: Is this == -1 or < 1? continue } - colorData[i*4] = palette.Colors[indexData[i]].R - colorData[i*4+1] = palette.Colors[indexData[i]].G - colorData[i*4+2] = palette.Colors[indexData[i]].B - colorData[i*4+3] = 0xff + colorData[i*bytesPerPixel] = palette.Colors[indexData[i]].R + colorData[i*bytesPerPixel+1] = palette.Colors[indexData[i]].G + colorData[i*bytesPerPixel+2] = palette.Colors[indexData[i]].B + colorData[i*bytesPerPixel+3] = 0xff } if err := sfc.ReplacePixels(colorData); err != nil { @@ -269,9 +274,14 @@ func (a *Animation) Render(target d2interface.Surface) error { frame := direction.frames[a.frameIndex] target.PushTranslation(frame.offsetX, frame.offsetY) + defer target.Pop() + target.PushCompositeMode(a.compositeMode) + defer target.Pop() + target.PushColor(a.colorMod) - defer target.PopN(3) + defer target.Pop() + return target.Render(frame.image) } @@ -356,9 +366,11 @@ func (a *Animation) GetDirectionCount() int { // SetDirection places the animation in the direction of an animation func (a *Animation) SetDirection(directionIndex int) error { - if directionIndex >= 64 { + const smallestInvalidDirectionIndex = 64 + if directionIndex >= smallestInvalidDirectionIndex { return errors.New("invalid direction index") } + a.directionIndex = d2dcc.Dir64ToDcc(directionIndex, len(a.directions)) a.frameIndex = 0 @@ -378,6 +390,7 @@ func (a *Animation) SetCurrentFrame(frameIndex int) error { a.frameIndex = frameIndex a.lastFrameTime = 0 + return nil } @@ -414,17 +427,21 @@ func (a *Animation) SetPlaySpeed(playSpeed float64) { a.SetPlayLength(playSpeed * float64(a.GetFrameCount())) } -func (a *Animation) SetPlayLength(playLength float64) { +// SetPlayLength sets the Animation's play length in seconds +func (a *Animation) SetPlayLength(playLength float64) { // TODO refactor to use time.Duration instead of float64 a.playLength = playLength a.lastFrameTime = 0 } -func (a *Animation) SetPlayLengthMs(playLengthMs int) { - a.SetPlayLength(float64(playLengthMs) / 1000.0) +// SetPlayLengthMs sets the Animation's play length in milliseconds +func (a *Animation) SetPlayLengthMs(playLengthMs int) { // TODO remove this method + const millisecondsPerSecond = 1000.0 + a.SetPlayLength(float64(playLengthMs) / millisecondsPerSecond) } -func (a *Animation) SetColorMod(color color.Color) { - a.colorMod = color +// SetColorMod sets the Animation's color mod +func (a *Animation) SetColorMod(colorMod color.Color) { + a.colorMod = colorMod } // GetPlayedCount gets the number of times the application played @@ -437,7 +454,7 @@ func (a *Animation) ResetPlayedCount() { a.playedCount = 0 } -// SetBlend sets the animation alpha blending status +// SetBlend sets the Animation alpha blending status func (a *Animation) SetBlend(blend bool) { if blend { a.compositeMode = d2enum.CompositeModeLighter diff --git a/d2core/d2asset/animation_manager.go b/d2core/d2asset/animation_manager.go index fa9a2b3b..84b15f66 100644 --- a/d2core/d2asset/animation_manager.go +++ b/d2core/d2asset/animation_manager.go @@ -27,6 +27,7 @@ func (am *animationManager) loadAnimation(animationPath, palettePath string, tra } var animation *Animation + ext := strings.ToLower(filepath.Ext(animationPath)) switch ext { case ".dc6": diff --git a/d2core/d2asset/archive_manager.go b/d2core/d2asset/archive_manager.go index 3d8135dc..1cfe09de 100644 --- a/d2core/d2asset/archive_manager.go +++ b/d2core/d2asset/archive_manager.go @@ -17,7 +17,7 @@ type archiveEntry struct { type archiveManager struct { cache *d2common.Cache - config d2config.Configuration + config *d2config.Configuration entries []archiveEntry mutex sync.Mutex } @@ -26,7 +26,7 @@ const ( archiveBudget = 1024 * 1024 * 512 ) -func createArchiveManager(config d2config.Configuration) *archiveManager { +func createArchiveManager(config *d2config.Configuration) *archiveManager { return &archiveManager{cache: d2common.CreateCache(archiveBudget), config: config} } @@ -93,6 +93,7 @@ func (am *archiveManager) cacheArchiveEntries() error { for _, archiveName := range am.config.MpqLoadOrder { archivePath := path.Join(am.config.MpqPath, archiveName) + archive, err := am.loadArchive(archivePath) if err != nil { return err diff --git a/d2core/d2asset/asset_manager.go b/d2core/d2asset/asset_manager.go index b688707f..5a9d05da 100644 --- a/d2core/d2asset/asset_manager.go +++ b/d2core/d2asset/asset_manager.go @@ -1,18 +1,11 @@ package d2asset import ( - "errors" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc" ) -var ( - ErrWasInit = errors.New("asset system is already initialized") - ErrNotInit = errors.New("asset system is not initialized") -) - type assetManager struct { archiveManager *archiveManager fileManager *fileManager diff --git a/d2core/d2asset/composite.go b/d2core/d2asset/composite.go index 1d769e3f..7507a0f8 100644 --- a/d2core/d2asset/composite.go +++ b/d2core/d2asset/composite.go @@ -5,24 +5,26 @@ import ( "fmt" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" ) +// Composite is a composite entity animation type Composite struct { object *d2datadict.ObjectLookupRecord palettePath string mode *compositeMode } +// CreateComposite creates a Composite from a given ObjectLookupRecord and palettePath. func CreateComposite(object *d2datadict.ObjectLookupRecord, palettePath string) *Composite { return &Composite{object: object, palettePath: palettePath} } +// Advance moves the composite animation forward for a given elapsed time in nanoseconds. func (c *Composite) Advance(elapsed float64) error { if c.mode == nil { return nil @@ -46,6 +48,7 @@ func (c *Composite) Advance(elapsed float64) error { return nil } +// Render performs drawing of the Composite on the rendered d2interface.Surface. func (c *Composite) Render(target d2interface.Surface) error { if c.mode == nil { return nil @@ -63,10 +66,12 @@ func (c *Composite) Render(target d2interface.Surface) error { return nil } +// GetAnimationMode returns the animation mode the Composite should render with. func (c Composite) GetAnimationMode() string { return c.mode.animationMode } +// SetMode sets the Composite's animation mode weapon class and direction func (c *Composite) SetMode(animationMode, weaponClass string, direction int) error { if c.mode != nil && c.mode.animationMode == animationMode && c.mode.weaponClass == weaponClass && c.mode.cofDirection == direction { return nil @@ -77,11 +82,13 @@ func (c *Composite) SetMode(animationMode, weaponClass string, direction int) er return err } - c.ResetPlayedCount() + c.resetPlayedCount() c.mode = mode + return nil } +// SetSpeed sets the speed at which the Composite's animation should advance through its frames func (c *Composite) SetSpeed(speed int) { c.mode.animationSpeed = 1.0 / ((float64(speed) * 25.0) / 256.0) for layerIdx := range c.mode.layers { @@ -92,6 +99,7 @@ func (c *Composite) SetSpeed(speed int) { } } +// GetDirectionCount returns the Composites number of available animated directions func (c *Composite) GetDirectionCount() int { if c.mode == nil { return 0 @@ -100,6 +108,7 @@ func (c *Composite) GetDirectionCount() int { return c.mode.directionCount } +// GetPlayedCount returns the number of times the current animation mode has completed all its distinct frames func (c *Composite) GetPlayedCount() int { if c.mode == nil { return 0 @@ -108,7 +117,7 @@ func (c *Composite) GetPlayedCount() int { return c.mode.playedCount } -func (c *Composite) ResetPlayedCount() { +func (c *Composite) resetPlayedCount() { if c.mode != nil { c.mode.playedCount = 0 } @@ -117,7 +126,7 @@ func (c *Composite) ResetPlayedCount() { type compositeMode struct { animationMode string weaponClass string - cofDirection int + cofDirection int directionCount int playedCount int @@ -142,6 +151,7 @@ func (c *Composite) createMode(animationMode, weaponClass string, direction int) } 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") @@ -222,7 +232,9 @@ func (c *Composite) createMode(animationMode, weaponClass string, direction int) layer.SetPlaySpeed(mode.animationSpeed) layer.PlayForward() layer.SetBlend(blend) - layer.SetDirection(direction) + if err := layer.SetDirection(direction); err != nil { + return nil, err + } mode.layers[cofLayer.Type] = layer } } diff --git a/d2core/d2asset/d2asset.go b/d2core/d2asset/d2asset.go index bee0f4c7..2539e2e7 100644 --- a/d2core/d2asset/d2asset.go +++ b/d2core/d2asset/d2asset.go @@ -1,3 +1,6 @@ +/* +Package d2asset has behaviors to load and save assets from disk. +*/ package d2asset import ( @@ -8,19 +11,17 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2pl2" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2config" ) var singleton *assetManager +// Initialize creates and assigns all necessary dependencies for the assetManager top-level functions to work correctly func Initialize(term d2interface.Terminal) error { - verifyNotInit() - var ( config = d2config.Get() - archiveManager = createArchiveManager(config) - fileManager = createFileManager(config, archiveManager) + archiveManager = createArchiveManager(&config) + fileManager = createFileManager(&config, archiveManager) paletteManager = createPaletteManager() paletteTransformManager = createPaletteTransformManager() animationManager = createAnimationManager() @@ -36,7 +37,7 @@ func Initialize(term d2interface.Terminal) error { fontManager, } - term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) { + if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) { if verbose { term.OutputInfof("asset manager verbose logging enabled") } else { @@ -48,41 +49,47 @@ func Initialize(term d2interface.Terminal) error { paletteManager.cache.SetVerbose(verbose) paletteTransformManager.cache.SetVerbose(verbose) animationManager.cache.SetVerbose(verbose) - }) + }); err != nil { + return err + } - term.BindAction("assetstat", "display asset manager cache statistics", func() { - term.OutputInfof("archive cache: %f", float64(archiveManager.cache.GetWeight())/float64(archiveManager.cache.GetBudget())*100.0) - term.OutputInfof("file cache: %f", float64(fileManager.cache.GetWeight())/float64(fileManager.cache.GetBudget())*100.0) - term.OutputInfof("palette cache: %f", float64(paletteManager.cache.GetWeight())/float64(paletteManager.cache.GetBudget())*100.0) - term.OutputInfof("palette transform cache: %f", float64(paletteTransformManager.cache.GetWeight())/float64(paletteTransformManager.cache.GetBudget())*100.0) - term.OutputInfof("animation cache: %f", float64(animationManager.cache.GetWeight())/float64(animationManager.cache.GetBudget())*100.0) - term.OutputInfof("font cache: %f", float64(fontManager.cache.GetWeight())/float64(fontManager.cache.GetBudget())*100.0) - }) + if err := term.BindAction("assetstat", "display asset manager cache statistics", func() { + type cache interface { + GetWeight() int + GetBudget() int + } - term.BindAction("assetclear", "clear asset manager cache", func() { + var cacheStatistics = func(c cache) float64 { + const percent = 100.0 + return float64(c.GetWeight()) / float64(c.GetBudget()) * percent + } + + term.OutputInfof("archive cache: %f", cacheStatistics(archiveManager.cache)) + term.OutputInfof("file cache: %f", cacheStatistics(fileManager.cache)) + term.OutputInfof("palette cache: %f", cacheStatistics(paletteManager.cache)) + term.OutputInfof("palette transform cache: %f", cacheStatistics(paletteTransformManager.cache)) + term.OutputInfof("animation cache: %f", cacheStatistics(animationManager.cache)) + term.OutputInfof("font cache: %f", cacheStatistics(fontManager.cache)) + }); err != nil { + return err + } + + if err := term.BindAction("assetclear", "clear asset manager cache", func() { archiveManager.cache.Clear() fileManager.cache.Clear() paletteManager.cache.Clear() paletteTransformManager.cache.Clear() animationManager.cache.Clear() fontManager.cache.Clear() - }) + }); err != nil { + return err + } return nil } -func Shutdown() { - singleton = nil -} - -func LoadArchive(archivePath string) (*d2mpq.MPQ, error) { - verifyWasInit() - return singleton.archiveManager.loadArchive(archivePath) -} - +// LoadFileStream streams an MPQ file from a source file path func LoadFileStream(filePath string) (*d2mpq.MpqDataStream, error) { - verifyWasInit() - data, err := singleton.fileManager.loadFileStream(filePath) if err != nil { log.Printf("error loading file stream %s (%v)", filePath, err.Error()) @@ -91,9 +98,8 @@ func LoadFileStream(filePath string) (*d2mpq.MpqDataStream, error) { return data, err } +// LoadFile loads an entire file from a source file path as a []byte func LoadFile(filePath string) ([]byte, error) { - verifyWasInit() - data, err := singleton.fileManager.loadFile(filePath) if err != nil { log.Printf("error loading file %s (%v)", filePath, err.Error()) @@ -102,49 +108,32 @@ func LoadFile(filePath string) ([]byte, error) { return data, err } +// FileExists checks if a file exists on the underlying file system at the given file path. func FileExists(filePath string) (bool, error) { - verifyWasInit() return singleton.fileManager.fileExists(filePath) } +// LoadAnimation loads an animation by its resource path and its palette path func LoadAnimation(animationPath, palettePath string) (*Animation, error) { - verifyWasInit() return LoadAnimationWithTransparency(animationPath, palettePath, 255) } -func LoadPaletteTransform(pl2Path string) (*d2pl2.PL2, error) { - verifyWasInit() - return singleton.paletteTransformManager.loadPaletteTransform(pl2Path) -} - +// LoadAnimationWithTransparency loads an animation by its resource path and its palette path with a given transparency value func LoadAnimationWithTransparency(animationPath, palettePath string, transparency int) (*Animation, error) { - verifyWasInit() return singleton.animationManager.loadAnimation(animationPath, palettePath, transparency) } +// LoadComposite creates a composite object from a ObjectLookupRecord and palettePath describing it func LoadComposite(object *d2datadict.ObjectLookupRecord, palettePath string) (*Composite, error) { - verifyWasInit() return CreateComposite(object, palettePath), nil } +// LoadFont loads a font the resource files func LoadFont(tablePath, spritePath, palettePath string) (*Font, error) { - verifyWasInit() return singleton.fontManager.loadFont(tablePath, spritePath, palettePath) } +// LoadPalette loads a palette from a given palette path func LoadPalette(palettePath string) (*d2dat.DATPalette, error) { - verifyWasInit() return singleton.paletteManager.loadPalette(palettePath) } - -func verifyWasInit() { - if singleton == nil { - panic(ErrNotInit) - } -} - -func verifyNotInit() { - if singleton != nil { - panic(ErrWasInit) - } -} diff --git a/d2core/d2asset/file_manager.go b/d2core/d2asset/file_manager.go index 399e2e0c..4a4e32e4 100644 --- a/d2core/d2asset/file_manager.go +++ b/d2core/d2asset/file_manager.go @@ -16,14 +16,13 @@ const ( type fileManager struct { cache *d2common.Cache archiveManager *archiveManager - config d2config.Configuration + config *d2config.Configuration } -func createFileManager(config d2config.Configuration, archiveManager *archiveManager) *fileManager { +func createFileManager(config *d2config.Configuration, archiveManager *archiveManager) *fileManager { return &fileManager{d2common.CreateCache(fileBudget), archiveManager, config} } - func (fm *fileManager) loadFileStream(filePath string) (*d2mpq.MpqDataStream, error) { filePath = fm.fixupFilePath(filePath) diff --git a/d2core/d2asset/font.go b/d2core/d2asset/font.go index 9ce9db1e..783a1fbd 100644 --- a/d2core/d2asset/font.go +++ b/d2core/d2asset/font.go @@ -17,6 +17,7 @@ type fontGlyph struct { height int } +// Font represents a displayable font type Font struct { sheet *Animation glyphs map[rune]fontGlyph @@ -47,7 +48,7 @@ func loadFont(tablePath, spritePath, palettePath string) (*Font, error) { var glyph fontGlyph glyph.frame = int(binary.LittleEndian.Uint16(data[i+8 : i+10])) glyph.width = int(data[i+3]) - glyph.height = maxCharHeight // int(data[i+4]) + glyph.height = maxCharHeight glyphs[code] = glyph } @@ -61,11 +62,12 @@ func loadFont(tablePath, spritePath, palettePath string) (*Font, error) { return font, nil } -func (f *Font) SetColor(color color.Color) { - f.color = color +func (f *Font) SetColor(c color.Color) { + f.color = c } -func (f *Font) GetTextMetrics(text string) (int, int) { +// GetTextMetrics returns the dimensions of the Font element in pixels +func (f *Font) GetTextMetrics(text string) (width, height int) { var ( lineWidth int lineHeight int @@ -91,6 +93,7 @@ func (f *Font) GetTextMetrics(text string) (int, int) { return totalWidth, totalHeight } +// Clone creates a shallow copy of the Font func (f *Font) Clone() *Font { return &Font{ sheet: f.sheet, @@ -99,6 +102,7 @@ func (f *Font) Clone() *Font { } } +// RenderText draws a string of text in a style described by Font onto the d2interface.Surface func (f *Font) RenderText(text string, target d2interface.Surface) error { f.sheet.SetColorMod(f.color) f.sheet.SetBlend(false) @@ -112,13 +116,23 @@ func (f *Font) RenderText(text string, target d2interface.Surface) error { ) for _, c := range line { - if glyph, ok := f.glyphs[c]; ok { - f.sheet.SetCurrentFrame(glyph.frame) - f.sheet.Render(target) - lineHeight = d2common.MaxInt(lineHeight, glyph.height) - target.PushTranslation(glyph.width, 0) - lineLength++ + glyph, ok := f.glyphs[c] + if !ok { + continue } + + if err := f.sheet.SetCurrentFrame(glyph.frame); err != nil { + return err + } + + if err := f.sheet.Render(target); err != nil { + return err + } + + lineHeight = d2common.MaxInt(lineHeight, glyph.height) + lineLength++ + + target.PushTranslation(glyph.width, 0) } target.PopN(lineLength) diff --git a/d2core/d2asset/palette_manager.go b/d2core/d2asset/palette_manager.go index 0983c501..09be86ae 100644 --- a/d2core/d2asset/palette_manager.go +++ b/d2core/d2asset/palette_manager.go @@ -32,6 +32,9 @@ func (pm *paletteManager) loadPalette(palettePath string) (*d2dat.DATPalette, er return nil, err } - pm.cache.Insert(palettePath, palette, 1) + if err := pm.cache.Insert(palettePath, palette, 1); err != nil { + return nil, err + } + return palette, nil } diff --git a/d2core/d2asset/palette_transform_manager.go b/d2core/d2asset/palette_transform_manager.go index c117168a..a14e1699 100644 --- a/d2core/d2asset/palette_transform_manager.go +++ b/d2core/d2asset/palette_transform_manager.go @@ -32,6 +32,9 @@ func (pm *paletteTransformManager) loadPaletteTransform(path string) (*d2pl2.PL2 return nil, err } - pm.cache.Insert(path, pl2, 1) + if err := pm.cache.Insert(path, pl2, 1); err != nil { + return nil, err + } + return pl2, nil }