2020-01-26 00:39:13 -05:00
|
|
|
package d2cof
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
|
2020-09-12 16:25:09 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
2020-01-31 23:18:11 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
2020-01-26 00:39:13 -05:00
|
|
|
)
|
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
const (
|
2021-02-05 17:43:42 -05:00
|
|
|
numUnknownHeaderBytes = 21
|
|
|
|
numUnknownBodyBytes = 3
|
|
|
|
numHeaderBytes = 4 + numUnknownHeaderBytes
|
|
|
|
numLayerBytes = 9
|
2021-01-11 20:23:43 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
headerNumLayers = iota
|
|
|
|
headerFramesPerDir
|
|
|
|
headerNumDirs
|
|
|
|
headerSpeed = numHeaderBytes - 1
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
layerType = iota
|
|
|
|
layerShadow
|
|
|
|
layerSelectable
|
|
|
|
layerTransparent
|
|
|
|
layerDrawEffect
|
|
|
|
layerWeaponClass
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
badCharacter = string(byte(0))
|
|
|
|
)
|
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
// New creates a new COF
|
|
|
|
func New() *COF {
|
|
|
|
return &COF{
|
|
|
|
unknownHeaderBytes: make([]byte, numUnknownHeaderBytes),
|
|
|
|
unknownBodyBytes: make([]byte, numUnknownBodyBytes),
|
|
|
|
NumberOfDirections: 0,
|
|
|
|
FramesPerDirection: 0,
|
|
|
|
NumberOfLayers: 0,
|
|
|
|
Speed: 0,
|
|
|
|
CofLayers: make([]CofLayer, 0),
|
|
|
|
CompositeLayers: make(map[d2enum.CompositeType]int),
|
|
|
|
AnimationFrames: make([]d2enum.AnimationFrame, 0),
|
|
|
|
Priority: make([][][]d2enum.CompositeType, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal a COF to a new byte slice
|
|
|
|
func Marshal(c *COF) []byte {
|
|
|
|
return c.Marshal()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal a byte slice to a new COF
|
|
|
|
func Unmarshal(data []byte) (*COF, error) {
|
|
|
|
c := New()
|
|
|
|
err := c.Unmarshal(data)
|
|
|
|
|
|
|
|
return c, err
|
|
|
|
}
|
|
|
|
|
2020-06-28 22:32:34 -04:00
|
|
|
// COF is a structure that represents a COF file.
|
2020-01-26 00:39:13 -05:00
|
|
|
type COF struct {
|
2021-01-30 11:09:37 -05:00
|
|
|
// unknown bytes for header
|
|
|
|
unknownHeaderBytes []byte
|
|
|
|
// unknown bytes (first "body's" bytes)
|
2021-02-05 17:43:42 -05:00
|
|
|
unknownBodyBytes []byte
|
2020-01-26 00:39:13 -05:00
|
|
|
NumberOfDirections int
|
|
|
|
FramesPerDirection int
|
|
|
|
NumberOfLayers int
|
|
|
|
Speed int
|
|
|
|
CofLayers []CofLayer
|
|
|
|
CompositeLayers map[d2enum.CompositeType]int
|
|
|
|
AnimationFrames []d2enum.AnimationFrame
|
|
|
|
Priority [][][]d2enum.CompositeType
|
|
|
|
}
|
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
// Unmarshal a byte slice to this COF
|
2021-02-01 05:15:42 -05:00
|
|
|
// nolint:funlen // no need to change
|
2021-02-05 17:43:42 -05:00
|
|
|
func (c *COF) Unmarshal(fileData []byte) error {
|
2020-09-08 15:58:35 -04:00
|
|
|
streamReader := d2datautils.CreateStreamReader(fileData)
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
var b []byte
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
b, err = streamReader.ReadBytes(numHeaderBytes)
|
|
|
|
if err != nil {
|
2021-02-05 17:43:42 -05:00
|
|
|
return err
|
2021-01-11 20:23:43 -05:00
|
|
|
}
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
c.NumberOfLayers = int(b[headerNumLayers])
|
|
|
|
c.FramesPerDirection = int(b[headerFramesPerDir])
|
|
|
|
c.NumberOfDirections = int(b[headerNumDirs])
|
|
|
|
c.unknownHeaderBytes = b[headerNumDirs+1 : headerSpeed]
|
|
|
|
c.Speed = int(b[headerSpeed])
|
2021-01-30 11:09:37 -05:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
c.unknownBodyBytes, err = streamReader.ReadBytes(numUnknownBodyBytes)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
c.CofLayers = make([]CofLayer, c.NumberOfLayers)
|
|
|
|
c.CompositeLayers = make(map[d2enum.CompositeType]int)
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
for i := 0; i < c.NumberOfLayers; i++ {
|
2020-01-26 00:39:13 -05:00
|
|
|
layer := CofLayer{}
|
2021-01-11 20:23:43 -05:00
|
|
|
|
|
|
|
b, err = streamReader.ReadBytes(numLayerBytes)
|
|
|
|
if err != nil {
|
2021-02-05 17:43:42 -05:00
|
|
|
return err
|
2021-01-11 20:23:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
layer.Type = d2enum.CompositeType(b[layerType])
|
|
|
|
layer.Shadow = b[layerShadow]
|
|
|
|
layer.Selectable = b[layerSelectable] > 0
|
|
|
|
layer.Transparent = b[layerTransparent] > 0
|
|
|
|
layer.DrawEffect = d2enum.DrawEffect(b[layerDrawEffect])
|
|
|
|
|
|
|
|
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(
|
|
|
|
string(b[layerWeaponClass:]), badCharacter, "")))
|
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
c.CofLayers[i] = layer
|
|
|
|
c.CompositeLayers[layer.Type] = i
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
b, err = streamReader.ReadBytes(c.FramesPerDirection)
|
2021-01-11 20:23:43 -05:00
|
|
|
if err != nil {
|
2021-02-05 17:43:42 -05:00
|
|
|
return err
|
2021-01-11 20:23:43 -05:00
|
|
|
}
|
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
c.AnimationFrames = make([]d2enum.AnimationFrame, c.FramesPerDirection)
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-01-11 20:23:43 -05:00
|
|
|
for i := range b {
|
2021-02-05 17:43:42 -05:00
|
|
|
c.AnimationFrames[i] = d2enum.AnimationFrame(b[i])
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
priorityLen := c.FramesPerDirection * c.NumberOfDirections * c.NumberOfLayers
|
|
|
|
c.Priority = make([][][]d2enum.CompositeType, c.NumberOfDirections)
|
2021-01-11 20:23:43 -05:00
|
|
|
|
|
|
|
priorityBytes, err := streamReader.ReadBytes(priorityLen)
|
|
|
|
if err != nil {
|
2021-02-05 17:43:42 -05:00
|
|
|
return err
|
2021-01-11 20:23:43 -05:00
|
|
|
}
|
|
|
|
|
2020-01-26 00:39:13 -05:00
|
|
|
priorityIndex := 0
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
for direction := 0; direction < c.NumberOfDirections; direction++ {
|
|
|
|
c.Priority[direction] = make([][]d2enum.CompositeType, c.FramesPerDirection)
|
|
|
|
for frame := 0; frame < c.FramesPerDirection; frame++ {
|
|
|
|
c.Priority[direction][frame] = make([]d2enum.CompositeType, c.NumberOfLayers)
|
|
|
|
for i := 0; i < c.NumberOfLayers; i++ {
|
|
|
|
c.Priority[direction][frame][i] = d2enum.CompositeType(priorityBytes[priorityIndex])
|
2020-01-26 00:39:13 -05:00
|
|
|
priorityIndex++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 22:32:34 -04:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
return nil
|
2020-01-26 00:39:13 -05:00
|
|
|
}
|
2021-01-30 11:09:37 -05:00
|
|
|
|
2021-02-05 17:43:42 -05:00
|
|
|
// Marshal this COF to a byte slice
|
2021-01-31 06:11:54 -05:00
|
|
|
func (c *COF) Marshal() []byte {
|
2021-01-31 12:57:57 -05:00
|
|
|
sw := d2datautils.CreateStreamWriter()
|
2021-01-30 11:09:37 -05:00
|
|
|
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(c.NumberOfLayers))
|
|
|
|
sw.PushBytes(byte(c.FramesPerDirection))
|
|
|
|
sw.PushBytes(byte(c.NumberOfDirections))
|
2021-02-01 05:15:42 -05:00
|
|
|
sw.PushBytes(c.unknownHeaderBytes...)
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(c.Speed))
|
2021-02-05 17:43:42 -05:00
|
|
|
sw.PushBytes(c.unknownBodyBytes...)
|
2021-01-30 11:09:37 -05:00
|
|
|
|
|
|
|
for i := range c.CofLayers {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(c.CofLayers[i].Type.Int()))
|
|
|
|
sw.PushBytes(c.CofLayers[i].Shadow)
|
2021-01-30 11:09:37 -05:00
|
|
|
|
|
|
|
if c.CofLayers[i].Selectable {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(1))
|
2021-01-30 11:09:37 -05:00
|
|
|
} else {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(0))
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.CofLayers[i].Transparent {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(1))
|
2021-01-30 11:09:37 -05:00
|
|
|
} else {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(0))
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|
|
|
|
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(c.CofLayers[i].DrawEffect))
|
2021-01-30 11:09:37 -05:00
|
|
|
|
2021-02-10 06:59:07 -05:00
|
|
|
const (
|
|
|
|
maxCodeLength = 3 // we assume item codes to look like 'hax' or 'kit'
|
|
|
|
terminator = 0
|
|
|
|
)
|
2021-02-10 02:24:53 -05:00
|
|
|
|
2021-02-10 06:59:07 -05:00
|
|
|
weaponCode := c.CofLayers[i].WeaponClass.String()
|
|
|
|
|
|
|
|
for idx, letter := range weaponCode {
|
|
|
|
if idx > maxCodeLength {
|
|
|
|
break
|
2021-02-10 02:24:53 -05:00
|
|
|
}
|
2021-02-10 06:59:07 -05:00
|
|
|
|
|
|
|
sw.PushBytes(byte(letter))
|
2021-02-10 02:24:53 -05:00
|
|
|
}
|
2021-02-10 06:59:07 -05:00
|
|
|
|
|
|
|
sw.PushBytes(terminator)
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, i := range c.AnimationFrames {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(i))
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for direction := 0; direction < c.NumberOfDirections; direction++ {
|
|
|
|
for frame := 0; frame < c.FramesPerDirection; frame++ {
|
|
|
|
for i := 0; i < c.NumberOfLayers; i++ {
|
2021-02-01 05:20:44 -05:00
|
|
|
sw.PushBytes(byte(c.Priority[direction][frame][i]))
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 12:57:57 -05:00
|
|
|
return sw.GetBytes()
|
2021-01-30 11:09:37 -05:00
|
|
|
}
|