Browse Source

Player Class Flags (#8173)

pull/8177/head
Andrettin 6 months ago committed by GitHub
parent
commit
d87c0dcf8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      Source/data/value_reader.hpp
  2. 7
      Source/inv.cpp
  3. 20
      Source/items.cpp
  4. 3
      Source/objects.cpp
  5. 7
      Source/player.cpp
  6. 10
      Source/playerdat.cpp
  7. 22
      Source/playerdat.hpp
  8. 1
      assets/txtdata/classes/barbarian/attributes.tsv
  9. 1
      assets/txtdata/classes/bard/attributes.tsv
  10. 1
      assets/txtdata/classes/monk/attributes.tsv
  11. 1
      assets/txtdata/classes/rogue/attributes.tsv
  12. 1
      assets/txtdata/classes/sorcerer/attributes.tsv
  13. 1
      assets/txtdata/classes/warrior/attributes.tsv

13
Source/data/value_reader.hpp

@ -46,6 +46,19 @@ public:
});
}
template <typename T, typename F>
void readEnumList(std::string_view expectedKey, T &outValue, F &&parseFn)
{
readValue(expectedKey, outValue, [&parseFn](DataFileField &valueField, T &outValue) -> tl::expected<void, devilution::DataFileField::Error> {
const auto result = valueField.parseEnumList(outValue, std::forward<F>(parseFn));
if (!result.has_value()) {
return tl::make_unexpected(devilution::DataFileField::Error::InvalidValue);
}
return {};
});
}
template <typename T>
typename std::enable_if_t<std::is_integral_v<T>, void>
readInt(std::string_view expectedKey, T &outValue)

7
Source/inv.cpp

@ -207,7 +207,8 @@ bool CanWield(Player &player, const Item &item)
// Bard can dual wield swords and maces, so we allow equiping one-handed weapons in her free slot as long as her occupied
// slot is another one-handed weapon.
if (player._pClass == HeroClass::Bard) {
const ClassAttributes &classAttributes = GetClassAttributes(player._pClass);
if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::DualWield)) {
const bool occupiedHandIsOneHandedSwordOrMace = player.GetItemLocation(occupiedHand) == ILOC_ONEHAND
&& IsAnyOf(occupiedHand._itype, ItemType::Sword, ItemType::Mace);
@ -358,8 +359,10 @@ void ChangeEquippedItem(Player &player, uint8_t slot)
const inv_body_loc selectedHand = slot == SLOTXY_HAND_LEFT ? INVLOC_HAND_LEFT : INVLOC_HAND_RIGHT;
const inv_body_loc otherHand = slot == SLOTXY_HAND_LEFT ? INVLOC_HAND_RIGHT : INVLOC_HAND_LEFT;
const ClassAttributes &classAttributes = GetClassAttributes(player._pClass);
const bool pasteIntoSelectedHand = (player.InvBody[otherHand].isEmpty() || player.InvBody[otherHand]._iClass != player.HoldItem._iClass)
|| (player._pClass == HeroClass::Bard && player.InvBody[otherHand]._iClass == ICLASS_WEAPON && player.HoldItem._iClass == ICLASS_WEAPON);
|| (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::DualWield) && player.InvBody[otherHand]._iClass == ICLASS_WEAPON && player.HoldItem._iClass == ICLASS_WEAPON);
const bool dequipTwoHandedWeapon = (!player.InvBody[otherHand].isEmpty() && player.GetItemLocation(player.InvBody[otherHand]) == ILOC_TWOHAND);

20
Source/items.cpp

@ -2553,14 +2553,14 @@ void CalcPlrDamageMod(Player &player)
switch (player._pClass) {
case HeroClass::Rogue:
player._pDamageMod = strDexMod / 200;
return;
break;
case HeroClass::Monk:
if (player.isHoldingItem(ItemType::Staff) || (leftHandItem.isEmpty() && rightHandItem.isEmpty())) {
player._pDamageMod = strDexMod / 150;
} else {
player._pDamageMod = strDexMod / 300;
}
return;
break;
case HeroClass::Bard:
if (player.isHoldingItem(ItemType::Sword)) {
player._pDamageMod = strDexMod / 150;
@ -2569,7 +2569,7 @@ void CalcPlrDamageMod(Player &player)
} else {
player._pDamageMod = strMod / 100;
}
return;
break;
case HeroClass::Barbarian:
if (player.isHoldingItem(ItemType::Axe) || player.isHoldingItem(ItemType::Mace)) {
player._pDamageMod = strMod / 75;
@ -2586,11 +2586,15 @@ void CalcPlrDamageMod(Player &player)
} else if (!player.isHoldingItem(ItemType::Staff) && !player.isHoldingItem(ItemType::Bow)) {
player._pDamageMod += playerLevel * player._pVitality / 100;
}
player._pIAC += playerLevel / 4;
return;
break;
default:
player._pDamageMod = strMod / 100;
return;
break;
}
const ClassAttributes &classAttributes = GetClassAttributes(player._pClass);
if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::IronSkin)) {
player._pIAC += playerLevel / 4;
}
}
@ -2598,7 +2602,9 @@ void CalcPlrResistances(Player &player, ItemSpecialEffect iflgs, int fire, int l
{
const uint8_t playerLevel = player.getCharacterLevel();
if (player._pClass == HeroClass::Barbarian) {
const ClassAttributes &classAttributes = GetClassAttributes(player._pClass);
if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::NaturalResistance)) {
magic += playerLevel;
fire += playerLevel;
lightning += playerLevel;

3
Source/objects.cpp

@ -4890,7 +4890,8 @@ StringOrView Object::name() const
void GetObjectStr(const Object &object)
{
InfoString = object.name();
if (MyPlayer->_pClass == HeroClass::Rogue) {
const ClassAttributes &classAttributes = GetClassAttributes(MyPlayer->_pClass);
if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::TrapSense)) {
if (object._oTrapFlag) {
InfoString = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} will either be a chest or a door */ "Trapped {:s}")), InfoString.str());
InfoColor = UiFlags::ColorRed;

7
Source/player.cpp

@ -563,7 +563,9 @@ bool PlrHitMonst(Player &player, Monster &monster, bool adjacentDamage = false)
dam += player._pIBonusDamMod;
int dam2 = dam << 6;
dam += player._pDamageMod;
if (player._pClass == HeroClass::Warrior || player._pClass == HeroClass::Barbarian) {
const ClassAttributes &classAttributes = GetClassAttributes(player._pClass);
if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::CriticalStrike)) {
if (GenerateRnd(100) < player.getCharacterLevel()) {
dam *= 2;
}
@ -730,7 +732,8 @@ bool PlrHitPlr(Player &attacker, Player &target)
dam += (dam * attacker._pIBonusDam) / 100;
dam += attacker._pIBonusDamMod + attacker._pDamageMod;
if (attacker._pClass == HeroClass::Warrior || attacker._pClass == HeroClass::Barbarian) {
const ClassAttributes &classAttributes = GetClassAttributes(attacker._pClass);
if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::CriticalStrike)) {
if (GenerateRnd(100) < attacker.getCharacterLevel()) {
dam *= 2;
}

10
Source/playerdat.cpp

@ -156,6 +156,15 @@ void ReloadExperienceData()
}
}
tl::expected<PlayerClassFlag, std::string> ParsePlayerClassFlag(std::string_view value)
{
const std::optional<PlayerClassFlag> enumValueOpt = magic_enum::enum_cast<PlayerClassFlag>(value);
if (enumValueOpt.has_value()) {
return enumValueOpt.value();
}
return tl::make_unexpected("Unknown enum value");
}
void LoadClassData(std::string_view classPath, ClassAttributes &attributes, PlayerCombatData &combat)
{
const std::string filename = StrCat("txtdata\\classes\\", classPath, "\\attributes.tsv");
@ -165,6 +174,7 @@ void LoadClassData(std::string_view classPath, ClassAttributes &attributes, Play
ValueReader reader { dataFile, filename };
reader.readEnumList("classFlags", attributes.classFlags, ParsePlayerClassFlag);
reader.readInt("baseStr", attributes.baseStr);
reader.readInt("baseMag", attributes.baseMag);
reader.readInt("baseDex", attributes.baseDex);

22
Source/playerdat.hpp

@ -27,6 +27,20 @@ enum class HeroClass : uint8_t {
LAST = Barbarian,
};
enum class PlayerClassFlag : uint8_t {
// clang-format off
None = 0,
CriticalStrike = 1 << 0,
DualWield = 1 << 1,
IronSkin = 1 << 2,
NaturalResistance = 1 << 3,
TrapSense = 1 << 4,
Last = TrapSense
// clang-format on
};
use_enum_as_flags(PlayerClassFlag);
struct PlayerData {
/* Class Name */
std::string className;
@ -39,6 +53,8 @@ struct PlayerData {
};
struct ClassAttributes {
/* Class Flags */
PlayerClassFlag classFlags;
/* Class Starting Strength Stat */
uint8_t baseStr;
/* Class Starting Magic Stat */
@ -213,3 +229,9 @@ const PlayerSpriteData &GetPlayerSpriteDataForClass(HeroClass clazz);
const PlayerAnimData &GetPlayerAnimDataForClass(HeroClass clazz);
} // namespace devilution
template <>
struct magic_enum::customize::enum_range<devilution::PlayerClassFlag> {
static constexpr uint8_t min = static_cast<uint64_t>(devilution::PlayerClassFlag::None);
static constexpr uint8_t max = static_cast<uint64_t>(devilution::PlayerClassFlag::Last);
};

1
assets/txtdata/classes/barbarian/attributes.tsv

@ -1,4 +1,5 @@
Attribute Value
classFlags CriticalStrike,IronSkin,NaturalResistance
baseStr 40
baseMag 0
baseDex 20

1 Attribute Value
2 classFlags CriticalStrike,IronSkin,NaturalResistance
3 baseStr 40
4 baseMag 0
5 baseDex 20

1
assets/txtdata/classes/bard/attributes.tsv

@ -1,4 +1,5 @@
Attribute Value
classFlags DualWield
baseStr 20
baseMag 20
baseDex 25

1 Attribute Value
2 classFlags DualWield
3 baseStr 20
4 baseMag 20
5 baseDex 25

1
assets/txtdata/classes/monk/attributes.tsv

@ -1,4 +1,5 @@
Attribute Value
classFlags
baseStr 25
baseMag 15
baseDex 25

1 Attribute Value
2 classFlags
3 baseStr 25
4 baseMag 15
5 baseDex 25

1
assets/txtdata/classes/rogue/attributes.tsv

@ -1,4 +1,5 @@
Attribute Value
classFlags TrapSense
baseStr 20
baseMag 15
baseDex 30

1 Attribute Value
2 classFlags TrapSense
3 baseStr 20
4 baseMag 15
5 baseDex 30

1
assets/txtdata/classes/sorcerer/attributes.tsv

@ -1,4 +1,5 @@
Attribute Value
classFlags
baseStr 15
baseMag 35
baseDex 15

1 Attribute Value
2 classFlags
3 baseStr 15
4 baseMag 35
5 baseDex 15

1
assets/txtdata/classes/warrior/attributes.tsv

@ -1,4 +1,5 @@
Attribute Value
classFlags CriticalStrike
baseStr 30
baseMag 10
baseDex 20

1 Attribute Value
2 classFlags CriticalStrike
3 baseStr 30
4 baseMag 10
5 baseDex 20
Loading…
Cancel
Save