mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-17 18:06:03 -05:00
Merge pull request #1066 from gravestench/d2ds1_refactor
Refactoring d2ds1
This commit is contained in:
commit
59114a72ed
@ -38,7 +38,7 @@ func (v *StreamWriter) PushBytes(b ...byte) {
|
||||
// use another Push... method, bits'll not be pushed
|
||||
func (v *StreamWriter) PushBit(b bool) {
|
||||
if b {
|
||||
v.bitCache |= (1 << v.bitOffset)
|
||||
v.bitCache |= 1 << v.bitOffset
|
||||
}
|
||||
v.bitOffset++
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
package d2enum
|
||||
|
||||
// LayerStreamType represents a layer stream type
|
||||
type LayerStreamType int
|
||||
|
||||
// Layer stream types
|
||||
const (
|
||||
LayerStreamWall1 LayerStreamType = iota
|
||||
LayerStreamWall2
|
||||
LayerStreamWall3
|
||||
LayerStreamWall4
|
||||
LayerStreamOrientation1
|
||||
LayerStreamOrientation2
|
||||
LayerStreamOrientation3
|
||||
LayerStreamOrientation4
|
||||
LayerStreamFloor1
|
||||
LayerStreamFloor2
|
||||
LayerStreamShadow
|
||||
LayerStreamSubstitute
|
||||
)
|
@ -232,7 +232,7 @@ func (ad *AnimationData) Marshal() []byte {
|
||||
recordIdx := 0
|
||||
|
||||
// numberOfEntries is a number of entries in all map indexes
|
||||
var numberOfEntries int = 0
|
||||
var numberOfEntries = 0
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
numberOfEntries += len(ad.entries[keys[i]])
|
||||
|
@ -1,2 +1,2 @@
|
||||
// Package d2ds1 provides functionality for loading/processing DS1 files
|
||||
// Package d2ds1 provides functionality for loading/processing DS1 Files
|
||||
package d2ds1
|
||||
|
@ -1,6 +1,8 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
@ -11,25 +13,11 @@ import (
|
||||
const (
|
||||
subType1 = 1
|
||||
subType2 = 2
|
||||
v2 = 2
|
||||
v3 = 3
|
||||
v4 = 4
|
||||
v7 = 7
|
||||
v8 = 8
|
||||
v9 = 9
|
||||
v10 = 10
|
||||
v12 = 12
|
||||
v13 = 13
|
||||
v14 = 14
|
||||
v15 = 15
|
||||
v16 = 16
|
||||
v18 = 18
|
||||
)
|
||||
|
||||
const (
|
||||
wallZeroBitmask = 0xFFFFFF00
|
||||
wallZeroOffset = 8
|
||||
|
||||
wallTypeBitmask = 0x000000FF
|
||||
)
|
||||
|
||||
@ -39,101 +27,41 @@ const (
|
||||
|
||||
// DS1 represents the "stamp" data that is used to build up maps.
|
||||
type DS1 struct {
|
||||
*ds1Layers
|
||||
Files []string // FilePtr table of file string pointers
|
||||
Objects []Object // Objects
|
||||
Tiles [][]TileRecord // The tile data for the DS1
|
||||
SubstitutionGroups []SubstitutionGroup // Substitution groups for the DS1
|
||||
Version int32 // The version of the DS1
|
||||
Width int32 // Width of map, in # of tiles
|
||||
Height int32 // Height of map, in # of tiles
|
||||
Act int32 // Act, from 1 to 5. This tells which act table to use for the Objects list
|
||||
substitutionGroups []SubstitutionGroup // Substitution groups for the DS1
|
||||
|
||||
version ds1version
|
||||
Act int32 // Act, from 1 to 5. This tells which Act table to use for the Objects list
|
||||
SubstitutionType int32 // SubstitutionType (layer type): 0 if no layer, else type 1 or type 2
|
||||
NumberOfWalls int32 // WallNum number of wall & orientation layers used
|
||||
NumberOfFloors int32 // number of floor layers used
|
||||
NumberOfShadowLayers int32 // ShadowNum number of shadow layer used
|
||||
NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used
|
||||
SubstitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths
|
||||
unknown1 []byte
|
||||
LayerStreamTypes []d2enum.LayerStreamType
|
||||
unknown1 [unknown1BytesCount]byte
|
||||
unknown2 uint32
|
||||
NpcIndexes []int
|
||||
}
|
||||
|
||||
// LoadDS1 loads the specified DS1 file
|
||||
func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
ds1 := &DS1{
|
||||
Act: 1,
|
||||
NumberOfFloors: 0,
|
||||
NumberOfWalls: 0,
|
||||
NumberOfShadowLayers: 1,
|
||||
NumberOfSubstitutionLayers: 0,
|
||||
const (
|
||||
defaultNumFloors = 1
|
||||
defaultNumShadows = maxShadowLayers
|
||||
defaultNumSubstitutions = 0
|
||||
)
|
||||
|
||||
// Unmarshal the given bytes to a DS1 struct
|
||||
func Unmarshal(fileData []byte) (*DS1, error) {
|
||||
return (&DS1{}).Unmarshal(fileData)
|
||||
}
|
||||
|
||||
// Unmarshal the given bytes to a DS1 struct
|
||||
func (ds1 *DS1) Unmarshal(fileData []byte) (*DS1, error) {
|
||||
ds1.ds1Layers = &ds1Layers{}
|
||||
|
||||
stream := d2datautils.CreateStreamReader(fileData)
|
||||
|
||||
if err := ds1.loadHeader(stream); err != nil {
|
||||
return nil, fmt.Errorf("loading header: %w", err)
|
||||
}
|
||||
|
||||
br := d2datautils.CreateStreamReader(fileData)
|
||||
|
||||
var err error
|
||||
|
||||
err = ds1.loadHeader(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ds1.Version >= v9 && ds1.Version <= v13 {
|
||||
// Skipping two dwords because they are "meaningless"?
|
||||
ds1.unknown1, err = br.ReadBytes(unknown1BytesCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if ds1.Version >= v4 {
|
||||
ds1.NumberOfWalls, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ds1.Version >= v16 {
|
||||
ds1.NumberOfFloors, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ds1.NumberOfFloors = 1
|
||||
}
|
||||
}
|
||||
|
||||
ds1.LayerStreamTypes = ds1.setupStreamLayerTypes()
|
||||
|
||||
ds1.Tiles = make([][]TileRecord, ds1.Height)
|
||||
|
||||
for y := range ds1.Tiles {
|
||||
ds1.Tiles[y] = make([]TileRecord, ds1.Width)
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
ds1.Tiles[y][x].Walls = make([]WallRecord, ds1.NumberOfWalls)
|
||||
ds1.Tiles[y][x].Floors = make([]FloorShadowRecord, ds1.NumberOfFloors)
|
||||
ds1.Tiles[y][x].Shadows = make([]FloorShadowRecord, ds1.NumberOfShadowLayers)
|
||||
ds1.Tiles[y][x].Substitutions = make([]SubstitutionRecord, ds1.NumberOfSubstitutionLayers)
|
||||
}
|
||||
}
|
||||
|
||||
err = ds1.loadLayerStreams(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ds1.loadObjects(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ds1.loadSubstitutions(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ds1.loadNPCs(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err := ds1.loadBody(stream); err != nil {
|
||||
return nil, fmt.Errorf("loading body: %w", err)
|
||||
}
|
||||
|
||||
return ds1, nil
|
||||
@ -142,58 +70,139 @@ func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error {
|
||||
var err error
|
||||
|
||||
ds1.Version, err = br.ReadInt32()
|
||||
var width, height int32
|
||||
|
||||
v, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading version: %w", err)
|
||||
}
|
||||
|
||||
ds1.Width, err = br.ReadInt32()
|
||||
ds1.version = ds1version(v)
|
||||
|
||||
width, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading width: %w", err)
|
||||
}
|
||||
|
||||
ds1.Height, err = br.ReadInt32()
|
||||
height, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading height: %w", err)
|
||||
}
|
||||
|
||||
ds1.Width++
|
||||
ds1.Height++
|
||||
width++
|
||||
height++
|
||||
|
||||
if ds1.Version >= v8 {
|
||||
ds1.SetSize(int(width), int(height))
|
||||
|
||||
if ds1.version.specifiesAct() {
|
||||
ds1.Act, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading Act: %w", err)
|
||||
}
|
||||
|
||||
ds1.Act = d2math.MinInt32(d2enum.ActsNumber, ds1.Act+1)
|
||||
}
|
||||
|
||||
if ds1.Version >= v10 {
|
||||
if ds1.version.specifiesSubstitutionType() {
|
||||
ds1.SubstitutionType, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading substitution type: %w", err)
|
||||
}
|
||||
|
||||
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
|
||||
ds1.NumberOfSubstitutionLayers = 1
|
||||
switch ds1.SubstitutionType {
|
||||
case subType1, subType2:
|
||||
ds1.PushSubstitution(&layer{})
|
||||
}
|
||||
}
|
||||
|
||||
err = ds1.loadFileList(br)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("loading file list: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadBody(stream *d2datautils.StreamReader) error {
|
||||
var numWalls, numFloors, numShadows, numSubstitutions int32
|
||||
|
||||
numFloors = defaultNumFloors
|
||||
numShadows = defaultNumShadows
|
||||
numSubstitutions = defaultNumSubstitutions
|
||||
|
||||
if ds1.version.hasUnknown1Bytes() {
|
||||
var err error
|
||||
|
||||
bytes, err := stream.ReadBytes(unknown1BytesCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading unknown1: %w", err)
|
||||
}
|
||||
|
||||
copy(ds1.unknown1[:], bytes[:unknown1BytesCount])
|
||||
}
|
||||
|
||||
if ds1.version.specifiesWalls() {
|
||||
var err error
|
||||
|
||||
numWalls, err = stream.ReadInt32()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading wall number: %w", err)
|
||||
}
|
||||
|
||||
if ds1.version.specifiesFloors() {
|
||||
numFloors, err = stream.ReadInt32()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading number of Floors: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ; numWalls > 0; numWalls-- {
|
||||
ds1.PushWall(&layer{})
|
||||
}
|
||||
|
||||
for ; numShadows > 0; numShadows-- {
|
||||
ds1.PushShadow(&layer{})
|
||||
}
|
||||
|
||||
for ; numFloors > 0; numFloors-- {
|
||||
ds1.PushFloor(&layer{})
|
||||
}
|
||||
|
||||
for ; numSubstitutions > 0; numSubstitutions-- {
|
||||
ds1.PushSubstitution(&layer{})
|
||||
}
|
||||
|
||||
ds1.SetSize(ds1.width, ds1.height)
|
||||
|
||||
if err := ds1.loadLayerStreams(stream); err != nil {
|
||||
return fmt.Errorf("loading layer streams: %w", err)
|
||||
}
|
||||
|
||||
if err := ds1.loadObjects(stream); err != nil {
|
||||
return fmt.Errorf("loading Objects: %w", err)
|
||||
}
|
||||
|
||||
if err := ds1.loadSubstitutions(stream); err != nil {
|
||||
return fmt.Errorf("loading Substitutions: %w", err)
|
||||
}
|
||||
|
||||
if err := ds1.loadNPCs(stream); err != nil {
|
||||
return fmt.Errorf("loading npc's: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error {
|
||||
if ds1.Version >= v3 {
|
||||
// These files reference things that don't exist anymore :-?
|
||||
if !ds1.version.hasFileList() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// These Files reference things that don't exist anymore :-?
|
||||
numberOfFiles, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading number of Files: %w", err)
|
||||
}
|
||||
|
||||
ds1.Files = make([]string, numberOfFiles)
|
||||
@ -204,7 +213,7 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error {
|
||||
for {
|
||||
ch, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading file character: %w", err)
|
||||
}
|
||||
|
||||
if ch == 0 {
|
||||
@ -214,47 +223,49 @@ func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error {
|
||||
ds1.Files[i] += string(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
|
||||
if ds1.Version < v2 {
|
||||
if !ds1.version.hasObjects() {
|
||||
ds1.Objects = make([]Object, 0)
|
||||
} else {
|
||||
numberOfObjects, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
ds1.Objects = make([]Object, numberOfObjects)
|
||||
numObjects, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading number of Objects: %w", err)
|
||||
}
|
||||
|
||||
for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ {
|
||||
ds1.Objects = make([]Object, numObjects)
|
||||
|
||||
for objIdx := 0; objIdx < int(numObjects); objIdx++ {
|
||||
obj := Object{}
|
||||
|
||||
objType, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading object's %d type: %v", objIdx, err)
|
||||
}
|
||||
|
||||
objID, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading object's %d ID: %v", objIdx, err)
|
||||
}
|
||||
|
||||
objX, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading object's %d X: %v", objIdx, err)
|
||||
}
|
||||
|
||||
objY, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading object's %d Y: %v", objY, err)
|
||||
}
|
||||
|
||||
objFlags, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading object's %d flags: %v", objIdx, err)
|
||||
}
|
||||
|
||||
obj.Type = int(objType)
|
||||
@ -265,7 +276,6 @@ func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
|
||||
|
||||
ds1.Objects[objIdx] = obj
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -273,94 +283,108 @@ func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
|
||||
func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error {
|
||||
var err error
|
||||
|
||||
hasSubstitutions := ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2)
|
||||
hasSubstitutions := ds1.version.hasSubstitutions() &&
|
||||
(ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2)
|
||||
|
||||
if !hasSubstitutions {
|
||||
ds1.SubstitutionGroups = make([]SubstitutionGroup, 0)
|
||||
ds1.substitutionGroups = make([]SubstitutionGroup, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
if ds1.Version >= v18 {
|
||||
if ds1.version.hasUnknown2Bytes() {
|
||||
ds1.unknown2, err = br.ReadUInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading unknown 2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
numberOfSubGroups, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading number of sub groups: %w", err)
|
||||
}
|
||||
|
||||
ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
|
||||
ds1.substitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
|
||||
|
||||
for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ {
|
||||
newSub := SubstitutionGroup{}
|
||||
|
||||
newSub.TileX, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading substitution's %d X: %v", subIdx, err)
|
||||
}
|
||||
|
||||
newSub.TileY, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading substitution's %d Y: %v", subIdx, err)
|
||||
}
|
||||
|
||||
newSub.WidthInTiles, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading substitution's %d W: %v", subIdx, err)
|
||||
}
|
||||
|
||||
newSub.HeightInTiles, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading substitution's %d H: %v", subIdx, err)
|
||||
}
|
||||
|
||||
newSub.Unknown, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading substitution's %d unknown: %v", subIdx, err)
|
||||
}
|
||||
|
||||
ds1.SubstitutionGroups[subIdx] = newSub
|
||||
ds1.substitutionGroups[subIdx] = newSub
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType {
|
||||
var layerStream []d2enum.LayerStreamType
|
||||
func (ds1 *DS1) getLayerSchema() []layerStreamType {
|
||||
var layerStream []layerStreamType
|
||||
|
||||
if ds1.Version < v4 {
|
||||
layerStream = []d2enum.LayerStreamType{
|
||||
d2enum.LayerStreamWall1,
|
||||
d2enum.LayerStreamFloor1,
|
||||
d2enum.LayerStreamOrientation1,
|
||||
d2enum.LayerStreamSubstitute,
|
||||
d2enum.LayerStreamShadow,
|
||||
if ds1.version.hasStandardLayers() {
|
||||
layerStream = []layerStreamType{
|
||||
layerStreamWall1,
|
||||
layerStreamFloor1,
|
||||
layerStreamOrientation1,
|
||||
layerStreamSubstitute1,
|
||||
layerStreamShadow1,
|
||||
}
|
||||
} else {
|
||||
// nolint:gomnd // constant
|
||||
layerStream = make([]d2enum.LayerStreamType,
|
||||
(ds1.NumberOfWalls*2)+ds1.NumberOfFloors+ds1.NumberOfShadowLayers+ds1.NumberOfSubstitutionLayers)
|
||||
|
||||
return layerStream
|
||||
}
|
||||
|
||||
numWalls := len(ds1.Walls)
|
||||
numOrientations := numWalls
|
||||
numFloors := len(ds1.Floors)
|
||||
numShadows := len(ds1.Shadows)
|
||||
numSubs := len(ds1.Substitutions)
|
||||
numLayers := numWalls + numOrientations + numFloors + numShadows + numSubs
|
||||
|
||||
layerStream = make([]layerStreamType, numLayers)
|
||||
|
||||
layerIdx := 0
|
||||
for i := 0; i < int(ds1.NumberOfWalls); i++ {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamType(int(d2enum.LayerStreamWall1) + i)
|
||||
layerStream[layerIdx+1] = d2enum.LayerStreamType(int(d2enum.LayerStreamOrientation1) + i)
|
||||
layerIdx += 2
|
||||
}
|
||||
for i := 0; i < int(ds1.NumberOfFloors); i++ {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamType(int(d2enum.LayerStreamFloor1) + i)
|
||||
|
||||
for i := 0; i < numWalls; i++ {
|
||||
layerStream[layerIdx] = layerStreamType(int(layerStreamWall1) + i)
|
||||
layerIdx++
|
||||
|
||||
layerStream[layerIdx] = layerStreamType(int(layerStreamOrientation1) + i)
|
||||
layerIdx++
|
||||
}
|
||||
if ds1.NumberOfShadowLayers > 0 {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamShadow
|
||||
|
||||
for i := 0; i < numFloors; i++ {
|
||||
layerStream[layerIdx] = layerStreamType(int(layerStreamFloor1) + i)
|
||||
layerIdx++
|
||||
}
|
||||
if ds1.NumberOfSubstitutionLayers > 0 {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamSubstitute
|
||||
|
||||
if numShadows > 0 {
|
||||
layerStream[layerIdx] = layerStreamShadow1
|
||||
layerIdx++
|
||||
}
|
||||
|
||||
if numSubs > 0 {
|
||||
layerStream[layerIdx] = layerStreamSubstitute1
|
||||
}
|
||||
|
||||
return layerStream
|
||||
@ -369,29 +393,29 @@ func (ds1 *DS1) setupStreamLayerTypes() []d2enum.LayerStreamType {
|
||||
func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
|
||||
var err error
|
||||
|
||||
if ds1.Version < v14 {
|
||||
return err
|
||||
if !ds1.version.specifiesNPCs() {
|
||||
return nil
|
||||
}
|
||||
|
||||
numberOfNpcs, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading number of npcs: %w", err)
|
||||
}
|
||||
|
||||
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ {
|
||||
numPaths, err := br.ReadInt32() // nolint:govet // I want to re-use this error variable
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading number of paths for npc %d: %v", npcIdx, err)
|
||||
}
|
||||
|
||||
npcX, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading X pos for NPC %d: %v", npcIdx, err)
|
||||
}
|
||||
|
||||
npcY, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading Y pos for NPC %d: %v", npcIdx, err)
|
||||
}
|
||||
|
||||
objIdx := -1
|
||||
@ -399,7 +423,6 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
|
||||
for idx, ds1Obj := range ds1.Objects {
|
||||
if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) {
|
||||
objIdx = idx
|
||||
ds1.NpcIndexes = append(ds1.NpcIndexes, idx)
|
||||
|
||||
break
|
||||
}
|
||||
@ -408,10 +431,10 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
|
||||
if objIdx > -1 {
|
||||
err = ds1.loadNpcPaths(br, objIdx, int(numPaths))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("loading paths for NPC %d: %v", npcIdx, err)
|
||||
}
|
||||
} else {
|
||||
if ds1.Version >= v15 {
|
||||
if ds1.version.specifiesNPCActions() {
|
||||
br.SkipBytes(int(numPaths) * 3) //nolint:gomnd // Unknown data
|
||||
} else {
|
||||
br.SkipBytes(int(numPaths) * 2) //nolint:gomnd // Unknown data
|
||||
@ -423,8 +446,6 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) error {
|
||||
var err error
|
||||
|
||||
if ds1.Objects[objIdx].Paths == nil {
|
||||
ds1.Objects[objIdx].Paths = make([]d2path.Path, numPaths)
|
||||
}
|
||||
@ -434,20 +455,20 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int)
|
||||
|
||||
px, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading X point for path %d: %v", pathIdx, err)
|
||||
}
|
||||
|
||||
py, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading Y point for path %d: %v", pathIdx, err)
|
||||
}
|
||||
|
||||
newPath.Position = d2vector.NewPosition(float64(px), float64(py))
|
||||
|
||||
if ds1.Version >= v15 {
|
||||
if ds1.version.specifiesNPCActions() {
|
||||
action, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading action for path %d: %v", pathIdx, err)
|
||||
}
|
||||
|
||||
newPath.Action = int(action)
|
||||
@ -456,58 +477,62 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int)
|
||||
ds1.Objects[objIdx].Paths[pathIdx] = newPath
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error {
|
||||
var err error
|
||||
|
||||
var dirLookup = []int32{
|
||||
dirLookup := []int32{
|
||||
0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06,
|
||||
0x06, 0x07, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x14,
|
||||
}
|
||||
|
||||
for lIdx := range ds1.LayerStreamTypes {
|
||||
layerStreamType := ds1.LayerStreamTypes[lIdx]
|
||||
layerStreamTypes := ds1.getLayerSchema()
|
||||
|
||||
for y := 0; y < int(ds1.Height); y++ {
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
dw, err := br.ReadUInt32() //nolint:govet // i want to re-use the err variable...
|
||||
for _, layerStreamType := range layerStreamTypes {
|
||||
for y := 0; y < ds1.height; y++ {
|
||||
for x := 0; x < ds1.width; x++ {
|
||||
dw, err := br.ReadUInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("reading layer's dword: %w", err)
|
||||
}
|
||||
|
||||
switch layerStreamType {
|
||||
case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Decode(dw)
|
||||
case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2,
|
||||
d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
|
||||
case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(layerStreamWall1)
|
||||
ds1.Walls[wallIndex].Tile(x, y).DecodeWall(dw)
|
||||
case layerStreamOrientation1, layerStreamOrientation2,
|
||||
layerStreamOrientation3, layerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(layerStreamOrientation1)
|
||||
c := int32(dw & wallTypeBitmask)
|
||||
|
||||
if ds1.Version < v7 {
|
||||
if ds1.version < v7 {
|
||||
if c < int32(len(dirLookup)) {
|
||||
c = dirLookup[c]
|
||||
}
|
||||
}
|
||||
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset)
|
||||
case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Decode(dw)
|
||||
case d2enum.LayerStreamShadow:
|
||||
ds1.Tiles[y][x].Shadows[0].Decode(dw)
|
||||
case d2enum.LayerStreamSubstitute:
|
||||
ds1.Tiles[y][x].Substitutions[0].Unknown = dw
|
||||
tile := ds1.Walls[wallIndex].Tile(x, y)
|
||||
tile.Type = d2enum.TileType(c)
|
||||
tile.Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset)
|
||||
case layerStreamFloor1, layerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(layerStreamFloor1)
|
||||
ds1.Floors[floorIndex].Tile(x, y).DecodeFloor(dw)
|
||||
case layerStreamShadow1:
|
||||
ds1.Shadows[0].Tile(x, y).DecodeShadow(dw)
|
||||
case layerStreamSubstitute1:
|
||||
ds1.Substitutions[0].Tile(x, y).Substitution = dw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSize sets the size of all layers in the DS1
|
||||
func (ds1 *DS1) SetSize(w, h int) {
|
||||
ds1.ds1Layers.SetSize(w, h)
|
||||
}
|
||||
|
||||
// Marshal encodes ds1 back to byte slice
|
||||
@ -516,19 +541,19 @@ func (ds1 *DS1) Marshal() []byte {
|
||||
sw := d2datautils.CreateStreamWriter()
|
||||
|
||||
// Step 1 - encode header
|
||||
sw.PushInt32(ds1.Version)
|
||||
sw.PushInt32(ds1.Width - 1)
|
||||
sw.PushInt32(ds1.Height - 1)
|
||||
sw.PushInt32(int32(ds1.version))
|
||||
sw.PushInt32(int32(ds1.width - 1))
|
||||
sw.PushInt32(int32(ds1.height - 1))
|
||||
|
||||
if ds1.Version >= v8 {
|
||||
if ds1.version.specifiesAct() {
|
||||
sw.PushInt32(ds1.Act - 1)
|
||||
}
|
||||
|
||||
if ds1.Version >= v10 {
|
||||
if ds1.version.specifiesSubstitutionType() {
|
||||
sw.PushInt32(ds1.SubstitutionType)
|
||||
}
|
||||
|
||||
if ds1.Version >= v3 {
|
||||
if ds1.version.hasFileList() {
|
||||
sw.PushInt32(int32(len(ds1.Files)))
|
||||
|
||||
for _, i := range ds1.Files {
|
||||
@ -539,23 +564,23 @@ func (ds1 *DS1) Marshal() []byte {
|
||||
}
|
||||
}
|
||||
|
||||
if ds1.Version >= v9 && ds1.Version <= v13 {
|
||||
sw.PushBytes(ds1.unknown1...)
|
||||
if ds1.version.hasUnknown1Bytes() {
|
||||
sw.PushBytes(ds1.unknown1[:]...)
|
||||
}
|
||||
|
||||
if ds1.Version >= v4 {
|
||||
sw.PushInt32(ds1.NumberOfWalls)
|
||||
if ds1.version.specifiesWalls() {
|
||||
sw.PushInt32(int32(len(ds1.Walls)))
|
||||
|
||||
if ds1.Version >= v16 {
|
||||
sw.PushInt32(ds1.NumberOfFloors)
|
||||
if ds1.version.specifiesFloors() {
|
||||
sw.PushInt32(int32(len(ds1.Walls)))
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2 - encode layers
|
||||
// Step 2 - encode grid
|
||||
ds1.encodeLayers(sw)
|
||||
|
||||
// Step 3 - encode objects
|
||||
if !(ds1.Version < v2) {
|
||||
// Step 3 - encode Objects
|
||||
if ds1.version.hasObjects() {
|
||||
sw.PushInt32(int32(len(ds1.Objects)))
|
||||
|
||||
for _, i := range ds1.Objects {
|
||||
@ -567,13 +592,16 @@ func (ds1 *DS1) Marshal() []byte {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4 - encode substitutions
|
||||
if ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) {
|
||||
// Step 4 - encode Substitutions
|
||||
hasSubstitutions := ds1.version.hasSubstitutions() &&
|
||||
(ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2)
|
||||
|
||||
if hasSubstitutions {
|
||||
sw.PushUint32(ds1.unknown2)
|
||||
|
||||
sw.PushUint32(uint32(len(ds1.SubstitutionGroups)))
|
||||
sw.PushUint32(uint32(len(ds1.substitutionGroups)))
|
||||
|
||||
for _, i := range ds1.SubstitutionGroups {
|
||||
for _, i := range ds1.substitutionGroups {
|
||||
sw.PushInt32(i.TileX)
|
||||
sw.PushInt32(i.TileY)
|
||||
sw.PushInt32(i.WidthInTiles)
|
||||
@ -589,31 +617,31 @@ func (ds1 *DS1) Marshal() []byte {
|
||||
}
|
||||
|
||||
func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) {
|
||||
for lIdx := range ds1.LayerStreamTypes {
|
||||
layerStreamType := ds1.LayerStreamTypes[lIdx]
|
||||
layerStreamTypes := ds1.getLayerSchema()
|
||||
|
||||
for y := 0; y < int(ds1.Height); y++ {
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
for _, layerStreamType := range layerStreamTypes {
|
||||
for y := 0; y < ds1.height; y++ {
|
||||
for x := 0; x < ds1.width; x++ {
|
||||
dw := uint32(0)
|
||||
|
||||
switch layerStreamType {
|
||||
case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Encode(sw)
|
||||
case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2,
|
||||
d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
|
||||
dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type)
|
||||
dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & wallZeroBitmask) << wallZeroOffset
|
||||
case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(layerStreamWall1)
|
||||
ds1.Walls[wallIndex].Tile(x, y).EncodeWall(sw)
|
||||
case layerStreamOrientation1, layerStreamOrientation2,
|
||||
layerStreamOrientation3, layerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(layerStreamOrientation1)
|
||||
dw |= uint32(ds1.Walls[wallIndex].Tile(x, y).Type)
|
||||
dw |= (uint32(ds1.Walls[wallIndex].Tile(x, y).Zero) & wallZeroBitmask) << wallZeroOffset
|
||||
|
||||
sw.PushUint32(dw)
|
||||
case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Encode(sw)
|
||||
case d2enum.LayerStreamShadow:
|
||||
ds1.Tiles[y][x].Shadows[0].Encode(sw)
|
||||
case d2enum.LayerStreamSubstitute:
|
||||
sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown)
|
||||
case layerStreamFloor1, layerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(layerStreamFloor1)
|
||||
ds1.Floors[floorIndex].Tile(x, y).EncodeFloor(sw)
|
||||
case layerStreamShadow1:
|
||||
ds1.Shadows[0].Tile(x, y).EncodeShadow(sw)
|
||||
case layerStreamSubstitute1:
|
||||
sw.PushUint32(ds1.Substitutions[0].Tile(x, y).Substitution)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -621,22 +649,44 @@ func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) {
|
||||
}
|
||||
|
||||
func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) {
|
||||
objectsWithPaths := make([]int, 0)
|
||||
|
||||
for n, obj := range ds1.Objects {
|
||||
if len(obj.Paths) != 0 {
|
||||
objectsWithPaths = append(objectsWithPaths, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5.1 - encode npc's
|
||||
sw.PushUint32(uint32(len(ds1.NpcIndexes)))
|
||||
sw.PushUint32(uint32(len(objectsWithPaths)))
|
||||
|
||||
// Step 5.2 - enoce npcs' paths
|
||||
for _, i := range ds1.NpcIndexes {
|
||||
sw.PushUint32(uint32(len(ds1.Objects[i].Paths)))
|
||||
sw.PushUint32(uint32(ds1.Objects[i].X))
|
||||
sw.PushUint32(uint32(ds1.Objects[i].Y))
|
||||
for objectIdx := range objectsWithPaths {
|
||||
sw.PushUint32(uint32(len(ds1.Objects[objectIdx].Paths)))
|
||||
sw.PushUint32(uint32(ds1.Objects[objectIdx].X))
|
||||
sw.PushUint32(uint32(ds1.Objects[objectIdx].Y))
|
||||
|
||||
for _, j := range ds1.Objects[i].Paths {
|
||||
sw.PushUint32(uint32(j.Position.X()))
|
||||
sw.PushUint32(uint32(j.Position.Y()))
|
||||
for _, path := range ds1.Objects[objectIdx].Paths {
|
||||
sw.PushUint32(uint32(path.Position.X()))
|
||||
sw.PushUint32(uint32(path.Position.Y()))
|
||||
|
||||
if ds1.Version >= v15 {
|
||||
sw.PushUint32(uint32(j.Action))
|
||||
if ds1.version >= v15 {
|
||||
sw.PushUint32(uint32(path.Action))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the ds1 version
|
||||
func (ds1 *DS1) Version() int {
|
||||
return int(ds1.version)
|
||||
}
|
||||
|
||||
// SetVersion sets the ds1 version, can not be negative.
|
||||
func (ds1 *DS1) SetVersion(v int) {
|
||||
if v < 0 {
|
||||
v = 0
|
||||
}
|
||||
|
||||
ds1.version = ds1version(v)
|
||||
}
|
||||
|
348
d2common/d2fileformats/d2ds1/ds1_layers.go
Normal file
348
d2common/d2fileformats/d2ds1/ds1_layers.go
Normal file
@ -0,0 +1,348 @@
|
||||
package d2ds1
|
||||
|
||||
const (
|
||||
maxWallLayers = 4
|
||||
maxFloorLayers = 2
|
||||
maxShadowLayers = 1
|
||||
maxSubstitutionLayers = 1
|
||||
)
|
||||
|
||||
type layerGroupType int
|
||||
|
||||
const (
|
||||
floorLayerGroup layerGroupType = iota
|
||||
wallLayerGroup
|
||||
shadowLayerGroup
|
||||
substitutionLayerGroup
|
||||
)
|
||||
|
||||
type layerGroup []*layer
|
||||
|
||||
type ds1Layers struct {
|
||||
width, height int
|
||||
Floors layerGroup
|
||||
Walls layerGroup
|
||||
Shadows layerGroup
|
||||
Substitutions layerGroup
|
||||
}
|
||||
|
||||
func (l *ds1Layers) ensureInit() {
|
||||
if l.Floors == nil {
|
||||
l.Floors = make(layerGroup, 0)
|
||||
}
|
||||
|
||||
if l.Walls == nil {
|
||||
l.Walls = make(layerGroup, 0)
|
||||
}
|
||||
|
||||
if l.Shadows == nil {
|
||||
l.Shadows = make(layerGroup, 0)
|
||||
}
|
||||
|
||||
if l.Substitutions == nil {
|
||||
l.Substitutions = make(layerGroup, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// removes nil layers from all layer groups
|
||||
func (l *ds1Layers) cull() {
|
||||
l.cullNilLayers(floorLayerGroup)
|
||||
l.cullNilLayers(wallLayerGroup)
|
||||
l.cullNilLayers(shadowLayerGroup)
|
||||
l.cullNilLayers(substitutionLayerGroup)
|
||||
}
|
||||
|
||||
// removes nil layers of given layer group type
|
||||
func (l *ds1Layers) cullNilLayers(t layerGroupType) {
|
||||
group := l.getLayersGroup(t)
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// from last to first layer, remove first encountered nil layer and restart the culling procedure.
|
||||
// exit culling procedure when no nil layers are found in entire group.
|
||||
culling:
|
||||
for {
|
||||
for idx := len(*group) - 1; idx > 0; idx-- {
|
||||
if (*group)[idx] == nil {
|
||||
*group = (*group)[:idx]
|
||||
continue culling
|
||||
}
|
||||
}
|
||||
|
||||
break culling // encountered no new nil layers
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ds1Layers) Size() (w, h int) {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
return l.width, l.height
|
||||
}
|
||||
|
||||
func (l *ds1Layers) SetSize(w, h int) {
|
||||
l.width, l.height = w, h
|
||||
|
||||
l.enforceSize(floorLayerGroup)
|
||||
l.enforceSize(wallLayerGroup)
|
||||
l.enforceSize(shadowLayerGroup)
|
||||
l.enforceSize(substitutionLayerGroup)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) enforceSize(t layerGroupType) {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
group := l.getLayersGroup(t)
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for idx := range *group {
|
||||
(*group)[idx].SetSize(l.width, l.height)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ds1Layers) Width() int {
|
||||
w, _ := l.Size()
|
||||
return w
|
||||
}
|
||||
|
||||
func (l *ds1Layers) SetWidth(w int) {
|
||||
l.SetSize(w, l.height)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) Height() int {
|
||||
_, h := l.Size()
|
||||
return h
|
||||
}
|
||||
|
||||
func (l *ds1Layers) SetHeight(h int) {
|
||||
l.SetSize(l.width, h)
|
||||
}
|
||||
|
||||
// generic push func for all layer types
|
||||
func (l *ds1Layers) push(t layerGroupType, layer *layer) {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
group := l.getLayersGroup(t)
|
||||
|
||||
max := getMaxGroupLen(t)
|
||||
|
||||
if len(*group) < max {
|
||||
*group = append(*group, layer)
|
||||
}
|
||||
}
|
||||
|
||||
// generic pop func for all layer types
|
||||
func (l *ds1Layers) pop(t layerGroupType) *layer {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
group := l.getLayersGroup(t)
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var theLayer *layer
|
||||
|
||||
// remove last layer of slice and return it
|
||||
if len(*group) > 0 {
|
||||
lastIdx := len(*group) - 1
|
||||
theLayer = (*group)[lastIdx]
|
||||
*group = (*group)[:lastIdx]
|
||||
|
||||
return theLayer
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ds1Layers) get(t layerGroupType, idx int) *layer {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
group := l.getLayersGroup(t)
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if idx >= len(*group) || idx < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*group)[idx]
|
||||
}
|
||||
|
||||
func (l *ds1Layers) insert(t layerGroupType, idx int, newLayer *layer) {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
if newLayer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
group := l.getLayersGroup(t)
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(*group)+1 > getMaxGroupLen(t) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(*group) == 0 {
|
||||
*group = append(*group, newLayer) // nolint:staticcheck // we possibly use group later
|
||||
return
|
||||
}
|
||||
|
||||
if l := len(*group) - 1; idx > l {
|
||||
idx = l
|
||||
}
|
||||
|
||||
// example:
|
||||
// suppose
|
||||
// idx=1
|
||||
// newLayer=c
|
||||
// existing layerGroup is [a, b]
|
||||
newGroup := append((*group)[:idx], append([]*layer{newLayer}, (*group)[idx:]...)...)
|
||||
*group = newGroup
|
||||
}
|
||||
|
||||
func (l *ds1Layers) delete(t layerGroupType, idx int) {
|
||||
l.ensureInit()
|
||||
l.cull()
|
||||
|
||||
group := l.getLayersGroup(t)
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if idx >= len(*group) || idx < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
(*group)[idx] = nil
|
||||
|
||||
l.cull()
|
||||
}
|
||||
|
||||
func (l *ds1Layers) GetFloor(idx int) *layer {
|
||||
return l.get(floorLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PushFloor(floor *layer) *ds1Layers {
|
||||
l.push(floorLayerGroup, floor)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PopFloor() *layer {
|
||||
return l.pop(floorLayerGroup)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) InsertFloor(idx int, newFloor *layer) {
|
||||
l.insert(floorLayerGroup, idx, newFloor)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) DeleteFloor(idx int) {
|
||||
l.delete(floorLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) GetWall(idx int) *layer {
|
||||
return l.get(wallLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PushWall(wall *layer) *ds1Layers {
|
||||
l.push(wallLayerGroup, wall)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PopWall() *layer {
|
||||
return l.pop(wallLayerGroup)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) InsertWall(idx int, newWall *layer) {
|
||||
l.insert(wallLayerGroup, idx, newWall)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) DeleteWall(idx int) {
|
||||
l.delete(wallLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) GetShadow(idx int) *layer {
|
||||
return l.get(shadowLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PushShadow(shadow *layer) *ds1Layers {
|
||||
l.push(shadowLayerGroup, shadow)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PopShadow() *layer {
|
||||
return l.pop(shadowLayerGroup)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) InsertShadow(idx int, newShadow *layer) {
|
||||
l.insert(shadowLayerGroup, idx, newShadow)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) DeleteShadow(idx int) {
|
||||
l.delete(shadowLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) GetSubstitution(idx int) *layer {
|
||||
return l.get(substitutionLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PushSubstitution(sub *layer) *ds1Layers {
|
||||
l.push(substitutionLayerGroup, sub)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *ds1Layers) PopSubstitution() *layer {
|
||||
return l.pop(substitutionLayerGroup)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) InsertSubstitution(idx int, newSubstitution *layer) {
|
||||
l.insert(substitutionLayerGroup, idx, newSubstitution)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) DeleteSubstitution(idx int) {
|
||||
l.delete(shadowLayerGroup, idx)
|
||||
}
|
||||
|
||||
func (l *ds1Layers) getLayersGroup(t layerGroupType) (group *layerGroup) {
|
||||
switch t {
|
||||
case floorLayerGroup:
|
||||
group = &l.Floors
|
||||
case wallLayerGroup:
|
||||
group = &l.Walls
|
||||
case shadowLayerGroup:
|
||||
group = &l.Shadows
|
||||
case substitutionLayerGroup:
|
||||
group = &l.Substitutions
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
func getMaxGroupLen(t layerGroupType) (max int) {
|
||||
switch t {
|
||||
case floorLayerGroup:
|
||||
max = maxFloorLayers
|
||||
case wallLayerGroup:
|
||||
max = maxWallLayers
|
||||
case shadowLayerGroup:
|
||||
max = maxShadowLayers
|
||||
case substitutionLayerGroup:
|
||||
max = maxSubstitutionLayers
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
333
d2common/d2fileformats/d2ds1/ds1_layers_test.go
Normal file
333
d2common/d2fileformats/d2ds1/ds1_layers_test.go
Normal file
@ -0,0 +1,333 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ds1Layers_Delete(t *testing.T) {
|
||||
t.Run("Floors", func(t *testing.T) {
|
||||
ds1LayersDelete(t, floorLayerGroup)
|
||||
})
|
||||
t.Run("Walls", func(t *testing.T) {
|
||||
ds1LayersDelete(t, wallLayerGroup)
|
||||
})
|
||||
t.Run("Shadows", func(t *testing.T) {
|
||||
ds1LayersDelete(t, shadowLayerGroup)
|
||||
})
|
||||
t.Run("Substitution", func(t *testing.T) {
|
||||
ds1LayersDelete(t, substitutionLayerGroup)
|
||||
})
|
||||
}
|
||||
|
||||
func ds1LayersDelete(t *testing.T, lt layerGroupType) {
|
||||
ds1 := DS1{}
|
||||
|
||||
ds1.ds1Layers = &ds1Layers{
|
||||
width: 1,
|
||||
height: 1,
|
||||
Floors: make(layerGroup, 1),
|
||||
Walls: make(layerGroup, 1),
|
||||
Shadows: make(layerGroup, 1),
|
||||
Substitutions: make(layerGroup, 1),
|
||||
}
|
||||
|
||||
var lg layerGroup
|
||||
|
||||
var del func(i int)
|
||||
|
||||
switch lt {
|
||||
case floorLayerGroup:
|
||||
del = func(i int) { ds1.DeleteFloor(0) }
|
||||
case wallLayerGroup:
|
||||
del = func(i int) { ds1.DeleteWall(0) }
|
||||
case shadowLayerGroup:
|
||||
del = func(i int) { ds1.DeleteShadow(0) }
|
||||
case substitutionLayerGroup:
|
||||
del = func(i int) { ds1.DeleteSubstitution(0) }
|
||||
default:
|
||||
t.Fatal("unknown layer type given")
|
||||
return
|
||||
}
|
||||
|
||||
del(0)
|
||||
|
||||
if len(lg) > 0 {
|
||||
t.Errorf("unexpected layer present after deletion")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ds1Layers_Get(t *testing.T) {
|
||||
t.Run("Floors", func(t *testing.T) {
|
||||
ds1LayersGet(t, floorLayerGroup)
|
||||
})
|
||||
t.Run("Walls", func(t *testing.T) {
|
||||
ds1LayersGet(t, wallLayerGroup)
|
||||
})
|
||||
t.Run("Shadows", func(t *testing.T) {
|
||||
ds1LayersGet(t, shadowLayerGroup)
|
||||
})
|
||||
t.Run("Substitution", func(t *testing.T) {
|
||||
ds1LayersGet(t, substitutionLayerGroup)
|
||||
})
|
||||
}
|
||||
|
||||
func ds1LayersGet(t *testing.T, lt layerGroupType) {
|
||||
ds1 := exampleData()
|
||||
|
||||
var get func(i int) *layer
|
||||
|
||||
switch lt {
|
||||
case floorLayerGroup:
|
||||
get = func(i int) *layer { return ds1.GetFloor(0) }
|
||||
case wallLayerGroup:
|
||||
get = func(i int) *layer { return ds1.GetWall(0) }
|
||||
case shadowLayerGroup:
|
||||
get = func(i int) *layer { return ds1.GetShadow(0) }
|
||||
case substitutionLayerGroup:
|
||||
get = func(i int) *layer { return ds1.GetSubstitution(0) }
|
||||
default:
|
||||
t.Fatal("unknown layer type given")
|
||||
return
|
||||
}
|
||||
|
||||
layer := get(0)
|
||||
|
||||
// example has nil substitution layer, maybe we need another test
|
||||
if layer == nil && lt != substitutionLayerGroup {
|
||||
t.Errorf("layer expected")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ds1Layers_Insert(t *testing.T) {
|
||||
t.Run("Floors", func(t *testing.T) {
|
||||
ds1LayersInsert(t, floorLayerGroup)
|
||||
})
|
||||
t.Run("Walls", func(t *testing.T) {
|
||||
ds1LayersInsert(t, wallLayerGroup)
|
||||
})
|
||||
t.Run("Shadows", func(t *testing.T) {
|
||||
ds1LayersInsert(t, shadowLayerGroup)
|
||||
})
|
||||
t.Run("Substitution", func(t *testing.T) {
|
||||
ds1LayersInsert(t, substitutionLayerGroup)
|
||||
})
|
||||
}
|
||||
|
||||
func ds1LayersInsert(t *testing.T, lt layerGroupType) {
|
||||
ds1 := DS1{}
|
||||
|
||||
layers := make([]*layer, getMaxGroupLen(lt)+1)
|
||||
|
||||
for i := range layers {
|
||||
i := i
|
||||
layers[i] = &layer{}
|
||||
layers[i].tiles = make(tileGrid, 1)
|
||||
layers[i].tiles[0] = make(tileRow, 1)
|
||||
layers[i].SetSize(3, 3)
|
||||
layers[i].tiles[0][0].Prop1 = byte(i)
|
||||
}
|
||||
|
||||
ds1.ds1Layers = &ds1Layers{}
|
||||
|
||||
var insert func(i int)
|
||||
|
||||
group := ds1.getLayersGroup(lt)
|
||||
|
||||
switch lt {
|
||||
case floorLayerGroup:
|
||||
insert = func(i int) { ds1.InsertFloor(0, layers[i]) }
|
||||
case wallLayerGroup:
|
||||
insert = func(i int) { ds1.InsertWall(0, layers[i]) }
|
||||
case shadowLayerGroup:
|
||||
insert = func(i int) { ds1.InsertShadow(0, layers[i]) }
|
||||
case substitutionLayerGroup:
|
||||
insert = func(i int) { ds1.InsertSubstitution(0, layers[i]) }
|
||||
default:
|
||||
t.Fatal("unknown layer type given")
|
||||
}
|
||||
|
||||
for i := range layers {
|
||||
insert(i)
|
||||
}
|
||||
|
||||
if len(*group) != getMaxGroupLen(lt) {
|
||||
t.Fatal("unexpected floor len after setting")
|
||||
}
|
||||
|
||||
idx := 0
|
||||
for i := len(layers) - 2; i > 0; i-- {
|
||||
if (*group)[idx].tiles[0][0].Prop1 != byte(i) {
|
||||
t.Fatal("unexpected tile inserted")
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ds1Layers_Pop(t *testing.T) {
|
||||
t.Run("Floor", func(t *testing.T) {
|
||||
ds1layerPop(floorLayerGroup, t)
|
||||
})
|
||||
|
||||
t.Run("Wall", func(t *testing.T) {
|
||||
ds1layerPop(wallLayerGroup, t)
|
||||
})
|
||||
|
||||
t.Run("Shadow", func(t *testing.T) {
|
||||
ds1layerPop(shadowLayerGroup, t)
|
||||
})
|
||||
|
||||
t.Run("Substitution", func(t *testing.T) {
|
||||
ds1layerPop(substitutionLayerGroup, t)
|
||||
})
|
||||
}
|
||||
|
||||
func ds1layerPop(lt layerGroupType, t *testing.T) {
|
||||
ds1 := exampleData()
|
||||
|
||||
var pop func() *layer
|
||||
|
||||
var numBefore, numAfter int
|
||||
|
||||
switch lt {
|
||||
case floorLayerGroup:
|
||||
numBefore = len(ds1.Floors)
|
||||
pop = func() *layer {
|
||||
l := ds1.PopFloor()
|
||||
numAfter = len(ds1.Floors)
|
||||
|
||||
return l
|
||||
}
|
||||
case wallLayerGroup:
|
||||
numBefore = len(ds1.Walls)
|
||||
pop = func() *layer {
|
||||
l := ds1.PopWall()
|
||||
numAfter = len(ds1.Walls)
|
||||
|
||||
return l
|
||||
}
|
||||
case shadowLayerGroup:
|
||||
numBefore = len(ds1.Shadows)
|
||||
pop = func() *layer {
|
||||
l := ds1.PopShadow()
|
||||
numAfter = len(ds1.Shadows)
|
||||
|
||||
return l
|
||||
}
|
||||
case substitutionLayerGroup:
|
||||
numBefore = len(ds1.Substitutions)
|
||||
pop = func() *layer {
|
||||
l := ds1.PopSubstitution()
|
||||
numAfter = len(ds1.Substitutions)
|
||||
|
||||
return l
|
||||
}
|
||||
default:
|
||||
t.Fatal("unknown layer type given")
|
||||
return
|
||||
}
|
||||
|
||||
attempts := 10
|
||||
|
||||
for attempts > 0 {
|
||||
attempts--
|
||||
|
||||
l := pop()
|
||||
|
||||
if l == nil && numBefore < numAfter {
|
||||
t.Fatal("popped nil layer, expected layer count to remain the same")
|
||||
}
|
||||
|
||||
if l != nil && numBefore <= numAfter {
|
||||
t.Fatal("popped non-nil, expected layer count to be lower")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ds1Layers_Push(t *testing.T) {
|
||||
t.Run("Floor", func(t *testing.T) {
|
||||
ds1layerPush(floorLayerGroup, t)
|
||||
})
|
||||
|
||||
t.Run("Wall", func(t *testing.T) {
|
||||
ds1layerPush(wallLayerGroup, t)
|
||||
})
|
||||
|
||||
t.Run("Shadow", func(t *testing.T) {
|
||||
ds1layerPush(shadowLayerGroup, t)
|
||||
})
|
||||
|
||||
t.Run("Substitution", func(t *testing.T) {
|
||||
ds1layerPush(substitutionLayerGroup, t)
|
||||
})
|
||||
}
|
||||
|
||||
// for all layer types, the test is the same
|
||||
// when we push a layer, we expect an increment, and when we push a bunch of times,
|
||||
// we expect to never exceed the max. we also expect to be able to retrieve a non-nil
|
||||
// layer after we push.
|
||||
func ds1layerPush(lt layerGroupType, t *testing.T) { //nolint:funlen // no biggie
|
||||
layers := &ds1Layers{}
|
||||
|
||||
// we need to set up some shit to handle the test in a generic way
|
||||
var push func()
|
||||
|
||||
var get func(idx int) *layer
|
||||
|
||||
var max int
|
||||
|
||||
var group *layerGroup
|
||||
|
||||
check := func(expected int) {
|
||||
actual := len(*group)
|
||||
got := get(expected - 1)
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("unexpected number of layers: expected %d, got %d", expected, actual)
|
||||
}
|
||||
|
||||
if got == nil {
|
||||
t.Fatal("got nil layer")
|
||||
}
|
||||
}
|
||||
|
||||
switch lt {
|
||||
case floorLayerGroup:
|
||||
push = func() { layers.PushFloor(&layer{}) }
|
||||
get = layers.GetFloor
|
||||
max = maxFloorLayers
|
||||
group = &layers.Floors
|
||||
case wallLayerGroup:
|
||||
push = func() { layers.PushWall(&layer{}) }
|
||||
get = layers.GetWall
|
||||
max = maxWallLayers
|
||||
group = &layers.Walls
|
||||
case shadowLayerGroup:
|
||||
push = func() { layers.PushShadow(&layer{}) }
|
||||
get = layers.GetShadow
|
||||
max = maxShadowLayers
|
||||
group = &layers.Shadows
|
||||
case substitutionLayerGroup:
|
||||
push = func() { layers.PushSubstitution(&layer{}) }
|
||||
get = layers.GetSubstitution
|
||||
max = maxSubstitutionLayers
|
||||
group = &layers.Substitutions
|
||||
default:
|
||||
t.Fatal("unknown layer type given")
|
||||
}
|
||||
|
||||
// push one time, we expect a single layer to exist
|
||||
push()
|
||||
check(1)
|
||||
|
||||
// if we push a bunch of times, we expect to not exceed the max
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
push()
|
||||
check(max)
|
||||
}
|
247
d2common/d2fileformats/d2ds1/ds1_test.go
Normal file
247
d2common/d2fileformats/d2ds1/ds1_test.go
Normal file
@ -0,0 +1,247 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2path"
|
||||
)
|
||||
|
||||
func exampleData() *DS1 { //nolint:funlen // not a big deal if this is long func
|
||||
exampleFloor1 := Tile{
|
||||
// common fields
|
||||
tileCommonFields: tileCommonFields{
|
||||
Prop1: 2,
|
||||
Sequence: 89,
|
||||
Unknown1: 123,
|
||||
Style: 20,
|
||||
Unknown2: 53,
|
||||
HiddenBytes: 1,
|
||||
RandomIndex: 2,
|
||||
YAdjust: 21,
|
||||
},
|
||||
tileFloorShadowFields: tileFloorShadowFields{
|
||||
Animated: false,
|
||||
},
|
||||
}
|
||||
|
||||
exampleFloor2 := Tile{
|
||||
// common fields
|
||||
tileCommonFields: tileCommonFields{
|
||||
Prop1: 3,
|
||||
Sequence: 89,
|
||||
Unknown1: 213,
|
||||
Style: 28,
|
||||
Unknown2: 53,
|
||||
HiddenBytes: 7,
|
||||
RandomIndex: 3,
|
||||
YAdjust: 28,
|
||||
},
|
||||
tileFloorShadowFields: tileFloorShadowFields{
|
||||
Animated: true,
|
||||
},
|
||||
}
|
||||
|
||||
exampleWall1 := Tile{
|
||||
// common fields
|
||||
tileCommonFields: tileCommonFields{
|
||||
Prop1: 3,
|
||||
Sequence: 89,
|
||||
Unknown1: 213,
|
||||
Style: 28,
|
||||
Unknown2: 53,
|
||||
HiddenBytes: 7,
|
||||
RandomIndex: 3,
|
||||
YAdjust: 28,
|
||||
},
|
||||
tileWallFields: tileWallFields{
|
||||
Type: d2enum.TileRightWall,
|
||||
},
|
||||
}
|
||||
|
||||
exampleWall2 := Tile{
|
||||
// common fields
|
||||
tileCommonFields: tileCommonFields{
|
||||
Prop1: 3,
|
||||
Sequence: 93,
|
||||
Unknown1: 193,
|
||||
Style: 17,
|
||||
Unknown2: 13,
|
||||
HiddenBytes: 1,
|
||||
RandomIndex: 1,
|
||||
YAdjust: 22,
|
||||
},
|
||||
tileWallFields: tileWallFields{
|
||||
Type: d2enum.TileLeftWall,
|
||||
},
|
||||
}
|
||||
|
||||
exampleShadow := Tile{
|
||||
// common fields
|
||||
tileCommonFields: tileCommonFields{
|
||||
Prop1: 3,
|
||||
Sequence: 93,
|
||||
Unknown1: 173,
|
||||
Style: 17,
|
||||
Unknown2: 12,
|
||||
HiddenBytes: 1,
|
||||
RandomIndex: 1,
|
||||
YAdjust: 22,
|
||||
},
|
||||
tileFloorShadowFields: tileFloorShadowFields{
|
||||
Animated: false,
|
||||
},
|
||||
}
|
||||
|
||||
result := &DS1{
|
||||
ds1Layers: &ds1Layers{
|
||||
width: 2,
|
||||
height: 2,
|
||||
Floors: layerGroup{
|
||||
// number of floors (one floor)
|
||||
{
|
||||
// tile grid = []tileRow
|
||||
tiles: tileGrid{
|
||||
// tile rows = []Tile
|
||||
// 2x2 tiles
|
||||
{
|
||||
exampleFloor1,
|
||||
exampleFloor2,
|
||||
},
|
||||
{
|
||||
exampleFloor2,
|
||||
exampleFloor1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Walls: layerGroup{
|
||||
// number of walls (two floors)
|
||||
{
|
||||
// tile grid = []tileRow
|
||||
tiles: tileGrid{
|
||||
// tile rows = []Tile
|
||||
// 2x2 tiles
|
||||
{
|
||||
exampleWall1,
|
||||
exampleWall2,
|
||||
},
|
||||
{
|
||||
exampleWall2,
|
||||
exampleWall1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// tile grid = []tileRow
|
||||
tiles: tileGrid{
|
||||
// tile rows = []Tile
|
||||
// 2x2 tiles
|
||||
{
|
||||
exampleWall1,
|
||||
exampleWall2,
|
||||
},
|
||||
{
|
||||
exampleWall2,
|
||||
exampleWall1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Shadows: layerGroup{
|
||||
// number of shadows (always 1)
|
||||
{
|
||||
// tile grid = []tileRow
|
||||
tiles: tileGrid{
|
||||
// tile rows = []Tile
|
||||
// 2x2 tiles
|
||||
{
|
||||
exampleShadow,
|
||||
exampleShadow,
|
||||
},
|
||||
{
|
||||
exampleShadow,
|
||||
exampleShadow,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Files: []string{"a.dt1", "bfile.dt1"},
|
||||
Objects: []Object{
|
||||
{0, 0, 0, 0, 0, nil},
|
||||
{0, 1, 0, 0, 0, []d2path.Path{{}}},
|
||||
{0, 2, 0, 0, 0, nil},
|
||||
{0, 3, 0, 0, 0, nil},
|
||||
},
|
||||
substitutionGroups: nil,
|
||||
version: 17,
|
||||
Act: 1,
|
||||
SubstitutionType: 0,
|
||||
unknown2: 20,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func TestDS1_MarshalUnmarshal(t *testing.T) {
|
||||
ds1 := exampleData()
|
||||
|
||||
data := ds1.Marshal()
|
||||
|
||||
_, loadErr := Unmarshal(data)
|
||||
if loadErr != nil {
|
||||
t.Error(loadErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDS1_Version(t *testing.T) {
|
||||
ds1 := exampleData()
|
||||
|
||||
v := ds1.Version()
|
||||
|
||||
ds1.SetVersion(v + 1)
|
||||
|
||||
if ds1.Version() == v {
|
||||
t.Fatal("expected different ds1 version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDS1_SetSize(t *testing.T) {
|
||||
ds1 := exampleData()
|
||||
|
||||
w, h := ds1.Size()
|
||||
|
||||
ds1.SetSize(w+1, h-1)
|
||||
|
||||
w2, h2 := ds1.Size()
|
||||
|
||||
if w2 != (w+1) || h2 != (h-1) {
|
||||
t.Fatal("unexpected width/height after setting size")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getLayerSchema(t *testing.T) {
|
||||
ds1 := exampleData()
|
||||
|
||||
expected := map[int]layerStreamType{
|
||||
0: layerStreamWall1,
|
||||
1: layerStreamOrientation1,
|
||||
2: layerStreamWall2,
|
||||
3: layerStreamOrientation2,
|
||||
4: layerStreamFloor1,
|
||||
5: layerStreamShadow1,
|
||||
}
|
||||
|
||||
schema := ds1.getLayerSchema()
|
||||
|
||||
if len(schema) != len(expected) {
|
||||
t.Fatal("unexpected schema length")
|
||||
}
|
||||
|
||||
for idx := range expected {
|
||||
if schema[idx] != expected[idx] {
|
||||
t.Fatal("unexpected layer type in schema")
|
||||
}
|
||||
}
|
||||
}
|
72
d2common/d2fileformats/d2ds1/ds1_version.go
Normal file
72
d2common/d2fileformats/d2ds1/ds1_version.go
Normal file
@ -0,0 +1,72 @@
|
||||
package d2ds1
|
||||
|
||||
type ds1version int
|
||||
|
||||
const (
|
||||
v3 ds1version = 3
|
||||
v4 ds1version = 4
|
||||
v7 ds1version = 7
|
||||
v8 ds1version = 8
|
||||
v9 ds1version = 9
|
||||
v10 ds1version = 10
|
||||
v12 ds1version = 12
|
||||
v13 ds1version = 13
|
||||
v14 ds1version = 14
|
||||
v15 ds1version = 15
|
||||
v16 ds1version = 16
|
||||
v18 ds1version = 18
|
||||
)
|
||||
|
||||
func (v ds1version) hasUnknown1Bytes() bool {
|
||||
// just after the header will be some meaningless (?) bytes
|
||||
return v >= v9 && v <= v13
|
||||
}
|
||||
|
||||
func (v ds1version) hasUnknown2Bytes() bool {
|
||||
return v >= v18
|
||||
}
|
||||
|
||||
func (v ds1version) specifiesAct() bool {
|
||||
// in the header
|
||||
return v >= v8
|
||||
}
|
||||
|
||||
func (v ds1version) specifiesSubstitutionType() bool {
|
||||
// in the header
|
||||
return v >= v10
|
||||
}
|
||||
|
||||
func (v ds1version) hasStandardLayers() bool {
|
||||
// 1 of each layer, very simple ds1
|
||||
return v < v4
|
||||
}
|
||||
|
||||
func (v ds1version) specifiesWalls() bool {
|
||||
// just after header, specifies number of Walls
|
||||
return v >= v4
|
||||
}
|
||||
|
||||
func (v ds1version) specifiesFloors() bool {
|
||||
// just after header, specifies number of Floors
|
||||
return v >= v16
|
||||
}
|
||||
|
||||
func (v ds1version) hasFileList() bool {
|
||||
return v >= v3
|
||||
}
|
||||
|
||||
func (v ds1version) hasObjects() bool {
|
||||
return v >= v3
|
||||
}
|
||||
|
||||
func (v ds1version) hasSubstitutions() bool {
|
||||
return v >= v12
|
||||
}
|
||||
|
||||
func (v ds1version) specifiesNPCs() bool {
|
||||
return v > v14
|
||||
}
|
||||
|
||||
func (v ds1version) specifiesNPCActions() bool {
|
||||
return v > v15
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
)
|
||||
|
||||
const (
|
||||
prop1Bitmask = 0x000000FF
|
||||
prop1Offset = 0
|
||||
prop1Length = 8
|
||||
|
||||
sequenceBitmask = 0x00003F00
|
||||
sequenceOffset = 8
|
||||
sequenceLength = 6
|
||||
|
||||
unknown1Bitmask = 0x000FC000
|
||||
unknown1Offset = 14
|
||||
unknown1Length = 6
|
||||
|
||||
styleBitmask = 0x03F00000
|
||||
styleOffset = 20
|
||||
styleLength = 6
|
||||
|
||||
unknown2Bitmask = 0x7C000000
|
||||
unknown2Offset = 26
|
||||
unknown2Length = 5
|
||||
|
||||
hiddenBitmask = 0x80000000
|
||||
hiddenOffset = 31
|
||||
hiddenLength = 1
|
||||
)
|
||||
|
||||
// FloorShadowRecord represents a floor or shadow record in a DS1 file.
|
||||
type FloorShadowRecord struct {
|
||||
Prop1 byte
|
||||
Sequence byte
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
HiddenBytes byte
|
||||
RandomIndex byte
|
||||
Animated bool
|
||||
YAdjust int
|
||||
}
|
||||
|
||||
// Hidden returns if floor/shadow is hidden
|
||||
func (f *FloorShadowRecord) Hidden() bool {
|
||||
return f.HiddenBytes > 0
|
||||
}
|
||||
|
||||
// Decode decodes floor-shadow record
|
||||
func (f *FloorShadowRecord) Decode(dw uint32) {
|
||||
f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
|
||||
f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
|
||||
f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
|
||||
f.Style = byte((dw & styleBitmask) >> styleOffset)
|
||||
f.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
|
||||
f.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset)
|
||||
}
|
||||
|
||||
// Encode adds Floor's bits to stream writter given
|
||||
func (f *FloorShadowRecord) Encode(sw *d2datautils.StreamWriter) {
|
||||
sw.PushBits32(uint32(f.Prop1), prop1Length)
|
||||
sw.PushBits32(uint32(f.Sequence), sequenceLength)
|
||||
sw.PushBits32(uint32(f.Unknown1), unknown1Length)
|
||||
sw.PushBits32(uint32(f.Style), styleLength)
|
||||
sw.PushBits32(uint32(f.Unknown2), unknown2Length)
|
||||
sw.PushBits32(uint32(f.HiddenBytes), hiddenLength)
|
||||
}
|
129
d2common/d2fileformats/d2ds1/layer.go
Normal file
129
d2common/d2fileformats/d2ds1/layer.go
Normal file
@ -0,0 +1,129 @@
|
||||
package d2ds1
|
||||
|
||||
// layerStreamType represents a layer stream type
|
||||
type layerStreamType int
|
||||
|
||||
// Layer stream types
|
||||
const (
|
||||
layerStreamWall1 layerStreamType = iota
|
||||
layerStreamWall2
|
||||
layerStreamWall3
|
||||
layerStreamWall4
|
||||
layerStreamOrientation1
|
||||
layerStreamOrientation2
|
||||
layerStreamOrientation3
|
||||
layerStreamOrientation4
|
||||
layerStreamFloor1
|
||||
layerStreamFloor2
|
||||
layerStreamShadow1
|
||||
layerStreamSubstitute1
|
||||
)
|
||||
|
||||
type tileRow []Tile // index is x coordinate
|
||||
type tileGrid []tileRow // index is y coordinate
|
||||
|
||||
type layer struct {
|
||||
tiles tileGrid
|
||||
}
|
||||
|
||||
func (l *layer) Tile(x, y int) *Tile {
|
||||
if l.Width() < x || l.Height() < y {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &l.tiles[y][x]
|
||||
}
|
||||
|
||||
func (l *layer) SetTile(x, y int, t *Tile) {
|
||||
if l.Width() > x || l.Height() > y {
|
||||
return
|
||||
}
|
||||
|
||||
l.tiles[y][x] = *t
|
||||
}
|
||||
|
||||
func (l *layer) Width() int {
|
||||
if len(l.tiles[0]) < 1 {
|
||||
l.SetWidth(1)
|
||||
}
|
||||
|
||||
return len(l.tiles[0])
|
||||
}
|
||||
|
||||
func (l *layer) SetWidth(w int) *layer {
|
||||
if w < 1 {
|
||||
w = 1
|
||||
}
|
||||
|
||||
// ensure at least one row
|
||||
if len(l.tiles) < 1 {
|
||||
l.tiles = make(tileGrid, 1)
|
||||
}
|
||||
|
||||
// create/copy tiles as required to satisfy width
|
||||
for y := range l.tiles {
|
||||
if (w - len(l.tiles[y])) == 0 { // if requested width same as row width
|
||||
continue
|
||||
}
|
||||
|
||||
tmpRow := make(tileRow, w)
|
||||
|
||||
for x := range tmpRow {
|
||||
if x < len(l.tiles[y]) { // if tile exists
|
||||
tmpRow[x] = l.tiles[y][x] // copy it
|
||||
}
|
||||
}
|
||||
|
||||
l.tiles[y] = tmpRow
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *layer) Height() int {
|
||||
if len(l.tiles) < 1 {
|
||||
l.SetHeight(1)
|
||||
}
|
||||
|
||||
return len(l.tiles)
|
||||
}
|
||||
|
||||
func (l *layer) SetHeight(h int) *layer {
|
||||
if h < 1 {
|
||||
h = 1
|
||||
}
|
||||
|
||||
// make tmpGrid to move existing tiles into
|
||||
tmpGrid := make(tileGrid, h)
|
||||
|
||||
for y := range tmpGrid {
|
||||
tmpGrid[y] = make(tileRow, l.Width())
|
||||
}
|
||||
|
||||
// move existing tiles over
|
||||
for y := range l.tiles {
|
||||
if y >= len(tmpGrid) {
|
||||
continue
|
||||
}
|
||||
|
||||
for x := range l.tiles[y] {
|
||||
if x >= len(tmpGrid[y]) {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpGrid[y][x] = l.tiles[y][x]
|
||||
}
|
||||
}
|
||||
|
||||
l.tiles = tmpGrid
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *layer) Size() (w, h int) {
|
||||
return l.Width(), l.Height()
|
||||
}
|
||||
|
||||
func (l *layer) SetSize(w, h int) *layer {
|
||||
return l.SetWidth(w).SetHeight(h)
|
||||
}
|
29
d2common/d2fileformats/d2ds1/layer_test.go
Normal file
29
d2common/d2fileformats/d2ds1/layer_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package d2ds1
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_layers(t *testing.T) {
|
||||
const (
|
||||
fmtWidthHeightError = "unexpected wall layer width/height: %dx%d"
|
||||
)
|
||||
|
||||
l := &layer{}
|
||||
|
||||
l.SetSize(0, 0)
|
||||
|
||||
if l.Width() != 1 || l.Height() != 1 {
|
||||
t.Fatalf(fmtWidthHeightError, l.Width(), l.Height())
|
||||
}
|
||||
|
||||
l.SetSize(4, 5)
|
||||
|
||||
if l.Width() != 4 || l.Height() != 5 {
|
||||
t.Fatalf(fmtWidthHeightError, l.Width(), l.Height())
|
||||
}
|
||||
|
||||
l.SetSize(4, 3)
|
||||
|
||||
if l.Width() != 4 || l.Height() != 3 {
|
||||
t.Fatalf(fmtWidthHeightError, l.Width(), l.Height())
|
||||
}
|
||||
}
|
@ -13,3 +13,13 @@ type Object struct {
|
||||
Flags int
|
||||
Paths []d2path.Path
|
||||
}
|
||||
|
||||
// Equals checks if this Object is equivalent to the given Object
|
||||
func (o *Object) Equals(other *Object) bool {
|
||||
return o.Type == other.Type &&
|
||||
o.ID == other.ID &&
|
||||
o.X == other.X &&
|
||||
o.Y == other.Y &&
|
||||
o.Flags == other.Flags &&
|
||||
len(o.Paths) == len(other.Paths)
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
package d2ds1
|
||||
|
||||
// SubstitutionRecord represents a substitution record in a DS1 file.
|
||||
type SubstitutionRecord struct {
|
||||
Unknown uint32
|
||||
}
|
127
d2common/d2fileformats/d2ds1/tile.go
Normal file
127
d2common/d2fileformats/d2ds1/tile.go
Normal file
@ -0,0 +1,127 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
const (
|
||||
prop1Bitmask = 0x000000FF
|
||||
prop1Offset = 0
|
||||
prop1Length = 8
|
||||
|
||||
sequenceBitmask = 0x00003F00
|
||||
sequenceOffset = 8
|
||||
sequenceLength = 6
|
||||
|
||||
unknown1Bitmask = 0x000FC000
|
||||
unknown1Offset = 14
|
||||
unknown1Length = 6
|
||||
|
||||
styleBitmask = 0x03F00000
|
||||
styleOffset = 20
|
||||
styleLength = 6
|
||||
|
||||
unknown2Bitmask = 0x7C000000
|
||||
unknown2Offset = 26
|
||||
unknown2Length = 5
|
||||
|
||||
hiddenBitmask = 0x80000000
|
||||
hiddenOffset = 31
|
||||
hiddenLength = 1
|
||||
)
|
||||
|
||||
type tileCommonFields struct {
|
||||
Prop1 byte
|
||||
Sequence byte
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
HiddenBytes byte
|
||||
RandomIndex byte
|
||||
YAdjust int
|
||||
}
|
||||
|
||||
type tileFloorShadowFields struct {
|
||||
Animated bool
|
||||
}
|
||||
|
||||
type tileSubstitutionFields struct {
|
||||
Substitution uint32 // unknown
|
||||
}
|
||||
|
||||
type tileWallFields struct {
|
||||
Type d2enum.TileType
|
||||
Zero byte
|
||||
}
|
||||
|
||||
// Tile represents a tile record in a DS1 file.
|
||||
type Tile struct {
|
||||
tileCommonFields
|
||||
tileSubstitutionFields
|
||||
tileWallFields
|
||||
tileFloorShadowFields
|
||||
}
|
||||
|
||||
// Hidden returns if wall is hidden
|
||||
func (t *Tile) Hidden() bool {
|
||||
return t.HiddenBytes > 0
|
||||
}
|
||||
|
||||
// DecodeWall decodes as a wall record
|
||||
func (t *Tile) DecodeWall(dw uint32) {
|
||||
t.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
|
||||
t.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
|
||||
t.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
|
||||
t.Style = byte((dw & styleBitmask) >> styleOffset)
|
||||
t.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
|
||||
t.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset)
|
||||
}
|
||||
|
||||
// EncodeWall adds wall's record's bytes into stream writer given
|
||||
func (t *Tile) EncodeWall(sw *d2datautils.StreamWriter) {
|
||||
sw.PushBits32(uint32(t.Prop1), prop1Length)
|
||||
sw.PushBits32(uint32(t.Sequence), sequenceLength)
|
||||
sw.PushBits32(uint32(t.Unknown1), unknown1Length)
|
||||
sw.PushBits32(uint32(t.Style), styleLength)
|
||||
sw.PushBits32(uint32(t.Unknown2), unknown2Length)
|
||||
sw.PushBits32(uint32(t.HiddenBytes), hiddenLength)
|
||||
}
|
||||
|
||||
func (t *Tile) decodeFloorShadow(dw uint32) {
|
||||
t.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
|
||||
t.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
|
||||
t.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
|
||||
t.Style = byte((dw & styleBitmask) >> styleOffset)
|
||||
t.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
|
||||
t.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset)
|
||||
}
|
||||
|
||||
func (t *Tile) encodeFloorShadow(sw *d2datautils.StreamWriter) {
|
||||
sw.PushBits32(uint32(t.Prop1), prop1Length)
|
||||
sw.PushBits32(uint32(t.Sequence), sequenceLength)
|
||||
sw.PushBits32(uint32(t.Unknown1), unknown1Length)
|
||||
sw.PushBits32(uint32(t.Style), styleLength)
|
||||
sw.PushBits32(uint32(t.Unknown2), unknown2Length)
|
||||
sw.PushBits32(uint32(t.HiddenBytes), hiddenLength)
|
||||
}
|
||||
|
||||
// DecodeFloor decodes as a floor record
|
||||
func (t *Tile) DecodeFloor(dw uint32) {
|
||||
t.decodeFloorShadow(dw)
|
||||
}
|
||||
|
||||
// EncodeFloor adds Floor's bits to stream writer given
|
||||
func (t *Tile) EncodeFloor(sw *d2datautils.StreamWriter) {
|
||||
t.encodeFloorShadow(sw)
|
||||
}
|
||||
|
||||
// DecodeShadow decodes as a shadow record
|
||||
func (t *Tile) DecodeShadow(dw uint32) {
|
||||
t.decodeFloorShadow(dw)
|
||||
}
|
||||
|
||||
// EncodeShadow adds shadow's bits to stream writer given
|
||||
func (t *Tile) EncodeShadow(sw *d2datautils.StreamWriter) {
|
||||
t.encodeFloorShadow(sw)
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package d2ds1
|
||||
|
||||
// TileRecord represents a tile record in a DS1 file.
|
||||
type TileRecord struct {
|
||||
Floors []FloorShadowRecord // Collection of floor records
|
||||
Walls []WallRecord // Collection of wall records
|
||||
Shadows []FloorShadowRecord // Collection of shadow records
|
||||
Substitutions []SubstitutionRecord // Collection of substitutions
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
// WallRecord represents a wall record.
|
||||
type WallRecord struct {
|
||||
Type d2enum.TileType
|
||||
Zero byte
|
||||
Prop1 byte
|
||||
Sequence byte
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
HiddenBytes byte
|
||||
RandomIndex byte
|
||||
YAdjust int
|
||||
}
|
||||
|
||||
// Hidden returns if wall is hidden
|
||||
func (w *WallRecord) Hidden() bool {
|
||||
return w.HiddenBytes > 0
|
||||
}
|
||||
|
||||
// Decode decodes wall record
|
||||
func (w *WallRecord) Decode(dw uint32) {
|
||||
w.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
|
||||
w.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
|
||||
w.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
|
||||
w.Style = byte((dw & styleBitmask) >> styleOffset)
|
||||
w.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
|
||||
w.HiddenBytes = byte((dw & hiddenBitmask) >> hiddenOffset)
|
||||
}
|
||||
|
||||
// Encode adds wall's record's bytes into stream writer given
|
||||
func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) {
|
||||
sw.PushBits32(uint32(w.Prop1), prop1Length)
|
||||
sw.PushBits32(uint32(w.Sequence), sequenceLength)
|
||||
sw.PushBits32(uint32(w.Unknown1), unknown1Length)
|
||||
sw.PushBits32(uint32(w.Style), styleLength)
|
||||
sw.PushBits32(uint32(w.Unknown2), unknown2Length)
|
||||
sw.PushBits32(uint32(w.HiddenBytes), hiddenLength)
|
||||
}
|
@ -517,9 +517,9 @@ func (am *AssetManager) LoadDS1(ds1Path string) (*d2ds1.DS1, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1, err := d2ds1.LoadDS1(fileData)
|
||||
ds1, err := d2ds1.Unmarshal(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("loading ds1 file %s: %v", "/data/global/tiles"+ds1Path, err)
|
||||
}
|
||||
|
||||
if err := am.dt1s.Insert(ds1Path, ds1, defaultCacheEntryWeight); err != nil {
|
||||
|
@ -388,7 +388,7 @@ func (box *Box) setupTitle(sectionHeight int) error {
|
||||
}
|
||||
|
||||
func (box *Box) setupOptions(sectionHeight int) error {
|
||||
box.contentLayout.SetSize(box.width, (box.height - sectionHeight))
|
||||
box.contentLayout.SetSize(box.width, box.height-sectionHeight)
|
||||
|
||||
if !box.disableBorder {
|
||||
cornerLeft, err := box.uiManager.NewSprite(d2resource.BoxPieces, d2resource.PaletteSky)
|
||||
|
@ -115,7 +115,7 @@ func (m *MapEngine) AddDS1(fileName string) {
|
||||
|
||||
ds1, err := m.asset.LoadDS1(fileName)
|
||||
if err != nil {
|
||||
m.Error(err.Error())
|
||||
m.Fatalf("Loading ds1: %v", err)
|
||||
}
|
||||
|
||||
for idx := range ds1.Files {
|
||||
|
@ -2,13 +2,14 @@ package d2mapengine
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||
)
|
||||
|
||||
// MapTile is a tile placed on the map
|
||||
type MapTile struct {
|
||||
Components d2ds1.TileRecord
|
||||
Components d2mapstamp.Tile
|
||||
RegionType d2enum.RegionIdType
|
||||
SubTiles [25]d2dt1.SubTileFlags
|
||||
}
|
||||
|
@ -284,7 +284,9 @@ func (g *MapGenerator) generateWilderness1Contents(rect d2geom.Rectangle) {
|
||||
for x := 0; x < rect.Width; x++ {
|
||||
tile := g.engine.Tile(rect.Left+x, rect.Top+y)
|
||||
tile.RegionType = d2enum.RegionIdType(levelDetails.LevelType)
|
||||
tile.Components.Floors = []d2ds1.FloorShadowRecord{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass
|
||||
floorTile := d2ds1.Tile{}
|
||||
floorTile.Prop1 = 1
|
||||
tile.Components.Floors = []d2ds1.Tile{floorTile} // wildernessGrass
|
||||
tile.PrepareTile(x, y, g.engine)
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ func (mr *MapRenderer) renderTilePass3(tile *d2mapengine.MapTile, target d2inter
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2interface.Surface) {
|
||||
func (mr *MapRenderer) renderFloor(tile d2ds1.Tile, target d2interface.Surface) {
|
||||
var img d2interface.Surface
|
||||
if !tile.Animated {
|
||||
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
|
||||
@ -415,7 +415,7 @@ func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2interf
|
||||
target.Render(img)
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2interface.Surface) {
|
||||
func (mr *MapRenderer) renderWall(tile d2ds1.Tile, viewport *Viewport, target d2interface.Surface) {
|
||||
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex)
|
||||
if img == nil {
|
||||
mr.Warningf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type)
|
||||
@ -431,7 +431,7 @@ func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, tar
|
||||
target.Render(img)
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2interface.Surface) {
|
||||
func (mr *MapRenderer) renderShadow(tile d2ds1.Tile, target d2interface.Surface) {
|
||||
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex)
|
||||
if img == nil {
|
||||
mr.Warningf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence)
|
||||
|
@ -53,7 +53,7 @@ func (mr *MapRenderer) generateTileCache() {
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) {
|
||||
func (mr *MapRenderer) generateFloorCache(tile *d2ds1.Tile) {
|
||||
tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), 0)
|
||||
|
||||
var tileData []*d2dt1.Tile
|
||||
@ -110,7 +110,7 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) {
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) {
|
||||
func (mr *MapRenderer) generateShadowCache(tile *d2ds1.Tile) {
|
||||
tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), d2enum.TileShadow)
|
||||
|
||||
var tileData *d2dt1.Tile
|
||||
@ -153,7 +153,7 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) {
|
||||
mr.setImageCacheRecord(tile.Style, tile.Sequence, d2enum.TileShadow, tile.RandomIndex, image)
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) {
|
||||
func (mr *MapRenderer) generateWallCache(tile *d2ds1.Tile) {
|
||||
tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), tile.Type)
|
||||
|
||||
var tileData *d2dt1.Tile
|
||||
|
@ -87,7 +87,7 @@ func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fil
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stamp.ds1, err = d2ds1.LoadDS1(fileData)
|
||||
stamp.ds1, err = d2ds1.Unmarshal(fileData)
|
||||
if err != nil {
|
||||
f.Error(err.Error())
|
||||
return nil
|
||||
|
@ -31,7 +31,7 @@ type Stamp struct {
|
||||
|
||||
// Size returns the size of the stamp in tiles.
|
||||
func (mr *Stamp) Size() d2geom.Size {
|
||||
return d2geom.Size{Width: int(mr.ds1.Width), Height: int(mr.ds1.Height)}
|
||||
return d2geom.Size{Width: mr.ds1.Width(), Height: mr.ds1.Height()}
|
||||
}
|
||||
|
||||
// LevelPreset returns the level preset ID.
|
||||
@ -54,9 +54,42 @@ func (mr *Stamp) RegionPath() string {
|
||||
return mr.regionPath
|
||||
}
|
||||
|
||||
// Tile represents a map tile, which can have a variable amount of floors, walls, shadows as layers.
|
||||
// Typically, there will be an Orientation layer for each wall layer.
|
||||
type Tile struct {
|
||||
Walls []d2ds1.Tile
|
||||
Orientations []d2ds1.Tile
|
||||
Floors []d2ds1.Tile
|
||||
Shadows []d2ds1.Tile
|
||||
Substitutions []d2ds1.Tile
|
||||
}
|
||||
|
||||
// Tile returns the tile at the given x and y tile coordinates.
|
||||
func (mr *Stamp) Tile(x, y int) *d2ds1.TileRecord {
|
||||
return &mr.ds1.Tiles[y][x]
|
||||
func (mr *Stamp) Tile(x, y int) *Tile {
|
||||
t := &Tile{
|
||||
Walls: make([]d2ds1.Tile, len(mr.ds1.Walls)),
|
||||
Floors: make([]d2ds1.Tile, len(mr.ds1.Floors)),
|
||||
Shadows: make([]d2ds1.Tile, len(mr.ds1.Shadows)),
|
||||
Substitutions: make([]d2ds1.Tile, len(mr.ds1.Substitutions)),
|
||||
}
|
||||
|
||||
for idx := range mr.ds1.Walls {
|
||||
t.Walls[idx] = *mr.ds1.Walls[idx].Tile(x, y)
|
||||
}
|
||||
|
||||
for idx := range mr.ds1.Floors {
|
||||
t.Floors[idx] = *mr.ds1.Floors[idx].Tile(x, y)
|
||||
}
|
||||
|
||||
for idx := range mr.ds1.Shadows {
|
||||
t.Shadows[idx] = *mr.ds1.Shadows[idx].Tile(x, y)
|
||||
}
|
||||
|
||||
for idx := range mr.ds1.Substitutions {
|
||||
t.Substitutions[idx] = *mr.ds1.Substitutions[idx].Tile(x, y)
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// TileData returns the tile data for the tile with given style, sequence and type.
|
||||
@ -109,7 +142,6 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity
|
||||
// nolint:gomnd // constant
|
||||
entity, err := mr.entity.NewObject((tileOffsetX*5)+object.X,
|
||||
(tileOffsetY*5)+object.Y, objectRecord, d2resource.PaletteUnits)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ func levelPresetLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||
Scan: d.Number("Scan") == 1,
|
||||
Pops: d.Number("Pops"),
|
||||
PopPad: d.Number("PopPad"),
|
||||
FileCount: d.Number("Files"),
|
||||
FileCount: d.Number("files"),
|
||||
Files: [6]string{
|
||||
d.String("File1"),
|
||||
d.String("File2"),
|
||||
|
4
main.go
4
main.go
@ -8,11 +8,11 @@ import (
|
||||
|
||||
// GitBranch is set by the CI build process to the name of the branch
|
||||
//nolint:gochecknoglobals // This is filled in by the build system
|
||||
var GitBranch string = "local"
|
||||
var GitBranch = "local"
|
||||
|
||||
// GitCommit is set by the CI build process to the commit hash
|
||||
//nolint:gochecknoglobals // This is filled in by the build system
|
||||
var GitCommit string = "build"
|
||||
var GitCommit = "build"
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
@ -6,7 +6,7 @@
|
||||
//
|
||||
// Usage:
|
||||
// First run `go install extract-mpq.go` in this directory.
|
||||
// Navigate to the Diablo II directory (ex: C:/Program Files (x86)/Diablo II)
|
||||
// Navigate to the Diablo II directory (ex: C:/Program files (x86)/Diablo II)
|
||||
// then run extract-mpq(.exe) with the filename of the mpq to be extracted.
|
||||
//
|
||||
// extract-mpq d2char.mpq
|
||||
|
Loading…
Reference in New Issue
Block a user