Decouple asset manager from renderer (#730)

* improve AssetManager implementation

Notable changes are:
 * removed the individual managers inside of d2asset, only one asset manager
 * AssetManager now has caches for the types of files it loads
 * created a type for TextDictionary (the txt file structs)
 * fixed a file path bug in d2loader Source
 * fixed a asset stream bug in d2loader Asset
 * d2loader.Loader now needs a d2config.Config on creation (for resolving locale files)
 * updated the mpq file in d2asset test data, added test case for "sub-directory"
 * added a Data method to d2asset.Asset. The data is cached on first full read.
 * renamed ArchiveDataStream to DataStream in d2interface
 * moved palette utility func out of d2asset and into d2util
 * bugfix for MacOS mpq loader issue

* lint fixes, added data caching to filesystem asset

* adding comment for mpq asset close

* Decouple d2asset from d2render

Notable changes in d2common:
 * d2dcc.Load now fully decodes the dcc and stores the directions/frames in the dcc struct
 * un-exported dcc.decodeDirection, it is only used in d2dcc
 * removed font interface from d2interface, we only have one font implementation
 * added `Renderer` method to d2interface.Surface, animations use this to bind to a renderer and create surfaces as they need
 * added `BindRenderer` method to animation interface

Notable changes in d2common/d2asset:
 * **d2asset.NewAssetManager only needs to be passed a d2config.Config**, it is decoupled from d2render
 * exported Animation
 * Animation implementation binds to the renderer to create surfaces only on the first time it is rendered
 * font, dcc, dc6 initialization logic moved out of asset_manager.go
 * for dc6 and dcc animations, the process of decoding and creating render surfaces has been broken into different methods
 * the d2asset.Font struct now stores font table data for initialization purposes

Notable changes in d2core/d2render:
 * Surfaces store a renderer reference, this allows animations to bind to the renderer and create a surface just-in-time

**These last changes should have been a separate PR, sorry.**
Notable changes in d2core/d2ui:
 * ui.NewSprite now handles creating an animation internally, only needs image and palette path as arguments

Notable Changes in d2game:
Because of the change in d2ui, all instances of this code pattern...
```golang
animation, err := screen.asset.LoadAnimation(imgPath, palettePath)
sprite, err := screen.ui.NewSprite(animation)
```
... becomes this ...
```golang
sprite, err := screen.ui.NewSprite(imgPath, palettePath)
```
This commit is contained in:
lord 2020-09-14 14:31:45 -07:00 committed by GitHub
parent 8a670d7482
commit 7e3aff557b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 532 additions and 335 deletions

View File

@ -15,6 +15,7 @@ type DCC struct {
Version int
NumberOfDirections int
FramesPerDirection int
Directions []*DCCDirection
directionOffsets []int
fileData []byte
}
@ -37,6 +38,8 @@ func Load(fileData []byte) (*DCC, error) {
result.NumberOfDirections = int(bm.GetByte())
result.FramesPerDirection = int(bm.GetInt32())
result.Directions = make([]*DCCDirection, result.NumberOfDirections)
if bm.GetInt32() != 1 {
return nil, errors.New("this value isn't 1. It has to be 1")
}
@ -47,13 +50,14 @@ func Load(fileData []byte) (*DCC, error) {
for i := 0; i < result.NumberOfDirections; i++ {
result.directionOffsets[i] = int(bm.GetInt32())
result.Directions[i] = result.decodeDirection(i)
}
return result, nil
}
// DecodeDirection decodes and returns the given direction
func (dcc *DCC) DecodeDirection(direction int) *DCCDirection {
// decodeDirection decodes and returns the given direction
func (dcc *DCC) decodeDirection(direction int) *DCCDirection {
return CreateDCCDirection(d2datautils.CreateBitMuncher(dcc.fileData,
dcc.directionOffsets[direction]*directionOffsetMultiplier), dcc)
}

View File

@ -9,6 +9,7 @@ import (
// Animation is an animation
type Animation interface {
BindRenderer(Renderer) error
Clone() Animation
SetSubLoop(startFrame, EndFrame int)
Advance(elapsed float64) error

View File

@ -1,12 +0,0 @@
package d2interface
import (
"image/color"
)
// Font is a graphical representation associated with a set of glyphs.
type Font interface {
SetColor(c color.Color)
GetTextMetrics(text string) (width, height int)
RenderText(text string, target Surface) error
}

View File

@ -9,6 +9,7 @@ import (
// Surface represents a renderable surface.
type Surface interface {
Renderer() Renderer
Clear(color color.Color) error
DrawRect(width, height int, color color.Color)
DrawLine(x, y int, color color.Color)

View File

@ -23,6 +23,8 @@ const (
const defaultPlayLength = 1.0
type animationFrame struct {
decoded bool
width int
height int
offsetX int
@ -33,11 +35,13 @@ type animationFrame struct {
type animationDirection struct {
decoded bool
frames []*animationFrame
frames []animationFrame
}
// animation has directionality, play modes, and frame counting
type animation struct {
// Animation has directionality, play modes, and frame counting
type Animation struct {
renderer d2interface.Renderer
onBindRenderer func(renderer d2interface.Renderer) error
directions []animationDirection
effect d2enum.DrawEffect
colorMod color.Color
@ -56,14 +60,14 @@ type animation struct {
}
// SetSubLoop sets a sub loop for the animation
func (a *animation) SetSubLoop(startFrame, endFrame int) {
func (a *Animation) SetSubLoop(startFrame, endFrame int) {
a.subStartingFrame = startFrame
a.subEndingFrame = endFrame
a.hasSubLoop = true
}
// Advance advances the animation state
func (a *animation) Advance(elapsed float64) error {
func (a *Animation) Advance(elapsed float64) error {
if a.playMode == playModePause {
return nil
}
@ -112,7 +116,7 @@ func (a *animation) Advance(elapsed float64) error {
return nil
}
func (a *animation) renderShadow(target d2interface.Surface) error {
func (a *Animation) renderShadow(target d2interface.Surface) error {
direction := a.directions[a.directionIndex]
frame := direction.frames[a.frameIndex]
@ -131,7 +135,11 @@ func (a *animation) renderShadow(target d2interface.Surface) error {
}
// Render renders the animation to the given surface
func (a *animation) Render(target d2interface.Surface) error {
func (a *Animation) Render(target d2interface.Surface) error {
if a.renderer == nil {
a.BindRenderer(target.Renderer())
}
direction := a.directions[a.directionIndex]
frame := direction.frames[a.frameIndex]
@ -147,8 +155,22 @@ func (a *animation) Render(target d2interface.Surface) error {
return target.Render(frame.image)
}
// BindRenderer binds the given renderer to the animation so that it can initialize
// the required surfaces
func (a *Animation) BindRenderer(r d2interface.Renderer) error {
if a.onBindRenderer == nil {
return errors.New("the Animation does not have a onBindRenderer handler")
}
return a.onBindRenderer(r)
}
// RenderFromOrigin renders the animation from the animation origin
func (a *animation) RenderFromOrigin(target d2interface.Surface, shadow bool) error {
func (a *Animation) RenderFromOrigin(target d2interface.Surface, shadow bool) error {
if a.renderer == nil {
a.BindRenderer(target.Renderer())
}
if a.originAtBottom {
direction := a.directions[a.directionIndex]
frame := direction.frames[a.frameIndex]
@ -171,7 +193,11 @@ func (a *animation) RenderFromOrigin(target d2interface.Surface, shadow bool) er
}
// RenderSection renders the section of the animation frame enclosed by bounds
func (a *animation) RenderSection(sfc d2interface.Surface, bound image.Rectangle) error {
func (a *Animation) RenderSection(sfc d2interface.Surface, bound image.Rectangle) error {
if a.renderer == nil {
a.BindRenderer(sfc.Renderer())
}
direction := a.directions[a.directionIndex]
frame := direction.frames[a.frameIndex]
@ -185,7 +211,7 @@ func (a *animation) RenderSection(sfc d2interface.Surface, bound image.Rectangle
}
// GetFrameSize gets the Size(width, height) of a indexed frame.
func (a *animation) GetFrameSize(frameIndex int) (width, height int, err error) {
func (a *Animation) GetFrameSize(frameIndex int) (width, height int, err error) {
direction := a.directions[a.directionIndex]
if frameIndex >= len(direction.frames) {
return 0, 0, errors.New("invalid frame index")
@ -197,16 +223,17 @@ func (a *animation) GetFrameSize(frameIndex int) (width, height int, err error)
}
// GetCurrentFrameSize gets the Size(width, height) of the current frame.
func (a *animation) GetCurrentFrameSize() (width, height int) {
func (a *Animation) GetCurrentFrameSize() (width, height int) {
width, height, _ = a.GetFrameSize(a.frameIndex)
return width, height
}
// GetFrameBounds gets maximum Size(width, height) of all frame.
func (a *animation) GetFrameBounds() (maxWidth, maxHeight int) {
func (a *Animation) GetFrameBounds() (maxWidth, maxHeight int) {
maxWidth, maxHeight = 0, 0
direction := a.directions[a.directionIndex]
for _, frame := range direction.frames {
maxWidth = d2math.MaxInt(maxWidth, frame.width)
maxHeight = d2math.MaxInt(maxHeight, frame.height)
@ -216,33 +243,33 @@ func (a *animation) GetFrameBounds() (maxWidth, maxHeight int) {
}
// GetCurrentFrame gets index of current frame in animation
func (a *animation) GetCurrentFrame() int {
func (a *Animation) GetCurrentFrame() int {
return a.frameIndex
}
// GetFrameCount gets number of frames in animation
func (a *animation) GetFrameCount() int {
func (a *Animation) GetFrameCount() int {
direction := a.directions[a.directionIndex]
return len(direction.frames)
}
// IsOnFirstFrame gets if the animation on its first frame
func (a *animation) IsOnFirstFrame() bool {
func (a *Animation) IsOnFirstFrame() bool {
return a.frameIndex == 0
}
// IsOnLastFrame gets if the animation on its last frame
func (a *animation) IsOnLastFrame() bool {
func (a *Animation) IsOnLastFrame() bool {
return a.frameIndex == a.GetFrameCount()-1
}
// GetDirectionCount gets the number of animation direction
func (a *animation) GetDirectionCount() int {
func (a *Animation) GetDirectionCount() int {
return len(a.directions)
}
// SetDirection places the animation in the direction of an animation
func (a *animation) SetDirection(directionIndex int) error {
func (a *Animation) SetDirection(directionIndex int) error {
const smallestInvalidDirectionIndex = 64
if directionIndex >= smallestInvalidDirectionIndex {
return errors.New("invalid direction index")
@ -255,12 +282,12 @@ func (a *animation) SetDirection(directionIndex int) error {
}
// GetDirection get the current animation direction
func (a *animation) GetDirection() int {
func (a *Animation) GetDirection() int {
return a.directionIndex
}
// SetCurrentFrame sets animation at a specific frame
func (a *animation) SetCurrentFrame(frameIndex int) error {
func (a *Animation) SetCurrentFrame(frameIndex int) error {
if frameIndex >= a.GetFrameCount() {
return errors.New("invalid frame index")
}
@ -272,47 +299,47 @@ func (a *animation) SetCurrentFrame(frameIndex int) error {
}
// Rewind animation to beginning
func (a *animation) Rewind() {
func (a *Animation) Rewind() {
_ = a.SetCurrentFrame(0)
}
// PlayForward plays animation forward
func (a *animation) PlayForward() {
func (a *Animation) PlayForward() {
a.playMode = playModeForward
a.lastFrameTime = 0
}
// PlayBackward plays animation backward
func (a *animation) PlayBackward() {
func (a *Animation) PlayBackward() {
a.playMode = playModeBackward
a.lastFrameTime = 0
}
// Pause animation
func (a *animation) Pause() {
func (a *Animation) Pause() {
a.playMode = playModePause
a.lastFrameTime = 0
}
// SetPlayLoop sets whether to loop the animation
func (a *animation) SetPlayLoop(loop bool) {
func (a *Animation) SetPlayLoop(loop bool) {
a.playLoop = loop
}
// SetPlaySpeed sets play speed of the animation
func (a *animation) SetPlaySpeed(playSpeed float64) {
func (a *Animation) SetPlaySpeed(playSpeed float64) {
a.SetPlayLength(playSpeed * float64(a.GetFrameCount()))
}
// SetPlayLength sets the Animation's play length in seconds
func (a *animation) SetPlayLength(playLength float64) {
func (a *Animation) SetPlayLength(playLength float64) {
// TODO refactor to use time.Duration instead of float64
a.playLength = playLength
a.lastFrameTime = 0
}
// SetPlayLengthMs sets the Animation's play length in milliseconds
func (a *animation) SetPlayLengthMs(playLengthMs int) {
func (a *Animation) SetPlayLengthMs(playLengthMs int) {
// TODO remove this method
const millisecondsPerSecond = 1000.0
@ -320,24 +347,24 @@ func (a *animation) SetPlayLengthMs(playLengthMs int) {
}
// SetColorMod sets the Animation's color mod
func (a *animation) SetColorMod(colorMod color.Color) {
func (a *Animation) SetColorMod(colorMod color.Color) {
a.colorMod = colorMod
}
// GetPlayedCount gets the number of times the application played
func (a *animation) GetPlayedCount() int {
func (a *Animation) GetPlayedCount() int {
return a.playedCount
}
// ResetPlayedCount resets the play count
func (a *animation) ResetPlayedCount() {
func (a *Animation) ResetPlayedCount() {
a.playedCount = 0
}
func (a *animation) SetEffect(e d2enum.DrawEffect) {
func (a *Animation) SetEffect(e d2enum.DrawEffect) {
a.effect = e
}
func (a *animation) SetShadow(shadow bool) {
func (a *Animation) SetShadow(shadow bool) {
a.hasShadow = shadow
}

View File

@ -1,7 +1,6 @@
package d2asset
import (
"encoding/binary"
"fmt"
"image/color"
"log"
@ -32,7 +31,6 @@ const (
// AssetManager loads files and game objects
type AssetManager struct {
renderer d2interface.Renderer
loader *d2loader.Loader
tables d2interface.Cache
animations d2interface.Cache
@ -80,12 +78,12 @@ func (am *AssetManager) FileExists(filePath string) (bool, error) {
return true, nil
}
// LoadAnimation loads an animation by its resource path and its palette path
// LoadAnimation loads an Animation by its resource path and its palette path
func (am *AssetManager) LoadAnimation(animationPath, palettePath string) (d2interface.Animation, error) {
return am.LoadAnimationWithEffect(animationPath, palettePath, d2enum.DrawEffectNone)
}
// LoadAnimationWithEffect loads an animation by its resource path and its palette path with a given transparency value
// LoadAnimationWithEffect loads an Animation by its resource path and its palette path with a given transparency value
func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath string,
effect d2enum.DrawEffect) (d2interface.Animation, error) {
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, effect)
@ -108,17 +106,17 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
switch animAsset.Type() {
case types.AssetTypeDC6:
animation, err = am.createDC6Animation(animationPath, palette, effect)
animation, err = am.loadDC6(animationPath, palette, effect)
if err != nil {
return nil, err
}
case types.AssetTypeDCC:
animation, err = am.createDCCAnimation(animationPath, palette, effect)
animation, err = am.loadDCC(animationPath, palette, effect)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown animation format for file: %s", animAsset.Path())
return nil, fmt.Errorf("unknown Animation format for file: %s", animAsset.Path())
}
err = am.animations.Insert(cachePath, animation, defaultCacheEntryWeight)
@ -140,11 +138,11 @@ func (am *AssetManager) LoadComposite(baseType d2enum.ObjectType, token, palette
}
// LoadFont loads a font the resource files
func (am *AssetManager) LoadFont(tablePath, spritePath, palettePath string) (d2interface.Font, error) {
func (am *AssetManager) LoadFont(tablePath, spritePath, palettePath string) (*Font, error) {
cachePath := fmt.Sprintf("%s;%s;%s", tablePath, spritePath, palettePath)
if cached, found := am.fonts.Retrieve(cachePath); found {
return cached.(d2interface.Font), nil
return cached.(*Font), nil
}
sheet, err := am.LoadAnimation(spritePath, palettePath)
@ -161,25 +159,10 @@ func (am *AssetManager) LoadFont(tablePath, spritePath, palettePath string) (d2i
return nil, fmt.Errorf("invalid font table format: %s", tablePath)
}
_, maxCharHeight := sheet.GetFrameBounds()
glyphs := make(map[rune]fontGlyph)
for i := 12; i < len(tableData); i += 14 {
code := rune(binary.LittleEndian.Uint16(tableData[i : i+2]))
var glyph fontGlyph
glyph.frame = int(binary.LittleEndian.Uint16(tableData[i+8 : i+10]))
glyph.width = int(tableData[i+3])
glyph.height = maxCharHeight
glyphs[code] = glyph
}
font := &Font{
sheet: sheet,
glyphs: glyphs,
color: color.White,
table: tableData,
sheet: sheet,
color: color.White,
}
err = am.fonts.Insert(cachePath, font, defaultCacheEntryWeight)
@ -229,7 +212,7 @@ func (am *AssetManager) LoadStringTable(tablePath string) (d2tbl.TextDictionary,
}
table := d2tbl.LoadTextDictionary(data)
if table != nil {
if table == nil {
return nil, fmt.Errorf("table not found: %s", tablePath)
}
@ -261,66 +244,9 @@ func (am *AssetManager) LoadPaletteTransform(path string) (*d2pl2.PL2, error) {
return pl2, nil
}
// createDC6Animation creates an Animation from d2dc6.DC6 and d2dat.DATPalette
func (am *AssetManager) createDC6Animation(dc6Path string,
// loadDC6 creates an Animation from d2dc6.DC6 and d2dat.DATPalette
func (am *AssetManager) loadDC6(path string,
palette d2interface.Palette, effect d2enum.DrawEffect) (d2interface.Animation, error) {
dc6, err := am.loadDC6(dc6Path)
if err != nil {
return nil, err
}
anim := DC6Animation{
animation: animation{
directions: make([]animationDirection, dc6.Directions),
playLength: defaultPlayLength,
playLoop: true,
originAtBottom: true,
effect: effect,
},
dc6Path: dc6Path,
dc6: dc6,
palette: palette,
renderer: am.renderer,
}
err = anim.SetDirection(0)
return &anim, err
}
// createDCCAnimation creates an animation from d2dcc.DCC and d2dat.DATPalette
func (am *AssetManager) createDCCAnimation(dccPath string,
palette d2interface.Palette,
effect d2enum.DrawEffect) (d2interface.Animation, error) {
dcc, err := am.loadDCC(dccPath)
if err != nil {
return nil, err
}
anim := animation{
playLength: defaultPlayLength,
playLoop: true,
directions: make([]animationDirection, dcc.NumberOfDirections),
effect: effect,
}
DCC := DCCAnimation{
animation: anim,
AssetManager: am,
dccPath: dccPath,
palette: palette,
renderer: am.renderer,
}
err = DCC.SetDirection(0)
if err != nil {
return nil, err
}
return &DCC, nil
}
func (am *AssetManager) loadDC6(path string) (*d2dc6.DC6, error) {
dc6Data, err := am.LoadFile(path)
if err != nil {
return nil, err
@ -331,16 +257,27 @@ func (am *AssetManager) loadDC6(path string) (*d2dc6.DC6, error) {
return nil, err
}
return dc6, nil
animation, err := newDC6Animation(dc6, palette, effect)
return animation, err
}
func (am *AssetManager) loadDCC(path string) (*d2dcc.DCC, error) {
// loadDCC creates an Animation from d2dcc.DCC and d2dat.DATPalette
func (am *AssetManager) loadDCC(path string,
palette d2interface.Palette, effect d2enum.DrawEffect) (d2interface.Animation, error) {
dccData, err := am.LoadFile(path)
if err != nil {
return nil, err
}
return d2dcc.Load(dccData)
dcc, err := d2dcc.Load(dccData)
if err != nil {
return nil, err
}
animation, err := newDCCAnimation(dcc, palette, effect)
return animation, nil
}
// BindTerminalCommands binds the in-game terminal comands for the asset manager.
@ -368,7 +305,7 @@ func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
term.OutputInfof("palette cache: %f", cacheStatistics(am.palettes))
term.OutputInfof("palette transform cache: %f", cacheStatistics(am.transforms))
term.OutputInfof("animation cache: %f", cacheStatistics(am.animations))
term.OutputInfof("Animation cache: %f", cacheStatistics(am.animations))
term.OutputInfof("font cache: %f", cacheStatistics(am.fonts))
}); err != nil {
return err

View File

@ -63,6 +63,7 @@ func (c *Composite) Render(target d2interface.Surface) error {
for _, layerIndex := range c.mode.cof.Priority[direction][c.mode.frameIndex] {
layer := c.mode.layers[layerIndex]
if layer != nil {
if err := layer.RenderFromOrigin(target, true); err != nil {
return err
@ -145,6 +146,10 @@ func (c *Composite) SetAnimSpeed(speed int) {
// SetDirection sets the direction of the composite and its layers
func (c *Composite) SetDirection(direction int) {
if c.mode == nil {
return
}
c.direction = direction
for layerIdx := range c.mode.layers {
layer := c.mode.layers[layerIdx]
@ -241,7 +246,7 @@ func (c *Composite) createMode(animationMode animationMode, weaponClass string)
animationData := d2data.AnimationData[animationKey]
if len(animationData) == 0 {
return nil, errors.New("could not find animation data")
return nil, errors.New("could not find Animation data")
}
mode := &compositeMode{
@ -300,7 +305,7 @@ func (c *Composite) loadCompositeLayer(layerKey, layerValue, animationMode, weap
}
}
return nil, errors.New("animation not found")
return nil, errors.New("Animation not found")
}
// GetSize returns the size of the composite

View File

@ -2,16 +2,13 @@ package d2asset
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
)
// NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly
func NewAssetManager(renderer d2interface.Renderer, config *d2config.Configuration,
term d2interface.Terminal) (*AssetManager, error) {
func NewAssetManager(config *d2config.Configuration) (*AssetManager, error) {
manager := &AssetManager{
renderer,
d2loader.NewLoader(config),
d2cache.CreateCache(animationBudget),
d2cache.CreateCache(tableBudget),
@ -20,10 +17,5 @@ func NewAssetManager(renderer d2interface.Renderer, config *d2config.Configurati
d2cache.CreateCache(paletteTransformBudget),
}
if term != nil {
err := manager.BindTerminalCommands(term)
return manager, err
}
return manager, nil
}

View File

@ -3,9 +3,9 @@ package d2asset
import (
"errors"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -14,13 +14,58 @@ import (
var _ d2interface.Animation = &DC6Animation{} // Static check to confirm struct conforms to
// interface
func newDC6Animation(
dc6 *d2dc6.DC6,
pal d2interface.Palette,
effect d2enum.DrawEffect,
) (d2interface.Animation, error) {
DC6 := &DC6Animation{
dc6: dc6,
palette: pal,
}
anim := Animation{
playLength: defaultPlayLength,
playLoop: true,
originAtBottom: true,
effect: effect,
onBindRenderer: func(r d2interface.Renderer) error {
if DC6.renderer != r {
DC6.renderer = r
return DC6.createSurfaces()
}
return nil
},
}
DC6.Animation = anim
err := DC6.init()
if err != nil {
return nil, err
}
return DC6, nil
}
// DC6Animation is an animation made from a DC6 file
type DC6Animation struct {
animation
dc6Path string
dc6 *d2dc6.DC6
palette d2interface.Palette
renderer d2interface.Renderer
Animation
dc6 *d2dc6.DC6
palette d2interface.Palette
}
func (a *DC6Animation) init() error {
a.directions = make([]animationDirection, a.dc6.Directions)
for directionIndex := range a.directions {
a.directions[directionIndex].frames = make([]animationFrame, a.dc6.FramesPerDirection)
}
err := a.decode()
return err
}
// SetDirection decodes and sets the direction
@ -31,7 +76,8 @@ func (a *DC6Animation) SetDirection(directionIndex int) error {
}
direction := d2dcc.Dir64ToDcc(directionIndex, len(a.directions))
if !a.directions[direction].decoded {
if !a.directions[directionIndex].decoded {
err := a.decodeDirection(direction)
if err != nil {
return err
@ -39,44 +85,115 @@ func (a *DC6Animation) SetDirection(directionIndex int) error {
}
a.directionIndex = direction
a.frameIndex = 0
return nil
}
func (a *DC6Animation) decode() error {
for directionIndex := 0; directionIndex < len(a.directions); directionIndex++ {
err := a.decodeDirection(directionIndex)
if err != nil {
return err
}
}
return nil
}
func (a *DC6Animation) decodeDirection(directionIndex int) error {
dc6 := a.dc6
startFrame := directionIndex * int(dc6.FramesPerDirection)
for i := 0; i < int(dc6.FramesPerDirection); i++ {
dc6Frame := dc6.Frames[startFrame+i]
sfc, err := a.renderer.NewSurface(int(dc6Frame.Width), int(dc6Frame.Height),
d2enum.FilterNearest)
for frameIndex := 0; frameIndex < int(a.dc6.FramesPerDirection); frameIndex++ {
frame, err := a.decodeFrame(directionIndex, frameIndex)
if err != nil {
return err
}
indexData := dc6.DecodeFrame(startFrame + i)
colorData := d2util.ImgIndexToRGBA(indexData, a.palette)
a.directions[directionIndex].frames[frameIndex] = frame
}
if err := sfc.ReplacePixels(colorData); err != nil {
a.directions[directionIndex].decoded = true
return nil
}
func (a *DC6Animation) decodeFrame(directionIndex, frameIndex int) (animationFrame, error) {
startFrame := directionIndex * int(a.dc6.FramesPerDirection)
dc6Frame := a.dc6.Frames[startFrame+frameIndex]
frame := animationFrame{
width: int(dc6Frame.Width),
height: int(dc6Frame.Height),
offsetX: int(dc6Frame.OffsetX),
offsetY: int(dc6Frame.OffsetY),
}
a.directions[directionIndex].frames[frameIndex].decoded = true
return frame, nil
}
func (a *DC6Animation) createSurfaces() error {
for directionIndex := 0; directionIndex < len(a.directions); directionIndex++ {
err := a.createDirectionSurfaces(directionIndex)
if err != nil {
return err
}
a.directions[directionIndex].decoded = true
a.directions[directionIndex].frames = append(a.directions[directionIndex].frames, &animationFrame{
width: int(dc6Frame.Width),
height: int(dc6Frame.Height),
offsetX: int(dc6Frame.OffsetX),
offsetY: int(dc6Frame.OffsetY),
image: sfc,
})
}
return nil
}
func (a *DC6Animation) createDirectionSurfaces(directionIndex int) error {
for frameIndex := 0; frameIndex < int(a.dc6.FramesPerDirection); frameIndex++ {
if !a.directions[directionIndex].decoded {
err := a.decodeDirection(directionIndex)
if err != nil {
return err
}
}
surface, err := a.createFrameSurface(directionIndex, frameIndex)
if err != nil {
return err
}
a.directions[directionIndex].frames[frameIndex].image = surface
}
return nil
}
func (a *DC6Animation) createFrameSurface(directionIndex, frameIndex int) (d2interface.Surface, error) {
if !a.directions[directionIndex].frames[frameIndex].decoded {
frame, err := a.decodeFrame(directionIndex, frameIndex)
if err != nil {
return nil, err
}
a.directions[directionIndex].frames[frameIndex] = frame
}
startFrame := directionIndex * int(a.dc6.FramesPerDirection)
dc6Frame := a.dc6.Frames[startFrame+frameIndex]
indexData := a.dc6.DecodeFrame(startFrame + frameIndex)
colorData := d2util.ImgIndexToRGBA(indexData, a.palette)
if a.renderer == nil {
return nil, errors.New("no renderer")
}
sfc, err := a.renderer.NewSurface(int(dc6Frame.Width), int(dc6Frame.Height), d2enum.FilterNearest)
if err != nil {
return nil, err
}
if err := sfc.ReplacePixels(colorData); err != nil {
return nil, err
}
return sfc, nil
}
// Clone creates a copy of the animation
func (a *DC6Animation) Clone() d2interface.Animation {
animation := *a

View File

@ -4,10 +4,11 @@ import (
"errors"
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -16,13 +17,57 @@ import (
var _ d2interface.Animation = &DCCAnimation{} // Static check to confirm struct conforms to
// interface
func newDCCAnimation(
dcc *d2dcc.DCC,
pal d2interface.Palette,
effect d2enum.DrawEffect,
) (d2interface.Animation, error) {
DCC := &DCCAnimation{
dcc: dcc,
palette: pal,
}
anim := Animation{
playLength: defaultPlayLength,
playLoop: true,
effect: effect,
onBindRenderer: func(r d2interface.Renderer) error {
if DCC.renderer != r {
DCC.renderer = r
return DCC.createSurfaces()
}
return nil
},
}
DCC.Animation = anim
err := DCC.init()
if err != nil {
return nil, err
}
return DCC, nil
}
// DCCAnimation represents an animation decoded from DCC
type DCCAnimation struct {
animation
*AssetManager
dccPath string
palette d2interface.Palette
renderer d2interface.Renderer
Animation
dcc *d2dcc.DCC
palette d2interface.Palette
}
func (a *DCCAnimation) init() error {
a.directions = make([]animationDirection, a.dcc.NumberOfDirections)
for directionIndex := range a.directions {
a.directions[directionIndex].frames = make([]animationFrame, a.dcc.FramesPerDirection)
}
err := a.decode()
return err
}
// Clone creates a copy of the animation
@ -52,18 +97,45 @@ func (a *DCCAnimation) SetDirection(directionIndex int) error {
return nil
}
func (a *DCCAnimation) decodeDirection(directionIndex int) error {
dcc, err := a.loadDCC(a.dccPath)
if err != nil {
return err
func (a *DCCAnimation) decode() error {
for directionIndex := 0; directionIndex < len(a.directions); directionIndex++ {
err := a.decodeDirection(directionIndex)
if err != nil {
return err
}
}
direction := dcc.DecodeDirection(directionIndex)
return nil
}
func (a *DCCAnimation) decodeDirection(directionIndex int) error {
dccDirection := a.dcc.Directions[directionIndex]
for frameIndex := range dccDirection.Frames {
if a.directions[directionIndex].frames == nil {
a.directions[directionIndex].frames = make([]animationFrame, a.dcc.FramesPerDirection)
}
a.directions[directionIndex].decoded = true
frame, err := a.decodeFrame(directionIndex, frameIndex)
if err != nil {
return err
}
a.directions[directionIndex].frames[frameIndex] = frame
}
return nil
}
func (a *DCCAnimation) decodeFrame(directionIndex, frameIndex int) (animationFrame, error) {
dccDirection := a.dcc.Directions[directionIndex]
minX, minY := math.MaxInt32, math.MaxInt32
maxX, maxY := math.MinInt32, math.MinInt32
for _, dccFrame := range direction.Frames {
for _, dccFrame := range dccDirection.Frames {
minX = d2math.MinInt(minX, dccFrame.Box.Left)
minY = d2math.MinInt(minY, dccFrame.Box.Top)
maxX = d2math.MaxInt(maxX, dccFrame.Box.Right())
@ -73,27 +145,80 @@ func (a *DCCAnimation) decodeDirection(directionIndex int) error {
frameWidth := maxX - minX
frameHeight := maxY - minY
for _, dccFrame := range direction.Frames {
pixels := d2util.ImgIndexToRGBA(dccFrame.PixelData, a.palette)
frame := animationFrame{
width: frameWidth,
height: frameHeight,
offsetX: minX,
offsetY: minY,
decoded: true,
}
sfc, err := a.renderer.NewSurface(frameWidth, frameHeight, d2enum.FilterNearest)
return frame, nil
}
func (a *DCCAnimation) createSurfaces() error {
for directionIndex := 0; directionIndex < len(a.directions); directionIndex++ {
err := a.createDirectionSurfaces(directionIndex)
if err != nil {
return err
}
if err := sfc.ReplacePixels(pixels); err != nil {
return err
}
a.directions[directionIndex].decoded = true
a.directions[directionIndex].frames = append(a.directions[directionIndex].frames, &animationFrame{
width: frameWidth,
height: frameHeight,
offsetX: minX,
offsetY: minY,
image: sfc,
})
}
return nil
}
func (a *DCCAnimation) createDirectionSurfaces(directionIndex int) error {
for frameIndex := 0; frameIndex < int(a.dcc.FramesPerDirection); frameIndex++ {
if !a.directions[directionIndex].decoded {
err := a.decodeDirection(directionIndex)
if err != nil {
return err
}
}
surface, err := a.createFrameSurface(directionIndex, frameIndex)
if err != nil {
return err
}
a.directions[directionIndex].frames[frameIndex].image = surface
}
return nil
}
func (a *DCCAnimation) createFrameSurface(directionIndex, frameIndex int) (d2interface.Surface, error) {
if !a.directions[directionIndex].frames[frameIndex].decoded {
frame, err := a.decodeFrame(directionIndex, frameIndex)
if err != nil {
return nil, err
}
a.directions[directionIndex].frames[frameIndex] = frame
}
dccFrame := a.dcc.Directions[directionIndex].Frames[frameIndex]
animFrame := a.directions[directionIndex].frames[frameIndex]
indexData := dccFrame.PixelData
if len(indexData) != (animFrame.width * animFrame.height) {
return nil, errors.New("pixel data incorrect")
}
colorData := d2util.ImgIndexToRGBA(indexData, a.palette)
if a.renderer == nil {
return nil, errors.New("no renderer")
}
sfc, err := a.renderer.NewSurface(animFrame.width, animFrame.height, d2enum.FilterNearest)
if err != nil {
return nil, err
}
if err := sfc.ReplacePixels(colorData); err != nil {
return nil, err
}
return sfc, nil
}

View File

@ -1,6 +1,7 @@
package d2asset
import (
"encoding/binary"
"image/color"
"strings"
@ -8,8 +9,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)
var _ d2interface.Font = &Font{} // Static check to confirm struct conforms to interface
type fontGlyph struct {
frame int
width int
@ -19,6 +18,7 @@ type fontGlyph struct {
// Font represents a displayable font
type Font struct {
sheet d2interface.Animation
table []byte
glyphs map[rune]fontGlyph
color color.Color
}
@ -30,17 +30,19 @@ func (f *Font) SetColor(c color.Color) {
// GetTextMetrics returns the dimensions of the Font element in pixels
func (f *Font) GetTextMetrics(text string) (width, height int) {
if f.glyphs == nil {
f.initGlyphs()
}
var (
lineWidth int
lineHeight int
totalWidth int
totalHeight int
lineWidth int
lineHeight int
)
for _, c := range text {
if c == '\n' {
totalWidth = d2math.MaxInt(totalWidth, lineWidth)
totalHeight += lineHeight
width = d2math.MaxInt(width, lineWidth)
height += lineHeight
lineWidth = 0
lineHeight = 0
} else if glyph, ok := f.glyphs[c]; ok {
@ -49,14 +51,23 @@ func (f *Font) GetTextMetrics(text string) (width, height int) {
}
}
totalWidth = d2math.MaxInt(totalWidth, lineWidth)
totalHeight += lineHeight
width = d2math.MaxInt(width, lineWidth)
height += lineHeight
return totalWidth, totalHeight
return width, height
}
// RenderText prints a text using its configured style on a Surface (multi-lines are left-aligned, use label otherwise)
func (f *Font) RenderText(text string, target d2interface.Surface) error {
if f.glyphs == nil {
err := f.sheet.BindRenderer(target.Renderer())
if err != nil {
return err
}
f.initGlyphs()
}
f.sheet.SetColorMod(f.color)
lines := strings.Split(text, "\n")
@ -95,3 +106,22 @@ func (f *Font) RenderText(text string, target d2interface.Surface) error {
return nil
}
func (f *Font) initGlyphs() {
_, maxCharHeight := f.sheet.GetFrameBounds()
glyphs := make(map[rune]fontGlyph)
for i := 12; i < len(f.table); i += 14 {
code := rune(binary.LittleEndian.Uint16(f.table[i : i+2]))
var glyph fontGlyph
glyph.frame = int(binary.LittleEndian.Uint16(f.table[i+8 : i+10]))
glyph.width = int(f.table[i+3])
glyph.height = maxCharHeight
glyphs[code] = glyph
}
f.glyphs = glyphs
}

View File

@ -4,11 +4,13 @@ import (
"errors"
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)
func loadFont(fontStyle FontStyle) (d2interface.Font, error) {
func loadFont(fontStyle FontStyle) (*d2asset.Font, error) {
config := getFontStyleConfig(fontStyle)
if config == nil {
return nil, errors.New("invalid font style")

View File

@ -3,6 +3,7 @@ package d2gui
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// Label is renderable text
@ -11,7 +12,7 @@ type Label struct {
renderer d2interface.Renderer
text string
font d2interface.Font
font *d2asset.Font
surface d2interface.Surface
}

View File

@ -29,7 +29,7 @@ type Renderer struct {
// Update updates the screen with the given *ebiten.Image
func (r *Renderer) Update(screen *ebiten.Image) error {
err := r.renderCallback(createEbitenSurface(screen))
err := r.renderCallback(createEbitenSurface(r, screen))
if err != nil {
return err
}
@ -88,19 +88,18 @@ func (r *Renderer) Run(f func(surface d2interface.Surface) error, width, height
// CreateSurface creates a renderer surface from an existing surface
func (r *Renderer) CreateSurface(surface d2interface.Surface) (d2interface.Surface, error) {
result := createEbitenSurface(
surface.(*ebitenSurface).image,
surfaceState{
filter: ebiten.FilterNearest,
effect: d2enum.DrawEffectNone,
saturation: defaultSaturation,
brightness: defaultBrightness,
skewX: defaultSkewX,
skewY: defaultSkewY,
scaleX: defaultScaleX,
scaleY: defaultScaleY,
},
)
img := surface.(*ebitenSurface).image
sfcState := surfaceState{
filter: ebiten.FilterNearest,
effect: d2enum.DrawEffectNone,
saturation: defaultSaturation,
brightness: defaultBrightness,
skewX: defaultSkewX,
skewY: defaultSkewY,
scaleX: defaultScaleX,
scaleY: defaultScaleY,
}
result := createEbitenSurface(r, img, sfcState)
return result, nil
}
@ -114,7 +113,7 @@ func (r *Renderer) NewSurface(width, height int, filter d2enum.Filter) (d2interf
return nil, err
}
return createEbitenSurface(img), nil
return createEbitenSurface(r, img), nil
}
// IsFullScreen returns a boolean for whether or not the renderer is currently set to fullscreen

View File

@ -30,6 +30,7 @@ type colorMCacheEntry struct {
}
type ebitenSurface struct {
renderer *Renderer
stateStack []surfaceState
stateCurrent surfaceState
image *ebiten.Image
@ -37,7 +38,7 @@ type ebitenSurface struct {
monotonicClock int64
}
func createEbitenSurface(img *ebiten.Image, currentState ...surfaceState) *ebitenSurface {
func createEbitenSurface(r *Renderer, img *ebiten.Image, currentState ...surfaceState) *ebitenSurface {
state := surfaceState{
effect: d2enum.DrawEffectNone,
saturation: defaultSaturation,
@ -52,12 +53,18 @@ func createEbitenSurface(img *ebiten.Image, currentState ...surfaceState) *ebite
}
return &ebitenSurface{
renderer: r,
image: img,
stateCurrent: state,
colorMCache: make(map[colorMCacheKey]*colorMCacheEntry),
}
}
// Renderer returns the renderer
func (s *ebitenSurface) Renderer() d2interface.Renderer {
return s.renderer
}
// PushTranslation pushes an x,y translation to the state stack
func (s *ebitenSurface) PushTranslation(x, y int) {
s.stateStack = append(s.stateStack, s.stateCurrent)

View File

@ -191,8 +191,7 @@ func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button {
lbl.Color[0] = d2util.Color(greyAlpha100)
lbl.Alignment = d2gui.HorizontalAlignCenter
animation, _ := ui.asset.LoadAnimation(buttonLayout.ResourceName, buttonLayout.PaletteName)
buttonSprite, _ := ui.NewSprite(animation)
buttonSprite, _ := ui.NewSprite(buttonLayout.ResourceName, buttonLayout.PaletteName)
for i := 0; i < buttonLayout.XSegments; i++ {
w, _, _ := buttonSprite.GetFrameSize(i)

View File

@ -31,8 +31,7 @@ func (ui *UIManager) NewCheckbox(checkState bool) *Checkbox {
enabled: true,
}
animation, _ := ui.asset.LoadAnimation(d2resource.Checkbox, d2resource.PaletteFechar)
checkboxSprite, _ := ui.NewSprite(animation)
checkboxSprite, _ := ui.NewSprite(d2resource.Checkbox, d2resource.PaletteFechar)
result.width, result.height, _ = checkboxSprite.GetFrameSize(0)
checkboxSprite.SetPosition(0, 0)

View File

@ -6,6 +6,8 @@ import (
"regexp"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
@ -18,7 +20,7 @@ type Label struct {
X int
Y int
Alignment d2gui.HorizontalAlign
font d2interface.Font
font *d2asset.Font
Color map[int]color.Color
backgroundColor color.Color
}
@ -26,6 +28,7 @@ type Label struct {
// NewLabel creates a new instance of a UI label
func (ui *UIManager) NewLabel(fontPath, palettePath string) *Label {
font, _ := ui.asset.LoadFont(fontPath+".tbl", fontPath+".dc6", palettePath)
result := &Label{
Alignment: d2gui.HorizontalAlignLeft,
Color: map[int]color.Color{0: color.White},

View File

@ -28,8 +28,7 @@ type Scrollbar struct {
// NewScrollbar creates a scrollbar instance
func (ui *UIManager) NewScrollbar(x, y, height int) *Scrollbar {
animation, _ := ui.asset.LoadAnimation(d2resource.Scrollbar, d2resource.PaletteSky)
scrollbarSprite, _ := ui.NewSprite(animation)
scrollbarSprite, _ := ui.NewSprite(d2resource.Scrollbar, d2resource.PaletteSky)
result := &Scrollbar{
visible: true,
enabled: true,

View File

@ -22,11 +22,17 @@ const (
)
// NewSprite creates a new Sprite
func (ui *UIManager) NewSprite(animation d2interface.Animation) (*Sprite, error) {
if animation == nil {
func (ui *UIManager) NewSprite(animationPath, palettePath string) (*Sprite, error) {
animation, err := ui.asset.LoadAnimation(animationPath, palettePath)
if animation == nil || err != nil {
return nil, fmt.Errorf(errNoAnimation)
}
err = animation.BindRenderer(ui.renderer)
if err != nil {
return nil, err
}
return &Sprite{animation: animation}, nil
}
@ -56,7 +62,8 @@ func (s *Sprite) RenderSegmented(target d2interface.Surface, segmentsX, segments
var currentX, maxFrameHeight int
for x := 0; x < segmentsX; x++ {
if err := s.animation.SetCurrentFrame(x + y*segmentsX + frameOffset*segmentsX*segmentsY); err != nil {
idx := x + y*segmentsX + frameOffset*segmentsX*segmentsY
if err := s.animation.SetCurrentFrame(idx); err != nil {
return err
}

View File

@ -26,8 +26,7 @@ type TextBox struct {
// NewTextbox creates a new instance of a text box
func (ui *UIManager) NewTextbox() *TextBox {
animation, _ := ui.asset.LoadAnimation(d2resource.TextBox2, d2resource.PaletteUnits)
bgSprite, _ := ui.NewSprite(animation)
bgSprite, _ := ui.NewSprite(d2resource.TextBox2, d2resource.PaletteUnits)
tb := &TextBox{
filter: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
bgSprite: bgSprite,

View File

@ -138,10 +138,8 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
loading.Progress(tenPercent)
animation, _ := v.asset.LoadAnimation(d2resource.CharacterSelectionBackground,
d2resource.PaletteSky)
bgX, bgY := 0, 0
v.background, _ = v.uiManager.NewSprite(animation)
v.background, _ = v.uiManager.NewSprite(d2resource.CharacterSelectionBackground, d2resource.PaletteSky)
v.background.SetPosition(bgX, bgY)
v.createButtons(loading)
@ -160,14 +158,11 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
deleteConfirmX, deleteConfirmY := 400, 185
v.deleteCharConfirmLabel.SetPosition(deleteConfirmX, deleteConfirmY)
animation, _ = v.asset.LoadAnimation(d2resource.CharacterSelectionSelectBox,
d2resource.PaletteSky)
v.selectionBox, _ = v.uiManager.NewSprite(animation)
v.selectionBox, _ = v.uiManager.NewSprite(d2resource.CharacterSelectionSelectBox, d2resource.PaletteSky)
selBoxX, selBoxY := 37, 86
v.selectionBox.SetPosition(selBoxX, selBoxY)
animation, _ = v.asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
v.okCancelBox, _ = v.uiManager.NewSprite(animation)
v.okCancelBox, _ = v.uiManager.NewSprite(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
okCancelX, okCancelY := 270, 175
v.okCancelBox.SetPosition(okCancelX, okCancelY)

View File

@ -89,8 +89,7 @@ func (v *Credits) LoadContributors() []string {
// OnLoad is called to load the resources for the credits screen
func (v *Credits) OnLoad(loading d2screen.LoadingState) {
animation, _ := v.asset.LoadAnimation(d2resource.CreditsBackground, d2resource.PaletteSky)
v.creditsBackground, _ = v.uiManager.NewSprite(animation)
v.creditsBackground, _ = v.uiManager.NewSprite(d2resource.CreditsBackground, d2resource.PaletteSky)
v.creditsBackground.SetPosition(creditsX, creditsY)
loading.Progress(twentyPercent)

View File

@ -172,20 +172,16 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) {
}
func (v *MainMenu) loadBackgroundSprites() {
animation, _ := v.asset.LoadAnimation(d2resource.GameSelectScreen, d2resource.PaletteSky)
v.background, _ = v.uiManager.NewSprite(animation)
v.background, _ = v.uiManager.NewSprite(d2resource.GameSelectScreen, d2resource.PaletteSky)
v.background.SetPosition(backgroundX, backgroundY)
animation, _ = v.asset.LoadAnimation(d2resource.TrademarkScreen, d2resource.PaletteSky)
v.trademarkBackground, _ = v.uiManager.NewSprite(animation)
v.trademarkBackground, _ = v.uiManager.NewSprite(d2resource.TrademarkScreen, d2resource.PaletteSky)
v.trademarkBackground.SetPosition(backgroundX, backgroundY)
animation, _ = v.asset.LoadAnimation(d2resource.TCPIPBackground, d2resource.PaletteSky)
v.tcpIPBackground, _ = v.uiManager.NewSprite(animation)
v.tcpIPBackground, _ = v.uiManager.NewSprite(d2resource.TCPIPBackground, d2resource.PaletteSky)
v.tcpIPBackground.SetPosition(backgroundX, backgroundY)
animation, _ = v.asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
v.serverIPBackground, _ = v.uiManager.NewSprite(animation)
v.serverIPBackground, _ = v.uiManager.NewSprite(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
v.serverIPBackground.SetPosition(serverIPbackgroundX, serverIPbackgroundY)
}
@ -236,25 +232,21 @@ func (v *MainMenu) createLabels(loading d2screen.LoadingState) {
}
func (v *MainMenu) createLogos(loading d2screen.LoadingState) {
animation, _ := v.asset.LoadAnimation(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits)
v.diabloLogoLeft, _ = v.uiManager.NewSprite(animation)
v.diabloLogoLeft, _ = v.uiManager.NewSprite(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits)
v.diabloLogoLeft.SetEffect(d2enum.DrawEffectModulate)
v.diabloLogoLeft.PlayForward()
v.diabloLogoLeft.SetPosition(diabloLogoX, diabloLogoY)
loading.Progress(sixtyPercent)
animation, _ = v.asset.LoadAnimation(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits)
v.diabloLogoRight, _ = v.uiManager.NewSprite(animation)
v.diabloLogoRight, _ = v.uiManager.NewSprite(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits)
v.diabloLogoRight.SetEffect(d2enum.DrawEffectModulate)
v.diabloLogoRight.PlayForward()
v.diabloLogoRight.SetPosition(diabloLogoX, diabloLogoY)
animation, _ = v.asset.LoadAnimation(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits)
v.diabloLogoLeftBack, _ = v.uiManager.NewSprite(animation)
v.diabloLogoLeftBack, _ = v.uiManager.NewSprite(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits)
v.diabloLogoLeftBack.SetPosition(diabloLogoX, diabloLogoY)
animation, _ = v.asset.LoadAnimation(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits)
v.diabloLogoRightBack, _ = v.uiManager.NewSprite(animation)
v.diabloLogoRightBack, _ = v.uiManager.NewSprite(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits)
v.diabloLogoRightBack.SetPosition(diabloLogoX, diabloLogoY)
}

View File

@ -734,29 +734,23 @@ func (v *SelectHeroClass) loadSprite(animationPath string, position image.Point,
return nil
}
animation, err := v.asset.LoadAnimation(animationPath, d2resource.PaletteFechar)
if err != nil {
fmt.Printf("could not load animation: %s\n", animationPath)
return nil
}
animation.PlayForward()
animation.SetPlayLoop(playLoop)
if blend {
animation.SetEffect(d2enum.DrawEffectModulate)
}
if playLength != 0 {
animation.SetPlayLengthMs(playLength)
}
sprite, err := v.uiManager.NewSprite(animation)
sprite, err := v.uiManager.NewSprite(animationPath, d2resource.PaletteFechar)
if err != nil {
fmt.Printf("could not load sprite for the animation: %s\n", animationPath)
return nil
}
sprite.PlayForward()
sprite.SetPlayLoop(playLoop)
if blend {
sprite.SetEffect(d2enum.DrawEffectModulate)
}
if playLength != 0 {
sprite.SetPlayLengthMs(playLength)
}
sprite.SetPosition(position.X, position.Y)
return sprite

View File

@ -381,21 +381,16 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
// Load the resources required for the GameControls
func (g *GameControls) Load() {
animation, _ := g.asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
g.globeSprite, _ = g.uiManager.NewSprite(animation)
g.globeSprite, _ = g.uiManager.NewSprite(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
animation, _ = g.asset.LoadAnimation(d2resource.HealthManaIndicator, d2resource.PaletteSky)
g.hpManaStatusSprite, _ = g.uiManager.NewSprite(animation)
g.hpManaStatusSprite, _ = g.uiManager.NewSprite(d2resource.HealthManaIndicator, d2resource.PaletteSky)
animation, _ = g.asset.LoadAnimation(d2resource.GamePanels, d2resource.PaletteSky)
g.mainPanel, _ = g.uiManager.NewSprite(animation)
g.mainPanel, _ = g.uiManager.NewSprite(d2resource.GamePanels, d2resource.PaletteSky)
animation, _ = g.asset.LoadAnimation(d2resource.MenuButton, d2resource.PaletteSky)
_ = animation.SetCurrentFrame(2)
g.menuButton, _ = g.uiManager.NewSprite(animation)
g.menuButton, _ = g.uiManager.NewSprite(d2resource.MenuButton, d2resource.PaletteSky)
_ = g.menuButton.SetCurrentFrame(2)
animation, _ = g.asset.LoadAnimation(d2resource.GenericSkills, d2resource.PaletteSky)
g.skillIcon, _ = g.uiManager.NewSprite(animation)
g.skillIcon, _ = g.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
g.loadUIButtons()

View File

@ -94,9 +94,8 @@ func (h *Overlay) Load() {
prevY = 0
)
for frameIndex := 0; frameIndex < 7; frameIndex++ {
animation, _ := h.asset.LoadAnimation(d2resource.HelpBorder, d2resource.PaletteSky)
_ = animation.SetCurrentFrame(frameIndex)
f, _ := h.uiManager.NewSprite(animation)
f, _ := h.uiManager.NewSprite(d2resource.HelpBorder, d2resource.PaletteSky)
_ = f.SetCurrentFrame(frameIndex)
ww, hh := f.GetCurrentFrameSize()
//fmt.Printf("Help frame %d size: %d, %d\n", frameIndex, ww, hh)
@ -143,9 +142,7 @@ func (h *Overlay) Load() {
h.text = append(h.text, newLabel)
// Close
anim, _ := h.asset.LoadAnimation(d2resource.SquareButton, d2resource.PaletteSky)
close, _ := h.uiManager.NewSprite(anim)
close, _ := h.uiManager.NewSprite(d2resource.SquareButton, d2resource.PaletteSky)
_ = close.SetCurrentFrame(0)
close.SetPosition(685, 57)
h.frames = append(h.frames, close)
@ -344,8 +341,7 @@ func (h *Overlay) createBullet(c callout) {
newLabel.SetPosition(c.LabelX, c.LabelY)
h.text = append(h.text, newLabel)
anim, _ := h.asset.LoadAnimation(d2resource.HelpYellowBullet, d2resource.PaletteSky)
newDot, _ := h.uiManager.NewSprite(anim)
newDot, _ := h.uiManager.NewSprite(d2resource.HelpYellowBullet, d2resource.PaletteSky)
_ = newDot.SetCurrentFrame(0)
newDot.SetPosition(c.DotX, c.DotY+14)
h.frames = append(h.frames, newDot)
@ -370,8 +366,7 @@ func (h *Overlay) createCallout(c callout) {
}
h.lines = append(h.lines, l)
anim, _ := h.asset.LoadAnimation(d2resource.HelpWhiteBullet, d2resource.PaletteSky)
newDot, _ := h.uiManager.NewSprite(anim)
newDot, _ := h.uiManager.NewSprite(d2resource.HelpWhiteBullet, d2resource.PaletteSky)
_ = newDot.SetCurrentFrame(0)
newDot.SetPosition(c.DotX, c.DotY)
h.frames = append(h.frames, newDot)

View File

@ -78,10 +78,8 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName
// Load the data for the hero status panel
func (s *HeroStatsPanel) Load() {
animation, _ := s.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
s.frame, _ = s.uiManager.NewSprite(animation)
animation, _ = s.asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
s.panel, _ = s.uiManager.NewSprite(animation)
s.frame, _ = s.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
s.panel, _ = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
s.initStatValueLabels()
}

View File

@ -71,11 +71,9 @@ func (g *Inventory) Close() {
// Load the resources required by the inventory
func (g *Inventory) Load() {
animation, _ := g.asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
g.frame, _ = g.uiManager.NewSprite(animation)
g.frame, _ = g.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
animation, _ = g.asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
g.panel, _ = g.uiManager.NewSprite(animation)
g.panel, _ = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
items := []InventoryItem{
diablo2item.NewItem("kit", "Crimson", "of the Bat", "of Frost").Identify(),
diablo2item.NewItem("rin", "Steel", "of Shock").Identify(),

View File

@ -121,17 +121,8 @@ func (g *ItemGrid) loadItem(item InventoryItem) {
var itemSprite *d2ui.Sprite
// TODO: Put the pattern into D2Shared
animation, err := g.asset.LoadAnimation(
fmt.Sprintf("/data/global/items/inv%s.dc6", item.GetItemCode()),
d2resource.PaletteSky,
)
if err != nil {
log.Printf("failed to load sprite for item (%s): %v", item.GetItemCode(), err)
return
}
itemSprite, err = g.uiManager.NewSprite(animation)
imgPath := fmt.Sprintf("/data/global/items/inv%s.dc6", item.GetItemCode())
itemSprite, err := g.uiManager.NewSprite(imgPath, d2resource.PaletteSky)
if err != nil {
log.Printf("Failed to load sprite, error: " + err.Error())
}

View File

@ -23,11 +23,9 @@ func newMiniPanel(asset *d2asset.AssetManager, uiManager *d2ui.UIManager, isSing
miniPanelContainerPath = d2resource.MinipanelSmall
}
animation, _ := asset.LoadAnimation(miniPanelContainerPath, d2resource.PaletteSky)
containerSprite, _ := uiManager.NewSprite(animation)
containerSprite, _ := uiManager.NewSprite(miniPanelContainerPath, d2resource.PaletteSky)
animation, _ = asset.LoadAnimation(d2resource.MinipanelButton, d2resource.PaletteSky)
buttonSprite, _ := uiManager.NewSprite(animation)
buttonSprite, _ := uiManager.NewSprite(d2resource.MinipanelButton, d2resource.PaletteSky)
rectangle := d2geom.Rectangle{Left: 325, Top: 526, Width: 156, Height: 26}

View File

@ -35,7 +35,7 @@ func main() {
panic(err)
}
asset, err := d2asset.NewAssetManager(renderer, d2config.Config, nil)
asset, err := d2asset.NewAssetManager(d2config.Config)
if err != nil {
panic(err)
}