Extract spell data to a TSV

This commit is contained in:
Gleb Mazovetskiy committed 2023-12-05 21:33:23 +01:00
1 parent 2da492d203
commit 124d113b57
13 files changed
+368 -77

No files matched your search

+1
View File
@@ -163,6 +163,7 @@ set(devilutionx_assets
txtdata/monsters/monstdat.tsv
txtdata/monsters/unique_monstdat.tsv
txtdata/sound/effects.tsv
txtdata/spells/spelldat.tsv
ui_art/diablo.pal
ui_art/hellfire.pal
ui_art/creditsw.clx
+41 -1
View File
@@ -1,13 +1,14 @@
#pragma once
#include <charconv>
#include <optional>
#include <ostream>
#include <expected.hpp>
#include <function_ref.hpp>
#include "parser.hpp"
#include "utils/parse_int.hpp"
#include "utils/str_cat.hpp"
#include "utils/str_split.hpp"
namespace devilution {
@@ -157,6 +158,45 @@ public:
return parseIntArray<T>(destination.data(), N);
}
template <typename T, typename ParseFn>
[[nodiscard]] tl::expected<void, std::string> parseEnumArray(T *destination, size_t n, std::optional<T> fillMissing, ParseFn &&parseFn)
{
size_t i = 0;
const std::string_view str = value();
if (!str.empty()) {
for (const std::string_view part : SplitByChar(str, ',')) {
if (i == n)
return tl::make_unexpected(StrCat("Too many values, max: ", n));
auto result = parseFn(part);
if (!result.has_value()) {
return tl::make_unexpected(std::move(result).error());
}
destination[i++] = *result;
}
}
if (i != n) {
if (!fillMissing.has_value()) {
return tl::make_unexpected(StrCat("Too few values, expected ", n, " got ", i));
}
while (i < n) {
destination[i++] = *fillMissing;
}
}
return {};
}
template <typename T, size_t N, typename ParseFn>
[[nodiscard]] tl::expected<void, std::string> parseEnumArray(T (&destination)[N], std::optional<T> fillMissing, ParseFn &&parseFn)
{
return parseEnumArray<T, ParseFn>(destination, N, std::move(fillMissing), std::forward<ParseFn>(parseFn));
}
template <typename T, size_t N, typename ParseFn>
[[nodiscard]] tl::expected<void, std::string> parseIntArray(std::array<T, N> &destination, std::optional<T> fillMissing, ParseFn &&parseFn)
{
return parseEnumArray<T, ParseFn>(destination.data(), N, std::move(fillMissing), std::forward<ParseFn>(parseFn));
}
template <typename T, typename ParseFn>
[[nodiscard]] tl::expected<void, std::string> parseEnumList(T &destination, ParseFn &&parseFn)
{
+21 -16
View File
@@ -28,8 +28,7 @@ public:
typename std::enable_if_t<std::is_integral_v<T>, void>
readInt(std::string_view name, T &out)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
failOnError(field.parseInt(out), name, field);
}
@@ -37,8 +36,7 @@ public:
typename std::enable_if_t<std::is_integral_v<T>, void>
readOptionalInt(std::string_view name, T &out)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
if (field.value().empty()) return;
failOnError(field.parseInt(out), name, field);
}
@@ -46,16 +44,21 @@ public:
template <typename T, size_t N>
void readIntArray(std::string_view name, T (&out)[N])
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
failOnError(field.parseIntArray(out), name, field);
}
template <typename T, size_t N, typename F>
void readEnumArray(std::string_view name, std::optional<T> fillMissing, T (&out)[N], F &&parseFn)
{
DataFileField field = nextField();
failOnError(field.parseEnumArray(out, fillMissing, parseFn), name, field, DataFileField::Error::InvalidValue);
}
template <typename T, size_t N>
void readIntArray(std::string_view name, std::array<T, N> &out)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
failOnError(field.parseIntArray(out), name, field);
}
@@ -63,15 +66,13 @@ public:
typename std::enable_if_t<std::is_integral_v<T>, void>
readFixed6(std::string_view name, T &out)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
failOnError(field.parseFixed6(out), name, field);
}
void readBool(std::string_view name, bool &out)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
failOnError(field.parseBool(out), name, field);
}
@@ -84,8 +85,7 @@ public:
template <typename T, typename F>
void read(std::string_view name, T &out, F &&parseFn)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
tl::expected<T, std::string> result = parseFn(field.value());
failOnError(result, name, field, DataFileField::Error::InvalidValue);
out = *std::move(result);
@@ -94,8 +94,7 @@ public:
template <typename T, typename F>
void readEnumList(std::string_view name, T &out, F &&parseFn)
{
advance();
DataFileField field = *it_;
DataFileField field = nextField();
failOnError(field.parseEnumList(out, std::forward<F>(parseFn)),
name, field, DataFileField::Error::InvalidValue);
}
@@ -109,6 +108,12 @@ public:
void advance();
DataFileField nextField()
{
advance();
return *it_;
}
private:
template <typename T>
void failOnError(const tl::expected<T, DataFileField::Error> &result, std::string_view name, const DataFileField &field)
+1
View File
@@ -2476,6 +2476,7 @@ int DiabloMain(int argc, char **argv)
LoadPlayerDataFiles();
// TODO: We can probably load this much later (when the game is starting).
LoadSpellData();
LoadMissileData();
LoadMonsterData();
LoadItemData();
+183 -58
View File
@@ -5,11 +5,14 @@
*/
#include "spelldat.h"
#include <optional>
#include <string_view>
#include <expected.hpp>
#include "utils/language.h"
#include "data/file.hpp"
#include "data/iterators.hpp"
#include "data/record_reader.hpp"
namespace devilution {
@@ -19,66 +22,158 @@ const auto Lightning = SpellDataFlags::Lightning;
const auto Magic = SpellDataFlags::Magic;
const auto Targeted = SpellDataFlags::Targeted;
const auto AllowedInTown = SpellDataFlags::AllowedInTown;
void AddNullSpell()
{
SpellData &null = SpellsData.emplace_back();
null.sSFX = SfxID::None;
null.bookCost10 = null.staffCost10 = null.sManaCost = 0;
null.flags = SpellDataFlags::Fire;
null.sBookLvl = null.sStaffLvl = 0;
null.minInt = 0;
null.sMissiles[0] = null.sMissiles[1] = MissileID::Null;
null.sManaAdj = null.sMinMana = 0;
null.sStaffMin = 40;
null.sStaffMax = 80;
}
// A temporary solution for parsing soundID until we have a more general one.
tl::expected<SfxID, std::string> ParseSpellSoundId(std::string_view value)
{
if (value == "CastFire") return SfxID::CastFire;
if (value == "CastHealing") return SfxID::CastHealing;
if (value == "CastLightning") return SfxID::CastLightning;
if (value == "CastSkill") return SfxID::CastSkill;
return tl::make_unexpected("Unknown enum value (only a few are supported for now)");
}
tl::expected<SpellDataFlags, std::string> ParseSpellDataFlag(std::string_view value)
{
if (value == "Fire") return SpellDataFlags::Fire;
if (value == "Lightning") return SpellDataFlags::Lightning;
if (value == "Magic") return SpellDataFlags::Magic;
if (value == "Targeted") return SpellDataFlags::Targeted;
if (value == "AllowedInTown") return SpellDataFlags::AllowedInTown;
return tl::make_unexpected("Unknown enum value");
}
tl::expected<MissileID, std::string> ParseMissileId(std::string_view value)
{
if (value == "Arrow") return MissileID::Arrow;
if (value == "Firebolt") return MissileID::Firebolt;
if (value == "Guardian") return MissileID::Guardian;
if (value == "Phasing") return MissileID::Phasing;
if (value == "NovaBall") return MissileID::NovaBall;
if (value == "FireWall") return MissileID::FireWall;
if (value == "Fireball") return MissileID::Fireball;
if (value == "LightningControl") return MissileID::LightningControl;
if (value == "Lightning") return MissileID::Lightning;
if (value == "MagmaBallExplosion") return MissileID::MagmaBallExplosion;
if (value == "TownPortal") return MissileID::TownPortal;
if (value == "FlashBottom") return MissileID::FlashBottom;
if (value == "FlashTop") return MissileID::FlashTop;
if (value == "ManaShield") return MissileID::ManaShield;
if (value == "FlameWave") return MissileID::FlameWave;
if (value == "ChainLightning") return MissileID::ChainLightning;
if (value == "ChainBall") return MissileID::ChainBall;
if (value == "BloodHit") return MissileID::BloodHit;
if (value == "BoneHit") return MissileID::BoneHit;
if (value == "MetalHit") return MissileID::MetalHit;
if (value == "Rhino") return MissileID::Rhino;
if (value == "MagmaBall") return MissileID::MagmaBall;
if (value == "ThinLightningControl") return MissileID::ThinLightningControl;
if (value == "ThinLightning") return MissileID::ThinLightning;
if (value == "BloodStar") return MissileID::BloodStar;
if (value == "BloodStarExplosion") return MissileID::BloodStarExplosion;
if (value == "Teleport") return MissileID::Teleport;
if (value == "FireArrow") return MissileID::FireArrow;
if (value == "DoomSerpents") return MissileID::DoomSerpents;
if (value == "FireOnly") return MissileID::FireOnly;
if (value == "StoneCurse") return MissileID::StoneCurse;
if (value == "BloodRitual") return MissileID::BloodRitual;
if (value == "Invisibility") return MissileID::Invisibility;
if (value == "Golem") return MissileID::Golem;
if (value == "Etherealize") return MissileID::Etherealize;
if (value == "Spurt") return MissileID::Spurt;
if (value == "ApocalypseBoom") return MissileID::ApocalypseBoom;
if (value == "Healing") return MissileID::Healing;
if (value == "FireWallControl") return MissileID::FireWallControl;
if (value == "Infravision") return MissileID::Infravision;
if (value == "Identify") return MissileID::Identify;
if (value == "FlameWaveControl") return MissileID::FlameWaveControl;
if (value == "Nova") return MissileID::Nova;
if (value == "Rage") return MissileID::Rage;
if (value == "Apocalypse") return MissileID::Apocalypse;
if (value == "ItemRepair") return MissileID::ItemRepair;
if (value == "StaffRecharge") return MissileID::StaffRecharge;
if (value == "TrapDisarm") return MissileID::TrapDisarm;
if (value == "Inferno") return MissileID::Inferno;
if (value == "InfernoControl") return MissileID::InfernoControl;
if (value == "FireMan") return MissileID::FireMan;
if (value == "Krull") return MissileID::Krull;
if (value == "ChargedBolt") return MissileID::ChargedBolt;
if (value == "HolyBolt") return MissileID::HolyBolt;
if (value == "Resurrect") return MissileID::Resurrect;
if (value == "Telekinesis") return MissileID::Telekinesis;
if (value == "LightningArrow") return MissileID::LightningArrow;
if (value == "Acid") return MissileID::Acid;
if (value == "AcidSplat") return MissileID::AcidSplat;
if (value == "AcidPuddle") return MissileID::AcidPuddle;
if (value == "HealOther") return MissileID::HealOther;
if (value == "Elemental") return MissileID::Elemental;
if (value == "ResurrectBeam") return MissileID::ResurrectBeam;
if (value == "BoneSpirit") return MissileID::BoneSpirit;
if (value == "WeaponExplosion") return MissileID::WeaponExplosion;
if (value == "RedPortal") return MissileID::RedPortal;
if (value == "DiabloApocalypseBoom") return MissileID::DiabloApocalypseBoom;
if (value == "DiabloApocalypse") return MissileID::DiabloApocalypse;
if (value == "Mana") return MissileID::Mana;
if (value == "Magi") return MissileID::Magi;
if (value == "LightningWall") return MissileID::LightningWall;
if (value == "LightningWallControl") return MissileID::LightningWallControl;
if (value == "Immolation") return MissileID::Immolation;
if (value == "SpectralArrow") return MissileID::SpectralArrow;
if (value == "FireballBow") return MissileID::FireballBow;
if (value == "LightningBow") return MissileID::LightningBow;
if (value == "ChargedBoltBow") return MissileID::ChargedBoltBow;
if (value == "HolyBoltBow") return MissileID::HolyBoltBow;
if (value == "Warp") return MissileID::Warp;
if (value == "Reflect") return MissileID::Reflect;
if (value == "Berserk") return MissileID::Berserk;
if (value == "RingOfFire") return MissileID::RingOfFire;
if (value == "StealPotions") return MissileID::StealPotions;
if (value == "StealMana") return MissileID::StealMana;
if (value == "RingOfLightning") return MissileID::RingOfLightning;
if (value == "Search") return MissileID::Search;
if (value == "Aura") return MissileID::Aura;
if (value == "Aura2") return MissileID::Aura2;
if (value == "SpiralFireball") return MissileID::SpiralFireball;
if (value == "RuneOfFire") return MissileID::RuneOfFire;
if (value == "RuneOfLight") return MissileID::RuneOfLight;
if (value == "RuneOfNova") return MissileID::RuneOfNova;
if (value == "RuneOfImmolation") return MissileID::RuneOfImmolation;
if (value == "RuneOfStone") return MissileID::RuneOfStone;
if (value == "BigExplosion") return MissileID::BigExplosion;
if (value == "HorkSpawn") return MissileID::HorkSpawn;
if (value == "Jester") return MissileID::Jester;
if (value == "OpenNest") return MissileID::OpenNest;
if (value == "OrangeFlare") return MissileID::OrangeFlare;
if (value == "BlueFlare") return MissileID::BlueFlare;
if (value == "RedFlare") return MissileID::RedFlare;
if (value == "YellowFlare") return MissileID::YellowFlare;
if (value == "BlueFlare2") return MissileID::BlueFlare2;
if (value == "YellowExplosion") return MissileID::YellowExplosion;
if (value == "RedExplosion") return MissileID::RedExplosion;
if (value == "BlueExplosion") return MissileID::BlueExplosion;
if (value == "BlueExplosion2") return MissileID::BlueExplosion2;
if (value == "OrangeExplosion") return MissileID::OrangeExplosion;
return tl::make_unexpected("Unknown enum value");
}
} // namespace
/** Data related to each spell ID. */
const SpellData SpellsData[] = {
// clang-format off
// id sNameText, sSFX, bookCost10, staffCost10, sManaCost, flags, sBookLvl, sStaffLvl, minInt, { sMissiles[2] } sManaAdj, sMinMana, sStaffMin, sStaffMax
/*SpellID::Null*/ { nullptr, SfxID::None, 0, 0, 0, Fire, 0, 0, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::Firebolt*/ { P_("spell", "Firebolt"), SfxID::CastFire, 100, 5, 6, Fire | Targeted, 1, 1, 15, { MissileID::Firebolt, MissileID::Null, }, 1, 3, 40, 80 },
/*SpellID::Healing*/ { P_("spell", "Healing"), SfxID::CastHealing, 100, 5, 5, Magic | AllowedInTown, 1, 1, 17, { MissileID::Healing, MissileID::Null, }, 3, 1, 20, 40 },
/*SpellID::Lightning*/ { P_("spell", "Lightning"), SfxID::CastLightning, 300, 15, 10, Lightning | Targeted, 4, 3, 20, { MissileID::LightningControl, MissileID::Null, }, 1, 6, 20, 60 },
/*SpellID::Flash*/ { P_("spell", "Flash"), SfxID::CastLightning, 750, 50, 30, Lightning, 5, 4, 33, { MissileID::FlashBottom, MissileID::FlashTop }, 2, 16, 20, 40 },
/*SpellID::Identify*/ { P_("spell", "Identify"), SfxID::CastSkill, 0, 10, 13, Magic | AllowedInTown, -1, -1, 23, { MissileID::Identify, MissileID::Null, }, 2, 1, 8, 12 },
/*SpellID::FireWall*/ { P_("spell", "Fire Wall"), SfxID::CastFire, 600, 40, 28, Fire | Targeted, 3, 2, 27, { MissileID::FireWallControl, MissileID::Null, }, 2, 16, 8, 16 },
/*SpellID::TownPortal*/ { P_("spell", "Town Portal"), SfxID::CastSkill, 300, 20, 35, Magic | Targeted, 3, 3, 20, { MissileID::TownPortal, MissileID::Null, }, 3, 18, 8, 12 },
/*SpellID::StoneCurse*/ { P_("spell", "Stone Curse"), SfxID::CastFire, 1200, 80, 60, Magic | Targeted, 6, 5, 51, { MissileID::StoneCurse, MissileID::Null, }, 3, 40, 8, 16 },
/*SpellID::Infravision*/ { P_("spell", "Infravision"), SfxID::CastHealing, 0, 60, 40, Magic, -1, -1, 36, { MissileID::Infravision, MissileID::Null, }, 5, 20, 0, 0 },
/*SpellID::Phasing*/ { P_("spell", "Phasing"), SfxID::CastFire, 350, 20, 12, Magic, 7, 6, 39, { MissileID::Phasing, MissileID::Null, }, 2, 4, 40, 80 },
/*SpellID::ManaShield*/ { P_("spell", "Mana Shield"), SfxID::CastFire, 1600, 120, 33, Magic, 6, 5, 25, { MissileID::ManaShield, MissileID::Null, }, 0, 33, 4, 10 },
/*SpellID::Fireball*/ { P_("spell", "Fireball"), SfxID::CastFire, 800, 30, 16, Fire | Targeted, 8, 7, 48, { MissileID::Fireball, MissileID::Null, }, 1, 10, 40, 80 },
/*SpellID::Guardian*/ { P_("spell", "Guardian"), SfxID::CastFire, 1400, 95, 50, Fire | Targeted, 9, 8, 61, { MissileID::Guardian, MissileID::Null, }, 2, 30, 16, 32 },
/*SpellID::ChainLightning*/ { P_("spell", "Chain Lightning"), SfxID::CastFire, 1100, 75, 30, Lightning, 8, 7, 54, { MissileID::ChainLightning, MissileID::Null, }, 1, 18, 20, 60 },
/*SpellID::FlameWave*/ { P_("spell", "Flame Wave"), SfxID::CastFire, 1000, 65, 35, Fire | Targeted, 9, 8, 54, { MissileID::FlameWaveControl, MissileID::Null, }, 3, 20, 20, 40 },
/*SpellID::DoomSerpents*/ { P_("spell", "Doom Serpents"), SfxID::CastFire, 0, 0, 0, Lightning, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::BloodRitual*/ { P_("spell", "Blood Ritual"), SfxID::CastFire, 0, 0, 0, Magic, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::Nova*/ { P_("spell", "Nova"), SfxID::CastLightning, 2100, 130, 60, Magic, 14, 10, 87, { MissileID::Nova, MissileID::Null, }, 3, 35, 16, 32 },
/*SpellID::Invisibility*/ { P_("spell", "Invisibility"), SfxID::CastFire, 0, 0, 0, Magic, -1, -1, 0, { MissileID::Null, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::Inferno*/ { P_("spell", "Inferno"), SfxID::CastFire, 200, 10, 11, Fire | Targeted, 3, 2, 20, { MissileID::InfernoControl, MissileID::Null, }, 1, 6, 20, 40 },
/*SpellID::Golem*/ { P_("spell", "Golem"), SfxID::CastFire, 1800, 110, 100, Fire | Targeted, 11, 9, 81, { MissileID::Golem, MissileID::Null, }, 6, 60, 16, 32 },
/*SpellID::Rage*/ { P_("spell", "Rage"), SfxID::CastHealing, 0, 0, 15, Magic, -1, -1, 0, { MissileID::Rage, MissileID::Null, }, 1, 1, 0, 0 },
/*SpellID::Teleport*/ { P_("spell", "Teleport"), SfxID::CastSkill, 2000, 125, 35, Magic | Targeted, 14, 12, 105, { MissileID::Teleport, MissileID::Null, }, 3, 15, 16, 32 },
/*SpellID::Apocalypse*/ { P_("spell", "Apocalypse"), SfxID::CastFire, 3000, 200, 150, Fire, 19, 15, 149, { MissileID::Apocalypse, MissileID::Null, }, 6, 90, 8, 12 },
/*SpellID::Etherealize*/ { P_("spell", "Etherealize"), SfxID::CastFire, 2600, 160, 100, Magic, -1, -1, 93, { MissileID::Etherealize, MissileID::Null, }, 0, 100, 2, 6 },
/*SpellID::ItemRepair*/ { P_("spell", "Item Repair"), SfxID::CastSkill, 0, 0, 0, Magic | AllowedInTown, -1, -1, 255, { MissileID::ItemRepair, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::StaffRecharge*/ { P_("spell", "Staff Recharge"), SfxID::CastSkill, 0, 0, 0, Magic | AllowedInTown, -1, -1, 255, { MissileID::StaffRecharge, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::TrapDisarm*/ { P_("spell", "Trap Disarm"), SfxID::CastSkill, 0, 0, 0, Magic, -1, -1, 255, { MissileID::TrapDisarm, MissileID::Null, }, 0, 0, 40, 80 },
/*SpellID::Elemental*/ { P_("spell", "Elemental"), SfxID::CastFire, 1050, 70, 35, Fire, 8, 6, 68, { MissileID::Elemental, MissileID::Null, }, 2, 20, 20, 60 },
/*SpellID::ChargedBolt*/ { P_("spell", "Charged Bolt"), SfxID::CastFire, 100, 5, 6, Lightning | Targeted, 1, 1, 25, { MissileID::ChargedBolt, MissileID::Null, }, 1, 6, 40, 80 },
/*SpellID::HolyBolt*/ { P_("spell", "Holy Bolt"), SfxID::CastFire, 100, 5, 7, Magic | Targeted, 1, 1, 20, { MissileID::HolyBolt, MissileID::Null, }, 1, 3, 40, 80 },
/*SpellID::Resurrect*/ { P_("spell", "Resurrect"), SfxID::CastHealing, 400, 25, 20, Magic | AllowedInTown, -1, 5, 30, { MissileID::Resurrect, MissileID::Null, }, 0, 20, 4, 10 },
/*SpellID::Telekinesis*/ { P_("spell", "Telekinesis"), SfxID::CastFire, 250, 20, 15, Magic, 2, 2, 33, { MissileID::Telekinesis, MissileID::Null, }, 2, 8, 20, 40 },
/*SpellID::HealOther*/ { P_("spell", "Heal Other"), SfxID::CastHealing, 100, 5, 5, Magic | AllowedInTown, 1, 1, 17, { MissileID::HealOther, MissileID::Null, }, 3, 1, 20, 40 },
/*SpellID::BloodStar*/ { P_("spell", "Blood Star"), SfxID::CastFire, 2750, 180, 25, Magic, 14, 13, 70, { MissileID::BloodStar, MissileID::Null, }, 2, 14, 20, 60 },
/*SpellID::BoneSpirit*/ { P_("spell", "Bone Spirit"), SfxID::CastFire, 1150, 80, 24, Magic, 9, 7, 34, { MissileID::BoneSpirit, MissileID::Null, }, 1, 12, 20, 60 },
/*SpellID::Mana*/ { P_("spell", "Mana"), SfxID::CastHealing, 100, 5, 255, Magic | AllowedInTown, -1, 5, 17, { MissileID::Mana, MissileID::Null, }, 3, 1, 12, 24 },
/*SpellID::Magi*/ { P_("spell", "the Magi"), SfxID::CastHealing, 10000, 20, 255, Magic | AllowedInTown, -1, 20, 45, { MissileID::Magi, MissileID::Null, }, 3, 1, 15, 30 },
/*SpellID::Jester*/ { P_("spell", "the Jester"), SfxID::CastHealing, 10000, 20, 255, Magic | Targeted, -1, 4, 30, { MissileID::Jester, MissileID::Null, }, 3, 1, 15, 30 },
/*SpellID::LightningWall*/ { P_("spell", "Lightning Wall"), SfxID::CastLightning, 600, 40, 28, Lightning | Targeted, 3, 2, 27, { MissileID::LightningWallControl, MissileID::Null, }, 2, 16, 8, 16 },
/*SpellID::Immolation*/ { P_("spell", "Immolation"), SfxID::CastFire, 2100, 130, 60, Fire, 14, 10, 87, { MissileID::Immolation, MissileID::Null, }, 3, 35, 16, 32 },
/*SpellID::Warp*/ { P_("spell", "Warp"), SfxID::CastSkill, 300, 20, 35, Magic, 3, 3, 25, { MissileID::Warp, MissileID::Null, }, 3, 18, 8, 12 },
/*SpellID::Reflect*/ { P_("spell", "Reflect"), SfxID::CastSkill, 300, 20, 35, Magic, 3, 3, 25, { MissileID::Reflect, MissileID::Null, }, 3, 15, 8, 12 },
/*SpellID::Berserk*/ { P_("spell", "Berserk"), SfxID::CastSkill, 300, 20, 35, Magic | Targeted, 3, 3, 35, { MissileID::Berserk, MissileID::Null, }, 3, 15, 8, 12 },
/*SpellID::RingOfFire*/ { P_("spell", "Ring of Fire"), SfxID::CastFire, 600, 40, 28, Fire, 5, 5, 27, { MissileID::RingOfFire, MissileID::Null, }, 2, 16, 8, 16 },
/*SpellID::Search*/ { P_("spell", "Search"), SfxID::CastSkill, 300, 20, 15, Magic, 1, 3, 25, { MissileID::Search, MissileID::Null, }, 1, 1, 8, 12 },
/*SpellID::RuneOfFire*/ { P_("spell", "Rune of Fire"), SfxID::CastHealing, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfFire, MissileID::Null, }, 1, 10, 40, 80 },
/*SpellID::RuneOfLight*/ { P_("spell", "Rune of Light"), SfxID::CastHealing, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfLight, MissileID::Null, }, 1, 10, 40, 80 },
/*SpellID::RuneOfNova*/ { P_("spell", "Rune of Nova"), SfxID::CastHealing, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfNova, MissileID::Null, }, 1, 10, 40, 80 },
/*SpellID::RuneOfImmolation*/ { P_("spell", "Rune of Immolation"), SfxID::CastHealing, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfImmolation, MissileID::Null, }, 1, 10, 40, 80 },
/*SpellID::RuneOfStone*/ { P_("spell", "Rune of Stone"), SfxID::CastHealing, 800, 30, 255, Magic | Targeted, -1, -1, 48, { MissileID::RuneOfStone, MissileID::Null, }, 1, 10, 40, 80 },
// clang-format on
};
std::vector<SpellData> SpellsData;
tl::expected<SpellID, std::string> ParseSpellId(std::string_view value)
{
@@ -137,4 +232,34 @@ tl::expected<SpellID, std::string> ParseSpellId(std::string_view value)
return tl::make_unexpected("Unknown enum value");
}
void LoadSpellData()
{
SpellsData.clear();
const std::string_view filename = "txtdata\\spells\\spelldat.tsv";
DataFile dataFile = DataFile::loadOrDie(filename);
SpellsData.reserve(dataFile.numRecords() + 1);
AddNullSpell();
dataFile.skipHeaderOrDie(filename);
for (DataFileRecord record : dataFile) {
RecordReader reader { record, filename };
SpellData &item = SpellsData.emplace_back();
reader.advance(); // skip id
reader.readString("name", item.sNameText);
reader.read("soundId", item.sSFX, ParseSpellSoundId);
reader.readInt("bookCost10", item.bookCost10);
reader.readInt("staffCost10", item.staffCost10);
reader.readInt("manaCost", item.sManaCost);
reader.readEnumList("flags", item.flags, ParseSpellDataFlag);
reader.readInt("bookLevel", item.sBookLvl);
reader.readInt("staffLevel", item.sStaffLvl);
reader.readInt("minIntelligence", item.minInt);
reader.readEnumArray("missiles", /*fillMissing=*/std::make_optional(MissileID::Null), item.sMissiles, ParseMissileId);
reader.readInt("manaMultiplier", item.sManaAdj);
reader.readInt("minMana", item.sMinMana);
reader.readInt("staffMin", item.sStaffMin);
reader.readInt("staffMax", item.sStaffMax);
}
SpellsData.shrink_to_fit();
}
} // namespace devilution
+6 -2
View File
@@ -6,8 +6,10 @@
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <expected.hpp>
@@ -221,7 +223,7 @@ enum class SpellDataFlags : uint8_t {
use_enum_as_flags(SpellDataFlags);
struct SpellData {
const char *sNameText;
std::string sNameText;
SfxID sSFX;
uint16_t bookCost10;
uint8_t staffCost10;
@@ -262,11 +264,13 @@ struct SpellData {
}
};
extern const SpellData SpellsData[];
extern std::vector<SpellData> SpellsData;
inline const SpellData &GetSpellData(SpellID spellId)
{
return SpellsData[static_cast<std::underlying_type<SpellID>::type>(spellId)];
}
void LoadSpellData();
} // namespace devilution
+51
View File
@@ -781,3 +781,54 @@ const char *ITEM_SUFFIX_94_NAME = N_("blocking");
const char *ITEM_SUFFIX_95_NAME = N_("devastation");
const char *ITEM_SUFFIX_96_NAME = N_("decay");
const char *ITEM_SUFFIX_97_NAME = N_("peril");
const char *SPELL_FIREBOLT_NAME = P_("spell", "Firebolt");
const char *SPELL_HEALING_NAME = P_("spell", "Healing");
const char *SPELL_LIGHTNING_NAME = P_("spell", "Lightning");
const char *SPELL_FLASH_NAME = P_("spell", "Flash");
const char *SPELL_IDENTIFY_NAME = P_("spell", "Identify");
const char *SPELL_FIRE_WALL_NAME = P_("spell", "Fire Wall");
const char *SPELL_TOWN_PORTAL_NAME = P_("spell", "Town Portal");
const char *SPELL_STONE_CURSE_NAME = P_("spell", "Stone Curse");
const char *SPELL_INFRAVISION_NAME = P_("spell", "Infravision");
const char *SPELL_PHASING_NAME = P_("spell", "Phasing");
const char *SPELL_MANA_SHIELD_NAME = P_("spell", "Mana Shield");
const char *SPELL_FIREBALL_NAME = P_("spell", "Fireball");
const char *SPELL_GUARDIAN_NAME = P_("spell", "Guardian");
const char *SPELL_CHAIN_LIGHTNING_NAME = P_("spell", "Chain Lightning");
const char *SPELL_FLAME_WAVE_NAME = P_("spell", "Flame Wave");
const char *SPELL_DOOM_SERPENTS_NAME = P_("spell", "Doom Serpents");
const char *SPELL_BLOOD_RITUAL_NAME = P_("spell", "Blood Ritual");
const char *SPELL_NOVA_NAME = P_("spell", "Nova");
const char *SPELL_INVISIBILITY_NAME = P_("spell", "Invisibility");
const char *SPELL_INFERNO_NAME = P_("spell", "Inferno");
const char *SPELL_GOLEM_NAME = P_("spell", "Golem");
const char *SPELL_RAGE_NAME = P_("spell", "Rage");
const char *SPELL_TELEPORT_NAME = P_("spell", "Teleport");
const char *SPELL_APOCALYPSE_NAME = P_("spell", "Apocalypse");
const char *SPELL_ETHEREALIZE_NAME = P_("spell", "Etherealize");
const char *SPELL_ITEM_REPAIR_NAME = P_("spell", "Item Repair");
const char *SPELL_STAFF_RECHARGE_NAME = P_("spell", "Staff Recharge");
const char *SPELL_TRAP_DISARM_NAME = P_("spell", "Trap Disarm");
const char *SPELL_ELEMENTAL_NAME = P_("spell", "Elemental");
const char *SPELL_CHARGED_BOLT_NAME = P_("spell", "Charged Bolt");
const char *SPELL_HOLY_BOLT_NAME = P_("spell", "Holy Bolt");
const char *SPELL_RESURRECT_NAME = P_("spell", "Resurrect");
const char *SPELL_TELEKINESIS_NAME = P_("spell", "Telekinesis");
const char *SPELL_HEAL_OTHER_NAME = P_("spell", "Heal Other");
const char *SPELL_BLOOD_STAR_NAME = P_("spell", "Blood Star");
const char *SPELL_BONE_SPIRIT_NAME = P_("spell", "Bone Spirit");
const char *SPELL_MANA_NAME = P_("spell", "Mana");
const char *SPELL_THE_MAGI_NAME = P_("spell", "the Magi");
const char *SPELL_THE_JESTER_NAME = P_("spell", "the Jester");
const char *SPELL_LIGHTNING_WALL_NAME = P_("spell", "Lightning Wall");
const char *SPELL_IMMOLATION_NAME = P_("spell", "Immolation");
const char *SPELL_WARP_NAME = P_("spell", "Warp");
const char *SPELL_REFLECT_NAME = P_("spell", "Reflect");
const char *SPELL_BERSERK_NAME = P_("spell", "Berserk");
const char *SPELL_RING_OF_FIRE_NAME = P_("spell", "Ring of Fire");
const char *SPELL_SEARCH_NAME = P_("spell", "Search");
const char *SPELL_RUNE_OF_FIRE_NAME = P_("spell", "Rune of Fire");
const char *SPELL_RUNE_OF_LIGHT_NAME = P_("spell", "Rune of Light");
const char *SPELL_RUNE_OF_NOVA_NAME = P_("spell", "Rune of Nova");
const char *SPELL_RUNE_OF_IMMOLATION_NAME = P_("spell", "Rune of Immolation");
const char *SPELL_RUNE_OF_STONE_NAME = P_("spell", "Rune of Stone");
+52
View File
@@ -0,0 +1,52 @@
id name soundId bookCost10 staffCost10 manaCost flags bookLevel staffLevel minIntelligence missiles manaMultiplier minMana staffMin staffMax
Firebolt Firebolt CastFire 100 5 6 Fire,Targeted 1 1 15 Firebolt 1 3 40 80
Healing Healing CastHealing 100 5 5 Magic,AllowedInTown 1 1 17 Healing 3 1 20 40
Lightning Lightning CastLightning 300 15 10 Lightning,Targeted 4 3 20 LightningControl 1 6 20 60
Flash Flash CastLightning 750 50 30 Lightning 5 4 33 FlashBottom,FlashTop 2 16 20 40
Identify Identify CastSkill 0 10 13 Magic,AllowedInTown -1 -1 23 Identify 2 1 8 12
FireWall Fire Wall CastFire 600 40 28 Fire,Targeted 3 2 27 FireWallControl 2 16 8 16
TownPortal Town Portal CastSkill 300 20 35 Magic,Targeted 3 3 20 TownPortal 3 18 8 12
StoneCurse Stone Curse CastFire 1200 80 60 Magic,Targeted 6 5 51 StoneCurse 3 40 8 16
Infravision Infravision CastHealing 0 60 40 Magic -1 -1 36 Infravision 5 20 0 0
Phasing Phasing CastFire 350 20 12 Magic 7 6 39 Phasing 2 4 40 80
ManaShield Mana Shield CastFire 1600 120 33 Magic 6 5 25 ManaShield 0 33 4 10
Fireball Fireball CastFire 800 30 16 Fire,Targeted 8 7 48 Fireball 1 10 40 80
Guardian Guardian CastFire 1400 95 50 Fire,Targeted 9 8 61 Guardian 2 30 16 32
ChainLightning Chain Lightning CastFire 1100 75 30 Lightning 8 7 54 ChainLightning 1 18 20 60
FlameWave Flame Wave CastFire 1000 65 35 Fire,Targeted 9 8 54 FlameWaveControl 3 20 20 40
DoomSerpents Doom Serpents CastFire 0 0 0 Lightning -1 -1 0 0 0 40 80
BloodRitual Blood Ritual CastFire 0 0 0 Magic -1 -1 0 0 0 40 80
Nova Nova CastLightning 2100 130 60 Magic 14 10 87 Nova 3 35 16 32
Invisibility Invisibility CastFire 0 0 0 Magic -1 -1 0 0 0 40 80
Inferno Inferno CastFire 200 10 11 Fire,Targeted 3 2 20 InfernoControl 1 6 20 40
Golem Golem CastFire 1800 110 100 Fire,Targeted 11 9 81 Golem 6 60 16 32
Rage Rage CastHealing 0 0 15 Magic -1 -1 0 Rage 1 1 0 0
Teleport Teleport CastSkill 2000 125 35 Magic,Targeted 14 12 105 Teleport 3 15 16 32
Apocalypse Apocalypse CastFire 3000 200 150 Fire 19 15 149 Apocalypse 6 90 8 12
Etherealize Etherealize CastFire 2600 160 100 Magic -1 -1 93 Etherealize 0 100 2 6
ItemRepair Item Repair CastSkill 0 0 0 Magic,AllowedInTown -1 -1 255 ItemRepair 0 0 40 80
StaffRecharge Staff Recharge CastSkill 0 0 0 Magic,AllowedInTown -1 -1 255 StaffRecharge 0 0 40 80
TrapDisarm Trap Disarm CastSkill 0 0 0 Magic -1 -1 255 TrapDisarm 0 0 40 80
Elemental Elemental CastFire 1050 70 35 Fire 8 6 68 Elemental 2 20 20 60
ChargedBolt Charged Bolt CastFire 100 5 6 Lightning,Targeted 1 1 25 ChargedBolt 1 6 40 80
HolyBolt Holy Bolt CastFire 100 5 7 Magic,Targeted 1 1 20 HolyBolt 1 3 40 80
Resurrect Resurrect CastHealing 400 25 20 Magic,AllowedInTown -1 5 30 Resurrect 0 20 4 10
Telekinesis Telekinesis CastFire 250 20 15 Magic 2 2 33 Telekinesis 2 8 20 40
HealOther Heal Other CastHealing 100 5 5 Magic,AllowedInTown 1 1 17 HealOther 3 1 20 40
BloodStar Blood Star CastFire 2750 180 25 Magic 14 13 70 BloodStar 2 14 20 60
BoneSpirit Bone Spirit CastFire 1150 80 24 Magic 9 7 34 BoneSpirit 1 12 20 60
Mana Mana CastHealing 100 5 255 Magic,AllowedInTown -1 5 17 Mana 3 1 12 24
Magi the Magi CastHealing 10000 20 255 Magic,AllowedInTown -1 20 45 Magi 3 1 15 30
Jester the Jester CastHealing 10000 20 255 Magic,Targeted -1 4 30 Jester 3 1 15 30
LightningWall Lightning Wall CastLightning 600 40 28 Lightning,Targeted 3 2 27 LightningWallControl 2 16 8 16
Immolation Immolation CastFire 2100 130 60 Fire 14 10 87 Immolation 3 35 16 32
Warp Warp CastSkill 300 20 35 Magic 3 3 25 Warp 3 18 8 12
Reflect Reflect CastSkill 300 20 35 Magic 3 3 25 Reflect 3 15 8 12
Berserk Berserk CastSkill 300 20 35 Magic,Targeted 3 3 35 Berserk 3 15 8 12
RingOfFire Ring of Fire CastFire 600 40 28 Fire 5 5 27 RingOfFire 2 16 8 16
Search Search CastSkill 300 20 15 Magic 1 3 25 Search 1 1 8 12
RuneOfFire Rune of Fire CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfFire 1 10 40 80
RuneOfLight Rune of Light CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfLight 1 10 40 80
RuneOfNova Rune of Nova CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfNova 1 10 40 80
RuneOfImmolation Rune of Immolation CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfImmolation 1 10 40 80
RuneOfStone Rune of Stone CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfStone 1 10 40 80
1 id name soundId bookCost10 staffCost10 manaCost flags bookLevel staffLevel minIntelligence missiles manaMultiplier minMana staffMin staffMax
2 Firebolt Firebolt CastFire 100 5 6 Fire,Targeted 1 1 15 Firebolt 1 3 40 80
3 Healing Healing CastHealing 100 5 5 Magic,AllowedInTown 1 1 17 Healing 3 1 20 40
4 Lightning Lightning CastLightning 300 15 10 Lightning,Targeted 4 3 20 LightningControl 1 6 20 60
5 Flash Flash CastLightning 750 50 30 Lightning 5 4 33 FlashBottom,FlashTop 2 16 20 40
6 Identify Identify CastSkill 0 10 13 Magic,AllowedInTown -1 -1 23 Identify 2 1 8 12
7 FireWall Fire Wall CastFire 600 40 28 Fire,Targeted 3 2 27 FireWallControl 2 16 8 16
8 TownPortal Town Portal CastSkill 300 20 35 Magic,Targeted 3 3 20 TownPortal 3 18 8 12
9 StoneCurse Stone Curse CastFire 1200 80 60 Magic,Targeted 6 5 51 StoneCurse 3 40 8 16
10 Infravision Infravision CastHealing 0 60 40 Magic -1 -1 36 Infravision 5 20 0 0
11 Phasing Phasing CastFire 350 20 12 Magic 7 6 39 Phasing 2 4 40 80
12 ManaShield Mana Shield CastFire 1600 120 33 Magic 6 5 25 ManaShield 0 33 4 10
13 Fireball Fireball CastFire 800 30 16 Fire,Targeted 8 7 48 Fireball 1 10 40 80
14 Guardian Guardian CastFire 1400 95 50 Fire,Targeted 9 8 61 Guardian 2 30 16 32
15 ChainLightning Chain Lightning CastFire 1100 75 30 Lightning 8 7 54 ChainLightning 1 18 20 60
16 FlameWave Flame Wave CastFire 1000 65 35 Fire,Targeted 9 8 54 FlameWaveControl 3 20 20 40
17 DoomSerpents Doom Serpents CastFire 0 0 0 Lightning -1 -1 0 0 0 40 80
18 BloodRitual Blood Ritual CastFire 0 0 0 Magic -1 -1 0 0 0 40 80
19 Nova Nova CastLightning 2100 130 60 Magic 14 10 87 Nova 3 35 16 32
20 Invisibility Invisibility CastFire 0 0 0 Magic -1 -1 0 0 0 40 80
21 Inferno Inferno CastFire 200 10 11 Fire,Targeted 3 2 20 InfernoControl 1 6 20 40
22 Golem Golem CastFire 1800 110 100 Fire,Targeted 11 9 81 Golem 6 60 16 32
23 Rage Rage CastHealing 0 0 15 Magic -1 -1 0 Rage 1 1 0 0
24 Teleport Teleport CastSkill 2000 125 35 Magic,Targeted 14 12 105 Teleport 3 15 16 32
25 Apocalypse Apocalypse CastFire 3000 200 150 Fire 19 15 149 Apocalypse 6 90 8 12
26 Etherealize Etherealize CastFire 2600 160 100 Magic -1 -1 93 Etherealize 0 100 2 6
27 ItemRepair Item Repair CastSkill 0 0 0 Magic,AllowedInTown -1 -1 255 ItemRepair 0 0 40 80
28 StaffRecharge Staff Recharge CastSkill 0 0 0 Magic,AllowedInTown -1 -1 255 StaffRecharge 0 0 40 80
29 TrapDisarm Trap Disarm CastSkill 0 0 0 Magic -1 -1 255 TrapDisarm 0 0 40 80
30 Elemental Elemental CastFire 1050 70 35 Fire 8 6 68 Elemental 2 20 20 60
31 ChargedBolt Charged Bolt CastFire 100 5 6 Lightning,Targeted 1 1 25 ChargedBolt 1 6 40 80
32 HolyBolt Holy Bolt CastFire 100 5 7 Magic,Targeted 1 1 20 HolyBolt 1 3 40 80
33 Resurrect Resurrect CastHealing 400 25 20 Magic,AllowedInTown -1 5 30 Resurrect 0 20 4 10
34 Telekinesis Telekinesis CastFire 250 20 15 Magic 2 2 33 Telekinesis 2 8 20 40
35 HealOther Heal Other CastHealing 100 5 5 Magic,AllowedInTown 1 1 17 HealOther 3 1 20 40
36 BloodStar Blood Star CastFire 2750 180 25 Magic 14 13 70 BloodStar 2 14 20 60
37 BoneSpirit Bone Spirit CastFire 1150 80 24 Magic 9 7 34 BoneSpirit 1 12 20 60
38 Mana Mana CastHealing 100 5 255 Magic,AllowedInTown -1 5 17 Mana 3 1 12 24
39 Magi the Magi CastHealing 10000 20 255 Magic,AllowedInTown -1 20 45 Magi 3 1 15 30
40 Jester the Jester CastHealing 10000 20 255 Magic,Targeted -1 4 30 Jester 3 1 15 30
41 LightningWall Lightning Wall CastLightning 600 40 28 Lightning,Targeted 3 2 27 LightningWallControl 2 16 8 16
42 Immolation Immolation CastFire 2100 130 60 Fire 14 10 87 Immolation 3 35 16 32
43 Warp Warp CastSkill 300 20 35 Magic 3 3 25 Warp 3 18 8 12
44 Reflect Reflect CastSkill 300 20 35 Magic 3 3 25 Reflect 3 15 8 12
45 Berserk Berserk CastSkill 300 20 35 Magic,Targeted 3 3 35 Berserk 3 15 8 12
46 RingOfFire Ring of Fire CastFire 600 40 28 Fire 5 5 27 RingOfFire 2 16 8 16
47 Search Search CastSkill 300 20 15 Magic 1 3 25 Search 1 1 8 12
48 RuneOfFire Rune of Fire CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfFire 1 10 40 80
49 RuneOfLight Rune of Light CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfLight 1 10 40 80
50 RuneOfNova Rune of Nova CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfNova 1 10 40 80
51 RuneOfImmolation Rune of Immolation CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfImmolation 1 10 40 80
52 RuneOfStone Rune of Stone CastHealing 800 30 255 Magic,Targeted -1 -1 48 RuneOfStone 1 10 40 80
+1
View File
@@ -18,6 +18,7 @@ public:
static void SetUpTestSuite()
{
LoadSpellData();
LoadItemData();
}
};
+2
View File
@@ -414,6 +414,7 @@ public:
static void SetUpTestSuite()
{
LoadSpellData();
LoadItemData();
}
};
@@ -953,6 +954,7 @@ public:
static void SetUpTestSuite()
{
LoadSpellData();
LoadPlayerDataFiles();
LoadMonsterData();
LoadItemData();
+1
View File
@@ -52,6 +52,7 @@ void RunTimedemo(std::string timedemoFolderName)
HeadlessMode = true;
demo::InitPlayBack(demoNumber, true);
LoadSpellData();
LoadPlayerDataFiles();
LoadMissileData();
LoadMonsterData();
+1
View File
@@ -374,6 +374,7 @@ TEST(Writehero, pfile_write_hero)
MyPlayerId = 0;
MyPlayer = &Players[MyPlayerId];
LoadSpellData();
LoadPlayerDataFiles();
LoadItemData();
_uiheroinfo info {};
+7
View File
@@ -10,6 +10,7 @@ itemdat_path = root.joinpath("assets/txtdata/items/itemdat.tsv")
unique_itemdat_path = root.joinpath("assets/txtdata/items/unique_itemdat.tsv")
item_prefixes_path = root.joinpath("assets/txtdata/items/item_prefixes.tsv")
item_suffixes_path = root.joinpath("assets/txtdata/items/item_suffixes.tsv")
spelldat_path = root.joinpath("assets/txtdata/spells/spelldat.tsv")
with open(translation_dummy_path, 'w') as temp_source:
temp_source.write(f'/**\n')
@@ -62,3 +63,9 @@ with open(translation_dummy_path, 'w') as temp_source:
name = row['name']
var_name = f'ITEM_SUFFIX_{i}'
temp_source.write(f'const char *{var_name}_NAME = N_("{name}");\n')
with open(spelldat_path, 'r') as tsv:
reader = csv.DictReader(tsv, delimiter='\t')
for i, row in enumerate(reader):
name = row['name']
var_name = 'SPELL_' + name.upper().replace(' ', '_').replace('-', '_')
temp_source.write(f'const char *{var_name}_NAME = P_("spell", "{name}");\n')