mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-06-10 09:50:42 +00:00
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:
parent
404a87afd4
commit
1c2b4869a1
|
@ -3,7 +3,7 @@ package d2audio
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/audio"
|
"github.com/hajimehoshi/ebiten/audio"
|
||||||
"github.com/hajimehoshi/ebiten/audio/wav"
|
"github.com/hajimehoshi/ebiten/audio/wav"
|
||||||
|
|
|
@ -3,9 +3,9 @@ package d2audio
|
||||||
import (
|
import (
|
||||||
"log"
|
"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"
|
"github.com/hajimehoshi/ebiten/audio/wav"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
|
|
@ -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]]
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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)
|
|
||||||
)
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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]]
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package d2enum
|
|
||||||
|
|
||||||
type InventoryItemType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
InventoryItemTypeItem InventoryItemType = 0 // Item
|
|
||||||
InventoryItemTypeWeapon InventoryItemType = 1 // Weapon
|
|
||||||
InventoryItemTypeArmor InventoryItemType = 2 // Armor
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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"
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
```
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -1,9 +0,0 @@
|
||||||
package d2enum
|
|
||||||
|
|
||||||
type RegionLayerType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RegionLayerTypeFloors RegionLayerType = 0
|
|
||||||
RegionLayerTypeWalls RegionLayerType = 1
|
|
||||||
RegionLayerTypeShadows RegionLayerType = 2
|
|
||||||
)
|
|
|
@ -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
|
|
|
@ -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]]
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package d2common
|
|
||||||
|
|
||||||
type Path struct {
|
|
||||||
X int32
|
|
||||||
Y int32
|
|
||||||
Action int32
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +1,19 @@
|
||||||
package d2scene
|
package d2scene
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2video"
|
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2data/d2video"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BlizzardIntro struct {
|
type BlizzardIntro struct {
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
videoDecoder *d2video.BinkDecoder
|
videoDecoder *d2video.BinkDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBlizzardIntro(fileProvider d2interface.FileProvider, sceneProvider d2interface.SceneProvider) *BlizzardIntro {
|
func CreateBlizzardIntro(fileProvider d2interface.FileProvider, sceneProvider d2coreinterface.SceneProvider) *BlizzardIntro {
|
||||||
result := &BlizzardIntro{
|
result := &BlizzardIntro{
|
||||||
fileProvider: fileProvider,
|
fileProvider: fileProvider,
|
||||||
sceneProvider: sceneProvider,
|
sceneProvider: sceneProvider,
|
||||||
|
|
|
@ -9,13 +9,14 @@ import (
|
||||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
dh "github.com/OpenDiablo2/D2Shared/d2helper"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
@ -25,7 +26,7 @@ type CharacterSelect struct {
|
||||||
uiManager *d2ui.Manager
|
uiManager *d2ui.Manager
|
||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
background d2render.Sprite
|
background d2render.Sprite
|
||||||
newCharButton d2ui.Button
|
newCharButton d2ui.Button
|
||||||
convertCharButton d2ui.Button
|
convertCharButton d2ui.Button
|
||||||
|
@ -51,7 +52,7 @@ type CharacterSelect struct {
|
||||||
|
|
||||||
func CreateCharacterSelect(
|
func CreateCharacterSelect(
|
||||||
fileProvider d2interface.FileProvider,
|
fileProvider d2interface.FileProvider,
|
||||||
sceneProvider d2interface.SceneProvider,
|
sceneProvider d2coreinterface.SceneProvider,
|
||||||
uiManager *d2ui.Manager,
|
uiManager *d2ui.Manager,
|
||||||
soundManager *d2audio.Manager,
|
soundManager *d2audio.Manager,
|
||||||
) *CharacterSelect {
|
) *CharacterSelect {
|
||||||
|
|
|
@ -8,19 +8,20 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"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/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/d2audio"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
dh "github.com/OpenDiablo2/D2Shared/d2helper"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
@ -36,7 +37,7 @@ type Credits struct {
|
||||||
uiManager *d2ui.Manager
|
uiManager *d2ui.Manager
|
||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
creditsBackground d2render.Sprite
|
creditsBackground d2render.Sprite
|
||||||
exitButton d2ui.Button
|
exitButton d2ui.Button
|
||||||
creditsText []string
|
creditsText []string
|
||||||
|
@ -47,7 +48,7 @@ type Credits struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCredits creates an instance of the credits scene
|
// 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{
|
result := &Credits{
|
||||||
fileProvider: fileProvider,
|
fileProvider: fileProvider,
|
||||||
uiManager: uiManager,
|
uiManager: uiManager,
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||||
|
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
"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"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
@ -19,7 +20,7 @@ type Game struct {
|
||||||
uiManager *d2ui.Manager
|
uiManager *d2ui.Manager
|
||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
pentSpinLeft d2render.Sprite
|
pentSpinLeft d2render.Sprite
|
||||||
pentSpinRight d2render.Sprite
|
pentSpinRight d2render.Sprite
|
||||||
testLabel d2ui.Label
|
testLabel d2ui.Label
|
||||||
|
@ -27,7 +28,7 @@ type Game struct {
|
||||||
|
|
||||||
func CreateGame(
|
func CreateGame(
|
||||||
fileProvider d2interface.FileProvider,
|
fileProvider d2interface.FileProvider,
|
||||||
sceneProvider d2interface.SceneProvider,
|
sceneProvider d2coreinterface.SceneProvider,
|
||||||
uiManager *d2ui.Manager,
|
uiManager *d2ui.Manager,
|
||||||
soundManager *d2audio.Manager,
|
soundManager *d2audio.Manager,
|
||||||
gameState *d2core.GameState,
|
gameState *d2core.GameState,
|
||||||
|
|
|
@ -10,18 +10,19 @@ import (
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
"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/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/d2audio"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
@ -32,7 +33,7 @@ type MainMenu struct {
|
||||||
uiManager *d2ui.Manager
|
uiManager *d2ui.Manager
|
||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
trademarkBackground d2render.Sprite
|
trademarkBackground d2render.Sprite
|
||||||
background d2render.Sprite
|
background d2render.Sprite
|
||||||
diabloLogoLeft d2render.Sprite
|
diabloLogoLeft d2render.Sprite
|
||||||
|
@ -56,7 +57,7 @@ type MainMenu struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMainMenu creates an instance of MainMenu
|
// 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{
|
result := &MainMenu{
|
||||||
fileProvider: fileProvider,
|
fileProvider: fileProvider,
|
||||||
uiManager: uiManager,
|
uiManager: uiManager,
|
||||||
|
|
|
@ -5,14 +5,15 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
"github.com/OpenDiablo2/D2Shared/d2helper"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
"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/d2audio"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
_map "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
|
_map "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
@ -24,7 +25,7 @@ type MapEngineTest struct {
|
||||||
uiManager *d2ui.Manager
|
uiManager *d2ui.Manager
|
||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
gameState *d2core.GameState
|
gameState *d2core.GameState
|
||||||
mapEngine *_map.Engine
|
mapEngine *_map.Engine
|
||||||
currentRegion int
|
currentRegion int
|
||||||
|
@ -33,7 +34,7 @@ type MapEngineTest struct {
|
||||||
|
|
||||||
func CreateMapEngineTest(
|
func CreateMapEngineTest(
|
||||||
fileProvider d2interface.FileProvider,
|
fileProvider d2interface.FileProvider,
|
||||||
sceneProvider d2interface.SceneProvider,
|
sceneProvider d2coreinterface.SceneProvider,
|
||||||
uiManager *d2ui.Manager,
|
uiManager *d2ui.Manager,
|
||||||
soundManager *d2audio.Manager,
|
soundManager *d2audio.Manager,
|
||||||
currentRegion int) *MapEngineTest {
|
currentRegion int) *MapEngineTest {
|
||||||
|
|
|
@ -6,18 +6,19 @@ import (
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
"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/d2render"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"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/d2audio"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
dh "github.com/OpenDiablo2/D2Shared/d2helper"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +42,7 @@ type SelectHeroClass struct {
|
||||||
uiManager *d2ui.Manager
|
uiManager *d2ui.Manager
|
||||||
soundManager *d2audio.Manager
|
soundManager *d2audio.Manager
|
||||||
fileProvider d2interface.FileProvider
|
fileProvider d2interface.FileProvider
|
||||||
sceneProvider d2interface.SceneProvider
|
sceneProvider d2coreinterface.SceneProvider
|
||||||
bgImage d2render.Sprite
|
bgImage d2render.Sprite
|
||||||
campfire d2render.Sprite
|
campfire d2render.Sprite
|
||||||
headingLabel d2ui.Label
|
headingLabel d2ui.Label
|
||||||
|
@ -63,7 +64,7 @@ type SelectHeroClass struct {
|
||||||
|
|
||||||
func CreateSelectHeroClass(
|
func CreateSelectHeroClass(
|
||||||
fileProvider d2interface.FileProvider,
|
fileProvider d2interface.FileProvider,
|
||||||
sceneProvider d2interface.SceneProvider,
|
sceneProvider d2coreinterface.SceneProvider,
|
||||||
uiManager *d2ui.Manager, soundManager *d2audio.Manager,
|
uiManager *d2ui.Manager, soundManager *d2audio.Manager,
|
||||||
) *SelectHeroClass {
|
) *SelectHeroClass {
|
||||||
result := &SelectHeroClass{
|
result := &SelectHeroClass{
|
||||||
|
|
|
@ -10,25 +10,26 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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/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/d2audio"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
@ -38,7 +39,7 @@ import (
|
||||||
|
|
||||||
// Engine is the core OpenDiablo2 engine
|
// Engine is the core OpenDiablo2 engine
|
||||||
type Engine struct {
|
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
|
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.
|
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
|
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
|
loadingIndex int // Determines which load function is currently being called
|
||||||
thingsToLoad []func() // The load functions for the next scene
|
thingsToLoad []func() // The load functions for the next scene
|
||||||
stepLoadingSize float64 // The size for each loading step
|
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
|
UIManager *d2ui.Manager // The UI manager
|
||||||
SoundManager *d2audio.Manager // The sound 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
|
fullscreenKey bool // When true, the fullscreen toggle is still being pressed
|
||||||
lastTime float64 // Last time we updated the scene
|
lastTime float64 // Last time we updated the scene
|
||||||
showFPS bool
|
showFPS bool
|
||||||
|
@ -92,7 +93,7 @@ func CreateEngine() Engine {
|
||||||
|
|
||||||
func (v *Engine) loadConfigurationFile() {
|
func (v *Engine) loadConfigurationFile() {
|
||||||
log.Println("Loading configuration file")
|
log.Println("Loading configuration file")
|
||||||
v.Settings = d2common.LoadConfiguration()
|
v.Settings = d2corecommon.LoadConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Engine) mapMpqFiles() {
|
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
|
// 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
|
v.nextScene = nextScene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package d2core
|
package d2core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"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/OpenDiablo2/OpenDiablo2/d2render"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package d2core
|
package d2core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HeroObjects map[d2enum.Hero]CharacterEquipment
|
var HeroObjects map[d2enum.Hero]CharacterEquipment
|
||||||
|
|
|
@ -3,8 +3,8 @@ package d2core
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InventoryItemArmor struct {
|
type InventoryItemArmor struct {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package d2core
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InventoryItemWeapon struct {
|
type InventoryItemWeapon struct {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package d2core
|
package d2core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||||
"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/OpenDiablo2/OpenDiablo2/d2render"
|
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package d2common
|
package d2corecommon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
||||||
package d2interface
|
package d2coreinterface
|
||||||
|
|
||||||
import "github.com/hajimehoshi/ebiten"
|
import "github.com/hajimehoshi/ebiten"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package d2interface
|
package d2coreinterface
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
|
@ -1,4 +1,4 @@
|
||||||
package d2interface
|
package d2coreinterface
|
||||||
|
|
||||||
// SceneProvider provides the ability to change scenes
|
// SceneProvider provides the ability to change scenes
|
||||||
type SceneProvider interface {
|
type SceneProvider interface {
|
|
@ -1,4 +1,4 @@
|
||||||
package d2helper
|
package d2corehelper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package d2dcc
|
|
||||||
|
|
||||||
type DCCPixelBufferEntry struct {
|
|
||||||
Value [4]byte
|
|
||||||
Frame int
|
|
||||||
FrameCellIndex int
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package d2ds1
|
|
||||||
|
|
||||||
type FloorShadowRecord struct {
|
|
||||||
Prop1 byte
|
|
||||||
SubIndex byte
|
|
||||||
Unknown1 byte
|
|
||||||
MainIndex byte
|
|
||||||
Unknown2 byte
|
|
||||||
Hidden bool
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package d2ds1
|
|
||||||
|
|
||||||
type SubstitutionGroup struct {
|
|
||||||
TileX int32
|
|
||||||
TileY int32
|
|
||||||
WidthInTiles int32
|
|
||||||
HeightInTiles int32
|
|
||||||
Unknown int32
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package d2ds1
|
|
||||||
|
|
||||||
type SubstitutionRecord struct {
|
|
||||||
Unknown uint32
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package d2ds1
|
|
||||||
|
|
||||||
type TileRecord struct {
|
|
||||||
Floors []FloorShadowRecord
|
|
||||||
Walls []WallRecord
|
|
||||||
Shadows []FloorShadowRecord
|
|
||||||
Substitutions []SubstitutionRecord
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package d2mpq
|
|
||||||
|
|
||||||
type MpqFileRecord struct {
|
|
||||||
MpqFile string
|
|
||||||
IsPatch bool
|
|
||||||
UnpatchedMpqFile string
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user