devilutionX/Source/items.cpp
Anders Jenbo 8a7ec56d3a Player by reference part 2
This mostly change player to be by reference instead of by index.
But additionally it does stript checks for gold in the belt, move some
value types to the initialization and short circute a few functiongs.
2021-05-24 21:00:32 +02:00

4996 lines
129 KiB
C++

/**
* @file items.cpp
*
* Implementation of item functionality.
*/
#include <algorithm>
#include <climits>
#include <cstdint>
#include <bitset>
#include <fmt/format.h>
#include "cursor.h"
#include "doom.h"
#include "dx.h"
#include "engine/render/cel_render.hpp"
#include "engine/render/text_render.hpp"
#include "init.h"
#include "lighting.h"
#include "missiles.h"
#include "options.h"
#include "stores.h"
#include "utils/language.h"
#include "utils/math.h"
#define ITEMTYPES 43
namespace devilution {
namespace {
std::optional<CelSprite> itemanims[ITEMTYPES];
} // namespace
enum anim_armor_id : uint8_t {
// clang-format off
ANIM_ID_LIGHT_ARMOR = 0,
ANIM_ID_MEDIUM_ARMOR = 1 << 4,
ANIM_ID_HEAVY_ARMOR = 1 << 5,
// clang-format on
};
int itemactive[MAXITEMS];
bool uitemflag;
int itemavail[MAXITEMS];
ItemStruct curruitem;
ItemGetRecordStruct itemrecord[MAXITEMS];
/** Contains the items on ground in the current game. */
ItemStruct items[MAXITEMS + 1];
bool itemhold[3][3];
CornerStoneStruct CornerStone;
bool UniqueItemFlags[128];
int numitems;
int gnNumGetRecords;
/* data */
int OilLevels[] = { 1, 10, 1, 10, 4, 1, 5, 17, 1, 10 };
int OilValues[] = { 500, 2500, 500, 2500, 1500, 100, 2500, 15000, 500, 2500 };
item_misc_id OilMagic[] = {
IMISC_OILACC,
IMISC_OILMAST,
IMISC_OILSHARP,
IMISC_OILDEATH,
IMISC_OILSKILL,
IMISC_OILBSMTH,
IMISC_OILFORT,
IMISC_OILPERM,
IMISC_OILHARD,
IMISC_OILIMP,
};
char OilNames[10][25] = {
N_("Oil of Accuracy"),
N_("Oil of Mastery"),
N_("Oil of Sharpness"),
N_("Oil of Death"),
N_("Oil of Skill"),
N_("Blacksmith Oil"),
N_("Oil of Fortitude"),
N_("Oil of Permanence"),
N_("Oil of Hardening"),
N_("Oil of Imperviousness")
};
int MaxGold = GOLD_MAX_LIMIT;
/** Maps from item_cursor_graphic to in-memory item type. */
BYTE ItemCAnimTbl[] = {
20, 16, 16, 16, 4, 4, 4, 12, 12, 12,
12, 12, 12, 12, 12, 21, 21, 25, 12, 28,
28, 28, 38, 38, 38, 32, 38, 38, 38, 24,
24, 26, 2, 25, 22, 23, 24, 25, 27, 27,
29, 0, 0, 0, 12, 12, 12, 12, 12, 0,
8, 8, 0, 8, 8, 8, 8, 8, 8, 6,
8, 8, 8, 6, 8, 8, 6, 8, 8, 6,
6, 6, 8, 8, 8, 5, 9, 13, 13, 13,
5, 5, 5, 15, 5, 5, 18, 18, 18, 30,
5, 5, 14, 5, 14, 13, 16, 18, 5, 5,
7, 1, 3, 17, 1, 15, 10, 14, 3, 11,
8, 0, 1, 7, 0, 7, 15, 7, 3, 3,
3, 6, 6, 11, 11, 11, 31, 14, 14, 14,
6, 6, 7, 3, 8, 14, 0, 14, 14, 0,
33, 1, 1, 1, 1, 1, 7, 7, 7, 14,
14, 17, 17, 17, 0, 34, 1, 0, 3, 17,
8, 8, 6, 1, 3, 3, 11, 3, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 35, 39, 36,
36, 36, 37, 38, 38, 38, 38, 38, 41, 42,
8, 8, 8, 17, 0, 6, 8, 11, 11, 3,
3, 1, 6, 6, 6, 1, 8, 6, 11, 3,
6, 8, 1, 6, 6, 17, 40, 0, 0
};
/** Map of item type .cel file names. */
const char *const ItemDropNames[] = {
"Armor2",
"Axe",
"FBttle",
"Bow",
"GoldFlip",
"Helmut",
"Mace",
"Shield",
"SwrdFlip",
"Rock",
"Cleaver",
"Staff",
"Ring",
"CrownF",
"LArmor",
"WShield",
"Scroll",
"FPlateAr",
"FBook",
"Food",
"FBttleBB",
"FBttleDY",
"FBttleOR",
"FBttleBR",
"FBttleBL",
"FBttleBY",
"FBttleWH",
"FBttleDB",
"FEar",
"FBrain",
"FMush",
"Innsign",
"Bldstn",
"Fanvil",
"FLazStaf",
"bombs1",
"halfps1",
"wholeps1",
"runes1",
"teddys1",
"cows1",
"donkys1",
"mooses1",
};
/** Maps of item drop animation length. */
BYTE ItemAnimLs[] = {
15,
13,
16,
13,
10,
13,
13,
13,
13,
10,
13,
13,
13,
13,
13,
13,
13,
13,
13,
1,
16,
16,
16,
16,
16,
16,
16,
16,
13,
12,
12,
13,
13,
13,
8,
10,
16,
16,
10,
10,
15,
15,
15,
};
/** Maps of drop sounds effect of dropping the item on ground. */
_sfx_id ItemDropSnds[] = {
IS_FHARM,
IS_FAXE,
IS_FPOT,
IS_FBOW,
IS_GOLD,
IS_FCAP,
IS_FSWOR,
IS_FSHLD,
IS_FSWOR,
IS_FROCK,
IS_FAXE,
IS_FSTAF,
IS_FRING,
IS_FCAP,
IS_FLARM,
IS_FSHLD,
IS_FSCRL,
IS_FHARM,
IS_FBOOK,
IS_FLARM,
IS_FPOT,
IS_FPOT,
IS_FPOT,
IS_FPOT,
IS_FPOT,
IS_FPOT,
IS_FPOT,
IS_FPOT,
IS_FBODY,
IS_FBODY,
IS_FMUSH,
IS_ISIGN,
IS_FBLST,
IS_FANVL,
IS_FSTAF,
IS_FROCK,
IS_FSCRL,
IS_FSCRL,
IS_FROCK,
IS_FMUSH,
IS_FHARM,
IS_FLARM,
IS_FLARM,
};
/** Maps of drop sounds effect of placing the item in the inventory. */
_sfx_id ItemInvSnds[] = {
IS_IHARM,
IS_IAXE,
IS_IPOT,
IS_IBOW,
IS_GOLD,
IS_ICAP,
IS_ISWORD,
IS_ISHIEL,
IS_ISWORD,
IS_IROCK,
IS_IAXE,
IS_ISTAF,
IS_IRING,
IS_ICAP,
IS_ILARM,
IS_ISHIEL,
IS_ISCROL,
IS_IHARM,
IS_IBOOK,
IS_IHARM,
IS_IPOT,
IS_IPOT,
IS_IPOT,
IS_IPOT,
IS_IPOT,
IS_IPOT,
IS_IPOT,
IS_IPOT,
IS_IBODY,
IS_IBODY,
IS_IMUSH,
IS_ISIGN,
IS_IBLST,
IS_IANVL,
IS_ISTAF,
IS_IROCK,
IS_ISCROL,
IS_ISCROL,
IS_IROCK,
IS_IMUSH,
IS_IHARM,
IS_ILARM,
IS_ILARM,
};
/** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */
int premiumlvladd[] = {
// clang-format off
-1,
-1,
0,
0,
1,
2,
// clang-format on
};
/** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */
int premiumLvlAddHellfire[] = {
// clang-format off
-1,
-1,
-1,
0,
0,
0,
0,
1,
1,
1,
1,
2,
2,
3,
3,
// clang-format on
};
bool IsItemAvailable(int i)
{
if (gbIsHellfire)
return true;
return (
i != IDI_MAPOFDOOM // Cathedral Map
&& i != IDI_LGTFORGE // Bovine Plate
&& (i < IDI_OIL || i > IDI_GREYSUIT) // Hellfire exclusive items
&& (i < 83 || i > 86) // Oils
&& i != 92 // Scroll of Search
&& (i < 161 || i > 165) // Runes
&& i != IDI_SORCERER // Short Staff of Mana
)
|| (
// Bard items are technically Hellfire-exclusive
// but are just normal items with adjusted stats.
sgOptions.Gameplay.bTestBard && (i == IDI_BARDSWORD || i == IDI_BARDDAGGER));
}
BYTE GetOutlineColor(const ItemStruct &item, bool checkReq)
{
if (checkReq && !item._iStatFlag)
return ICOL_RED;
if (item._itype == ITYPE_GOLD)
return ICOL_YELLOW;
if (item._iMagical == ITEM_QUALITY_MAGIC)
return ICOL_BLUE;
if (item._iMagical == ITEM_QUALITY_UNIQUE)
return ICOL_YELLOW;
return ICOL_WHITE;
}
bool IsUniqueAvailable(int i)
{
return gbIsHellfire || i <= 89;
}
static bool IsPrefixValidForItemType(int i, int flgs)
{
int PLIType = PL_Prefix[i].PLIType;
if (!gbIsHellfire) {
if (i > 82)
return false;
if (i >= 12 && i <= 20)
PLIType &= ~PLT_STAFF;
}
return (flgs & PLIType) != 0;
}
static bool IsSuffixValidForItemType(int i, int flgs)
{
int PLIType = PL_Suffix[i].PLIType;
if (!gbIsHellfire) {
if (i > 94)
return false;
if ((i >= 0 && i <= 1)
|| (i >= 14 && i <= 15)
|| (i >= 21 && i <= 22)
|| (i >= 34 && i <= 36)
|| (i >= 41 && i <= 44)
|| (i >= 60 && i <= 63))
PLIType &= ~PLT_STAFF;
}
return (flgs & PLIType) != 0;
}
int items_get_currlevel()
{
int lvl;
lvl = currlevel;
if (currlevel >= 17 && currlevel <= 20)
lvl = currlevel - 8;
if (currlevel >= 21 && currlevel <= 24)
lvl = currlevel - 7;
return lvl;
}
void InitItemGFX()
{
char arglist[64];
int itemTypes = gbIsHellfire ? ITEMTYPES : 35;
for (int i = 0; i < itemTypes; i++) {
sprintf(arglist, "Items\\%s.CEL", ItemDropNames[i]);
itemanims[i] = LoadCel(arglist, ItemAnimWidth);
}
memset(UniqueItemFlags, 0, sizeof(UniqueItemFlags));
}
bool ItemPlace(int xp, int yp)
{
if (dMonster[xp][yp] != 0)
return false;
if (dPlayer[xp][yp] != 0)
return false;
if (dItem[xp][yp] != 0)
return false;
if (dObject[xp][yp] != 0)
return false;
if ((dFlags[xp][yp] & BFLAG_POPULATED) != 0)
return false;
if (nSolidTable[dPiece[xp][yp]])
return false;
return true;
}
void AddInitItems()
{
int x, y, j, rnd;
int curlv = items_get_currlevel();
rnd = GenerateRnd(3) + 3;
for (j = 0; j < rnd; j++) {
int ii = AllocateItem();
x = GenerateRnd(80) + 16;
y = GenerateRnd(80) + 16;
while (!ItemPlace(x, y)) {
x = GenerateRnd(80) + 16;
y = GenerateRnd(80) + 16;
}
items[ii].position = { x, y };
dItem[x][y] = ii + 1;
items[ii]._iSeed = AdvanceRndSeed();
SetRndSeed(items[ii]._iSeed);
if (GenerateRnd(2) != 0)
GetItemAttrs(ii, IDI_HEAL, curlv);
else
GetItemAttrs(ii, IDI_MANA, curlv);
items[ii]._iCreateInfo = curlv | CF_PREGEN;
SetupItem(ii);
items[ii]._iAnimFrame = items[ii]._iAnimLen;
items[ii]._iAnimFlag = false;
items[ii]._iSelFlag = 1;
DeltaAddItem(ii);
}
}
static void items_42390F()
{
int x, y, id;
x = GenerateRnd(80) + 16;
y = GenerateRnd(80) + 16;
while (!ItemPlace(x, y)) {
x = GenerateRnd(80) + 16;
y = GenerateRnd(80) + 16;
}
switch (currlevel) {
case 22:
id = IDI_NOTE2;
break;
case 23:
id = IDI_NOTE3;
break;
default:
id = IDI_NOTE1;
break;
}
SpawnQuestItem(id, { x, y }, 0, 1);
}
void InitItems()
{
int i;
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_GOLD, 1);
golditem = items[0];
golditem._iStatFlag = true;
numitems = 0;
for (i = 0; i < MAXITEMS; i++) {
items[i]._itype = ITYPE_NONE;
items[i].position = { 0, 0 };
items[i]._iAnimFlag = false;
items[i]._iSelFlag = 0;
items[i]._iIdentified = false;
items[i]._iPostDraw = false;
}
for (i = 0; i < MAXITEMS; i++) {
itemavail[i] = i;
itemactive[i] = 0;
}
if (!setlevel) {
AdvanceRndSeed(); /* unused */
if (QuestStatus(Q_ROCK))
SpawnRock();
if (QuestStatus(Q_ANVIL))
SpawnQuestItem(IDI_ANVIL, { 2 * setpc_x + 27, 2 * setpc_y + 27 }, 0, 1);
if (sgGameInitInfo.bCowQuest && currlevel == 20)
SpawnQuestItem(IDI_BROWNSUIT, { 25, 25 }, 3, 1);
if (sgGameInitInfo.bCowQuest && currlevel == 19)
SpawnQuestItem(IDI_GREYSUIT, { 25, 25 }, 3, 1);
if (currlevel > 0 && currlevel < 16)
AddInitItems();
if (currlevel >= 21 && currlevel <= 23)
items_42390F();
}
uitemflag = false;
}
void CalcPlrItemVals(int playerId, bool Loadgfx)
{
auto &player = plr[playerId];
int mind = 0; // min damage
int maxd = 0; // max damage
int tac = 0; // accuracy
int g;
int i;
int bdam = 0; // bonus damage
int btohit = 0; // bonus chance to hit
int bac = 0; // bonus accuracy
int iflgs = ISPL_NONE; // item_special_effect flags
int pDamAcFlags = 0;
int sadd = 0; // added strength
int madd = 0; // added magic
int dadd = 0; // added dexterity
int vadd = 0; // added vitality
uint64_t spl = 0; // bitarray for all enabled/active spells
int fr = 0; // fire resistance
int lr = 0; // lightning resistance
int mr = 0; // magic resistance
int dmod = 0; // bonus damage mod?
int ghit = 0; // increased damage from enemies
int lrad = 10; // light radius
int ihp = 0; // increased HP
int imana = 0; // increased mana
int spllvladd = 0; // increased spell level
int enac = 0; // enhanced accuracy
int fmin = 0; // minimum fire damage
int fmax = 0; // maximum fire damage
int lmin = 0; // minimum lightning damage
int lmax = 0; // maximum lightning damage
for (i = 0; i < NUM_INVLOC; i++) {
ItemStruct *itm = &player.InvBody[i];
if (!itm->isEmpty() && itm->_iStatFlag) {
mind += itm->_iMinDam;
maxd += itm->_iMaxDam;
tac += itm->_iAC;
if (itm->_iSpell != SPL_NULL) {
spl |= GetSpellBitmask(itm->_iSpell);
}
if (itm->_iMagical == ITEM_QUALITY_NORMAL || itm->_iIdentified) {
bdam += itm->_iPLDam;
btohit += itm->_iPLToHit;
if (itm->_iPLAC) {
int tmpac = itm->_iAC;
tmpac *= itm->_iPLAC;
tmpac /= 100;
if (tmpac == 0)
tmpac = math::Sign(itm->_iPLAC);
bac += tmpac;
}
iflgs |= itm->_iFlags;
pDamAcFlags |= itm->_iDamAcFlags;
sadd += itm->_iPLStr;
madd += itm->_iPLMag;
dadd += itm->_iPLDex;
vadd += itm->_iPLVit;
fr += itm->_iPLFR;
lr += itm->_iPLLR;
mr += itm->_iPLMR;
dmod += itm->_iPLDamMod;
ghit += itm->_iPLGetHit;
lrad += itm->_iPLLight;
ihp += itm->_iPLHP;
imana += itm->_iPLMana;
spllvladd += itm->_iSplLvlAdd;
enac += itm->_iPLEnAc;
fmin += itm->_iFMinDam;
fmax += itm->_iFMaxDam;
lmin += itm->_iLMinDam;
lmax += itm->_iLMaxDam;
}
}
}
if (mind == 0 && maxd == 0) {
mind = 1;
maxd = 1;
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD && player.InvBody[INVLOC_HAND_LEFT]._iStatFlag) {
maxd = 3;
}
if (player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD && player.InvBody[INVLOC_HAND_RIGHT]._iStatFlag) {
maxd = 3;
}
if (player._pClass == HeroClass::Monk) {
mind = std::max(mind, player._pLevel / 2);
maxd = std::max(maxd, (int)player._pLevel);
}
}
if ((player._pSpellFlags & 2) == 2) {
sadd += 2 * player._pLevel;
dadd += player._pLevel + player._pLevel / 2;
vadd += 2 * player._pLevel;
}
if ((player._pSpellFlags & 4) == 4) {
sadd -= 2 * player._pLevel;
dadd -= player._pLevel + player._pLevel / 2;
vadd -= 2 * player._pLevel;
}
player._pIMinDam = mind;
player._pIMaxDam = maxd;
player._pIAC = tac;
player._pIBonusDam = bdam;
player._pIBonusToHit = btohit;
player._pIBonusAC = bac;
player._pIFlags = iflgs;
player.pDamAcFlags = pDamAcFlags;
player._pIBonusDamMod = dmod;
player._pIGetHit = ghit;
lrad = clamp(lrad, 2, 15);
if (player._pLightRad != lrad && playerId == myplr) {
ChangeLightRadius(player._plid, lrad);
ChangeVisionRadius(player._pvid, lrad);
player._pLightRad = lrad;
}
player._pStrength = std::max(0, sadd + player._pBaseStr);
player._pMagic = std::max(0, madd + player._pBaseMag);
player._pDexterity = std::max(0, dadd + player._pBaseDex);
player._pVitality = std::max(0, vadd + player._pBaseVit);
if (player._pClass == HeroClass::Rogue) {
player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 200;
} else if (player._pClass == HeroClass::Monk) {
if (player.InvBody[INVLOC_HAND_LEFT]._itype != ITYPE_STAFF) {
if (player.InvBody[INVLOC_HAND_RIGHT]._itype != ITYPE_STAFF && (!player.InvBody[INVLOC_HAND_LEFT].isEmpty() || !player.InvBody[INVLOC_HAND_RIGHT].isEmpty())) {
player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 300;
} else {
player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 150;
}
} else {
player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 150;
}
} else if (player._pClass == HeroClass::Bard) {
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SWORD || player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SWORD)
player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 150;
else if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_BOW || player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_BOW) {
player._pDamageMod = player._pLevel * (player._pStrength + player._pDexterity) / 250;
} else {
player._pDamageMod = player._pLevel * player._pStrength / 100;
}
} else if (player._pClass == HeroClass::Barbarian) {
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_AXE || player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_AXE) {
player._pDamageMod = player._pLevel * player._pStrength / 75;
} else if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_MACE || player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_MACE) {
player._pDamageMod = player._pLevel * player._pStrength / 75;
} else if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_BOW || player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_BOW) {
player._pDamageMod = player._pLevel * player._pStrength / 300;
} else {
player._pDamageMod = player._pLevel * player._pStrength / 100;
}
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD || player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD) {
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD)
player._pIAC -= player.InvBody[INVLOC_HAND_LEFT]._iAC / 2;
else if (player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD)
player._pIAC -= player.InvBody[INVLOC_HAND_RIGHT]._iAC / 2;
} else if (player.InvBody[INVLOC_HAND_LEFT]._itype != ITYPE_STAFF && player.InvBody[INVLOC_HAND_RIGHT]._itype != ITYPE_STAFF && player.InvBody[INVLOC_HAND_LEFT]._itype != ITYPE_BOW && player.InvBody[INVLOC_HAND_RIGHT]._itype != ITYPE_BOW) {
player._pDamageMod += player._pLevel * player._pVitality / 100;
}
player._pIAC += player._pLevel / 4;
} else {
player._pDamageMod = player._pLevel * player._pStrength / 100;
}
player._pISpells = spl;
EnsureValidReadiedSpell(player);
player._pISplLvlAdd = spllvladd;
player._pIEnAc = enac;
if (player._pClass == HeroClass::Barbarian) {
mr += player._pLevel;
fr += player._pLevel;
lr += player._pLevel;
}
if ((player._pSpellFlags & 4) == 4) {
mr -= player._pLevel;
fr -= player._pLevel;
lr -= player._pLevel;
}
if ((iflgs & ISPL_ALLRESZERO) != 0) {
// reset resistances to zero if the respective special effect is active
mr = 0;
fr = 0;
lr = 0;
}
player._pMagResist = clamp(mr, 0, MAXRESIST);
player._pFireResist = clamp(fr, 0, MAXRESIST);
player._pLghtResist = clamp(lr, 0, MAXRESIST);
if (player._pClass == HeroClass::Warrior) {
vadd *= 2;
} else if (player._pClass == HeroClass::Barbarian) {
vadd += vadd;
vadd += (vadd / 4);
} else if (player._pClass == HeroClass::Rogue || player._pClass == HeroClass::Monk || player._pClass == HeroClass::Bard) {
vadd += vadd / 2;
}
ihp += (vadd << 6); // BUGFIX: blood boil can cause negative shifts here (see line 757)
if (player._pClass == HeroClass::Sorcerer) {
madd *= 2;
}
if (player._pClass == HeroClass::Rogue || player._pClass == HeroClass::Monk) {
madd += madd / 2;
} else if (player._pClass == HeroClass::Bard) {
madd += (madd / 4) + (madd / 2);
}
imana += (madd << 6);
player._pMaxHP = ihp + player._pMaxHPBase;
player._pHitPoints = std::min(ihp + player._pHPBase, player._pMaxHP);
if (playerId == myplr && (player._pHitPoints >> 6) <= 0) {
SetPlayerHitPoints(playerId, 0);
}
player._pMaxMana = imana + player._pMaxManaBase;
player._pMana = std::min(imana + player._pManaBase, player._pMaxMana);
player._pIFMinDam = fmin;
player._pIFMaxDam = fmax;
player._pILMinDam = lmin;
player._pILMaxDam = lmax;
if ((iflgs & ISPL_INFRAVISION) != 0) {
player._pInfraFlag = true;
} else {
player._pInfraFlag = false;
}
player._pBlockFlag = false;
if (player._pClass == HeroClass::Monk) {
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_STAFF && player.InvBody[INVLOC_HAND_LEFT]._iStatFlag) {
player._pBlockFlag = true;
player._pIFlags |= ISPL_FASTBLOCK;
}
if (player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_STAFF && player.InvBody[INVLOC_HAND_RIGHT]._iStatFlag) {
player._pBlockFlag = true;
player._pIFlags |= ISPL_FASTBLOCK;
}
if (player.InvBody[INVLOC_HAND_LEFT].isEmpty() && player.InvBody[INVLOC_HAND_RIGHT].isEmpty())
player._pBlockFlag = true;
if (player.InvBody[INVLOC_HAND_LEFT]._iClass == ICLASS_WEAPON && player.InvBody[INVLOC_HAND_LEFT]._iLoc != ILOC_TWOHAND && player.InvBody[INVLOC_HAND_RIGHT].isEmpty())
player._pBlockFlag = true;
if (player.InvBody[INVLOC_HAND_RIGHT]._iClass == ICLASS_WEAPON && player.InvBody[INVLOC_HAND_RIGHT]._iLoc != ILOC_TWOHAND && player.InvBody[INVLOC_HAND_LEFT].isEmpty())
player._pBlockFlag = true;
}
player._pwtype = WT_MELEE;
g = 0;
if (!player.InvBody[INVLOC_HAND_LEFT].isEmpty()
&& player.InvBody[INVLOC_HAND_LEFT]._iClass == ICLASS_WEAPON
&& player.InvBody[INVLOC_HAND_LEFT]._iStatFlag) {
g = player.InvBody[INVLOC_HAND_LEFT]._itype;
}
if (!player.InvBody[INVLOC_HAND_RIGHT].isEmpty()
&& player.InvBody[INVLOC_HAND_RIGHT]._iClass == ICLASS_WEAPON
&& player.InvBody[INVLOC_HAND_RIGHT]._iStatFlag) {
g = player.InvBody[INVLOC_HAND_RIGHT]._itype;
}
switch (g) {
case ITYPE_SWORD:
g = ANIM_ID_SWORD;
break;
case ITYPE_AXE:
g = ANIM_ID_AXE;
break;
case ITYPE_BOW:
player._pwtype = WT_RANGED;
g = ANIM_ID_BOW;
break;
case ITYPE_MACE:
g = ANIM_ID_MACE;
break;
case ITYPE_STAFF:
g = ANIM_ID_STAFF;
break;
}
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD && player.InvBody[INVLOC_HAND_LEFT]._iStatFlag) {
player._pBlockFlag = true;
g++;
}
if (player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD && player.InvBody[INVLOC_HAND_RIGHT]._iStatFlag) {
player._pBlockFlag = true;
g++;
}
if (player.InvBody[INVLOC_CHEST]._itype == ITYPE_HARMOR && player.InvBody[INVLOC_CHEST]._iStatFlag) {
if (player._pClass == HeroClass::Monk && player.InvBody[INVLOC_CHEST]._iMagical == ITEM_QUALITY_UNIQUE)
player._pIAC += player._pLevel / 2;
g += ANIM_ID_HEAVY_ARMOR;
} else if (player.InvBody[INVLOC_CHEST]._itype == ITYPE_MARMOR && player.InvBody[INVLOC_CHEST]._iStatFlag) {
if (player._pClass == HeroClass::Monk) {
if (player.InvBody[INVLOC_CHEST]._iMagical == ITEM_QUALITY_UNIQUE)
player._pIAC += player._pLevel * 2;
else
player._pIAC += player._pLevel / 2;
}
g += ANIM_ID_MEDIUM_ARMOR;
} else if (player._pClass == HeroClass::Monk) {
player._pIAC += player._pLevel * 2;
}
if (player._pgfxnum != g && Loadgfx) {
player._pgfxnum = g;
ResetPlayerGFX(player);
SetPlrAnims(player);
if (player._pmode == PM_STAND) {
LoadPlrGFX(player, player_graphic::Stand);
player.AnimInfo.ChangeAnimationData(&*player.AnimationData[static_cast<size_t>(player_graphic::Stand)].CelSpritesForDirections[player._pdir], player._pNFrames, 3);
} else {
LoadPlrGFX(player, player_graphic::Walk);
player.AnimInfo.ChangeAnimationData(&*player.AnimationData[static_cast<size_t>(player_graphic::Walk)].CelSpritesForDirections[player._pdir], player._pWFrames, 0);
}
} else {
player._pgfxnum = g;
}
if (player.InvBody[INVLOC_AMULET].isEmpty() || player.InvBody[INVLOC_AMULET].IDidx != IDI_AURIC) {
int half = MaxGold;
MaxGold = GOLD_MAX_LIMIT;
if (half != MaxGold)
StripTopGold(playerId);
} else {
MaxGold = GOLD_MAX_LIMIT * 2;
}
drawmanaflag = true;
drawhpflag = true;
}
void CalcPlrStaff(int p)
{
plr[p]._pISpells = 0;
if (!plr[p].InvBody[INVLOC_HAND_LEFT].isEmpty()
&& plr[p].InvBody[INVLOC_HAND_LEFT]._iStatFlag
&& plr[p].InvBody[INVLOC_HAND_LEFT]._iCharges > 0) {
plr[p]._pISpells |= GetSpellBitmask(plr[p].InvBody[INVLOC_HAND_LEFT]._iSpell);
}
}
void CalcSelfItems(PlayerStruct &player)
{
int i;
ItemStruct *pi;
bool sf, changeflag;
int sa, ma, da;
sa = 0;
ma = 0;
da = 0;
pi = player.InvBody;
for (i = 0; i < NUM_INVLOC; i++, pi++) {
if (!pi->isEmpty()) {
pi->_iStatFlag = true;
if (pi->_iIdentified) {
sa += pi->_iPLStr;
ma += pi->_iPLMag;
da += pi->_iPLDex;
}
}
}
do {
changeflag = false;
pi = player.InvBody;
for (i = 0; i < NUM_INVLOC; i++, pi++) {
if (!pi->isEmpty() && pi->_iStatFlag) {
sf = true;
if (sa + player._pBaseStr < pi->_iMinStr)
sf = false;
if (ma + player._pBaseMag < pi->_iMinMag)
sf = false;
if (da + player._pBaseDex < pi->_iMinDex)
sf = false;
if (!sf) {
changeflag = true;
pi->_iStatFlag = false;
if (pi->_iIdentified) {
sa -= pi->_iPLStr;
ma -= pi->_iPLMag;
da -= pi->_iPLDex;
}
}
}
}
} while (changeflag);
}
static bool ItemMinStats(const PlayerStruct &player, ItemStruct *x)
{
if (player._pMagic < x->_iMinMag)
return false;
if (player._pStrength < x->_iMinStr)
return false;
if (player._pDexterity < x->_iMinDex)
return false;
return true;
}
void CalcPlrItemMin(PlayerStruct &player)
{
ItemStruct *pi = player.InvList;
int i = player._pNumInv;
while (i--) {
pi->_iStatFlag = ItemMinStats(player, pi);
pi++;
}
pi = player.SpdList;
for (i = MAXBELTITEMS; i != 0; i--) {
if (!pi->isEmpty()) {
pi->_iStatFlag = ItemMinStats(player, pi);
}
pi++;
}
}
void CalcPlrBookVals(PlayerStruct &player)
{
if (currlevel == 0) {
for (int i = 1; !witchitem[i].isEmpty(); i++) {
WitchBookLevel(i);
witchitem[i]._iStatFlag = StoreStatOk(&witchitem[i]);
}
}
for (int i = 0; i < player._pNumInv; i++) {
if (player.InvList[i]._itype == ITYPE_MISC && player.InvList[i]._iMiscId == IMISC_BOOK) {
player.InvList[i]._iMinMag = spelldata[player.InvList[i]._iSpell].sMinInt;
int slvl = player._pSplLvl[player.InvList[i]._iSpell];
while (slvl != 0) {
player.InvList[i]._iMinMag += 20 * player.InvList[i]._iMinMag / 100;
slvl--;
if (player.InvList[i]._iMinMag + 20 * player.InvList[i]._iMinMag / 100 > 255) {
player.InvList[i]._iMinMag = 255;
slvl = 0;
}
}
player.InvList[i]._iStatFlag = ItemMinStats(player, &player.InvList[i]);
}
}
}
void CalcPlrInv(int playerId, bool Loadgfx)
{
auto &player = plr[playerId];
CalcPlrItemMin(player);
CalcSelfItems(player);
CalcPlrItemVals(playerId, Loadgfx);
CalcPlrItemMin(player);
if (playerId == myplr) {
CalcPlrBookVals(player);
player.CalcScrolls();
CalcPlrStaff(playerId);
if (playerId == myplr && currlevel == 0)
RecalcStoreStats();
}
}
void SetPlrHandItem(ItemStruct *h, int idata)
{
ItemDataStruct *pAllItem;
pAllItem = &AllItemsList[idata];
// zero-initialize struct
memset(h, 0, sizeof(*h));
h->_itype = pAllItem->itype;
h->_iCurs = pAllItem->iCurs;
strcpy(h->_iName, _(pAllItem->iName));
strcpy(h->_iIName, _(pAllItem->iName));
h->_iLoc = pAllItem->iLoc;
h->_iClass = pAllItem->iClass;
h->_iMinDam = pAllItem->iMinDam;
h->_iMaxDam = pAllItem->iMaxDam;
h->_iAC = pAllItem->iMinAC;
h->_iMiscId = pAllItem->iMiscId;
h->_iSpell = pAllItem->iSpell;
if (pAllItem->iMiscId == IMISC_STAFF) {
h->_iCharges = gbIsHellfire ? 18 : 40;
}
h->_iMaxCharges = h->_iCharges;
h->_iDurability = pAllItem->iDurability;
h->_iMaxDur = pAllItem->iDurability;
h->_iMinStr = pAllItem->iMinStr;
h->_iMinMag = pAllItem->iMinMag;
h->_iMinDex = pAllItem->iMinDex;
h->_ivalue = pAllItem->iValue;
h->_iIvalue = pAllItem->iValue;
h->_iPrePower = IPL_INVALID;
h->_iSufPower = IPL_INVALID;
h->_iMagical = ITEM_QUALITY_NORMAL;
h->IDidx = idata;
if (gbIsHellfire)
h->dwBuff |= CF_HELLFIRE;
}
void GetPlrHandSeed(ItemStruct *h)
{
h->_iSeed = AdvanceRndSeed();
}
/**
* @brief Set a new unique seed value on the given item
* @param pnum Player id
* @param h Item to update
*/
void GetGoldSeed(int pnum, ItemStruct *h)
{
int i, ii, s;
bool doneflag;
do {
doneflag = true;
s = AdvanceRndSeed();
for (i = 0; i < numitems; i++) {
ii = itemactive[i];
if (items[ii]._iSeed == s)
doneflag = false;
}
if (pnum == myplr) {
for (i = 0; i < plr[pnum]._pNumInv; i++) {
if (plr[pnum].InvList[i]._iSeed == s)
doneflag = false;
}
}
} while (!doneflag);
h->_iSeed = s;
}
void SetPlrHandSeed(ItemStruct *h, int iseed)
{
h->_iSeed = iseed;
}
int GetGoldCursor(int value)
{
if (value >= GOLD_MEDIUM_LIMIT)
return ICURS_GOLD_LARGE;
if (value <= GOLD_SMALL_LIMIT)
return ICURS_GOLD_SMALL;
return ICURS_GOLD_MEDIUM;
}
/**
* @brief Update the gold cursor on the given gold item
* @param h The item to update
*/
void SetPlrHandGoldCurs(ItemStruct *h)
{
h->_iCurs = GetGoldCursor(h->_ivalue);
}
void CreatePlrItems(int playerId)
{
auto &player = plr[playerId];
for (auto &item : player.InvBody) {
item._itype = ITYPE_NONE;
}
// converting this to a for loop creates a `rep stosd` instruction,
// so this probably actually was a memset
memset(&player.InvGrid, 0, sizeof(player.InvGrid));
for (auto &item : player.InvList) {
item._itype = ITYPE_NONE;
}
player._pNumInv = 0;
for (auto &item : player.SpdList) {
item._itype = ITYPE_NONE;
}
switch (player._pClass) {
case HeroClass::Warrior:
SetPlrHandItem(&player.InvBody[INVLOC_HAND_LEFT], IDI_WARRIOR);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_LEFT]);
SetPlrHandItem(&player.InvBody[INVLOC_HAND_RIGHT], IDI_WARRSHLD);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_RIGHT]);
#ifdef _DEBUG
if (!debug_mode_key_w)
#endif
{
SetPlrHandItem(&player.HoldItem, IDI_WARRCLUB);
GetPlrHandSeed(&player.HoldItem);
AutoPlaceItemInInventory(player, player.HoldItem, true);
}
SetPlrHandItem(&player.SpdList[0], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[0]);
SetPlrHandItem(&player.SpdList[1], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[1]);
break;
case HeroClass::Rogue:
SetPlrHandItem(&player.InvBody[INVLOC_HAND_LEFT], IDI_ROGUE);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_LEFT]);
SetPlrHandItem(&player.SpdList[0], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[0]);
SetPlrHandItem(&player.SpdList[1], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[1]);
break;
case HeroClass::Sorcerer:
SetPlrHandItem(&player.InvBody[INVLOC_HAND_LEFT], gbIsHellfire ? IDI_SORCERER : 166);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_LEFT]);
SetPlrHandItem(&player.SpdList[0], gbIsHellfire ? IDI_HEAL : IDI_MANA);
GetPlrHandSeed(&player.SpdList[0]);
SetPlrHandItem(&player.SpdList[1], gbIsHellfire ? IDI_HEAL : IDI_MANA);
GetPlrHandSeed(&player.SpdList[1]);
break;
case HeroClass::Monk:
SetPlrHandItem(&player.InvBody[INVLOC_HAND_LEFT], IDI_SHORTSTAFF);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_LEFT]);
SetPlrHandItem(&player.SpdList[0], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[0]);
SetPlrHandItem(&player.SpdList[1], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[1]);
break;
case HeroClass::Bard:
SetPlrHandItem(&player.InvBody[INVLOC_HAND_LEFT], IDI_BARDSWORD);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_LEFT]);
SetPlrHandItem(&player.InvBody[INVLOC_HAND_RIGHT], IDI_BARDDAGGER);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_RIGHT]);
SetPlrHandItem(&player.SpdList[0], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[0]);
SetPlrHandItem(&player.SpdList[1], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[1]);
break;
case HeroClass::Barbarian:
SetPlrHandItem(&player.InvBody[INVLOC_HAND_LEFT], 139); // TODO: add more enums to items
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_LEFT]);
SetPlrHandItem(&player.InvBody[INVLOC_HAND_RIGHT], IDI_WARRSHLD);
GetPlrHandSeed(&player.InvBody[INVLOC_HAND_RIGHT]);
SetPlrHandItem(&player.SpdList[0], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[0]);
SetPlrHandItem(&player.SpdList[1], IDI_HEAL);
GetPlrHandSeed(&player.SpdList[1]);
break;
}
SetPlrHandItem(&player.HoldItem, IDI_GOLD);
GetPlrHandSeed(&player.HoldItem);
#ifdef _DEBUG
if (!debug_mode_key_w) {
#endif
player.HoldItem._ivalue = 100;
player.HoldItem._iCurs = ICURS_GOLD_SMALL;
player._pGold = player.HoldItem._ivalue;
player.InvList[player._pNumInv++] = player.HoldItem;
player.InvGrid[30] = player._pNumInv;
#ifdef _DEBUG
} else {
player.HoldItem._ivalue = GOLD_MAX_LIMIT;
player.HoldItem._iCurs = ICURS_GOLD_LARGE;
player._pGold = player.HoldItem._ivalue * 40;
for (auto &cell : player.InvGrid) {
GetPlrHandSeed(&player.HoldItem);
player.InvList[player._pNumInv++] = player.HoldItem;
cell = player._pNumInv;
}
}
#endif
CalcPlrItemVals(playerId, false);
}
bool ItemSpaceOk(int i, int j)
{
int oi;
// BUGFIX: Check `i + 1 >= MAXDUNX` and `j + 1 >= MAXDUNY` (applied)
if (i < 0 || i + 1 >= MAXDUNX || j < 0 || j + 1 >= MAXDUNY)
return false;
if (dMonster[i][j] != 0)
return false;
if (dPlayer[i][j] != 0)
return false;
if (dItem[i][j] != 0)
return false;
if (dObject[i][j] != 0) {
oi = dObject[i][j] > 0 ? dObject[i][j] - 1 : -(dObject[i][j] + 1);
if (object[oi]._oSolidFlag)
return false;
}
if (dObject[i + 1][j + 1] > 0 && object[dObject[i + 1][j + 1] - 1]._oSelFlag != 0)
return false;
if (dObject[i + 1][j + 1] < 0 && object[-(dObject[i + 1][j + 1] + 1)]._oSelFlag != 0)
return false;
if (dObject[i + 1][j] > 0
&& dObject[i][j + 1] > 0
&& object[dObject[i + 1][j] - 1]._oSelFlag != 0
&& object[dObject[i][j + 1] - 1]._oSelFlag != 0) {
return false;
}
return !nSolidTable[dPiece[i][j]];
}
static bool GetItemSpace(Point position, int8_t inum)
{
int rs;
int xx, yy;
bool savail;
yy = 0;
for (int j = position.y - 1; j <= position.y + 1; j++) {
xx = 0;
for (int i = position.x - 1; i <= position.x + 1; i++) {
itemhold[xx][yy] = ItemSpaceOk(i, j);
xx++;
}
yy++;
}
savail = false;
for (int j = 0; j < 3; j++) {
for (int i = 0; i < 3; i++) {
if (itemhold[i][j])
savail = true;
}
}
rs = GenerateRnd(15) + 1;
if (!savail)
return false;
xx = 0;
yy = 0;
while (rs > 0) {
if (itemhold[xx][yy])
rs--;
if (rs <= 0)
continue;
xx++;
if (xx != 3)
continue;
xx = 0;
yy++;
if (yy == 3)
yy = 0;
}
xx += position.x - 1;
yy += position.y - 1;
items[inum].position = { xx, yy };
dItem[xx][yy] = inum + 1;
return true;
}
int AllocateItem()
{
int inum = itemavail[0];
itemavail[0] = itemavail[MAXITEMS - numitems - 1];
itemactive[numitems] = inum;
numitems++;
memset(&items[inum], 0, sizeof(*items));
return inum;
}
static void GetSuperItemSpace(Point position, int8_t inum)
{
if (GetItemSpace(position, inum))
return;
for (int k = 2; k < 50; k++) {
for (int j = -k; j <= k; j++) {
int yy = position.y + j;
for (int i = -k; i <= k; i++) {
int xx = i + position.x;
if (!ItemSpaceOk(xx, yy))
continue;
items[inum].position = { xx, yy };
dItem[xx][yy] = inum + 1;
return;
}
}
}
}
Point GetSuperItemLoc(Point position)
{
Point ret;
for (int k = 1; k < 50; k++) {
for (int j = -k; j <= k; j++) {
ret.y = position.y + j;
for (int i = -k; i <= k; i++) {
ret.x = i + position.x;
if (ItemSpaceOk(ret.x, ret.y)) {
return ret;
}
}
}
}
return ret; // TODO handle no space for dropping items
}
void CalcItemValue(int i)
{
int v = items[i]._iVMult1 + items[i]._iVMult2;
if (v > 0) {
v *= items[i]._ivalue;
}
if (v < 0) {
v = items[i]._ivalue / v;
}
v = items[i]._iVAdd1 + items[i]._iVAdd2 + v;
items[i]._iIvalue = std::max(v, 1);
}
void GetBookSpell(int i, int lvl)
{
int rv;
if (lvl == 0)
lvl = 1;
int maxSpells = gbIsHellfire ? MAX_SPELLS : 37;
rv = GenerateRnd(maxSpells) + 1;
if (gbIsSpawn && lvl > 5)
lvl = 5;
int s = SPL_FIREBOLT;
enum spell_id bs = SPL_FIREBOLT;
while (rv > 0) {
int sLevel = GetSpellBookLevel(static_cast<spell_id>(s));
if (sLevel != -1 && lvl >= sLevel) {
rv--;
bs = static_cast<spell_id>(s);
}
s++;
if (!gbIsMultiplayer) {
if (s == SPL_RESURRECT)
s = SPL_TELEKINESIS;
}
if (!gbIsMultiplayer) {
if (s == SPL_HEALOTHER)
s = SPL_FLARE;
}
if (s == maxSpells)
s = 1;
}
strcat(items[i]._iName, _(spelldata[bs].sNameText));
strcat(items[i]._iIName, _(spelldata[bs].sNameText));
items[i]._iSpell = bs;
items[i]._iMinMag = spelldata[bs].sMinInt;
items[i]._ivalue += spelldata[bs].sBookCost;
items[i]._iIvalue += spelldata[bs].sBookCost;
if (spelldata[bs].sType == STYPE_FIRE)
items[i]._iCurs = ICURS_BOOK_RED;
else if (spelldata[bs].sType == STYPE_LIGHTNING)
items[i]._iCurs = ICURS_BOOK_BLUE;
else if (spelldata[bs].sType == STYPE_MAGIC)
items[i]._iCurs = ICURS_BOOK_GREY;
}
static bool control_WriteStringToBuffer(const char *str)
{
return GetLineWidth(str, GameFontSmall, 0) < 125;
}
void GetStaffPower(int i, int lvl, int bs, bool onlygood)
{
int l[256];
char istr[128];
int nl, j, preidx;
int tmp;
tmp = GenerateRnd(10);
preidx = -1;
if (tmp == 0 || onlygood) {
nl = 0;
for (j = 0; PL_Prefix[j].PLPower != IPL_INVALID; j++) {
if (!IsPrefixValidForItemType(j, PLT_STAFF) || PL_Prefix[j].PLMinLvl > lvl)
continue;
if (onlygood && !PL_Prefix[j].PLOk)
continue;
l[nl] = j;
nl++;
if (PL_Prefix[j].PLDouble) {
l[nl] = j;
nl++;
}
}
if (nl != 0) {
preidx = l[GenerateRnd(nl)];
sprintf(istr, "%s %s", _(PL_Prefix[preidx].PLName), items[i]._iIName);
strcpy(items[i]._iIName, istr);
items[i]._iMagical = ITEM_QUALITY_MAGIC;
SaveItemPower(
i,
PL_Prefix[preidx].PLPower,
PL_Prefix[preidx].PLParam1,
PL_Prefix[preidx].PLParam2,
PL_Prefix[preidx].PLMinVal,
PL_Prefix[preidx].PLMaxVal,
PL_Prefix[preidx].PLMultVal);
items[i]._iPrePower = PL_Prefix[preidx].PLPower;
}
}
if (!control_WriteStringToBuffer(items[i]._iIName)) {
strcpy(items[i]._iIName, _(AllItemsList[items[i].IDidx].iSName));
if (preidx != -1) {
sprintf(istr, "%s %s", _(PL_Prefix[preidx].PLName), items[i]._iIName);
strcpy(items[i]._iIName, istr);
}
strcpy(istr, fmt::format(_(/* TRANSLATORS: Constructs item names. Format: <Prefix> <Item> of <Suffix>. Example: King's Long Sword of the Whale */ "{:s} of {:s}"), items[i]._iIName, _(spelldata[bs].sNameText)).c_str());
strcpy(items[i]._iIName, istr);
if (items[i]._iMagical == ITEM_QUALITY_NORMAL)
strcpy(items[i]._iName, items[i]._iIName);
}
CalcItemValue(i);
}
void GetStaffSpell(int i, int lvl, bool onlygood)
{
int l, rv, minc, maxc, v;
char istr[68];
if (!gbIsHellfire && GenerateRnd(4) == 0) {
GetItemPower(i, lvl / 2, lvl, PLT_STAFF, onlygood);
} else {
int maxSpells = gbIsHellfire ? MAX_SPELLS : 37;
l = lvl / 2;
if (l == 0)
l = 1;
rv = GenerateRnd(maxSpells) + 1;
if (gbIsSpawn && lvl > 10)
lvl = 10;
int s = SPL_FIREBOLT;
enum spell_id bs = SPL_NULL;
while (rv > 0) {
int sLevel = GetSpellStaffLevel(static_cast<spell_id>(s));
if (sLevel != -1 && l >= sLevel) {
rv--;
bs = static_cast<spell_id>(s);
}
s++;
if (!gbIsMultiplayer && s == SPL_RESURRECT)
s = SPL_TELEKINESIS;
if (!gbIsMultiplayer && s == SPL_HEALOTHER)
s = SPL_FLARE;
if (s == maxSpells)
s = SPL_FIREBOLT;
}
if (!control_WriteStringToBuffer(istr))
strcpy(istr, fmt::format(_("{:s} of {:s}"), items[i]._iName, _(spelldata[bs].sNameText)).c_str());
strcpy(istr, fmt::format(_("Staff of {:s}"), _(spelldata[bs].sNameText)).c_str());
strcpy(items[i]._iName, istr);
strcpy(items[i]._iIName, istr);
minc = spelldata[bs].sStaffMin;
maxc = spelldata[bs].sStaffMax - minc + 1;
items[i]._iSpell = bs;
items[i]._iCharges = minc + GenerateRnd(maxc);
items[i]._iMaxCharges = items[i]._iCharges;
items[i]._iMinMag = spelldata[bs].sMinInt;
v = items[i]._iCharges * spelldata[bs].sStaffCost / 5;
items[i]._ivalue += v;
items[i]._iIvalue += v;
GetStaffPower(i, lvl, bs, onlygood);
}
}
void GetOilType(int i, int max_lvl)
{
int cnt, t, j, r;
char rnd[32];
if (!gbIsMultiplayer) {
if (max_lvl == 0)
max_lvl = 1;
cnt = 0;
for (j = 0; j < (int)(sizeof(OilLevels) / sizeof(OilLevels[0])); j++) {
if (OilLevels[j] <= max_lvl) {
rnd[cnt] = j;
cnt++;
}
}
r = GenerateRnd(cnt);
t = rnd[r];
} else {
r = GenerateRnd(2);
t = (r != 0 ? 6 : 5);
}
strcpy(items[i]._iName, _(OilNames[t]));
strcpy(items[i]._iIName, _(OilNames[t]));
items[i]._iMiscId = OilMagic[t];
items[i]._ivalue = OilValues[t];
items[i]._iIvalue = OilValues[t];
}
void GetItemAttrs(int i, int idata, int lvl)
{
items[i]._itype = AllItemsList[idata].itype;
items[i]._iCurs = AllItemsList[idata].iCurs;
strcpy(items[i]._iName, _(AllItemsList[idata].iName));
strcpy(items[i]._iIName, _(AllItemsList[idata].iName));
items[i]._iLoc = AllItemsList[idata].iLoc;
items[i]._iClass = AllItemsList[idata].iClass;
items[i]._iMinDam = AllItemsList[idata].iMinDam;
items[i]._iMaxDam = AllItemsList[idata].iMaxDam;
items[i]._iAC = AllItemsList[idata].iMinAC + GenerateRnd(AllItemsList[idata].iMaxAC - AllItemsList[idata].iMinAC + 1);
items[i]._iFlags = AllItemsList[idata].iFlags;
items[i]._iMiscId = AllItemsList[idata].iMiscId;
items[i]._iSpell = AllItemsList[idata].iSpell;
items[i]._iMagical = ITEM_QUALITY_NORMAL;
items[i]._ivalue = AllItemsList[idata].iValue;
items[i]._iIvalue = AllItemsList[idata].iValue;
items[i]._iDurability = AllItemsList[idata].iDurability;
items[i]._iMaxDur = AllItemsList[idata].iDurability;
items[i]._iMinStr = AllItemsList[idata].iMinStr;
items[i]._iMinMag = AllItemsList[idata].iMinMag;
items[i]._iMinDex = AllItemsList[idata].iMinDex;
items[i].IDidx = idata;
if (gbIsHellfire)
items[i].dwBuff |= CF_HELLFIRE;
items[i]._iPrePower = IPL_INVALID;
items[i]._iSufPower = IPL_INVALID;
if (items[i]._iMiscId == IMISC_BOOK)
GetBookSpell(i, lvl);
if (gbIsHellfire && items[i]._iMiscId == IMISC_OILOF)
GetOilType(i, lvl);
if (items[i]._itype != ITYPE_GOLD)
return;
int rndv;
int itemlevel = items_get_currlevel();
switch (sgGameInitInfo.nDifficulty) {
case DIFF_NORMAL:
rndv = 5 * itemlevel + GenerateRnd(10 * itemlevel);
break;
case DIFF_NIGHTMARE:
rndv = 5 * (itemlevel + 16) + GenerateRnd(10 * (itemlevel + 16));
break;
case DIFF_HELL:
rndv = 5 * (itemlevel + 32) + GenerateRnd(10 * (itemlevel + 32));
break;
}
if (leveltype == DTYPE_HELL)
rndv += rndv / 8;
items[i]._ivalue = std::min(rndv, GOLD_MAX_LIMIT);
SetPlrHandGoldCurs(&items[i]);
}
int RndPL(int param1, int param2)
{
return param1 + GenerateRnd(param2 - param1 + 1);
}
int PLVal(int pv, int p1, int p2, int minv, int maxv)
{
if (p1 == p2)
return minv;
if (minv == maxv)
return minv;
return minv + (maxv - minv) * (100 * (pv - p1) / (p2 - p1)) / 100;
}
void SaveItemPower(int i, item_effect_type power, int param1, int param2, int minval, int maxval, int multval)
{
int r, r2;
r = RndPL(param1, param2);
switch (power) {
case IPL_TOHIT:
items[i]._iPLToHit += r;
break;
case IPL_TOHIT_CURSE:
items[i]._iPLToHit -= r;
break;
case IPL_DAMP:
items[i]._iPLDam += r;
break;
case IPL_DAMP_CURSE:
items[i]._iPLDam -= r;
break;
case IPL_DOPPELGANGER:
items[i]._iDamAcFlags |= 16;
[[fallthrough]];
case IPL_TOHIT_DAMP:
r = RndPL(param1, param2);
items[i]._iPLDam += r;
if (param1 == 20)
r2 = RndPL(1, 5);
if (param1 == 36)
r2 = RndPL(6, 10);
if (param1 == 51)
r2 = RndPL(11, 15);
if (param1 == 66)
r2 = RndPL(16, 20);
if (param1 == 81)
r2 = RndPL(21, 30);
if (param1 == 96)
r2 = RndPL(31, 40);
if (param1 == 111)
r2 = RndPL(41, 50);
if (param1 == 126)
r2 = RndPL(51, 75);
if (param1 == 151)
r2 = RndPL(76, 100);
items[i]._iPLToHit += r2;
break;
case IPL_TOHIT_DAMP_CURSE:
items[i]._iPLDam -= r;
if (param1 == 25)
r2 = RndPL(1, 5);
if (param1 == 50)
r2 = RndPL(6, 10);
items[i]._iPLToHit -= r2;
break;
case IPL_ACP:
items[i]._iPLAC += r;
break;
case IPL_ACP_CURSE:
items[i]._iPLAC -= r;
break;
case IPL_SETAC:
items[i]._iAC = r;
break;
case IPL_AC_CURSE:
items[i]._iAC -= r;
break;
case IPL_FIRERES:
items[i]._iPLFR += r;
break;
case IPL_LIGHTRES:
items[i]._iPLLR += r;
break;
case IPL_MAGICRES:
items[i]._iPLMR += r;
break;
case IPL_ALLRES:
items[i]._iPLFR = std::max(items[i]._iPLFR + r, 0);
items[i]._iPLLR = std::max(items[i]._iPLLR + r, 0);
items[i]._iPLMR = std::max(items[i]._iPLMR + r, 0);
break;
case IPL_SPLLVLADD:
items[i]._iSplLvlAdd = r;
break;
case IPL_CHARGES:
items[i]._iCharges *= param1;
items[i]._iMaxCharges = items[i]._iCharges;
break;
case IPL_SPELL:
items[i]._iSpell = static_cast<spell_id>(param1);
items[i]._iCharges = param2;
items[i]._iMaxCharges = param2;
break;
case IPL_FIREDAM:
items[i]._iFlags |= ISPL_FIREDAM;
items[i]._iFlags &= ~ISPL_LIGHTDAM;
items[i]._iFMinDam = param1;
items[i]._iFMaxDam = param2;
items[i]._iLMinDam = 0;
items[i]._iLMaxDam = 0;
break;
case IPL_LIGHTDAM:
items[i]._iFlags |= ISPL_LIGHTDAM;
items[i]._iFlags &= ~ISPL_FIREDAM;
items[i]._iLMinDam = param1;
items[i]._iLMaxDam = param2;
items[i]._iFMinDam = 0;
items[i]._iFMaxDam = 0;
break;
case IPL_STR:
items[i]._iPLStr += r;
break;
case IPL_STR_CURSE:
items[i]._iPLStr -= r;
break;
case IPL_MAG:
items[i]._iPLMag += r;
break;
case IPL_MAG_CURSE:
items[i]._iPLMag -= r;
break;
case IPL_DEX:
items[i]._iPLDex += r;
break;
case IPL_DEX_CURSE:
items[i]._iPLDex -= r;
break;
case IPL_VIT:
items[i]._iPLVit += r;
break;
case IPL_VIT_CURSE:
items[i]._iPLVit -= r;
break;
case IPL_ATTRIBS:
items[i]._iPLStr += r;
items[i]._iPLMag += r;
items[i]._iPLDex += r;
items[i]._iPLVit += r;
break;
case IPL_ATTRIBS_CURSE:
items[i]._iPLStr -= r;
items[i]._iPLMag -= r;
items[i]._iPLDex -= r;
items[i]._iPLVit -= r;
break;
case IPL_GETHIT_CURSE:
items[i]._iPLGetHit += r;
break;
case IPL_GETHIT:
items[i]._iPLGetHit -= r;
break;
case IPL_LIFE:
items[i]._iPLHP += r << 6;
break;
case IPL_LIFE_CURSE:
items[i]._iPLHP -= r << 6;
break;
case IPL_MANA:
items[i]._iPLMana += r << 6;
drawmanaflag = true;
break;
case IPL_MANA_CURSE:
items[i]._iPLMana -= r << 6;
drawmanaflag = true;
break;
case IPL_DUR:
r2 = r * items[i]._iMaxDur / 100;
items[i]._iMaxDur += r2;
items[i]._iDurability += r2;
break;
case IPL_CRYSTALLINE:
items[i]._iPLDam += 140 + r * 2;
[[fallthrough]];
case IPL_DUR_CURSE:
items[i]._iMaxDur -= r * items[i]._iMaxDur / 100;
items[i]._iMaxDur = std::max<uint8_t>(items[i]._iMaxDur, 1);
items[i]._iDurability = items[i]._iMaxDur;
break;
case IPL_INDESTRUCTIBLE:
items[i]._iDurability = DUR_INDESTRUCTIBLE;
items[i]._iMaxDur = DUR_INDESTRUCTIBLE;
break;
case IPL_LIGHT:
items[i]._iPLLight += param1;
break;
case IPL_LIGHT_CURSE:
items[i]._iPLLight -= param1;
break;
case IPL_MULT_ARROWS:
items[i]._iFlags |= ISPL_MULT_ARROWS;
break;
case IPL_FIRE_ARROWS:
items[i]._iFlags |= ISPL_FIRE_ARROWS;
items[i]._iFlags &= ~ISPL_LIGHT_ARROWS;
items[i]._iFMinDam = param1;
items[i]._iFMaxDam = param2;
items[i]._iLMinDam = 0;
items[i]._iLMaxDam = 0;
break;
case IPL_LIGHT_ARROWS:
items[i]._iFlags |= ISPL_LIGHT_ARROWS;
items[i]._iFlags &= ~ISPL_FIRE_ARROWS;
items[i]._iLMinDam = param1;
items[i]._iLMaxDam = param2;
items[i]._iFMinDam = 0;
items[i]._iFMaxDam = 0;
break;
case IPL_FIREBALL:
items[i]._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS);
items[i]._iFMinDam = param1;
items[i]._iFMaxDam = param2;
items[i]._iLMinDam = 0;
items[i]._iLMaxDam = 0;
break;
case IPL_THORNS:
items[i]._iFlags |= ISPL_THORNS;
break;
case IPL_NOMANA:
items[i]._iFlags |= ISPL_NOMANA;
drawmanaflag = true;
break;
case IPL_NOHEALPLR:
items[i]._iFlags |= ISPL_NOHEALPLR;
break;
case IPL_ABSHALFTRAP:
items[i]._iFlags |= ISPL_ABSHALFTRAP;
break;
case IPL_KNOCKBACK:
items[i]._iFlags |= ISPL_KNOCKBACK;
break;
case IPL_3XDAMVDEM:
items[i]._iFlags |= ISPL_3XDAMVDEM;
break;
case IPL_ALLRESZERO:
items[i]._iFlags |= ISPL_ALLRESZERO;
break;
case IPL_NOHEALMON:
items[i]._iFlags |= ISPL_NOHEALMON;
break;
case IPL_STEALMANA:
if (param1 == 3)
items[i]._iFlags |= ISPL_STEALMANA_3;
if (param1 == 5)
items[i]._iFlags |= ISPL_STEALMANA_5;
drawmanaflag = true;
break;
case IPL_STEALLIFE:
if (param1 == 3)
items[i]._iFlags |= ISPL_STEALLIFE_3;
if (param1 == 5)
items[i]._iFlags |= ISPL_STEALLIFE_5;
drawhpflag = true;
break;
case IPL_TARGAC:
if (gbIsHellfire)
items[i]._iPLEnAc = param1;
else
items[i]._iPLEnAc += r;
break;
case IPL_FASTATTACK:
if (param1 == 1)
items[i]._iFlags |= ISPL_QUICKATTACK;
if (param1 == 2)
items[i]._iFlags |= ISPL_FASTATTACK;
if (param1 == 3)
items[i]._iFlags |= ISPL_FASTERATTACK;
if (param1 == 4)
items[i]._iFlags |= ISPL_FASTESTATTACK;
break;
case IPL_FASTRECOVER:
if (param1 == 1)
items[i]._iFlags |= ISPL_FASTRECOVER;
if (param1 == 2)
items[i]._iFlags |= ISPL_FASTERRECOVER;
if (param1 == 3)
items[i]._iFlags |= ISPL_FASTESTRECOVER;
break;
case IPL_FASTBLOCK:
items[i]._iFlags |= ISPL_FASTBLOCK;
break;
case IPL_DAMMOD:
items[i]._iPLDamMod += r;
break;
case IPL_RNDARROWVEL:
items[i]._iFlags |= ISPL_RNDARROWVEL;
break;
case IPL_SETDAM:
items[i]._iMinDam = param1;
items[i]._iMaxDam = param2;
break;
case IPL_SETDUR:
items[i]._iDurability = param1;
items[i]._iMaxDur = param1;
break;
case IPL_FASTSWING:
items[i]._iFlags |= ISPL_FASTERATTACK;
break;
case IPL_ONEHAND:
items[i]._iLoc = ILOC_ONEHAND;
break;
case IPL_DRAINLIFE:
items[i]._iFlags |= ISPL_DRAINLIFE;
break;
case IPL_RNDSTEALLIFE:
items[i]._iFlags |= ISPL_RNDSTEALLIFE;
break;
case IPL_INFRAVISION:
items[i]._iFlags |= ISPL_INFRAVISION;
break;
case IPL_NOMINSTR:
items[i]._iMinStr = 0;
break;
case IPL_INVCURS:
items[i]._iCurs = param1;
break;
case IPL_ADDACLIFE:
items[i]._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS);
items[i]._iFMinDam = param1;
items[i]._iFMaxDam = param2;
items[i]._iLMinDam = 1;
items[i]._iLMaxDam = 0;
break;
case IPL_ADDMANAAC:
items[i]._iFlags |= (ISPL_LIGHTDAM | ISPL_FIREDAM);
items[i]._iFMinDam = param1;
items[i]._iFMaxDam = param2;
items[i]._iLMinDam = 2;
items[i]._iLMaxDam = 0;
break;
case IPL_FIRERESCLVL:
items[i]._iPLFR = 30 - plr[myplr]._pLevel;
items[i]._iPLFR = std::max<int16_t>(items[i]._iPLFR, 0);
break;
case IPL_FIRERES_CURSE:
items[i]._iPLFR -= r;
break;
case IPL_LIGHTRES_CURSE:
items[i]._iPLLR -= r;
break;
case IPL_MAGICRES_CURSE:
items[i]._iPLMR -= r;
break;
case IPL_ALLRES_CURSE:
items[i]._iPLFR -= r;
items[i]._iPLLR -= r;
items[i]._iPLMR -= r;
break;
case IPL_DEVASTATION:
items[i]._iDamAcFlags |= 0x01;
break;
case IPL_DECAY:
items[i]._iDamAcFlags |= 0x02;
items[i]._iPLDam += r;
break;
case IPL_PERIL:
items[i]._iDamAcFlags |= 0x04;
break;
case IPL_JESTERS:
items[i]._iDamAcFlags |= 0x08;
break;
case IPL_ACDEMON:
items[i]._iDamAcFlags |= 0x20;
break;
case IPL_ACUNDEAD:
items[i]._iDamAcFlags |= 0x40;
break;
case IPL_MANATOLIFE:
r2 = ((plr[myplr]._pMaxManaBase >> 6) * 50 / 100);
items[i]._iPLMana -= (r2 << 6);
items[i]._iPLHP += (r2 << 6);
break;
case IPL_LIFETOMANA:
r2 = ((plr[myplr]._pMaxHPBase >> 6) * 40 / 100);
items[i]._iPLHP -= (r2 << 6);
items[i]._iPLMana += (r2 << 6);
break;
default:
break;
}
if (items[i]._iVAdd1 || items[i]._iVMult1) {
items[i]._iVAdd2 = PLVal(r, param1, param2, minval, maxval);
items[i]._iVMult2 = multval;
} else {
items[i]._iVAdd1 = PLVal(r, param1, param2, minval, maxval);
items[i]._iVMult1 = multval;
}
}
static void SaveItemSuffix(int i, int sufidx)
{
int param1 = PL_Suffix[sufidx].PLParam1;
int param2 = PL_Suffix[sufidx].PLParam2;
if (!gbIsHellfire) {
if (sufidx >= 84 && sufidx <= 86) {
param1 = 2 << param1;
param2 = 6 << param2;
}
}
SaveItemPower(
i,
PL_Suffix[sufidx].PLPower,
param1,
param2,
PL_Suffix[sufidx].PLMinVal,
PL_Suffix[sufidx].PLMaxVal,
PL_Suffix[sufidx].PLMultVal);
}
void GetItemPower(int i, int minlvl, int maxlvl, affix_item_type flgs, bool onlygood)
{
int l[256];
char istr[128];
goodorevil goe;
int pre = GenerateRnd(4);
int post = GenerateRnd(3);
if (pre != 0 && post == 0) {
if (GenerateRnd(2) != 0)
post = 1;
else
pre = 0;
}
int preidx = -1;
int sufidx = -1;
goe = GOE_ANY;
if (!onlygood && GenerateRnd(3) != 0)
onlygood = true;
if (pre == 0) {
int nt = 0;
for (int j = 0; PL_Prefix[j].PLPower != IPL_INVALID; j++) {
if (!IsPrefixValidForItemType(j, flgs))
continue;
if (PL_Prefix[j].PLMinLvl < minlvl || PL_Prefix[j].PLMinLvl > maxlvl)
continue;
if (onlygood && !PL_Prefix[j].PLOk)
continue;
if (flgs == PLT_STAFF && PL_Prefix[j].PLPower == IPL_CHARGES)
continue;
l[nt] = j;
nt++;
if (PL_Prefix[j].PLDouble) {
l[nt] = j;
nt++;
}
}
if (nt != 0) {
preidx = l[GenerateRnd(nt)];
sprintf(istr, "%s %s", _(PL_Prefix[preidx].PLName), items[i]._iIName);
strcpy(items[i]._iIName, istr);
items[i]._iMagical = ITEM_QUALITY_MAGIC;
SaveItemPower(
i,
PL_Prefix[preidx].PLPower,
PL_Prefix[preidx].PLParam1,
PL_Prefix[preidx].PLParam2,
PL_Prefix[preidx].PLMinVal,
PL_Prefix[preidx].PLMaxVal,
PL_Prefix[preidx].PLMultVal);
items[i]._iPrePower = PL_Prefix[preidx].PLPower;
goe = PL_Prefix[preidx].PLGOE;
}
}
if (post != 0) {
int nl = 0;
for (int j = 0; PL_Suffix[j].PLPower != IPL_INVALID; j++) {
if (IsSuffixValidForItemType(j, flgs)
&& PL_Suffix[j].PLMinLvl >= minlvl && PL_Suffix[j].PLMinLvl <= maxlvl
&& !((goe == GOE_GOOD && PL_Suffix[j].PLGOE == GOE_EVIL) || (goe == GOE_EVIL && PL_Suffix[j].PLGOE == GOE_GOOD))
&& (!onlygood || PL_Suffix[j].PLOk)) {
l[nl] = j;
nl++;
}
}
if (nl != 0) {
sufidx = l[GenerateRnd(nl)];
strcpy(istr, fmt::format(_("{:s} of {:s}"), items[i]._iIName, _(PL_Suffix[sufidx].PLName)).c_str());
strcpy(items[i]._iIName, istr);
items[i]._iMagical = ITEM_QUALITY_MAGIC;
SaveItemSuffix(i, sufidx);
items[i]._iSufPower = PL_Suffix[sufidx].PLPower;
}
}
if (!control_WriteStringToBuffer(items[i]._iIName)) {
int aii = items[i].IDidx;
if (AllItemsList[aii].iSName != nullptr)
strcpy(items[i]._iIName, _(AllItemsList[aii].iSName));
else
items[i]._iName[0] = 0;
if (preidx != -1) {
sprintf(istr, "%s %s", _(PL_Prefix[preidx].PLName), items[i]._iIName);
strcpy(items[i]._iIName, istr);
}
if (sufidx != -1) {
strcpy(istr, fmt::format(_("{:s} of {:s}"), items[i]._iIName, _(PL_Suffix[sufidx].PLName)).c_str());
strcpy(items[i]._iIName, istr);
}
}
if (preidx != -1 || sufidx != -1)
CalcItemValue(i);
}
void GetItemBonus(int i, int minlvl, int maxlvl, bool onlygood, bool allowspells)
{
if (minlvl > 25)
minlvl = 25;
switch (items[i]._itype) {
case ITYPE_SWORD:
case ITYPE_AXE:
case ITYPE_MACE:
GetItemPower(i, minlvl, maxlvl, PLT_WEAP, onlygood);
break;
case ITYPE_BOW:
GetItemPower(i, minlvl, maxlvl, PLT_BOW, onlygood);
break;
case ITYPE_SHIELD:
GetItemPower(i, minlvl, maxlvl, PLT_SHLD, onlygood);
break;
case ITYPE_LARMOR:
case ITYPE_HELM:
case ITYPE_MARMOR:
case ITYPE_HARMOR:
GetItemPower(i, minlvl, maxlvl, PLT_ARMO, onlygood);
break;
case ITYPE_STAFF:
if (allowspells)
GetStaffSpell(i, maxlvl, onlygood);
else
GetItemPower(i, minlvl, maxlvl, PLT_STAFF, onlygood);
break;
case ITYPE_RING:
case ITYPE_AMULET:
GetItemPower(i, minlvl, maxlvl, PLT_MISC, onlygood);
break;
case ITYPE_NONE:
case ITYPE_MISC:
case ITYPE_GOLD:
break;
}
}
void SetupItem(int i)
{
int it;
it = ItemCAnimTbl[items[i]._iCurs];
items[i]._iAnimData = itemanims[it] ? &*itemanims[it] : nullptr;
items[i]._iAnimLen = ItemAnimLs[it];
items[i]._iIdentified = false;
items[i]._iPostDraw = false;
if (!plr[myplr].pLvlLoad) {
items[i]._iAnimFrame = 1;
items[i]._iAnimFlag = true;
items[i]._iSelFlag = 0;
} else {
items[i]._iAnimFrame = items[i]._iAnimLen;
items[i]._iAnimFlag = false;
items[i]._iSelFlag = 1;
}
}
int RndItem(int m)
{
int i, ri, r;
int ril[512];
if ((monster[m].MData->mTreasure & 0x8000) != 0)
return -((monster[m].MData->mTreasure & 0xFFF) + 1);
if ((monster[m].MData->mTreasure & 0x4000) != 0)
return 0;
if (GenerateRnd(100) > 40)
return 0;
if (GenerateRnd(100) > 25)
return IDI_GOLD + 1;
ri = 0;
for (i = 0; AllItemsList[i].iLoc != ILOC_INVALID; i++) {
if (!IsItemAvailable(i))
continue;
if (AllItemsList[i].iRnd == IDROP_DOUBLE && monster[m].mLevel >= AllItemsList[i].iMinMLvl
&& ri < 512) {
ril[ri] = i;
ri++;
}
if (AllItemsList[i].iRnd != IDROP_NEVER && monster[m].mLevel >= AllItemsList[i].iMinMLvl
&& ri < 512) {
ril[ri] = i;
ri++;
}
if (AllItemsList[i].iSpell == SPL_RESURRECT && !gbIsMultiplayer)
ri--;
if (AllItemsList[i].iSpell == SPL_HEALOTHER && !gbIsMultiplayer)
ri--;
}
r = GenerateRnd(ri);
return ril[r] + 1;
}
int RndUItem(int m)
{
int ril[512];
bool okflag;
if (m != -1 && (monster[m].MData->mTreasure & 0x8000) != 0 && !gbIsMultiplayer)
return -((monster[m].MData->mTreasure & 0xFFF) + 1);
int curlv = items_get_currlevel();
int ri = 0;
for (int i = 0; AllItemsList[i].iLoc != ILOC_INVALID; i++) {
if (!IsItemAvailable(i))
continue;
okflag = true;
if (AllItemsList[i].iRnd == IDROP_NEVER)
okflag = false;
if (m != -1) {
if (monster[m].mLevel < AllItemsList[i].iMinMLvl)
okflag = false;
} else {
if (2 * curlv < AllItemsList[i].iMinMLvl)
okflag = false;
}
if (AllItemsList[i].itype == ITYPE_MISC)
okflag = false;
if (AllItemsList[i].itype == ITYPE_GOLD)
okflag = false;
if (AllItemsList[i].iMiscId == IMISC_BOOK)
okflag = true;
if (AllItemsList[i].iSpell == SPL_RESURRECT && !gbIsMultiplayer)
okflag = false;
if (AllItemsList[i].iSpell == SPL_HEALOTHER && !gbIsMultiplayer)
okflag = false;
if (okflag && ri < 512) {
ril[ri] = i;
ri++;
}
}
return ril[GenerateRnd(ri)];
}
int RndAllItems()
{
int ril[512];
if (GenerateRnd(100) > 25)
return 0;
int curlv = items_get_currlevel();
int ri = 0;
for (int i = 0; AllItemsList[i].iLoc != ILOC_INVALID; i++) {
if (!IsItemAvailable(i))
continue;
if (AllItemsList[i].iRnd != IDROP_NEVER && 2 * curlv >= AllItemsList[i].iMinMLvl && ri < 512) {
ril[ri] = i;
ri++;
}
if (AllItemsList[i].iSpell == SPL_RESURRECT && !gbIsMultiplayer)
ri--;
if (AllItemsList[i].iSpell == SPL_HEALOTHER && !gbIsMultiplayer)
ri--;
}
return ril[GenerateRnd(ri)];
}
int RndTypeItems(int itype, int imid, int lvl)
{
int ril[512];
int ri = 0;
for (int i = 0; AllItemsList[i].iLoc != ILOC_INVALID; i++) {
if (!IsItemAvailable(i))
continue;
bool okflag = true;
if (AllItemsList[i].iRnd == IDROP_NEVER)
okflag = false;
if (lvl * 2 < AllItemsList[i].iMinMLvl)
okflag = false;
if (AllItemsList[i].itype != itype)
okflag = false;
if (imid != -1 && AllItemsList[i].iMiscId != imid)
okflag = false;
if (okflag && ri < 512) {
ril[ri] = i;
ri++;
}
}
return ril[GenerateRnd(ri)];
}
_unique_items CheckUnique(int i, int lvl, int uper, bool recreate)
{
std::bitset<128> uok = {};
if (GenerateRnd(100) > uper)
return UITEM_INVALID;
int numu = 0;
for (int j = 0; UniqueItemList[j].UIItemId != UITYPE_INVALID; j++) {
if (!IsUniqueAvailable(j))
break;
if (UniqueItemList[j].UIItemId == AllItemsList[items[i].IDidx].iItemId
&& lvl >= UniqueItemList[j].UIMinLvl
&& (recreate || !UniqueItemFlags[j] || gbIsMultiplayer)) {
uok[j] = true;
numu++;
}
}
if (numu == 0)
return UITEM_INVALID;
GenerateRnd(10); /// BUGFIX: unused, last unique in array always gets chosen
uint8_t idata = 0;
while (numu > 0) {
if (uok[idata])
numu--;
if (numu > 0)
idata = (idata + 1) % 128;
}
return (_unique_items)idata;
}
void GetUniqueItem(int i, _unique_items uid)
{
UniqueItemFlags[uid] = true;
SaveItemPower(i, UniqueItemList[uid].UIPower1, UniqueItemList[uid].UIParam1, UniqueItemList[uid].UIParam2, 0, 0, 1);
if (UniqueItemList[uid].UINumPL > 1)
SaveItemPower(i, UniqueItemList[uid].UIPower2, UniqueItemList[uid].UIParam3, UniqueItemList[uid].UIParam4, 0, 0, 1);
if (UniqueItemList[uid].UINumPL > 2)
SaveItemPower(i, UniqueItemList[uid].UIPower3, UniqueItemList[uid].UIParam5, UniqueItemList[uid].UIParam6, 0, 0, 1);
if (UniqueItemList[uid].UINumPL > 3)
SaveItemPower(i, UniqueItemList[uid].UIPower4, UniqueItemList[uid].UIParam7, UniqueItemList[uid].UIParam8, 0, 0, 1);
if (UniqueItemList[uid].UINumPL > 4)
SaveItemPower(i, UniqueItemList[uid].UIPower5, UniqueItemList[uid].UIParam9, UniqueItemList[uid].UIParam10, 0, 0, 1);
if (UniqueItemList[uid].UINumPL > 5)
SaveItemPower(i, UniqueItemList[uid].UIPower6, UniqueItemList[uid].UIParam11, UniqueItemList[uid].UIParam12, 0, 0, 1);
strcpy(items[i]._iIName, _(UniqueItemList[uid].UIName));
items[i]._iIvalue = UniqueItemList[uid].UIValue;
if (items[i]._iMiscId == IMISC_UNIQUE)
items[i]._iSeed = uid;
items[i]._iUid = uid;
items[i]._iMagical = ITEM_QUALITY_UNIQUE;
items[i]._iCreateInfo |= CF_UNIQUE;
}
void SpawnUnique(_unique_items uid, int x, int y)
{
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
GetSuperItemSpace({ x, y }, ii);
int curlv = items_get_currlevel();
int idx = 0;
while (AllItemsList[idx].iItemId != UniqueItemList[uid].UIItemId)
idx++;
GetItemAttrs(ii, idx, curlv);
GetUniqueItem(ii, uid);
SetupItem(ii);
return;
}
void ItemRndDur(int ii)
{
if (items[ii]._iDurability && items[ii]._iDurability != DUR_INDESTRUCTIBLE)
items[ii]._iDurability = GenerateRnd(items[ii]._iMaxDur / 2) + (items[ii]._iMaxDur / 4) + 1;
}
void SetupAllItems(int ii, int idx, int iseed, int lvl, int uper, bool onlygood, bool recreate, bool pregen)
{
int iblvl;
items[ii]._iSeed = iseed;
SetRndSeed(iseed);
GetItemAttrs(ii, idx, lvl / 2);
items[ii]._iCreateInfo = lvl;
if (pregen)
items[ii]._iCreateInfo |= CF_PREGEN;
if (onlygood)
items[ii]._iCreateInfo |= CF_ONLYGOOD;
if (uper == 15)
items[ii]._iCreateInfo |= CF_UPER15;
else if (uper == 1)
items[ii]._iCreateInfo |= CF_UPER1;
if (items[ii]._iMiscId != IMISC_UNIQUE) {
iblvl = -1;
if (GenerateRnd(100) <= 10 || GenerateRnd(100) <= lvl) {
iblvl = lvl;
}
if (iblvl == -1 && items[ii]._iMiscId == IMISC_STAFF) {
iblvl = lvl;
}
if (iblvl == -1 && items[ii]._iMiscId == IMISC_RING) {
iblvl = lvl;
}
if (iblvl == -1 && items[ii]._iMiscId == IMISC_AMULET) {
iblvl = lvl;
}
if (onlygood)
iblvl = lvl;
if (uper == 15)
iblvl = lvl + 4;
if (iblvl != -1) {
_unique_items uid = CheckUnique(ii, iblvl, uper, recreate);
if (uid == UITEM_INVALID) {
GetItemBonus(ii, iblvl / 2, iblvl, onlygood, true);
} else {
GetUniqueItem(ii, uid);
}
}
if (items[ii]._iMagical != ITEM_QUALITY_UNIQUE)
ItemRndDur(ii);
} else {
if (items[ii]._iLoc != ILOC_UNEQUIPABLE) {
GetUniqueItem(ii, (_unique_items)iseed); // uid is stored in iseed for uniques
}
}
SetupItem(ii);
}
void SpawnItem(int m, Point position, bool sendmsg)
{
int idx;
bool onlygood = true;
if (monster[m]._uniqtype || ((monster[m].MData->mTreasure & 0x8000) && gbIsMultiplayer)) {
idx = RndUItem(m);
if (idx < 0) {
SpawnUnique((_unique_items) - (idx + 1), position.x, position.y);
return;
}
onlygood = true;
} else if (quests[Q_MUSHROOM]._qactive != QUEST_ACTIVE || quests[Q_MUSHROOM]._qvar1 != QS_MUSHGIVEN) {
idx = RndItem(m);
if (!idx)
return;
if (idx > 0) {
idx--;
onlygood = false;
} else {
SpawnUnique((_unique_items) - (idx + 1), position.x, position.y);
return;
}
} else {
idx = IDI_BRAIN;
quests[Q_MUSHROOM]._qvar1 = QS_BRAINSPAWNED;
}
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
GetSuperItemSpace(position, ii);
int uper = monster[m]._uniqtype ? 15 : 1;
int mLevel = monster[m].MData->mLevel;
if (!gbIsHellfire && monster[m].MType->mtype == MT_DIABLO)
mLevel -= 15;
SetupAllItems(ii, idx, AdvanceRndSeed(), mLevel, uper, onlygood, false, false);
if (sendmsg)
NetSendCmdDItem(false, ii);
}
static void SetupBaseItem(Point position, int idx, bool onlygood, bool sendmsg, bool delta)
{
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
GetSuperItemSpace(position, ii);
int curlv = items_get_currlevel();
SetupAllItems(ii, idx, AdvanceRndSeed(), 2 * curlv, 1, onlygood, false, delta);
if (sendmsg)
NetSendCmdDItem(false, ii);
if (delta)
DeltaAddItem(ii);
}
void CreateRndItem(Point position, bool onlygood, bool sendmsg, bool delta)
{
int idx = onlygood ? RndUItem(-1) : RndAllItems();
SetupBaseItem(position, idx, onlygood, sendmsg, delta);
}
void SetupAllUseful(int ii, int iseed, int lvl)
{
int idx;
items[ii]._iSeed = iseed;
SetRndSeed(iseed);
if (gbIsHellfire) {
idx = GenerateRnd(7);
switch (idx) {
case 0:
idx = IDI_PORTAL;
if ((lvl <= 1))
idx = IDI_HEAL;
break;
case 1:
case 2:
idx = IDI_HEAL;
break;
case 3:
idx = IDI_PORTAL;
if ((lvl <= 1))
idx = IDI_MANA;
break;
case 4:
case 5:
idx = IDI_MANA;
break;
default:
idx = IDI_OIL;
break;
}
} else {
if (GenerateRnd(2) != 0)
idx = IDI_HEAL;
else
idx = IDI_MANA;
if (lvl > 1 && GenerateRnd(3) == 0)
idx = IDI_PORTAL;
}
GetItemAttrs(ii, idx, lvl);
items[ii]._iCreateInfo = lvl | CF_USEFUL;
SetupItem(ii);
}
void CreateRndUseful(Point position, bool sendmsg)
{
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
GetSuperItemSpace(position, ii);
int curlv = items_get_currlevel();
SetupAllUseful(ii, AdvanceRndSeed(), curlv);
if (sendmsg)
NetSendCmdDItem(false, ii);
}
void CreateTypeItem(Point position, bool onlygood, int itype, int imisc, bool sendmsg, bool delta)
{
int idx;
int curlv = items_get_currlevel();
if (itype != ITYPE_GOLD)
idx = RndTypeItems(itype, imisc, curlv);
else
idx = IDI_GOLD;
SetupBaseItem(position, idx, onlygood, sendmsg, delta);
}
void RecreateItem(int ii, int idx, uint16_t icreateinfo, int iseed, int ivalue, bool isHellfire)
{
bool _gbIsHellfire = gbIsHellfire;
gbIsHellfire = isHellfire;
if (idx == IDI_GOLD) {
SetPlrHandItem(&items[ii], IDI_GOLD);
items[ii]._iSeed = iseed;
items[ii]._iCreateInfo = icreateinfo;
items[ii]._ivalue = ivalue;
SetPlrHandGoldCurs(&items[ii]);
gbIsHellfire = _gbIsHellfire;
return;
}
if (icreateinfo == 0) {
SetPlrHandItem(&items[ii], idx);
SetPlrHandSeed(&items[ii], iseed);
gbIsHellfire = _gbIsHellfire;
return;
}
if ((icreateinfo & CF_UNIQUE) == 0) {
if ((icreateinfo & CF_TOWN) != 0) {
RecreateTownItem(ii, idx, icreateinfo, iseed);
gbIsHellfire = _gbIsHellfire;
return;
}
if ((icreateinfo & CF_USEFUL) == CF_USEFUL) {
SetupAllUseful(ii, iseed, icreateinfo & CF_LEVEL);
gbIsHellfire = _gbIsHellfire;
return;
}
}
int level = icreateinfo & CF_LEVEL;
int uper = 0;
if ((icreateinfo & CF_UPER1) != 0)
uper = 1;
if ((icreateinfo & CF_UPER15) != 0)
uper = 15;
bool onlygood = (icreateinfo & CF_ONLYGOOD) != 0;
bool recreate = (icreateinfo & CF_UNIQUE) != 0;
bool pregen = (icreateinfo & CF_PREGEN) != 0;
SetupAllItems(ii, idx, iseed, level, uper, onlygood, recreate, pregen);
gbIsHellfire = _gbIsHellfire;
}
void RecreateEar(int ii, uint16_t ic, int iseed, int Id, int dur, int mdur, int ch, int mch, int ivalue, int ibuff)
{
SetPlrHandItem(&items[ii], IDI_EAR);
tempstr[0] = (ic >> 8) & 0x7F;
tempstr[1] = ic & 0x7F;
tempstr[2] = (iseed >> 24) & 0x7F;
tempstr[3] = (iseed >> 16) & 0x7F;
tempstr[4] = (iseed >> 8) & 0x7F;
tempstr[5] = iseed & 0x7F;
tempstr[6] = Id & 0x7F;
tempstr[7] = dur & 0x7F;
tempstr[8] = mdur & 0x7F;
tempstr[9] = ch & 0x7F;
tempstr[10] = mch & 0x7F;
tempstr[11] = (ivalue >> 8) & 0x7F;
tempstr[12] = (ibuff >> 24) & 0x7F;
tempstr[13] = (ibuff >> 16) & 0x7F;
tempstr[14] = (ibuff >> 8) & 0x7F;
tempstr[15] = ibuff & 0x7F;
tempstr[16] = '\0';
strcpy(items[ii]._iName, fmt::format(_(/* TRANSLATORS: {:s} will be a Character Name */ "Ear of {:s}"), tempstr).c_str());
items[ii]._iCurs = ((ivalue >> 6) & 3) + ICURS_EAR_SORCERER;
items[ii]._ivalue = ivalue & 0x3F;
items[ii]._iCreateInfo = ic;
items[ii]._iSeed = iseed;
}
void items_427A72()
{
if (!CornerStone.activated)
return;
if (!CornerStone.item.isEmpty()) {
PkItemStruct id;
PackItem(&id, &CornerStone.item);
BYTE *buffer = (BYTE *)&id;
for (size_t i = 0; i < sizeof(PkItemStruct); i++) {
sprintf(&sgOptions.Hellfire.szItem[i * 2], "%02X", buffer[i]);
}
} else {
sgOptions.Hellfire.szItem[0] = '\0';
}
}
int char2int(char input)
{
if (input >= '0' && input <= '9')
return input - '0';
if (input >= 'A' && input <= 'F')
return input - 'A' + 10;
return 0;
}
void hex2bin(const char *src, int bytes, char *target)
{
for (int i = 0; i < bytes; i++, src += 2) {
target[i] = (char2int(*src) << 4) | char2int(src[1]);
}
}
void items_427ABA(Point position)
{
PkItemStruct PkSItem;
if (CornerStone.activated || position.x == 0 || position.y == 0) {
return;
}
CornerStone.item._itype = ITYPE_NONE;
CornerStone.activated = true;
if (dItem[position.x][position.y]) {
int ii = dItem[position.x][position.y] - 1;
for (int i = 0; i < numitems; i++) {
if (itemactive[i] == ii) {
DeleteItem(ii, i);
break;
}
}
dItem[position.x][position.y] = 0;
}
if (strlen(sgOptions.Hellfire.szItem) < sizeof(PkItemStruct) * 2)
return;
hex2bin(sgOptions.Hellfire.szItem, sizeof(PkItemStruct), (char *)&PkSItem);
int ii = AllocateItem();
dItem[position.x][position.y] = ii + 1;
UnPackItem(&PkSItem, &items[ii], (PkSItem.dwBuff & CF_HELLFIRE) != 0);
items[ii].position = position;
RespawnItem(&items[ii], false);
CornerStone.item = items[ii];
}
void SpawnQuestItem(int itemid, Point position, int randarea, int selflag)
{
if (randarea) {
int tries = 0;
while (true) {
tries++;
if (tries > 1000 && randarea > 1)
randarea--;
position.x = GenerateRnd(MAXDUNX);
position.y = GenerateRnd(MAXDUNY);
bool failed = false;
for (int i = 0; i < randarea && !failed; i++) {
for (int j = 0; j < randarea && !failed; j++) {
failed = !ItemSpaceOk(i + position.x, j + position.y);
}
}
if (!failed)
break;
}
}
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
items[ii].position = position;
dItem[position.x][position.y] = ii + 1;
int curlv = items_get_currlevel();
GetItemAttrs(ii, itemid, curlv);
SetupItem(ii);
items[ii]._iPostDraw = true;
if (selflag) {
items[ii]._iSelFlag = selflag;
items[ii]._iAnimFrame = items[ii]._iAnimLen;
items[ii]._iAnimFlag = false;
}
}
void SpawnRock()
{
if (numitems >= MAXITEMS)
return;
int oi;
bool ostand = false;
for (int i = 0; i < nobjects && !ostand; i++) {
oi = objectactive[i];
ostand = object[oi]._otype == OBJ_STAND;
}
if (!ostand)
return;
int ii = AllocateItem();
int xx = object[oi].position.x;
int yy = object[oi].position.y;
items[ii].position = { xx, yy };
dItem[xx][items[ii].position.y] = ii + 1;
int curlv = items_get_currlevel();
GetItemAttrs(ii, IDI_ROCK, curlv);
SetupItem(ii);
items[ii]._iSelFlag = 2;
items[ii]._iPostDraw = true;
items[ii]._iAnimFrame = 11;
}
void SpawnRewardItem(int itemid, Point position)
{
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
items[ii].position = position;
dItem[position.x][position.y] = ii + 1;
int curlv = items_get_currlevel();
GetItemAttrs(ii, itemid, curlv);
SetupItem(ii);
items[ii]._iSelFlag = 2;
items[ii]._iPostDraw = true;
items[ii]._iAnimFrame = 1;
items[ii]._iAnimFlag = true;
items[ii]._iIdentified = true;
}
void SpawnMapOfDoom(Point position)
{
SpawnRewardItem(IDI_MAPOFDOOM, position);
}
void SpawnRuneBomb(Point position)
{
SpawnRewardItem(IDI_RUNEBOMB, position);
}
void SpawnTheodore(Point position)
{
SpawnRewardItem(IDI_THEODORE, position);
}
void RespawnItem(ItemStruct *item, bool FlipFlag)
{
int it;
it = ItemCAnimTbl[item->_iCurs];
item->_iAnimData = &*itemanims[it];
item->_iAnimLen = ItemAnimLs[it];
item->_iPostDraw = false;
item->_iRequest = false;
if (FlipFlag) {
item->_iAnimFrame = 1;
item->_iAnimFlag = true;
item->_iSelFlag = 0;
} else {
item->_iAnimFrame = item->_iAnimLen;
item->_iAnimFlag = false;
item->_iSelFlag = 1;
}
if (item->_iCurs == ICURS_MAGIC_ROCK) {
item->_iSelFlag = 1;
PlaySfxLoc(ItemDropSnds[it], item->position.x, item->position.y);
}
if (item->_iCurs == ICURS_TAVERN_SIGN)
item->_iSelFlag = 1;
if (item->_iCurs == ICURS_ANVIL_OF_FURY)
item->_iSelFlag = 1;
}
void DeleteItem(int ii, int i)
{
itemavail[MAXITEMS - numitems] = ii;
numitems--;
if (numitems > 0 && i != numitems)
itemactive[i] = itemactive[numitems];
}
void ItemDoppel()
{
if (!gbIsMultiplayer)
return;
static int idoppely = 16;
for (int idoppelx = 16; idoppelx < 96; idoppelx++) {
if (dItem[idoppelx][idoppely]) {
ItemStruct *i = &items[dItem[idoppelx][idoppely] - 1];
if (i->position.x != idoppelx || i->position.y != idoppely)
dItem[idoppelx][idoppely] = 0;
}
}
idoppely++;
if (idoppely == 96)
idoppely = 16;
}
void ProcessItems()
{
for (int i = 0; i < numitems; i++) {
int ii = itemactive[i];
if (!items[ii]._iAnimFlag)
continue;
items[ii]._iAnimFrame++;
if (items[ii]._iCurs == ICURS_MAGIC_ROCK) {
if (items[ii]._iSelFlag == 1 && items[ii]._iAnimFrame == 11)
items[ii]._iAnimFrame = 1;
if (items[ii]._iSelFlag == 2 && items[ii]._iAnimFrame == 21)
items[ii]._iAnimFrame = 11;
} else {
if (items[ii]._iAnimFrame == items[ii]._iAnimLen / 2)
PlaySfxLoc(ItemDropSnds[ItemCAnimTbl[items[ii]._iCurs]], items[ii].position.x, items[ii].position.y);
if (items[ii]._iAnimFrame >= items[ii]._iAnimLen) {
items[ii]._iAnimFrame = items[ii]._iAnimLen;
items[ii]._iAnimFlag = false;
items[ii]._iSelFlag = 1;
}
}
}
ItemDoppel();
}
void FreeItemGFX()
{
for (auto &itemanim : itemanims) {
itemanim = std::nullopt;
}
}
void GetItemFrm(int i)
{
items[i]._iAnimData = &*itemanims[ItemCAnimTbl[items[i]._iCurs]];
}
void GetItemStr(int i)
{
if (items[i]._itype != ITYPE_GOLD) {
if (items[i]._iIdentified)
strcpy(infostr, items[i]._iIName);
else
strcpy(infostr, items[i]._iName);
if (items[i]._iMagical == ITEM_QUALITY_MAGIC)
infoclr = UIS_BLUE;
if (items[i]._iMagical == ITEM_QUALITY_UNIQUE)
infoclr = UIS_GOLD;
} else {
int nGold = items[i]._ivalue;
strcpy(infostr, fmt::format(ngettext("{:d} gold piece", "{:d} gold pieces", nGold), nGold).c_str());
}
}
void CheckIdentify(int pnum, int cii)
{
ItemStruct *pi;
if (cii >= NUM_INVLOC)
pi = &plr[pnum].InvList[cii - NUM_INVLOC];
else
pi = &plr[pnum].InvBody[cii];
pi->_iIdentified = true;
CalcPlrInv(pnum, true);
if (pnum == myplr)
NewCursor(CURSOR_HAND);
}
static void RepairItem(ItemStruct *i, int lvl)
{
if (i->_iDurability == i->_iMaxDur) {
return;
}
if (i->_iMaxDur <= 0) {
i->_itype = ITYPE_NONE;
return;
}
int rep = 0;
do {
rep += lvl + GenerateRnd(lvl);
i->_iMaxDur -= std::max(i->_iMaxDur / (lvl + 9), 1);
if (!i->_iMaxDur) {
i->_itype = ITYPE_NONE;
return;
}
} while (rep + i->_iDurability < i->_iMaxDur);
i->_iDurability = std::min<int>(i->_iDurability + rep, i->_iMaxDur);
}
void DoRepair(int pnum, int cii)
{
ItemStruct *pi;
auto &player = plr[pnum];
PlaySfxLoc(IS_REPAIR, player.position.tile.x, player.position.tile.y);
if (cii >= NUM_INVLOC) {
pi = &player.InvList[cii - NUM_INVLOC];
} else {
pi = &player.InvBody[cii];
}
RepairItem(pi, player._pLevel);
CalcPlrInv(pnum, true);
if (pnum == myplr)
NewCursor(CURSOR_HAND);
}
static void RechargeItem(ItemStruct *i, int r)
{
if (i->_iCharges == i->_iMaxCharges)
return;
do {
i->_iMaxCharges--;
if (i->_iMaxCharges == 0) {
return;
}
i->_iCharges += r;
} while (i->_iCharges < i->_iMaxCharges);
i->_iCharges = std::min(i->_iCharges, i->_iMaxCharges);
}
void DoRecharge(int pnum, int cii)
{
ItemStruct *pi;
auto &player = plr[pnum];
if (cii >= NUM_INVLOC) {
pi = &player.InvList[cii - NUM_INVLOC];
} else {
pi = &player.InvBody[cii];
}
if (pi->_itype == ITYPE_STAFF && pi->_iSpell != SPL_NULL) {
int r = GetSpellBookLevel(pi->_iSpell);
r = GenerateRnd(player._pLevel / r) + 1;
RechargeItem(pi, r);
CalcPlrInv(pnum, true);
}
if (pnum == myplr)
NewCursor(CURSOR_HAND);
}
static bool OilItem(ItemStruct *x, PlayerStruct &player)
{
int r;
if (x->_iClass == ICLASS_MISC) {
return false;
}
if (x->_iClass == ICLASS_GOLD) {
return false;
}
if (x->_iClass == ICLASS_QUEST) {
return false;
}
switch (player._pOilType) {
case IMISC_OILACC:
case IMISC_OILMAST:
case IMISC_OILSHARP:
if (x->_iClass == ICLASS_ARMOR) {
return false;
}
break;
case IMISC_OILDEATH:
if (x->_iClass == ICLASS_ARMOR) {
return false;
}
if (x->_itype == ITYPE_BOW) {
return false;
}
break;
case IMISC_OILHARD:
case IMISC_OILIMP:
if (x->_iClass == ICLASS_WEAPON) {
return false;
}
break;
default:
break;
}
switch (player._pOilType) {
case IMISC_OILACC:
if (x->_iPLToHit < 50) {
x->_iPLToHit += GenerateRnd(2) + 1;
}
break;
case IMISC_OILMAST:
if (x->_iPLToHit < 100) {
x->_iPLToHit += GenerateRnd(3) + 3;
}
break;
case IMISC_OILSHARP:
if (x->_iMaxDam - x->_iMinDam < 30) {
x->_iMaxDam = x->_iMaxDam + 1;
}
break;
case IMISC_OILDEATH:
if (x->_iMaxDam - x->_iMinDam < 30) {
x->_iMinDam = x->_iMinDam + 1;
x->_iMaxDam = x->_iMaxDam + 2;
}
break;
case IMISC_OILSKILL:
r = GenerateRnd(6) + 5;
x->_iMinStr = std::max(0, x->_iMinStr - r);
x->_iMinMag = std::max(0, x->_iMinMag - r);
x->_iMinDex = std::max(0, x->_iMinDex - r);
break;
case IMISC_OILBSMTH:
if (x->_iMaxDur == 255)
return true;
if (x->_iDurability < x->_iMaxDur) {
x->_iDurability = (x->_iMaxDur + 4) / 5 + x->_iDurability;
x->_iDurability = std::min<int>(x->_iDurability, x->_iMaxDur);
} else {
if (x->_iMaxDur >= 100) {
return true;
}
x->_iMaxDur++;
x->_iDurability = x->_iMaxDur;
}
break;
case IMISC_OILFORT:
if (x->_iMaxDur != 255 && x->_iMaxDur < 200) {
r = GenerateRnd(41) + 10;
x->_iMaxDur += r;
x->_iDurability += r;
}
break;
case IMISC_OILPERM:
x->_iDurability = 255;
x->_iMaxDur = 255;
break;
case IMISC_OILHARD:
if (x->_iAC < 60) {
x->_iAC += GenerateRnd(2) + 1;
}
break;
case IMISC_OILIMP:
if (x->_iAC < 120) {
x->_iAC += GenerateRnd(3) + 3;
}
break;
default:
return false;
}
return true;
}
void DoOil(int pnum, int cii)
{
if (cii < NUM_INVLOC && cii != INVLOC_HEAD && (cii <= INVLOC_AMULET || cii > INVLOC_CHEST))
return;
auto &player = plr[pnum];
if (!OilItem(&player.InvBody[cii], player))
return;
CalcPlrInv(pnum, true);
if (pnum == myplr) {
NewCursor(CURSOR_HAND);
}
}
void PrintItemOil(char IDidx)
{
switch (IDidx) {
case IMISC_OILACC:
strcpy(tempstr, _("increases a weapon's"));
AddPanelString(tempstr);
strcpy(tempstr, _("chance to hit"));
AddPanelString(tempstr);
break;
case IMISC_OILMAST:
strcpy(tempstr, _("greatly increases a"));
AddPanelString(tempstr);
strcpy(tempstr, _("weapon's chance to hit"));
AddPanelString(tempstr);
break;
case IMISC_OILSHARP:
strcpy(tempstr, _("increases a weapon's"));
AddPanelString(tempstr);
strcpy(tempstr, _("damage potential"));
AddPanelString(tempstr);
break;
case IMISC_OILDEATH:
strcpy(tempstr, _("greatly increases a weapon's"));
AddPanelString(tempstr);
strcpy(tempstr, _("damage potential - not bows"));
AddPanelString(tempstr);
break;
case IMISC_OILSKILL:
strcpy(tempstr, _("reduces attributes needed"));
AddPanelString(tempstr);
strcpy(tempstr, _("to use armor or weapons"));
AddPanelString(tempstr);
break;
case IMISC_OILBSMTH:
/*xgettext:no-c-format*/ strcpy(tempstr, _("restores 20% of an"));
AddPanelString(tempstr);
strcpy(tempstr, _("item's durability"));
AddPanelString(tempstr);
break;
case IMISC_OILFORT:
strcpy(tempstr, _("increases an item's"));
AddPanelString(tempstr);
strcpy(tempstr, _("current and max durability"));
AddPanelString(tempstr);
break;
case IMISC_OILPERM:
strcpy(tempstr, _("makes an item indestructible"));
AddPanelString(tempstr);
break;
case IMISC_OILHARD:
strcpy(tempstr, _("increases the armor class"));
AddPanelString(tempstr);
strcpy(tempstr, _("of armor and shields"));
AddPanelString(tempstr);
break;
case IMISC_OILIMP:
strcpy(tempstr, _("greatly increases the armor"));
AddPanelString(tempstr);
strcpy(tempstr, _("class of armor and shields"));
AddPanelString(tempstr);
break;
case IMISC_RUNEF:
strcpy(tempstr, _("sets fire trap"));
AddPanelString(tempstr);
break;
case IMISC_RUNEL:
strcpy(tempstr, _("sets lightning trap"));
AddPanelString(tempstr);
break;
case IMISC_GR_RUNEL:
strcpy(tempstr, _("sets lightning trap"));
AddPanelString(tempstr);
break;
case IMISC_GR_RUNEF:
strcpy(tempstr, _("sets fire trap"));
AddPanelString(tempstr);
break;
case IMISC_RUNES:
strcpy(tempstr, _("sets petrification trap"));
AddPanelString(tempstr);
break;
case IMISC_FULLHEAL:
strcpy(tempstr, _("fully recover life"));
AddPanelString(tempstr);
break;
case IMISC_HEAL:
strcpy(tempstr, _("recover partial life"));
AddPanelString(tempstr);
break;
case IMISC_OLDHEAL:
strcpy(tempstr, _("recover life"));
AddPanelString(tempstr);
break;
case IMISC_DEADHEAL:
strcpy(tempstr, _("deadly heal"));
AddPanelString(tempstr);
break;
case IMISC_MANA:
strcpy(tempstr, _("recover mana"));
AddPanelString(tempstr);
break;
case IMISC_FULLMANA:
strcpy(tempstr, _("fully recover mana"));
AddPanelString(tempstr);
break;
case IMISC_ELIXSTR:
strcpy(tempstr, _("increase strength"));
AddPanelString(tempstr);
break;
case IMISC_ELIXMAG:
strcpy(tempstr, _("increase magic"));
AddPanelString(tempstr);
break;
case IMISC_ELIXDEX:
strcpy(tempstr, _("increase dexterity"));
AddPanelString(tempstr);
break;
case IMISC_ELIXVIT:
strcpy(tempstr, _("increase vitality"));
AddPanelString(tempstr);
break;
case IMISC_ELIXWEAK:
strcpy(tempstr, _("decrease strength"));
AddPanelString(tempstr);
break;
case IMISC_ELIXDIS:
strcpy(tempstr, _("decrease strength"));
AddPanelString(tempstr);
break;
case IMISC_ELIXCLUM:
strcpy(tempstr, _("decrease dexterity"));
AddPanelString(tempstr);
break;
case IMISC_ELIXSICK:
strcpy(tempstr, _("decrease vitality"));
AddPanelString(tempstr);
break;
case IMISC_REJUV:
strcpy(tempstr, _("recover life and mana"));
AddPanelString(tempstr);
break;
case IMISC_FULLREJUV:
strcpy(tempstr, _("fully recover life and mana"));
AddPanelString(tempstr);
break;
}
}
void PrintItemPower(char plidx, ItemStruct *x)
{
switch (plidx) {
case IPL_TOHIT:
case IPL_TOHIT_CURSE:
strcpy(tempstr, fmt::format(_("chance to hit: {:+d}%"), x->_iPLToHit).c_str());
break;
case IPL_DAMP:
case IPL_DAMP_CURSE:
strcpy(tempstr, fmt::format(_("{:+d}% damage"), x->_iPLDam).c_str());
break;
case IPL_TOHIT_DAMP:
case IPL_TOHIT_DAMP_CURSE:
strcpy(tempstr, fmt::format(_("to hit: {:+d}%, {:+d}% damage"), x->_iPLToHit, x->_iPLDam).c_str());
break;
case IPL_ACP:
case IPL_ACP_CURSE:
strcpy(tempstr, fmt::format(_("{:+d}% armor"), x->_iPLAC).c_str());
break;
case IPL_SETAC:
strcpy(tempstr, fmt::format(_("armor class: {:d}"), x->_iAC).c_str());
break;
case IPL_AC_CURSE:
strcpy(tempstr, fmt::format(_("armor class: {:d}"), x->_iAC).c_str());
break;
case IPL_FIRERES:
case IPL_FIRERES_CURSE:
if (x->_iPLFR < 75)
strcpy(tempstr, fmt::format(_("Resist Fire: {:+d}%"), x->_iPLFR).c_str());
else
/*xgettext:no-c-format*/ strcpy(tempstr, _("Resist Fire: 75% MAX"));
break;
case IPL_LIGHTRES:
case IPL_LIGHTRES_CURSE:
if (x->_iPLLR < 75)
strcpy(tempstr, fmt::format(_("Resist Lightning: {:+d}%"), x->_iPLLR).c_str());
else
/*xgettext:no-c-format*/ strcpy(tempstr, _("Resist Lightning: 75% MAX"));
break;
case IPL_MAGICRES:
case IPL_MAGICRES_CURSE:
if (x->_iPLMR < 75)
strcpy(tempstr, fmt::format(_("Resist Magic: {:+d}%"), x->_iPLMR).c_str());
else
/*xgettext:no-c-format*/ strcpy(tempstr, _("Resist Magic: 75% MAX"));
break;
case IPL_ALLRES:
case IPL_ALLRES_CURSE:
if (x->_iPLFR < 75)
strcpy(tempstr, fmt::format(_("Resist All: {:+d}%"), x->_iPLFR).c_str());
if (x->_iPLFR >= 75)
/*xgettext:no-c-format*/ strcpy(tempstr, _("Resist All: 75% MAX"));
break;
case IPL_SPLLVLADD:
if (x->_iSplLvlAdd > 0)
strcpy(tempstr, fmt::format(ngettext("spells are increased {:d} level", "spells are increased {:d} levels", x->_iSplLvlAdd), x->_iSplLvlAdd).c_str());
else if (x->_iSplLvlAdd < 0)
strcpy(tempstr, fmt::format(ngettext("spells are decreased {:d} level", "spells are decreased {:d} levels", -x->_iSplLvlAdd), -x->_iSplLvlAdd).c_str());
else if (x->_iSplLvlAdd == 0)
strcpy(tempstr, _("spell levels unchanged (?)"));
break;
case IPL_CHARGES:
strcpy(tempstr, _("Extra charges"));
break;
case IPL_SPELL:
strcpy(tempstr, fmt::format(ngettext("{:d} {:s} charge", "{:d} {:s} charges", x->_iMaxCharges), x->_iMaxCharges, _(spelldata[x->_iSpell].sNameText)).c_str());
break;
case IPL_FIREDAM:
if (x->_iFMinDam == x->_iFMaxDam)
strcpy(tempstr, fmt::format(_("Fire hit damage: {:d}"), x->_iFMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("Fire hit damage: {:d}-{:d}"), x->_iFMinDam, x->_iFMaxDam).c_str());
break;
case IPL_LIGHTDAM:
if (x->_iLMinDam == x->_iLMaxDam)
strcpy(tempstr, fmt::format(_("Lightning hit damage: {:d}"), x->_iLMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("Lightning hit damage: {:d}-{:d}"), x->_iLMinDam, x->_iLMaxDam).c_str());
break;
case IPL_STR:
case IPL_STR_CURSE:
strcpy(tempstr, fmt::format(_("{:+d} to strength"), x->_iPLStr).c_str());
break;
case IPL_MAG:
case IPL_MAG_CURSE:
strcpy(tempstr, fmt::format(_("{:+d} to magic"), x->_iPLMag).c_str());
break;
case IPL_DEX:
case IPL_DEX_CURSE:
strcpy(tempstr, fmt::format(_("{:+d} to dexterity"), x->_iPLDex).c_str());
break;
case IPL_VIT:
case IPL_VIT_CURSE:
strcpy(tempstr, fmt::format(_("{:+d} to vitality"), x->_iPLVit).c_str());
break;
case IPL_ATTRIBS:
case IPL_ATTRIBS_CURSE:
strcpy(tempstr, fmt::format(_("{:+d} to all attributes"), x->_iPLStr).c_str());
break;
case IPL_GETHIT_CURSE:
case IPL_GETHIT:
strcpy(tempstr, fmt::format(_("{:+d} damage from enemies"), x->_iPLGetHit).c_str());
break;
case IPL_LIFE:
case IPL_LIFE_CURSE:
strcpy(tempstr, fmt::format(_("Hit Points: {:+d}"), x->_iPLHP >> 6).c_str());
break;
case IPL_MANA:
case IPL_MANA_CURSE:
strcpy(tempstr, fmt::format(_("Mana: {:+d}"), x->_iPLMana >> 6).c_str());
break;
case IPL_DUR:
strcpy(tempstr, _("high durability"));
break;
case IPL_DUR_CURSE:
strcpy(tempstr, _("decreased durability"));
break;
case IPL_INDESTRUCTIBLE:
strcpy(tempstr, _("indestructible"));
break;
case IPL_LIGHT:
strcpy(tempstr, fmt::format(_("+{:d}% light radius"), 10 * x->_iPLLight).c_str());
break;
case IPL_LIGHT_CURSE:
strcpy(tempstr, fmt::format(_("-{:d}% light radius"), -10 * x->_iPLLight).c_str());
break;
case IPL_MULT_ARROWS:
strcpy(tempstr, _("multiple arrows per shot"));
break;
case IPL_FIRE_ARROWS:
if (x->_iFMinDam == x->_iFMaxDam)
strcpy(tempstr, fmt::format(_("fire arrows damage: {:d}"), x->_iFMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("fire arrows damage: {:d}-{:d}"), x->_iFMinDam, x->_iFMaxDam).c_str());
break;
case IPL_LIGHT_ARROWS:
if (x->_iLMinDam == x->_iLMaxDam)
strcpy(tempstr, fmt::format(_("lightning arrows damage {:d}"), x->_iLMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("lightning arrows damage {:d}-{:d}"), x->_iLMinDam, x->_iLMaxDam).c_str());
break;
case IPL_FIREBALL:
if (x->_iFMinDam == x->_iFMaxDam)
strcpy(tempstr, fmt::format(_("fireball damage: {:d}"), x->_iFMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("fireball damage: {:d}-{:d}"), x->_iFMinDam, x->_iFMaxDam).c_str());
break;
case IPL_THORNS:
strcpy(tempstr, _("attacker takes 1-3 damage"));
break;
case IPL_NOMANA:
strcpy(tempstr, _("user loses all mana"));
break;
case IPL_NOHEALPLR:
strcpy(tempstr, _("you can't heal"));
break;
case IPL_ABSHALFTRAP:
strcpy(tempstr, _("absorbs half of trap damage"));
break;
case IPL_KNOCKBACK:
strcpy(tempstr, _("knocks target back"));
break;
case IPL_3XDAMVDEM:
/*xgettext:no-c-format*/ strcpy(tempstr, _("+200% damage vs. demons"));
break;
case IPL_ALLRESZERO:
strcpy(tempstr, _("All Resistance equals 0"));
break;
case IPL_NOHEALMON:
strcpy(tempstr, _("hit monster doesn't heal"));
break;
case IPL_STEALMANA:
if ((x->_iFlags & ISPL_STEALMANA_3) != 0)
/*xgettext:no-c-format*/ strcpy(tempstr, _("hit steals 3% mana"));
if ((x->_iFlags & ISPL_STEALMANA_5) != 0)
/*xgettext:no-c-format*/ strcpy(tempstr, _("hit steals 5% mana"));
break;
case IPL_STEALLIFE:
if ((x->_iFlags & ISPL_STEALLIFE_3) != 0)
/*xgettext:no-c-format*/ strcpy(tempstr, _("hit steals 3% life"));
if ((x->_iFlags & ISPL_STEALLIFE_5) != 0)
/*xgettext:no-c-format*/ strcpy(tempstr, _("hit steals 5% life"));
break;
case IPL_TARGAC:
strcpy(tempstr, _("penetrates target's armor"));
break;
case IPL_FASTATTACK:
if ((x->_iFlags & ISPL_QUICKATTACK) != 0)
strcpy(tempstr, _("quick attack"));
if ((x->_iFlags & ISPL_FASTATTACK) != 0)
strcpy(tempstr, _("fast attack"));
if ((x->_iFlags & ISPL_FASTERATTACK) != 0)
strcpy(tempstr, _("faster attack"));
if ((x->_iFlags & ISPL_FASTESTATTACK) != 0)
strcpy(tempstr, _("fastest attack"));
break;
case IPL_FASTRECOVER:
if ((x->_iFlags & ISPL_FASTRECOVER) != 0)
strcpy(tempstr, _("fast hit recovery"));
if ((x->_iFlags & ISPL_FASTERRECOVER) != 0)
strcpy(tempstr, _("faster hit recovery"));
if ((x->_iFlags & ISPL_FASTESTRECOVER) != 0)
strcpy(tempstr, _("fastest hit recovery"));
break;
case IPL_FASTBLOCK:
strcpy(tempstr, _("fast block"));
break;
case IPL_DAMMOD:
strcpy(tempstr, fmt::format(ngettext("adds {:d} point to damage", "adds {:d} points to damage", x->_iPLDamMod), x->_iPLDamMod).c_str());
break;
case IPL_RNDARROWVEL:
strcpy(tempstr, _("fires random speed arrows"));
break;
case IPL_SETDAM:
strcpy(tempstr, _("unusual item damage"));
break;
case IPL_SETDUR:
strcpy(tempstr, _("altered durability"));
break;
case IPL_FASTSWING:
strcpy(tempstr, _("Faster attack swing"));
break;
case IPL_ONEHAND:
strcpy(tempstr, _("one handed sword"));
break;
case IPL_DRAINLIFE:
strcpy(tempstr, _("constantly lose hit points"));
break;
case IPL_RNDSTEALLIFE:
strcpy(tempstr, _("life stealing"));
break;
case IPL_NOMINSTR:
strcpy(tempstr, _("no strength requirement"));
break;
case IPL_INFRAVISION:
strcpy(tempstr, _("see with infravision"));
break;
case IPL_INVCURS:
strcpy(tempstr, " ");
break;
case IPL_ADDACLIFE:
if (x->_iFMinDam == x->_iFMaxDam)
strcpy(tempstr, fmt::format(_("lightning damage: {:d}"), x->_iFMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("lightning damage: {:d}-{:d}"), x->_iFMinDam, x->_iFMaxDam).c_str());
break;
case IPL_ADDMANAAC:
strcpy(tempstr, _("charged bolts on hits"));
break;
case IPL_FIRERESCLVL:
if (x->_iPLFR <= 0)
strcpy(tempstr, " ");
else if (x->_iPLFR >= 1)
strcpy(tempstr, fmt::format(_("Resist Fire: {:+d}%"), x->_iPLFR).c_str());
break;
case IPL_DEVASTATION:
strcpy(tempstr, _("occasional triple damage"));
break;
case IPL_DECAY:
strcpy(tempstr, fmt::format(_("decaying {:+d}% damage"), x->_iPLDam).c_str());
break;
case IPL_PERIL:
strcpy(tempstr, _("2x dmg to monst, 1x to you"));
break;
case IPL_JESTERS:
/*xgettext:no-c-format*/ strcpy(tempstr, _("Random 0 - 500% damage"));
break;
case IPL_CRYSTALLINE:
strcpy(tempstr, fmt::format(_("low dur, {:+d}% damage"), x->_iPLDam).c_str());
break;
case IPL_DOPPELGANGER:
strcpy(tempstr, fmt::format(_("to hit: {:+d}%, {:+d}% damage"), x->_iPLToHit, x->_iPLDam).c_str());
break;
case IPL_ACDEMON:
strcpy(tempstr, _("extra AC vs demons"));
break;
case IPL_ACUNDEAD:
strcpy(tempstr, _("extra AC vs undead"));
break;
case IPL_MANATOLIFE:
/*xgettext:no-c-format*/ strcpy(tempstr, _("50% Mana moved to Health"));
break;
case IPL_LIFETOMANA:
/*xgettext:no-c-format*/ strcpy(tempstr, _("40% Health moved to Mana"));
break;
default:
strcpy(tempstr, _("Another ability (NW)"));
break;
}
}
static void DrawUTextBack(const CelOutputBuffer &out)
{
CelDrawTo(out, RIGHT_PANEL_X - SPANEL_WIDTH + 24, 327, *pSTextBoxCels, 1);
DrawHalfTransparentRectTo(out, RIGHT_PANEL_X - SPANEL_WIDTH + 27, 28, 265, 297);
}
static void DrawULine(const CelOutputBuffer &out, int y)
{
BYTE *src = out.at(26 + RIGHT_PANEL - SPANEL_WIDTH, 25);
BYTE *dst = out.at(26 + RIGHT_PANEL_X - SPANEL_WIDTH, y * 12 + 38);
for (int i = 0; i < 3; i++, src += out.pitch(), dst += out.pitch())
memcpy(dst, src, 267); // BUGFIX: should be 267 (fixed)
}
void DrawUniqueInfo(const CelOutputBuffer &out)
{
if ((chrflag || questlog) && gnScreenWidth < SPANEL_WIDTH * 3) {
return;
}
DrawUTextBack(GlobalBackBuffer());
SDL_Rect rect { 32 + RIGHT_PANEL - SPANEL_WIDTH, 44 + 2 * 12, 257, 0 };
const UItemStruct &uitem = UniqueItemList[curruitem._iUid];
DrawString(out, _(uitem.UIName), rect, UIS_CENTER);
DrawULine(out, 5);
rect.y += (12 - uitem.UINumPL) * 12;
PrintItemPower(uitem.UIPower1, &curruitem);
DrawString(out, tempstr, rect, UIS_SILVER | UIS_CENTER);
if (uitem.UINumPL > 1) {
rect.y += 2 * 12;
PrintItemPower(uitem.UIPower2, &curruitem);
DrawString(out, tempstr, rect, UIS_SILVER | UIS_CENTER);
}
if (uitem.UINumPL > 2) {
rect.y += 2 * 12;
PrintItemPower(uitem.UIPower3, &curruitem);
DrawString(out, tempstr, rect, UIS_SILVER | UIS_CENTER);
}
if (uitem.UINumPL > 3) {
rect.y += 2 * 12;
PrintItemPower(uitem.UIPower4, &curruitem);
DrawString(out, tempstr, rect, UIS_SILVER | UIS_CENTER);
}
if (uitem.UINumPL > 4) {
rect.y += 2 * 12;
PrintItemPower(uitem.UIPower5, &curruitem);
DrawString(out, tempstr, rect, UIS_SILVER | UIS_CENTER);
}
if (uitem.UINumPL > 5) {
rect.y += 2 * 12;
PrintItemPower(uitem.UIPower6, &curruitem);
DrawString(out, tempstr, rect, UIS_SILVER | UIS_CENTER);
}
}
void PrintItemMisc(ItemStruct *x)
{
if (x->_iMiscId == IMISC_SCROLL) {
strcpy(tempstr, _("Right-click to read"));
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_SCROLLT) {
strcpy(tempstr, _("Right-click to read, then"));
AddPanelString(tempstr);
strcpy(tempstr, _("left-click to target"));
AddPanelString(tempstr);
}
if (x->_iMiscId >= IMISC_USEFIRST && x->_iMiscId <= IMISC_USELAST) {
PrintItemOil(x->_iMiscId);
strcpy(tempstr, _("Right-click to use"));
AddPanelString(tempstr);
}
if (x->_iMiscId > IMISC_OILFIRST && x->_iMiscId < IMISC_OILLAST) {
PrintItemOil(x->_iMiscId);
strcpy(tempstr, _("Right click to use"));
AddPanelString(tempstr);
}
if (x->_iMiscId > IMISC_RUNEFIRST && x->_iMiscId < IMISC_RUNELAST) {
PrintItemOil(x->_iMiscId);
strcpy(tempstr, _("Right click to use"));
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_BOOK) {
strcpy(tempstr, _("Right-click to read"));
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_NOTE) {
strcpy(tempstr, _("Right click to read"));
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_MAPOFDOOM) {
strcpy(tempstr, _("Right-click to view"));
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_EAR) {
strcpy(tempstr, fmt::format(_("Level: {:d}"), x->_ivalue).c_str());
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_AURIC) {
strcpy(tempstr, _("Doubles gold capacity"));
AddPanelString(tempstr);
}
}
static void PrintItemInfo(ItemStruct *x)
{
PrintItemMisc(x);
uint8_t str = x->_iMinStr;
uint8_t dex = x->_iMinDex;
uint8_t mag = x->_iMinMag;
if (str != 0 || mag != 0 || dex != 0) {
strcpy(tempstr, _("Required:"));
if (str)
strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Str"), str).c_str());
if (mag)
strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Mag"), mag).c_str());
if (dex)
strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Dex"), dex).c_str());
AddPanelString(tempstr);
}
pinfoflag = true;
}
void PrintItemDetails(ItemStruct *x)
{
if (x->_iClass == ICLASS_WEAPON) {
if (x->_iMinDam == x->_iMaxDam) {
if (x->_iMaxDur == DUR_INDESTRUCTIBLE)
strcpy(tempstr, fmt::format(_("damage: {:d} Indestructible"), x->_iMinDam).c_str());
else
strcpy(tempstr, fmt::format(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}"), x->_iMinDam, x->_iDurability, x->_iMaxDur).c_str());
} else {
if (x->_iMaxDur == DUR_INDESTRUCTIBLE)
strcpy(tempstr, fmt::format(_("damage: {:d}-{:d} Indestructible"), x->_iMinDam, x->_iMaxDam).c_str());
else
strcpy(tempstr, fmt::format(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}"), x->_iMinDam, x->_iMaxDam, x->_iDurability, x->_iMaxDur).c_str());
}
AddPanelString(tempstr);
}
if (x->_iClass == ICLASS_ARMOR) {
if (x->_iMaxDur == DUR_INDESTRUCTIBLE)
strcpy(tempstr, fmt::format(_("armor: {:d} Indestructible"), x->_iAC).c_str());
else
strcpy(tempstr, fmt::format(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}"), x->_iAC, x->_iDurability, x->_iMaxDur).c_str());
AddPanelString(tempstr);
}
if (x->_iMiscId == IMISC_STAFF && x->_iMaxCharges != 0) {
if (x->_iMinDam == x->_iMaxDam)
strcpy(tempstr, fmt::format(_(/* TRANSLATORS: dam: is damage Dur: is durability */ "dam: {:d} Dur: {:d}/{:d}"), x->_iMinDam, x->_iDurability, x->_iMaxDur).c_str());
else
strcpy(tempstr, fmt::format(_(/* TRANSLATORS: dam: is damage Dur: is durability */ "dam: {:d}-{:d} Dur: {:d}/{:d}"), x->_iMinDam, x->_iMaxDam, x->_iDurability, x->_iMaxDur).c_str());
strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), x->_iCharges, x->_iMaxCharges).c_str());
AddPanelString(tempstr);
}
if (x->_iPrePower != -1) {
PrintItemPower(x->_iPrePower, x);
AddPanelString(tempstr);
}
if (x->_iSufPower != -1) {
PrintItemPower(x->_iSufPower, x);
AddPanelString(tempstr);
}
if (x->_iMagical == ITEM_QUALITY_UNIQUE) {
AddPanelString(_("unique item"));
uitemflag = true;
curruitem = *x;
}
PrintItemInfo(x);
}
void PrintItemDur(ItemStruct *x)
{
if (x->_iClass == ICLASS_WEAPON) {
if (x->_iMinDam == x->_iMaxDam) {
if (x->_iMaxDur == DUR_INDESTRUCTIBLE)
strcpy(tempstr, fmt::format(_("damage: {:d} Indestructible"), x->_iMinDam).c_str());
else
strcpy(tempstr, fmt::format(_("damage: {:d} Dur: {:d}/{:d}"), x->_iMinDam, x->_iDurability, x->_iMaxDur).c_str());
} else {
if (x->_iMaxDur == DUR_INDESTRUCTIBLE)
strcpy(tempstr, fmt::format(_("damage: {:d}-{:d} Indestructible"), x->_iMinDam, x->_iMaxDam).c_str());
else
strcpy(tempstr, fmt::format(_("damage: {:d}-{:d} Dur: {:d}/{:d}"), x->_iMinDam, x->_iMaxDam, x->_iDurability, x->_iMaxDur).c_str());
}
AddPanelString(tempstr);
if (x->_iMiscId == IMISC_STAFF && x->_iMaxCharges) {
strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), x->_iCharges, x->_iMaxCharges).c_str());
AddPanelString(tempstr);
}
if (x->_iMagical != ITEM_QUALITY_NORMAL)
AddPanelString(_("Not Identified"));
}
if (x->_iClass == ICLASS_ARMOR) {
if (x->_iMaxDur == DUR_INDESTRUCTIBLE)
strcpy(tempstr, fmt::format(_("armor: {:d} Indestructible"), x->_iAC).c_str());
else
strcpy(tempstr, fmt::format(_("armor: {:d} Dur: {:d}/{:d}"), x->_iAC, x->_iDurability, x->_iMaxDur).c_str());
AddPanelString(tempstr);
if (x->_iMagical != ITEM_QUALITY_NORMAL)
AddPanelString(_("Not Identified"));
if (x->_iMiscId == IMISC_STAFF && x->_iMaxCharges) {
strcpy(tempstr, fmt::format(_("Charges: {:d}/{:d}"), x->_iCharges, x->_iMaxCharges).c_str());
AddPanelString(tempstr);
}
}
if (x->_itype == ITYPE_RING || x->_itype == ITYPE_AMULET)
AddPanelString(_("Not Identified"));
PrintItemInfo(x);
}
void UseItem(int p, item_misc_id Mid, spell_id spl)
{
int l, j;
switch (Mid) {
case IMISC_HEAL:
case IMISC_FOOD:
j = plr[p]._pMaxHP >> 8;
l = ((j >> 1) + GenerateRnd(j)) << 6;
if (plr[p]._pClass == HeroClass::Warrior || plr[p]._pClass == HeroClass::Barbarian)
l *= 2;
if (plr[p]._pClass == HeroClass::Rogue || plr[p]._pClass == HeroClass::Monk || plr[p]._pClass == HeroClass::Bard)
l += l / 2;
plr[p]._pHitPoints = std::min(l + plr[p]._pHitPoints, plr[p]._pMaxHP);
plr[p]._pHPBase = std::min(l + plr[p]._pHPBase, plr[p]._pMaxHPBase);
drawhpflag = true;
break;
case IMISC_FULLHEAL:
plr[p]._pHitPoints = plr[p]._pMaxHP;
plr[p]._pHPBase = plr[p]._pMaxHPBase;
drawhpflag = true;
break;
case IMISC_MANA:
j = plr[p]._pMaxMana >> 8;
l = ((j >> 1) + GenerateRnd(j)) << 6;
if (plr[p]._pClass == HeroClass::Sorcerer)
l *= 2;
if (plr[p]._pClass == HeroClass::Rogue || plr[p]._pClass == HeroClass::Monk || plr[p]._pClass == HeroClass::Bard)
l += l / 2;
if ((plr[p]._pIFlags & ISPL_NOMANA) == 0) {
plr[p]._pMana = std::min(l + plr[p]._pMana, plr[p]._pMaxMana);
plr[p]._pManaBase = std::min(l + plr[p]._pManaBase, plr[p]._pMaxManaBase);
drawmanaflag = true;
}
break;
case IMISC_FULLMANA:
if ((plr[p]._pIFlags & ISPL_NOMANA) == 0) {
plr[p]._pMana = plr[p]._pMaxMana;
plr[p]._pManaBase = plr[p]._pMaxManaBase;
drawmanaflag = true;
}
break;
case IMISC_ELIXSTR:
ModifyPlrStr(p, 1);
break;
case IMISC_ELIXMAG:
ModifyPlrMag(p, 1);
if (gbIsHellfire) {
plr[p]._pMana = plr[p]._pMaxMana;
plr[p]._pManaBase = plr[p]._pMaxManaBase;
drawmanaflag = true;
}
break;
case IMISC_ELIXDEX:
ModifyPlrDex(p, 1);
break;
case IMISC_ELIXVIT:
ModifyPlrVit(p, 1);
if (gbIsHellfire) {
plr[p]._pHitPoints = plr[p]._pMaxHP;
plr[p]._pHPBase = plr[p]._pMaxHPBase;
drawhpflag = true;
}
break;
case IMISC_REJUV:
j = plr[p]._pMaxHP >> 8;
l = ((j / 2) + GenerateRnd(j)) << 6;
if (plr[p]._pClass == HeroClass::Warrior || plr[p]._pClass == HeroClass::Barbarian)
l *= 2;
if (plr[p]._pClass == HeroClass::Rogue)
l += l / 2;
plr[p]._pHitPoints = std::min(plr[p]._pHitPoints + l, plr[p]._pMaxHP);
plr[p]._pHPBase = std::min(plr[p]._pHPBase + l, plr[p]._pMaxHPBase);
drawhpflag = true;
j = plr[p]._pMaxMana >> 8;
l = ((j / 2) + GenerateRnd(j)) << 6;
if (plr[p]._pClass == HeroClass::Sorcerer)
l *= 2;
if (plr[p]._pClass == HeroClass::Rogue)
l += l / 2;
if ((plr[p]._pIFlags & ISPL_NOMANA) == 0) {
plr[p]._pMana = std::min(plr[p]._pMana + l, plr[p]._pMaxMana);
plr[p]._pManaBase = std::min(plr[p]._pManaBase + l, plr[p]._pMaxManaBase);
drawmanaflag = true;
}
break;
case IMISC_FULLREJUV:
plr[p]._pHitPoints = plr[p]._pMaxHP;
plr[p]._pHPBase = plr[p]._pMaxHPBase;
drawhpflag = true;
if ((plr[p]._pIFlags & ISPL_NOMANA) == 0) {
plr[p]._pMana = plr[p]._pMaxMana;
plr[p]._pManaBase = plr[p]._pMaxManaBase;
drawmanaflag = true;
}
break;
case IMISC_SCROLL:
if (spelldata[spl].sTargeted) {
plr[p]._pTSpell = spl;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
} else {
ClrPlrPath(plr[p]);
plr[p]._pSpell = spl;
plr[p]._pSplType = RSPLTYPE_INVALID;
plr[p]._pSplFrom = 3;
plr[p].destAction = ACTION_SPELL;
plr[p].destParam1 = cursmx;
plr[p].destParam2 = cursmy;
if (p == myplr && spl == SPL_NOVA)
NetSendCmdLoc(myplr, true, CMD_NOVA, { cursmx, cursmy });
}
break;
case IMISC_SCROLLT:
if (spelldata[spl].sTargeted) {
plr[p]._pTSpell = spl;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
} else {
ClrPlrPath(plr[p]);
plr[p]._pSpell = spl;
plr[p]._pSplType = RSPLTYPE_INVALID;
plr[p]._pSplFrom = 3;
plr[p].destAction = ACTION_SPELL;
plr[p].destParam1 = cursmx;
plr[p].destParam2 = cursmy;
}
break;
case IMISC_BOOK:
plr[p]._pMemSpells |= GetSpellBitmask(spl);
if (plr[p]._pSplLvl[spl] < MAX_SPELL_LEVEL)
plr[p]._pSplLvl[spl]++;
if ((plr[p]._pIFlags & ISPL_NOMANA) == 0) {
plr[p]._pMana += spelldata[spl].sManaCost << 6;
plr[p]._pMana = std::min(plr[p]._pMana, plr[p]._pMaxMana);
plr[p]._pManaBase += spelldata[spl].sManaCost << 6;
plr[p]._pManaBase = std::min(plr[p]._pManaBase, plr[p]._pMaxManaBase);
}
if (p == myplr)
CalcPlrBookVals(plr[p]);
drawmanaflag = true;
break;
case IMISC_MAPOFDOOM:
doom_init();
break;
case IMISC_OILACC:
case IMISC_OILMAST:
case IMISC_OILSHARP:
case IMISC_OILDEATH:
case IMISC_OILSKILL:
case IMISC_OILBSMTH:
case IMISC_OILFORT:
case IMISC_OILPERM:
case IMISC_OILHARD:
case IMISC_OILIMP:
plr[p]._pOilType = Mid;
if (p != myplr) {
return;
}
if (sbookflag) {
sbookflag = false;
}
if (!invflag) {
invflag = true;
}
NewCursor(CURSOR_OIL);
break;
case IMISC_SPECELIX:
ModifyPlrStr(p, 3);
ModifyPlrMag(p, 3);
ModifyPlrDex(p, 3);
ModifyPlrVit(p, 3);
break;
case IMISC_RUNEF:
plr[p]._pTSpell = SPL_RUNEFIRE;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
break;
case IMISC_RUNEL:
plr[p]._pTSpell = SPL_RUNELIGHT;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
break;
case IMISC_GR_RUNEL:
plr[p]._pTSpell = SPL_RUNENOVA;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
break;
case IMISC_GR_RUNEF:
plr[p]._pTSpell = SPL_RUNEIMMOLAT;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
break;
case IMISC_RUNES:
plr[p]._pTSpell = SPL_RUNESTONE;
plr[p]._pTSplType = RSPLTYPE_INVALID;
if (p == myplr)
NewCursor(CURSOR_TELEPORT);
break;
default:
break;
}
}
bool StoreStatOk(ItemStruct *h)
{
if (plr[myplr]._pStrength < h->_iMinStr)
return false;
if (plr[myplr]._pMagic < h->_iMinMag)
return false;
if (plr[myplr]._pDexterity < h->_iMinDex)
return false;
return true;
}
bool SmithItemOk(int i)
{
if (AllItemsList[i].itype == ITYPE_MISC)
return false;
if (AllItemsList[i].itype == ITYPE_GOLD)
return false;
if (AllItemsList[i].itype == ITYPE_STAFF && (!gbIsHellfire || AllItemsList[i].iSpell))
return false;
if (AllItemsList[i].itype == ITYPE_RING)
return false;
if (AllItemsList[i].itype == ITYPE_AMULET)
return false;
return true;
}
template <bool (*Ok)(int), bool ConsiderDropRate = false>
int RndVendorItem(int minlvl, int maxlvl)
{
int ril[512];
int ri = 0;
for (int i = 1; AllItemsList[i].iLoc != ILOC_INVALID; i++) {
if (!IsItemAvailable(i))
continue;
if (AllItemsList[i].iRnd == IDROP_NEVER)
continue;
if (!Ok(i))
continue;
if (AllItemsList[i].iMinMLvl < minlvl || AllItemsList[i].iMinMLvl > maxlvl)
continue;
ril[ri] = i;
ri++;
if (ri == 512)
break;
if (!ConsiderDropRate || AllItemsList[i].iRnd != IDROP_DOUBLE)
continue;
ril[ri] = i;
ri++;
if (ri == 512)
break;
}
return ril[GenerateRnd(ri)] + 1;
}
int RndSmithItem(int lvl)
{
return RndVendorItem<SmithItemOk, true>(0, lvl);
}
void SortVendor(ItemStruct *items)
{
int count = 1;
while (!items[count].isEmpty())
count++;
auto cmp = [](const ItemStruct &a, const ItemStruct &b) {
return a.IDidx < b.IDidx;
};
std::sort(items, items + count, cmp);
}
void SpawnSmith(int lvl)
{
constexpr int PinnedItemCount = 0;
int maxValue, maxItems;
ItemStruct holditem;
holditem = items[0];
if (gbIsHellfire) {
maxValue = 200000;
maxItems = 25;
} else {
maxValue = 140000;
maxItems = 20;
}
int iCnt = GenerateRnd(maxItems - 10) + 10;
for (int i = 0; i < iCnt; i++) {
do {
memset(&items[0], 0, sizeof(*items));
items[0]._iSeed = AdvanceRndSeed();
SetRndSeed(items[0]._iSeed);
int idata = RndSmithItem(lvl) - 1;
GetItemAttrs(0, idata, lvl);
} while (items[0]._iIvalue > maxValue);
smithitem[i] = items[0];
smithitem[i]._iCreateInfo = lvl | CF_SMITH;
smithitem[i]._iIdentified = true;
smithitem[i]._iStatFlag = StoreStatOk(&smithitem[i]);
}
for (int i = iCnt; i < SMITH_ITEMS; i++)
smithitem[i]._itype = ITYPE_NONE;
SortVendor(smithitem + PinnedItemCount);
items[0] = holditem;
}
bool PremiumItemOk(int i)
{
if (AllItemsList[i].itype == ITYPE_MISC)
return false;
if (AllItemsList[i].itype == ITYPE_GOLD)
return false;
if (!gbIsHellfire && AllItemsList[i].itype == ITYPE_STAFF)
return false;
if (gbIsMultiplayer) {
if (AllItemsList[i].iMiscId == IMISC_OILOF)
return false;
if (AllItemsList[i].itype == ITYPE_RING)
return false;
if (AllItemsList[i].itype == ITYPE_AMULET)
return false;
}
return true;
}
int RndPremiumItem(int minlvl, int maxlvl)
{
return RndVendorItem<PremiumItemOk>(minlvl, maxlvl);
}
static void SpawnOnePremium(int i, int plvl, int myplr)
{
int ivalue = 0;
bool keepgoing = false;
ItemStruct holditem = items[0];
int strength = std::max(plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Strength), plr[myplr]._pStrength);
int dexterity = std::max(plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Dexterity), plr[myplr]._pDexterity);
int magic = std::max(plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Magic), plr[myplr]._pMagic);
strength *= 1.2;
dexterity *= 1.2;
magic *= 1.2;
plvl = clamp(plvl, 1, 30);
int count = 0;
do {
keepgoing = false;
memset(&items[0], 0, sizeof(*items));
items[0]._iSeed = AdvanceRndSeed();
SetRndSeed(items[0]._iSeed);
int itype = RndPremiumItem(plvl / 4, plvl) - 1;
GetItemAttrs(0, itype, plvl);
GetItemBonus(0, plvl / 2, plvl, true, !gbIsHellfire);
if (!gbIsHellfire) {
if (items[0]._iIvalue > 140000) {
keepgoing = true; // prevent breaking the do/while loop too early by failing hellfire's condition in while
continue;
}
break;
}
switch (items[0]._itype) {
case ITYPE_LARMOR:
case ITYPE_MARMOR:
case ITYPE_HARMOR: {
const auto mostValuablePlayerArmor = plr[myplr].GetMostValuableItem(
[](const ItemStruct &item) {
return item._itype == ITYPE_LARMOR
|| item._itype == ITYPE_MARMOR
|| item._itype == ITYPE_HARMOR;
});
ivalue = mostValuablePlayerArmor == nullptr ? 0 : mostValuablePlayerArmor->_iIvalue;
break;
}
case ITYPE_SHIELD:
case ITYPE_AXE:
case ITYPE_BOW:
case ITYPE_MACE:
case ITYPE_SWORD:
case ITYPE_HELM:
case ITYPE_STAFF:
case ITYPE_RING:
case ITYPE_AMULET: {
const auto mostValuablePlayerItem = plr[myplr].GetMostValuableItem(
[](const ItemStruct &item) { return item._itype == items[0]._itype; });
ivalue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue;
break;
}
default:
ivalue = 0;
break;
}
ivalue *= 0.8;
count++;
} while (keepgoing
|| ((
items[0]._iIvalue > 200000
|| items[0]._iMinStr > strength
|| items[0]._iMinMag > magic
|| items[0]._iMinDex > dexterity
|| items[0]._iIvalue < ivalue)
&& count < 150));
premiumitems[i] = items[0];
premiumitems[i]._iCreateInfo = plvl | CF_SMITHPREMIUM;
premiumitems[i]._iIdentified = true;
premiumitems[i]._iStatFlag = StoreStatOk(&premiumitems[i]);
items[0] = holditem;
}
void SpawnPremium(int pnum)
{
int lvl = plr[pnum]._pLevel;
int maxItems = gbIsHellfire ? SMITH_PREMIUM_ITEMS : 6;
if (numpremium < maxItems) {
for (int i = 0; i < maxItems; i++) {
if (premiumitems[i].isEmpty()) {
int plvl = premiumlevel + (gbIsHellfire ? premiumLvlAddHellfire[i] : premiumlvladd[i]);
SpawnOnePremium(i, plvl, pnum);
}
}
numpremium = maxItems;
}
while (premiumlevel < lvl) {
premiumlevel++;
if (gbIsHellfire) {
premiumitems[0] = premiumitems[3];
premiumitems[1] = premiumitems[4];
premiumitems[2] = premiumitems[5];
premiumitems[3] = premiumitems[6];
premiumitems[4] = premiumitems[7];
premiumitems[5] = premiumitems[8];
premiumitems[6] = premiumitems[9];
premiumitems[7] = premiumitems[10];
premiumitems[8] = premiumitems[11];
premiumitems[9] = premiumitems[12];
SpawnOnePremium(10, premiumlevel + premiumLvlAddHellfire[10], pnum);
premiumitems[11] = premiumitems[13];
SpawnOnePremium(12, premiumlevel + premiumLvlAddHellfire[12], pnum);
premiumitems[13] = premiumitems[14];
SpawnOnePremium(14, premiumlevel + premiumLvlAddHellfire[14], pnum);
} else {
premiumitems[0] = premiumitems[2];
premiumitems[1] = premiumitems[3];
premiumitems[2] = premiumitems[4];
SpawnOnePremium(3, premiumlevel + premiumlvladd[3], pnum);
premiumitems[4] = premiumitems[5];
SpawnOnePremium(5, premiumlevel + premiumlvladd[5], pnum);
}
}
}
bool WitchItemOk(int i)
{
if (AllItemsList[i].itype != ITYPE_MISC && AllItemsList[i].itype != ITYPE_STAFF)
return false;
if (AllItemsList[i].iMiscId == IMISC_MANA)
return false;
if (AllItemsList[i].iMiscId == IMISC_FULLMANA)
return false;
if (AllItemsList[i].iSpell == SPL_TOWN)
return false;
if (AllItemsList[i].iMiscId == IMISC_FULLHEAL)
return false;
if (AllItemsList[i].iMiscId == IMISC_HEAL)
return false;
if (AllItemsList[i].iMiscId > IMISC_OILFIRST && AllItemsList[i].iMiscId < IMISC_OILLAST)
return false;
if (AllItemsList[i].iSpell == SPL_RESURRECT && !gbIsMultiplayer)
return false;
if (AllItemsList[i].iSpell == SPL_HEALOTHER && !gbIsMultiplayer)
return false;
return true;
}
int RndWitchItem(int lvl)
{
return RndVendorItem<WitchItemOk>(0, lvl);
}
void WitchBookLevel(int ii)
{
if (witchitem[ii]._iMiscId != IMISC_BOOK)
return;
witchitem[ii]._iMinMag = spelldata[witchitem[ii]._iSpell].sMinInt;
int slvl = plr[myplr]._pSplLvl[witchitem[ii]._iSpell];
while (slvl) {
witchitem[ii]._iMinMag += 20 * witchitem[ii]._iMinMag / 100;
slvl--;
if (witchitem[ii]._iMinMag + 20 * witchitem[ii]._iMinMag / 100 > 255) {
witchitem[ii]._iMinMag = 255;
slvl = 0;
}
}
}
void SpawnWitch(int lvl)
{
constexpr int PinnedItemCount = 3;
int iCnt;
int idata, maxlvl, maxValue;
int j = PinnedItemCount;
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_MANA, 1);
witchitem[0] = items[0];
witchitem[0]._iCreateInfo = lvl;
witchitem[0]._iStatFlag = true;
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_FULLMANA, 1);
witchitem[1] = items[0];
witchitem[1]._iCreateInfo = lvl;
witchitem[1]._iStatFlag = true;
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_PORTAL, 1);
witchitem[2] = items[0];
witchitem[2]._iCreateInfo = lvl;
witchitem[2]._iStatFlag = true;
if (gbIsHellfire) {
iCnt = GenerateRnd(WITCH_ITEMS - 10) + 10;
maxValue = 200000;
int books = GenerateRnd(4);
for (int i = 114, bCnt = 0; i <= 117 && bCnt < books; ++i) {
if (!WitchItemOk(i))
continue;
if (lvl < AllItemsList[i].iMinMLvl)
continue;
memset(&items[0], 0, sizeof(*items));
items[0]._iSeed = AdvanceRndSeed();
SetRndSeed(items[0]._iSeed);
GenerateRnd(1);
GetItemAttrs(0, i, lvl);
witchitem[j] = items[0];
witchitem[j]._iCreateInfo = lvl | CF_WITCH;
witchitem[j]._iIdentified = true;
WitchBookLevel(j);
witchitem[j]._iStatFlag = StoreStatOk(&witchitem[j]);
j++;
bCnt++;
}
} else {
iCnt = GenerateRnd(WITCH_ITEMS - 12) + 10;
maxValue = 140000;
}
for (int i = j; i < iCnt; i++) {
do {
memset(&items[0], 0, sizeof(*items));
items[0]._iSeed = AdvanceRndSeed();
SetRndSeed(items[0]._iSeed);
idata = RndWitchItem(lvl) - 1;
GetItemAttrs(0, idata, lvl);
maxlvl = -1;
if (GenerateRnd(100) <= 5)
maxlvl = 2 * lvl;
if (maxlvl == -1 && items[0]._iMiscId == IMISC_STAFF)
maxlvl = 2 * lvl;
if (maxlvl != -1)
GetItemBonus(0, maxlvl / 2, maxlvl, true, true);
} while (items[0]._iIvalue > maxValue);
witchitem[i] = items[0];
witchitem[i]._iCreateInfo = lvl | CF_WITCH;
witchitem[i]._iIdentified = true;
WitchBookLevel(i);
witchitem[i]._iStatFlag = StoreStatOk(&witchitem[i]);
}
for (int i = iCnt; i < WITCH_ITEMS; i++)
witchitem[i]._itype = ITYPE_NONE;
SortVendor(witchitem + PinnedItemCount);
}
int RndBoyItem(int lvl)
{
return RndVendorItem<PremiumItemOk>(0, lvl);
}
void SpawnBoy(int lvl)
{
int itype;
int ivalue = 0;
bool keepgoing = false;
int count = 0;
HeroClass pc = plr[myplr]._pClass;
int strength = std::max(plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Strength), plr[myplr]._pStrength);
int dexterity = std::max(plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Dexterity), plr[myplr]._pDexterity);
int magic = std::max(plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Magic), plr[myplr]._pMagic);
strength *= 1.2;
dexterity *= 1.2;
magic *= 1.2;
if (boylevel >= (lvl / 2) && !boyitem.isEmpty())
return;
do {
keepgoing = false;
memset(&items[0], 0, sizeof(*items));
items[0]._iSeed = AdvanceRndSeed();
SetRndSeed(items[0]._iSeed);
itype = RndBoyItem(lvl) - 1;
GetItemAttrs(0, itype, lvl);
GetItemBonus(0, lvl, 2 * lvl, true, true);
if (!gbIsHellfire) {
if (items[0]._iIvalue > 90000) {
keepgoing = true; // prevent breaking the do/while loop too early by failing hellfire's condition in while
continue;
}
break;
}
ivalue = 0;
int itemType = items[0]._itype;
switch (itemType) {
case ITYPE_LARMOR:
case ITYPE_MARMOR:
case ITYPE_HARMOR: {
const auto mostValuablePlayerArmor = plr[myplr].GetMostValuableItem(
[](const ItemStruct &item) {
return item._itype == ITYPE_LARMOR
|| item._itype == ITYPE_MARMOR
|| item._itype == ITYPE_HARMOR;
});
ivalue = mostValuablePlayerArmor == nullptr ? 0 : mostValuablePlayerArmor->_iIvalue;
break;
}
case ITYPE_SHIELD:
case ITYPE_AXE:
case ITYPE_BOW:
case ITYPE_MACE:
case ITYPE_SWORD:
case ITYPE_HELM:
case ITYPE_STAFF:
case ITYPE_RING:
case ITYPE_AMULET: {
const auto mostValuablePlayerItem = plr[myplr].GetMostValuableItem(
[itemType](const ItemStruct &item) { return item._itype == itemType; });
ivalue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue;
break;
}
}
ivalue *= 0.8;
count++;
if (count < 200) {
switch (pc) {
case HeroClass::Warrior:
if (itemType == ITYPE_BOW || itemType == ITYPE_STAFF)
ivalue = INT_MAX;
break;
case HeroClass::Rogue:
if (itemType == ITYPE_SWORD || itemType == ITYPE_STAFF || itemType == ITYPE_AXE || itemType == ITYPE_MACE || itemType == ITYPE_SHIELD)
ivalue = INT_MAX;
break;
case HeroClass::Sorcerer:
if (itemType == ITYPE_STAFF || itemType == ITYPE_AXE || itemType == ITYPE_BOW || itemType == ITYPE_MACE)
ivalue = INT_MAX;
break;
case HeroClass::Monk:
if (itemType == ITYPE_BOW || itemType == ITYPE_MARMOR || itemType == ITYPE_SHIELD || itemType == ITYPE_MACE)
ivalue = INT_MAX;
break;
case HeroClass::Bard:
if (itemType == ITYPE_AXE || itemType == ITYPE_MACE || itemType == ITYPE_STAFF)
ivalue = INT_MAX;
break;
case HeroClass::Barbarian:
if (itemType == ITYPE_BOW || itemType == ITYPE_STAFF)
ivalue = INT_MAX;
break;
}
}
} while (keepgoing
|| ((
items[0]._iIvalue > 200000
|| items[0]._iMinStr > strength
|| items[0]._iMinMag > magic
|| items[0]._iMinDex > dexterity
|| items[0]._iIvalue < ivalue)
&& count < 250));
boyitem = items[0];
boyitem._iCreateInfo = lvl | CF_BOY;
boyitem._iIdentified = true;
boyitem._iStatFlag = StoreStatOk(&boyitem);
boylevel = lvl / 2;
}
bool HealerItemOk(int i)
{
if (AllItemsList[i].itype != ITYPE_MISC)
return false;
if (AllItemsList[i].iMiscId == IMISC_SCROLL)
return AllItemsList[i].iSpell == SPL_HEAL;
if (AllItemsList[i].iMiscId == IMISC_SCROLLT)
return AllItemsList[i].iSpell == SPL_HEALOTHER && gbIsMultiplayer;
if (!gbIsMultiplayer) {
if (AllItemsList[i].iMiscId == IMISC_ELIXSTR)
return !gbIsHellfire || plr[myplr]._pBaseStr < plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Strength);
if (AllItemsList[i].iMiscId == IMISC_ELIXMAG)
return !gbIsHellfire || plr[myplr]._pBaseMag < plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Magic);
if (AllItemsList[i].iMiscId == IMISC_ELIXDEX)
return !gbIsHellfire || plr[myplr]._pBaseDex < plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Dexterity);
if (AllItemsList[i].iMiscId == IMISC_ELIXVIT)
return !gbIsHellfire || plr[myplr]._pBaseVit < plr[myplr].GetMaximumAttributeValue(CharacterAttribute::Vitality);
}
if (AllItemsList[i].iMiscId == IMISC_REJUV)
return true;
if (AllItemsList[i].iMiscId == IMISC_FULLREJUV)
return true;
return false;
}
int RndHealerItem(int lvl)
{
return RndVendorItem<HealerItemOk>(0, lvl);
}
void SpawnHealer(int lvl)
{
constexpr int PinnedItemCount = 2;
int srnd;
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_HEAL, 1);
healitem[0] = items[0];
healitem[0]._iCreateInfo = lvl;
healitem[0]._iStatFlag = true;
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_FULLHEAL, 1);
healitem[1] = items[0];
healitem[1]._iCreateInfo = lvl;
healitem[1]._iStatFlag = true;
if (gbIsMultiplayer) {
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_RESURRECT, 1);
healitem[2] = items[0];
healitem[2]._iCreateInfo = lvl;
healitem[2]._iStatFlag = true;
srnd = 3;
} else {
srnd = 2;
}
int nsi = GenerateRnd(gbIsHellfire ? 10 : 8) + 10;
for (int i = srnd; i < nsi; i++) {
memset(&items[0], 0, sizeof(*items));
items[0]._iSeed = AdvanceRndSeed();
SetRndSeed(items[0]._iSeed);
int itype = RndHealerItem(lvl) - 1;
GetItemAttrs(0, itype, lvl);
healitem[i] = items[0];
healitem[i]._iCreateInfo = lvl | CF_HEALER;
healitem[i]._iIdentified = true;
healitem[i]._iStatFlag = StoreStatOk(&healitem[i]);
}
for (int i = nsi; i < 20; i++) {
healitem[i]._itype = ITYPE_NONE;
}
SortVendor(healitem + PinnedItemCount);
}
void SpawnStoreGold()
{
memset(&items[0], 0, sizeof(*items));
GetItemAttrs(0, IDI_GOLD, 1);
golditem = items[0];
golditem._iStatFlag = true;
}
void RecreateSmithItem(int ii, int lvl, int iseed)
{
SetRndSeed(iseed);
int itype = RndSmithItem(lvl) - 1;
GetItemAttrs(ii, itype, lvl);
items[ii]._iSeed = iseed;
items[ii]._iCreateInfo = lvl | CF_SMITH;
items[ii]._iIdentified = true;
}
void RecreatePremiumItem(int ii, int plvl, int iseed)
{
SetRndSeed(iseed);
int itype = RndPremiumItem(plvl / 4, plvl) - 1;
GetItemAttrs(ii, itype, plvl);
GetItemBonus(ii, plvl / 2, plvl, true, !gbIsHellfire);
items[ii]._iSeed = iseed;
items[ii]._iCreateInfo = plvl | CF_SMITHPREMIUM;
items[ii]._iIdentified = true;
}
void RecreateBoyItem(int ii, int lvl, int iseed)
{
SetRndSeed(iseed);
int itype = RndBoyItem(lvl) - 1;
GetItemAttrs(ii, itype, lvl);
GetItemBonus(ii, lvl, 2 * lvl, true, true);
items[ii]._iSeed = iseed;
items[ii]._iCreateInfo = lvl | CF_BOY;
items[ii]._iIdentified = true;
}
void RecreateWitchItem(int ii, int idx, int lvl, int iseed)
{
if (idx == IDI_MANA || idx == IDI_FULLMANA || idx == IDI_PORTAL) {
GetItemAttrs(ii, idx, lvl);
} else if (gbIsHellfire && idx >= 114 && idx <= 117) {
SetRndSeed(iseed);
GenerateRnd(1);
GetItemAttrs(ii, idx, lvl);
} else {
SetRndSeed(iseed);
int itype = RndWitchItem(lvl) - 1;
GetItemAttrs(ii, itype, lvl);
int iblvl = -1;
if (GenerateRnd(100) <= 5)
iblvl = 2 * lvl;
if (iblvl == -1 && items[ii]._iMiscId == IMISC_STAFF)
iblvl = 2 * lvl;
if (iblvl != -1)
GetItemBonus(ii, iblvl / 2, iblvl, true, true);
}
items[ii]._iSeed = iseed;
items[ii]._iCreateInfo = lvl | CF_WITCH;
items[ii]._iIdentified = true;
}
void RecreateHealerItem(int ii, int idx, int lvl, int iseed)
{
if (idx == IDI_HEAL || idx == IDI_FULLHEAL || idx == IDI_RESURRECT) {
GetItemAttrs(ii, idx, lvl);
} else {
SetRndSeed(iseed);
int itype = RndHealerItem(lvl) - 1;
GetItemAttrs(ii, itype, lvl);
}
items[ii]._iSeed = iseed;
items[ii]._iCreateInfo = lvl | CF_HEALER;
items[ii]._iIdentified = true;
}
void RecreateTownItem(int ii, int idx, uint16_t icreateinfo, int iseed)
{
if ((icreateinfo & CF_SMITH) != 0)
RecreateSmithItem(ii, icreateinfo & CF_LEVEL, iseed);
else if ((icreateinfo & CF_SMITHPREMIUM) != 0)
RecreatePremiumItem(ii, icreateinfo & CF_LEVEL, iseed);
else if ((icreateinfo & CF_BOY) != 0)
RecreateBoyItem(ii, icreateinfo & CF_LEVEL, iseed);
else if ((icreateinfo & CF_WITCH) != 0)
RecreateWitchItem(ii, idx, icreateinfo & CF_LEVEL, iseed);
else if ((icreateinfo & CF_HEALER) != 0)
RecreateHealerItem(ii, idx, icreateinfo & CF_LEVEL, iseed);
}
void RecalcStoreStats()
{
for (int i = 0; i < SMITH_ITEMS; i++) {
if (!smithitem[i].isEmpty()) {
smithitem[i]._iStatFlag = StoreStatOk(&smithitem[i]);
}
}
for (int i = 0; i < SMITH_PREMIUM_ITEMS; i++) {
if (!premiumitems[i].isEmpty()) {
premiumitems[i]._iStatFlag = StoreStatOk(&premiumitems[i]);
}
}
for (int i = 0; i < 20; i++) {
if (!witchitem[i].isEmpty()) {
witchitem[i]._iStatFlag = StoreStatOk(&witchitem[i]);
}
}
for (int i = 0; i < 20; i++) {
if (!healitem[i].isEmpty()) {
healitem[i]._iStatFlag = StoreStatOk(&healitem[i]);
}
}
boyitem._iStatFlag = StoreStatOk(&boyitem);
}
int ItemNoFlippy()
{
int r = itemactive[numitems - 1];
items[r]._iAnimFrame = items[r]._iAnimLen;
items[r]._iAnimFlag = false;
items[r]._iSelFlag = 1;
return r;
}
void CreateSpellBook(Point position, spell_id ispell, bool sendmsg, bool delta)
{
int lvl = currlevel;
if (gbIsHellfire) {
lvl = GetSpellBookLevel(ispell) + 1;
if (lvl < 1) {
return;
}
}
int idx = RndTypeItems(ITYPE_MISC, IMISC_BOOK, lvl);
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
while (true) {
memset(&items[ii], 0, sizeof(*items));
SetupAllItems(ii, idx, AdvanceRndSeed(), 2 * lvl, 1, true, false, delta);
if (items[ii]._iMiscId == IMISC_BOOK && items[ii]._iSpell == ispell)
break;
}
GetSuperItemSpace(position, ii);
if (sendmsg)
NetSendCmdDItem(false, ii);
if (delta)
DeltaAddItem(ii);
}
static void CreateMagicItem(Point position, int lvl, int imisc, int imid, int icurs, bool sendmsg, bool delta)
{
if (numitems >= MAXITEMS)
return;
int ii = AllocateItem();
int idx = RndTypeItems(imisc, imid, lvl);
while (true) {
memset(&items[ii], 0, sizeof(*items));
SetupAllItems(ii, idx, AdvanceRndSeed(), 2 * lvl, 1, true, false, delta);
if (items[ii]._iCurs == icurs)
break;
idx = RndTypeItems(imisc, imid, lvl);
}
GetSuperItemSpace(position, ii);
if (sendmsg)
NetSendCmdDItem(false, ii);
if (delta)
DeltaAddItem(ii);
}
void CreateMagicArmor(Point position, int imisc, int icurs, bool sendmsg, bool delta)
{
int lvl = items_get_currlevel();
CreateMagicItem(position, lvl, imisc, IMISC_NONE, icurs, sendmsg, delta);
}
void CreateAmulet(Point position, int lvl, bool sendmsg, bool delta)
{
CreateMagicItem(position, lvl, ITYPE_AMULET, IMISC_AMULET, ICURS_AMULET, sendmsg, delta);
}
void CreateMagicWeapon(Point position, int imisc, int icurs, bool sendmsg, bool delta)
{
int imid = IMISC_NONE;
if (imisc == ITYPE_STAFF)
imid = IMISC_STAFF;
int curlv = items_get_currlevel();
CreateMagicItem(position, curlv, imisc, imid, icurs, sendmsg, delta);
}
static void NextItemRecord(int i)
{
gnNumGetRecords--;
if (gnNumGetRecords == 0) {
return;
}
itemrecord[i].dwTimestamp = itemrecord[gnNumGetRecords].dwTimestamp;
itemrecord[i].nSeed = itemrecord[gnNumGetRecords].nSeed;
itemrecord[i].wCI = itemrecord[gnNumGetRecords].wCI;
itemrecord[i].nIndex = itemrecord[gnNumGetRecords].nIndex;
}
bool GetItemRecord(int nSeed, uint16_t wCI, int nIndex)
{
uint32_t ticks = SDL_GetTicks();
for (int i = 0; i < gnNumGetRecords; i++) {
if (ticks - itemrecord[i].dwTimestamp > 6000) {
NextItemRecord(i);
i--;
} else if (nSeed == itemrecord[i].nSeed && wCI == itemrecord[i].wCI && nIndex == itemrecord[i].nIndex) {
return false;
}
}
return true;
}
void SetItemRecord(int nSeed, uint16_t wCI, int nIndex)
{
uint32_t ticks = SDL_GetTicks();
if (gnNumGetRecords == MAXITEMS) {
return;
}
itemrecord[gnNumGetRecords].dwTimestamp = ticks;
itemrecord[gnNumGetRecords].nSeed = nSeed;
itemrecord[gnNumGetRecords].wCI = wCI;
itemrecord[gnNumGetRecords].nIndex = nIndex;
gnNumGetRecords++;
}
void PutItemRecord(int nSeed, uint16_t wCI, int nIndex)
{
uint32_t ticks = SDL_GetTicks();
for (int i = 0; i < gnNumGetRecords; i++) {
if (ticks - itemrecord[i].dwTimestamp > 6000) {
NextItemRecord(i);
i--;
} else if (nSeed == itemrecord[i].nSeed && wCI == itemrecord[i].wCI && nIndex == itemrecord[i].nIndex) {
NextItemRecord(i);
break;
}
}
}
} // namespace devilution