From 3d4e86392c5b43051eb1659a7cf44f5d54ad6a5d Mon Sep 17 00:00:00 2001 From: ephphatha Date: Wed, 30 Aug 2023 23:50:28 +1000 Subject: [PATCH] load starting loadout dynamically --- Source/items.cpp | 58 ++++++++++++++++++++++---------------------- Source/player.cpp | 21 ++-------------- Source/playerdat.cpp | 31 +++++++++++++++++------ Source/playerdat.hpp | 31 +++++++++++++++++++++++ 4 files changed, 86 insertions(+), 55 deletions(-) diff --git a/Source/items.cpp b/Source/items.cpp index 0292b0742e32..9d49391e1d19 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -3003,40 +3003,40 @@ void CreatePlrItems(Player &player) item.clear(); } - switch (player._pClass) { - case HeroClass::Warrior: - for (_item_indexes itemData : { IDI_WARRIOR, IDI_WARRSHLD, IDI_WARRCLUB, IDI_HEAL, IDI_HEAL }) - CreateStartingItem(player, itemData); - break; - case HeroClass::Rogue: - for (_item_indexes itemData : { IDI_ROGUE, IDI_HEAL, IDI_HEAL }) - CreateStartingItem(player, itemData); - break; - case HeroClass::Sorcerer: - for (_item_indexes itemData : { gbIsHellfire ? IDI_SORCERER : IDI_SORCERER_DIABLO, gbIsHellfire ? IDI_HEAL : IDI_MANA, gbIsHellfire ? IDI_HEAL : IDI_MANA }) - CreateStartingItem(player, itemData); - break; - case HeroClass::Monk: - for (_item_indexes itemData : { IDI_SHORTSTAFF, IDI_HEAL, IDI_HEAL }) - CreateStartingItem(player, itemData); - break; - case HeroClass::Bard: - for (_item_indexes itemData : { IDI_BARDSWORD, IDI_BARDDAGGER, IDI_HEAL, IDI_HEAL }) - CreateStartingItem(player, itemData); - break; - case HeroClass::Barbarian: - for (_item_indexes itemData : { IDI_BARBARIAN, IDI_WARRSHLD, IDI_HEAL, IDI_HEAL }) + const PlayerStartingLoadoutData &loadout = GetPlayerStartingLoadoutForClass(player._pClass); + + if (loadout.spell != SpellID::Null && loadout.spellLevel > 0) { + player._pMemSpells = GetSpellBitmask(loadout.spell); + player._pRSplType = SpellType::Spell; + player._pRSpell = loadout.spell; + player._pSplLvl[static_cast(loadout.spell)] = loadout.spellLevel; + } else { + player._pMemSpells = 0; + } + + if (loadout.skill != SpellID::Null) { + player._pAblSpells = GetSpellBitmask(loadout.skill); + if (player._pRSplType == SpellType::Invalid) { + player._pRSplType = SpellType::Skill; + player._pRSpell = loadout.skill; + } + } + + for (auto &itemChoice : loadout.items) { + _item_indexes itemData = gbIsHellfire && itemChoice.hellfire != _item_indexes::IDI_NONE ? itemChoice.hellfire : itemChoice.diablo; + if (itemData != _item_indexes::IDI_NONE) CreateStartingItem(player, itemData); - break; } - Item &goldItem = player.InvList[player._pNumInv]; - MakeGoldStack(goldItem, 100); + if (loadout.gold > 0) { + Item &goldItem = player.InvList[player._pNumInv]; + MakeGoldStack(goldItem, loadout.gold); - player._pNumInv++; - player.InvGrid[30] = player._pNumInv; + player._pNumInv++; + player.InvGrid[30] = player._pNumInv; - player._pGold = goldItem._ivalue; + player._pGold = goldItem._ivalue; + } CalcPlrItemVals(player, false); } diff --git a/Source/player.cpp b/Source/player.cpp index 5e43fe126a9b..d6b0f4bba823 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2310,28 +2310,12 @@ void CreatePlayer(Player &player, HeroClass c) player._pLightRad = 10; player._pInfraFlag = false; - player._pRSplType = SpellType::Skill; - SpellID s = player.getPlayerData().skill; - player._pAblSpells = GetSpellBitmask(s); - player._pRSpell = s; - - if (c == HeroClass::Sorcerer) { - player._pMemSpells = GetSpellBitmask(SpellID::Firebolt); - player._pRSplType = SpellType::Spell; - player._pRSpell = SpellID::Firebolt; - } else { - player._pMemSpells = 0; - } - for (uint8_t &spellLevel : player._pSplLvl) { spellLevel = 0; } player._pSpellFlags = SpellFlag::None; - - if (player._pClass == HeroClass::Sorcerer) { - player._pSplLvl[static_cast(SpellID::Firebolt)] = 2; - } + player._pRSplType = SpellType::Invalid; // Initializing the hotkey bindings to no selection std::fill(player._pSplHotKey, player._pSplHotKey + NumHotkeys, SpellID::Invalid); @@ -2517,8 +2501,7 @@ void InitPlayer(Player &player, bool firstTime) ActivateVision(player.position.tile, player._pLightRad, player.getId()); } - SpellID s = player.getPlayerData().skill; - player._pAblSpells = GetSpellBitmask(s); + player._pAblSpells = GetSpellBitmask(GetPlayerStartingLoadoutForClass(player._pClass).skill); player._pInvincible = false; diff --git a/Source/playerdat.cpp b/Source/playerdat.cpp index 328a6b920f55..4414b2b913a7 100644 --- a/Source/playerdat.cpp +++ b/Source/playerdat.cpp @@ -245,18 +245,30 @@ void LoadClassesAttributes() /** Contains the data related to each player class. */ const PlayerData PlayersData[] = { // clang-format off -// HeroClass className, skill +// HeroClass className // TRANSLATORS: Player Block start -/* HeroClass::Warrior */ { N_("Warrior"), SpellID::ItemRepair }, -/* HeroClass::Rogue */ { N_("Rogue"), SpellID::TrapDisarm }, -/* HeroClass::Sorcerer */ { N_("Sorcerer"), SpellID::StaffRecharge }, -/* HeroClass::Monk */ { N_("Monk"), SpellID::Search }, -/* HeroClass::Bard */ { N_("Bard"), SpellID::Identify }, +/* HeroClass::Warrior */ { N_("Warrior"), }, +/* HeroClass::Rogue */ { N_("Rogue"), }, +/* HeroClass::Sorcerer */ { N_("Sorcerer"), }, +/* HeroClass::Monk */ { N_("Monk"), }, +/* HeroClass::Bard */ { N_("Bard"), }, // TRANSLATORS: Player Block end -/* HeroClass::Barbarian */ { N_("Barbarian"), SpellID::Rage }, +/* HeroClass::Barbarian */ { N_("Barbarian"), }, // clang-format on }; +const std::array::value> PlayersStartingLoadoutData { { + // clang-format off +// HeroClass skill, spell, spellLevel, items[0].diablo, items[0].hellfire, items[1].diablo, items[1].hellfire, items[2].diablo, items[2].hellfire, items[3].diablo, items[3].hellfire, items[4].diablo, items[4].hellfire, gold, +/* HeroClass::Warrior */ { SpellID::ItemRepair, SpellID::Null, 0, { { { IDI_WARRIOR, IDI_WARRIOR, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_WARRCLUB, IDI_WARRCLUB, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, }, }, 100, }, +/* HeroClass::Rogue */ { SpellID::TrapDisarm, SpellID::Null, 0, { { { IDI_ROGUE, IDI_ROGUE, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Sorcerer */ { SpellID::StaffRecharge, SpellID::Fireball, 2, { { { IDI_SORCERER_DIABLO, IDI_SORCERER, }, { IDI_MANA, IDI_HEAL, }, { IDI_MANA, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Monk */ { SpellID::Search, SpellID::Null, 0, { { { IDI_SHORTSTAFF, IDI_SHORTSTAFF, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Bard */ { SpellID::Identify, SpellID::Null, 0, { { { IDI_BARDSWORD, IDI_BARDSWORD, }, { IDI_BARDDAGGER, IDI_BARDDAGGER, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, }, +/* HeroClass::Barbarian */ { SpellID::Rage, SpellID::Null, 0, { { { IDI_BARBARIAN, IDI_BARBARIAN, }, { IDI_WARRSHLD, IDI_WARRSHLD, }, { IDI_HEAL, IDI_HEAL, }, { IDI_HEAL, IDI_HEAL, }, { IDI_NONE, IDI_NONE, }, }, }, 100, } + // clang-format on +} }; + } // namespace const ClassAttributes &GetClassAttributes(HeroClass playerClass) @@ -301,6 +313,11 @@ const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz) return PlayersCombatData[static_cast(clazz)]; } +const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz) +{ + return PlayersStartingLoadoutData[static_cast(clazz)]; +} + /** Contains the data related to each player class. */ const PlayerSpriteData PlayersSpriteData[] = { // clang-format off diff --git a/Source/playerdat.hpp b/Source/playerdat.hpp index 4a4e829432db..86bcd689d4de 100644 --- a/Source/playerdat.hpp +++ b/Source/playerdat.hpp @@ -8,6 +8,7 @@ #include #include "effects.h" +#include "itemdat.h" #include "spelldat.h" namespace devilution { @@ -78,6 +79,35 @@ struct PlayerCombatData { uint8_t baseMagicToHit; }; +/** + * @brief Data used to set known skills and provide initial equipment when starting a new game + * + * Items will be created in order starting with item 1, 2, etc. If the item can be equipped it + * will be placed in the first available slot, otherwise if it fits on the belt it will be + * placed in the first free space, finally being placed in the first free inventory position. + * + * The active game mode at the time we're creating a new character controls the choice of item + * type. ItemType.hellfire is used if we're in Hellfire mode, ItemType.diablo otherwise. + */ +struct PlayerStartingLoadoutData { + /* Class Skill */ + SpellID skill; + /* Starting Spell (if any) */ + SpellID spell; + /* Initial level of the starting spell */ + uint8_t spellLevel; + + struct ItemType { + _item_indexes diablo; + _item_indexes hellfire; + }; + + std::array items; + + /* Initial gold amount, up to a single stack (5000 gold) */ + uint16_t gold; +}; + struct PlayerSpriteData { /* Class Directory Path */ const char *classPath; @@ -170,6 +200,7 @@ uint32_t GetNextExperienceThresholdForLevel(unsigned level); uint8_t GetMaximumCharacterLevel(); const PlayerData &GetPlayerDataForClass(HeroClass clazz); const PlayerCombatData &GetPlayerCombatDataForClass(HeroClass clazz); +const PlayerStartingLoadoutData &GetPlayerStartingLoadoutForClass(HeroClass clazz); extern const PlayerSpriteData PlayersSpriteData[]; extern const PlayerAnimData PlayersAnimData[];