1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-12-26 03:56:42 -05:00

merged d2shared into the core package (#275)

This commit is contained in:
Tim Sarbin 2020-01-26 00:39:13 -05:00 committed by GitHub
parent 8de0cde818
commit 6832a5a0db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
129 changed files with 14391 additions and 81 deletions

View File

@ -5,10 +5,11 @@ import (
"image/color"
"math"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dc6"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/hajimehoshi/ebiten"

View File

@ -5,8 +5,8 @@ import (
"path"
"sync"
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq"
)
type archiveEntry struct {

View File

@ -4,12 +4,12 @@ import (
"errors"
"log"
"github.com/OpenDiablo2/D2Shared/d2data/d2cof"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2cof"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dc6"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2term"
)

View File

@ -5,10 +5,10 @@ import (
"fmt"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
)

View File

@ -3,7 +3,7 @@ package d2asset
import (
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
)

View File

@ -1,7 +1,7 @@
package d2asset
import (
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
)
type paletteManager struct {

View File

@ -3,8 +3,8 @@ package d2audio
import (
"log"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/hajimehoshi/ebiten/audio/wav"

85
d2common/bitmuncher.go Normal file
View File

@ -0,0 +1,85 @@
package d2common
type BitMuncher struct {
data []byte
Offset int
BitsRead int
}
func CreateBitMuncher(data []byte, offset int) *BitMuncher {
return &BitMuncher{
data: data,
Offset: offset,
BitsRead: 0,
}
}
func CopyBitMuncher(source *BitMuncher) *BitMuncher {
return &BitMuncher{
source.data,
source.Offset,
0,
}
}
func (v *BitMuncher) GetBit() uint32 {
result := uint32(v.data[v.Offset/8]>>uint(v.Offset%8)) & 0x01
v.Offset++
v.BitsRead++
return result
}
func (v *BitMuncher) SkipBits(bits int) {
v.Offset += bits
v.BitsRead += bits
}
func (v *BitMuncher) GetByte() byte {
return byte(v.GetBits(8))
}
func (v *BitMuncher) GetInt32() int32 {
return v.MakeSigned(v.GetBits(32), 32)
}
func (v *BitMuncher) GetUInt32() uint32 {
return v.GetBits(32)
}
func (v *BitMuncher) GetBits(bits int) uint32 {
if bits == 0 {
return 0
}
result := uint32(0)
for i := 0; i < bits; i++ {
result |= v.GetBit() << uint(i)
}
return result
}
func (v *BitMuncher) GetSignedBits(bits int) int {
return int(v.MakeSigned(v.GetBits(bits), bits))
}
func (v *BitMuncher) MakeSigned(value uint32, bits int) int32 {
if bits == 0 {
return 0
}
// If its a single bit, a value of 1 is -1 automagically
if bits == 1 {
return -int32(value)
}
// If there is no sign bit, return the value as is
if (value & (1 << uint(bits-1))) == 0 {
return int32(value)
}
// We need to extend the signed bit out so that the negative value representation still works with the 2s compliment rule.
result := uint32(4294967295)
for i := byte(0); i < byte(bits); i++ {
if ((value >> uint(i)) & 1) == 0 {
result -= uint32(1 << uint(i))
}
}
// Force casting to a signed value
return int32(result)
}

64
d2common/bitstream.go Normal file
View File

@ -0,0 +1,64 @@
package d2common
import "log"
// BitStream is a utility class for reading groups of bits from a stream
type BitStream struct {
data []byte
dataPosition int
current int
bitCount int
}
// CreateBitStream creates a new BitStream
func CreateBitStream(newData []byte) *BitStream {
result := &BitStream{
data: newData,
dataPosition: 0,
current: 0,
bitCount: 0,
}
return result
}
// ReadBits reads the specified number of bits and returns the value
func (v *BitStream) ReadBits(bitCount int) int {
if bitCount > 16 {
log.Panic("Maximum BitCount is 16")
}
if !v.EnsureBits(bitCount) {
return -1
}
result := v.current & (0xffff >> uint(16-bitCount))
v.WasteBits(bitCount)
return result
}
// PeekByte returns the current byte without adjusting the position
func (v *BitStream) PeekByte() int {
if !v.EnsureBits(8) {
return -1
}
return v.current & 0xff
}
// EnsureBits ensures that the specified number of bits are available
func (v *BitStream) EnsureBits(bitCount int) bool {
if bitCount <= v.bitCount {
return true
}
if v.dataPosition >= len(v.data) {
return false
}
nextValue := v.data[v.dataPosition]
v.dataPosition++
v.current |= int(nextValue) << uint(v.bitCount)
v.bitCount += 8
return true
}
// WasteBits dry-reads the specified number of bits
func (v *BitStream) WasteBits(bitCount int) {
v.current >>= uint(bitCount)
v.bitCount -= bitCount
}

View File

@ -0,0 +1,33 @@
package d2common
import (
"testing"
)
func TestBitStreamBits(t *testing.T) {
data := []byte{0xAA}
bitStream := CreateBitStream(data)
shouldBeOne := 0
for i := 0; i < 8; i++ {
bit := bitStream.ReadBits(1)
if bit != shouldBeOne {
t.Fatalf("Expected %d but got %d on iteration %d", shouldBeOne, bit, i)
}
if shouldBeOne == 1 {
shouldBeOne = 0
} else {
shouldBeOne = 1
}
}
}
func TestBitStreamBytes(t *testing.T) {
data := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0x12, 0x34, 0x56, 0x78}
bitStream := CreateBitStream(data)
for i := 0; i < 8; i++ {
b := byte(bitStream.ReadBits(8))
if b != data[i] {
t.Fatalf("Expected %d but got %d on iteration %d", data[i], b, i)
}
}
}

20
d2common/build_info.go Normal file
View File

@ -0,0 +1,20 @@
package d2common
// BuildInfoRecord is the structure used to hold information about the current build
type BuildInfoRecord struct {
// Branch is the branch this build is based on (or 'Local' if built locally)
Branch string
// Commit is the commit hash of the build (or blank if built locally)
Commit string
}
// BuildInfo contains information about the build currently being ran
var BuildInfo BuildInfoRecord
// SetBuildInfo is called at the start of the application to generate the global BuildInfo value
func SetBuildInfo(branch, commit string) {
BuildInfo = BuildInfoRecord{
Branch: branch,
Commit: commit,
}
}

9
d2common/calcstring.go Normal file
View File

@ -0,0 +1,9 @@
package d2common
// a calcstring is a type of string often used in datafiles to specify
// a value that is calculated dynamically based on the stats of the relevant
// source, for instance a missile might have a movement speed of lvl*2
type CalcString string
// todo: the logic for parsing these should exist here

View File

@ -0,0 +1,11 @@
package d2enum
type AnimationFrame int
const (
AnimationFrameNoEvent AnimationFrame = 0
AnimationFrameAttack AnimationFrame = 1
AnimationFrameMissile AnimationFrame = 2
AnimationFrameSound AnimationFrame = 3
AnimationFrameSkill AnimationFrame = 4
)

View File

@ -0,0 +1,52 @@
package d2enum
type AnimationMode int
const (
AnimationModePlayerDeath AnimationMode = 0 // DT
AnimationModePlayerNeutral AnimationMode = 1 // NU
AnimationModePlayerWalk AnimationMode = 2 // WL
AnimationModePlayerRun AnimationMode = 3 // RN
AnimationModePlayerGetHit AnimationMode = 4 // GH
AnimationModePlayerTownNeutral AnimationMode = 5 // TN
AnimationModePlayerTownWalk AnimationMode = 6 // TW
AnimationModePlayerAttack1 AnimationMode = 7 // A1
AnimationModePlayerAttack2 AnimationMode = 8 // A2
AnimationModePlayerBlock AnimationMode = 9 // BL
AnimationModePlayerCast AnimationMode = 10 // SC
AnimationModePlayerThrow AnimationMode = 11 // TH
AnimationModePlayerKick AnimationMode = 12 // KK
AnimationModePlayerSkill1 AnimationMode = 13 // S1
AnimationModePlayerSkill2 AnimationMode = 14 // S2
AnimationModePlayerSkill3 AnimationMode = 15 // S3
AnimationModePlayerSkill4 AnimationMode = 16 // S4
AnimationModePlayerDead AnimationMode = 17 // DD
AnimationModePlayerSequence AnimationMode = 18 // GH
AnimationModePlayerKnockBack AnimationMode = 19 // GH
AnimationModeMonsterDeath AnimationMode = 20 // DT
AnimationModeMonsterNeutral AnimationMode = 21 // NU
AnimationModeMonsterWalk AnimationMode = 22 // WL
AnimationModeMonsterGetHit AnimationMode = 23 // GH
AnimationModeMonsterAttack1 AnimationMode = 24 // A1
AnimationModeMonsterAttack2 AnimationMode = 25 // A2
AnimationModeMonsterBlock AnimationMode = 26 // BL
AnimationModeMonsterCast AnimationMode = 27 // SC
AnimationModeMonsterSkill1 AnimationMode = 28 // S1
AnimationModeMonsterSkill2 AnimationMode = 29 // S2
AnimationModeMonsterSkill3 AnimationMode = 30 // S3
AnimationModeMonsterSkill4 AnimationMode = 31 // S4
AnimationModeMonsterDead AnimationMode = 32 // DD
AnimationModeMonsterKnockback AnimationMode = 33 // GH
AnimationModeMonsterSequence AnimationMode = 34 // xx
AnimationModeMonsterRun AnimationMode = 35 // RN
AnimationModeObjectNeutral AnimationMode = 36 // NU
AnimationModeObjectOperating AnimationMode = 37 // OP
AnimationModeObjectOpened AnimationMode = 38 // ON
AnimationModeObjectSpecial1 AnimationMode = 39 // S1
AnimationModeObjectSpecial2 AnimationMode = 40 // S2
AnimationModeObjectSpecial3 AnimationMode = 41 // S3
AnimationModeObjectSpecial4 AnimationMode = 42 // S4
AnimationModeObjectSpecial5 AnimationMode = 43 // S5
)
//go:generate stringer -linecomment -type AnimationMode

View File

@ -0,0 +1,66 @@
// Code generated by "stringer -linecomment -type AnimationMode"; DO NOT EDIT.
package d2enum
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[AnimationModePlayerDeath-0]
_ = x[AnimationModePlayerNeutral-1]
_ = x[AnimationModePlayerWalk-2]
_ = x[AnimationModePlayerRun-3]
_ = x[AnimationModePlayerGetHit-4]
_ = x[AnimationModePlayerTownNeutral-5]
_ = x[AnimationModePlayerTownWalk-6]
_ = x[AnimationModePlayerAttack1-7]
_ = x[AnimationModePlayerAttack2-8]
_ = x[AnimationModePlayerBlock-9]
_ = x[AnimationModePlayerCast-10]
_ = x[AnimationModePlayerThrow-11]
_ = x[AnimationModePlayerKick-12]
_ = x[AnimationModePlayerSkill1-13]
_ = x[AnimationModePlayerSkill2-14]
_ = x[AnimationModePlayerSkill3-15]
_ = x[AnimationModePlayerSkill4-16]
_ = x[AnimationModePlayerDead-17]
_ = x[AnimationModePlayerSequence-18]
_ = x[AnimationModePlayerKnockBack-19]
_ = x[AnimationModeMonsterDeath-20]
_ = x[AnimationModeMonsterNeutral-21]
_ = x[AnimationModeMonsterWalk-22]
_ = x[AnimationModeMonsterGetHit-23]
_ = x[AnimationModeMonsterAttack1-24]
_ = x[AnimationModeMonsterAttack2-25]
_ = x[AnimationModeMonsterBlock-26]
_ = x[AnimationModeMonsterCast-27]
_ = x[AnimationModeMonsterSkill1-28]
_ = x[AnimationModeMonsterSkill2-29]
_ = x[AnimationModeMonsterSkill3-30]
_ = x[AnimationModeMonsterSkill4-31]
_ = x[AnimationModeMonsterDead-32]
_ = x[AnimationModeMonsterKnockback-33]
_ = x[AnimationModeMonsterSequence-34]
_ = x[AnimationModeMonsterRun-35]
_ = x[AnimationModeObjectNeutral-36]
_ = x[AnimationModeObjectOperating-37]
_ = x[AnimationModeObjectOpened-38]
_ = x[AnimationModeObjectSpecial1-39]
_ = x[AnimationModeObjectSpecial2-40]
_ = x[AnimationModeObjectSpecial3-41]
_ = x[AnimationModeObjectSpecial4-42]
_ = x[AnimationModeObjectSpecial5-43]
}
const _AnimationMode_name = "DTNUWLRNGHTNTWA1A2BLSCTHKKS1S2S3S4DDGHGHDTNUWLGHA1A2BLSCS1S2S3S4DDGHxxRNNUOPONS1S2S3S4S5"
var _AnimationMode_index = [...]uint8{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88}
func (i AnimationMode) String() string {
if i < 0 || i >= AnimationMode(len(_AnimationMode_index)-1) {
return "AnimationMode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AnimationMode_name[_AnimationMode_index[i]:_AnimationMode_index[i+1]]
}

View File

@ -0,0 +1,23 @@
package d2enum
type CompositeType int
const (
CompositeTypeHead CompositeType = 0
CompositeTypeTorso CompositeType = 1
CompositeTypeLegs CompositeType = 2
CompositeTypeRightArm CompositeType = 3
CompositeTypeLeftArm CompositeType = 4
CompositeTypeRightHand CompositeType = 5
CompositeTypeLeftHand CompositeType = 6
CompositeTypeShield CompositeType = 7
CompositeTypeSpecial1 CompositeType = 8
CompositeTypeSpecial2 CompositeType = 9
CompositeTypeSpecial3 CompositeType = 10
CompositeTypeSpecial4 CompositeType = 11
CompositeTypeSpecial5 CompositeType = 12
CompositeTypeSpecial6 CompositeType = 13
CompositeTypeSpecial7 CompositeType = 14
CompositeTypeSpecial8 CompositeType = 15
CompositeTypeMax CompositeType = 16
)

View File

@ -0,0 +1,15 @@
package d2enum
type DrawEffect int
// Names courtesy of Necrolis
const (
DrawEffectPctTransparency25 = 0 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 25 % transparency (colormaps 49-304 in a .pl2)
DrawEffectPctTransparency50 = 1 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 50 % transparency (colormaps 305-560 in a .pl2)
DrawEffectPctTransparency75 = 2 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 75 % transparency (colormaps 561-816 in a .pl2)
DrawEffectModulate = 3 //GL_MODULATE; GL_SRC_ALPHA, GL_DST_ALPHA (colormaps 817-1072 in a .pl2)
DrawEffectBurn = 4 //GL_MODULATE; GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA (colormaps 1073-1328 in a .pl2)
DrawEffectNormal = 5 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA (colormaps 1457-1712 in a .pl2)
DrawEffectMod2XTrans = 6 //GL_MODULATE; GL_SRC_COLOR, GL_DST_ALPHA (colormaps 1457-1712 in a .pl2)
DrawEffectMod2X = 7 //GL_COMBINE_ARB; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA (colormaps 1457-1712 in a .pl2)
)

41
d2common/d2enum/hero.go Normal file
View File

@ -0,0 +1,41 @@
package d2enum
import "log"
type Hero int
const (
HeroNone Hero = 0 //
HeroBarbarian Hero = 1 // Barbarian
HeroNecromancer Hero = 2 // Necromancer
HeroPaladin Hero = 3 // Paladin
HeroAssassin Hero = 4 // Assassin
HeroSorceress Hero = 5 // Sorceress
HeroAmazon Hero = 6 // Amazon
HeroDruid Hero = 7 // Druid
)
func (v Hero) GetToken() string {
switch v {
case HeroBarbarian:
return "BA"
case HeroNecromancer:
return "NE"
case HeroPaladin:
return "PA"
case HeroAssassin:
return "AI"
case HeroSorceress:
return "SO"
case HeroAmazon:
return "AM"
case HeroDruid:
return "DZ"
default:
log.Fatalf("Unknown hero token: %d", v)
}
return ""
}
//go:generate stringer -linecomment -type Hero
//go:generate string2enum -samepkg -linecomment -type Hero

View File

@ -0,0 +1,11 @@
package d2enum
type HeroStance int
const (
HeroStanceIdle HeroStance = 0
HeroStanceIdleSelected HeroStance = 1
HeroStanceApproaching HeroStance = 2
HeroStanceSelected HeroStance = 3
HeroStanceRetreating HeroStance = 4
)

View File

@ -0,0 +1,30 @@
// Code generated by "stringer -linecomment -type Hero"; DO NOT EDIT.
package d2enum
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[HeroNone-0]
_ = x[HeroBarbarian-1]
_ = x[HeroNecromancer-2]
_ = x[HeroPaladin-3]
_ = x[HeroAssassin-4]
_ = x[HeroSorceress-5]
_ = x[HeroAmazon-6]
_ = x[HeroDruid-7]
}
const _Hero_name = "BarbarianNecromancerPaladinAssassinSorceressAmazonDruid"
var _Hero_index = [...]uint8{0, 0, 9, 20, 27, 35, 44, 50, 55}
func (i Hero) String() string {
if i < 0 || i >= Hero(len(_Hero_index)-1) {
return "Hero(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Hero_name[_Hero_index[i]:_Hero_index[i+1]]
}

View File

@ -0,0 +1,18 @@
// Code generated by "string2enum -samepkg -linecomment -type Hero"; DO NOT EDIT.
package d2enum
import "fmt"
// HeroFromString returns the Hero enum corresponding to s.
func HeroFromString(s string) Hero {
if len(s) == 0 {
return 0
}
for i := range _Hero_index[:len(_Hero_index)-1] {
if s == _Hero_name[_Hero_index[i]:_Hero_index[i+1]] {
return Hero(i)
}
}
panic(fmt.Errorf("unable to locate Hero enum corresponding to %q", s))
}

View File

@ -0,0 +1,9 @@
package d2enum
type InventoryItemType int
const (
InventoryItemTypeItem InventoryItemType = 0 // Item
InventoryItemTypeWeapon InventoryItemType = 1 // Weapon
InventoryItemTypeArmor InventoryItemType = 2 // Armor
)

View File

@ -0,0 +1,18 @@
package d2enum
type LayerStreamType int
const (
LayerStreamWall1 LayerStreamType = 0
LayerStreamWall2 LayerStreamType = 1
LayerStreamWall3 LayerStreamType = 2
LayerStreamWall4 LayerStreamType = 3
LayerStreamOrientation1 LayerStreamType = 4
LayerStreamOrientation2 LayerStreamType = 5
LayerStreamOrientation3 LayerStreamType = 6
LayerStreamOrientation4 LayerStreamType = 7
LayerStreamFloor1 LayerStreamType = 8
LayerStreamFloor2 LayerStreamType = 9
LayerStreamShadow LayerStreamType = 10
LayerStreamSubstitute LayerStreamType = 11
)

View File

@ -0,0 +1,43 @@
package d2enum
// PaletteType represents a named palette
type PaletteType string
const (
// Act1 palette
Act1 PaletteType = "act1"
// Act2 palette
Act2 PaletteType = "act2"
// Act3 palette
Act3 PaletteType = "act3"
// Act4 palette
Act4 PaletteType = "act4"
// Act5 palette
Act5 PaletteType = "act5"
// EndGame palette
EndGame PaletteType = "endgame"
// EndGame2 palette
EndGame2 PaletteType = "endgame2"
// Fechar palette
Fechar PaletteType = "fechar"
// Loading palette
Loading PaletteType = "loading"
// Menu0 palette
Menu0 PaletteType = "menu0"
// Menu1 palette
Menu1 PaletteType = "menu1"
// Menu2 palette
Menu2 PaletteType = "menu2"
// Menu3 palette
Menu3 PaletteType = "menu3"
// Menu4 palette
Menu4 PaletteType = "menu4"
// Sky palette
Sky PaletteType = "sky"
// Static palette
Static PaletteType = "static"
// Trademark palette
Trademark PaletteType = "trademark"
// Units palette
Units PaletteType = "units"
)

17
d2common/d2enum/readme.md Normal file
View File

@ -0,0 +1,17 @@
# OpenDiablo2 Enums
Items in this folder are compiled with two programs. You can obtain them
by running the following:
```
go get golang.org/x/tools/cmd/stringer
go get github.com/mewspring/tools/cmd/string2enum
```
Once you have the tools installed, simply run the following command in this
folder to regenerate the support files:
```
go generate
```
If you add any enums (e.g. `AnimationMode`), make sure to add the following to the end of the
file:
```go
//go:generate stringer -linecomment -type AnimationMode
```

View File

@ -0,0 +1,41 @@
package d2enum
type RegionIdType int
const (
RegionAct1Town RegionIdType = 1
RegionAct1Wilderness RegionIdType = 2
RegionAct1Cave RegionIdType = 3
RegionAct1Crypt RegionIdType = 4
RegionAct1Monestary RegionIdType = 5
RegionAct1Courtyard RegionIdType = 6
RegionAct1Barracks RegionIdType = 7
RegionAct1Jail RegionIdType = 8
RegionAct1Cathedral RegionIdType = 9
RegionAct1Catacombs RegionIdType = 10
RegionAct1Tristram RegionIdType = 11
RegionAct2Town RegionIdType = 12
RegionAct2Sewer RegionIdType = 13
RegionAct2Harem RegionIdType = 14
RegionAct2Basement RegionIdType = 15
RegionAct2Desert RegionIdType = 16
RegionAct2Tomb RegionIdType = 17
RegionAct2Lair RegionIdType = 18
RegionAct2Arcane RegionIdType = 19
RegionAct3Town RegionIdType = 20
RegionAct3Jungle RegionIdType = 21
RegionAct3Kurast RegionIdType = 22
RegionAct3Spider RegionIdType = 23
RegionAct3Dungeon RegionIdType = 24
RegionAct3Sewer RegionIdType = 25
RegionAct4Town RegionIdType = 26
RegionAct4Mesa RegionIdType = 27
RegionAct4Lava RegionIdType = 28
RegonAct5Town RegionIdType = 29
RegionAct5Siege RegionIdType = 30
RegionAct5Barricade RegionIdType = 31
RegionAct5Temple RegionIdType = 32
RegionAct5IceCaves RegionIdType = 33
RegionAct5Baal RegionIdType = 34
RegionAct5Lava RegionIdType = 35
)

View File

@ -0,0 +1,9 @@
package d2enum
type RegionLayerType int
const (
RegionLayerTypeFloors RegionLayerType = 0
RegionLayerTypeWalls RegionLayerType = 1
RegionLayerTypeShadows RegionLayerType = 2
)

View File

@ -0,0 +1,44 @@
package d2enum
type TileType byte
const (
Floor TileType = 0
LeftWall TileType = 1
RightWall TileType = 2
RightPartOfNorthCornerWall TileType = 3
LeftPartOfNorthCornerWall TileType = 4
LeftEndWall TileType = 5
RightEndWall TileType = 6
SouthCornerWall TileType = 7
LeftWallWithDoor TileType = 8
RightWallWithDoor TileType = 9
SpecialTile1 TileType = 10
SpecialTile2 TileType = 11
PillarsColumnsAndStandaloneObjects TileType = 12
Shadow TileType = 13
Tree TileType = 14
Roof TileType = 15
LowerWallsEquivalentToLeftWall TileType = 16
LowerWallsEquivalentToRightWall TileType = 17
LowerWallsEquivalentToRightLeftNorthCornerWall TileType = 18
LowerWallsEquivalentToSouthCornerwall TileType = 19
)
func (tile TileType) LowerWall() bool {
switch tile {
case 16, 17, 18, 19:
return true
default:
return false
}
}
func (tile TileType) UpperWall() bool {
switch tile {
case 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14:
return true
default:
return false
}
}

View File

@ -0,0 +1,24 @@
package d2enum
type WeaponClass int
const (
WeaponClassNone WeaponClass = 0 //
WeaponClassHandToHand WeaponClass = 1 // hth
WeaponClassBow WeaponClass = 2 // bow
WeaponClassOneHandSwing WeaponClass = 3 // 1hs
WeaponClassOneHandThrust WeaponClass = 4 // 1ht
WeaponClassStaff WeaponClass = 5 // stf
WeaponClassTwoHandSwing WeaponClass = 6 // 2hs
WeaponClassTwoHandThrust WeaponClass = 7 // 2ht
WeaponClassCrossbow WeaponClass = 8 // xbw
WeaponClassLeftJabRightSwing WeaponClass = 9 // 1js
WeaponClassLeftJabRightThrust WeaponClass = 10 // 1jt
WeaponClassLeftSwingRightSwing WeaponClass = 11 // 1ss
WeaponClassLeftSwingRightThrust WeaponClass = 12 // 1st
WeaponClassOneHandToHand WeaponClass = 13 // ht1
WeaponClassTwoHandToHand WeaponClass = 14 // ht2
)
//go:generate stringer -linecomment -type WeaponClass
//go:generate string2enum -samepkg -linecomment -type WeaponClass

View File

@ -0,0 +1,37 @@
// Code generated by "stringer -linecomment -type WeaponClass"; DO NOT EDIT.
package d2enum
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[WeaponClassNone-0]
_ = x[WeaponClassHandToHand-1]
_ = x[WeaponClassBow-2]
_ = x[WeaponClassOneHandSwing-3]
_ = x[WeaponClassOneHandThrust-4]
_ = x[WeaponClassStaff-5]
_ = x[WeaponClassTwoHandSwing-6]
_ = x[WeaponClassTwoHandThrust-7]
_ = x[WeaponClassCrossbow-8]
_ = x[WeaponClassLeftJabRightSwing-9]
_ = x[WeaponClassLeftJabRightThrust-10]
_ = x[WeaponClassLeftSwingRightSwing-11]
_ = x[WeaponClassLeftSwingRightThrust-12]
_ = x[WeaponClassOneHandToHand-13]
_ = x[WeaponClassTwoHandToHand-14]
}
const _WeaponClass_name = "hthbow1hs1htstf2hs2htxbw1js1jt1ss1stht1ht2"
var _WeaponClass_index = [...]uint8{0, 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42}
func (i WeaponClass) String() string {
if i < 0 || i >= WeaponClass(len(_WeaponClass_index)-1) {
return "WeaponClass(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _WeaponClass_name[_WeaponClass_index[i]:_WeaponClass_index[i+1]]
}

View File

@ -0,0 +1,18 @@
// Code generated by "string2enum -samepkg -linecomment -type WeaponClass"; DO NOT EDIT.
package d2enum
import "fmt"
// WeaponClassFromString returns the WeaponClass enum corresponding to s.
func WeaponClassFromString(s string) WeaponClass {
if len(s) == 0 {
return 0
}
for i := range _WeaponClass_index[:len(_WeaponClass_index)-1] {
if s == _WeaponClass_name[_WeaponClass_index[i]:_WeaponClass_index[i+1]] {
return WeaponClass(i)
}
}
panic(fmt.Errorf("unable to locate WeaponClass enum corresponding to %q", s))
}

View File

@ -0,0 +1,7 @@
package d2interface
// FileProvider is an instance that can provide different types of files
type FileProvider interface {
LoadFile(fileName string) []byte
//LoadSprite(fileName string, palette enums.PaletteType) *d2render.Sprite
}

View File

@ -0,0 +1,16 @@
package d2interface
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
type InventoryItem interface {
// GetInventoryItemName returns the name of this inventory item
GetInventoryItemName() string
// GetInventoryItemType returns the type of item this is
GetInventoryItemType() d2enum.InventoryItemType
// GetInventoryGridSize returns the width/height grid size of this inventory item
GetInventoryGridSize() (int, int)
// Returns the item code
GetItemCode() string
// Serializes the object for transport
Serialize() []byte
}

View File

@ -0,0 +1,282 @@
package d2resource
var LanguageCode string
const (
// --- Screens ---
LoadingScreen = "/data/global/ui/Loading/loadingscreen.dc6"
// --- Main Menu ---
TrademarkScreen = "/data/global/ui/FrontEnd/trademarkscreenEXP.dc6"
GameSelectScreen = "/data/global/ui/FrontEnd/gameselectscreenEXP.dc6"
Diablo2LogoFireLeft = "/data/global/ui/FrontEnd/D2logoFireLeft.DC6"
Diablo2LogoFireRight = "/data/global/ui/FrontEnd/D2logoFireRight.DC6"
Diablo2LogoBlackLeft = "/data/global/ui/FrontEnd/D2logoBlackLeft.DC6"
Diablo2LogoBlackRight = "/data/global/ui/FrontEnd/D2logoBlackRight.DC6"
// --- Credits ---
CreditsBackground = "/data/global/ui/CharSelect/creditsbckgexpand.dc6"
CreditsText = "/data/local/ui/{LANG}/ExpansionCredits.txt"
// --- Character Select Screen ---
CharacterSelectBackground = "/data/global/ui/FrontEnd/charactercreationscreenEXP.dc6"
CharacterSelectCampfire = "/data/global/ui/FrontEnd/fire.DC6"
CharacterSelectBarbarianUnselected = "/data/global/ui/FrontEnd/barbarian/banu1.DC6"
CharacterSelectBarbarianUnselectedH = "/data/global/ui/FrontEnd/barbarian/banu2.DC6"
CharacterSelectBarbarianSelected = "/data/global/ui/FrontEnd/barbarian/banu3.DC6"
CharacterSelectBarbarianForwardWalk = "/data/global/ui/FrontEnd/barbarian/bafw.DC6"
CharacterSelectBarbarianForwardWalkOverlay = "/data/global/ui/FrontEnd/barbarian/BAFWs.DC6"
CharacterSelectBarbarianBackWalk = "/data/global/ui/FrontEnd/barbarian/babw.DC6"
CharacterSelecSorceressUnselected = "/data/global/ui/FrontEnd/sorceress/SONU1.DC6"
CharacterSelecSorceressUnselectedH = "/data/global/ui/FrontEnd/sorceress/SONU2.DC6"
CharacterSelecSorceressSelected = "/data/global/ui/FrontEnd/sorceress/SONU3.DC6"
CharacterSelecSorceressSelectedOverlay = "/data/global/ui/FrontEnd/sorceress/SONU3s.DC6"
CharacterSelecSorceressForwardWalk = "/data/global/ui/FrontEnd/sorceress/SOFW.DC6"
CharacterSelecSorceressForwardWalkOverlay = "/data/global/ui/FrontEnd/sorceress/SOFWs.DC6"
CharacterSelecSorceressBackWalk = "/data/global/ui/FrontEnd/sorceress/SOBW.DC6"
CharacterSelecSorceressBackWalkOverlay = "/data/global/ui/FrontEnd/sorceress/SOBWs.DC6"
CharacterSelectNecromancerUnselected = "/data/global/ui/FrontEnd/necromancer/NENU1.DC6"
CharacterSelectNecromancerUnselectedH = "/data/global/ui/FrontEnd/necromancer/NENU2.DC6"
CharacterSelecNecromancerSelected = "/data/global/ui/FrontEnd/necromancer/NENU3.DC6"
CharacterSelecNecromancerSelectedOverlay = "/data/global/ui/FrontEnd/necromancer/NENU3s.DC6"
CharacterSelecNecromancerForwardWalk = "/data/global/ui/FrontEnd/necromancer/NEFW.DC6"
CharacterSelecNecromancerForwardWalkOverlay = "/data/global/ui/FrontEnd/necromancer/NEFWs.DC6"
CharacterSelecNecromancerBackWalk = "/data/global/ui/FrontEnd/necromancer/NEBW.DC6"
CharacterSelecNecromancerBackWalkOverlay = "/data/global/ui/FrontEnd/necromancer/NEBWs.DC6"
CharacterSelectPaladinUnselected = "/data/global/ui/FrontEnd/paladin/PANU1.DC6"
CharacterSelectPaladinUnselectedH = "/data/global/ui/FrontEnd/paladin/PANU2.DC6"
CharacterSelecPaladinSelected = "/data/global/ui/FrontEnd/paladin/PANU3.DC6"
CharacterSelecPaladinForwardWalk = "/data/global/ui/FrontEnd/paladin/PAFW.DC6"
CharacterSelecPaladinForwardWalkOverlay = "/data/global/ui/FrontEnd/paladin/PAFWs.DC6"
CharacterSelecPaladinBackWalk = "/data/global/ui/FrontEnd/paladin/PABW.DC6"
CharacterSelectAmazonUnselected = "/data/global/ui/FrontEnd/amazon/AMNU1.DC6"
CharacterSelectAmazonUnselectedH = "/data/global/ui/FrontEnd/amazon/AMNU2.DC6"
CharacterSelecAmazonSelected = "/data/global/ui/FrontEnd/amazon/AMNU3.DC6"
CharacterSelecAmazonForwardWalk = "/data/global/ui/FrontEnd/amazon/AMFW.DC6"
CharacterSelecAmazonForwardWalkOverlay = "/data/global/ui/FrontEnd/amazon/AMFWs.DC6"
CharacterSelecAmazonBackWalk = "/data/global/ui/FrontEnd/amazon/AMBW.DC6"
CharacterSelectAssassinUnselected = "/data/global/ui/FrontEnd/assassin/ASNU1.DC6"
CharacterSelectAssassinUnselectedH = "/data/global/ui/FrontEnd/assassin/ASNU2.DC6"
CharacterSelectAssassinSelected = "/data/global/ui/FrontEnd/assassin/ASNU3.DC6"
CharacterSelectAssassinForwardWalk = "/data/global/ui/FrontEnd/assassin/ASFW.DC6"
CharacterSelectAssassinBackWalk = "/data/global/ui/FrontEnd/assassin/ASBW.DC6"
CharacterSelectDruidUnselected = "/data/global/ui/FrontEnd/druid/DZNU1.dc6"
CharacterSelectDruidUnselectedH = "/data/global/ui/FrontEnd/druid/DZNU2.dc6"
CharacterSelectDruidSelected = "/data/global/ui/FrontEnd/druid/DZNU3.DC6"
CharacterSelectDruidForwardWalk = "/data/global/ui/FrontEnd/druid/DZFW.DC6"
CharacterSelectDruidBackWalk = "/data/global/ui/FrontEnd/druid/DZBW.DC6"
// -- Character Selection
CharacterSelectionBackground = "/data/global/ui/CharSelect/characterselectscreenEXP.dc6"
CharacterSelectionSelectBox = "/data/global/ui/CharSelect/charselectbox.dc6"
PopUpOkCancel = "/data/global/ui/FrontEnd/PopUpOKCancel.dc6"
// --- Game ---
GamePanels = "/data/global/ui/PANEL/800ctrlpnl7.dc6"
GameGlobeOverlap = "/data/global/ui/PANEL/overlap.DC6"
HealthMana = "/data/global/ui/PANEL/hlthmana.DC6"
GameSmallMenuButton = "/data/global/ui/PANEL/menubutton.DC6" // TODO: Used for inventory popout
SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6" // TODO: Used for skill icon button
AddSkillButton = "/data/global/ui/PANEL/level.DC6"
// --- Mouse Pointers ---
CursorDefault = "/data/global/ui/CURSOR/ohand.DC6"
// --- Fonts ---
Font6 = "/data/local/font/{LANG_FONT}/font6"
Font8 = "/data/local/font/{LANG_FONT}/font8"
Font16 = "/data/local/font/{LANG_FONT}/font16"
Font24 = "/data/local/font/{LANG_FONT}/font24"
Font30 = "/data/local/font/{LANG_FONT}/font30"
Font42 = "/data/local/font/{LANG_FONT}/font42"
FontFormal12 = "/data/local/font/{LANG_FONT}/fontformal12"
FontFormal11 = "/data/local/font/{LANG_FONT}/fontformal11"
FontFormal10 = "/data/local/font/{LANG_FONT}/fontformal10"
FontExocet10 = "/data/local/font/{LANG_FONT}/fontexocet10"
FontExocet8 = "/data/local/font/{LANG_FONT}/fontexocet8"
FontSucker = "/data/local/font/{LANG_FONT}/ReallyTheLastSucker"
FontRediculous = "/data/local/font/{LANG_FONT}/fontridiculous"
// --- UI ---
WideButtonBlank = "/data/global/ui/FrontEnd/WideButtonBlank.dc6"
MediumButtonBlank = "/data/global/ui/FrontEnd/MediumButtonBlank.dc6"
CancelButton = "/data/global/ui/FrontEnd/CancelButtonBlank.dc6"
NarrowButtonBlank = "/data/global/ui/FrontEnd/NarrowButtonBlank.dc6"
ShortButtonBlank = "/data/global/ui/CharSelect/ShortButtonBlank.dc6"
TextBox2 = "/data/global/ui/FrontEnd/textbox2.dc6"
TallButtonBlank = "/data/global/ui/CharSelect/TallButtonBlank.dc6"
Checkbox = "/data/global/ui/FrontEnd/clickbox.dc6"
Scrollbar = "/data/global/ui/PANEL/scrollbar.dc6"
// --- GAME UI ---
PentSpin = "/data/global/ui/CURSOR/pentspin.DC6"
MinipanelSmall = "/data/global/ui/PANEL/minipanel_s.dc6"
MinipanelButton = "/data/global/ui/PANEL/minipanelbtn.DC6"
Frame = "/data/global/ui/PANEL/800borderframe.dc6"
InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.DC6"
InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6"
SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6"
SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6"
SkillsPanelDruid = "/data/global/ui/SPELLS/skltree_d_back.DC6"
SkillsPanelAssassin = "/data/global/ui/SPELLS/skltree_i_back.DC6"
SkillsPanelNecromancer = "/data/global/ui/SPELLS/skltree_n_back.DC6"
SkillsPanelPaladin = "/data/global/ui/SPELLS/skltree_p_back.DC6"
SkillsPanelSorcerer = "/data/global/ui/SPELLS/skltree_s_back.DC6"
GenericSkills = "/data/global/ui/SPELLS/Skillicon.DC6"
AmazonSkills = "/data/global/ui/SPELLS/AmSkillicon.DC6"
BarbarianSkills = "/data/global/ui/SPELLS/BaSkillicon.DC6"
DruidSkills = "/data/global/ui/SPELLS/DrSkillicon.DC6"
AssassinSkills = "/data/global/ui/SPELLS/AsSkillicon.DC6"
NecromancerSkills = "/data/global/ui/SPELLS/NeSkillicon.DC6"
PaladinSkills = "/data/global/ui/SPELLS/PaSkillicon.DC6"
SorcererSkills = "/data/global/ui/SPELLS/SoSkillicon.DC6"
RunButton = "/data/global/ui/PANEL/runbutton.dc6"
MenuButton = "/data/global/ui/PANEL/menubutton.DC6"
GoldCoinButton = "/data/global/ui/panel/goldcoinbtn.dc6"
SquareButton = "/data/global/ui/panel/buysellbtn.dc6"
ArmorPlaceholder = "/data/global/ui/PANEL/inv_armor.DC6"
BeltPlaceholder = "/data/global/ui/PANEL/inv_belt.DC6"
BootsPlaceholder = "/data/global/ui/PANEL/inv_boots.DC6"
HelmGlovePlaceholder = "/data/global/ui/PANEL/inv_helm_glove.DC6"
RingAmuletPlaceholder = "/data/global/ui/PANEL/inv_ring_amulet.DC6"
WeaponsPlaceholder = "/data/global/ui/PANEL/inv_weapons.DC6"
// --- Data ---
ExpansionStringTable = "/data/local/lng/{LANG}/expansionstring.tbl"
StringTable = "/data/local/lng/{LANG}/string.tbl"
PatchStringTable = "/data/local/lng/{LANG}/patchstring.tbl"
LevelPreset = "/data/global/excel/LvlPrest.txt"
LevelType = "/data/global/excel/LvlTypes.txt"
ObjectType = "/data/global/excel/objtype.bin"
LevelWarp = "/data/global/excel/LvlWarp.bin"
LevelDetails = "/data/global/excel/Levels.bin"
ObjectDetails = "/data/global/excel/Objects.txt"
SoundSettings = "/data/global/excel/Sounds.txt"
// --- Animations ---
ObjectData = "/data/global/objects"
AnimationData = "/data/global/animdata.d2"
PlayerAnimationBase = "/data/global/CHARS"
// --- Inventory Data ---
Weapons = "/data/global/excel/weapons.txt"
Armor = "/data/global/excel/armor.txt"
Misc = "/data/global/excel/misc.txt"
UniqueItems = "/data/global/excel/UniqueItems.txt"
// --- Character Data ---
Experience = "/data/global/excel/experience.txt"
CharStats = "/data/global/excel/charstats.txt"
// --- Music ---
BGMTitle = "/data/global/music/introedit.wav"
BGMOptions = "/data/global/music/Common/options.wav"
BGMAct1AndarielAction = "/data/global/music/Act1/andarielaction.wav"
BGMAct1BloodRavenResolution = "/data/global/music/Act1/bloodravenresolution.wav"
BGMAct1Caves = "/data/global/music/Act1/caves.wav"
BGMAct1Crypt = "/data/global/music/Act1/crypt.wav"
BGMAct1DenOfEvilAction = "/data/global/music/Act1/denofevilaction.wav"
BGMAct1Monastery = "/data/global/music/Act1/monastery.wav"
BGMAct1Town1 = "/data/global/music/Act1/town1.wav"
BGMAct1Tristram = "/data/global/music/Act1/tristram.wav"
BGMAct1Wild = "/data/global/music/Act1/wild.wav"
BGMAct2Desert = "/data/global/music/Act2/desert.wav"
BGMAct2Harem = "/data/global/music/Act2/harem.wav"
BGMAct2HoradricAction = "/data/global/music/Act2/horadricaction.wav"
BGMAct2Lair = "/data/global/music/Act2/lair.wav"
BGMAct2RadamentResolution = "/data/global/music/Act2/radamentresolution.wav"
BGMAct2Sanctuary = "/data/global/music/Act2/sanctuary.wav"
BGMAct2Sewer = "/data/global/music/Act2/sewer.wav"
BGMAct2TaintedSunAction = "/data/global/music/Act2/taintedsunaction.wav"
BGMAct2Tombs = "/data/global/music/Act2/tombs.wav"
BGMAct2Town2 = "/data/global/music/Act2/town2.wav"
BGMAct2Valley = "/data/global/music/Act2/valley.wav"
BGMAct3Jungle = "/data/global/music/Act3/jungle.wav"
BGMAct3Kurast = "/data/global/music/Act3/kurast.wav"
BGMAct3KurastSewer = "/data/global/music/Act3/kurastsewer.wav"
BGMAct3MefDeathAction = "/data/global/music/Act3/mefdeathaction.wav"
BGMAct3OrbAction = "/data/global/music/Act3/orbaction.wav"
BGMAct3Spider = "/data/global/music/Act3/spider.wav"
BGMAct3Town3 = "/data/global/music/Act3/town3.wav"
BGMAct4Diablo = "/data/global/music/Act4/diablo.wav"
BGMAct4DiabloAction = "/data/global/music/Act4/diabloaction.wav"
BGMAct4ForgeAction = "/data/global/music/Act4/forgeaction.wav"
BGMAct4IzualAction = "/data/global/music/Act4/izualaction.wav"
BGMAct4Mesa = "/data/global/music/Act4/mesa.wav"
BGMAct4Town4 = "/data/global/music/Act4/town4.wav"
BGMAct5Baal = "/data/global/music/Act5/baal.wav"
BGMAct5XTown = "/data/global/music/Act5/xtown.wav"
// --- Sound Effects ---
SFXButtonClick = "cursor_button_click"
SFXAmazonDeselect = "cursor_amazon_deselect"
SFXAmazonSelect = "cursor_amazon_select"
SFXAssassinDeselect = "/data/global/sfx/Cursor/intro/assassin deselect.wav"
SFXAssassinSelect = "/data/global/sfx/Cursor/intro/assassin select.wav"
SFXBarbarianDeselect = "cursor_barbarian_deselect"
SFXBarbarianSelect = "cursor_barbarian_select"
SFXDruidDeselect = "/data/global/sfx/Cursor/intro/druid deselect.wav"
SFXDruidSelect = "/data/global/sfx/Cursor/intro/druid select.wav"
SFXNecromancerDeselect = "cursor_necromancer_deselect"
SFXNecromancerSelect = "cursor_necromancer_select"
SFXPaladinDeselect = "cursor_paladin_deselect"
SFXPaladinSelect = "cursor_paladin_select"
SFXSorceressDeselect = "cursor_sorceress_deselect"
SFXSorceressSelect = "cursor_sorceress_select"
// --- Enemy Data ---
MonStats = "/data/global/excel/monstats.txt"
// --- Skill Data ---
Missiles = "/data/global/excel/Missiles.txt"
// --- Palettes ---
PaletteAct1 = "/data/global/palette/act1/pal.dat"
PaletteAct2 = "/data/global/palette/act2/pal.dat"
PaletteAct3 = "/data/global/palette/act3/pal.dat"
PaletteAct4 = "/data/global/palette/act4/pal.dat"
PaletteAct5 = "/data/global/palette/act5/pal.dat"
PaletteEndGame = "/data/global/palette/endgame/pal.dat"
PaletteEndGame2 = "/data/global/palette/endgame2/pal.dat"
PaletteFechar = "/data/global/palette/fechar/pal.dat"
PaletteLoading = "/data/global/palette/loading/pal.dat"
PaletteMenu0 = "/data/global/palette/menu0/pal.dat"
PaletteMenu1 = "/data/global/palette/menu1/pal.dat"
PaletteMenu2 = "/data/global/palette/menu2/pal.dat"
PaletteMenu3 = "/data/global/palette/menu3/pal.dat"
PaletteMenu4 = "/data/global/palette/menu4/pal.dat"
PaletteSky = "/data/global/palette/sky/pal.dat"
PaletteStatic = "/data/global/palette/static/pal.dat"
PaletteTrademark = "/data/global/palette/trademark/pal.dat"
PaletteUnits = "/data/global/palette/units/pal.dat"
)

View File

@ -0,0 +1,47 @@
package d2common
import (
"log"
"strconv"
"strings"
)
// DataDictionary represents a data file (Excel)
type DataDictionary struct {
FieldNameLookup map[string]int
Data [][]string
}
func LoadDataDictionary(text string) *DataDictionary {
result := &DataDictionary{}
lines := strings.Split(text, "\r\n")
fileNames := strings.Split(lines[0], "\t")
result.FieldNameLookup = make(map[string]int)
for i, fieldName := range fileNames {
result.FieldNameLookup[fieldName] = i
}
result.Data = make([][]string, len(lines)-1)
for i, line := range lines[1:] {
if len(strings.TrimSpace(line)) == 0 {
continue
}
values := strings.Split(line, "\t")
if len(values) != len(result.FieldNameLookup) {
continue
}
result.Data[i] = values
}
return result
}
func (v *DataDictionary) GetString(fieldName string, index int) string {
return v.Data[index][v.FieldNameLookup[fieldName]]
}
func (v *DataDictionary) GetNumber(fieldName string, index int) int {
result, err := strconv.Atoi(v.GetString(fieldName, index))
if err != nil {
log.Panic(err)
}
return result
}

7
d2common/path.go Normal file
View File

@ -0,0 +1,7 @@
package d2common
type Path struct {
X int32
Y int32
Action int32
}

20
d2common/rectangle.go Normal file
View File

@ -0,0 +1,20 @@
package d2common
type Rectangle struct {
Left int
Top int
Width int
Height int
}
func (v *Rectangle) Bottom() int {
return v.Top + v.Height
}
func (v *Rectangle) Right() int {
return v.Left + v.Width
}
func (v *Rectangle) IsInRect(x, y int) bool {
return x >= v.Left && x < v.Left+v.Width && y >= v.Top && y < v.Top+v.Height
}

134
d2common/stream_reader.go Normal file
View File

@ -0,0 +1,134 @@
package d2common
import (
"io"
)
// StreamReader allows you to read data from a byte array in various formats
type StreamReader struct {
data []byte
position uint64
}
// CreateStreamReader creates an instance of the stream reader
func CreateStreamReader(source []byte) *StreamReader {
result := &StreamReader{
data: source,
position: 0,
}
return result
}
// GetPosition returns the current stream position
func (v *StreamReader) GetPosition() uint64 {
return v.position
}
// GetSize returns the total size of the stream in bytes
func (v *StreamReader) GetSize() uint64 {
return uint64(len(v.data))
}
// GetByte returns a byte from the stream
func (v *StreamReader) GetByte() byte {
result := v.data[v.position]
v.position++
return result
}
// GetUInt16 returns a uint16 word from the stream
func (v *StreamReader) GetUInt16() uint16 {
result := uint16(v.data[v.position])
result += uint16(v.data[v.position+1]) << 8
v.position += 2
return result
}
// GetInt16 returns a int16 word from the stream
func (v *StreamReader) GetInt16() int16 {
result := (int16(v.data[v.position+1]) << uint(8)) + int16(v.data[v.position])
v.position += 2
return result
}
func (v *StreamReader) SetPosition(newPosition uint64) {
v.position = newPosition
}
// GetUInt32 returns a uint32 dword from the stream
func (v *StreamReader) GetUInt32() uint32 {
result := (uint32(v.data[v.position+3]) << uint(24)) + (uint32(v.data[v.position+2]) << uint(16)) + (uint32(v.data[v.position+1]) << uint(8)) + uint32(v.data[v.position])
v.position += 4
return result
}
// GetInt32 returns an int32 dword from the stream
func (v *StreamReader) GetInt32() int32 {
result := (int32(v.data[v.position+3]) << uint(24)) + (int32(v.data[v.position+2]) << uint(16)) + (int32(v.data[v.position+1]) << uint(8)) + int32(v.data[v.position])
v.position += 4
return result
}
// GetUint64 returns a uint64 qword from the stream
func (v *StreamReader) GetUint64() uint64 {
result := (uint64(v.data[v.position+7]) << uint(56)) +
(uint64(v.data[v.position+6]) << uint(48)) +
(uint64(v.data[v.position+5]) << uint(40)) +
(uint64(v.data[v.position+4]) << uint(32)) +
(uint64(v.data[v.position+3]) << uint(24)) +
(uint64(v.data[v.position+2]) << uint(16)) +
(uint64(v.data[v.position+1]) << uint(8)) +
uint64(v.data[v.position])
v.position += 8
return result
}
// GetInt64 returns a uint64 qword from the stream
func (v *StreamReader) GetInt64() int64 {
result := (uint64(v.data[v.position+7]) << uint(56)) +
(uint64(v.data[v.position+6]) << uint(48)) +
(uint64(v.data[v.position+5]) << uint(40)) +
(uint64(v.data[v.position+4]) << uint(32)) +
(uint64(v.data[v.position+3]) << uint(24)) +
(uint64(v.data[v.position+2]) << uint(16)) +
(uint64(v.data[v.position+1]) << uint(8)) +
uint64(v.data[v.position])
v.position += 8
return int64(result)
}
// ReadByte implements io.ByteReader
func (v *StreamReader) ReadByte() (byte, error) {
return v.GetByte(), nil
}
// ReadBytes reads multiple bytes
func (v *StreamReader) ReadBytes(count int) ([]byte, error) {
result := make([]byte, count)
for i := 0; i < count; i++ {
result[i] = v.GetByte()
}
return result, nil
}
func (v *StreamReader) SkipBytes(count int) {
v.position += uint64(count)
}
// Read implements io.Reader
func (v *StreamReader) Read(p []byte) (n int, err error) {
streamLength := v.GetSize()
for i := 0; ; i++ {
if v.GetPosition() >= streamLength {
return i, io.EOF
}
if i >= len(p) {
return i, nil
}
p[i] = v.GetByte()
}
}
func (v *StreamReader) Eof() bool {
return v.position >= uint64(len(v.data))
}

View File

@ -0,0 +1,56 @@
package d2common
import (
"testing"
)
func TestStreamReaderByte(t *testing.T) {
data := []byte{0x78, 0x56, 0x34, 0x12}
sr := CreateStreamReader(data)
if sr.GetPosition() != 0 {
t.Fatal("StreamReader.GetPosition() did not start at 0")
}
if ss := sr.GetSize(); ss != 4 {
t.Fatalf("StreamREader.GetSize() was expected to return %d, but returned %d instead", 4, ss)
}
for i := 0; i < len(data); i++ {
ret := sr.GetByte()
if ret != data[i] {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", data[i], ret)
}
if pos := sr.GetPosition(); pos != uint64(i+1) {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", i, pos)
}
}
}
func TestStreamReaderWord(t *testing.T) {
data := []byte{0x78, 0x56, 0x34, 0x12}
sr := CreateStreamReader(data)
ret := sr.GetUInt16()
if ret != 0x5678 {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x5678, ret)
}
if pos := sr.GetPosition(); pos != 2 {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 2, pos)
}
ret = sr.GetUInt16()
if ret != 0x1234 {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x1234, ret)
}
if pos := sr.GetPosition(); pos != 4 {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos)
}
}
func TestStreamReaderDword(t *testing.T) {
data := []byte{0x78, 0x56, 0x34, 0x12}
sr := CreateStreamReader(data)
ret := sr.GetUInt32()
if ret != 0x12345678 {
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x12345678, ret)
}
if pos := sr.GetPosition(); pos != 4 {
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos)
}
}

69
d2common/stream_writer.go Normal file
View File

@ -0,0 +1,69 @@
package d2common
// StreamWriter allows you to create a byte array by streaming in writes of various sizes
type StreamWriter struct {
data []byte
}
// CreateStreamWriter creates a new StreamWriter instance
func CreateStreamWriter() *StreamWriter {
result := &StreamWriter{
data: make([]byte, 0),
}
return result
}
// PushByte writes a byte to the stream
func (v *StreamWriter) PushByte(val byte) {
v.data = append(v.data, val)
}
// PushUint16 writes an uint16 word to the stream
func (v *StreamWriter) PushUint16(val uint16) {
v.data = append(v.data, byte(val&0xFF))
v.data = append(v.data, byte((val>>8)&0xFF))
}
// PushInt16 writes a int16 word to the stream
func (v *StreamWriter) PushInt16(val int16) {
v.data = append(v.data, byte(val&0xFF))
v.data = append(v.data, byte((val>>8)&0xFF))
}
// PushUint32 writes a uint32 dword to the stream
func (v *StreamWriter) PushUint32(val uint32) {
v.data = append(v.data, byte(val&0xFF))
v.data = append(v.data, byte((val>>8)&0xFF))
v.data = append(v.data, byte((val>>16)&0xFF))
v.data = append(v.data, byte((val>>24)&0xFF))
}
// PushUint64 writes a uint64 qword to the stream
func (v *StreamWriter) PushUint64(val uint64) {
v.data = append(v.data, byte(val&0xFF))
v.data = append(v.data, byte((val>>8)&0xFF))
v.data = append(v.data, byte((val>>16)&0xFF))
v.data = append(v.data, byte((val>>24)&0xFF))
v.data = append(v.data, byte((val>>32)&0xFF))
v.data = append(v.data, byte((val>>40)&0xFF))
v.data = append(v.data, byte((val>>48)&0xFF))
v.data = append(v.data, byte((val>>56)&0xFF))
}
// PushInt64 writes a uint64 qword to the stream
func (v *StreamWriter) PushInt64(val int64) {
result := uint64(val)
v.data = append(v.data, byte(result&0xFF))
v.data = append(v.data, byte((result>>8)&0xFF))
v.data = append(v.data, byte((result>>16)&0xFF))
v.data = append(v.data, byte((result>>24)&0xFF))
v.data = append(v.data, byte((result>>32)&0xFF))
v.data = append(v.data, byte((result>>40)&0xFF))
v.data = append(v.data, byte((result>>48)&0xFF))
v.data = append(v.data, byte((result>>56)&0xFF))
}
// GetBytes returns the the byte slice of the underlying data
func (v *StreamWriter) GetBytes() []byte {
return v.data
}

View File

@ -0,0 +1,44 @@
package d2common
import (
"testing"
)
func TestStreamWriterByte(t *testing.T) {
sr := CreateStreamWriter()
data := []byte{0x12, 0x34, 0x56, 0x78}
for _, d := range data {
sr.PushByte(d)
}
output := sr.GetBytes()
for i, d := range data {
if output[i] != d {
t.Fatalf("sr.PushByte() pushed %X, but wrote %X instead", d, output[i])
}
}
}
func TestStreamWriterWord(t *testing.T) {
sr := CreateStreamWriter()
data := []byte{0x12, 0x34, 0x56, 0x78}
sr.PushUint16(0x3412)
sr.PushUint16(0x7856)
output := sr.GetBytes()
for i, d := range data {
if output[i] != d {
t.Fatalf("sr.PushWord() pushed byte %X to %d, but %X was expected instead", output[i], i, d)
}
}
}
func TestStreamWriterDword(t *testing.T) {
sr := CreateStreamWriter()
data := []byte{0x12, 0x34, 0x56, 0x78}
sr.PushUint32(0x78563412)
output := sr.GetBytes()
for i, d := range data {
if output[i] != d {
t.Fatalf("sr.PushDword() pushed byte %X to %d, but %X was expected instead", output[i], i, d)
}
}
}

110
d2common/text_dictionary.go Normal file
View File

@ -0,0 +1,110 @@
package d2common
import (
"log"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
type textDictionaryHashEntry struct {
IsActive bool
Index uint16
HashValue uint32
IndexString uint32
NameString uint32
NameLength uint16
}
var lookupTable map[string]string
func TranslateString(key string) string {
result, ok := lookupTable[key]
if !ok {
log.Panicf("Could not find a string for the key '%s'", key)
}
return result
}
func LoadTextDictionary(fileProvider d2interface.FileProvider) {
lookupTable = make(map[string]string)
loadDictionary(fileProvider, d2resource.PatchStringTable)
loadDictionary(fileProvider, d2resource.ExpansionStringTable)
loadDictionary(fileProvider, d2resource.StringTable)
log.Printf("Loaded %d entries from the string table", len(lookupTable))
}
func loadDictionary(fileProvider d2interface.FileProvider, dictionaryName string) {
dictionaryData := fileProvider.LoadFile(dictionaryName)
br := CreateStreamReader(dictionaryData)
// CRC
if _, err := br.ReadBytes(2); err != nil {
log.Fatal("Error reading CRC")
}
numberOfElements := br.GetUInt16()
hashTableSize := br.GetUInt32()
// Version (always 0)
if _, err := br.ReadByte(); err != nil {
log.Fatal("Error reading Version record")
}
br.GetUInt32() // StringOffset
br.GetUInt32() // When the number of times you have missed a match with a hash key equals this value, you give up because it is not there.
br.GetUInt32() // FileSize
elementIndex := make([]uint16, numberOfElements)
for i := 0; i < int(numberOfElements); i++ {
elementIndex[i] = br.GetUInt16()
}
hashEntries := make([]textDictionaryHashEntry, hashTableSize)
for i := 0; i < int(hashTableSize); i++ {
hashEntries[i] = textDictionaryHashEntry{
br.GetByte() == 1,
br.GetUInt16(),
br.GetUInt32(),
br.GetUInt32(),
br.GetUInt32(),
br.GetUInt16(),
}
}
for idx, hashEntry := range hashEntries {
if !hashEntry.IsActive {
continue
}
br.SetPosition(uint64(hashEntry.NameString))
nameVal, _ := br.ReadBytes(int(hashEntry.NameLength - 1))
value := string(nameVal)
br.SetPosition(uint64(hashEntry.IndexString))
key := ""
for {
b := br.GetByte()
if b == 0 {
break
}
key += string(b)
}
if key == "x" || key == "X" {
key = "#" + strconv.Itoa(idx)
}
_, exists := lookupTable[key]
if !exists {
lookupTable[key] = value
}
// Use the following code to write out the values
/*
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\langdict.txt`,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
}
defer f.Close()
if _, err := f.WriteString("\n[" + key + "] " + value); err != nil {
log.Println(err)
}
*/
}
}

View File

@ -1,9 +1,9 @@
package d2scene
import (
"github.com/OpenDiablo2/D2Shared/d2data/d2video"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2video"
"github.com/hajimehoshi/ebiten"
)

View File

@ -6,12 +6,13 @@ import (
"os"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
dh "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"

View File

@ -8,16 +8,17 @@ import (
"path"
"strings"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common"
dh "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
)

View File

@ -3,8 +3,8 @@ package d2scene
import (
"image/color"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"

View File

@ -10,14 +10,14 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
)

View File

@ -9,7 +9,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"

View File

@ -6,16 +6,17 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common"
dh "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
)

View File

@ -6,9 +6,9 @@ import (
"runtime"
"strconv"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2input"
@ -16,13 +16,14 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2data"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"

View File

@ -9,9 +9,9 @@ import (
"strings"
"time"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
/*

View File

@ -1,9 +1,9 @@
package d2core
import (
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
)

View File

@ -1,7 +1,7 @@
package d2core
import (
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
var HeroObjects map[d2enum.Hero]CharacterEquipment

View File

@ -3,8 +3,8 @@ package d2core
import (
"log"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
)
type InventoryItemArmor struct {

View File

@ -3,8 +3,8 @@ package d2core
import (
"log"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
)
type InventoryItemWeapon struct {

View File

@ -1,9 +1,10 @@
package d2core
import (
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
)

View File

@ -100,7 +100,7 @@ func getDefaultConfiguration() *Configuration {
"d2xmusic.mpq",
"d2xtalk.mpq",
"d2xvideo.mpq",
"d2data.mpq",
"github.com/OpenDiablo2/OpenDiablo2/d2data.mpq",
"d2char.mpq",
"d2music.mpq",
"d2sfx.mpq",

52
d2data/animation_data.go Normal file
View File

@ -0,0 +1,52 @@
package d2data
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
// AnimationDataRecord represents a single entry in the animation data dictionary file
type AnimationDataRecord struct {
// COFName is the name of the COF file used for this animation
COFName string
// FramesPerDirection specifies how many frames are in each direction
FramesPerDirection int
// AnimationSpeed represents a value of X where the rate is a ration of (x/255) at 25FPS
AnimationSpeed int
// Flags are used in keyframe triggers
Flags []byte
}
// AnimationData represents all of the animation data records, mapped by the COF index
var AnimationData map[string][]*AnimationDataRecord
// LoadAnimationData loads the animation data table into the global AnimationData dictionary
func LoadAnimationData(fileProvider d2interface.FileProvider) {
AnimationData = make(map[string][]*AnimationDataRecord)
rawData := fileProvider.LoadFile(d2resource.AnimationData)
streamReader := d2common.CreateStreamReader(rawData)
for !streamReader.Eof() {
dataCount := int(streamReader.GetInt32())
for i := 0; i < dataCount; i++ {
cofNameBytes, _ := streamReader.ReadBytes(8)
data := &AnimationDataRecord{
COFName: strings.ReplaceAll(string(cofNameBytes), string(0), ""),
FramesPerDirection: int(streamReader.GetInt32()),
AnimationSpeed: int(streamReader.GetInt32()),
}
data.Flags, _ = streamReader.ReadBytes(144)
cofIndex := strings.ToLower(data.COFName)
if _, found := AnimationData[cofIndex]; !found {
AnimationData[cofIndex] = make([]*AnimationDataRecord, 0)
}
AnimationData[cofIndex] = append(AnimationData[cofIndex], data)
}
}
log.Printf("Loaded %d animation data records", len(AnimationData))
}

65
d2data/d2cof/cof.go Normal file
View File

@ -0,0 +1,65 @@
package d2cof
import (
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type COF struct {
NumberOfDirections int
FramesPerDirection int
NumberOfLayers int
Speed int
CofLayers []CofLayer
CompositeLayers map[d2enum.CompositeType]int
AnimationFrames []d2enum.AnimationFrame
Priority [][][]d2enum.CompositeType
}
func LoadCOF(fileData []byte) (*COF, error) {
result := &COF{}
streamReader := d2common.CreateStreamReader(fileData)
result.NumberOfLayers = int(streamReader.GetByte())
result.FramesPerDirection = int(streamReader.GetByte())
result.NumberOfDirections = int(streamReader.GetByte())
streamReader.SkipBytes(21) // Skip 21 unknown bytes...
result.Speed = int(streamReader.GetByte())
streamReader.SkipBytes(3)
result.CofLayers = make([]CofLayer, result.NumberOfLayers)
result.CompositeLayers = make(map[d2enum.CompositeType]int, 0)
for i := 0; i < result.NumberOfLayers; i++ {
layer := CofLayer{}
layer.Type = d2enum.CompositeType(streamReader.GetByte())
layer.Shadow = streamReader.GetByte()
layer.Selectable = streamReader.GetByte() != 0
layer.Transparent = streamReader.GetByte() != 0
layer.DrawEffect = d2enum.DrawEffect(streamReader.GetByte())
weaponClassStr, _ := streamReader.ReadBytes(4)
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(string(weaponClassStr), string(0), "")))
result.CofLayers[i] = layer
result.CompositeLayers[layer.Type] = i
}
animationFrameBytes, _ := streamReader.ReadBytes(result.FramesPerDirection)
result.AnimationFrames = make([]d2enum.AnimationFrame, result.FramesPerDirection)
for i := range animationFrameBytes {
result.AnimationFrames[i] = d2enum.AnimationFrame(animationFrameBytes[i])
}
priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers
result.Priority = make([][][]d2enum.CompositeType, result.NumberOfDirections)
priorityBytes, _ := streamReader.ReadBytes(priorityLen)
priorityIndex := 0
for direction := 0; direction < result.NumberOfDirections; direction++ {
result.Priority[direction] = make([][]d2enum.CompositeType, result.FramesPerDirection)
for frame := 0; frame < result.FramesPerDirection; frame++ {
result.Priority[direction][frame] = make([]d2enum.CompositeType, result.NumberOfLayers)
for i := 0; i < result.NumberOfLayers; i++ {
result.Priority[direction][frame][i] = d2enum.CompositeType(priorityBytes[priorityIndex])
priorityIndex++
}
}
}
return result, nil
}

12
d2data/d2cof/coflayer.go Normal file
View File

@ -0,0 +1,12 @@
package d2cof
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
type CofLayer struct {
Type d2enum.CompositeType
Shadow byte
Selectable bool
Transparent bool
DrawEffect d2enum.DrawEffect
WeaponClass d2enum.WeaponClass
}

View File

@ -0,0 +1,371 @@
//
// MpqHuffman.go based on the origina CS file
//
// Authors:
// Foole (fooleau@gmail.com)
// Tim Sarbin (tim.sarbin@gmail.com) (go translation)
//
// (C) 2006 Foole (fooleau@gmail.com)
// Based on code from StormLib by Ladislav Zezula and ShadowFlare
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
package d2compression
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
// linkedNode is a node which is both hierachcical (parent/child) and doubly linked (next/prev)
type linkedNode struct {
DecompressedValue int
Weight int
Parent *linkedNode
Child0 *linkedNode
Prev *linkedNode
Next *linkedNode
}
func CreateLinkedNode(decompVal, weight int) *linkedNode {
result := &linkedNode{
DecompressedValue: decompVal,
Weight: weight,
}
return result
}
func (v *linkedNode) GetChild1() *linkedNode {
return v.Child0.Prev
}
func (v *linkedNode) Insert(other *linkedNode) *linkedNode {
// 'Next' should have a lower weight we should return the lower weight
if other.Weight <= v.Weight {
// insert before
if v.Next != nil {
v.Next.Prev = other
other.Next = v.Next
}
v.Next = other
other.Prev = v
return other
}
if v.Prev == nil {
// Insert after
other.Prev = nil
v.Prev = other
other.Next = v
} else {
v.Prev.Insert(other)
}
return v
}
var sPrime = [][]byte{
{ // Compression type 0
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
}, { // Compression type 1
0x54, 0x16, 0x16, 0x0D, 0x0C, 0x08, 0x06, 0x05, 0x06, 0x05, 0x06, 0x03, 0x04, 0x04, 0x03, 0x05,
0x0E, 0x0B, 0x14, 0x13, 0x13, 0x09, 0x0B, 0x06, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02,
0x0D, 0x07, 0x09, 0x06, 0x06, 0x04, 0x03, 0x02, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02,
0x09, 0x06, 0x04, 0x04, 0x04, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04,
0x08, 0x03, 0x04, 0x07, 0x09, 0x05, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02,
0x06, 0x0A, 0x08, 0x08, 0x06, 0x07, 0x04, 0x03, 0x04, 0x04, 0x02, 0x02, 0x04, 0x02, 0x03, 0x03,
0x04, 0x03, 0x07, 0x07, 0x09, 0x06, 0x04, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02,
0x0A, 0x02, 0x02, 0x03, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x03, 0x05, 0x02, 0x03,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x04, 0x04, 0x04, 0x07, 0x09, 0x08, 0x0C, 0x02,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x03,
0x04, 0x01, 0x02, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B,
}, { // Compression type 2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x27, 0x00, 0x00, 0x23, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x06, 0x0E, 0x10, 0x04,
0x06, 0x08, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x01, 0x01, 0x02, 0x01, 0x01,
0x01, 0x04, 0x02, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x04, 0x01, 0x01, 0x02, 0x03, 0x03, 0x02,
0x03, 0x01, 0x03, 0x06, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01,
0x01, 0x29, 0x07, 0x16, 0x12, 0x40, 0x0A, 0x0A, 0x11, 0x25, 0x01, 0x03, 0x17, 0x10, 0x26, 0x2A,
0x10, 0x01, 0x23, 0x23, 0x2F, 0x10, 0x06, 0x07, 0x02, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01,
}, { // Compression type 3
0xFF, 0x0B, 0x07, 0x05, 0x0B, 0x02, 0x02, 0x02, 0x06, 0x02, 0x02, 0x01, 0x04, 0x02, 0x01, 0x03,
0x09, 0x01, 0x01, 0x01, 0x03, 0x04, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x05, 0x01, 0x01, 0x01, 0x0D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
0x0A, 0x04, 0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01,
0x05, 0x02, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03,
0x01, 0x03, 0x01, 0x01, 0x02, 0x05, 0x01, 0x01, 0x04, 0x03, 0x05, 0x01, 0x03, 0x01, 0x03, 0x03,
0x02, 0x01, 0x04, 0x03, 0x0A, 0x06, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x02, 0x01, 0x0A, 0x02, 0x05, 0x01, 0x01, 0x02, 0x07, 0x02, 0x17, 0x01, 0x05, 0x01, 0x01,
0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x06, 0x02, 0x01, 0x04, 0x05, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11,
}, { // Compression type 4
0xFF, 0xFB, 0x98, 0x9A, 0x84, 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17,
}, { // Compression type 5
0xFF, 0xF1, 0x9D, 0x9E, 0x9A, 0x9B, 0x9A, 0x97, 0x93, 0x93, 0x8C, 0x8E, 0x86, 0x88, 0x80, 0x82,
0x7C, 0x7C, 0x72, 0x73, 0x69, 0x6B, 0x5F, 0x60, 0x55, 0x56, 0x4A, 0x4B, 0x40, 0x41, 0x37, 0x37,
0x2F, 0x2F, 0x27, 0x27, 0x21, 0x21, 0x1B, 0x1C, 0x17, 0x17, 0x13, 0x13, 0x10, 0x10, 0x0D, 0x0D,
0x0B, 0x0B, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x19, 0x18,
}, { // Compression type 6
0xC3, 0xCB, 0xF5, 0x41, 0xFF, 0x7B, 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xBF, 0xCC, 0xF2, 0x40, 0xFD, 0x7C, 0xF7, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7A, 0x46,
}, { // Compression type 7
0xC3, 0xD9, 0xEF, 0x3D, 0xF9, 0x7C, 0xE9, 0x1E, 0xFD, 0xAB, 0xF1, 0x2C, 0xFC, 0x5B, 0xFE, 0x17,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xBD, 0xD9, 0xEC, 0x3D, 0xF5, 0x7D, 0xE8, 0x1D, 0xFB, 0xAE, 0xF0, 0x2C, 0xFB, 0x5C, 0xFF, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x70, 0x6C,
}, { // Compression type 8
0xBA, 0xC5, 0xDA, 0x33, 0xE3, 0x6D, 0xD8, 0x18, 0xE5, 0x94, 0xDA, 0x23, 0xDF, 0x4A, 0xD1, 0x10,
0xEE, 0xAF, 0xE4, 0x2C, 0xEA, 0x5A, 0xDE, 0x15, 0xF4, 0x87, 0xE9, 0x21, 0xF6, 0x43, 0xFC, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xB0, 0xC7, 0xD8, 0x33, 0xE3, 0x6B, 0xD6, 0x18, 0xE7, 0x95, 0xD8, 0x23, 0xDB, 0x49, 0xD0, 0x11,
0xE9, 0xB2, 0xE2, 0x2B, 0xE8, 0x5C, 0xDD, 0x15, 0xF1, 0x87, 0xE7, 0x20, 0xF7, 0x44, 0xFF, 0x13,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x5F, 0x9E,
},
}
func decode(input *d2common.BitStream, head *linkedNode) *linkedNode {
node := head
for node.Child0 != nil {
bit := input.ReadBits(1)
if bit == -1 {
log.Fatal("unexpected end of file")
}
if bit == 0 {
node = node.Child0
continue
}
node = node.GetChild1()
}
return node
}
func buildList(primeData []byte) *linkedNode {
root := CreateLinkedNode(256, 1)
root = root.Insert(CreateLinkedNode(257, 1))
for i := 0; i < len(primeData); i++ {
if primeData[i] != 0 {
root = root.Insert(CreateLinkedNode(i, int(primeData[i])))
}
}
return root
}
func insertNode(tail *linkedNode, decomp int) *linkedNode {
parent := tail
result := tail.Prev // This will be the new tail after the tree is updated
temp := CreateLinkedNode(parent.DecompressedValue, parent.Weight)
temp.Parent = parent
newnode := CreateLinkedNode(decomp, 0)
newnode.Parent = parent
parent.Child0 = newnode
tail.Next = temp
temp.Prev = tail
newnode.Prev = temp
temp.Next = newnode
adjustTree(newnode)
// TODO: For compression type 0, AdjustTree should be called
// once for every value written and only once here
adjustTree(newnode)
return result
}
// This increases the weight of the new node and its antecendants
// and adjusts the tree if needed
func adjustTree(newNode *linkedNode) {
current := newNode
for current != nil {
current.Weight++
var insertpoint *linkedNode
var prev *linkedNode
// Go backwards thru the list looking for the insertion point
insertpoint = current
for true {
prev = insertpoint.Prev
if prev == nil {
break
}
if prev.Weight >= current.Weight {
break
}
insertpoint = prev
}
// No insertion point found
if insertpoint == current {
current = current.Parent
continue
}
// The following code basicly swaps insertpoint with current
// remove insert point
if insertpoint.Prev != nil {
insertpoint.Prev.Next = insertpoint.Next
}
insertpoint.Next.Prev = insertpoint.Prev
// Insert insertpoint after current
insertpoint.Next = current.Next
insertpoint.Prev = current
if current.Next != nil {
current.Next.Prev = insertpoint
}
current.Next = insertpoint
// remove current
current.Prev.Next = current.Next
current.Next.Prev = current.Prev
// insert current after prev
if prev == nil {
log.Fatal("previous frame not defined!")
}
temp := prev.Next
current.Next = temp
current.Prev = prev
temp.Prev = current
prev.Next = current
// Set up parent/child links
currentparent := current.Parent
insertparent := insertpoint.Parent
if currentparent.Child0 == current {
currentparent.Child0 = insertpoint
}
if currentparent != insertparent && insertparent.Child0 == insertpoint {
insertparent.Child0 = current
}
current.Parent = insertparent
insertpoint.Parent = currentparent
current = current.Parent
}
}
func buildTree(tail *linkedNode) *linkedNode {
current := tail
for current != nil {
child0 := current
child1 := current.Prev
if child1 == nil {
break
}
parent := CreateLinkedNode(0, child0.Weight+child1.Weight)
parent.Child0 = child0
child0.Parent = parent
child1.Parent = parent
current.Insert(parent)
current = current.Prev.Prev
}
return current
}
func HuffmanDecompress(data []byte) []byte {
comptype := data[0]
if comptype == 0 {
log.Panic("compression type 0 is not currently supported")
}
tail := buildList(sPrime[comptype])
head := buildTree(tail)
outputstream := d2common.CreateStreamWriter()
bitstream := d2common.CreateBitStream(data[1:])
var decoded int
for true {
node := decode(bitstream, head)
decoded = node.DecompressedValue
switch decoded {
case 256:
break
case 257:
newvalue := bitstream.ReadBits(8)
outputstream.PushByte(byte(newvalue))
tail = insertNode(tail, newvalue)
break
default:
outputstream.PushByte(byte(decoded))
break
}
if decoded == 256 {
break
}
}
return outputstream.GetBytes()
}

131
d2data/d2compression/wav.go Normal file
View File

@ -0,0 +1,131 @@
package d2compression
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
var sLookup = []int{
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462,
0x7FFF,
}
var sLookup2 = []int{
-1, 0, -1, 4, -1, 2, -1, 6,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 2, -1, 4, -1, 6, -1, 8,
}
func WavDecompress(data []byte, channelCount int) []byte {
Array1 := []int{0x2c, 0x2c}
Array2 := make([]int, channelCount)
input := d2common.CreateStreamReader(data)
output := d2common.CreateStreamWriter()
input.GetByte()
shift := input.GetByte()
for i := 0; i < channelCount; i++ {
temp := input.GetInt16()
Array2[i] = int(temp)
output.PushInt16(temp)
}
channel := channelCount - 1
for input.GetPosition() < input.GetSize() {
value := input.GetByte()
if channelCount == 2 {
channel = 1 - channel
}
if (value & 0x80) != 0 {
switch value & 0x7f {
case 0:
if Array1[channel] != 0 {
Array1[channel]--
}
output.PushInt16(int16(Array2[channel]))
break
case 1:
Array1[channel] += 8
if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
if channelCount == 2 {
channel = 1 - channel
}
break
case 2:
break
default:
Array1[channel] -= 8
if Array1[channel] < 0 {
Array1[channel] = 0
}
if channelCount == 2 {
channel = 1 - channel
}
break
}
} else {
temp1 := sLookup[Array1[channel]]
temp2 := temp1 >> shift
if (value & 1) != 0 {
temp2 += temp1 >> 0
}
if (value & 2) != 0 {
temp2 += temp1 >> 1
}
if (value & 4) != 0 {
temp2 += temp1 >> 2
}
if (value & 8) != 0 {
temp2 += temp1 >> 3
}
if (value & 0x10) != 0 {
temp2 += temp1 >> 4
}
if (value & 0x20) != 0 {
temp2 += temp1 >> 5
}
temp3 := Array2[channel]
if (value & 0x40) != 0 {
temp3 -= temp2
if temp3 <= -32768 {
temp3 = -32768
}
} else {
temp3 += temp2
if temp3 >= 32767 {
temp3 = 32767
}
}
Array2[channel] = temp3
output.PushInt16(int16(temp3))
Array1[channel] += sLookup2[value&0x1f]
if Array1[channel] < 0 {
Array1[channel] = 0
} else {
if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
}
}
}
return output.GetBytes()
}

View File

@ -0,0 +1,18 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
var Armors map[string]*ItemCommonRecord
func LoadArmors(fileProvider d2interface.FileProvider) {
Armors = *LoadCommonItems(fileProvider, d2resource.Armor, d2enum.InventoryItemTypeArmor)
log.Printf("Loaded %d armors", len(Armors))
}

View File

@ -0,0 +1,416 @@
package d2datadict
import (
"strconv"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
type ItemCommonRecord struct {
Source d2enum.InventoryItemType
Name string
Version int // 0 = classic, 100 = expansion
CompactSave bool // if true, doesn't store any stats upon saving
Rarity int // higher, the rarer
Spawnable bool // if 0, cannot spawn in shops
MinAC int
MaxAC int
Absorbs int // unused?
Speed int // affects movement speed of wielder, >0 = you move slower, <0 = you move faster
RequiredStrength int
Block int // chance to block, capped at 75%
Durability int // base durability 0-255
NoDurability bool // if true, item has no durability
Level int // base item level (controls monster drops, for instance a lv20 monster cannot drop a lv30 item)
RequiredLevel int // required level to wield
Cost int // base cost
GambleCost int // for reference only, not used
Code string // identifies the item
NameString string // seems to be identical to code?
MagicLevel int // additional magic level (for gambling?)
AutoPrefix int // prefix automatically assigned to this item on spawn, maps to group column of Automagic.txt
AlternateGfx string // code of the DCC used when equipped
OpenBetaGfx string // unknown
NormalCode string
UberCode string
UltraCode string
SpellOffset int // unknown
Component int // corresponds to Composit.txt, player animation layer used by this
InventoryWidth int
InventoryHeight int
HasInventory bool // if true, item can store gems or runes
GemSockets int // number of gems to store
GemApplyType int // what kind of gem effect is applied
// 0 = weapon, 1= armor or helmet, 2 = shield
FlippyFile string // DC6 file animation to play when item drops on the ground
InventoryFile string // DC6 file used in your inventory
UniqueInventoryFile string // DC6 file used by the unique version of this item
SetInventoryFile string // DC6 file used by the set version of this item
// these represent how player animations and graphics change upon wearing this
// these come from ArmType.txt
AnimRightArm int
AnimLeftArm int
AnimTorso int
AnimLegs int
AnimRightShoulderPad int
AnimLeftShoulderPad int
Useable bool // can be used via right click if true
// game knows what to do if used by item code
Throwable bool
Stackable bool // can be stacked in inventory
MinStack int // min size of stack when item is spawned, used if stackable
MaxStack int // max size of stack when item is spawned
Type string // base type in ItemTypes.txt
Type2 string
DropSound string // sfx for dropping
DropSfxFrame int // what frame of drop animation the sfx triggers on
UseSound string // sfx for using
Unique bool // if true, only spawns as unique
Transparent bool // unused
TransTable int // unknown, related to blending mode?
Quivered bool // if true, requires ammo to use
LightRadius int // apparently unused
Belt bool // tells what kind of belt this item is
Quest int // indicates that this item belongs to a given quest?
MissileType int // missile gfx for throwing
DurabilityWarning int // controls what warning icon appears when durability is low
QuantityWarning int // controls at what quantity the low quantity warning appears
MinDamage int
MaxDamage int
StrengthBonus int
DexterityBonus int
// final mindam = min * str / strbonus + min * dex / dexbonus
// same for maxdam
GemOffset int // unknown
BitField1 int // 1 = leather item, 3 = metal
Vendors map[string]*ItemVendorParams // controls vendor settings
SourceArt string // unused?
GameArt string // unused?
ColorTransform int // colormap to use for player's gfx
InventoryColorTransform int // colormap to use for inventory's gfx
SkipName bool // if true, don't include the base name in the item description
NightmareUpgrade string // upgraded in higher difficulties
HellUpgrade string
Nameable bool // if true, item can be personalized
// weapon params
BarbOneOrTwoHanded bool // if true, barb can wield this in one or two hands
UsesTwoHands bool // if true, it's a 2handed weapon
Min2HandDamage int
Max2HandDamage int
MinMissileDamage int // ranged damage stats
MaxMissileDamage int
MissileSpeed int // unknown, affects movement speed of wielder during ranged attacks?
ExtraRange int // base range = 1, if this is non-zero add this to the range
// final mindam = min * str / strbonus + min * dex / dexbonus
// same for maxdam
RequiredDexterity int
WeaponClass string // what kind of attack does this weapon have (i.e. determines attack animations)
WeaponClass2Hand string // what kind of attack when wielded with two hands
HitClass string // determines sounds/graphic effects when attacking
SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?)
SpecialFeature string // Just a comment
QuestDifficultyCheck bool // if true, item only works in the difficulty it was found in
PermStoreItem bool // if true, vendor will always sell this
// misc params
FlavorText string // unknown, probably just for reference
Transmogrify bool // if true, can be turned into another item via right click
TransmogCode string // the 3 char code representing the item this becomes via transmog
TransmogMin int // min amount of the transmog item to create
TransmogMax int // max ''
AutoBelt bool // if true, item is put into your belt when picked up
SpellIcon int // which icon to display when used? Is this always -1?
SpellType int // determines what kind of function is used when you use this item
OverlayState string // name of the overlay state to be applied upon use of this item
CureOverlayStates [2]string // name of the overlay states that are removed upon use of this item
EffectLength int // timer for timed usage effects
UsageStats [3]ItemUsageStat // stat boosts applied upon usage
SpellDescriptionType int // specifies how to format the usage description
// 0 = none, 1 = use desc string, 2 = use desc string + calc value
SpellDescriptionString string // points to a string containing the description
SpellDescriptionCalc d2common.CalcString // a calc string what value to display
BetterGem string // 3 char code pointing to the gem this upgrades to (non if not applicable)
Multibuy bool // if true, when you buy via right click + shift it will fill your belt automatically
}
type ItemUsageStat struct {
Stat string // name of the stat to add to
Calc d2common.CalcString // calc string representing the amount to add
}
type ItemVendorParams struct {
Min int // minimum of this item they can stock
Max int // max they can stock
MagicMin int
MagicMax int
MagicLevel uint8
}
// Loading Functions
var CommonItems map[string]*ItemCommonRecord
func LoadCommonItems(fileProvider d2interface.FileProvider, filepath string, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord {
if CommonItems == nil {
CommonItems = make(map[string]*ItemCommonRecord)
}
items := make(map[string]*ItemCommonRecord)
data := strings.Split(string(fileProvider.LoadFile(filepath)), "\r\n")
mapping := MapHeaders(data[0])
for lineno, line := range data {
if lineno == 0 {
continue
}
if len(line) == 0 {
continue
}
rec := createCommonItemRecord(line, &mapping, source)
items[rec.Code] = &rec
CommonItems[rec.Code] = &rec
}
return &items
}
func createCommonItemRecord(line string, mapping *map[string]int, source d2enum.InventoryItemType) ItemCommonRecord {
r := strings.Split(line, "\t")
result := ItemCommonRecord{
Source: source,
Name: MapLoadString(&r, mapping, "name"),
Version: MapLoadInt(&r, mapping, "version"),
CompactSave: MapLoadBool(&r, mapping, "compactsave"),
Rarity: MapLoadInt(&r, mapping, "rarity"),
Spawnable: MapLoadBool(&r, mapping, "spawnable"),
MinAC: MapLoadInt(&r, mapping, "minac"),
MaxAC: MapLoadInt(&r, mapping, "maxac"),
Absorbs: MapLoadInt(&r, mapping, "absorbs"),
Speed: MapLoadInt(&r, mapping, "speed"),
RequiredStrength: MapLoadInt(&r, mapping, "reqstr"),
Block: MapLoadInt(&r, mapping, "block"),
Durability: MapLoadInt(&r, mapping, "durability"),
NoDurability: MapLoadBool(&r, mapping, "nodurability"),
Level: MapLoadInt(&r, mapping, "level"),
RequiredLevel: MapLoadInt(&r, mapping, "levelreq"),
Cost: MapLoadInt(&r, mapping, "cost"),
GambleCost: MapLoadInt(&r, mapping, "gamble cost"),
Code: MapLoadString(&r, mapping, "code"),
NameString: MapLoadString(&r, mapping, "namestr"),
MagicLevel: MapLoadInt(&r, mapping, "magic lvl"),
AutoPrefix: MapLoadInt(&r, mapping, "auto prefix"),
AlternateGfx: MapLoadString(&r, mapping, "alternategfx"),
OpenBetaGfx: MapLoadString(&r, mapping, "OpenBetaGfx"),
NormalCode: MapLoadString(&r, mapping, "normcode"),
UberCode: MapLoadString(&r, mapping, "ubercode"),
UltraCode: MapLoadString(&r, mapping, "ultracode"),
SpellOffset: MapLoadInt(&r, mapping, "spelloffset"),
Component: MapLoadInt(&r, mapping, "component"),
InventoryWidth: MapLoadInt(&r, mapping, "invwidth"),
InventoryHeight: MapLoadInt(&r, mapping, "invheight"),
HasInventory: MapLoadBool(&r, mapping, "hasinv"),
GemSockets: MapLoadInt(&r, mapping, "gemsockets"),
GemApplyType: MapLoadInt(&r, mapping, "gemapplytype"),
FlippyFile: MapLoadString(&r, mapping, "flippyfile"),
InventoryFile: MapLoadString(&r, mapping, "invfile"),
UniqueInventoryFile: MapLoadString(&r, mapping, "uniqueinvfile"),
SetInventoryFile: MapLoadString(&r, mapping, "setinvfile"),
AnimRightArm: MapLoadInt(&r, mapping, "rArm"),
AnimLeftArm: MapLoadInt(&r, mapping, "lArm"),
AnimTorso: MapLoadInt(&r, mapping, "Torso"),
AnimLegs: MapLoadInt(&r, mapping, "Legs"),
AnimRightShoulderPad: MapLoadInt(&r, mapping, "rSPad"),
AnimLeftShoulderPad: MapLoadInt(&r, mapping, "lSPad"),
Useable: MapLoadBool(&r, mapping, "useable"),
Throwable: MapLoadBool(&r, mapping, "throwable"),
Stackable: MapLoadBool(&r, mapping, "stackable"),
MinStack: MapLoadInt(&r, mapping, "minstack"),
MaxStack: MapLoadInt(&r, mapping, "maxstack"),
Type: MapLoadString(&r, mapping, "type"),
Type2: MapLoadString(&r, mapping, "type2"),
DropSound: MapLoadString(&r, mapping, "dropsound"),
DropSfxFrame: MapLoadInt(&r, mapping, "dropsfxframe"),
UseSound: MapLoadString(&r, mapping, "usesound"),
Unique: MapLoadBool(&r, mapping, "unique"),
Transparent: MapLoadBool(&r, mapping, "transparent"),
TransTable: MapLoadInt(&r, mapping, "transtbl"),
Quivered: MapLoadBool(&r, mapping, "quivered"),
LightRadius: MapLoadInt(&r, mapping, "lightradius"),
Belt: MapLoadBool(&r, mapping, "belt"),
Quest: MapLoadInt(&r, mapping, "quest"),
MissileType: MapLoadInt(&r, mapping, "missiletype"),
DurabilityWarning: MapLoadInt(&r, mapping, "durwarning"),
QuantityWarning: MapLoadInt(&r, mapping, "qntwarning"),
MinDamage: MapLoadInt(&r, mapping, "mindam"),
MaxDamage: MapLoadInt(&r, mapping, "maxdam"),
StrengthBonus: MapLoadInt(&r, mapping, "StrBonus"),
DexterityBonus: MapLoadInt(&r, mapping, "DexBonus"),
GemOffset: MapLoadInt(&r, mapping, "gemoffset"),
BitField1: MapLoadInt(&r, mapping, "bitfield1"),
Vendors: createItemVendorParams(&r, mapping),
SourceArt: MapLoadString(&r, mapping, "Source Art"),
GameArt: MapLoadString(&r, mapping, "Game Art"),
ColorTransform: MapLoadInt(&r, mapping, "Transform"),
InventoryColorTransform: MapLoadInt(&r, mapping, "InvTrans"),
SkipName: MapLoadBool(&r, mapping, "SkipName"),
NightmareUpgrade: MapLoadString(&r, mapping, "NightmareUpgrade"),
HellUpgrade: MapLoadString(&r, mapping, "HellUpgrade"),
Nameable: MapLoadBool(&r, mapping, "Nameable"),
// weapon params
BarbOneOrTwoHanded: MapLoadBool(&r, mapping, "1or2handed"),
UsesTwoHands: MapLoadBool(&r, mapping, "2handed"),
Min2HandDamage: MapLoadInt(&r, mapping, "2handmindam"),
Max2HandDamage: MapLoadInt(&r, mapping, "2handmaxdam"),
MinMissileDamage: MapLoadInt(&r, mapping, "minmisdam"),
MaxMissileDamage: MapLoadInt(&r, mapping, "maxmisdam"),
MissileSpeed: MapLoadInt(&r, mapping, "misspeed"),
ExtraRange: MapLoadInt(&r, mapping, "rangeadder"),
RequiredDexterity: MapLoadInt(&r, mapping, "reqdex"),
WeaponClass: MapLoadString(&r, mapping, "wclass"),
WeaponClass2Hand: MapLoadString(&r, mapping, "2handedwclass"),
HitClass: MapLoadString(&r, mapping, "hit class"),
SpawnStack: MapLoadInt(&r, mapping, "spawnstack"),
SpecialFeature: MapLoadString(&r, mapping, "special"),
QuestDifficultyCheck: MapLoadBool(&r, mapping, "questdiffcheck"),
PermStoreItem: MapLoadBool(&r, mapping, "PermStoreItem"),
// misc params
FlavorText: MapLoadString(&r, mapping, "szFlavorText"),
Transmogrify: MapLoadBool(&r, mapping, "Transmogrify"),
TransmogCode: MapLoadString(&r, mapping, "TMogType"),
TransmogMin: MapLoadInt(&r, mapping, "TMogMin"),
TransmogMax: MapLoadInt(&r, mapping, "TMogMax"),
AutoBelt: MapLoadBool(&r, mapping, "autobelt"),
SpellIcon: MapLoadInt(&r, mapping, "spellicon"),
SpellType: MapLoadInt(&r, mapping, "pSpell"),
OverlayState: MapLoadString(&r, mapping, "state"),
CureOverlayStates: [2]string{
MapLoadString(&r, mapping, "cstate1"),
MapLoadString(&r, mapping, "cstate2"),
},
EffectLength: MapLoadInt(&r, mapping, "len"),
UsageStats: createItemUsageStats(&r, mapping),
SpellDescriptionType: MapLoadInt(&r, mapping, "spelldesc"),
// 0 = none, 1 = use desc string, 2 = use desc string + calc value
SpellDescriptionString: MapLoadString(&r, mapping, "spelldescstr"),
SpellDescriptionCalc: d2common.CalcString(MapLoadString(&r, mapping, "spelldesccalc")),
BetterGem: MapLoadString(&r, mapping, "BetterGem"),
Multibuy: MapLoadBool(&r, mapping, "multibuy"),
}
return result
}
func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*ItemVendorParams {
vs := make([]string, 17)
vs[0] = "Charsi"
vs[1] = "Gheed"
vs[2] = "Akara"
vs[3] = "Fara"
vs[4] = "Lysander"
vs[5] = "Drognan"
vs[6] = "Hralti"
vs[7] = "Alkor"
vs[8] = "Ormus"
vs[9] = "Elzix"
vs[10] = "Asheara"
vs[11] = "Cain"
vs[12] = "Halbu"
vs[13] = "Jamella"
vs[14] = "Larzuk"
vs[15] = "Malah"
vs[16] = "Drehya"
result := make(map[string]*ItemVendorParams)
for _, name := range vs {
wvp := ItemVendorParams{
Min: MapLoadInt(r, mapping, name + "Min"),
Max: MapLoadInt(r, mapping, name + "Max"),
MagicMin: MapLoadInt(r, mapping, name + "MagicMin"),
MagicMax: MapLoadInt(r, mapping, name + "MagicMax"),
MagicLevel: MapLoadUint8(r, mapping, name + "MagicLvl"),
}
result[name] = &wvp
}
return result
}
func createItemUsageStats(r *[]string, mapping *map[string]int) [3]ItemUsageStat {
result := [3]ItemUsageStat{}
for i := 0; i < 3; i++ {
result[i].Stat = MapLoadString(r, mapping, "stat" + strconv.Itoa(i))
result[i].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc" + strconv.Itoa(i)))
}
return result
}

View File

@ -0,0 +1,93 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
type LevelPresetRecord struct {
Name string
DefinitionId int
LevelId int
Populate bool
Logicals bool
Outdoors bool
Animate bool
KillEdge bool
FillBlanks bool
SizeX int
SizeY int
AutoMap bool
Scan bool
Pops int
PopPad int
FileCount int
Files [6]string
Dt1Mask uint
Beta bool
Expansion bool
}
// CreateLevelPresetRecord parses a row from lvlprest.txt into a LevelPresetRecord
func createLevelPresetRecord(props []string) LevelPresetRecord {
i := -1
inc := func() int {
i++
return i
}
result := LevelPresetRecord{
Name: props[inc()],
DefinitionId: dh.StringToInt(props[inc()]),
LevelId: dh.StringToInt(props[inc()]),
Populate: dh.StringToUint8(props[inc()]) == 1,
Logicals: dh.StringToUint8(props[inc()]) == 1,
Outdoors: dh.StringToUint8(props[inc()]) == 1,
Animate: dh.StringToUint8(props[inc()]) == 1,
KillEdge: dh.StringToUint8(props[inc()]) == 1,
FillBlanks: dh.StringToUint8(props[inc()]) == 1,
SizeX: dh.StringToInt(props[inc()]),
SizeY: dh.StringToInt(props[inc()]),
AutoMap: dh.StringToUint8(props[inc()]) == 1,
Scan: dh.StringToUint8(props[inc()]) == 1,
Pops: dh.StringToInt(props[inc()]),
PopPad: dh.StringToInt(props[inc()]),
FileCount: dh.StringToInt(props[inc()]),
Files: [6]string{
props[inc()],
props[inc()],
props[inc()],
props[inc()],
props[inc()],
props[inc()],
},
Dt1Mask: dh.StringToUint(props[inc()]),
Beta: dh.StringToUint8(props[inc()]) == 1,
Expansion: dh.StringToUint8(props[inc()]) == 1,
}
return result
}
var LevelPresets map[int]LevelPresetRecord
func LoadLevelPresets(fileProvider d2interface.FileProvider) {
LevelPresets = make(map[int]LevelPresetRecord)
data := strings.Split(string(fileProvider.LoadFile(d2resource.LevelPreset)), "\r\n")[1:]
for _, line := range data {
if len(line) == 0 {
continue
}
props := strings.Split(line, "\t")
if props[1] == "" {
continue // any line without a definition id is skipped (e.g. the "Expansion" line)
}
rec := createLevelPresetRecord(props)
LevelPresets[rec.DefinitionId] = rec
}
log.Printf("Loaded %d level presets", len(LevelPresets))
}

View File

@ -0,0 +1,56 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
type LevelTypeRecord struct {
Name string
Id int
Files [32]string
Beta bool
Act int
Expansion bool
}
var LevelTypes []LevelTypeRecord
func LoadLevelTypes(fileProvider d2interface.FileProvider) {
data := strings.Split(string(fileProvider.LoadFile(d2resource.LevelType)), "\r\n")[1:]
LevelTypes = make([]LevelTypeRecord, len(data))
for i, j := 0, 0; i < len(data); i, j = i+1, j+1 {
idx := -1
inc := func() int {
idx++
return idx
}
if len(data[i]) == 0 {
continue
}
parts := strings.Split(data[i], "\t")
if parts[0] == "Expansion" {
j--
continue
}
LevelTypes[j].Name = parts[inc()]
LevelTypes[j].Id = dh.StringToInt(parts[inc()])
for fileIdx := range LevelTypes[i].Files {
LevelTypes[j].Files[fileIdx] = parts[inc()]
if LevelTypes[j].Files[fileIdx] == "0" {
LevelTypes[j].Files[fileIdx] = ""
}
}
LevelTypes[j].Beta = parts[inc()] != "1"
LevelTypes[j].Act = dh.StringToInt(parts[inc()])
LevelTypes[j].Expansion = parts[inc()] != "1"
}
log.Printf("Loaded %d LevelType records", len(LevelTypes))
}

View File

@ -0,0 +1,53 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type LevelWarpRecord struct {
Id int32
SelectX int32
SelectY int32
SelectDX int32
SelectDY int32
ExitWalkX int32
ExitWalkY int32
OffsetX int32
OffsetY int32
LitVersion bool
Tiles int32
Direction string
}
var LevelWarps map[int]*LevelWarpRecord
func LoadLevelWarps(fileProvider d2interface.FileProvider) {
LevelWarps = make(map[int]*LevelWarpRecord)
levelWarpData := fileProvider.LoadFile(d2resource.LevelWarp)
streamReader := d2common.CreateStreamReader(levelWarpData)
numRecords := int(streamReader.GetInt32())
for i := 0; i < numRecords; i++ {
id := int(streamReader.GetInt32())
LevelWarps[id] = &LevelWarpRecord{}
LevelWarps[id].Id = int32(id)
LevelWarps[id].SelectX = streamReader.GetInt32()
LevelWarps[id].SelectY = streamReader.GetInt32()
LevelWarps[id].SelectDX = streamReader.GetInt32()
LevelWarps[id].SelectDY = streamReader.GetInt32()
LevelWarps[id].ExitWalkX = streamReader.GetInt32()
LevelWarps[id].ExitWalkY = streamReader.GetInt32()
LevelWarps[id].OffsetX = streamReader.GetInt32()
LevelWarps[id].OffsetY = streamReader.GetInt32()
LevelWarps[id].LitVersion = streamReader.GetInt32() == 1
LevelWarps[id].Tiles = streamReader.GetInt32()
LevelWarps[id].Direction = string(streamReader.GetByte())
streamReader.SkipBytes(3)
}
log.Printf("Loaded %d level warps", len(LevelWarps))
}

View File

@ -0,0 +1,43 @@
package d2datadict
import (
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"strings"
)
func MapHeaders(line string) map[string]int {
m := make(map[string]int)
r := strings.Split(line, "\t")
for index, header := range r {
m[header] = index
}
return m
}
func MapLoadInt(r *[]string, mapping *map[string]int, field string) int {
index, ok := (*mapping)[field]
if ok {
return dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty((*r)[index])))
}
return 0
}
func MapLoadString(r *[]string, mapping *map[string]int, field string) string {
index, ok := (*mapping)[field]
if ok {
return dh.AsterToEmpty((*r)[index])
}
return ""
}
func MapLoadBool(r *[]string, mapping *map[string]int, field string) bool {
return MapLoadInt(r, mapping, field) == 1
}
func MapLoadUint8(r *[]string, mapping *map[string]int, field string) uint8 {
index, ok := (*mapping)[field]
if ok {
return dh.StringToUint8(dh.EmptyToZero(dh.AsterToEmpty((*r)[index])))
}
return 0
}

18
d2data/d2datadict/misc.go Normal file
View File

@ -0,0 +1,18 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
var MiscItems map[string]*ItemCommonRecord
func LoadMiscItems(fileProvider d2interface.FileProvider) {
MiscItems = *LoadCommonItems(fileProvider, d2resource.Misc, d2enum.InventoryItemTypeItem)
log.Printf("Loaded %d misc items", len(MiscItems))
}

View File

@ -0,0 +1,411 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
type MissileCalcParam struct {
Param int
Desc string
}
type MissileCalc struct {
Calc d2common.CalcString
Desc string
Params []MissileCalcParam
}
type MissileLight struct {
Diameter int
Flicker int
Red uint8
Green uint8
Blue uint8
}
type MissileAnimation struct {
StepsBeforeVisible int
StepsBeforeActive int
LoopAnimation bool
CelFileName string
AnimationRate int // seems to do nothing
AnimationLength int
AnimationSpeed int
StartingFrame int // called "RandFrame"
HasSubLoop bool // runs after first animation ends
SubStartingFrame int
SubEndingFrame int
}
type MissileCollision struct {
CollisionType int // controls the kind of collision
// 0 = none, 1 = units only, 3 = normal (units, walls),
// 6 = walls only, 8 = walls, units, and floors
DestroyedUponCollision bool
FriendlyFire bool
LastCollide bool // unknown
Collision bool // unknown
ClientCollision bool // unknown
ClientSend bool // unclear
UseCollisionTimer bool // after hit, use timer before dying
TimerFrames int // how many frames to persist
}
type MissileDamage struct {
MinDamage int
MaxDamage int
MinLevelDamage [5]int // additional damage per missile level
// [0]: lvs 2-8, [1]: lvs 9-16, [2]: lvs 17-22, [3]: lvs 23-28, [4]: lv 29+
MaxLevelDamage [5]int // see above
DamageSynergyPerCalc d2common.CalcString // works like synergy in skills.txt, not clear
}
type MissileElementalDamage struct {
Damage MissileDamage
ElementType string
Duration int // frames, 25 = 1 second
LevelDuration [3]int // 0,1,2, unknown level intervals, bonus duration per level
}
type MissileRecord struct {
Name string
Id int
ClientMovementFunc int
ClientCollisionFunc int
ServerMovementFunc int
ServerCollisionFunc int
ServerDamageFunc int
ServerMovementCalc MissileCalc
ClientMovementCalc MissileCalc
ServerCollisionCalc MissileCalc
ClientCollisionCalc MissileCalc
ServerDamageCalc MissileCalc
Velocity int
MaxVelocity int
LevelVelocityBonus int
Accel int
Range int
LevelRangeBonus int
Light MissileLight
Animation MissileAnimation
Collision MissileCollision
XOffset int
YOffset int
ZOffset int
Size int // diameter
DestroyedByTP bool // if true, destroyed when source player teleports to town
DestroyedByTPFrame int // see above, for client side, (this is a guess) which frame it vanishes on
CanDestroy bool // unknown
UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses
// if false, has a 95% chance to hit.
AlwaysExplode bool // if true, always calls its collision function when it is destroyed, even if it doesn't hit anything
// note that some collision functions (lightning fury) seem to ignore this and always explode regardless of setting (requires investigation)
ClientExplosion bool // if true, does not really exist
// is only aesthetic / client side
TownSafe bool // if true, doesn't vanish when spawned in town
// if false, vanishes when spawned in town
IgnoreBossModifiers bool // if true, doesn't get bonuses from boss mods
IgnoreMultishot bool // if true, can't gain the mulitshot modifier
HolyFilterType int // specifies what this missile can hit
// 0 = all units, 1 = undead only, 2 = demons only, 3 = all units (again?)
CanBeSlowed bool // if true, is affected by skill_handofathena
TriggersHitEvents bool // if true, triggers events that happen "upon getting hit" on targets
TriggersGetHit bool // if true, can cause target to enter hit recovery mode
SoftHit bool // unknown
KnockbackPercent int // chance of knocking the target back, 0-100
TransparencyMode int // controls rendering
// 0 = normal, 1 = alpha blending (darker = more transparent)
// 2 = special (black and white?)
UseQuantity bool // if true, uses quantity
// not clear what this means. Also apparently requires a special starting function in skills.txt
AffectedByPierce bool // if true, affected by the pierce modifier and the Pierce skill
SpecialSetup bool // unknown, only true for potions
MissileSkill bool // if true, applies elemental damage from items to the splash radius instead of normal damage modifiers
SkillName string // if not empty, the missile will refer to this skill instead of its own data for the following:
// "ResultFlags, HitFlags, HitShift, HitClass, SrcDamage (SrcDam in skills.txt!),
// MinDam, MinLevDam1-5, MaxDam, MaxLevDam1-5, DmgSymPerCalc, EType, EMin, EMinLev1-5,
// EMax, EMaxLev1-5, EDmgSymPerCalc, ELen, ELenLev1-3, ELenSymPerCalc"
ResultFlags int // unknown
// 4 = normal missiles, 5 = explosions, 8 = non-damaging missiles
HitFlags int // unknown
// 2 = explosions, 5 = freezing arrow
HitShift int // damage is measured in 256s
// the actual damage is [damage] * 2 ^ [hitshift]
// e.g. 100 damage, 8 hitshift = 100 * 2 ^ 8 = 100 * 256 = 25600
// (visually, the damage is this result / 256)
ApplyMastery bool // unknown
SourceDamage int // 0-128, 128 is 100%
// percentage of source units attack properties to apply to the missile?
// not only affects damage but also other modifiers like lifesteal and manasteal (need a complete list)
// setting this to -1 "gets rid of SrcDmg from skills.txt", not clear what that means
HalfDamageForTwoHander bool // if true, damage is halved when a two-handed weapon is used
SourceMissDamage int // 0-128, 128 is 100%
// unknown, only used for poison clouds.
Damage MissileDamage
ElementalDamage MissileElementalDamage
HitClass int // controls clientside aesthetic effects for collisions
// particularly sound effects that are played on a hit
NumDirections int // count of dirs in the DCC loaded by CelFile
// apparently this value is no longer needed in D2
LocalBlood int // blood effects?
// 0 = no blood, 1 = blood, 2 = blood and affected by open wounds
DamageReductionRate int // how many frames between applications of the
// magic_damage_reduced stat, so for instance on a 0 this stat applies every frame
// on a 3, only every 4th frame has damage reduced
TravelSound string // name of sound to play during lifetime
// whether or not it loops depends on the specific sound's settings?
// if it doesn't loop, it's just a on-spawn sound effect
HitSound string // sound plays upon collision
ProgSound string // plays at "special events", like a mariachi band
ProgOverlay string // name of an overlay from overlays.txt to use at special events
ExplosionMissile string // name of a missile from missiles.txt that is created upon collision
// or anytime it is destroyed if AlwaysExplode is true
SubMissile [3]string // 0,1,2 name of missiles spawned by movement function
HitSubMissile [4]string // 0,1,2 name of missiles spawned by collision function
ClientSubMissile [3]string // see above, but for client only
ClientHitSubMissile [4]string // see above, but for client only
}
func createMissileRecord(line string) MissileRecord {
r := strings.Split(line, "\t")
i := -1
inc := func() int {
i++
return i
}
// note: in this file, empties are equivalent to zero, so all numerical conversions should
// be wrapped in an dh.EmptyToZero transform
result := MissileRecord{
Name: r[inc()],
Id: dh.StringToInt(dh.EmptyToZero(r[inc()])),
ClientMovementFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
ClientCollisionFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
ServerMovementFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
ServerCollisionFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
ServerDamageFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
ServerMovementCalc: loadMissileCalc(&r, inc, 5),
ClientMovementCalc: loadMissileCalc(&r, inc, 5),
ServerCollisionCalc: loadMissileCalc(&r, inc, 3),
ClientCollisionCalc: loadMissileCalc(&r, inc, 3),
ServerDamageCalc: loadMissileCalc(&r, inc, 2),
Velocity: dh.StringToInt(dh.EmptyToZero(r[inc()])),
MaxVelocity: dh.StringToInt(dh.EmptyToZero(r[inc()])),
LevelVelocityBonus: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Accel: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Range: dh.StringToInt(dh.EmptyToZero(r[inc()])),
LevelRangeBonus: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Light: loadMissileLight(&r, inc),
Animation: loadMissileAnimation(&r, inc),
Collision: loadMissileCollision(&r, inc),
XOffset: dh.StringToInt(dh.EmptyToZero(r[inc()])),
YOffset: dh.StringToInt(dh.EmptyToZero(r[inc()])),
ZOffset: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Size: dh.StringToInt(dh.EmptyToZero(r[inc()])),
DestroyedByTP: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
DestroyedByTPFrame: dh.StringToInt(dh.EmptyToZero(r[inc()])),
CanDestroy: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
UseAttackRating: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
AlwaysExplode: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
ClientExplosion: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
TownSafe: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
IgnoreBossModifiers: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
IgnoreMultishot: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
HolyFilterType: dh.StringToInt(dh.EmptyToZero(r[inc()])),
CanBeSlowed: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
TriggersHitEvents: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
TriggersGetHit: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
SoftHit: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
KnockbackPercent: dh.StringToInt(dh.EmptyToZero(r[inc()])),
TransparencyMode: dh.StringToInt(dh.EmptyToZero(r[inc()])),
UseQuantity: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
AffectedByPierce: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
SpecialSetup: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
MissileSkill: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
SkillName: r[inc()],
ResultFlags: dh.StringToInt(dh.EmptyToZero(r[inc()])),
HitFlags: dh.StringToInt(dh.EmptyToZero(r[inc()])),
HitShift: dh.StringToInt(dh.EmptyToZero(r[inc()])),
ApplyMastery: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
SourceDamage: dh.StringToInt(dh.EmptyToZero(r[inc()])),
HalfDamageForTwoHander: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
SourceMissDamage: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Damage: loadMissileDamage(&r, inc),
ElementalDamage: loadMissileElementalDamage(&r, inc),
HitClass: dh.StringToInt(dh.EmptyToZero(r[inc()])),
NumDirections: dh.StringToInt(dh.EmptyToZero(r[inc()])),
LocalBlood: dh.StringToInt(dh.EmptyToZero(r[inc()])),
DamageReductionRate: dh.StringToInt(dh.EmptyToZero(r[inc()])),
TravelSound: r[inc()],
HitSound: r[inc()],
ProgSound: r[inc()],
ProgOverlay: r[inc()],
ExplosionMissile: r[inc()],
SubMissile: [3]string{r[inc()], r[inc()], r[inc()]},
HitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]},
ClientSubMissile: [3]string{r[inc()], r[inc()], r[inc()]},
ClientHitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]},
}
return result
}
var Missiles map[int]*MissileRecord
func LoadMissiles(fileProvider d2interface.FileProvider) {
Missiles = make(map[int]*MissileRecord)
data := strings.Split(string(fileProvider.LoadFile(d2resource.Missiles)), "\r\n")[1:]
for _, line := range data {
if len(line) == 0 {
continue
}
rec := createMissileRecord(line)
Missiles[rec.Id] = &rec
}
log.Printf("Loaded %d missiles", len(Missiles))
}
func loadMissileCalcParam(r *[]string, inc func() int) MissileCalcParam {
result := MissileCalcParam{
Param: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
Desc: (*r)[inc()],
}
return result
}
func loadMissileCalc(r *[]string, inc func() int, params int) MissileCalc {
result := MissileCalc{
Calc: d2common.CalcString((*r)[inc()]),
Desc: (*r)[inc()],
}
result.Params = make([]MissileCalcParam, params)
for p := 0; p < params; p++ {
result.Params[p] = loadMissileCalcParam(r, inc)
}
return result
}
func loadMissileLight(r *[]string, inc func() int) MissileLight {
result := MissileLight{
Diameter: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
Flicker: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
Red: dh.StringToUint8(dh.EmptyToZero((*r)[inc()])),
Green: dh.StringToUint8(dh.EmptyToZero((*r)[inc()])),
Blue: dh.StringToUint8(dh.EmptyToZero((*r)[inc()])),
}
return result
}
func loadMissileAnimation(r *[]string, inc func() int) MissileAnimation {
result := MissileAnimation{
StepsBeforeVisible: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
StepsBeforeActive: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
LoopAnimation: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
CelFileName: (*r)[inc()],
AnimationRate: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
AnimationLength: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
AnimationSpeed: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
StartingFrame: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
HasSubLoop: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
SubStartingFrame: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
SubEndingFrame: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
}
return result
}
func loadMissileCollision(r *[]string, inc func() int) MissileCollision {
result := MissileCollision{
CollisionType: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
DestroyedUponCollision: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
FriendlyFire: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
LastCollide: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
Collision: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
ClientCollision: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
ClientSend: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
UseCollisionTimer: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
TimerFrames: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
}
return result
}
func loadMissileDamage(r *[]string, inc func() int) MissileDamage {
result := MissileDamage{
MinDamage: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
MinLevelDamage: [5]int{
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
},
MaxDamage: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
MaxLevelDamage: [5]int{
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
},
DamageSynergyPerCalc: d2common.CalcString((*r)[inc()]),
}
return result
}
func loadMissileElementalDamage(r *[]string, inc func() int) MissileElementalDamage {
result := MissileElementalDamage{
ElementType: (*r)[inc()],
Damage: loadMissileDamage(r, inc),
Duration: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
LevelDuration: [3]int{
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
},
}
return result
}

View File

@ -0,0 +1,13 @@
package d2datadict
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
var MonStatsDictionary *d2common.DataDictionary
func LoadMonStats(fileProvider d2interface.FileProvider) {
MonStatsDictionary = d2common.LoadDataDictionary(string(fileProvider.LoadFile(d2resource.MonStats)))
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
package d2datadict
import (
"log"
)
type ObjectType int
const (
ObjectTypeCharacter ObjectType = 1
ObjectTypeItem ObjectType = 2
)
type ObjectLookupRecord struct {
Act int
Type ObjectType
Id int
Description string
ObjectsTxtId int
MonstatsTxtId int
Direction int
Base string
Token string
Mode string
Class string
HD string
TR string
LG string
RA string
LA string
RH string
LH string
SH string
S1 string
S2 string
S3 string
S4 string
S5 string
S6 string
S7 string
S8 string
ColorMap string
Index int
}
func LookupObject(act, typ, id int) *ObjectLookupRecord {
object := lookupObject(act, typ, id, indexedObjects)
if object == nil {
log.Panicf("Failed to look up object Act: %d, Type: %d, Id: %d", act, typ, id)
}
return object
}
func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLookupRecord {
if objects[act] != nil && objects[act][typ] != nil && objects[act][typ][id] != nil {
return objects[act][typ][id]
}
return nil
}
func init() {
indexedObjects = indexObjects(objectLookups)
}
func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord {
// Allocating 6 to allow Acts 1-5 without requiring a -1 at every read.
indexedObjects = make([][][]*ObjectLookupRecord, 6)
for i, _ := range objects {
record := &objects[i]
if indexedObjects[record.Act] == nil {
// Likewise allocating 3 so a -1 isn't necessary.
indexedObjects[record.Act] = make([][]*ObjectLookupRecord, 3)
}
if indexedObjects[record.Act][record.Type] == nil {
// For simplicity, allocating with length 1000 then filling the values in by index.
// If ids in the dictionary ever surpass 1000, raise this number.
indexedObjects[record.Act][record.Type] = make([]*ObjectLookupRecord, 1000)
}
indexedObjects[record.Act][record.Type][record.Id] = record
}
return indexedObjects
}
// Indexed slice of object records for quick lookups.
// nil checks should be done for uninitialized values at each level.
// [Act 1-5][Type 1-2][Id 0-855]
var indexedObjects [][][]*ObjectLookupRecord

View File

@ -0,0 +1,40 @@
package d2datadict
import (
testify "github.com/stretchr/testify/assert"
"testing"
)
// Verify the lookup returns the right object after indexing.
func TestIndexObjects(t *testing.T) {
assert := testify.New(t)
testObjects := []ObjectLookupRecord{
{Act: 1, Type: ObjectTypeCharacter, Id: 0, Description: "Act1CharId0"},
{Act: 1, Type: ObjectTypeCharacter, Id: 1, Description: "Act1CharId1"},
{Act: 1, Type: ObjectTypeCharacter, Id: 2, Description: "Act1CharId2"},
{Act: 1, Type: ObjectTypeCharacter, Id: 3, Description: "Act1CharId3"},
{Act: 1, Type: ObjectTypeItem, Id: 0, Description: "Act1ItemId0"},
{Act: 1, Type: ObjectTypeItem, Id: 1, Description: "Act1ItemId1"},
{Act: 1, Type: ObjectTypeItem, Id: 2, Description: "Act1ItemId2"},
{Act: 1, Type: ObjectTypeItem, Id: 3, Description: "Act1ItemId3"},
{Act: 2, Type: ObjectTypeCharacter, Id: 0, Description: "Act2CharId0"},
{Act: 2, Type: ObjectTypeCharacter, Id: 1, Description: "Act2CharId1"},
{Act: 2, Type: ObjectTypeCharacter, Id: 2, Description: "Act2CharId2"},
{Act: 2, Type: ObjectTypeCharacter, Id: 3, Description: "Act2CharId3"},
{Act: 2, Type: ObjectTypeItem, Id: 0, Description: "Act2ItemId0"},
{Act: 2, Type: ObjectTypeItem, Id: 1, Description: "Act2ItemId1"},
{Act: 2, Type: ObjectTypeItem, Id: 2, Description: "Act2ItemId2"},
{Act: 2, Type: ObjectTypeItem, Id: 3, Description: "Act2ItemId3"},
}
indexedTestObjects := indexObjects(testObjects)
typeCharacter := int(ObjectTypeCharacter)
typeItem := int(ObjectTypeItem)
assert.Equal("Act1CharId2", lookupObject(1, typeCharacter, 2, indexedTestObjects).Description)
assert.Equal("Act1ItemId0", lookupObject(1, typeItem, 0, indexedTestObjects).Description)
assert.Equal("Act2CharId3", lookupObject(2, typeCharacter, 3, indexedTestObjects).Description)
assert.Equal("Act2ItemId1", lookupObject(2, typeItem, 1, indexedTestObjects).Description)
}

View File

@ -0,0 +1,35 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type ObjectTypeRecord struct {
Name string
Token string
}
var ObjectTypes []ObjectTypeRecord
func LoadObjectTypes(fileProvider d2interface.FileProvider) {
objectTypeData := fileProvider.LoadFile(d2resource.ObjectType)
streamReader := d2common.CreateStreamReader(objectTypeData)
count := streamReader.GetInt32()
ObjectTypes = make([]ObjectTypeRecord, count)
for i := range ObjectTypes {
nameBytes, _ := streamReader.ReadBytes(32)
tokenBytes, _ := streamReader.ReadBytes(20)
ObjectTypes[i] = ObjectTypeRecord{
Name: strings.TrimSpace(strings.ReplaceAll(string(nameBytes), string(0), "")),
Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")),
}
}
log.Printf("Loaded %d object types", len(ObjectTypes))
}

View File

@ -0,0 +1,357 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
// An ObjectRecord represents the settings for one type of object from objects.txt
type ObjectRecord struct {
Name string
Description string
Id int
Token string // refers to what graphics this object uses
SpawnMax int // unused?
Selectable [8]bool // is this mode selectable
TrapProbability int // unused
SizeX int
SizeY int
NTgtFX int // unknown
NTgtFY int // unknown
NTgtBX int // unknown
NTgtBY int // unknown
FrameCount [8]int // how many frames does this mode have, 0 = skip
FrameDelta [8]int // what rate is the animation played at (256 = 100% speed)
CycleAnimation [8]bool // probably whether animation loops
LightDiameter [8]int
BlocksLight [8]bool
HasCollision [8]bool
IsAttackable bool // do we kick it when interacting
StartFrame [8]int
EnvEffect bool // unknown
IsDoor bool
BlockVisibility bool // only works with IsDoor
Orientation int // unknown (1=sw, 2=nw, 3=se, 4=ne)
Trans int // controls palette mapping
OrderFlag [8]int // 0 = object, 1 = floor, 2 = wall
PreOperate bool // unknown
HasAnimationMode [8]bool // 'Mode' in source, true if this mode is used
XOffset int // in pixels offset
YOffset int
Draw bool // if false, object isn't drawn (shadow is still drawn and player can still select though)
LightRed byte // if lightdiameter is set, rgb of the light
LightGreen byte
LightBlue byte
SelHD bool // whether these DCC components are selectable
SelTR bool
SelLG bool
SelRA bool
SelLA bool
SelRH bool
SelLH bool
SelSH bool
SelS [8]bool
TotalPieces int // selectable DCC components count
SubClass int // subclass of object:
// 1 = shrine
// 2 = obelisk
// 4 = portal
// 8 = container
// 16 = arcane sanctuary gateway
// 32 = well
// 64 = waypoint
// 128 = secret jails door
XSpace int // unknown
YSpace int
NameOffset int // pixels to offset the name from the animation pivot
MonsterOk bool // unknown
OperateRange int // distance object can be used from, might be unused
ShrineFunction int // unused
Restore bool // if true, object is stored in memory and will be retained if you leave and re-enter the area
Parm [8]int // unknown
Act int // what acts this object can appear in (15 = all three)
Lockable bool
Gore bool // unknown, something with corpses
Sync bool // unknown
Flicker bool // light flickers if true
Damage int // amount of damage done by this (used depending on operatefn)
Beta bool // if true, appeared in the beta?
Overlay bool // unknown
CollisionSubst bool // unknown, controls some kind of special collision checking?
Left int // unknown, clickable bounding box?
Top int
Width int
Height int
OperateFn int // what function is called when the player clicks on the object
// (todo: we should enumerate all the functions somewhere, but probably not here
// b/c it's a very long list)
PopulateFn int // what function is used to spawn this object?
// (see above todo)
InitFn int // what function is run when the object is initialized?
// (see above todo)
ClientFn int // controls special audio-visual functions
// (see above todo)
RestoreVirgins bool // if true, only restores unused objects (see Restore)
BlockMissile bool // if true, missiles collide with this
DrawUnder bool // if true, drawn as a floor tile is
OpenWarp bool // needs clarification, controls whether highlighting shows
// 'To ...' or 'trap door' when highlighting, not sure which is T/F
AutoMap int // controls how this object appears on the map
// 0 = it doesn't, rest of modes need to be analyzed
}
// CreateObjectRecord parses a row from objects.txt into an object record
func createObjectRecord(props []string) ObjectRecord {
i := -1
inc := func() int {
i++
return i
}
result := ObjectRecord{
Name: props[inc()],
Description: props[inc()],
Id: dh.StringToInt(props[inc()]),
Token: props[inc()],
SpawnMax: dh.StringToInt(props[inc()]),
Selectable: [8]bool{
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
},
TrapProbability: dh.StringToInt(props[inc()]),
SizeX: dh.StringToInt(props[inc()]),
SizeY: dh.StringToInt(props[inc()]),
NTgtFX: dh.StringToInt(props[inc()]),
NTgtFY: dh.StringToInt(props[inc()]),
NTgtBX: dh.StringToInt(props[inc()]),
NTgtBY: dh.StringToInt(props[inc()]),
FrameCount: [8]int{
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
},
FrameDelta: [8]int{
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
},
CycleAnimation: [8]bool{
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
},
LightDiameter: [8]int{
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
},
BlocksLight: [8]bool{
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
},
HasCollision: [8]bool{
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
},
IsAttackable: dh.StringToUint8(props[inc()]) == 1,
StartFrame: [8]int{
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
},
EnvEffect: dh.StringToUint8(props[inc()]) == 1,
IsDoor: dh.StringToUint8(props[inc()]) == 1,
BlockVisibility: dh.StringToUint8(props[inc()]) == 1,
Orientation: dh.StringToInt(props[inc()]),
Trans: dh.StringToInt(props[inc()]),
OrderFlag: [8]int{
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
},
PreOperate: dh.StringToUint8(props[inc()]) == 1,
HasAnimationMode: [8]bool{
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
},
XOffset: dh.StringToInt(props[inc()]),
YOffset: dh.StringToInt(props[inc()]),
Draw: dh.StringToUint8(props[inc()]) == 1,
LightRed: dh.StringToUint8(props[inc()]),
LightGreen: dh.StringToUint8(props[inc()]),
LightBlue: dh.StringToUint8(props[inc()]),
SelHD: dh.StringToUint8(props[inc()]) == 1,
SelTR: dh.StringToUint8(props[inc()]) == 1,
SelLG: dh.StringToUint8(props[inc()]) == 1,
SelRA: dh.StringToUint8(props[inc()]) == 1,
SelLA: dh.StringToUint8(props[inc()]) == 1,
SelRH: dh.StringToUint8(props[inc()]) == 1,
SelLH: dh.StringToUint8(props[inc()]) == 1,
SelSH: dh.StringToUint8(props[inc()]) == 1,
SelS: [8]bool{
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
dh.StringToUint8(props[inc()]) == 1,
},
TotalPieces: dh.StringToInt(props[inc()]),
SubClass: dh.StringToInt(props[inc()]),
XSpace: dh.StringToInt(props[inc()]),
YSpace: dh.StringToInt(props[inc()]),
NameOffset: dh.StringToInt(props[inc()]),
MonsterOk: dh.StringToUint8(props[inc()]) == 1,
OperateRange: dh.StringToInt(props[inc()]),
ShrineFunction: dh.StringToInt(props[inc()]),
Restore: dh.StringToUint8(props[inc()]) == 1,
Parm: [8]int{
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
dh.StringToInt(props[inc()]),
},
Act: dh.StringToInt(props[inc()]),
Lockable: dh.StringToUint8(props[inc()]) == 1,
Gore: dh.StringToUint8(props[inc()]) == 1,
Sync: dh.StringToUint8(props[inc()]) == 1,
Flicker: dh.StringToUint8(props[inc()]) == 1,
Damage: dh.StringToInt(props[inc()]),
Beta: dh.StringToUint8(props[inc()]) == 1,
Overlay: dh.StringToUint8(props[inc()]) == 1,
CollisionSubst: dh.StringToUint8(props[inc()]) == 1,
Left: dh.StringToInt(props[inc()]),
Top: dh.StringToInt(props[inc()]),
Width: dh.StringToInt(props[inc()]),
Height: dh.StringToInt(props[inc()]),
OperateFn: dh.StringToInt(props[inc()]),
PopulateFn: dh.StringToInt(props[inc()]),
InitFn: dh.StringToInt(props[inc()]),
ClientFn: dh.StringToInt(props[inc()]),
RestoreVirgins: dh.StringToUint8(props[inc()]) == 1,
BlockMissile: dh.StringToUint8(props[inc()]) == 1,
DrawUnder: dh.StringToUint8(props[inc()]) == 1,
OpenWarp: dh.StringToUint8(props[inc()]) == 1,
AutoMap: dh.StringToInt(props[inc()]),
}
return result
}
var Objects map[int]*ObjectRecord
func LoadObjects(fileProvider d2interface.FileProvider) {
Objects = make(map[int]*ObjectRecord)
data := strings.Split(string(fileProvider.LoadFile(d2resource.ObjectDetails)), "\r\n")[1:]
for _, line := range data {
if len(line) == 0 {
continue
}
props := strings.Split(line, "\t")
if props[2] == "" {
continue // skip a line that doesn't have an id
}
rec := createObjectRecord(props)
Objects[rec.Id] = &rec
}
log.Printf("Loaded %d objects", len(Objects))
}

View File

@ -0,0 +1,50 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
// PaletteRGB represents a color in a palette
type PaletteRGB struct {
R, G, B uint8
}
// PaletteType represents a palette
type PaletteRec struct {
Name d2enum.PaletteType
Colors [256]PaletteRGB
}
var Palettes map[d2enum.PaletteType]PaletteRec
// CreatePalette creates a palette
func CreatePalette(name d2enum.PaletteType, data []byte) PaletteRec {
result := PaletteRec{Name: name}
for i := 0; i <= 255; i++ {
result.Colors[i] = PaletteRGB{
B: data[i*3],
G: data[(i*3)+1],
R: data[(i*3)+2],
}
}
return result
}
func LoadPalettes(mpqFiles map[string]string, fileProvider d2interface.FileProvider) {
Palettes = make(map[d2enum.PaletteType]PaletteRec)
for _, pal := range []string{
"act1", "act2", "act3", "act4", "act5", "endgame", "endgame2", "fechar", "loading",
"menu0", "menu1", "menu2", "menu3", "menu4", "sky", "static", "trademark", "units",
} {
filePath := `data\global\palette\` + pal + `\pal.dat`
paletteName := d2enum.PaletteType(pal)
palette := CreatePalette(paletteName, fileProvider.LoadFile(filePath))
Palettes[paletteName] = palette
}
log.Printf("Loaded %d palettes", len(Palettes))
}

107
d2data/d2datadict/sounds.go Normal file
View File

@ -0,0 +1,107 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
// SoundEntry represents a sound entry
type SoundEntry struct {
Handle string
Index int
FileName string
Volume byte
GroupSize uint8
Loop bool
FadeIn uint8
FadeOut uint8
DeferInst uint8
StopInst uint8
Duration uint8
Compound int8
Reverb bool
Falloff uint8
Cache uint8
AsyncOnly bool
Priority uint8
Stream uint8
Stereo uint8
Tracking uint8
Solo uint8
MusicVol uint8
Block1 int
Block2 int
Block3 int
}
// CreateSoundEntry creates a sound entry based on a sound row on sounds.txt
func createSoundEntry(soundLine string) SoundEntry {
props := strings.Split(soundLine, "\t")
i := -1
inc := func() int {
i++
return i
}
result := SoundEntry{
Handle: props[inc()],
Index: dh.StringToInt(props[inc()]),
FileName: props[inc()],
Volume: dh.StringToUint8(props[inc()]),
GroupSize: dh.StringToUint8(props[inc()]),
Loop: dh.StringToUint8(props[inc()]) == 1,
FadeIn: dh.StringToUint8(props[inc()]),
FadeOut: dh.StringToUint8(props[inc()]),
DeferInst: dh.StringToUint8(props[inc()]),
StopInst: dh.StringToUint8(props[inc()]),
Duration: dh.StringToUint8(props[inc()]),
Compound: dh.StringToInt8(props[inc()]),
Reverb: dh.StringToUint8(props[inc()]) == 1,
Falloff: dh.StringToUint8(props[inc()]),
Cache: dh.StringToUint8(props[inc()]),
AsyncOnly: dh.StringToUint8(props[inc()]) == 1,
Priority: dh.StringToUint8(props[inc()]),
Stream: dh.StringToUint8(props[inc()]),
Stereo: dh.StringToUint8(props[inc()]),
Tracking: dh.StringToUint8(props[inc()]),
Solo: dh.StringToUint8(props[inc()]),
MusicVol: dh.StringToUint8(props[inc()]),
Block1: dh.StringToInt(props[inc()]),
Block2: dh.StringToInt(props[inc()]),
Block3: dh.StringToInt(props[inc()]),
}
return result
}
var Sounds map[string]SoundEntry
func LoadSounds(fileProvider d2interface.FileProvider) {
Sounds = make(map[string]SoundEntry)
soundData := strings.Split(string(fileProvider.LoadFile(d2resource.SoundSettings)), "\r\n")[1:]
for _, line := range soundData {
if len(line) == 0 {
continue
}
soundEntry := createSoundEntry(line)
soundEntry.FileName = "/data/global/sfx/" + strings.ReplaceAll(soundEntry.FileName, `\`, "/")
Sounds[soundEntry.Handle] = soundEntry
/*
// Use the following code to write out the values
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\sounds.txt`,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
}
defer f.Close()
if _, err := f.WriteString("\n[" + soundEntry.Handle + "] " + soundEntry.FileName); err != nil {
log.Println(err)
}
*/
}
log.Printf("Loaded %d sound definitions", len(Sounds))
}

View File

@ -0,0 +1,139 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
type UniqueItemRecord struct {
Name string
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
Enabled bool // if false, this record won't be loaded (should always be true...)
Ladder bool // if true, can only be found on ladder and not in single player / tcp/ip
Rarity int // 1-255, higher is more common (ironically...)
NoLimit bool // if true, can drop more than once per game
// (if false, can only drop once per game; if it would drop,
// instead a rare item with enhanced durability drops)
Level int // item's level, can only be dropped by monsters / recipes / vendors / objects of this level or higher
// otherwise they would drop a rare item with enhanced durability
RequiredLevel int // character must have this level to use this item
Code string // three letter code, points to a record in Weapons, Armor, or Misc
TypeDescription string
UberDescription string
SingleCopy bool // if true, player can only hold one of these. can't trade it or pick it up
CostMultiplier int // base price is multiplied by this when sold, repaired or bought
CostAdd int // after multiplied by above, this much is added to the price
CharacterGfxTransform string // palette shift applied to this items gfx when held and when
// on the ground (flippy). Points to a record in Colors.txt
InventoryGfxTransform string // palette shift applied to the inventory gfx
FlippyFile string // if non-empty, overrides the base item's dropped gfx
InventoryFile string // if non-empty, overrides the base item's inventory gfx
DropSound string // if non-empty, overrides the base item's drop sound
DropSfxFrame int // if non-empty, overrides the base item's frame at which the drop sound plays
UseSound string // if non-empty, overrides the sound played when item is used
Properties [12]UniqueItemProperty
}
type UniqueItemProperty struct {
Property string
Parameter d2common.CalcString // depending on the property, this may be an int (usually), or a string
Min int
Max int
}
func createUniqueItemRecord(r []string) UniqueItemRecord {
i := -1
inc := func() int {
i++
return i
}
result := UniqueItemRecord{
Name: r[inc()],
Version: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Enabled: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
Ladder: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
Rarity: dh.StringToInt(dh.EmptyToZero(r[inc()])),
NoLimit: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
Level: dh.StringToInt(dh.EmptyToZero(r[inc()])),
RequiredLevel: dh.StringToInt(dh.EmptyToZero(r[inc()])),
Code: r[inc()],
TypeDescription: r[inc()],
UberDescription: r[inc()],
SingleCopy: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
CostMultiplier: dh.StringToInt(dh.EmptyToZero(r[inc()])),
CostAdd: dh.StringToInt(dh.EmptyToZero(r[inc()])),
CharacterGfxTransform: r[inc()],
InventoryGfxTransform: r[inc()],
FlippyFile: r[inc()],
InventoryFile: r[inc()],
DropSound: r[inc()],
DropSfxFrame: dh.StringToInt(dh.EmptyToZero(r[inc()])),
UseSound: r[inc()],
Properties: [12]UniqueItemProperty{
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
},
}
return result
}
func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty {
result := UniqueItemProperty{
Property: (*r)[inc()],
Parameter: d2common.CalcString((*r)[inc()]),
Min: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
Max: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
}
return result
}
var UniqueItems map[string]*UniqueItemRecord
func LoadUniqueItems(fileProvider d2interface.FileProvider) {
UniqueItems = make(map[string]*UniqueItemRecord)
data := strings.Split(string(fileProvider.LoadFile(d2resource.UniqueItems)), "\r\n")[1:]
for _, line := range data {
if len(line) == 0 {
continue
}
r := strings.Split(line, "\t")
// skip rows that are not enabled
if r[2] != "1" {
continue
}
rec := createUniqueItemRecord(r)
UniqueItems[rec.Code] = &rec
}
log.Printf("Loaded %d unique items", len(UniqueItems))
}

View File

@ -0,0 +1,18 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
var Weapons map[string]*ItemCommonRecord
func LoadWeapons(fileProvider d2interface.FileProvider) {
Weapons = *LoadCommonItems(fileProvider, d2resource.Weapons, d2enum.InventoryItemTypeWeapon)
log.Printf("Loaded %d weapons", len(Weapons))
}

136
d2data/d2dc6/dc6.go Normal file
View File

@ -0,0 +1,136 @@
package d2dc6
import (
"encoding/binary"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/go-restruct/restruct"
)
type DC6File struct {
// Header
Version int32 `struct:"int32"`
Flags uint32 `struct:"uint32"`
Encoding uint32 `struct:"uint32"`
Termination []byte `struct:"[4]byte"`
Directions uint32 `struct:"uint32"`
FramesPerDirection uint32 `struct:"uint32"`
FramePointers []uint32 `struct:"[]uint32,size=Directions*FramesPerDirection"`
Frames []*DC6Frame `struct-size:"Directions*FramesPerDirection"`
valid bool
}
type DC6Header struct {
Version int32 `struct:"int32"`
Flags uint32 `struct:"uint32"`
Encoding uint32 `struct:"uint32"`
Termination []byte `struct:"[4]byte"`
Directions int32 `struct:"int32"`
FramesPerDirection int32 `struct:"int32"`
}
type DC6FrameHeader struct {
Flipped int32 `struct:"int32"`
Width int32 `struct:"int32"`
Height int32 `struct:"int32"`
OffsetX int32 `struct:"int32"`
OffsetY int32 `struct:"int32"`
Unknown uint32 `struct:"uint32"`
NextBlock uint32 `struct:"uint32"`
Length uint32 `struct:"uint32"`
}
type DC6Frame struct {
Flipped uint32 `struct:"uint32"`
Width uint32 `struct:"uint32"`
Height uint32 `struct:"uint32"`
OffsetX int32 `struct:"int32"`
OffsetY int32 `struct:"int32"`
Unknown uint32 `struct:"uint32"`
NextBlock uint32 `struct:"uint32"`
Length uint32 `struct:"uint32,sizeof=FrameData"`
FrameData []byte
Terminator []byte `struct:"[3]byte"`
colorData []byte
palette d2datadict.PaletteRec
valid bool
}
func (frame *DC6Frame) ColorData() []byte {
if frame.colorData == nil {
frame.completeLoad()
}
return frame.colorData
}
func (frame *DC6Frame) completeLoad() {
frame.valid = true
indexData := make([]int16, frame.Width*frame.Height)
for fi := range indexData {
indexData[fi] = -1
}
x := uint32(0)
y := frame.Height - 1
dataPointer := 0
for {
b := frame.FrameData[dataPointer]
dataPointer++
if b == 0x80 {
if y == 0 {
break
}
y--
x = 0
} else if (b & 0x80) > 0 {
transparentPixels := b & 0x7F
for ti := byte(0); ti < transparentPixels; ti++ {
indexData[x+(y*frame.Width)+uint32(ti)] = -1
}
x += uint32(transparentPixels)
} else {
for bi := 0; bi < int(b); bi++ {
indexData[x+(y*frame.Width)+uint32(bi)] = int16(frame.FrameData[dataPointer])
dataPointer++
}
x += uint32(b)
}
}
// Probably don't need this data again
frame.FrameData = nil
frame.colorData = make([]byte, int(frame.Width*frame.Height)*4)
for i := uint32(0); i < frame.Width*frame.Height; i++ {
if indexData[i] < 1 { // TODO: Is this == -1 or < 1?
continue
}
frame.colorData[i*4] = frame.palette.Colors[indexData[i]].R
frame.colorData[(i*4)+1] = frame.palette.Colors[indexData[i]].G
frame.colorData[(i*4)+2] = frame.palette.Colors[indexData[i]].B
frame.colorData[(i*4)+3] = 0xFF
}
}
// LoadDC6 uses restruct to read the binary dc6 data into structs then parses image data from the frame data.
func LoadDC6(data []byte, palette d2datadict.PaletteRec) (DC6File, error) {
result := DC6File{valid: true}
restruct.EnableExprBeta()
err := restruct.Unpack(data, binary.LittleEndian, &result)
if err != nil {
result.valid = false
log.Printf("failed to read dc6: %v", err)
}
for _, frame := range result.Frames {
frame.palette = palette
}
return result, err
}

73
d2data/d2dc6/dc6.ksy Normal file
View File

@ -0,0 +1,73 @@
meta:
id: dc6
title: Diablo CEL 6
application: Diablo II
file-extension: dc6
license: MIT
ks-version: 0.7
encoding: ASCII
endian: le
seq:
- id: dc6
type: file
types:
file:
seq:
- id: header
type: file_header
- id: frame_pointers
type: u4
repeat: expr
repeat-expr: header.directions * header.frames_per_dir
- id: frames
type: frame
repeat: expr
repeat-expr: header.directions * header.frames_per_dir
file_header:
seq:
- id: version
type: s4
- id: flags
type: u4
enum: flags
- id: encoding
type: u4
- id: termination
size: 4
- id: directions
type: s4
- id: frames_per_dir
type: s4
enums:
flags:
1: celfile_serialised
4: celfile_24bit
frame:
seq:
- id: header
type: frame_header
- id: block
type: u1
repeat: expr
repeat-expr: header.length
- id: terminator
size: 3
types:
frame_header:
seq:
- id: flipped
type: s4
- id: width
type: s4
- id: height
type: s4
- id: offset_x
type: s4
- id: offset_y
type: s4
- id: unknown
type: u4
- id: next_block
type: s4
- id: length
type: s4

View File

@ -0,0 +1,11 @@
package d2dcc
var crazyBitTable = []byte{0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 26, 28, 30, 32}
var pixelMaskLookup = []int{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}
var CofToDir4 = []int{0, 1, 2, 3}
var CofToDir8 = []int{4, 0, 5, 1, 6, 2, 7, 3}
var CofToDir16 = []int{4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14, 3, 15}
var CofToDir32 = []int{
4, 16, 8, 17, 0, 18, 9, 19, 5, 20, 10, 21, 1, 22, 11, 23,
6, 24, 12, 25, 2, 26, 13, 27, 7, 28, 14, 29, 3, 30, 15, 31,
}

40
d2data/d2dcc/dcc.go Normal file
View File

@ -0,0 +1,40 @@
package d2dcc
import (
"errors"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type DCC struct {
Signature int
Version int
NumberOfDirections int
FramesPerDirection int
Directions []DCCDirection
}
func LoadDCC(fileData []byte) (*DCC, error) {
result := &DCC{}
var bm = d2common.CreateBitMuncher(fileData, 0)
result.Signature = int(bm.GetByte())
if result.Signature != 0x74 {
return nil, errors.New("Signature expected to be 0x74 but it is not.")
}
result.Version = int(bm.GetByte())
result.NumberOfDirections = int(bm.GetByte())
result.FramesPerDirection = int(bm.GetInt32())
if bm.GetInt32() != 1 {
return nil, errors.New("This value isn't 1. It has to be 1.")
}
bm.GetInt32() // TotalSizeCoded
directionOffsets := make([]int, result.NumberOfDirections)
for i := 0; i < result.NumberOfDirections; i++ {
directionOffsets[i] = int(bm.GetInt32())
}
result.Directions = make([]DCCDirection, result.NumberOfDirections)
for i := 0; i < result.NumberOfDirections; i++ {
result.Directions[i] = CreateDCCDirection(d2common.CreateBitMuncher(fileData, directionOffsets[i]*8), *result)
}
return result, nil
}

12
d2data/d2dcc/dcc_cell.go Normal file
View File

@ -0,0 +1,12 @@
package d2dcc
type DCCCell struct {
Width int
Height int
XOffset int
YOffset int
LastWidth int
LastHeight int
LastXOffset int
LastYOffset int
}

View File

@ -0,0 +1,361 @@
package d2dcc
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
type DCCDirection struct {
OutSizeCoded int
CompressionFlags int
Variable0Bits int
WidthBits int
HeightBits int
XOffsetBits int
YOffsetBits int
OptionalDataBits int
CodedBytesBits int
EqualCellsBitstreamSize int
PixelMaskBitstreamSize int
EncodingTypeBitsreamSize int
RawPixelCodesBitstreamSize int
Frames []*DCCDirectionFrame
PaletteEntries [256]byte
Box d2common.Rectangle
Cells []*DCCCell
PixelData []byte
HorizontalCellCount int
VerticalCellCount int
PixelBuffer []DCCPixelBufferEntry
}
func CreateDCCDirection(bm *d2common.BitMuncher, file DCC) DCCDirection {
result := DCCDirection{}
result.OutSizeCoded = int(bm.GetUInt32())
result.CompressionFlags = int(bm.GetBits(2))
result.Variable0Bits = int(crazyBitTable[bm.GetBits(4)])
result.WidthBits = int(crazyBitTable[bm.GetBits(4)])
result.HeightBits = int(crazyBitTable[bm.GetBits(4)])
result.XOffsetBits = int(crazyBitTable[bm.GetBits(4)])
result.YOffsetBits = int(crazyBitTable[bm.GetBits(4)])
result.OptionalDataBits = int(crazyBitTable[bm.GetBits(4)])
result.CodedBytesBits = int(crazyBitTable[bm.GetBits(4)])
result.Frames = make([]*DCCDirectionFrame, file.FramesPerDirection)
minx := 100000
miny := 100000
maxx := -100000
maxy := -100000
// Load the frame headers
for frameIdx := 0; frameIdx < file.FramesPerDirection; frameIdx++ {
result.Frames[frameIdx] = CreateDCCDirectionFrame(bm, result)
minx = int(d2helper.MinInt32(int32(result.Frames[frameIdx].Box.Left), int32(minx)))
miny = int(d2helper.MinInt32(int32(result.Frames[frameIdx].Box.Top), int32(miny)))
maxx = int(d2helper.MaxInt32(int32(result.Frames[frameIdx].Box.Right()), int32(maxx)))
maxy = int(d2helper.MaxInt32(int32(result.Frames[frameIdx].Box.Bottom()), int32(maxy)))
}
result.Box = d2common.Rectangle{minx, miny, (maxx - minx), (maxy - miny)}
if result.OptionalDataBits > 0 {
log.Panic("Optional bits in DCC data is not currently supported.")
}
if (result.CompressionFlags & 0x2) > 0 {
result.EqualCellsBitstreamSize = int(bm.GetBits(20))
}
result.PixelMaskBitstreamSize = int(bm.GetBits(20))
if (result.CompressionFlags & 0x1) > 0 {
result.EncodingTypeBitsreamSize = int(bm.GetBits(20))
result.RawPixelCodesBitstreamSize = int(bm.GetBits(20))
}
// PixelValuesKey
paletteEntryCount := 0
for i := 0; i < 256; i++ {
valid := bm.GetBit() != 0
if valid {
result.PaletteEntries[paletteEntryCount] = byte(i)
paletteEntryCount++
}
}
// HERE BE GIANTS:
// Because of the way this thing mashes bits together, BIT offset matters
// here. For example, if you are on byte offset 3, bit offset 6, and
// the EqualCellsBitstreamSize is 20 bytes, then the next bit stream
// will be located at byte 23, bit offset 6!
equalCellsBitstream := d2common.CopyBitMuncher(bm)
bm.SkipBits(result.EqualCellsBitstreamSize)
pixelMaskBitstream := d2common.CopyBitMuncher(bm)
bm.SkipBits(result.PixelMaskBitstreamSize)
encodingTypeBitsream := d2common.CopyBitMuncher(bm)
bm.SkipBits(result.EncodingTypeBitsreamSize)
rawPixelCodesBitstream := d2common.CopyBitMuncher(bm)
bm.SkipBits(result.RawPixelCodesBitstreamSize)
pixelCodeandDisplacement := d2common.CopyBitMuncher(bm)
// Calculate the cells for the direction
result.CalculateCells()
// Calculate the cells for each of the frames
for _, frame := range result.Frames {
frame.CalculateCells(result)
}
// Fill in the pixel buffer
result.FillPixelBuffer(pixelCodeandDisplacement, equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream)
// Generate the actual frame pixel data
result.GenerateFrames(pixelCodeandDisplacement)
result.PixelBuffer = nil
// Verify that everything we expected to read was actually read (sanity check)...
if equalCellsBitstream.BitsRead != result.EqualCellsBitstreamSize {
log.Panic("Did not read the correct number of bits!")
}
if pixelMaskBitstream.BitsRead != result.PixelMaskBitstreamSize {
log.Panic("Did not read the correct number of bits!")
}
if encodingTypeBitsream.BitsRead != result.EncodingTypeBitsreamSize {
log.Panic("Did not read the correct number of bits!")
}
if rawPixelCodesBitstream.BitsRead != result.RawPixelCodesBitstreamSize {
log.Panic("Did not read the correct number of bits!")
}
bm.SkipBits(pixelCodeandDisplacement.BitsRead)
return result
}
func (v *DCCDirection) GenerateFrames(pcd *d2common.BitMuncher) {
pbIdx := 0
for _, cell := range v.Cells {
cell.LastWidth = -1
cell.LastHeight = -1
}
v.PixelData = make([]byte, v.Box.Width*v.Box.Height)
frameIndex := -1
for _, frame := range v.Frames {
frameIndex++
frame.PixelData = make([]byte, v.Box.Width*v.Box.Height)
c := -1
for _, cell := range frame.Cells {
c++
cellX := cell.XOffset / 4
cellY := cell.YOffset / 4
cellIndex := cellX + (cellY * v.HorizontalCellCount)
bufferCell := v.Cells[cellIndex]
pbe := v.PixelBuffer[pbIdx]
if (pbe.Frame != frameIndex) || (pbe.FrameCellIndex != c) {
// This buffer cell has an EqualCell bit set to 1, so copy the frame cell or clear it
if (cell.Width != bufferCell.LastWidth) || (cell.Height != bufferCell.LastHeight) {
// Different sizes
/// TODO: Clear the pixels of the frame cell
for y := 0; y < cell.Height; y++ {
for x := 0; x < cell.Width; x++ {
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = 0
}
}
} else {
// Same sizes
// Copy the old frame cell into the new position
for fy := 0; fy < cell.Height; fy++ {
for fx := 0; fx < cell.Width; fx++ {
// Frame (buff.lastx, buff.lasty) -> Frame (cell.offx, cell.offy)
// Cell (0, 0,) ->
// blit(dir->bmp, dir->bmp, buff_cell->last_x0, buff_cell->last_y0, cell->x0, cell->y0, cell->w, cell->h );
v.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[fx+bufferCell.LastXOffset+((fy+bufferCell.LastYOffset)*v.Box.Width)]
}
}
// Copy it again into the final frame image
for fy := 0; fy < cell.Height; fy++ {
for fx := 0; fx < cell.Width; fx++ {
// blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
frame.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[cell.XOffset+fx+((cell.YOffset+fy)*v.Box.Width)]
}
}
}
} else {
if pbe.Value[0] == pbe.Value[1] {
// Clear the frame
//cell.PixelData = new byte[cell.Width * cell.Height];
for y := 0; y < cell.Height; y++ {
for x := 0; x < cell.Width; x++ {
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = pbe.Value[0]
}
}
} else {
// Fill the frame cell with the pixels
bitsToRead := 1
if pbe.Value[1] != pbe.Value[2] {
bitsToRead = 2
}
for y := 0; y < cell.Height; y++ {
for x := 0; x < cell.Width; x++ {
paletteIndex := pcd.GetBits(bitsToRead)
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = pbe.Value[paletteIndex]
}
}
}
// Copy the frame cell into the frame
for fy := 0; fy < cell.Height; fy++ {
for fx := 0; fx < cell.Width; fx++ {
//blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
frame.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)]
}
}
pbIdx++
}
bufferCell.LastWidth = cell.Width
bufferCell.LastHeight = cell.Height
bufferCell.LastXOffset = cell.XOffset
bufferCell.LastYOffset = cell.YOffset
}
// Free up the stuff we no longer need
frame.Cells = nil
}
v.Cells = nil
v.PixelData = nil
v.PixelBuffer = nil
}
func (v *DCCDirection) FillPixelBuffer(pcd, ec, pm, et, rp *d2common.BitMuncher) {
lastPixel := uint32(0)
maxCellX := 0
maxCellY := 0
for _, frame := range v.Frames {
if frame == nil {
continue
}
maxCellX += frame.HorizontalCellCount
maxCellY += frame.VerticalCellCount
}
v.PixelBuffer = make([]DCCPixelBufferEntry, maxCellX*maxCellY)
for i := 0; i < maxCellX*maxCellY; i++ {
v.PixelBuffer[i].Frame = -1
v.PixelBuffer[i].FrameCellIndex = -1
}
cellBuffer := make([]*DCCPixelBufferEntry, v.HorizontalCellCount*v.VerticalCellCount)
frameIndex := -1
pbIndex := -1
pixelMask := uint32(0x00)
for _, frame := range v.Frames {
frameIndex++
originCellX := (frame.Box.Left - v.Box.Left) / 4
originCellY := (frame.Box.Top - v.Box.Top) / 4
for cellY := 0; cellY < frame.VerticalCellCount; cellY++ {
currentCellY := cellY + originCellY
for cellX := 0; cellX < frame.HorizontalCellCount; cellX++ {
currentCell := originCellX + cellX + (currentCellY * v.HorizontalCellCount)
nextCell := false
tmp := 0
if cellBuffer[currentCell] != nil {
if v.EqualCellsBitstreamSize > 0 {
tmp = int(ec.GetBit())
} else {
tmp = 0
}
if tmp == 0 {
pixelMask = pm.GetBits(4)
} else {
nextCell = true
}
} else {
pixelMask = 0x0F
}
if nextCell {
continue
}
// Decode the pixels
var pixelStack [4]uint32
lastPixel = 0
numberOfPixelBits := pixelMaskLookup[pixelMask]
encodingType := 0
if (numberOfPixelBits != 0) && (v.EncodingTypeBitsreamSize > 0) {
encodingType = int(et.GetBit())
} else {
encodingType = 0
}
decodedPixel := 0
for i := 0; i < numberOfPixelBits; i++ {
if encodingType != 0 {
pixelStack[i] = rp.GetBits(8)
} else {
pixelStack[i] = lastPixel
pixelDisplacement := pcd.GetBits(4)
pixelStack[i] += pixelDisplacement
for pixelDisplacement == 15 {
pixelDisplacement = pcd.GetBits(4)
pixelStack[i] += pixelDisplacement
}
}
if pixelStack[i] == lastPixel {
pixelStack[i] = 0
break
} else {
lastPixel = pixelStack[i]
decodedPixel++
}
}
oldEntry := cellBuffer[currentCell]
pbIndex++
curIdx := decodedPixel - 1
for i := 0; i < 4; i++ {
if (pixelMask & (1 << uint(i))) != 0 {
if curIdx >= 0 {
v.PixelBuffer[pbIndex].Value[i] = byte(pixelStack[curIdx])
curIdx--
} else {
v.PixelBuffer[pbIndex].Value[i] = 0
}
} else {
v.PixelBuffer[pbIndex].Value[i] = oldEntry.Value[i]
}
}
cellBuffer[currentCell] = &v.PixelBuffer[pbIndex]
v.PixelBuffer[pbIndex].Frame = frameIndex
v.PixelBuffer[pbIndex].FrameCellIndex = cellX + (cellY * frame.HorizontalCellCount)
}
}
}
cellBuffer = nil
// Convert the palette entry index into actual palette entries
for i := 0; i <= pbIndex; i++ {
for x := 0; x < 4; x++ {
v.PixelBuffer[i].Value[x] = v.PaletteEntries[v.PixelBuffer[i].Value[x]]
}
}
}
func (v *DCCDirection) CalculateCells() {
// Calculate the number of vertical and horizontal cells we need
v.HorizontalCellCount = 1 + (v.Box.Width-1)/4
v.VerticalCellCount = 1 + (v.Box.Height-1)/4
// Calculate the cell widths
cellWidths := make([]int, v.HorizontalCellCount)
if v.HorizontalCellCount == 1 {
cellWidths[0] = v.Box.Width
} else {
for i := 0; i < v.HorizontalCellCount-1; i++ {
cellWidths[i] = 4
}
cellWidths[v.HorizontalCellCount-1] = v.Box.Width - (4 * (v.HorizontalCellCount - 1))
}
// Calculate the cell heights
cellHeights := make([]int, v.VerticalCellCount)
if v.VerticalCellCount == 1 {
cellHeights[0] = v.Box.Height
} else {
for i := 0; i < v.VerticalCellCount-1; i++ {
cellHeights[i] = 4
}
cellHeights[v.VerticalCellCount-1] = v.Box.Height - (4 * (v.VerticalCellCount - 1))
}
// Set the cell widths and heights in the cell buffer
v.Cells = make([]*DCCCell, v.VerticalCellCount*v.HorizontalCellCount)
yOffset := 0
for y := 0; y < v.VerticalCellCount; y++ {
xOffset := 0
for x := 0; x < v.HorizontalCellCount; x++ {
v.Cells[x+(y*v.HorizontalCellCount)] = &DCCCell{
Width: cellWidths[x],
Height: cellHeights[y],
XOffset: xOffset,
YOffset: yOffset,
}
xOffset += 4
}
yOffset += 4
}
}

View File

@ -0,0 +1,108 @@
package d2dcc
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type DCCDirectionFrame struct {
Width int
Height int
XOffset int
YOffset int
NumberOfOptionalBytes int
NumberOfCodedBytes int
FrameIsBottomUp bool
Box d2common.Rectangle
Cells []DCCCell
PixelData []byte
HorizontalCellCount int
VerticalCellCount int
valid bool
}
func CreateDCCDirectionFrame(bits *d2common.BitMuncher, direction DCCDirection) *DCCDirectionFrame {
result := &DCCDirectionFrame{}
bits.GetBits(direction.Variable0Bits) // Variable0
result.Width = int(bits.GetBits(direction.WidthBits))
result.Height = int(bits.GetBits(direction.HeightBits))
result.XOffset = bits.GetSignedBits(direction.XOffsetBits)
result.YOffset = bits.GetSignedBits(direction.YOffsetBits)
result.NumberOfOptionalBytes = int(bits.GetBits(direction.OptionalDataBits))
result.NumberOfCodedBytes = int(bits.GetBits(direction.CodedBytesBits))
result.FrameIsBottomUp = bits.GetBit() == 1
if result.FrameIsBottomUp {
log.Panic("Bottom up frames are not implemented.")
} else {
result.Box = d2common.Rectangle{
result.XOffset,
result.YOffset - result.Height + 1,
result.Width,
result.Height,
}
}
result.valid = true
return result
}
func (v *DCCDirectionFrame) CalculateCells(direction DCCDirection) {
var w = 4 - ((v.Box.Left - direction.Box.Left) % 4) // Width of the first column (in pixels)
if (v.Width - w) <= 1 {
v.HorizontalCellCount = 1
} else {
tmp := v.Width - w - 1
v.HorizontalCellCount = 2 + (tmp / 4)
if (tmp % 4) == 0 {
v.HorizontalCellCount--
}
}
h := 4 - ((v.Box.Top - direction.Box.Top) % 4) // Height of the first column (in pixels)
if (v.Height - h) <= 1 {
v.VerticalCellCount = 1
} else {
tmp := v.Height - h - 1
v.VerticalCellCount = 2 + (tmp / 4)
if (tmp % 4) == 0 {
v.VerticalCellCount--
}
}
// Calculate the cell widths and heights
cellWidths := make([]int, v.HorizontalCellCount)
if v.HorizontalCellCount == 1 {
cellWidths[0] = v.Width
} else {
cellWidths[0] = w
for i := 1; i < (v.HorizontalCellCount - 1); i++ {
cellWidths[i] = 4
}
cellWidths[v.HorizontalCellCount-1] = v.Width - w - (4 * (v.HorizontalCellCount - 2))
}
cellHeights := make([]int, v.VerticalCellCount)
if v.VerticalCellCount == 1 {
cellHeights[0] = v.Height
} else {
cellHeights[0] = h
for i := 1; i < (v.VerticalCellCount - 1); i++ {
cellHeights[i] = 4
}
cellHeights[v.VerticalCellCount-1] = v.Height - h - (4 * (v.VerticalCellCount - 2))
}
v.Cells = make([]DCCCell, v.HorizontalCellCount*v.VerticalCellCount)
offsetY := v.Box.Top - direction.Box.Top
for y := 0; y < v.VerticalCellCount; y++ {
offsetX := v.Box.Left - direction.Box.Left
for x := 0; x < v.HorizontalCellCount; x++ {
v.Cells[x+(y*v.HorizontalCellCount)] = DCCCell{
XOffset: offsetX,
YOffset: offsetY,
Width: cellWidths[x],
Height: cellHeights[y],
}
offsetX += cellWidths[x]
}
offsetY += cellHeights[y]
}
}

View File

@ -0,0 +1,7 @@
package d2dcc
type DCCPixelBufferEntry struct {
Value [4]byte
Frame int
FrameCellIndex int
}

View File

@ -0,0 +1,7 @@
package d2ds1
var 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,
}

247
d2data/d2ds1/ds1.go Normal file
View File

@ -0,0 +1,247 @@
package d2ds1
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
type DS1 struct {
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
SubstitutionType int32 // SubstitutionType (layer type): 0 if no layer, else type 1 or type 2
Files []string // FilePtr table of file string pointers
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
Objects []d2data.Object // Objects
Tiles [][]TileRecord
SubstitutionGroups []SubstitutionGroup
}
func LoadDS1(fileData []byte) DS1 {
ds1 := DS1{
NumberOfFloors: 1,
NumberOfWalls: 1,
NumberOfShadowLayers: 1,
NumberOfSubstitutionLayers: 0,
}
br := d2common.CreateStreamReader(fileData)
ds1.Version = br.GetInt32()
ds1.Width = br.GetInt32() + 1
ds1.Height = br.GetInt32() + 1
if ds1.Version >= 8 {
ds1.Act = d2helper.MinInt32(5, br.GetInt32()+1)
}
if ds1.Version >= 10 {
ds1.SubstitutionType = br.GetInt32()
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
ds1.NumberOfSubstitutionLayers = 1
}
}
if ds1.Version >= 3 {
// These files reference things that don't exist anymore :-?
numberOfFiles := br.GetInt32()
ds1.Files = make([]string, numberOfFiles)
for i := 0; i < int(numberOfFiles); i++ {
ds1.Files[i] = ""
for {
ch := br.GetByte()
if ch == 0 {
break
}
ds1.Files[i] += string(ch)
}
}
}
if ds1.Version >= 9 && ds1.Version <= 13 {
// Skipping two dwords because they are "meaningless"?
br.SkipBytes(16)
}
if ds1.Version >= 4 {
ds1.NumberOfWalls = br.GetInt32()
if ds1.Version >= 16 {
ds1.NumberOfFloors = br.GetInt32()
} else {
ds1.NumberOfFloors = 1
}
}
var layerStream []d2enum.LayerStreamType
if ds1.Version < 4 {
layerStream = []d2enum.LayerStreamType{
d2enum.LayerStreamWall1,
d2enum.LayerStreamFloor1,
d2enum.LayerStreamOrientation1,
d2enum.LayerStreamSubstitute,
d2enum.LayerStreamShadow,
}
} else {
layerStream = make([]d2enum.LayerStreamType, (ds1.NumberOfWalls*2)+ds1.NumberOfFloors+ds1.NumberOfShadowLayers+ds1.NumberOfSubstitutionLayers)
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)
layerIdx++
}
if ds1.NumberOfShadowLayers > 0 {
layerStream[layerIdx] = d2enum.LayerStreamShadow
layerIdx++
}
if ds1.NumberOfSubstitutionLayers > 0 {
layerStream[layerIdx] = d2enum.LayerStreamSubstitute
layerIdx++
}
}
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)
}
}
for _, layerStreamType := range layerStream {
for y := 0; y < int(ds1.Height); y++ {
for x := 0; x < int(ds1.Width); x++ {
dw := br.GetUInt32()
switch layerStreamType {
case d2enum.LayerStreamWall1:
fallthrough
case d2enum.LayerStreamWall2:
fallthrough
case d2enum.LayerStreamWall3:
fallthrough
case d2enum.LayerStreamWall4:
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1)
ds1.Tiles[y][x].Walls[wallIndex].Prop1 = byte(dw & 0x000000FF)
ds1.Tiles[y][x].Walls[wallIndex].Sequence = byte((dw & 0x00003F00) >> 8)
ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14)
ds1.Tiles[y][x].Walls[wallIndex].Style = byte((dw & 0x03F00000) >> 20)
ds1.Tiles[y][x].Walls[wallIndex].Unknown2 = byte((dw & 0x7C000000) >> 26)
ds1.Tiles[y][x].Walls[wallIndex].Hidden = byte((dw&0x80000000)>>31) > 0
case d2enum.LayerStreamOrientation1:
fallthrough
case d2enum.LayerStreamOrientation2:
fallthrough
case d2enum.LayerStreamOrientation3:
fallthrough
case d2enum.LayerStreamOrientation4:
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
c := int32(dw & 0x000000FF)
if ds1.Version < 7 {
if c < 25 {
c = dirLookup[c]
}
}
ds1.Tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c)
ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & 0xFFFFFF00) >> 8)
case d2enum.LayerStreamFloor1:
fallthrough
case d2enum.LayerStreamFloor2:
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1)
ds1.Tiles[y][x].Floors[floorIndex].Prop1 = byte(dw & 0x000000FF)
ds1.Tiles[y][x].Floors[floorIndex].Sequence = byte((dw & 0x00003F00) >> 8)
ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14)
ds1.Tiles[y][x].Floors[floorIndex].Style = byte((dw & 0x03F00000) >> 20)
ds1.Tiles[y][x].Floors[floorIndex].Unknown2 = byte((dw & 0x7C000000) >> 26)
ds1.Tiles[y][x].Floors[floorIndex].Hidden = byte((dw&0x80000000)>>31) > 0
case d2enum.LayerStreamShadow:
ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF)
ds1.Tiles[y][x].Shadows[0].Sequence = byte((dw & 0x00003F00) >> 8)
ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14)
ds1.Tiles[y][x].Shadows[0].Style = byte((dw & 0x03F00000) >> 20)
ds1.Tiles[y][x].Shadows[0].Unknown2 = byte((dw & 0x7C000000) >> 26)
ds1.Tiles[y][x].Shadows[0].Hidden = byte((dw&0x80000000)>>31) > 0
case d2enum.LayerStreamSubstitute:
ds1.Tiles[y][x].Substitutions[0].Unknown = dw
}
}
}
}
if ds1.Version >= 2 {
numberOfObjects := br.GetInt32()
ds1.Objects = make([]d2data.Object, numberOfObjects)
for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ {
newObject := d2data.Object{}
newObject.Type = br.GetInt32()
newObject.Id = br.GetInt32()
newObject.X = br.GetInt32()
newObject.Y = br.GetInt32()
newObject.Flags = br.GetInt32()
newObject.Lookup = d2datadict.LookupObject(int(ds1.Act), int(newObject.Type), int(newObject.Id))
if newObject.Lookup != nil && newObject.Lookup.ObjectsTxtId != -1 {
newObject.ObjectInfo = d2datadict.Objects[newObject.Lookup.ObjectsTxtId]
}
ds1.Objects[objIdx] = newObject
}
} else {
ds1.Objects = make([]d2data.Object, 0)
}
if ds1.Version >= 12 && (ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2) {
if ds1.Version >= 18 {
br.GetUInt32()
}
numberOfSubGroups := br.GetInt32()
ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ {
newSub := SubstitutionGroup{}
newSub.TileX = br.GetInt32()
newSub.TileY = br.GetInt32()
newSub.WidthInTiles = br.GetInt32()
newSub.HeightInTiles = br.GetInt32()
newSub.Unknown = br.GetInt32()
ds1.SubstitutionGroups[subIdx] = newSub
}
} else {
ds1.SubstitutionGroups = make([]SubstitutionGroup, 0)
}
if ds1.Version >= 14 {
numberOfNpcs := br.GetInt32()
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ {
numPaths := br.GetInt32()
npcX := br.GetInt32()
npcY := br.GetInt32()
objIdx := -1
for idx, ds1Obj := range ds1.Objects {
if ds1Obj.X == npcX && ds1Obj.Y == npcY {
objIdx = idx
break
}
}
if objIdx > -1 {
if ds1.Objects[objIdx].Paths == nil {
ds1.Objects[objIdx].Paths = make([]d2common.Path, numPaths)
}
for pathIdx := 0; pathIdx < int(numPaths); pathIdx++ {
newPath := d2common.Path{}
newPath.X = br.GetInt32()
newPath.Y = br.GetInt32()
if ds1.Version >= 15 {
newPath.Action = br.GetInt32()
}
ds1.Objects[objIdx].Paths[pathIdx] = newPath
}
} else {
if ds1.Version >= 15 {
br.SkipBytes(int(numPaths) * 3)
} else {
br.SkipBytes(int(numPaths) * 2)
}
}
}
}
return ds1
}

View File

@ -0,0 +1,13 @@
package d2ds1
type FloorShadowRecord struct {
Prop1 byte
Sequence byte
Unknown1 byte
Style byte
Unknown2 byte
Hidden bool
RandomIndex byte
Animated bool
YAdjust int
}

View File

@ -0,0 +1,9 @@
package d2ds1
type SubstitutionGroup struct {
TileX int32
TileY int32
WidthInTiles int32
HeightInTiles int32
Unknown int32
}

View File

@ -0,0 +1,5 @@
package d2ds1
type SubstitutionRecord struct {
Unknown uint32
}

View File

@ -0,0 +1,8 @@
package d2ds1
type TileRecord struct {
Floors []FloorShadowRecord
Walls []WallRecord
Shadows []FloorShadowRecord
Substitutions []SubstitutionRecord
}

View File

@ -0,0 +1,16 @@
package d2ds1
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
type WallRecord struct {
Type d2enum.TileType
Zero byte
Prop1 byte
Sequence byte
Unknown1 byte
Style byte
Unknown2 byte
Hidden bool
RandomIndex byte
YAdjust int
}

12
d2data/d2dt1/block.go Normal file
View File

@ -0,0 +1,12 @@
package d2dt1
type Block struct {
X int16
Y int16
GridX byte
GridY byte
Format BlockDataFormat
EncodedData []byte
Length int32
FileOffset int32
}

83
d2data/d2dt1/dt1.go Normal file
View File

@ -0,0 +1,83 @@
package d2dt1
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
// https://d2mods.info/forum/viewtopic.php?t=65163
type DT1 struct {
Tiles []Tile
}
type BlockDataFormat int16
const (
BlockFormatRLE BlockDataFormat = 0 // Not 1
BlockFormatIsometric BlockDataFormat = 1
)
func LoadDT1(fileData []byte) DT1 {
result := DT1{}
br := d2common.CreateStreamReader(fileData)
ver1 := br.GetInt32()
ver2 := br.GetInt32()
if ver1 != 7 || ver2 != 6 {
log.Panicf("Expected to have a version of 7.6, but got %d.%d instead", ver1, ver2)
}
br.SkipBytes(260)
numberOfTiles := br.GetInt32()
br.SetPosition(uint64(br.GetInt32()))
result.Tiles = make([]Tile, numberOfTiles)
for tileIdx := range result.Tiles {
newTile := Tile{}
newTile.Direction = br.GetInt32()
newTile.RoofHeight = br.GetInt16()
newTile.MaterialFlags = NewMaterialFlags(br.GetUInt16())
newTile.Height = br.GetInt32()
newTile.Width = br.GetInt32()
br.SkipBytes(4)
newTile.Type = br.GetInt32()
newTile.Style = br.GetInt32()
newTile.Sequence = br.GetInt32()
newTile.RarityFrameIndex = br.GetInt32()
br.SkipBytes(4)
for i := range newTile.SubTileFlags {
newTile.SubTileFlags[i] = NewSubTileFlags(br.GetByte())
}
br.SkipBytes(7)
newTile.blockHeaderPointer = br.GetInt32()
newTile.blockHeaderSize = br.GetInt32()
newTile.Blocks = make([]Block, br.GetInt32())
br.SkipBytes(12)
result.Tiles[tileIdx] = newTile
}
for tileIdx, tile := range result.Tiles {
br.SetPosition(uint64(tile.blockHeaderPointer))
for blockIdx := range tile.Blocks {
result.Tiles[tileIdx].Blocks[blockIdx].X = br.GetInt16()
result.Tiles[tileIdx].Blocks[blockIdx].Y = br.GetInt16()
br.SkipBytes(2)
result.Tiles[tileIdx].Blocks[blockIdx].GridX = br.GetByte()
result.Tiles[tileIdx].Blocks[blockIdx].GridY = br.GetByte()
formatValue := br.GetInt16()
if formatValue == 1 {
result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatIsometric
} else {
result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatRLE
}
result.Tiles[tileIdx].Blocks[blockIdx].Length = br.GetInt32()
br.SkipBytes(2)
result.Tiles[tileIdx].Blocks[blockIdx].FileOffset = br.GetInt32()
}
for blockIndex, block := range tile.Blocks {
br.SetPosition(uint64(tile.blockHeaderPointer + block.FileOffset))
encodedData, _ := br.ReadBytes(int(block.Length))
tile.Blocks[blockIndex].EncodedData = encodedData
}
}
return result
}

30
d2data/d2dt1/material.go Normal file
View File

@ -0,0 +1,30 @@
package d2dt1
// Lots of unknowns for now
type MaterialFlags struct {
Other bool
Water bool
WoodObject bool
InsideStone bool
OutsideStone bool
Dirt bool
Sand bool
Wood bool
Lava bool
Snow bool
}
func NewMaterialFlags(data uint16) MaterialFlags {
return MaterialFlags{
Other: data&0x0001 == 0x0001,
Water: data&0x0002 == 0x0002,
WoodObject: data&0x0004 == 0x0004,
InsideStone: data&0x0008 == 0x0008,
OutsideStone: data&0x0010 == 0x0010,
Dirt: data&0x0020 == 0x0020,
Sand: data&0x0040 == 0x0040,
Wood: data&0x0080 == 0x0080,
Lava: data&0x0100 == 0x0100,
Snow: data&0x0400 == 0x0400,
}
}

25
d2data/d2dt1/subtile.go Normal file
View File

@ -0,0 +1,25 @@
package d2dt1
type SubTileFlags struct {
BlockWalk bool
BlockLOS bool
BlockJump bool
BlockPlayerWalk bool
Unknown1 bool
BlockLight bool
Unknown2 bool
Unknown3 bool
}
func NewSubTileFlags(data byte) SubTileFlags {
return SubTileFlags{
BlockWalk: data & 1 == 1,
BlockLOS: data & 2 == 2,
BlockJump: data & 4 == 4,
BlockPlayerWalk: data & 8 == 8,
Unknown1: data & 16 == 16,
BlockLight: data & 32 == 32,
Unknown2: data & 64 == 64,
Unknown3: data & 128 == 128,
}
}

View File

@ -0,0 +1,24 @@
package d2dt1
import (
testify "github.com/stretchr/testify/assert"
"testing"
)
func TestNewSubTile(t *testing.T) {
assert := testify.New(t)
data := []byte{1, 2, 4, 8, 16, 32, 64, 128}
for i, b := range data {
tile := NewSubTileFlags(b)
assert.Equal(i == 0, tile.BlockWalk)
assert.Equal(i == 1, tile.BlockLOS)
assert.Equal(i == 2, tile.BlockJump)
assert.Equal(i == 3, tile.BlockPlayerWalk)
assert.Equal(i == 4, tile.Unknown1)
assert.Equal(i == 5, tile.BlockLight)
assert.Equal(i == 6, tile.Unknown2)
assert.Equal(i == 7, tile.Unknown3)
}
}

Some files were not shown because too many files have changed in this diff Show More