diff --git a/Common/StringUtils.go b/Common/StringUtils.go index 2ee0f78c..1b386931 100644 --- a/Common/StringUtils.go +++ b/Common/StringUtils.go @@ -67,6 +67,15 @@ func StringToInt8(text string) int8 { return int8(result) } +// StringToFloat64 converts a string to a float64 +func StringToFloat64(text string) float64 { + result, err := strconv.ParseFloat(text, 64) + if err != nil { + panic(err) + } + return float64(result) +} + func Utf16BytesToString(b []byte) (string, error) { if len(b)%2 != 0 { diff --git a/Common/Weapons.go b/Common/Weapons.go new file mode 100644 index 00000000..a9290d68 --- /dev/null +++ b/Common/Weapons.go @@ -0,0 +1,282 @@ +package Common + +import ( + "log" + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/ResourcePaths" +) + +type WeaponRecord struct { + Name string + + Type string // base type in ItemTypes.txt + Type2 string + Code string // identifies the item + AlternateGfx string // code of the DCC used when equipped + NameString string // seems to be identical to code? + 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 + + MinDamage int + MaxDamage int + 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 + Speed int // affects movement speed of wielder, >0 = you move slower, <0 = you move faster + StrengthBonus int + DexterityBonus int + // final mindam = min * str / strbonus + min * dex / dexbonus + // same for maxdam + RequiredStrength int + RequiredDexterity int + 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 + MagicLevel int // additional magic level (for gambling?) + AutoPrefix int // prefix automatically assigned to this item on spawn, maps to group column of Automagic.txt + OpenBetaGfx string // unknown + NormalCode string + UberCode string + UltraCode string + + 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 + Component int // corresponds to Composit.txt, player animation layer used by this + HitClass string // determines sounds/graphic effects when attacking + InventoryWidth int + InventoryHeight int + 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 + SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?) + + 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 + + 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 + + SpecialFeature string // Just a comment + + Useable bool // can be used via right click if true + // game knows what to do if used by item code + 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 // seems to be unused? supposed to be whether this can go in your quick access belt + + Quest int // indicates that this item belongs to a given quest? + QuestDifficultyCheck bool // if true, item only works in the difficulty it was found in + + 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 + GemOffset int // unknown + BitField1 int // 1 = leather item, 3 = metal + + Vendors map[string]*WeaponVendorParams // 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 + PermStoreItem bool // if true, vendor will always sell this +} + +type WeaponVendorParams struct { + Min int // minimum of this item they can stock + Max int // max they can stock + MagicMin int + MagicMax int + MagicLevel uint8 +} + +func createWeaponRecord(line string) WeaponRecord { + r := strings.Split(line, "\t") + i := -1 + inc := func() int { + i++ + return i + } + result := WeaponRecord{ + Name: r[inc()], + + Type: r[inc()], + Type2: r[inc()], + Code: r[inc()], + AlternateGfx: r[inc()], + NameString: r[inc()], + Version: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + CompactSave : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + Rarity: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Spawnable : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + + MinDamage: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + MaxDamage: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + BarbOneOrTwoHanded : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + UsesTwoHands : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + Min2HandDamage: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Max2HandDamage: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + MinMissileDamage: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + MaxMissileDamage: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + MissileSpeed: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + ExtraRange: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Speed: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + StrengthBonus: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + DexterityBonus: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + + + RequiredStrength: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + RequiredDexterity: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Durability: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + NoDurability : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + + Level: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + RequiredLevel: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Cost: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + GambleCost: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + MagicLevel: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + AutoPrefix: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + OpenBetaGfx: r[inc()], + NormalCode: r[inc()], + UberCode: r[inc()], + UltraCode: r[inc()], + + WeaponClass: r[inc()], + WeaponClass2Hand: r[inc()], + Component: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + HitClass: r[inc()], + InventoryWidth: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + InventoryHeight: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Stackable : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + MinStack: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + MaxStack: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + SpawnStack: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + + FlippyFile: r[inc()], + InventoryFile: r[inc()], + UniqueInventoryFile: r[inc()], + SetInventoryFile: r[inc()], + + HasInventory : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + GemSockets: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + GemApplyType: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + + + SpecialFeature: r[inc()], + + Useable : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + + DropSound: r[inc()], + DropSfxFrame: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + UseSound: r[inc()], + + Unique : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + Transparent : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + TransTable: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Quivered : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + LightRadius: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + Belt : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + + Quest: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + QuestDifficultyCheck : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + + MissileType: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + DurabilityWarning: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + QuantityWarning: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + GemOffset: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + BitField1: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + + Vendors: createWeaponVendorParams(&r, inc), + + SourceArt: r[inc()], + GameArt: r[inc()], + ColorTransform: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + InventoryColorTransform: StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))), + + SkipName : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + NightmareUpgrade: r[inc()], + HellUpgrade: r[inc()], + + Nameable : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + PermStoreItem : StringToInt(EmptyToZero(AsterToEmpty(r[inc()]))) == 1, + } + return result +} + +func createWeaponVendorParams(r *[]string, inc func() int) map[string]*WeaponVendorParams { + result := make(map[string]*WeaponVendorParams) + 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] = "Drehya" + vs[16] = "Malah" + + for _, name := range vs { + wvp := WeaponVendorParams{ + Min: StringToInt(EmptyToZero((*r)[inc()])), + Max: StringToInt(EmptyToZero((*r)[inc()])), + MagicMin: StringToInt(EmptyToZero((*r)[inc()])), + MagicMax: StringToInt(EmptyToZero((*r)[inc()])), + MagicLevel: StringToUint8(EmptyToZero((*r)[inc()])), + } + result[name] = &wvp; + } + return result +} + +var Weapons map[string]*WeaponRecord + +func LoadWeapons(fileProvider FileProvider) { + Weapons = make(map[string]*WeaponRecord) + data := strings.Split(string(fileProvider.LoadFile(ResourcePaths.Weapons)), "\r\n")[1:] + for _, line := range data { + if len(line) == 0 { + continue + } + rec := createWeaponRecord(line) + Weapons[rec.Code] = &rec + } + log.Printf("Loaded %d weapons", len(Weapons)) +} \ No newline at end of file diff --git a/Core/Engine.go b/Core/Engine.go index c1e48770..d0d64e5d 100644 --- a/Core/Engine.go +++ b/Core/Engine.go @@ -71,6 +71,7 @@ func CreateEngine() *Engine { Common.LoadLevelWarps(result) Common.LoadObjectTypes(result) Common.LoadObjects(result) + Common.LoadWeapons(result) Common.LoadMissiles(result) Common.LoadSounds(result) result.SoundManager = Sound.CreateManager(result)