Migrate out d2common d2helper d2data modules (#195)

* Switch items to dynamic load with a common struct, add misc.txt loading
* Update Ebiten Reference
* Switch references to point to D2Shared
* Migrate part 2
This commit is contained in:
ndechiara 2019-11-17 00:16:33 -05:00 committed by Tim Sarbin
parent 404a87afd4
commit 1c2b4869a1
119 changed files with 162 additions and 13970 deletions

View File

@ -3,7 +3,7 @@ package d2audio
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/hajimehoshi/ebiten/audio"
"github.com/hajimehoshi/ebiten/audio/wav"

View File

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

View File

@ -1,85 +0,0 @@
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)
}

View File

@ -1,64 +0,0 @@
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

@ -1,33 +0,0 @@
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)
}
}
}

View File

@ -1,20 +0,0 @@
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,
}
}

View File

@ -1,9 +0,0 @@
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

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

View File

@ -1,52 +0,0 @@
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

@ -1,66 +0,0 @@
// 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

@ -1,23 +0,0 @@
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

@ -1,12 +0,0 @@
package d2enum
type DrawEffect int
const (
DrawEffectPctTransparency75 = 0 //75 % transparency (colormaps 561-816 in a .pl2)
DrawEffectPctTransparency50 = 1 //50 % transparency (colormaps 305-560 in a .pl2)
DrawEffectPctTransparency25 = 2 //25 % transparency (colormaps 49-304 in a .pl2)
DrawEffectScreen = 3 //Screen (colormaps 817-1072 in a .pl2)
DrawEffectLuminance = 4 //luminance (colormaps 1073-1328 in a .pl2)
DrawEffectBringAlphaBlending = 5 //bright alpha blending (colormaps 1457-1712 in a .pl2)
)

View File

@ -1,41 +0,0 @@
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

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

View File

@ -1,30 +0,0 @@
// 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

@ -1,18 +0,0 @@
// 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

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

View File

@ -1,18 +0,0 @@
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

@ -1,26 +0,0 @@
package d2enum
type Orientation int32
const (
Floors Orientation = 0
LeftWall Orientation = 1
RightWall Orientation = 2
RightPartOfNorthCornerWall Orientation = 3
LeftPartOfNorthCornerWall Orientation = 4
LeftEndWall Orientation = 5
RightEndWall Orientation = 6
SouthCornerWall Orientation = 7
LeftWallWithDoor Orientation = 8
RightWallWithDoor Orientation = 9
SpecialTile1 Orientation = 10
SpecialTile2 Orientation = 11
PillarsColumnsAndStandaloneObjects Orientation = 12
Shadows Orientation = 13
Trees Orientation = 14
Roofs Orientation = 15
LowerWallsEquivalentToLeftWall Orientation = 16
LowerWallsEquivalentToRightWall Orientation = 17
LowerWallsEquivalentToRightLeftNorthCornerWall Orientation = 18
LowerWallsEquivalentToSouthCornerwall Orientation = 19
)

View File

@ -1,43 +0,0 @@
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"
)

View File

@ -1,17 +0,0 @@
# 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

@ -1,41 +0,0 @@
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

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

View File

@ -1,24 +0,0 @@
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

@ -1,37 +0,0 @@
// 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

@ -1,18 +0,0 @@
// 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

@ -1,7 +0,0 @@
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

@ -1,16 +0,0 @@
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

@ -1,261 +0,0 @@
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/latin/font6"
Font8 = "/data/local/font/latin/font8"
Font16 = "/data/local/font/latin/font16"
Font24 = "/data/local/font/latin/font24"
Font30 = "/data/local/font/latin/font30"
Font42 = "/data/local/font/latin/font42"
FontFormal12 = "/data/local/font/latin/fontformal12"
FontFormal11 = "/data/local/font/latin/fontformal11"
FontFormal10 = "/data/local/font/latin/fontformal10"
FontExocet10 = "/data/local/font/latin/fontexocet10"
FontExocet8 = "/data/local/font/latin/fontexocet8"
FontSucker = "/data/local/font/latin/ReallyTheLastSucker"
FontRediculous = "/data/local/font/latin/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"
)

View File

@ -1,47 +0,0 @@
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
}

View File

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

View File

@ -1,20 +0,0 @@
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
}

View File

@ -1,134 +0,0 @@
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

@ -1,56 +0,0 @@
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)
}
}

View File

@ -1,69 +0,0 @@
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

@ -1,44 +0,0 @@
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)
}
}
}

View File

@ -1,110 +0,0 @@
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,18 +1,19 @@
package d2scene
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2video"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2data/d2video"
"github.com/hajimehoshi/ebiten"
)
type BlizzardIntro struct {
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
videoDecoder *d2video.BinkDecoder
}
func CreateBlizzardIntro(fileProvider d2interface.FileProvider, sceneProvider d2interface.SceneProvider) *BlizzardIntro {
func CreateBlizzardIntro(fileProvider d2interface.FileProvider, sceneProvider d2coreinterface.SceneProvider) *BlizzardIntro {
result := &BlizzardIntro{
fileProvider: fileProvider,
sceneProvider: sceneProvider,

View File

@ -9,13 +9,14 @@ import (
"github.com/hajimehoshi/ebiten/ebitenutil"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
dh "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
@ -25,7 +26,7 @@ type CharacterSelect struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
background d2render.Sprite
newCharButton d2ui.Button
convertCharButton d2ui.Button
@ -51,7 +52,7 @@ type CharacterSelect struct {
func CreateCharacterSelect(
fileProvider d2interface.FileProvider,
sceneProvider d2interface.SceneProvider,
sceneProvider d2coreinterface.SceneProvider,
uiManager *d2ui.Manager,
soundManager *d2audio.Manager,
) *CharacterSelect {

View File

@ -8,19 +8,20 @@ import (
"path"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/D2Shared/d2common"
dh "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
)
@ -36,7 +37,7 @@ type Credits struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
creditsBackground d2render.Sprite
exitButton d2ui.Button
creditsText []string
@ -47,7 +48,7 @@ type Credits struct {
}
// CreateCredits creates an instance of the credits scene
func CreateCredits(fileProvider d2interface.FileProvider, sceneProvider d2interface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *Credits {
func CreateCredits(fileProvider d2interface.FileProvider, sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *Credits {
result := &Credits{
fileProvider: fileProvider,
uiManager: uiManager,

View File

@ -4,11 +4,12 @@ import (
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
@ -19,7 +20,7 @@ type Game struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
pentSpinLeft d2render.Sprite
pentSpinRight d2render.Sprite
testLabel d2ui.Label
@ -27,7 +28,7 @@ type Game struct {
func CreateGame(
fileProvider d2interface.FileProvider,
sceneProvider d2interface.SceneProvider,
sceneProvider d2coreinterface.SceneProvider,
uiManager *d2ui.Manager,
soundManager *d2audio.Manager,
gameState *d2core.GameState,

View File

@ -10,18 +10,19 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
@ -32,7 +33,7 @@ type MainMenu struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
trademarkBackground d2render.Sprite
background d2render.Sprite
diabloLogoLeft d2render.Sprite
@ -56,7 +57,7 @@ type MainMenu struct {
}
// CreateMainMenu creates an instance of MainMenu
func CreateMainMenu(fileProvider d2interface.FileProvider, sceneProvider d2interface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *MainMenu {
func CreateMainMenu(fileProvider d2interface.FileProvider, sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *MainMenu {
result := &MainMenu{
fileProvider: fileProvider,
uiManager: uiManager,

View File

@ -5,14 +5,15 @@ import (
"math"
"os"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
_map "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
@ -24,7 +25,7 @@ type MapEngineTest struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
gameState *d2core.GameState
mapEngine *_map.Engine
currentRegion int
@ -33,7 +34,7 @@ type MapEngineTest struct {
func CreateMapEngineTest(
fileProvider d2interface.FileProvider,
sceneProvider d2interface.SceneProvider,
sceneProvider d2coreinterface.SceneProvider,
uiManager *d2ui.Manager,
soundManager *d2audio.Manager,
currentRegion int) *MapEngineTest {

View File

@ -6,18 +6,19 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/D2Shared/d2common"
dh "github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
)
@ -41,7 +42,7 @@ type SelectHeroClass struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
fileProvider d2interface.FileProvider
sceneProvider d2interface.SceneProvider
sceneProvider d2coreinterface.SceneProvider
bgImage d2render.Sprite
campfire d2render.Sprite
headingLabel d2ui.Label
@ -63,7 +64,7 @@ type SelectHeroClass struct {
func CreateSelectHeroClass(
fileProvider d2interface.FileProvider,
sceneProvider d2interface.SceneProvider,
sceneProvider d2coreinterface.SceneProvider,
uiManager *d2ui.Manager, soundManager *d2audio.Manager,
) *SelectHeroClass {
result := &SelectHeroClass{

View File

@ -10,25 +10,26 @@ import (
"sync"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2data"
"github.com/OpenDiablo2/D2Shared/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq"
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
"github.com/hajimehoshi/ebiten"
@ -38,7 +39,7 @@ import (
// Engine is the core OpenDiablo2 engine
type Engine struct {
Settings *d2common.Configuration // Engine configuration settings from json file
Settings *d2corecommon.Configuration // Engine configuration settings from json file
Files map[string]string // Map that defines which files are in which MPQs
CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that.
LoadingSprite d2render.Sprite // The sprite shown when loading stuff
@ -46,10 +47,10 @@ type Engine struct {
loadingIndex int // Determines which load function is currently being called
thingsToLoad []func() // The load functions for the next scene
stepLoadingSize float64 // The size for each loading step
CurrentScene d2interface.Scene // The current scene being rendered
CurrentScene d2coreinterface.Scene // The current scene being rendered
UIManager *d2ui.Manager // The UI manager
SoundManager *d2audio.Manager // The sound manager
nextScene d2interface.Scene // The next scene to be loaded at the end of the game loop
nextScene d2coreinterface.Scene // The next scene to be loaded at the end of the game loop
fullscreenKey bool // When true, the fullscreen toggle is still being pressed
lastTime float64 // Last time we updated the scene
showFPS bool
@ -92,7 +93,7 @@ func CreateEngine() Engine {
func (v *Engine) loadConfigurationFile() {
log.Println("Loading configuration file")
v.Settings = d2common.LoadConfiguration()
v.Settings = d2corecommon.LoadConfiguration()
}
func (v *Engine) mapMpqFiles() {
@ -242,7 +243,7 @@ func (v Engine) Draw(screen *ebiten.Image) {
}
// SetNextScene tells the engine what scene to load on the next update cycle
func (v *Engine) SetNextScene(nextScene d2interface.Scene) {
func (v *Engine) SetNextScene(nextScene d2coreinterface.Scene) {
v.nextScene = nextScene
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
package d2core
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
"github.com/OpenDiablo2/D2Shared/d2common"
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/hajimehoshi/ebiten"
)

View File

@ -1,4 +1,4 @@
package d2common
package d2corecommon
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package d2interface
package d2coreinterface
import "github.com/hajimehoshi/ebiten"

View File

@ -1,4 +1,4 @@
package d2interface
package d2coreinterface
import (
"github.com/hajimehoshi/ebiten"

View File

@ -1,4 +1,4 @@
package d2interface
package d2coreinterface
// SceneProvider provides the ability to change scenes
type SceneProvider interface {

View File

@ -1,4 +1,4 @@
package d2helper
package d2corehelper
import (
"image/color"

View File

@ -1,52 +0,0 @@
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))
}

View File

@ -1,66 +0,0 @@
package d2cof
import (
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type COF struct {
NumberOfDirections int
FramesPerDirection int
NumberOfLayers int
CofLayers []CofLayer
CompositeLayers map[d2enum.CompositeType]int
AnimationFrames []d2enum.AnimationFrame
Priority [][][]d2enum.CompositeType
}
func LoadCOF(fileName string, fileProvider d2interface.FileProvider) *COF {
result := &COF{}
fileData := fileProvider.LoadFile(fileName)
if len(fileData) == 0 {
return result
}
streamReader := d2common.CreateStreamReader(fileData)
result.NumberOfLayers = int(streamReader.GetByte())
result.FramesPerDirection = int(streamReader.GetByte())
result.NumberOfDirections = int(streamReader.GetByte())
streamReader.SkipBytes(25) // Skip 25 unknown bytes...
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()
streamReader.SkipBytes(1) // Unknown
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)
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[i])
}
}
}
return result
}

View File

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

View File

@ -1,371 +0,0 @@
//
// 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()
}

View File

@ -1,131 +0,0 @@
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

@ -1,18 +0,0 @@
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

@ -1,416 +0,0 @@
package d2datadict
import (
"strings"
"strconv"
"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

@ -1,93 +0,0 @@
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

@ -1,55 +0,0 @@
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, line := range data {
idx := -1
inc := func() int {
idx++
return idx
}
if len(line) == 0 {
continue
}
parts := strings.Split(line, "\t")
if parts[0] == "Expansion" {
continue
}
LevelTypes[i].Name = parts[inc()]
LevelTypes[i].Id = dh.StringToInt(parts[inc()])
for fileIdx := range LevelTypes[i].Files {
LevelTypes[i].Files[fileIdx] = parts[inc()]
if LevelTypes[i].Files[fileIdx] == "0" {
LevelTypes[i].Files[fileIdx] = ""
}
}
LevelTypes[i].Beta = parts[inc()] != "1"
LevelTypes[i].Act = dh.StringToInt(parts[inc()])
LevelTypes[i].Expansion = parts[inc()] != "1"
}
log.Printf("Loaded %d LevelType records", len(LevelTypes))
}

View File

@ -1,53 +0,0 @@
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

@ -1,43 +0,0 @@
package d2datadict
import (
"strings"
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
)
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
}

View File

@ -1,18 +0,0 @@
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

@ -1,411 +0,0 @@
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

@ -1,13 +0,0 @@
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

@ -1,35 +0,0 @@
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

@ -1,357 +0,0 @@
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

@ -1,50 +0,0 @@
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))
}

View File

@ -1,107 +0,0 @@
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

@ -1,139 +0,0 @@
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

@ -1,18 +0,0 @@
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))
}

View File

@ -1,11 +0,0 @@
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 dccDir4 = []byte{0, 1, 2, 3}
var dccDir8 = []byte{4, 0, 5, 1, 6, 2, 7, 3}
var dccDir16 = []byte{4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14, 3, 15}
var dccDir32 = []byte{
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,
}

View File

@ -1,67 +0,0 @@
package d2dcc
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type DCC struct {
Signature int
Version int
NumberOfDirections int
FramesPerDirection int
Directions []DCCDirection
valid bool
}
func (v DCC) IsValid() bool {
return v.valid
}
func LoadDCC(path string, fileProvider d2interface.FileProvider) DCC {
result := DCC{}
fileData := fileProvider.LoadFile(path)
if len(fileData) == 0 {
ret := DCC{}
ret.valid = false
return ret
}
var bm = d2common.CreateBitMuncher(fileData, 0)
result.Signature = int(bm.GetByte())
if result.Signature != 0x74 {
log.Fatal("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 {
log.Fatal("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++ {
dir := byte(0)
switch result.NumberOfDirections {
case 1:
dir = 0
case 4:
dir = dccDir4[i]
case 8:
dir = dccDir8[i]
case 16:
dir = dccDir16[i]
case 32:
dir = dccDir32[i]
}
result.Directions[dir] = CreateDCCDirection(d2common.CreateBitMuncher(fileData, directionOffsets[i]*8), result)
}
result.valid = true
return result
}

View File

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

View File

@ -1,361 +0,0 @@
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)*frame.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

@ -1,108 +0,0 @@
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

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

View File

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

View File

@ -1,249 +0,0 @@
package d2ds1
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"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(path string, fileProvider d2interface.FileProvider) DS1 {
ds1 := DS1{
NumberOfFloors: 1,
NumberOfWalls: 1,
NumberOfShadowLayers: 1,
NumberOfSubstitutionLayers: 0,
}
fileData := fileProvider.LoadFile(path)
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].SubIndex = byte((dw & 0x00003F00) >> 8)
ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14)
ds1.Tiles[y][x].Walls[wallIndex].MainIndex = 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].Orientation = byte(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].SubIndex = byte((dw & 0x00003F00) >> 8)
ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14)
ds1.Tiles[y][x].Floors[floorIndex].MainIndex = 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].SubIndex = byte((dw & 0x00003F00) >> 8)
ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14)
ds1.Tiles[y][x].Shadows[0].MainIndex = 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

@ -1,10 +0,0 @@
package d2ds1
type FloorShadowRecord struct {
Prop1 byte
SubIndex byte
Unknown1 byte
MainIndex byte
Unknown2 byte
Hidden bool
}

View File

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

View File

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

View File

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

View File

@ -1,12 +0,0 @@
package d2ds1
type WallRecord struct {
Orientation byte
Zero byte
Prop1 byte
SubIndex byte
Unknown1 byte
MainIndex byte
Unknown2 byte
Hidden bool
}

View File

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

View File

@ -1,87 +0,0 @@
package d2dt1
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"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(path string, fileProvider d2interface.FileProvider) DT1 {
result := DT1{}
fileData := fileProvider.LoadFile(path)
br := d2common.CreateStreamReader(fileData)
ver1 := br.GetInt32()
ver2 := br.GetInt32()
if ver1 != 7 || ver2 != 6 {
log.Panicf("Expected %s to have a version of 7.6, but got %d.%d instead", path, 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.SoundIndex = br.GetByte()
newTile.Animated = br.GetByte() == 1
newTile.Height = br.GetInt32()
newTile.Width = br.GetInt32()
br.SkipBytes(4)
newTile.Orientation = br.GetInt32()
newTile.MainIndex = br.GetInt32()
newTile.SubIndex = br.GetInt32()
newTile.RarityFrameIndex = br.GetInt32()
br.SkipBytes(4)
for i := range newTile.SubTileFlags {
newTile.SubTileFlags[i] = 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
}

View File

@ -1,18 +0,0 @@
package d2dt1
type Tile struct {
Direction int32
RoofHeight int16
SoundIndex byte
Animated bool
Height int32
Width int32
Orientation int32
MainIndex int32
SubIndex int32
RarityFrameIndex int32
SubTileFlags [25]byte
blockHeaderPointer int32
blockHeaderSize int32
Blocks []Block
}

View File

@ -1,20 +0,0 @@
package d2mpq
// CryptoBuffer contains the crypto bytes for filename hashing
var CryptoBuffer [0x500]uint32
// InitializeCryptoBuffer initializes the crypto buffer
func InitializeCryptoBuffer() {
seed := uint32(0x00100001)
for index1 := 0; index1 < 0x100; index1++ {
index2 := index1
for i := 0; i < 5; i++ {
seed = (seed*125 + 3) % 0x2AAAAB
temp1 := (seed & 0xFFFF) << 0x10
seed = (seed*125 + 3) % 0x2AAAAB
temp2 := (seed & 0xFFFF)
CryptoBuffer[index2] = temp1 | temp2
index2 += 0x100
}
}
}

View File

@ -1,315 +0,0 @@
package d2mpq
import (
"bufio"
"encoding/binary"
"errors"
"log"
"os"
"path"
"strings"
"sync"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
)
// MPQ represents an MPQ archive
type MPQ struct {
FileName string
File *os.File
HashTableEntries []HashTableEntry
BlockTableEntries []BlockTableEntry
Data Data
fileCache map[string][]byte
}
// Data Represents a MPQ file
type Data struct {
Magic [4]byte
HeaderSize uint32
ArchiveSize uint32
FormatVersion uint16
BlockSize uint16
HashTableOffset uint32
BlockTableOffset uint32
HashTableEntries uint32
BlockTableEntries uint32
}
// HashTableEntry represents a hashed file entry in the MPQ file
type HashTableEntry struct { // 16 bytes
NamePartA uint32
NamePartB uint32
Locale uint16
Platform uint16
BlockIndex uint32
}
type PatchInfo struct {
Length uint32 // Length of patch info header, in bytes
Flags uint32 // Flags. 0x80000000 = MD5 (?)
DataSize uint32 // Uncompressed size of the patch file
Md5 [16]byte // MD5 of the entire patch file after decompression
}
// FileFlag represents flags for a file record in the MPQ archive
type FileFlag uint32
const (
// FileImplode - File is compressed using PKWARE Data compression library
FileImplode FileFlag = 0x00000100
// FileCompress - File is compressed using combination of compression methods
FileCompress FileFlag = 0x00000200
// FileEncrypted - The file is encrypted
FileEncrypted FileFlag = 0x00010000
// FileFixKey - The decryption key for the file is altered according to the position of the file in the archive
FileFixKey FileFlag = 0x00020000
// FilePatchFile - The file contains incremental patch for an existing file in base MPQ
FilePatchFile FileFlag = 0x00100000
// FileSingleUnit - Instead of being divided to 0x1000-bytes blocks, the file is stored as single unit
FileSingleUnit FileFlag = 0x01000000
// FileDeleteMarker - File is a deletion marker, indicating that the file no longer exists. This is used to allow patch
// archives to delete files present in lower-priority archives in the search chain. The file usually
// has length of 0 or 1 byte and its name is a hash
FileDeleteMarker FileFlag = 0x02000000
// FileSectorCrc - File has checksums for each sector. Ignored if file is not compressed or imploded.
FileSectorCrc FileFlag = 0x04000000
// FileExists - Set if file exists, reset when the file was deleted
FileExists FileFlag = 0x80000000
)
// BlockTableEntry represents an entry in the block table
type BlockTableEntry struct { // 16 bytes
FilePosition uint32
CompressedFileSize uint32
UncompressedFileSize uint32
Flags FileFlag
// Local Stuff...
FileName string
EncryptionSeed uint32
}
// HasFlag returns true if the specified flag is present
func (v BlockTableEntry) HasFlag(flag FileFlag) bool {
return (v.Flags & flag) != 0
}
var mpqMutex = sync.Mutex{}
var mpqCache = make(map[string]*MPQ)
// Load loads an MPQ file and returns a MPQ structure
func Load(fileName string) (*MPQ, error) {
mpqMutex.Lock()
defer mpqMutex.Unlock()
cached := mpqCache[fileName]
if cached != nil {
return cached, nil
}
result := &MPQ{
FileName: fileName,
fileCache: make(map[string][]byte),
}
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
result.File = file
err = result.readHeader()
if err != nil {
return nil, err
}
mpqCache[fileName] = result
return result, nil
}
func (v *MPQ) readHeader() error {
err := binary.Read(v.File, binary.LittleEndian, &v.Data)
if err != nil {
return err
}
if string(v.Data.Magic[:]) != "MPQ\x1A" {
return errors.New("invalid mpq header")
}
v.loadHashTable()
v.loadBlockTable()
return nil
}
func (v *MPQ) loadHashTable() {
_, err := v.File.Seek(int64(v.Data.HashTableOffset), 0)
if err != nil {
log.Panic(err)
}
hashData := make([]uint32, v.Data.HashTableEntries*4)
err = binary.Read(v.File, binary.LittleEndian, &hashData)
if err != nil {
log.Panic(err)
}
decrypt(hashData, hashString("(hash table)", 3))
for i := uint32(0); i < v.Data.HashTableEntries; i++ {
v.HashTableEntries = append(v.HashTableEntries, HashTableEntry{
NamePartA: hashData[i*4],
NamePartB: hashData[(i*4)+1],
// TODO: Verify that we're grabbing the right high/lo word for the vars below
Locale: uint16(hashData[(i*4)+2] >> 16),
Platform: uint16(hashData[(i*4)+2] & 0xFFFF),
BlockIndex: hashData[(i*4)+3],
})
}
}
func (v *MPQ) loadBlockTable() {
_, err := v.File.Seek(int64(v.Data.BlockTableOffset), 0)
if err != nil {
log.Panic(err)
}
blockData := make([]uint32, v.Data.BlockTableEntries*4)
err = binary.Read(v.File, binary.LittleEndian, &blockData)
if err != nil {
log.Panic(err)
}
decrypt(blockData, hashString("(block table)", 3))
for i := uint32(0); i < v.Data.BlockTableEntries; i++ {
v.BlockTableEntries = append(v.BlockTableEntries, BlockTableEntry{
FilePosition: blockData[(i * 4)],
CompressedFileSize: blockData[(i*4)+1],
UncompressedFileSize: blockData[(i*4)+2],
Flags: FileFlag(blockData[(i*4)+3]),
})
}
}
func decrypt(data []uint32, seed uint32) {
seed2 := uint32(0xeeeeeeee)
for i := 0; i < len(data); i++ {
seed2 += CryptoBuffer[0x400+(seed&0xff)]
result := data[i]
result ^= seed + seed2
seed = ((^seed << 21) + 0x11111111) | (seed >> 11)
seed2 = result + seed2 + (seed2 << 5) + 3
data[i] = result
}
}
func decryptBytes(data []byte, seed uint32) {
seed2 := uint32(0xEEEEEEEE)
for i := 0; i < len(data)-3; i += 4 {
seed2 += CryptoBuffer[0x400+(seed&0xFF)]
result := binary.LittleEndian.Uint32(data[i : i+4])
result ^= seed + seed2
seed = ((^seed << 21) + 0x11111111) | (seed >> 11)
seed2 = result + seed2 + (seed2 << 5) + 3
data[i+0] = uint8(result & 0xff)
data[i+1] = uint8((result >> 8) & 0xff)
data[i+2] = uint8((result >> 16) & 0xff)
data[i+3] = uint8((result >> 24) & 0xff)
}
}
func hashString(key string, hashType uint32) uint32 {
seed1 := uint32(0x7FED7FED)
seed2 := uint32(0xEEEEEEEE)
/* prepare seeds. */
for _, char := range strings.ToUpper(key) {
seed1 = CryptoBuffer[(hashType*0x100)+uint32(char)] ^ (seed1 + seed2)
seed2 = uint32(char) + seed1 + seed2 + (seed2 << 5) + 3
}
return seed1
}
func (v MPQ) getFileHashEntry(fileName string) (HashTableEntry, error) {
hashA := hashString(fileName, 1)
hashB := hashString(fileName, 2)
for idx, hashEntry := range v.HashTableEntries {
if hashEntry.NamePartA != hashA || hashEntry.NamePartB != hashB {
continue
}
return v.HashTableEntries[idx], nil
}
return HashTableEntry{}, errors.New("file not found")
}
// GetFileBlockData gets a block table entry
func (v MPQ) getFileBlockData(fileName string) (BlockTableEntry, error) {
fileEntry, err := v.getFileHashEntry(fileName)
if err != nil || fileEntry.BlockIndex >= uint32(len(v.BlockTableEntries)) {
return BlockTableEntry{}, err
}
return v.BlockTableEntries[fileEntry.BlockIndex], nil
}
// Close closes the MPQ file
func (v *MPQ) Close() {
err := v.File.Close()
if err != nil {
log.Panic(err)
}
}
func (v MPQ) FileExists(fileName string) bool {
_, err := v.getFileHashEntry(fileName)
return err == nil
}
// ReadFile reads a file from the MPQ and returns a memory stream
func (v MPQ) ReadFile(fileName string) ([]byte, error) {
fileName = strings.ReplaceAll(fileName, "{LANG}", d2resource.LanguageCode)
fileName = strings.ToLower(fileName)
fileName = strings.ReplaceAll(fileName, `/`, "\\")
cached := v.fileCache[fileName]
if cached != nil {
return cached, nil
}
fileBlockData, err := v.getFileBlockData(fileName)
if err != nil {
return []byte{}, err
}
fileBlockData.FileName = strings.ToLower(fileName)
fileBlockData.calculateEncryptionSeed()
mpqStream := CreateStream(v, fileBlockData, fileName)
buffer := make([]byte, fileBlockData.UncompressedFileSize)
mpqStream.Read(buffer, 0, fileBlockData.UncompressedFileSize)
v.fileCache[fileName] = buffer
return buffer, nil
}
// ReadTextFile reads a file and returns it as a string
func (v MPQ) ReadTextFile(fileName string) (string, error) {
data, err := v.ReadFile(fileName)
if err != nil {
return "", err
}
return string(data), nil
}
func (v *BlockTableEntry) calculateEncryptionSeed() {
fileName := path.Base(v.FileName)
v.EncryptionSeed = hashString(fileName, 3)
if !v.HasFlag(FileFixKey) {
return
}
v.EncryptionSeed = (v.EncryptionSeed + v.FilePosition) ^ v.UncompressedFileSize
}
// GetFileList returns the list of files in this MPQ
func (v MPQ) GetFileList() ([]string, error) {
data, err := v.ReadFile("(listfile)")
if err != nil {
return nil, err
}
raw := strings.TrimRight(string(data), "\x00")
s := bufio.NewScanner(strings.NewReader(raw))
var filePaths []string
for s.Scan() {
filePath := s.Text()
filePaths = append(filePaths, filePath)
}
return filePaths, nil
}

View File

@ -1,7 +0,0 @@
package d2mpq
type MpqFileRecord struct {
MpqFile string
IsPatch bool
UnpatchedMpqFile string
}

View File

@ -1,268 +0,0 @@
package d2mpq
import (
"bufio"
"bytes"
"compress/zlib"
"encoding/binary"
"fmt"
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
"github.com/JoshVarga/blast"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2compression"
)
// Stream represents a stream of data in an MPQ archive
type Stream struct {
MPQData MPQ
BlockTableEntry BlockTableEntry
FileName string
EncryptionSeed uint32
BlockPositions []uint32
CurrentPosition uint32
CurrentData []byte
CurrentBlockIndex uint32
BlockSize uint32
}
// CreateStream creates an MPQ stream
func CreateStream(mpq MPQ, blockTableEntry BlockTableEntry, fileName string) *Stream {
result := &Stream{
MPQData: mpq,
BlockTableEntry: blockTableEntry,
CurrentBlockIndex: 0xFFFFFFFF,
}
fileSegs := strings.Split(fileName, `\`)
result.EncryptionSeed = hashString(fileSegs[len(fileSegs)-1], 3)
if result.BlockTableEntry.HasFlag(FileFixKey) {
result.EncryptionSeed = (result.EncryptionSeed + result.BlockTableEntry.FilePosition) ^ result.BlockTableEntry.UncompressedFileSize
}
result.BlockSize = 0x200 << result.MPQData.Data.BlockSize
if result.BlockTableEntry.HasFlag(FilePatchFile) {
log.Fatal("Patching is not supported")
}
if (result.BlockTableEntry.HasFlag(FileCompress) || result.BlockTableEntry.HasFlag(FileImplode)) && !result.BlockTableEntry.HasFlag(FileSingleUnit) {
result.loadBlockOffsets()
}
return result
}
func (v *Stream) loadBlockOffsets() {
blockPositionCount := ((v.BlockTableEntry.UncompressedFileSize + v.BlockSize - 1) / v.BlockSize) + 1
v.BlockPositions = make([]uint32, blockPositionCount)
v.MPQData.File.Seek(int64(v.BlockTableEntry.FilePosition), 0)
reader := bufio.NewReader(v.MPQData.File)
bytes := make([]byte, blockPositionCount*4)
reader.Read(bytes)
for i := range v.BlockPositions {
idx := i * 4
v.BlockPositions[i] = binary.LittleEndian.Uint32(bytes[idx : idx+4])
}
//binary.Read(v.MPQData.File, binary.LittleEndian, &v.BlockPositions)
blockPosSize := blockPositionCount << 2
if v.BlockTableEntry.HasFlag(FileEncrypted) {
decrypt(v.BlockPositions, v.EncryptionSeed-1)
if v.BlockPositions[0] != blockPosSize {
panic("Decryption of MPQ failed!")
}
if v.BlockPositions[1] > v.BlockSize+blockPosSize {
panic("Decryption of MPQ failed!")
}
}
}
func (v *Stream) Read(buffer []byte, offset, count uint32) uint32 {
if v.BlockTableEntry.HasFlag(FileSingleUnit) {
return v.readInternalSingleUnit(buffer, offset, count)
}
toRead := count
readTotal := uint32(0)
for toRead > 0 {
read := v.readInternal(buffer, offset, count)
if read == 0 {
break
}
readTotal += read
offset += read
toRead -= read
}
return readTotal
}
func (v *Stream) readInternalSingleUnit(buffer []byte, offset, count uint32) uint32 {
if len(v.CurrentData) == 0 {
v.loadSingleUnit()
}
bytesToCopy := d2helper.Min(uint32(len(v.CurrentData))-v.CurrentPosition, count)
copy(buffer[offset:offset+bytesToCopy], v.CurrentData[v.CurrentPosition:v.CurrentPosition+bytesToCopy])
v.CurrentPosition += bytesToCopy
return bytesToCopy
}
func (v *Stream) readInternal(buffer []byte, offset, count uint32) uint32 {
v.bufferData()
localPosition := v.CurrentPosition % v.BlockSize
bytesToCopy := d2helper.MinInt32(int32(len(v.CurrentData))-int32(localPosition), int32(count))
if bytesToCopy <= 0 {
return 0
}
copy(buffer[offset:offset+uint32(bytesToCopy)], v.CurrentData[localPosition:localPosition+uint32(bytesToCopy)])
v.CurrentPosition += uint32(bytesToCopy)
return uint32(bytesToCopy)
}
func (v *Stream) bufferData() {
requiredBlock := uint32(v.CurrentPosition / v.BlockSize)
if requiredBlock == v.CurrentBlockIndex {
return
}
expectedLength := d2helper.Min(v.BlockTableEntry.UncompressedFileSize-(requiredBlock*v.BlockSize), v.BlockSize)
v.CurrentData = v.loadBlock(requiredBlock, expectedLength)
v.CurrentBlockIndex = requiredBlock
}
func (v *Stream) loadSingleUnit() {
fileData := make([]byte, v.BlockSize)
v.MPQData.File.Seek(int64(v.MPQData.Data.HeaderSize), 0)
//binary.Read(v.MPQData.File, binary.LittleEndian, &fileData)
reader := bufio.NewReader(v.MPQData.File)
reader.Read(fileData)
if v.BlockSize == v.BlockTableEntry.UncompressedFileSize {
v.CurrentData = fileData
return
}
v.CurrentData = decompressMulti(fileData, v.BlockTableEntry.UncompressedFileSize)
}
func (v *Stream) loadBlock(blockIndex, expectedLength uint32) []byte {
var (
offset uint32
toRead uint32
)
if v.BlockTableEntry.HasFlag(FileCompress) || v.BlockTableEntry.HasFlag(FileImplode) {
offset = v.BlockPositions[blockIndex]
toRead = v.BlockPositions[blockIndex+1] - offset
} else {
offset = blockIndex * v.BlockSize
toRead = expectedLength
}
offset += v.BlockTableEntry.FilePosition
data := make([]byte, toRead)
v.MPQData.File.Seek(int64(offset), 0)
//binary.Read(v.MPQData.File, binary.LittleEndian, &data)
reader := bufio.NewReader(v.MPQData.File)
reader.Read(data)
if v.BlockTableEntry.HasFlag(FileEncrypted) && v.BlockTableEntry.UncompressedFileSize > 3 {
if v.EncryptionSeed == 0 {
panic("Unable to determine encryption key")
}
decryptBytes(data, blockIndex+v.EncryptionSeed)
}
if v.BlockTableEntry.HasFlag(FileCompress) && (toRead != expectedLength) {
if !v.BlockTableEntry.HasFlag(FileSingleUnit) {
data = decompressMulti(data, expectedLength)
} else {
data = pkDecompress(data)
}
}
if v.BlockTableEntry.HasFlag(FileImplode) && (toRead != expectedLength) {
data = pkDecompress(data)
}
return data
}
func decompressMulti(data []byte, expectedLength uint32) []byte {
copmressionType := data[0]
switch copmressionType {
case 1: // Huffman
panic("huffman decompression not supported")
case 2: // ZLib/Deflate
return deflate(data[1:])
case 8: // PKLib/Impode
return pkDecompress(data[1:])
case 0x10: // BZip2
panic("bzip2 decompression not supported")
case 0x80: // IMA ADPCM Stereo
return d2compression.WavDecompress(data[1:], 2)
//return MpqWavCompression.Decompress(sinput, 2);
//panic("ima adpcm sterio decompression not supported")
case 0x40: // IMA ADPCM Mono
//return MpqWavCompression.Decompress(sinput, 1)
panic("mpq wav decompression not supported")
case 0x12:
panic("lzma decompression not supported")
// Combos
case 0x22:
// TODO: sparse then zlib
panic("sparse decompression + deflate decompression not supported")
case 0x30:
// TODO: sparse then bzip2
panic("sparse decompression + bzip2 decompression not supported")
case 0x41:
sinput := d2compression.HuffmanDecompress(data[1:])
sinput = d2compression.WavDecompress(sinput, 1)
tmp := make([]byte, len(sinput))
copy(tmp, sinput)
return tmp
case 0x48:
//byte[] result = PKDecompress(sinput, outputLength);
//return MpqWavCompression.Decompress(new MemoryStream(result), 1);
panic("pk + mpqwav decompression not supported")
case 0x81:
sinput := d2compression.HuffmanDecompress(data[1:])
sinput = d2compression.WavDecompress(sinput, 2)
tmp := make([]byte, len(sinput))
copy(tmp, sinput)
return tmp
case 0x88:
//byte[] result = PKDecompress(sinput, outputLength);
//return MpqWavCompression.Decompress(new MemoryStream(result), 2);
panic("pk + wav decompression not supported")
default:
panic(fmt.Sprintf("decompression not supported for unknown compression type %X", copmressionType))
}
}
func deflate(data []byte) []byte {
b := bytes.NewReader(data)
r, err := zlib.NewReader(b)
if err != nil {
panic(err)
}
buffer := new(bytes.Buffer)
_, err = buffer.ReadFrom(r)
if err != nil {
log.Panic(err)
}
err = r.Close()
if err != nil {
log.Panic(err)
}
return buffer.Bytes()
}
func pkDecompress(data []byte) []byte {
b := bytes.NewReader(data)
r, err := blast.NewReader(b)
if err != nil {
panic(err)
}
buffer := new(bytes.Buffer)
_, err = buffer.ReadFrom(r)
if err != nil {
panic(err)
}
err = r.Close()
if err != nil {
panic(err)
}
return buffer.Bytes()
}

View File

@ -1,111 +0,0 @@
package d2video
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
type BinkVideoMode uint32
const (
BinkVideoModeNormal BinkVideoMode = 0
BinkVideoModeHeightDoubled BinkVideoMode = 1
BinkVideoModeHeightInterlaced BinkVideoMode = 2
BinkVideoModeWidthDoubled BinkVideoMode = 3
BinkVideoModeWidthAndHeightDoubled BinkVideoMode = 4
BinkVideoModeWidthAndHeightInterlaced BinkVideoMode = 5
)
type BinkAudioAlgorithm uint32
const (
BinkAudioAlgorithmFFT BinkAudioAlgorithm = 0
BinkAudioAlgorithmDCT BinkAudioAlgorithm = 1
)
type BinkAudioTrack struct {
AudioChannels uint16
AudioSampleRateHz uint16
Stereo bool
Algorithm BinkAudioAlgorithm
AudioTrackId uint32
}
type BinkDecoder struct {
videoCodecRevision byte
fileSize uint32
numberOfFrames uint32
largestFrameSizeBytes uint32
VideoWidth uint32
VideoHeight uint32
FPS uint32
FrameTimeMS uint32
streamReader *d2common.StreamReader
VideoMode BinkVideoMode
HasAlphaPlane bool
Grayscale bool
AudioTracks []BinkAudioTrack
FrameIndexTable []uint32 // Mask bit 0, as this is defined as a keyframe
frameIndex uint32
}
func CreateBinkDecoder(source []byte) *BinkDecoder {
result := &BinkDecoder{
streamReader: d2common.CreateStreamReader(source),
}
result.loadHeaderInformation()
return result
}
func (v *BinkDecoder) GetNextFrame() {
//v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE))
lengthOfAudioPackets := v.streamReader.GetUInt32() - 4
samplesInPacket := v.streamReader.GetUInt32()
v.streamReader.SkipBytes(int(lengthOfAudioPackets))
log.Printf("Frame %d:\tSamp: %d", v.frameIndex, samplesInPacket)
v.frameIndex++
}
func (v *BinkDecoder) loadHeaderInformation() {
v.streamReader.SetPosition(0)
headerBytes, _ := v.streamReader.ReadBytes(3)
if string(headerBytes) != "BIK" {
log.Fatal("Invalid header for bink video")
}
v.videoCodecRevision = v.streamReader.GetByte()
v.fileSize = v.streamReader.GetUInt32()
v.numberOfFrames = v.streamReader.GetUInt32()
v.largestFrameSizeBytes = v.streamReader.GetUInt32()
v.streamReader.SkipBytes(4) // Number of frames again?
v.VideoWidth = v.streamReader.GetUInt32()
v.VideoHeight = v.streamReader.GetUInt32()
fpsDividend := v.streamReader.GetUInt32()
fpsDivider := v.streamReader.GetUInt32()
v.FPS = uint32(float32(fpsDividend) / float32(fpsDivider))
v.FrameTimeMS = 1000 / v.FPS
videoFlags := v.streamReader.GetUInt32()
v.VideoMode = BinkVideoMode((videoFlags >> 28) & 0x0F)
v.HasAlphaPlane = ((videoFlags >> 20) & 0x1) == 1
v.Grayscale = ((videoFlags >> 17) & 0x1) == 1
numberOfAudioTracks := v.streamReader.GetUInt32()
v.AudioTracks = make([]BinkAudioTrack, numberOfAudioTracks)
for i := 0; i < int(numberOfAudioTracks); i++ {
v.streamReader.SkipBytes(2) // Unknown
v.AudioTracks[i].AudioChannels = v.streamReader.GetUInt16()
}
for i := 0; i < int(numberOfAudioTracks); i++ {
v.AudioTracks[i].AudioSampleRateHz = v.streamReader.GetUInt16()
flags := v.streamReader.GetUInt16()
v.AudioTracks[i].Stereo = ((flags >> 13) & 0x1) == 1
v.AudioTracks[i].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1)
}
for i := 0; i < int(numberOfAudioTracks); i++ {
v.AudioTracks[i].AudioTrackId = v.streamReader.GetUInt32()
}
v.FrameIndexTable = make([]uint32, v.numberOfFrames+1)
for i := 0; i < int(v.numberOfFrames+1); i++ {
v.FrameIndexTable[i] = v.streamReader.GetUInt32()
}
}

View File

@ -1,17 +0,0 @@
package d2data
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
)
type Object struct {
Type int32
Id int32
X int32
Y int32
Flags int32
Paths []d2common.Path
Lookup *d2datadict.ObjectLookupRecord
ObjectInfo *d2datadict.ObjectRecord
}

View File

@ -1,66 +0,0 @@
package d2helper
// Min returns the lower of two values
func Min(a, b uint32) uint32 {
if a < b {
return a
}
return b
}
// Max returns the higher of two values
func Max(a, b uint32) uint32 {
if a > b {
return a
}
return b
}
// MaxInt32 returns the higher of two values
func MaxInt32(a, b int32) int32 {
if a > b {
return a
}
return b
}
func NextPow2(x int32) int32 {
result := int32(1)
for result < x {
result *= 2
}
return result
}
func AbsInt32(a int32) int32 {
if a < 0 {
return -a
}
return a
}
// MinInt32 returns the higher of two values
func MinInt32(a, b int32) int32 {
if a < b {
return a
}
return b
}
// BytesToInt32 converts 4 bytes to int32
func BytesToInt32(b []byte) int32 {
// equivalnt of return int32(binary.LittleEndian.Uint32(b))
return int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
}
func IsoToScreen(isoX, isoY, modX, modY int) (int, int) {
screenX := (isoX - isoY) * 80
screenY := (isoX + isoY) * 40
return screenX + modX, screenY + modY
}
func ScreenToIso(sx, sy float64) (float64, float64) {
x := (sx/80 + sy/40) / 2
y := (sy/40 - (sx / 80)) / 2
return x, y
}

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