1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-06 18:27:20 -05:00
OpenDiablo2/d2render/sprite.go

227 lines
5.6 KiB
Go
Raw Normal View History

2019-11-10 03:36:53 -05:00
package d2render
2019-10-24 09:31:59 -04:00
import (
"image/color"
"log"
"sync"
2019-10-24 09:31:59 -04:00
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2corehelper"
2019-11-10 03:36:53 -05:00
2019-10-24 09:31:59 -04:00
"github.com/hajimehoshi/ebiten"
)
// Sprite represents a type of object in D2 that is comprised of one or more frames and directions
2019-10-24 09:31:59 -04:00
type Sprite struct {
Directions uint32
FramesPerDirection uint32
Frames []SpriteFrame
2019-10-27 02:58:37 -04:00
SpecialFrameTime int
2019-11-13 00:31:52 -05:00
AnimateBackwards bool // Because why not
2019-10-27 02:58:37 -04:00
StopOnLastFrame bool
2019-10-24 09:31:59 -04:00
X, Y int
2019-11-13 00:31:52 -05:00
Frame, Direction int16
2019-10-24 09:31:59 -04:00
Blend bool
LastFrameTime float64
2019-10-24 09:31:59 -04:00
Animate bool
ColorMod color.Color
2019-11-10 10:44:13 -05:00
valid bool
2019-10-24 09:31:59 -04:00
}
// SpriteFrame represents a single frame of a sprite
2019-10-24 09:31:59 -04:00
type SpriteFrame struct {
Flip uint32
Width uint32
Height uint32
OffsetX int32
OffsetY int32
Unknown uint32
NextBlock uint32
Length uint32
FrameData []byte
2019-10-24 09:31:59 -04:00
Image *ebiten.Image
}
func CreateSpriteFromDC6(dc6 d2dc6.DC6File) Sprite {
2019-11-10 10:44:13 -05:00
result := Sprite{
2019-10-24 09:31:59 -04:00
X: 50,
Y: 50,
Frame: 0,
Direction: 0,
Blend: false,
ColorMod: nil,
Directions: dc6.Directions,
FramesPerDirection: dc6.FramesPerDirection,
2019-10-24 09:31:59 -04:00
Animate: false,
LastFrameTime: d2helper.Now(),
2019-10-27 02:58:37 -04:00
SpecialFrameTime: -1,
StopOnLastFrame: false,
valid: true,
2019-11-13 00:31:52 -05:00
AnimateBackwards: false,
2019-10-24 09:31:59 -04:00
}
result.Frames = make([]SpriteFrame, len(dc6.Frames))
2019-11-09 01:13:49 -05:00
wg := sync.WaitGroup{}
wg.Add(len(dc6.Frames))
for i, f := range dc6.Frames {
go func(i int, frame *d2dc6.DC6Frame) {
defer wg.Done()
image, err := ebiten.NewImage(int(frame.Width), int(frame.Height), ebiten.FilterNearest)
if err != nil {
log.Printf("failed to create new image: %v", err)
}
if err := image.ReplacePixels(frame.ColorData()); err != nil {
log.Printf("failed to replace pixels: %v", err)
}
result.Frames[i] = SpriteFrame{
Flip: frame.Flipped,
Width: frame.Width,
Height: frame.Height,
OffsetX: frame.OffsetX,
OffsetY: frame.OffsetY,
Unknown: frame.Unknown,
NextBlock: frame.NextBlock,
Length: frame.Length,
Image: image,
}
}(i, f)
2019-10-24 09:31:59 -04:00
}
wg.Wait()
2019-10-24 09:31:59 -04:00
return result
}
2019-11-10 10:44:13 -05:00
func (v Sprite) IsValid() bool {
return v.valid
}
// GetSize returns the size of the sprite
2019-11-10 10:44:13 -05:00
func (v Sprite) GetSize() (uint32, uint32) {
2019-10-24 09:31:59 -04:00
frame := v.Frames[uint32(v.Frame)+(uint32(v.Direction)*v.FramesPerDirection)]
return frame.Width, frame.Height
}
func (v *Sprite) updateAnimation() {
if !v.Animate {
return
}
var timePerFrame float64
2019-10-27 02:58:37 -04:00
if v.SpecialFrameTime >= 0 {
timePerFrame = (float64(v.SpecialFrameTime) / float64(len(v.Frames))) / 1000.0
2019-10-27 02:58:37 -04:00
} else {
timePerFrame = 1.0 / float64(len(v.Frames))
2019-10-24 09:31:59 -04:00
}
now := d2helper.Now()
for v.LastFrameTime+timePerFrame < now {
v.LastFrameTime += timePerFrame
2019-11-13 00:31:52 -05:00
if !v.AnimateBackwards {
v.Frame++
if v.Frame >= int16(v.FramesPerDirection) {
if v.StopOnLastFrame {
v.Frame = int16(v.FramesPerDirection) - 1
} else {
v.Frame = 0
}
}
continue
2019-11-13 00:31:52 -05:00
}
v.Frame--
if v.Frame < 0 {
2019-10-27 02:58:37 -04:00
if v.StopOnLastFrame {
v.Frame = 0
2019-11-13 00:31:52 -05:00
} else {
v.Frame = int16(v.FramesPerDirection) - 1
2019-10-27 02:58:37 -04:00
}
}
2019-10-24 09:31:59 -04:00
}
}
2019-10-27 02:58:37 -04:00
func (v *Sprite) ResetAnimation() {
v.LastFrameTime = d2helper.Now()
2019-10-27 02:58:37 -04:00
v.Frame = 0
}
2019-11-10 10:44:13 -05:00
func (v Sprite) OnLastFrame() bool {
2019-11-13 00:31:52 -05:00
return v.Frame == int16(v.FramesPerDirection-1)
2019-10-27 02:58:37 -04:00
}
2019-10-25 23:41:54 -04:00
// GetFrameSize returns the size of the specific frame
2019-11-10 10:44:13 -05:00
func (v Sprite) GetFrameSize(frame int) (width, height uint32) {
2019-10-25 23:41:54 -04:00
width = v.Frames[frame].Width
height = v.Frames[frame].Height
return
}
// GetTotalFrames returns the number of frames in this sprite (for all directions)
2019-11-10 10:44:13 -05:00
func (v Sprite) GetTotalFrames() int {
2019-10-25 23:41:54 -04:00
return len(v.Frames)
}
2019-10-24 09:31:59 -04:00
// Draw draws the sprite onto the target
func (v *Sprite) Draw(target *ebiten.Image) {
v.updateAnimation()
opts := &ebiten.DrawImageOptions{}
frame := v.Frames[uint32(v.Frame)+(uint32(v.Direction)*v.FramesPerDirection)]
opts.GeoM.Translate(
float64(int32(v.X)+frame.OffsetX),
2019-11-10 20:44:51 -05:00
float64(int32(v.Y)-int32(frame.Height)+frame.OffsetY),
2019-10-24 09:31:59 -04:00
)
if v.Blend {
2019-10-26 00:06:54 -04:00
opts.CompositeMode = ebiten.CompositeModeLighter
} else {
opts.CompositeMode = ebiten.CompositeModeSourceOver
2019-10-24 09:31:59 -04:00
}
if v.ColorMod != nil {
opts.ColorM = d2corehelper.ColorToColorM(v.ColorMod)
}
if err := target.DrawImage(frame.Image, opts); err != nil {
log.Panic(err.Error())
}
2019-10-24 09:31:59 -04:00
}
// DrawSegments draws the sprite via a grid of segments
2019-10-24 09:31:59 -04:00
func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset int) {
v.updateAnimation()
yOffset := int32(0)
for y := 0; y < ySegments; y++ {
xOffset := int32(0)
biggestYOffset := int32(0)
for x := 0; x < xSegments; x++ {
frame := v.Frames[uint32(x+(y*xSegments)+(offset*xSegments*ySegments))]
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(
float64(int32(v.X)+frame.OffsetX+xOffset),
float64(int32(v.Y)+frame.OffsetY+yOffset),
)
if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter
} else {
opts.CompositeMode = ebiten.CompositeModeSourceOver
}
if v.ColorMod != nil {
opts.ColorM = d2corehelper.ColorToColorM(v.ColorMod)
}
if err := target.DrawImage(frame.Image, opts); err != nil {
log.Panic(err.Error())
}
2019-10-24 09:31:59 -04:00
xOffset += int32(frame.Width)
2019-11-10 03:36:53 -05:00
biggestYOffset = d2helper.MaxInt32(biggestYOffset, int32(frame.Height))
2019-10-24 09:31:59 -04:00
}
yOffset += biggestYOffset
}
}
// MoveTo moves the sprite to the specified coordinates
func (v *Sprite) MoveTo(x, y int) {
v.X = x
v.Y = y
}
// GetLocation returns the location of the sprite
2019-11-10 10:44:13 -05:00
func (v Sprite) GetLocation() (int, int) {
return v.X, v.Y
}