mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-01-12 04:17:25 -05:00
Fix MPQ load, add Missiles.txt load, switch LvlPrest.bin to LvlPrest.txt, fix sound loading (#80)
* fix mpqs, add missiles.txt, fix lvlprest * fix sound loading issue
This commit is contained in:
parent
46a60398d4
commit
07b0724575
@ -8,62 +8,82 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LevelPresetRecord struct {
|
type LevelPresetRecord struct {
|
||||||
DefinitionId int32
|
Name string
|
||||||
LevelId int32
|
DefinitionId int
|
||||||
|
LevelId int
|
||||||
Populate bool
|
Populate bool
|
||||||
Logicals bool
|
Logicals bool
|
||||||
Outdoors bool
|
Outdoors bool
|
||||||
Animate bool
|
Animate bool
|
||||||
KillEdge bool
|
KillEdge bool
|
||||||
FillBlanks bool
|
FillBlanks bool
|
||||||
SizeX int32
|
SizeX int
|
||||||
SizeY int32
|
SizeY int
|
||||||
AutoMap bool
|
AutoMap bool
|
||||||
Scan bool
|
Scan bool
|
||||||
Pops int32
|
Pops int
|
||||||
PopPad int32
|
PopPad int
|
||||||
|
FileCount int
|
||||||
Files [6]string
|
Files [6]string
|
||||||
Dt1Mask uint32
|
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: StringToInt(props[inc()]),
|
||||||
|
LevelId: StringToInt(props[inc()]),
|
||||||
|
Populate: StringToUint8(props[inc()]) == 1,
|
||||||
|
Logicals: StringToUint8(props[inc()]) == 1,
|
||||||
|
Outdoors: StringToUint8(props[inc()]) == 1,
|
||||||
|
Animate: StringToUint8(props[inc()]) == 1,
|
||||||
|
KillEdge: StringToUint8(props[inc()]) == 1,
|
||||||
|
FillBlanks: StringToUint8(props[inc()]) == 1,
|
||||||
|
SizeX: StringToInt(props[inc()]),
|
||||||
|
SizeY: StringToInt(props[inc()]),
|
||||||
|
AutoMap: StringToUint8(props[inc()]) == 1,
|
||||||
|
Scan: StringToUint8(props[inc()]) == 1,
|
||||||
|
Pops: StringToInt(props[inc()]),
|
||||||
|
PopPad: StringToInt(props[inc()]),
|
||||||
|
FileCount: StringToInt(props[inc()]),
|
||||||
|
Files: [6]string{
|
||||||
|
props[inc()],
|
||||||
|
props[inc()],
|
||||||
|
props[inc()],
|
||||||
|
props[inc()],
|
||||||
|
props[inc()],
|
||||||
|
props[inc()],
|
||||||
|
},
|
||||||
|
Dt1Mask: StringToUint(props[inc()]),
|
||||||
|
Beta: StringToUint8(props[inc()]) == 1,
|
||||||
|
Expansion: StringToUint8(props[inc()]) == 1,
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
var LevelPresets map[int]*LevelPresetRecord
|
var LevelPresets map[int]*LevelPresetRecord
|
||||||
|
|
||||||
func LoadLevelPresets(fileProvider FileProvider) {
|
func LoadLevelPresets(fileProvider FileProvider) {
|
||||||
levelTypesData := fileProvider.LoadFile(ResourcePaths.LevelPreset)
|
|
||||||
sr := CreateStreamReader(levelTypesData)
|
|
||||||
sr.SkipBytes(4) // Count
|
|
||||||
LevelPresets = make(map[int]*LevelPresetRecord)
|
LevelPresets = make(map[int]*LevelPresetRecord)
|
||||||
for !sr.Eof() {
|
data := strings.Split(string(fileProvider.LoadFile(ResourcePaths.LevelPreset)), "\r\n")[1:]
|
||||||
i := int(sr.GetInt32())
|
for _, line := range data {
|
||||||
LevelPresets[i] = &LevelPresetRecord{}
|
if len(line) == 0 {
|
||||||
LevelPresets[i].DefinitionId = int32(i)
|
continue
|
||||||
LevelPresets[i].LevelId = sr.GetInt32()
|
|
||||||
LevelPresets[i].Populate = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].Logicals = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].Outdoors = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].Animate = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].KillEdge = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].FillBlanks = sr.GetInt32() != 0
|
|
||||||
sr.GetInt32() // What is this field?
|
|
||||||
LevelPresets[i].SizeX = sr.GetInt32()
|
|
||||||
LevelPresets[i].SizeY = sr.GetInt32()
|
|
||||||
LevelPresets[i].AutoMap = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].Scan = sr.GetInt32() != 0
|
|
||||||
LevelPresets[i].Pops = sr.GetInt32()
|
|
||||||
LevelPresets[i].PopPad = sr.GetInt32()
|
|
||||||
sr.GetUInt32() // Most likely NumFiles
|
|
||||||
for fileIdx := 0; fileIdx < 6; fileIdx++ {
|
|
||||||
strData, _ := sr.ReadBytes(60)
|
|
||||||
s := strings.Trim(string(strData), string(0))
|
|
||||||
if s == "0" {
|
|
||||||
LevelPresets[i].Files[fileIdx] = ""
|
|
||||||
} else {
|
|
||||||
LevelPresets[i].Files[fileIdx] = s
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
LevelPresets[i].Dt1Mask = sr.GetUInt32()
|
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 LevelPreset records", len(LevelPresets))
|
log.Printf("Loaded %d level presets", len(LevelPresets))
|
||||||
}
|
}
|
405
Common/Missiles.go
Normal file
405
Common/Missiles.go
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
package Common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/ResourcePaths"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MissileCalcParam struct {
|
||||||
|
Param int
|
||||||
|
Desc string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MissileCalc struct {
|
||||||
|
Calc string
|
||||||
|
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 string // 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 EmptyToZero transform
|
||||||
|
result := MissileRecord{
|
||||||
|
Name: r[inc()],
|
||||||
|
Id: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
ClientMovementFunc: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))),
|
||||||
|
ClientCollisionFunc: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))),
|
||||||
|
ServerMovementFunc: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))),
|
||||||
|
ServerCollisionFunc: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))),
|
||||||
|
ServerDamageFunc: StringToInt(EmptyToZero(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: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
MaxVelocity: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
LevelVelocityBonus: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
Accel: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
Range: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
LevelRangeBonus: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
Light: loadMissileLight(&r, inc),
|
||||||
|
|
||||||
|
Animation: loadMissileAnimation(&r, inc),
|
||||||
|
|
||||||
|
Collision: loadMissileCollision(&r, inc),
|
||||||
|
|
||||||
|
XOffset: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
YOffset: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
ZOffset: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
Size: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
DestroyedByTP: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
DestroyedByTPFrame: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
CanDestroy: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
|
||||||
|
UseAttackRating: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
AlwaysExplode: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
|
||||||
|
ClientExplosion: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
TownSafe: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
IgnoreBossModifiers: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
IgnoreMultishot: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
HolyFilterType: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
CanBeSlowed: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
TriggersHitEvents: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
TriggersGetHit: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
SoftHit: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
KnockbackPercent: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
TransparencyMode: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
UseQuantity: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
AffectedByPierce: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
SpecialSetup: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
|
||||||
|
MissileSkill: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
SkillName: r[inc()],
|
||||||
|
|
||||||
|
ResultFlags: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
HitFlags: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
HitShift: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
ApplyMastery: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
SourceDamage: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
HalfDamageForTwoHander: StringToInt(EmptyToZero(r[inc()])) == 1,
|
||||||
|
SourceMissDamage: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
|
||||||
|
Damage: loadMissileDamage(&r, inc),
|
||||||
|
ElementalDamage: loadMissileElementalDamage(&r, inc),
|
||||||
|
|
||||||
|
HitClass: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
NumDirections: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
LocalBlood: StringToInt(EmptyToZero(r[inc()])),
|
||||||
|
DamageReductionRate: StringToInt(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 FileProvider) {
|
||||||
|
Missiles = make(map[int]*MissileRecord)
|
||||||
|
data := strings.Split(string(fileProvider.LoadFile(ResourcePaths.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: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
Desc: (*r)[inc()],
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMissileCalc(r *[]string, inc func() int, params int) MissileCalc {
|
||||||
|
result := MissileCalc{
|
||||||
|
Calc: (*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: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
Flicker: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
Red: StringToUint8(EmptyToZero((*r)[inc()])),
|
||||||
|
Green: StringToUint8(EmptyToZero((*r)[inc()])),
|
||||||
|
Blue: StringToUint8(EmptyToZero((*r)[inc()])),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMissileAnimation(r *[]string, inc func() int) MissileAnimation {
|
||||||
|
result := MissileAnimation{
|
||||||
|
StepsBeforeVisible: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StepsBeforeActive: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
LoopAnimation: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
CelFileName: (*r)[inc()],
|
||||||
|
AnimationRate: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
AnimationLength: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
AnimationSpeed: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StartingFrame: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
HasSubLoop: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
SubStartingFrame: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
SubEndingFrame: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMissileCollision(r *[]string, inc func() int) MissileCollision {
|
||||||
|
result := MissileCollision{
|
||||||
|
CollisionType: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
DestroyedUponCollision: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
FriendlyFire: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
LastCollide: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
Collision: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
ClientCollision: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
ClientSend: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
UseCollisionTimer: StringToInt(EmptyToZero((*r)[inc()])) == 1,
|
||||||
|
TimerFrames: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMissileDamage(r *[]string, inc func() int) MissileDamage {
|
||||||
|
result := MissileDamage{
|
||||||
|
MinDamage: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
MinLevelDamage: [5]int{
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
},
|
||||||
|
MaxDamage: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
MaxLevelDamage: [5]int{
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
},
|
||||||
|
DamageSynergyPerCalc: (*r)[inc()],
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMissileElementalDamage(r *[]string, inc func() int) MissileElementalDamage {
|
||||||
|
result := MissileElementalDamage{
|
||||||
|
ElementType: (*r)[inc()],
|
||||||
|
Damage: loadMissileDamage(r, inc),
|
||||||
|
Duration: StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
LevelDuration: [3]int{
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
StringToInt(EmptyToZero((*r)[inc()])),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -120,8 +120,7 @@ type ObjectRecord struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateObjectRecord parses a row from objects.txt into an object record
|
// CreateObjectRecord parses a row from objects.txt into an object record
|
||||||
func createObjectRecord(line string) ObjectRecord {
|
func createObjectRecord(props []string) ObjectRecord {
|
||||||
props := strings.Split(line, "\t")
|
|
||||||
i := -1
|
i := -1
|
||||||
inc := func() int {
|
inc := func() int {
|
||||||
i++
|
i++
|
||||||
@ -343,7 +342,11 @@ func LoadObjects(fileProvider FileProvider) {
|
|||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rec := createObjectRecord(line)
|
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
|
Objects[rec.Id] = &rec
|
||||||
}
|
}
|
||||||
log.Printf("Loaded %d objects", len(Objects))
|
log.Printf("Loaded %d objects", len(Objects))
|
||||||
|
@ -9,6 +9,22 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AsterToEmpty converts strings beginning with "*" to "", for use when handling columns where an asterix can be used to comment out entries
|
||||||
|
func AsterToEmpty(text string) string {
|
||||||
|
if strings.HasPrefix(text, "*") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyToZero converts empty strings to "0" and leaves non-empty strings as is, for use before converting numerical data which equates empty to zero
|
||||||
|
func EmptyToZero(text string) string {
|
||||||
|
if text == "" || text == " " {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
// StringToInt converts a string to an integer
|
// StringToInt converts a string to an integer
|
||||||
func StringToInt(text string) int {
|
func StringToInt(text string) int {
|
||||||
result, err := strconv.Atoi(text)
|
result, err := strconv.Atoi(text)
|
||||||
@ -18,6 +34,15 @@ func StringToInt(text string) int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringToUint converts a string to a uint32
|
||||||
|
func StringToUint(text string) uint {
|
||||||
|
result, err := strconv.ParseUint(text, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uint(result)
|
||||||
|
}
|
||||||
|
|
||||||
// StringToUint8 converts a string to an uint8
|
// StringToUint8 converts a string to an uint8
|
||||||
func StringToUint8(text string) uint8 {
|
func StringToUint8(text string) uint8 {
|
||||||
result, err := strconv.Atoi(text)
|
result, err := strconv.Atoi(text)
|
||||||
@ -25,7 +50,7 @@ func StringToUint8(text string) uint8 {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if result < 0 || result > 255 {
|
if result < 0 || result > 255 {
|
||||||
panic("value out of range of byte")
|
panic(fmt.Sprintf("value %d out of range of byte", result))
|
||||||
}
|
}
|
||||||
return uint8(result)
|
return uint8(result)
|
||||||
}
|
}
|
||||||
@ -37,7 +62,7 @@ func StringToInt8(text string) int8 {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if result < -128 || result > 122 {
|
if result < -128 || result > 122 {
|
||||||
panic("value out of range of a signed byte")
|
panic(fmt.Sprintf("value %d out of range of a signed byte", result))
|
||||||
}
|
}
|
||||||
return int8(result)
|
return int8(result)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/PaletteDefs"
|
"github.com/OpenDiablo2/OpenDiablo2/PaletteDefs"
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ type EngineConfig struct {
|
|||||||
type Engine struct {
|
type Engine struct {
|
||||||
Settings *EngineConfig // Engine configuration settings from json file
|
Settings *EngineConfig // Engine configuration settings from json file
|
||||||
Files map[string]*Common.MpqFileRecord // Map that defines which files are in which MPQs
|
Files map[string]*Common.MpqFileRecord // Map that defines which files are in which MPQs
|
||||||
|
CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that.
|
||||||
LoadingSprite *Common.Sprite // The sprite shown when loading stuff
|
LoadingSprite *Common.Sprite // The sprite shown when loading stuff
|
||||||
loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
|
loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
|
||||||
stepLoadingSize float64 // The size for each loading step
|
stepLoadingSize float64 // The size for each loading step
|
||||||
@ -69,6 +71,7 @@ func CreateEngine() *Engine {
|
|||||||
Common.LoadLevelWarps(result)
|
Common.LoadLevelWarps(result)
|
||||||
Common.LoadObjectTypes(result)
|
Common.LoadObjectTypes(result)
|
||||||
Common.LoadObjects(result)
|
Common.LoadObjects(result)
|
||||||
|
Common.LoadMissiles(result)
|
||||||
Common.LoadSounds(result)
|
Common.LoadSounds(result)
|
||||||
result.SoundManager = Sound.CreateManager(result)
|
result.SoundManager = Sound.CreateManager(result)
|
||||||
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
|
result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume)
|
||||||
@ -112,9 +115,11 @@ func (v *Engine) loadConfigurationFile() {
|
|||||||
func (v *Engine) mapMpqFiles() {
|
func (v *Engine) mapMpqFiles() {
|
||||||
log.Println("mapping mpq file structure")
|
log.Println("mapping mpq file structure")
|
||||||
v.Files = make(map[string]*Common.MpqFileRecord)
|
v.Files = make(map[string]*Common.MpqFileRecord)
|
||||||
|
v.CheckedPatch = make(map[string]bool)
|
||||||
for _, mpqFileName := range v.Settings.MpqLoadOrder {
|
for _, mpqFileName := range v.Settings.MpqLoadOrder {
|
||||||
mpqPath := path.Join(v.Settings.MpqPath, mpqFileName)
|
mpqPath := path.Join(v.Settings.MpqPath, mpqFileName)
|
||||||
mpq, err := MPQ.Load(mpqPath)
|
mpq, err := MPQ.Load(mpqPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -126,14 +131,16 @@ func (v *Engine) mapMpqFiles() {
|
|||||||
fileList := strings.Split(string(fileListText), "\r\n")
|
fileList := strings.Split(string(fileListText), "\r\n")
|
||||||
|
|
||||||
for _, filePath := range fileList {
|
for _, filePath := range fileList {
|
||||||
if _, exists := v.Files[strings.ToLower(filePath)]; exists {
|
transFilePath := `/`+strings.ReplaceAll(strings.ToLower(filePath), `\`, `/`)
|
||||||
if v.Files[strings.ToLower(filePath)].IsPatch {
|
if _, exists := v.Files[transFilePath]; exists {
|
||||||
v.Files[strings.ToLower(filePath)].UnpatchedMpqFile = mpqPath
|
if v.Files[transFilePath].IsPatch {
|
||||||
|
v.Files[transFilePath].UnpatchedMpqFile = mpqPath
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v.Files[`/`+strings.ReplaceAll(strings.ToLower(filePath), `\`, `/`)] = &Common.MpqFileRecord{
|
v.Files[transFilePath] = &Common.MpqFileRecord{
|
||||||
mpqPath, false, ""}
|
mpqPath, false, ""}
|
||||||
|
v.CheckedPatch[transFilePath] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,27 +150,61 @@ var mutex sync.Mutex
|
|||||||
// LoadFile loads a file from the specified mpq and returns the data as a byte array
|
// LoadFile loads a file from the specified mpq and returns the data as a byte array
|
||||||
func (v *Engine) LoadFile(fileName string) []byte {
|
func (v *Engine) LoadFile(fileName string) []byte {
|
||||||
fileName = strings.ReplaceAll(fileName, "{LANG}", ResourcePaths.LanguageCode)
|
fileName = strings.ReplaceAll(fileName, "{LANG}", ResourcePaths.LanguageCode)
|
||||||
|
fileName = strings.ReplaceAll(fileName, `\`, `/`)
|
||||||
|
var mpqLookupFileName string
|
||||||
|
if strings.HasPrefix(fileName, "/") || strings.HasPrefix(fileName,"\\") {
|
||||||
|
mpqLookupFileName = strings.ReplaceAll(fileName, `/`, `\`)[1:]
|
||||||
|
} else {
|
||||||
|
mpqLookupFileName = strings.ReplaceAll(fileName, `/`, `\`)
|
||||||
|
}
|
||||||
|
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
// TODO: May want to cache some things if performance becomes an issue
|
// TODO: May want to cache some things if performance becomes an issue
|
||||||
mpqFile := v.Files[strings.ToLower(fileName)]
|
mpqFile := v.Files[strings.ToLower(fileName)]
|
||||||
var mpq MPQ.MPQ
|
var mpq MPQ.MPQ
|
||||||
var err error
|
var err error
|
||||||
if mpqFile == nil {
|
|
||||||
|
// always try to load from patch first
|
||||||
|
checked, checkok := v.CheckedPatch[strings.ToLower(fileName)]
|
||||||
|
patchLoaded := false
|
||||||
|
if !checked || !checkok {
|
||||||
|
patchMpqFilePath := path.Join(v.Settings.MpqPath, v.Settings.MpqLoadOrder[0])
|
||||||
|
mpq, err = MPQ.Load(patchMpqFilePath)
|
||||||
|
if err == nil {
|
||||||
|
// loaded patch mpq. check if this file exists in it
|
||||||
|
fileInPatch := mpq.FileExists(mpqLookupFileName)
|
||||||
|
if fileInPatch {
|
||||||
|
patchLoaded = true
|
||||||
|
// set the path to the patch so it will be loaded there in the future
|
||||||
|
mpqFile = &Common.MpqFileRecord{patchMpqFilePath, false, ""}
|
||||||
|
v.Files[strings.ToLower(fileName)] = mpqFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.CheckedPatch[strings.ToLower(fileName)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if patchLoaded {
|
||||||
|
// if we already loaded the correct mpq from the patch check, don't bother reloading it
|
||||||
|
} else if mpqFile == nil {
|
||||||
// Super secret non-listed file?
|
// Super secret non-listed file?
|
||||||
|
found := false
|
||||||
for _, mpqFile := range v.Settings.MpqLoadOrder {
|
for _, mpqFile := range v.Settings.MpqLoadOrder {
|
||||||
mpqFilePath := path.Join(v.Settings.MpqPath, mpqFile)
|
mpqFilePath := path.Join(v.Settings.MpqPath, mpqFile)
|
||||||
mpq, err = MPQ.Load(mpqFilePath)
|
mpq, err = MPQ.Load(mpqFilePath)
|
||||||
newFileName := strings.ReplaceAll(fileName, `/`, `\`)[1:]
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !mpq.FileExists(newFileName) {
|
if !mpq.FileExists(fileName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// We found the super-secret file!
|
// We found the super-secret file!
|
||||||
|
found = true
|
||||||
v.Files[strings.ToLower(fileName)] = &Common.MpqFileRecord{mpqFilePath, false, ""}
|
v.Files[strings.ToLower(fileName)] = &Common.MpqFileRecord{mpqFilePath, false, ""}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if !found {
|
||||||
|
log.Fatal(fmt.Sprintf("File '%s' not found during preload of listfiles, and could not be located in any MPQ checking manually.", fileName))
|
||||||
|
}
|
||||||
} else if mpqFile.IsPatch {
|
} else if mpqFile.IsPatch {
|
||||||
log.Fatal("Tried to load a patchfile")
|
log.Fatal("Tried to load a patchfile")
|
||||||
} else {
|
} else {
|
||||||
@ -174,13 +215,12 @@ func (v *Engine) LoadFile(fileName string) []byte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName = strings.ReplaceAll(fileName, `/`, `\`)[1:]
|
blockTableEntry, err := mpq.GetFileBlockData(mpqLookupFileName)
|
||||||
blockTableEntry, err := mpq.GetFileBlockData(fileName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error locating block data entry for '%s' in mpq file '%s'", fileName, mpq.FileName)
|
log.Printf("Error locating block data entry for '%s' in mpq file '%s'", mpqLookupFileName, mpq.FileName)
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
mpqStream := MPQ.CreateStream(mpq, blockTableEntry, fileName)
|
mpqStream := MPQ.CreateStream(mpq, blockTableEntry, mpqLookupFileName)
|
||||||
result := make([]byte, blockTableEntry.UncompressedFileSize)
|
result := make([]byte, blockTableEntry.UncompressedFileSize)
|
||||||
mpqStream.Read(result, 0, blockTableEntry.UncompressedFileSize)
|
mpqStream.Read(result, 0, blockTableEntry.UncompressedFileSize)
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
|
BIN
OpenDiablo2.exe
BIN
OpenDiablo2.exe
Binary file not shown.
@ -161,7 +161,7 @@ const (
|
|||||||
ExpansionStringTable = "/data/local/lng/{LANG}/expansionstring.tbl"
|
ExpansionStringTable = "/data/local/lng/{LANG}/expansionstring.tbl"
|
||||||
StringTable = "/data/local/lng/{LANG}/string.tbl"
|
StringTable = "/data/local/lng/{LANG}/string.tbl"
|
||||||
PatchStringTable = "/data/local/lng/{LANG}/patchstring.tbl"
|
PatchStringTable = "/data/local/lng/{LANG}/patchstring.tbl"
|
||||||
LevelPreset = "/data/global/excel/LvlPrest.bin"
|
LevelPreset = "/data/global/excel/LvlPrest.txt"
|
||||||
LevelType = "/data/global/excel/LvlTypes.bin"
|
LevelType = "/data/global/excel/LvlTypes.bin"
|
||||||
ObjectType = "/data/global/excel/objtype.bin"
|
ObjectType = "/data/global/excel/objtype.bin"
|
||||||
LevelWarp = "/data/global/excel/LvlWarp.bin"
|
LevelWarp = "/data/global/excel/LvlWarp.bin"
|
||||||
@ -228,21 +228,21 @@ const (
|
|||||||
|
|
||||||
// --- Sound Effects ---
|
// --- Sound Effects ---
|
||||||
|
|
||||||
SFXButtonClick = "ESOUND_CURSOR_BUTTON_CLICK"
|
SFXButtonClick = "cursor_button_click"
|
||||||
SFXAmazonDeselect = "ESOUND_CURSOR_AMAZON_DESELECT"
|
SFXAmazonDeselect = "cursor_amazon_deselect"
|
||||||
SFXAmazonSelect = "ESOUND_CURSOR_AMAZON_SELECT"
|
SFXAmazonSelect = "cursor_amazon_select"
|
||||||
SFXAssassinDeselect = "/data/global/sfx/Cursor/intro/assassin deselect.wav"
|
SFXAssassinDeselect = "/data/global/sfx/Cursor/intro/assassin deselect.wav"
|
||||||
SFXAssassinSelect = "/data/global/sfx/Cursor/intro/assassin select.wav"
|
SFXAssassinSelect = "/data/global/sfx/Cursor/intro/assassin select.wav"
|
||||||
SFXBarbarianDeselect = "ESOUND_CURSOR_BARBARIAN_DESELECT"
|
SFXBarbarianDeselect = "cursor_barbarian_deselect"
|
||||||
SFXBarbarianSelect = "ESOUND_CURSOR_BARBARIAN_SELECT"
|
SFXBarbarianSelect = "cursor_barbarian_select"
|
||||||
SFXDruidDeselect = "/data/global/sfx/Cursor/intro/druid deselect.wav"
|
SFXDruidDeselect = "/data/global/sfx/Cursor/intro/druid deselect.wav"
|
||||||
SFXDruidSelect = "/data/global/sfx/Cursor/intro/druid select.wav"
|
SFXDruidSelect = "/data/global/sfx/Cursor/intro/druid select.wav"
|
||||||
SFXNecromancerDeselect = "ESOUND_CURSOR_NECROMANCER_DESELECT"
|
SFXNecromancerDeselect = "cursor_necromancer_deselect"
|
||||||
SFXNecromancerSelect = "ESOUND_CURSOR_NECROMANCER_SELECT"
|
SFXNecromancerSelect = "cursor_necromancer_select"
|
||||||
SFXPaladinDeselect = "ESOUND_CURSOR_PALADIN_DESELECT"
|
SFXPaladinDeselect = "cursor_paladin_deselect"
|
||||||
SFXPaladinSelect = "ESOUND_CURSOR_PALADIN_SELECT"
|
SFXPaladinSelect = "cursor_paladin_select"
|
||||||
SFXSorceressDeselect = "ESOUND_CURSOR_SORCERESS_DESELECT"
|
SFXSorceressDeselect = "cursor_sorceress_deselect"
|
||||||
SFXSorceressSelect = "ESOUND_CURSOR_SORCERESS_SELECT"
|
SFXSorceressSelect = "cursor_sorceress_select"
|
||||||
|
|
||||||
// --- Enemy Data ---
|
// --- Enemy Data ---
|
||||||
|
|
||||||
@ -250,5 +250,5 @@ const (
|
|||||||
|
|
||||||
// --- Skill Data ---
|
// --- Skill Data ---
|
||||||
|
|
||||||
Missiles = "/data//global//excel//missiles.txt"
|
Missiles = "/data/global/excel/Missiles.txt"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user