Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 133 additions & 152 deletions Source/items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@
Point position = GetRandomAvailableItemPosition();
item.position = position;

dItem[position.x][position.y] = ii + 1;

Check warning on line 462 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:462:35 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int8_t' (aka 'signed char') is implementation-defined

item._iSeed = AdvanceRndSeed();
SetRndSeed(item._iSeed);
Expand All @@ -468,7 +468,7 @@

item._iCreateInfo = curlv | CF_PREGEN;
SetupItem(item);
item.AnimInfo.currentFrame = item.AnimInfo.numberOfFrames - 1;

Check warning on line 471 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:471:32 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int8_t' (aka 'signed char') is implementation-defined
item._iAnimFlag = false;
item.selectionRegion = SelectionRegion::Bottom;
DeltaAddItem(ii);
Expand Down Expand Up @@ -590,7 +590,7 @@
xx += position.x - 1;
yy += position.y - 1;
Items[inum].position = { xx, yy };
dItem[xx][yy] = inum + 1;

Check warning on line 593 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:593:18 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int8_t' (aka 'signed char') is implementation-defined

return true;
}
Expand Down Expand Up @@ -650,8 +650,8 @@
item._iSpell = bs;
const SpellData &spellData = GetSpellData(bs);
item._iMinMag = spellData.minInt;
item._ivalue += spellData.bookCost();

Check warning on line 653 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:653:18 [bugprone-narrowing-conversions]

narrowing conversion from 'uint32_t' (aka 'unsigned int') to signed type 'int' is implementation-defined
item._iIvalue += spellData.bookCost();

Check warning on line 654 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:654:19 [bugprone-narrowing-conversions]

narrowing conversion from 'uint32_t' (aka 'unsigned int') to signed type 'int' is implementation-defined
switch (spellData.type()) {
case MagicType::Fire:
item._iCurs = ICURS_BOOK_RED;
Expand Down Expand Up @@ -700,7 +700,7 @@
}
}

int SaveItemPower(const Player &player, Item &item, ItemPower &power)

Check warning on line 703 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:703:5 [readability-function-cognitive-complexity]

function 'SaveItemPower' has cognitive complexity of 29 (threshold 25)
{
if (!gbIsHellfire) {
if (power.type == IPL_TARGAC) {
Expand All @@ -713,16 +713,16 @@

switch (power.type) {
case IPL_TOHIT:
item._iPLToHit += r;

Check warning on line 716 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:716:21 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int16_t' (aka 'short') is implementation-defined
break;
case IPL_TOHIT_CURSE:
item._iPLToHit -= r;

Check warning on line 719 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:719:21 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int16_t' (aka 'short') is implementation-defined
break;
case IPL_DAMP:
item._iPLDam += r;

Check warning on line 722 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:722:19 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int16_t' (aka 'short') is implementation-defined
break;
case IPL_DAMP_CURSE:
item._iPLDam -= r;

Check warning on line 725 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / tidy-check

Source/items.cpp:725:19 [bugprone-narrowing-conversions]

narrowing conversion from 'int' to signed type 'int16_t' (aka 'short') is implementation-defined
break;
case IPL_DOPPELGANGER:
item._iDamAcFlags |= ItemSpecialEffectHf::Doppelganger;
Expand Down Expand Up @@ -1942,7 +1942,88 @@
return RndVendorItem<PremiumItemOk>(player, minlvl, maxlvl);
}

void SpawnOnePremium(Item &premiumItem, int plvl, const Player &player)
bool IsItemTypeAllowedForClass(ItemType type, HeroClass pc)
{
switch (pc) {
case HeroClass::Warrior:
return !IsAnyOf(type, ItemType::Bow, ItemType::Staff);
case HeroClass::Rogue:
return !IsAnyOf(type, ItemType::Sword, ItemType::Staff, ItemType::Axe, ItemType::Mace, ItemType::Shield);
case HeroClass::Sorcerer:
return !IsAnyOf(type, ItemType::Staff, ItemType::Axe, ItemType::Bow, ItemType::Mace);
case HeroClass::Monk:
return !IsAnyOf(type, ItemType::Bow, ItemType::MediumArmor, ItemType::Shield, ItemType::Mace);
case HeroClass::Bard:
return !IsAnyOf(type, ItemType::Axe, ItemType::Mace, ItemType::Staff);
case HeroClass::Barbarian:
return !IsAnyOf(type, ItemType::Bow, ItemType::Staff);
default:
return true;
}
}

bool IsVendorItemValid(const Item &item, int count, int strictCount, const Player &player, int strength, int magic, int dexterity, int maxValue, bool checkClass)
{
if (!gbIsHellfire) {
return item._iIvalue <= maxValue;
} else {
int itemValue = 0;
ItemType itemType = item._itype;
switch (itemType) {
case ItemType::LightArmor:
case ItemType::MediumArmor:
case ItemType::HeavyArmor: {
const auto *const mostValuableArmor = player.GetMostValuableItem(
[](const Item &it) {
return IsAnyOf(it._itype, ItemType::LightArmor, ItemType::MediumArmor, ItemType::HeavyArmor);
});
itemValue = mostValuableArmor ? mostValuableArmor->_iIvalue : 0;
break;
}
case ItemType::Shield:
case ItemType::Axe:
case ItemType::Bow:
case ItemType::Mace:
case ItemType::Sword:
case ItemType::Helm:
case ItemType::Staff:
case ItemType::Ring:
case ItemType::Amulet: {
const auto *const mostValuableItem = player.GetMostValuableItem(
[itemType](const Item &it) { return it._itype == itemType; });
itemValue = mostValuableItem ? mostValuableItem->_iIvalue : 0;
break;
}
default:
app_fatal("Invalid item spawn");
}
itemValue = itemValue * 4 / 5;

if (count < strictCount) {
if (gbIsHellfire && checkClass) {
if (!IsItemTypeAllowedForClass(itemType, player._pClass))
return false;
}
return (item._iIvalue <= maxValue && item._iMinStr <= strength && item._iMinMag <= magic && item._iMinDex <= dexterity && item._iIvalue >= itemValue);
} else {
return item._iIvalue <= maxValue;
}
}
}

Item GeneratePremiumItem(int plvl, const Player &player)
{
Item item {};
item._iSeed = AdvanceRndSeed();
SetRndSeed(item._iSeed);

_item_indexes itemType = RndPremiumItem(player, plvl / 4, plvl);
GetItemAttrs(item, itemType, plvl);
GetItemBonus(player, item, plvl / 2, plvl, true, !gbIsHellfire);
return item;
}

void SpawnOnePremium(Item &item, int plvl, const Player &player)
{
int strength = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Strength), player._pStrength);
int dexterity = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Dexterity), player._pDexterity);
Expand All @@ -1953,66 +2034,25 @@

plvl = std::clamp(plvl, 1, 30);

int maxCount = 150;
const bool unlimited = !gbIsHellfire; // TODO: This could lead to an infinite loop if a suitable item can never be generated
for (int count = 0; unlimited || count < maxCount; count++) {
premiumItem = {};
premiumItem._iSeed = AdvanceRndSeed();
SetRndSeed(premiumItem._iSeed);
_item_indexes itemType = RndPremiumItem(player, plvl / 4, plvl);
GetItemAttrs(premiumItem, itemType, plvl);
GetItemBonus(player, premiumItem, plvl / 2, plvl, true, !gbIsHellfire);

if (!gbIsHellfire) {
if (premiumItem._iIvalue <= MaxVendorValue) {
break;
}
} else {
int itemValue = 0;
switch (premiumItem._itype) {
case ItemType::LightArmor:
case ItemType::MediumArmor:
case ItemType::HeavyArmor: {
const auto *const mostValuablePlayerArmor = player.GetMostValuableItem(
[](const Item &item) {
return IsAnyOf(item._itype, ItemType::LightArmor, ItemType::MediumArmor, ItemType::HeavyArmor);
});

itemValue = mostValuablePlayerArmor == nullptr ? 0 : mostValuablePlayerArmor->_iIvalue;
break;
}
case ItemType::Shield:
case ItemType::Axe:
case ItemType::Bow:
case ItemType::Mace:
case ItemType::Sword:
case ItemType::Helm:
case ItemType::Staff:
case ItemType::Ring:
case ItemType::Amulet: {
const auto *const mostValuablePlayerItem = player.GetMostValuableItem(
[filterType = premiumItem._itype](const Item &item) { return item._itype == filterType; });

itemValue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue;
break;
}
default:
itemValue = 0;
break;
}
itemValue = itemValue * 4 / 5; // avoids forced int > float > int conversion
if (premiumItem._iIvalue <= MaxVendorValueHf
&& premiumItem._iMinStr <= strength
&& premiumItem._iMinMag <= magic
&& premiumItem._iMinDex <= dexterity
&& premiumItem._iIvalue >= itemValue) {
break;
}
}
constexpr int strictCount = 150;
constexpr int maxTotalTries = 300;
int count = 0;
int maxValue = gbIsHellfire ? MaxVendorValueHf : MaxVendorValue;

for (; count < maxTotalTries; count++) {
item = GeneratePremiumItem(plvl, player);
if (IsVendorItemValid(item, count, strictCount, player, strength, magic, dexterity, maxValue, false))
break;
}

if (count == maxTotalTries) {
item = {};
return;
}
premiumItem._iCreateInfo = plvl | CF_SMITHPREMIUM;
premiumItem._iIdentified = true;
premiumItem._iStatFlag = player.CanUseItem(premiumItem);

item._iCreateInfo = plvl | CF_SMITHPREMIUM;
item._iIdentified = true;
item._iStatFlag = player.CanUseItem(item);
}

bool WitchItemOk(const Player &player, const ItemData &item)
Expand Down Expand Up @@ -4515,115 +4555,56 @@
SortVendor(WitchItems + PinnedItemCount, itemCount - PinnedItemCount);
}

void SpawnBoy(int lvl)
namespace {

Item GenerateBoyItem(int lvl, const Player &player)
{
int ivalue = 0;
bool keepgoing = false;
int count = 0;
Item item = {};

Player &myPlayer = *MyPlayer;
item._iSeed = AdvanceRndSeed();
SetRndSeed(item._iSeed);

_item_indexes itype = RndBoyItem(player, lvl);

HeroClass pc = myPlayer._pClass;
GetItemAttrs(item, itype, lvl);
GetItemBonus(player, item, lvl, 2 * lvl, true, true);

return item;
}

} // namespace

void SpawnBoy(int lvl)
{
Player &myPlayer = *MyPlayer;
int strength = std::max(myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength), myPlayer._pStrength);
int dexterity = std::max(myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity), myPlayer._pDexterity);
int magic = std::max(myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic), myPlayer._pMagic);
strength += strength / 5;
dexterity += dexterity / 5;
magic += magic / 5;

lvl = std::clamp(lvl, 1, static_cast<int>(GetMaximumCharacterLevel()));

if (BoyItemLevel >= (lvl / 2) && !BoyItem.isEmpty())
return;
do {
keepgoing = false;
BoyItem = {};
BoyItem._iSeed = AdvanceRndSeed();
SetRndSeed(BoyItem._iSeed);
_item_indexes itype = RndBoyItem(*MyPlayer, lvl);
GetItemAttrs(BoyItem, itype, lvl);
GetItemBonus(*MyPlayer, BoyItem, lvl, 2 * lvl, true, true);

if (!gbIsHellfire) {
if (BoyItem._iIvalue > MaxBoyValue) {
keepgoing = true; // prevent breaking the do/while loop too early by failing hellfire's condition in while
continue;
}
break;
}

ivalue = 0;

ItemType itemType = BoyItem._itype;

switch (itemType) {
case ItemType::LightArmor:
case ItemType::MediumArmor:
case ItemType::HeavyArmor: {
const auto *const mostValuablePlayerArmor = myPlayer.GetMostValuableItem(
[](const Item &item) {
return IsAnyOf(item._itype, ItemType::LightArmor, ItemType::MediumArmor, ItemType::HeavyArmor);
});

ivalue = mostValuablePlayerArmor == nullptr ? 0 : mostValuablePlayerArmor->_iIvalue;
break;
}
case ItemType::Shield:
case ItemType::Axe:
case ItemType::Bow:
case ItemType::Mace:
case ItemType::Sword:
case ItemType::Helm:
case ItemType::Staff:
case ItemType::Ring:
case ItemType::Amulet: {
const auto *const mostValuablePlayerItem = myPlayer.GetMostValuableItem(
[itemType](const Item &item) { return item._itype == itemType; });
constexpr int strictCount = 250;
constexpr int maxTotalTries = 500;
int count = 0;
int maxValue = gbIsHellfire ? MaxBoyValueHf : MaxBoyValue;

ivalue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue;
for (; count < maxTotalTries; count++) {
BoyItem = GenerateBoyItem(lvl, myPlayer);
if (IsVendorItemValid(BoyItem, count, strictCount, myPlayer, strength, magic, dexterity, maxValue, true))
break;
}
default:
app_fatal("Invalid item spawn");
}
ivalue = ivalue * 4 / 5; // avoids forced int > float > int conversion
}

count++;
if (count == maxTotalTries) {
BoyItem = {};
return;
}

if (count < 200) {
switch (pc) {
case HeroClass::Warrior:
if (IsAnyOf(itemType, ItemType::Bow, ItemType::Staff))
ivalue = INT_MAX;
break;
case HeroClass::Rogue:
if (IsAnyOf(itemType, ItemType::Sword, ItemType::Staff, ItemType::Axe, ItemType::Mace, ItemType::Shield))
ivalue = INT_MAX;
break;
case HeroClass::Sorcerer:
if (IsAnyOf(itemType, ItemType::Staff, ItemType::Axe, ItemType::Bow, ItemType::Mace))
ivalue = INT_MAX;
break;
case HeroClass::Monk:
if (IsAnyOf(itemType, ItemType::Bow, ItemType::MediumArmor, ItemType::Shield, ItemType::Mace))
ivalue = INT_MAX;
break;
case HeroClass::Bard:
if (IsAnyOf(itemType, ItemType::Axe, ItemType::Mace, ItemType::Staff))
ivalue = INT_MAX;
break;
case HeroClass::Barbarian:
if (IsAnyOf(itemType, ItemType::Bow, ItemType::Staff))
ivalue = INT_MAX;
break;
}
}
} while (keepgoing
|| ((
BoyItem._iIvalue > MaxBoyValueHf
|| BoyItem._iMinStr > strength
|| BoyItem._iMinMag > magic
|| BoyItem._iMinDex > dexterity
|| BoyItem._iIvalue < ivalue)
&& count < 250));
BoyItem._iCreateInfo = lvl | CF_BOY;
BoyItem._iIdentified = true;
BoyItemLevel = lvl / 2;
Expand Down
Loading