1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-10-31 16:27:18 -04:00
OpenDiablo2/d2render/animated_entity.go
2019-11-17 16:06:02 -05:00

344 lines
12 KiB
Go

package d2render
import (
"fmt"
"log"
"math"
"strings"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2cof"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/hajimehoshi/ebiten"
)
var DccLayerNames = []string{"HD", "TR", "LG", "RA", "LA", "RH", "LH", "SH", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"}
// DirectionLookup is used to decode the direction offset indexes
var DirectionLookup = []int{9, 15, 5, 6, 4, 12, 10, 2, 8, 13, 1, 7, 0, 14, 11, 3}
// AnimatedEntity represents an entity on the map that can be animated
type AnimatedEntity struct {
fileProvider d2interface.FileProvider
// LocationX represents the tile X position of the entity
LocationX float64
// LocationY represents the tile Y position of the entity
subcellX, subcellY float64 // Subcell coordinates within the current tile
LocationY float64
dccLayers map[string]d2dcc.DCC
Cof *d2cof.COF
palette d2enum.PaletteType
base string
token string
animationMode string
weaponClass string
lastFrameTime float64
framesToAnimate int
animationSpeed float64
direction int
currentFrame int
offsetX, offsetY int32
frames []*ebiten.Image
//frameLocations []d2common.Rectangle
object *d2datadict.ObjectLookupRecord
}
// CreateAnimatedEntity creates an instance of AnimatedEntity
func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, fileProvider d2interface.FileProvider, palette d2enum.PaletteType) AnimatedEntity {
result := AnimatedEntity{
fileProvider: fileProvider,
base: object.Base,
token: object.Token,
object: object,
palette: palette,
frames: []*ebiten.Image{},
//frameLocations: []d2common.Rectangle{},
}
result.dccLayers = make(map[string]d2dcc.DCC)
result.LocationX = float64(x) / 5
result.LocationY = float64(y) / 5
result.subcellX = 1 + math.Mod(float64(x), 5)
result.subcellY = 1 + math.Mod(float64(y), 5)
return result
}
// SetMode changes the graphical mode of this animated entity
func (v *AnimatedEntity) SetMode(animationMode, weaponClass string, direction int) {
cofPath := fmt.Sprintf("%s/%s/COF/%s%s%s.COF", v.base, v.token, v.token, animationMode, weaponClass)
v.Cof = d2cof.LoadCOF(cofPath, v.fileProvider)
v.animationMode = animationMode
v.weaponClass = weaponClass
v.direction = direction
if v.direction >= v.Cof.NumberOfDirections {
v.direction = v.Cof.NumberOfDirections - 1
}
//v.frames = make(map[string][]*ebiten.Image)
//v.frameLocations = make(map[string][]d2common.Rectangle)
v.dccLayers = make(map[string]d2dcc.DCC)
for _, cofLayer := range v.Cof.CofLayers {
layerName := DccLayerNames[cofLayer.Type]
v.dccLayers[layerName] = v.LoadLayer(layerName, v.fileProvider)
if !v.dccLayers[layerName].IsValid() {
continue
}
}
v.updateFrameCache()
//v.cacheFrames()
}
func (v *AnimatedEntity) LoadLayer(layer string, fileProvider d2interface.FileProvider) d2dcc.DCC {
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 d2dcc.DCC{}
}
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 := d2dcc.LoadDCC(dccPath, fileProvider)
if !result.IsValid() {
dccPath = fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", v.base, v.token, layer, v.token, layer, layerName, v.animationMode, "HTH")
result = d2dcc.LoadDCC(dccPath, fileProvider)
}
return result
}
// Render draws this animated entity onto the target
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
}
}
}
if v.currentFrame < 0 || v.frames == nil || v.currentFrame > len(v.frames) || v.frames[v.currentFrame] == nil {
return
}
localX := (v.subcellX - v.subcellY) * 16
localY := ((v.subcellX + v.subcellY) * 8) - 5
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(float64(v.offsetX)+float64(offsetX)+localX, float64(v.offsetY)+float64(offsetY)+localY)
if err := target.DrawImage(v.frames[v.currentFrame], opts); err != nil {
log.Panic(err.Error())
}
//for idx := 0; idx < v.Cof.NumberOfLayers; idx++ {
// priority := v.Cof.Priority[v.direction][v.currentFrame][idx]
// if int(priority) >= len(DccLayerNames) {
// continue
// }
// frameName := DccLayerNames[priority]
// if v.frames[frameName] == nil {
// continue
// }
//
// // Location within the current tile
// localX := (v.subcellX - v.subcellY) * 16
// localY := ((v.subcellX + v.subcellY) * 8) - 5
//
// // TODO: Transparency op maybe, but it'l murder batch calls
// opts := &ebiten.DrawImageOptions{}
// opts.GeoM.Translate(float64(v.frameLocations[frameName][v.currentFrame].Left+offsetX)+localX,
// float64(v.frameLocations[frameName][v.currentFrame].Top+offsetY)+localY)
// if err := target.DrawImage(v.frames[frameName][v.currentFrame], opts); err != nil {
// log.Panic(err.Error())
// }
//}
}
func (v *AnimatedEntity) updateFrameCache() {
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()
minX := int32(10000)
minY := int32(10000)
maxX := int32(-10000)
maxY := int32(-10000)
for cofLayerIdx := range v.Cof.CofLayers {
layerName := DccLayerNames[v.Cof.CofLayers[cofLayerIdx].Type]
dccLayer := v.dccLayers[layerName]
if !dccLayer.IsValid() {
continue
}
for frameIdx := range dccLayer.Directions[v.direction].Frames {
minX = d2helper.MinInt32(minX, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Left))
minY = d2helper.MinInt32(minY, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Top))
maxX = d2helper.MaxInt32(maxX, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Right()))
maxY = d2helper.MaxInt32(maxY, int32(dccLayer.Directions[v.direction].Frames[frameIdx].Box.Bottom()))
}
}
v.offsetX = minX
v.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
}
v.frames = make([]*ebiten.Image, v.framesToAnimate)
pixels := make([]byte, actualWidth*actualHeight*4)
for animationIdx := 0; animationIdx < v.framesToAnimate; animationIdx++ {
// This should be faster than allocating all the bytes all over again...
for i := 0; i < int(actualWidth*actualHeight); i++ {
pixels[(i*4)+3] = 0
}
for cofLayerIdx := range v.Cof.CofLayers {
layerName := DccLayerNames[v.Cof.CofLayers[cofLayerIdx].Type]
dccLayer := v.dccLayers[layerName]
if !dccLayer.IsValid() {
continue
}
transparency := byte(255)
if v.Cof.CofLayers[cofLayerIdx].Transparent {
transparency = byte(128)
}
if animationIdx > len(dccLayer.Directions[v.direction].Frames) {
log.Printf("Invalid animation index of %d for animated entity", animationIdx)
continue
}
frame := dccLayer.Directions[v.direction].Frames[animationIdx]
for y := 0; y < dccLayer.Directions[v.direction].Box.Height; y++ {
for x := 0; x < dccLayer.Directions[v.direction].Box.Width; x++ {
paletteIndex := frame.PixelData[x+(y*dccLayer.Directions[v.direction].Box.Width)]
if paletteIndex == 0 {
continue
}
color := d2datadict.Palettes[v.palette].Colors[paletteIndex]
actualX := (x + dccLayer.Directions[v.direction].Box.Left) - int(minX)
actualY := (y + dccLayer.Directions[v.direction].Box.Top) - int(minY)
pixels[(actualX*4)+(actualY*int(actualWidth)*4)] = color.R
pixels[(actualX*4)+(actualY*int(actualWidth)*4)+1] = color.G
pixels[(actualX*4)+(actualY*int(actualWidth)*4)+2] = color.B
pixels[(actualX*4)+(actualY*int(actualWidth)*4)+3] = transparency
}
}
}
v.frames[animationIdx], _ = ebiten.NewImage(int(actualWidth), int(actualHeight), ebiten.FilterNearest)
_ = v.frames[animationIdx].ReplacePixels(pixels)
}
}
//func (v *AnimatedEntity) cacheFrames(layerName string) {
// dcc := v.dccLayers[layerName]
// v.currentFrame = 0
// animationData := d2data.AnimationData[strings.ToLower(v.token+v.animationMode+v.weaponClass)][0]
// v.animationSpeed = 1.0 / ((float64(animationData.AnimationSpeed) * 25.0) / 256.0)
// v.framesToAnimate = animationData.FramesPerDirection
// v.lastFrameTime = d2helper.Now()
// minX := int32(10000)
// minY := int32(10000)
// maxX := int32(-10000)
// maxY := int32(-10000)
// for _, layer := range dcc.Directions {
// minX = d2helper.MinInt32(minX, int32(layer.Box.Left))
// minY = d2helper.MinInt32(minY, int32(layer.Box.Top))
// maxX = d2helper.MaxInt32(maxX, int32(layer.Box.Right()))
// maxY = d2helper.MaxInt32(maxY, int32(layer.Box.Bottom()))
// }
// frameW := maxX - minX
// frameH := maxY - minY
// v.frames[layerName] = make([]*ebiten.Image, v.framesToAnimate)
// v.frameLocations[layerName] = make([]d2common.Rectangle, v.framesToAnimate)
// for frameIndex := range v.frames[layerName] {
// v.frames[layerName][frameIndex], _ = ebiten.NewImage(int(frameW), int(frameH), ebiten.FilterNearest)
// for layerIdx := 0; layerIdx < v.Cof.NumberOfLayers; layerIdx++ {
// transparency := byte(255)
// if v.Cof.CofLayers[layerIdx].Transparent {
// transparency = byte(128)
// }
//
// direction := dcc.Directions[v.direction]
// if frameIndex >= len(direction.Frames) {
// continue
// }
// frame := direction.Frames[frameIndex]
// img := image.NewRGBA(image.Rect(0, 0, int(frameW), int(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 := d2datadict.Palettes[v.palette].Colors[paletteIndex]
// actualX := x + direction.Box.Left - int(minX)
// actualY := y + direction.Box.Top - int(minY)
// img.Pix[(actualX*4)+(actualY*int(frameW)*4)] = color.R
// img.Pix[(actualX*4)+(actualY*int(frameW)*4)+1] = color.G
// img.Pix[(actualX*4)+(actualY*int(frameW)*4)+2] = color.B
// img.Pix[(actualX*4)+(actualY*int(frameW)*4)+3] = transparency
// }
// }
// newImage, _ := ebiten.NewImageFromImage(img, ebiten.FilterNearest)
// img = nil
// v.frames[layerName][frameIndex] = newImage
// v.frameLocations[layerName][frameIndex] = d2common.Rectangle{
// Left: int(minX),
// Top: int(minY),
// Width: int(frameW),
// Height: int(frameH),
// }
// }
// }
//}
func (v AnimatedEntity) GetDirection() int {
return v.direction
}