1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-16 04:25:23 +00:00
OpenDiablo2/d2core/d2asset/composite.go

264 lines
7.6 KiB
Go
Raw Normal View History

package d2asset
import (
"errors"
"fmt"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
// Composite is a composite entity animation
type Composite struct {
object *d2datadict.ObjectLookupRecord
palettePath string
mode *compositeMode
}
// CreateComposite creates a Composite from a given ObjectLookupRecord and palettePath.
func CreateComposite(object *d2datadict.ObjectLookupRecord, palettePath string) *Composite {
return &Composite{object: object, palettePath: palettePath}
}
// Advance moves the composite animation forward for a given elapsed time in nanoseconds.
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
}
// Render performs drawing of the Composite on the rendered d2interface.Surface.
func (c *Composite) Render(target d2interface.Surface) 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.RenderFromOrigin(target); err != nil {
return err
}
}
}
return nil
}
// GetAnimationMode returns the animation mode the Composite should render with.
func (c Composite) GetAnimationMode() string {
return c.mode.animationMode
}
// SetMode sets the Composite's animation mode weapon class and direction
func (c *Composite) SetMode(animationMode, weaponClass string, direction int) error {
if c.mode != nil && c.mode.animationMode == animationMode && c.mode.weaponClass == weaponClass && c.mode.cofDirection == direction {
return nil
}
mode, err := c.createMode(animationMode, weaponClass, direction)
if err != nil {
return err
}
c.resetPlayedCount()
c.mode = mode
return nil
}
// SetSpeed sets the speed at which the Composite's animation should advance through its frames
func (c *Composite) SetSpeed(speed int) {
c.mode.animationSpeed = 1.0 / ((float64(speed) * 25.0) / 256.0)
for layerIdx := range c.mode.layers {
layer := c.mode.layers[layerIdx]
if layer != nil {
layer.SetPlaySpeed(c.mode.animationSpeed)
}
}
}
// GetDirectionCount returns the Composites number of available animated directions
func (c *Composite) GetDirectionCount() int {
if c.mode == nil {
return 0
}
return c.mode.directionCount
}
// GetPlayedCount returns the number of times the current animation mode has completed all its distinct frames
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
cofDirection 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) {
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)
if err != nil {
return nil, err
}
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,
directionCount: cof.NumberOfDirections,
cofDirection: d2cof.Dir64ToCof(direction, 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[mode.cofDirection][frame]
}
for _, cofLayer := range cof.CofLayers {
var layerValue string
switch cofLayer.Type {
case d2enum.CompositeTypeHead:
layerValue = c.object.HD
case d2enum.CompositeTypeTorso:
layerValue = c.object.TR
case d2enum.CompositeTypeLegs:
layerValue = c.object.LG
case d2enum.CompositeTypeRightArm:
layerValue = c.object.RA
case d2enum.CompositeTypeLeftArm:
layerValue = c.object.LA
case d2enum.CompositeTypeRightHand:
layerValue = c.object.RH
case d2enum.CompositeTypeLeftHand:
layerValue = c.object.LH
case d2enum.CompositeTypeShield:
layerValue = c.object.SH
case d2enum.CompositeTypeSpecial1:
layerValue = c.object.S1
case d2enum.CompositeTypeSpecial2:
layerValue = c.object.S2
case d2enum.CompositeTypeSpecial3:
layerValue = c.object.S3
case d2enum.CompositeTypeSpecial4:
layerValue = c.object.S4
case d2enum.CompositeTypeSpecial5:
layerValue = c.object.S5
case d2enum.CompositeTypeSpecial6:
layerValue = c.object.S6
case d2enum.CompositeTypeSpecial7:
layerValue = c.object.S7
case d2enum.CompositeTypeSpecial8:
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, cofLayer.Type.String(), layerValue, animationMode, weaponClass, c.palettePath, transparency)
if err == nil {
layer.SetPlaySpeed(mode.animationSpeed)
layer.PlayForward()
layer.SetBlend(blend)
if err := layer.SetDirection(direction); err != nil {
return nil, err
}
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 {
if exists, _ := FileExists(animationPath); exists {
animation, err := LoadAnimationWithTransparency(animationPath, palettePath, transparency)
if err == nil {
return animation, nil
}
}
}
return nil, errors.New("animation not found")
}