mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-27 13:46:00 -04:00
parent
1b03e691b9
commit
0ee937f01b
@ -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
|
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.
|
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
|
## Screenshots
|
||||||
|
|
||||||
![Main Menu](docs/MainMenuSS.png)
|
![Main Menu](docs/MainMenuSS.png)
|
||||||
|
@ -3,8 +3,11 @@ package d2asset
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2helper"
|
"github.com/OpenDiablo2/D2Shared/d2helper"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
|
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
|
||||||
|
|
||||||
@ -37,6 +40,7 @@ type Animation struct {
|
|||||||
frameIndex int
|
frameIndex int
|
||||||
directionIndex int
|
directionIndex int
|
||||||
lastFrameTime float64
|
lastFrameTime float64
|
||||||
|
playedCount int
|
||||||
|
|
||||||
compositeMode ebiten.CompositeMode
|
compositeMode ebiten.CompositeMode
|
||||||
colorMod color.Color
|
colorMod color.Color
|
||||||
@ -46,19 +50,81 @@ type Animation struct {
|
|||||||
playLoop bool
|
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) {
|
func createAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) {
|
||||||
animation := &Animation{
|
animation := &Animation{
|
||||||
playLength: 1.0,
|
playLength: 1.0,
|
||||||
playLoop: true,
|
playLoop: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
for frameIndex, frame := range dc6.Frames {
|
for frameIndex, dc6Frame := range dc6.Frames {
|
||||||
image, err := ebiten.NewImage(int(frame.Width), int(frame.Height), ebiten.FilterNearest)
|
image, err := ebiten.NewImage(int(dc6Frame.Width), int(dc6Frame.Height), ebiten.FilterNearest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := image.ReplacePixels(frame.ColorData()); err != nil {
|
if err := image.ReplacePixels(dc6Frame.ColorData()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,10 +135,10 @@ func createAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) {
|
|||||||
|
|
||||||
direction := animation.directions[directionIndex]
|
direction := animation.directions[directionIndex]
|
||||||
direction.frames = append(direction.frames, &animationFrame{
|
direction.frames = append(direction.frames, &animationFrame{
|
||||||
width: int(frame.Width),
|
width: int(dc6Frame.Width),
|
||||||
height: int(frame.Height),
|
height: int(dc6Frame.Height),
|
||||||
offsetX: int(frame.OffsetX),
|
offsetX: int(dc6Frame.OffsetX),
|
||||||
offsetY: int(frame.OffsetY),
|
offsetY: int(dc6Frame.OffsetY),
|
||||||
image: image,
|
image: image,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -101,6 +167,7 @@ func (a *Animation) Advance(elapsed float64) error {
|
|||||||
case playModeForward:
|
case playModeForward:
|
||||||
a.frameIndex++
|
a.frameIndex++
|
||||||
if a.frameIndex >= frameCount {
|
if a.frameIndex >= frameCount {
|
||||||
|
a.playedCount++
|
||||||
if a.playLoop {
|
if a.playLoop {
|
||||||
a.frameIndex = 0
|
a.frameIndex = 0
|
||||||
} else {
|
} else {
|
||||||
@ -111,6 +178,7 @@ func (a *Animation) Advance(elapsed float64) error {
|
|||||||
case playModeBackward:
|
case playModeBackward:
|
||||||
a.frameIndex--
|
a.frameIndex--
|
||||||
if a.frameIndex < 0 {
|
if a.frameIndex < 0 {
|
||||||
|
a.playedCount++
|
||||||
if a.playLoop {
|
if a.playLoop {
|
||||||
a.frameIndex = frameCount - 1
|
a.frameIndex = frameCount - 1
|
||||||
} else {
|
} else {
|
||||||
@ -233,6 +301,10 @@ func (a *Animation) SetPlayLoop(loop bool) {
|
|||||||
a.playLoop = true
|
a.playLoop = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Animation) SetPlaySpeed(playSpeed float64) {
|
||||||
|
a.SetPlayLength(playSpeed * float64(a.GetFrameCount()))
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Animation) SetPlayLength(playLength float64) {
|
func (a *Animation) SetPlayLength(playLength float64) {
|
||||||
a.playLength = playLength
|
a.playLength = playLength
|
||||||
a.lastFrameTime = 0
|
a.lastFrameTime = 0
|
||||||
@ -246,6 +318,14 @@ func (a *Animation) SetColorMod(color color.Color) {
|
|||||||
a.colorMod = 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) {
|
func (a *Animation) SetBlend(blend bool) {
|
||||||
if blend {
|
if blend {
|
||||||
a.compositeMode = ebiten.CompositeModeLighter
|
a.compositeMode = ebiten.CompositeModeLighter
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package d2asset
|
package d2asset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type animationManager struct {
|
type animationManager struct {
|
||||||
cache *cache
|
cache *cache
|
||||||
}
|
}
|
||||||
@ -8,22 +15,47 @@ func createAnimationManager() *animationManager {
|
|||||||
return &animationManager{cache: createCache(AnimationBudget)}
|
return &animationManager{cache: createCache(AnimationBudget)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *animationManager) loadAnimation(animationPath, palettePath string) (*Animation, error) {
|
func (sm *animationManager) loadAnimation(animationPath, palettePath string, transparency int) (*Animation, error) {
|
||||||
cachePath := animationPath + palettePath
|
cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, transparency)
|
||||||
if animation, found := sm.cache.retrieve(cachePath); found {
|
if animation, found := sm.cache.retrieve(cachePath); found {
|
||||||
return animation.(*Animation).clone(), nil
|
return animation.(*Animation).clone(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dc6, err := loadDC6(animationPath, palettePath)
|
var animation *Animation
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
animation, err := createAnimationFromDC6(dc6)
|
return animation, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sm.cache.insert(cachePath, animation.clone(), 1)
|
|
||||||
return animation, err
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ const (
|
|||||||
|
|
||||||
// In counts
|
// In counts
|
||||||
PaletteBudget = 64
|
PaletteBudget = 64
|
||||||
PaperdollBudget = 64
|
|
||||||
AnimationBudget = 64
|
AnimationBudget = 64
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,7 +30,6 @@ type assetManager struct {
|
|||||||
archiveManager *archiveManager
|
archiveManager *archiveManager
|
||||||
fileManager *fileManager
|
fileManager *fileManager
|
||||||
paletteManager *paletteManager
|
paletteManager *paletteManager
|
||||||
paperdollManager *paperdollManager
|
|
||||||
animationManager *animationManager
|
animationManager *animationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +44,6 @@ func Initialize(config *d2corecommon.Configuration) error {
|
|||||||
archiveManager = createArchiveManager(config)
|
archiveManager = createArchiveManager(config)
|
||||||
fileManager = createFileManager(config, archiveManager)
|
fileManager = createFileManager(config, archiveManager)
|
||||||
paletteManager = createPaletteManager()
|
paletteManager = createPaletteManager()
|
||||||
paperdollManager = createPaperdollManager()
|
|
||||||
animationManager = createAnimationManager()
|
animationManager = createAnimationManager()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,7 +51,6 @@ func Initialize(config *d2corecommon.Configuration) error {
|
|||||||
archiveManager,
|
archiveManager,
|
||||||
fileManager,
|
fileManager,
|
||||||
paletteManager,
|
paletteManager,
|
||||||
paperdollManager,
|
|
||||||
animationManager,
|
animationManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,29 +74,19 @@ func LoadFile(filePath string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadAnimation(animationPath, palettePath string) (*Animation, 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 {
|
if singleton == nil {
|
||||||
return nil, ErrNoInit
|
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) {
|
func LoadComposite(object *d2datadict.ObjectLookupRecord, palettePath string) (*Composite, error) {
|
||||||
if singleton == nil {
|
return createComposite(object, palettePath), 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 loadPalette(palettePath string) (*d2datadict.PaletteRec, error) {
|
func loadPalette(palettePath string) (*d2datadict.PaletteRec, error) {
|
||||||
@ -130,7 +116,7 @@ func loadDC6(dc6Path, palettePath string) (*d2dc6.DC6File, error) {
|
|||||||
return &dc6, nil
|
return &dc6, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadDCC(dccPath string) (*d2dcc.DCC, error) {
|
func loadDCC(dccPath string) (*d2dcc.DCC, error) {
|
||||||
dccData, err := LoadFile(dccPath)
|
dccData, err := LoadFile(dccPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -139,7 +125,7 @@ func LoadDCC(dccPath string) (*d2dcc.DCC, error) {
|
|||||||
return d2dcc.LoadDCC(dccData)
|
return d2dcc.LoadDCC(dccData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadCOF(cofPath string) (*d2cof.COF, error) {
|
func loadCOF(cofPath string) (*d2cof.COF, error) {
|
||||||
cofData, err := LoadFile(cofPath)
|
cofData, err := LoadFile(cofPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
259
d2asset/composite.go
Normal file
259
d2asset/composite.go
Normal 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")
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -45,7 +45,10 @@ func (v *Manager) PlayBGM(song string) {
|
|||||||
log.Panic(err)
|
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))
|
d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -24,7 +24,12 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *Soun
|
|||||||
} else {
|
} else {
|
||||||
soundFile = sfx
|
soundFile = sfx
|
||||||
}
|
}
|
||||||
audioData := d2asset.MustLoadFile(soundFile)
|
|
||||||
|
audioData, err := d2asset.LoadFile(soundFile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
d, err := wav.Decode(context, audio.BytesReadSeekCloser(audioData))
|
d, err := wav.Decode(context, audio.BytesReadSeekCloser(audioData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -21,7 +21,10 @@ func CreateBlizzardIntro(sceneProvider d2coreinterface.SceneProvider) *BlizzardI
|
|||||||
func (v *BlizzardIntro) Load() []func() {
|
func (v *BlizzardIntro) Load() []func() {
|
||||||
return []func(){
|
return []func(){
|
||||||
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)
|
v.videoDecoder = d2video.CreateBinkDecoder(videoBytes)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,12 @@ func (v *Credits) Load() []func() {
|
|||||||
v.uiManager.AddWidget(&v.exitButton)
|
v.uiManager.AddWidget(&v.exitButton)
|
||||||
},
|
},
|
||||||
func() {
|
func() {
|
||||||
fileData, _ := dh.Utf16BytesToString(d2asset.MustLoadFile(d2resource.CreditsText)[2:])
|
fileData, err := d2asset.LoadFile(d2resource.CreditsText)
|
||||||
v.creditsText = strings.Split(fileData, "\r\n")
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
creditData, _ := dh.Utf16BytesToString(fileData[2:])
|
||||||
|
v.creditsText = strings.Split(creditData, "\r\n")
|
||||||
for i := range v.creditsText {
|
for i := range v.creditsText {
|
||||||
v.creditsText[i] = strings.Trim(v.creditsText[i], " ")
|
v.creditsText[i] = strings.Trim(v.creditsText[i], " ")
|
||||||
}
|
}
|
||||||
|
@ -2,41 +2,42 @@ package d2core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hero struct {
|
type Hero struct {
|
||||||
AnimatedEntity d2render.AnimatedEntity
|
AnimatedEntity *d2render.AnimatedEntity
|
||||||
Equipment CharacterEquipment
|
Equipment CharacterEquipment
|
||||||
mode d2enum.AnimationMode
|
mode d2enum.AnimationMode
|
||||||
direction int
|
direction int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment CharacterEquipment) *Hero {
|
func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment CharacterEquipment) *Hero {
|
||||||
result := &Hero{
|
object := &d2datadict.ObjectLookupRecord{
|
||||||
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, &d2datadict.ObjectLookupRecord{
|
Mode: d2enum.AnimationModePlayerNeutral.String(),
|
||||||
Mode: d2enum.AnimationModePlayerNeutral.String(),
|
Base: "/data/global/chars",
|
||||||
Base: "/data/global/chars",
|
Token: heroType.GetToken(),
|
||||||
Token: heroType.GetToken(),
|
Class: equipment.RightHand.GetWeaponClass(),
|
||||||
Class: equipment.RightHand.GetWeaponClass(),
|
SH: equipment.Shield.GetItemCode(),
|
||||||
SH: equipment.Shield.GetItemCode(),
|
// TODO: Offhand class?
|
||||||
// TODO: Offhand class?
|
HD: equipment.Head.GetArmorClass(),
|
||||||
HD: equipment.Head.GetArmorClass(),
|
TR: equipment.Torso.GetArmorClass(),
|
||||||
TR: equipment.Torso.GetArmorClass(),
|
LG: equipment.Legs.GetArmorClass(),
|
||||||
LG: equipment.Legs.GetArmorClass(),
|
RA: equipment.RightArm.GetArmorClass(),
|
||||||
RA: equipment.RightArm.GetArmorClass(),
|
LA: equipment.LeftArm.GetArmorClass(),
|
||||||
LA: equipment.LeftArm.GetArmorClass(),
|
RH: equipment.RightHand.GetItemCode(),
|
||||||
RH: equipment.RightHand.GetItemCode(),
|
LH: equipment.LeftHand.GetItemCode(),
|
||||||
LH: equipment.LeftHand.GetItemCode(),
|
|
||||||
},
|
|
||||||
d2enum.Units,
|
|
||||||
),
|
|
||||||
Equipment: equipment,
|
|
||||||
mode: d2enum.AnimationModePlayerTownNeutral,
|
|
||||||
direction: direction,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
result.AnimatedEntity.SetMode(result.mode.String(), equipment.RightHand.GetWeaponClass(), direction)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -47,6 +48,8 @@ func (v *Hero) Advance(tickTime float64) {
|
|||||||
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
|
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
|
||||||
v.AnimatedEntity.Step(tickTime)
|
v.AnimatedEntity.Step(tickTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.AnimatedEntity.Advance(tickTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Hero) Render(target *ebiten.Image, offsetX, offsetY int) {
|
func (v *Hero) Render(target *ebiten.Image, offsetX, offsetY int) {
|
||||||
|
@ -2,24 +2,26 @@ package d2core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
"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/d2datadict"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NPC struct {
|
type NPC struct {
|
||||||
AnimatedEntity d2render.AnimatedEntity
|
AnimatedEntity *d2render.AnimatedEntity
|
||||||
HasPaths bool
|
HasPaths bool
|
||||||
Paths []d2common.Path
|
Paths []d2common.Path
|
||||||
path int
|
path int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, direction int) *NPC {
|
func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, direction int) *NPC {
|
||||||
result := &NPC{
|
entity, err := d2render.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits)
|
||||||
AnimatedEntity: d2render.CreateAnimatedEntity(x, y, object, d2enum.Units),
|
if err != nil {
|
||||||
HasPaths: false,
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result := &NPC{AnimatedEntity: entity, HasPaths: false}
|
||||||
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
|
result.AnimatedEntity.SetMode(object.Mode, object.Class, direction)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -68,4 +70,6 @@ func (v *NPC) Advance(tickTime float64) {
|
|||||||
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
|
v.AnimatedEntity.LocationY != v.AnimatedEntity.TargetY {
|
||||||
v.AnimatedEntity.Step(tickTime)
|
v.AnimatedEntity.Step(tickTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.AnimatedEntity.Advance(tickTime)
|
||||||
}
|
}
|
||||||
|
@ -1,326 +1,83 @@
|
|||||||
package d2render
|
package d2render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"log"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
"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/d2datadict"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
|
|
||||||
"github.com/OpenDiablo2/D2Shared/d2helper"
|
"github.com/OpenDiablo2/D2Shared/d2helper"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"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
|
// AnimatedEntity represents an entity on the map that can be animated
|
||||||
type AnimatedEntity struct {
|
type AnimatedEntity struct {
|
||||||
LocationX float64
|
LocationX float64
|
||||||
LocationY float64
|
LocationY float64
|
||||||
TileX, TileY int // Coordinates of the tile the unit is within
|
TileX, TileY int // Coordinates of the tile the unit is within
|
||||||
subcellX, subcellY float64 // Subcell coordinates within the current tile
|
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
|
animationMode string
|
||||||
weaponClass string
|
weaponClass string
|
||||||
lastFrameTime float64
|
|
||||||
framesToAnimate int
|
|
||||||
animationSpeed float64
|
|
||||||
direction int
|
direction int
|
||||||
currentFrame int
|
|
||||||
offsetX, offsetY int32
|
offsetX, offsetY int32
|
||||||
object *d2datadict.ObjectLookupRecord
|
|
||||||
layerCache []LayerCacheEntry
|
|
||||||
drawOrder [][]d2enum.CompositeType
|
|
||||||
TargetX float64
|
TargetX float64
|
||||||
TargetY float64
|
TargetY float64
|
||||||
action int32
|
action int32
|
||||||
repetitions int32
|
repetitions int
|
||||||
|
|
||||||
|
composite *d2asset.Composite
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAnimatedEntity creates an instance of AnimatedEntity
|
// CreateAnimatedEntity creates an instance of AnimatedEntity
|
||||||
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, palette d2enum.PaletteType) AnimatedEntity {
|
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, palettePath string) (*AnimatedEntity, error) {
|
||||||
result := AnimatedEntity{
|
composite, err := d2asset.LoadComposite(object, palettePath)
|
||||||
base: object.Base,
|
if err != nil {
|
||||||
token: object.Token,
|
return nil, err
|
||||||
object: object,
|
|
||||||
palette: palette,
|
|
||||||
layerCache: make([]LayerCacheEntry, d2enum.CompositeTypeMax),
|
|
||||||
}
|
}
|
||||||
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)
|
entity := &AnimatedEntity{composite: composite}
|
||||||
result.TileY = int(result.LocationY / 5)
|
entity.LocationX = float64(x)
|
||||||
result.subcellX = 1 + math.Mod(result.LocationX, 5)
|
entity.LocationY = float64(y)
|
||||||
result.subcellY = 1 + math.Mod(result.LocationY, 5)
|
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
|
// SetMode changes the graphical mode of this animated entity
|
||||||
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int) {
|
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int) error {
|
||||||
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
|
|
||||||
v.animationMode = animationMode
|
v.animationMode = animationMode
|
||||||
v.weaponClass = weaponClass
|
|
||||||
v.direction = direction
|
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)
|
err := v.composite.SetMode(animationMode, weaponClass, direction)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
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")
|
err = v.composite.SetMode(animationMode, "HTH", direction)
|
||||||
result, err = d2asset.LoadDCC(dccPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an npc has a path to pause at each location.
|
// If an npc has a path to pause at each location.
|
||||||
// Waits for animation to end and all repetitions to be exhausted.
|
// Waits for animation to end and all repetitions to be exhausted.
|
||||||
func (v AnimatedEntity) Wait() bool {
|
func (v AnimatedEntity) Wait() bool {
|
||||||
// currentFrame might skip the final frame if framesToAdd doesn't match up,
|
return v.composite.GetPlayedCount() > v.repetitions
|
||||||
// bail immediately after the last repetition if that happens.
|
|
||||||
return v.repetitions < 0 || (v.repetitions == 0 && v.currentFrame >= v.framesToAnimate-1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render draws this animated entity onto the target
|
// Render draws this animated entity onto the target
|
||||||
func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) {
|
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
|
localX := (v.subcellX - v.subcellY) * 16
|
||||||
localY := ((v.subcellX + v.subcellY) * 8) - 5
|
localY := ((v.subcellX + v.subcellY) * 8) - 5
|
||||||
|
v.composite.Render(
|
||||||
if v.drawOrder == nil {
|
target,
|
||||||
return
|
int(v.offsetX)+offsetX+int(localX),
|
||||||
}
|
int(v.offsetY)+offsetY+int(localY),
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v AnimatedEntity) GetDirection() int {
|
func (v AnimatedEntity) GetDirection() int {
|
||||||
@ -365,8 +122,7 @@ func (v *AnimatedEntity) Step(tickTime float64) {
|
|||||||
v.TileY = int(v.LocationY / 5)
|
v.TileY = int(v.LocationY / 5)
|
||||||
|
|
||||||
if v.LocationX == v.TargetX && v.LocationY == v.TargetY {
|
if v.LocationX == v.TargetX && v.LocationY == v.TargetY {
|
||||||
|
v.repetitions = 3 + rand.Intn(5)
|
||||||
v.repetitions = 3 + rand.Int31n(5)
|
|
||||||
newAnimationMode := d2enum.AnimationModeObjectNeutral
|
newAnimationMode := d2enum.AnimationModeObjectNeutral
|
||||||
// TODO: Figure out what 1-3 are for, 4 is correct.
|
// TODO: Figure out what 1-3 are for, 4 is correct.
|
||||||
switch v.action {
|
switch v.action {
|
||||||
@ -381,10 +137,10 @@ func (v *AnimatedEntity) Step(tickTime float64) {
|
|||||||
v.repetitions = 0
|
v.repetitions = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.composite.ResetPlayedCount()
|
||||||
if v.animationMode != newAnimationMode.String() {
|
if v.animationMode != newAnimationMode.String() {
|
||||||
v.SetMode(newAnimationMode.String(), v.weaponClass, v.direction)
|
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()
|
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 {
|
if newDirection != v.GetDirection() || newAnimationMode != v.animationMode {
|
||||||
v.SetMode(newAnimationMode, v.weaponClass, newDirection)
|
v.SetMode(newAnimationMode, v.weaponClass, newDirection)
|
||||||
}
|
}
|
||||||
@ -428,8 +184,8 @@ func angleToDirection(angle float64, numberOfDirections int) int {
|
|||||||
return newDirection
|
return newDirection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *AnimatedEntity) Advance(tickTime float64) {
|
func (v *AnimatedEntity) Advance(elapsed float64) {
|
||||||
|
v.composite.Advance(elapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *AnimatedEntity) GetPosition() (float64, float64) {
|
func (v *AnimatedEntity) GetPosition() (float64, float64) {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package d2render
|
package d2render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAngleToDirection_16Directions(t *testing.T) {
|
func TestAngleToDirection_16Directions(t *testing.T) {
|
||||||
@ -61,7 +62,6 @@ func TestAngleToDirection_1Direction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestAngleToDirection_0Directions(t *testing.T) {
|
func TestAngleToDirection_0Directions(t *testing.T) {
|
||||||
angle := 0.0
|
angle := 0.0
|
||||||
for i := 0; i < 120; i++ {
|
for i := 0; i < 120; i++ {
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2data/d2ds1"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2ds1"
|
||||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dt1"
|
"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 {
|
for _, levelTypeDt1 := range region.levelType.Files {
|
||||||
if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" {
|
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...)
|
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.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{
|
region.tileRect = d2common.Rectangle{
|
||||||
Left: tileOffsetX,
|
Left: tileOffsetX,
|
||||||
Top: tileOffsetY,
|
Top: tileOffsetY,
|
||||||
@ -133,9 +143,12 @@ func (mr *MapRegion) loadEntities() []MapEntity {
|
|||||||
}
|
}
|
||||||
case d2datadict.ObjectTypeItem:
|
case d2datadict.ObjectTypeItem:
|
||||||
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
|
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)
|
entity.SetMode(object.Lookup.Mode, object.Lookup.Class, 0)
|
||||||
entities = append(entities, &entity)
|
entities = append(entities, entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// bug: performance issue when using CJK fonts, because ten thousand frames will be rendered PER font
|
||||||
result.fontSprite, _ = d2render.LoadSprite(font+".dc6", palettePath)
|
result.fontSprite, _ = d2render.LoadSprite(font+".dc6", palettePath)
|
||||||
woo := "Woo!\x01"
|
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 {
|
if string(fontData[0:5]) != woo {
|
||||||
panic("No woo :(")
|
panic("No woo :(")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user