2019-11-06 22:12:15 -05:00
|
|
|
package common
|
2019-11-06 18:25:19 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2019-11-06 22:12:15 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/palettedefs"
|
2019-11-06 18:25:19 -05:00
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten"
|
|
|
|
)
|
|
|
|
|
2019-11-06 22:12:15 -05:00
|
|
|
// AnimatedEntity represents an entity on the map that can be animated
|
2019-11-06 18:25:19 -05:00
|
|
|
type AnimatedEntity struct {
|
2019-11-06 22:12:15 -05:00
|
|
|
// LocationX represents the tile X position of the entity
|
|
|
|
LocationX float64
|
|
|
|
// LocationY represents the tile Y position of the entity
|
|
|
|
LocationY float64
|
2019-11-06 18:25:19 -05:00
|
|
|
dcc *DCC
|
|
|
|
cof *Cof
|
2019-11-06 22:12:15 -05:00
|
|
|
palette palettedefs.PaletteType
|
2019-11-06 18:25:19 -05:00
|
|
|
base string
|
|
|
|
token string
|
|
|
|
tr string
|
|
|
|
animationMode string
|
|
|
|
weaponClass string
|
|
|
|
lastFrameTime time.Time
|
|
|
|
framesToAnimate int
|
|
|
|
animationSpeed int
|
|
|
|
direction int
|
|
|
|
currentFrame int
|
|
|
|
frames []*ebiten.Image
|
|
|
|
frameLocations []Rectangle
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:12:15 -05:00
|
|
|
// CreateAnimatedEntity creates an instance of AnimatedEntity
|
|
|
|
func CreateAnimatedEntity(base, token, tr string, palette palettedefs.PaletteType) *AnimatedEntity {
|
2019-11-06 18:25:19 -05:00
|
|
|
result := &AnimatedEntity{
|
|
|
|
base: base,
|
|
|
|
token: token,
|
|
|
|
tr: tr,
|
|
|
|
palette: palette,
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:12:15 -05:00
|
|
|
// DirectionLookup is used to decode the direction offset indexes
|
2019-11-06 18:25:19 -05:00
|
|
|
var DirectionLookup = []int{3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14}
|
|
|
|
|
2019-11-06 22:12:15 -05:00
|
|
|
// SetMode changes the graphical mode of this animated entity
|
2019-11-06 18:25:19 -05:00
|
|
|
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int, provider FileProvider) {
|
|
|
|
dccPath := fmt.Sprintf("%s/%s/tr/%str%s%s%s.dcc", v.base, v.token, v.token, v.tr, animationMode, weaponClass)
|
|
|
|
v.dcc = LoadDCC(dccPath, provider)
|
|
|
|
cofPath := fmt.Sprintf("%s/%s/cof/%s%s%s.cof", v.base, v.token, v.token, animationMode, weaponClass)
|
|
|
|
v.cof = LoadCof(cofPath, provider)
|
|
|
|
v.animationMode = animationMode
|
|
|
|
v.weaponClass = weaponClass
|
|
|
|
v.direction = direction
|
|
|
|
v.cacheFrames()
|
|
|
|
}
|
|
|
|
|
2019-11-06 22:12:15 -05:00
|
|
|
// Render draws this animated entity onto the target
|
2019-11-06 18:25:19 -05:00
|
|
|
func (v *AnimatedEntity) Render(target *ebiten.Image, offsetX, offsetY int) {
|
|
|
|
for v.lastFrameTime.Add(time.Millisecond * time.Duration(v.animationSpeed)).Before(time.Now()) {
|
|
|
|
v.lastFrameTime = v.lastFrameTime.Add(time.Millisecond * time.Duration(v.animationSpeed))
|
|
|
|
v.currentFrame++
|
|
|
|
if v.currentFrame >= v.framesToAnimate {
|
|
|
|
v.currentFrame = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := &ebiten.DrawImageOptions{}
|
|
|
|
opts.GeoM.Translate(float64(v.frameLocations[v.currentFrame].Left+offsetX), float64(v.frameLocations[v.currentFrame].Top+offsetY+40))
|
|
|
|
target.DrawImage(v.frames[v.currentFrame], opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *AnimatedEntity) cacheFrames() {
|
|
|
|
animationData := AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)][v.direction]
|
2019-11-07 17:27:21 -05:00
|
|
|
v.animationSpeed = int(1000.0 / ((float64(animationData.AnimationSpeed) * 25.0) / 256.0))
|
2019-11-06 18:25:19 -05:00
|
|
|
v.framesToAnimate = animationData.FramesPerDirection
|
|
|
|
v.lastFrameTime = time.Now()
|
|
|
|
minX := int32(2147483647)
|
|
|
|
minY := int32(2147483647)
|
|
|
|
maxX := int32(-2147483648)
|
|
|
|
maxY := int32(-2147483648)
|
|
|
|
for _, layer := range v.dcc.Directions {
|
|
|
|
minX = MinInt32(minX, int32(layer.Box.Left))
|
|
|
|
minY = MinInt32(minY, int32(layer.Box.Top))
|
|
|
|
maxX = MaxInt32(maxX, int32(layer.Box.Right()))
|
|
|
|
maxY = MaxInt32(maxY, int32(layer.Box.Bottom()))
|
|
|
|
}
|
|
|
|
frameW := maxX - minX
|
|
|
|
frameH := maxY - minY
|
|
|
|
v.frames = make([]*ebiten.Image, v.framesToAnimate)
|
|
|
|
v.frameLocations = make([]Rectangle, v.framesToAnimate)
|
|
|
|
for frameIndex := range v.frames {
|
|
|
|
v.frames[frameIndex], _ = ebiten.NewImage(int(frameW), int(frameH), ebiten.FilterNearest)
|
|
|
|
priorityBase := (v.direction * animationData.FramesPerDirection * v.cof.NumberOfLayers) + (frameIndex * v.cof.NumberOfLayers)
|
|
|
|
for layerIdx := 0; layerIdx < v.cof.NumberOfLayers; layerIdx++ {
|
|
|
|
comp := v.cof.Priority[priorityBase+layerIdx]
|
|
|
|
if _, found := v.cof.CompositeLayers[comp]; !found {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
direction := v.dcc.Directions[v.direction]
|
|
|
|
frame := direction.Frames[frameIndex]
|
|
|
|
pixelData := make([]byte, 4*frameW*frameH)
|
|
|
|
for y := 0; y < direction.Box.Height; y++ {
|
|
|
|
for x := 0; x < direction.Box.Width; x++ {
|
|
|
|
paletteIndex := frame.PixelData[x+(y*direction.Box.Width)]
|
|
|
|
|
|
|
|
if paletteIndex == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
color := Palettes[v.palette].Colors[paletteIndex]
|
|
|
|
actualX := x + direction.Box.Left - int(minX)
|
|
|
|
actualY := y + direction.Box.Top - int(minY)
|
|
|
|
pixelData[(actualX*4)+(actualY*int(frameW)*4)] = color.R
|
|
|
|
pixelData[(actualX*4)+(actualY*int(frameW)*4)+1] = color.G
|
|
|
|
pixelData[(actualX*4)+(actualY*int(frameW)*4)+2] = color.B
|
|
|
|
pixelData[(actualX*4)+(actualY*int(frameW)*4)+3] = 255
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v.frames[frameIndex].ReplacePixels(pixelData)
|
|
|
|
v.frameLocations[frameIndex] = Rectangle{
|
|
|
|
Left: int(minX),
|
|
|
|
Top: int(minY),
|
|
|
|
Width: int(frameW),
|
|
|
|
Height: int(frameH),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|