2019-12-24 01:48:45 -05:00
|
|
|
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"
|
2019-12-28 16:46:08 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
2019-12-24 01:48:45 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-12-28 16:46:08 -05:00
|
|
|
func (c *Composite) Render(target *d2surface.Surface) error {
|
2019-12-24 01:48:45 -05:00
|
|
|
if c.mode == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, layerIndex := range c.mode.drawOrder[c.mode.frameIndex] {
|
|
|
|
layer := c.mode.layers[layerIndex]
|
|
|
|
if layer != nil {
|
2019-12-28 16:46:08 -05:00
|
|
|
if err := layer.Render(target); err != nil {
|
2019-12-24 01:48:45 -05:00
|
|
|
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) {
|
2019-12-28 23:32:24 -05:00
|
|
|
cofPath := fmt.Sprintf("%s/%s/COF/%s%s%s.COF", c.object.Base, c.object.Token, c.object.Token, animationMode, weaponClass)
|
|
|
|
if exists, _ := FileExists(cofPath); !exists {
|
|
|
|
return nil, errors.New("composite not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
cof, err := loadCOF(cofPath)
|
2019-12-24 01:48:45 -05:00
|
|
|
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 {
|
2019-12-28 23:32:24 -05:00
|
|
|
if exists, _ := FileExists(animationPath); exists {
|
|
|
|
animation, err := LoadAnimationWithTransparency(animationPath, palettePath, transparency)
|
|
|
|
if err == nil {
|
|
|
|
return animation, nil
|
|
|
|
}
|
2019-12-24 01:48:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("animation not found")
|
|
|
|
}
|