mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-05 19:14:14 -04:00
406 lines
15 KiB
Go
406 lines
15 KiB
Go
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
|
|
}
|