You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4959 lines
130 KiB
4959 lines
130 KiB
/** |
|
* @file items.cpp |
|
* |
|
* Implementation of item functionality. |
|
*/ |
|
#include "items.h" |
|
|
|
#include <algorithm> |
|
#include <bitset> |
|
#ifdef _DEBUG |
|
#include <random> |
|
#endif |
|
#include <climits> |
|
#include <cstdint> |
|
|
|
#include <fmt/format.h> |
|
|
|
#include "cursor.h" |
|
#include "doom.h" |
|
#include "dx.h" |
|
#include "engine/cel_sprite.hpp" |
|
#include "engine/load_cel.hpp" |
|
#include "engine/random.hpp" |
|
#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 "player.h" |
|
#include "stores.h" |
|
#include "utils/language.h" |
|
#include "utils/math.h" |
|
#include "utils/stdcompat/algorithm.hpp" |
|
|
|
namespace devilution { |
|
|
|
/** Contains the items on ground in the current game. */ |
|
Item Items[MAXITEMS + 1]; |
|
int ActiveItems[MAXITEMS]; |
|
int ActiveItemCount; |
|
int AvailableItems[MAXITEMS]; |
|
bool ShowUniqueItemInfoBox; |
|
CornerStoneStruct CornerStone; |
|
bool UniqueItemFlags[128]; |
|
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 |
|
}; |
|
|
|
/** 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, |
|
}; |
|
|
|
namespace { |
|
|
|
std::optional<CelSprite> itemanims[ITEMTYPES]; |
|
|
|
enum class PlayerArmorGraphic : uint8_t { |
|
// clang-format off |
|
Light = 0, |
|
Medium = 1 << 4, |
|
Heavy = 1 << 5, |
|
// clang-format on |
|
}; |
|
|
|
Item curruitem; |
|
|
|
/** Holds item get records, tracking items being recently looted. This is in an effort to prevent items being picked up more than once. */ |
|
ItemGetRecordStruct itemrecord[MAXITEMS]; |
|
|
|
bool itemhold[3][3]; |
|
|
|
/** Specifies the number of active item get records. */ |
|
int gnNumGetRecords; |
|
|
|
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") |
|
}; |
|
|
|
/** 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 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 IsPrefixValidForItemType(int i, int flgs) |
|
{ |
|
int itemTypes = ItemPrefixes[i].PLIType; |
|
|
|
if (!gbIsHellfire) { |
|
if (i > 82) |
|
return false; |
|
|
|
if (i >= 12 && i <= 20) |
|
itemTypes &= ~PLT_STAFF; |
|
} |
|
|
|
return (flgs & itemTypes) != 0; |
|
} |
|
|
|
bool IsSuffixValidForItemType(int i, int flgs) |
|
{ |
|
int itemTypes = ItemSuffixes[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)) |
|
itemTypes &= ~PLT_STAFF; |
|
} |
|
|
|
return (flgs & itemTypes) != 0; |
|
} |
|
|
|
int ItemsGetCurrlevel() |
|
{ |
|
int lvl = currlevel; |
|
if (currlevel >= 17 && currlevel <= 20) |
|
lvl = currlevel - 8; |
|
if (currlevel >= 21 && currlevel <= 24) |
|
lvl = currlevel - 7; |
|
|
|
return lvl; |
|
} |
|
|
|
bool ItemPlace(Point position) |
|
{ |
|
if (dMonster[position.x][position.y] != 0) |
|
return false; |
|
if (dPlayer[position.x][position.y] != 0) |
|
return false; |
|
if (dItem[position.x][position.y] != 0) |
|
return false; |
|
if (dObject[position.x][position.y] != 0) |
|
return false; |
|
if ((dFlags[position.x][position.y] & BFLAG_POPULATED) != 0) |
|
return false; |
|
if (nSolidTable[dPiece[position.x][position.y]]) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
Point GetRandomAvailableItemPosition() |
|
{ |
|
Point position = {}; |
|
do { |
|
position = Point { GenerateRnd(80), GenerateRnd(80) } + Displacement { 16, 16 }; |
|
} while (!ItemPlace(position)); |
|
|
|
return position; |
|
} |
|
|
|
void AddInitItems() |
|
{ |
|
int curlv = ItemsGetCurrlevel(); |
|
int rnd = GenerateRnd(3) + 3; |
|
for (int j = 0; j < rnd; j++) { |
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
|
|
Point position = GetRandomAvailableItemPosition(); |
|
item.position = position; |
|
|
|
dItem[position.x][position.y] = ii + 1; |
|
|
|
item._iSeed = AdvanceRndSeed(); |
|
SetRndSeed(item._iSeed); |
|
|
|
if (GenerateRnd(2) != 0) |
|
GetItemAttrs(item, IDI_HEAL, curlv); |
|
else |
|
GetItemAttrs(item, IDI_MANA, curlv); |
|
|
|
item._iCreateInfo = curlv | CF_PREGEN; |
|
SetupItem(item); |
|
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames; |
|
item._iAnimFlag = false; |
|
item._iSelFlag = 1; |
|
DeltaAddItem(ii); |
|
} |
|
} |
|
|
|
void SpawnNote() |
|
{ |
|
int id; |
|
|
|
switch (currlevel) { |
|
case 22: |
|
id = IDI_NOTE2; |
|
break; |
|
case 23: |
|
id = IDI_NOTE3; |
|
break; |
|
default: |
|
id = IDI_NOTE1; |
|
break; |
|
} |
|
|
|
Point position = GetRandomAvailableItemPosition(); |
|
SpawnQuestItem(id, position, 0, 1); |
|
} |
|
|
|
void CalcSelfItems(Player &player) |
|
{ |
|
int sa = 0; |
|
int ma = 0; |
|
int da = 0; |
|
for (int i = 0; i < NUM_INVLOC; i++) { |
|
auto &equipment = player.InvBody[i]; |
|
if (!equipment.isEmpty()) { |
|
equipment._iStatFlag = true; |
|
if (equipment._iIdentified) { |
|
sa += equipment._iPLStr; |
|
ma += equipment._iPLMag; |
|
da += equipment._iPLDex; |
|
} |
|
} |
|
} |
|
|
|
bool changeflag; |
|
do { |
|
changeflag = false; |
|
auto *pi = player.InvBody; |
|
for (int i = 0; i < NUM_INVLOC; i++, pi++) { |
|
if (!pi->isEmpty() && pi->_iStatFlag) { |
|
bool 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); |
|
} |
|
|
|
bool ItemMinStats(const Player &player, Item &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(Player &player) |
|
{ |
|
for (int i = 0; i < player._pNumInv; i++) { |
|
auto &item = player.InvList[i]; |
|
item._iStatFlag = ItemMinStats(player, item); |
|
} |
|
|
|
for (auto &item : player.SpdList) { |
|
if (!item.isEmpty()) { |
|
item._iStatFlag = ItemMinStats(player, item); |
|
} |
|
} |
|
} |
|
|
|
void WitchBookLevel(int ii) |
|
{ |
|
if (witchitem[ii]._iMiscId != IMISC_BOOK) |
|
return; |
|
witchitem[ii]._iMinMag = spelldata[witchitem[ii]._iSpell].sMinInt; |
|
int8_t spellLevel = Players[MyPlayerId]._pSplLvl[witchitem[ii]._iSpell]; |
|
while (spellLevel > 0) { |
|
witchitem[ii]._iMinMag += 20 * witchitem[ii]._iMinMag / 100; |
|
spellLevel--; |
|
if (witchitem[ii]._iMinMag + 20 * witchitem[ii]._iMinMag / 100 > 255) { |
|
witchitem[ii]._iMinMag = 255; |
|
spellLevel = 0; |
|
} |
|
} |
|
} |
|
|
|
bool StoreStatOk(Item &item) |
|
{ |
|
const auto &myPlayer = Players[MyPlayerId]; |
|
|
|
if (myPlayer._pStrength < item._iMinStr) |
|
return false; |
|
if (myPlayer._pMagic < item._iMinMag) |
|
return false; |
|
if (myPlayer._pDexterity < item._iMinDex) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
void CalcPlrBookVals(Player &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; |
|
int8_t spellLevel = player._pSplLvl[player.InvList[i]._iSpell]; |
|
|
|
while (spellLevel != 0) { |
|
player.InvList[i]._iMinMag += 20 * player.InvList[i]._iMinMag / 100; |
|
spellLevel--; |
|
if (player.InvList[i]._iMinMag + 20 * player.InvList[i]._iMinMag / 100 > 255) { |
|
player.InvList[i]._iMinMag = 255; |
|
spellLevel = 0; |
|
} |
|
} |
|
player.InvList[i]._iStatFlag = ItemMinStats(player, player.InvList[i]); |
|
} |
|
} |
|
} |
|
|
|
void SetPlrHandSeed(Item &item, int iseed) |
|
{ |
|
item._iSeed = iseed; |
|
} |
|
|
|
bool GetItemSpace(Point position, int8_t inum) |
|
{ |
|
int xx = 0; |
|
int 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++; |
|
} |
|
|
|
bool savail = false; |
|
for (int j = 0; j < 3; j++) { |
|
for (int i = 0; i < 3; i++) { // NOLINT(modernize-loop-convert) |
|
if (itemhold[i][j]) |
|
savail = true; |
|
} |
|
} |
|
|
|
int 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; |
|
} |
|
|
|
void GetSuperItemSpace(Point position, int8_t inum) |
|
{ |
|
Point positionToCheck = position; |
|
if (GetItemSpace(positionToCheck, inum)) |
|
return; |
|
for (int k = 2; k < 50; k++) { |
|
for (int j = -k; j <= k; j++) { |
|
for (int i = -k; i <= k; i++) { |
|
Displacement offset = { i, j }; |
|
positionToCheck = position + offset; |
|
if (!ItemSpaceOk(positionToCheck)) |
|
continue; |
|
Items[inum].position = positionToCheck; |
|
dItem[positionToCheck.x][positionToCheck.y] = inum + 1; |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CalcItemValue(Item &item) |
|
{ |
|
int v = item._iVMult1 + item._iVMult2; |
|
if (v > 0) { |
|
v *= item._ivalue; |
|
} |
|
if (v < 0) { |
|
v = item._ivalue / v; |
|
} |
|
v = item._iVAdd1 + item._iVAdd2 + v; |
|
item._iIvalue = std::max(v, 1); |
|
} |
|
|
|
void GetBookSpell(Item &item, 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(item._iName, _(spelldata[bs].sNameText)); |
|
strcat(item._iIName, _(spelldata[bs].sNameText)); |
|
item._iSpell = bs; |
|
item._iMinMag = spelldata[bs].sMinInt; |
|
item._ivalue += spelldata[bs].sBookCost; |
|
item._iIvalue += spelldata[bs].sBookCost; |
|
if (spelldata[bs].sType == STYPE_FIRE) |
|
item._iCurs = ICURS_BOOK_RED; |
|
else if (spelldata[bs].sType == STYPE_LIGHTNING) |
|
item._iCurs = ICURS_BOOK_BLUE; |
|
else if (spelldata[bs].sType == STYPE_MAGIC) |
|
item._iCurs = ICURS_BOOK_GREY; |
|
} |
|
|
|
int RndPL(int param1, int param2) |
|
{ |
|
return param1 + GenerateRnd(param2 - param1 + 1); |
|
} |
|
|
|
int CalculateToHitBonus(int level) |
|
{ |
|
switch (level) { |
|
case -50: |
|
return -RndPL(6, 10); |
|
case -25: |
|
return -RndPL(1, 5); |
|
case 20: |
|
return RndPL(1, 5); |
|
case 36: |
|
return RndPL(6, 10); |
|
case 51: |
|
return RndPL(11, 15); |
|
case 66: |
|
return RndPL(16, 20); |
|
case 81: |
|
return RndPL(21, 30); |
|
case 96: |
|
return RndPL(31, 40); |
|
case 111: |
|
return RndPL(41, 50); |
|
case 126: |
|
return RndPL(51, 75); |
|
case 151: |
|
return RndPL(76, 100); |
|
default: |
|
app_fatal("Unknown to hit bonus"); |
|
} |
|
} |
|
|
|
int SaveItemPower(Item &item, const ItemPower &power) |
|
{ |
|
int r = RndPL(power.param1, power.param2); |
|
|
|
switch (power.type) { |
|
case IPL_TOHIT: |
|
item._iPLToHit += r; |
|
break; |
|
case IPL_TOHIT_CURSE: |
|
item._iPLToHit -= r; |
|
break; |
|
case IPL_DAMP: |
|
item._iPLDam += r; |
|
break; |
|
case IPL_DAMP_CURSE: |
|
item._iPLDam -= r; |
|
break; |
|
case IPL_DOPPELGANGER: |
|
item._iDamAcFlags |= ISPLHF_DOPPELGANGER; |
|
[[fallthrough]]; |
|
case IPL_TOHIT_DAMP: |
|
r = RndPL(power.param1, power.param2); |
|
item._iPLDam += r; |
|
item._iPLToHit += CalculateToHitBonus(power.param1); |
|
break; |
|
case IPL_TOHIT_DAMP_CURSE: |
|
item._iPLDam -= r; |
|
item._iPLToHit += CalculateToHitBonus(-power.param1); |
|
break; |
|
case IPL_ACP: |
|
item._iPLAC += r; |
|
break; |
|
case IPL_ACP_CURSE: |
|
item._iPLAC -= r; |
|
break; |
|
case IPL_SETAC: |
|
item._iAC = r; |
|
break; |
|
case IPL_AC_CURSE: |
|
item._iAC -= r; |
|
break; |
|
case IPL_FIRERES: |
|
item._iPLFR += r; |
|
break; |
|
case IPL_LIGHTRES: |
|
item._iPLLR += r; |
|
break; |
|
case IPL_MAGICRES: |
|
item._iPLMR += r; |
|
break; |
|
case IPL_ALLRES: |
|
item._iPLFR = std::max(item._iPLFR + r, 0); |
|
item._iPLLR = std::max(item._iPLLR + r, 0); |
|
item._iPLMR = std::max(item._iPLMR + r, 0); |
|
break; |
|
case IPL_SPLLVLADD: |
|
item._iSplLvlAdd = r; |
|
break; |
|
case IPL_CHARGES: |
|
item._iCharges *= power.param1; |
|
item._iMaxCharges = item._iCharges; |
|
break; |
|
case IPL_SPELL: |
|
item._iSpell = static_cast<spell_id>(power.param1); |
|
item._iCharges = power.param2; |
|
item._iMaxCharges = power.param2; |
|
break; |
|
case IPL_FIREDAM: |
|
item._iFlags |= ISPL_FIREDAM; |
|
item._iFlags &= ~ISPL_LIGHTDAM; |
|
item._iFMinDam = power.param1; |
|
item._iFMaxDam = power.param2; |
|
item._iLMinDam = 0; |
|
item._iLMaxDam = 0; |
|
break; |
|
case IPL_LIGHTDAM: |
|
item._iFlags |= ISPL_LIGHTDAM; |
|
item._iFlags &= ~ISPL_FIREDAM; |
|
item._iLMinDam = power.param1; |
|
item._iLMaxDam = power.param2; |
|
item._iFMinDam = 0; |
|
item._iFMaxDam = 0; |
|
break; |
|
case IPL_STR: |
|
item._iPLStr += r; |
|
break; |
|
case IPL_STR_CURSE: |
|
item._iPLStr -= r; |
|
break; |
|
case IPL_MAG: |
|
item._iPLMag += r; |
|
break; |
|
case IPL_MAG_CURSE: |
|
item._iPLMag -= r; |
|
break; |
|
case IPL_DEX: |
|
item._iPLDex += r; |
|
break; |
|
case IPL_DEX_CURSE: |
|
item._iPLDex -= r; |
|
break; |
|
case IPL_VIT: |
|
item._iPLVit += r; |
|
break; |
|
case IPL_VIT_CURSE: |
|
item._iPLVit -= r; |
|
break; |
|
case IPL_ATTRIBS: |
|
item._iPLStr += r; |
|
item._iPLMag += r; |
|
item._iPLDex += r; |
|
item._iPLVit += r; |
|
break; |
|
case IPL_ATTRIBS_CURSE: |
|
item._iPLStr -= r; |
|
item._iPLMag -= r; |
|
item._iPLDex -= r; |
|
item._iPLVit -= r; |
|
break; |
|
case IPL_GETHIT_CURSE: |
|
item._iPLGetHit += r; |
|
break; |
|
case IPL_GETHIT: |
|
item._iPLGetHit -= r; |
|
break; |
|
case IPL_LIFE: |
|
item._iPLHP += r << 6; |
|
break; |
|
case IPL_LIFE_CURSE: |
|
item._iPLHP -= r << 6; |
|
break; |
|
case IPL_MANA: |
|
item._iPLMana += r << 6; |
|
drawmanaflag = true; |
|
break; |
|
case IPL_MANA_CURSE: |
|
item._iPLMana -= r << 6; |
|
drawmanaflag = true; |
|
break; |
|
case IPL_DUR: { |
|
int bonus = r * item._iMaxDur / 100; |
|
item._iMaxDur += bonus; |
|
item._iDurability += bonus; |
|
} break; |
|
case IPL_CRYSTALLINE: |
|
item._iPLDam += 140 + r * 2; |
|
[[fallthrough]]; |
|
case IPL_DUR_CURSE: |
|
item._iMaxDur -= r * item._iMaxDur / 100; |
|
item._iMaxDur = std::max<uint8_t>(item._iMaxDur, 1); |
|
item._iDurability = item._iMaxDur; |
|
break; |
|
case IPL_INDESTRUCTIBLE: |
|
item._iDurability = DUR_INDESTRUCTIBLE; |
|
item._iMaxDur = DUR_INDESTRUCTIBLE; |
|
break; |
|
case IPL_LIGHT: |
|
item._iPLLight += power.param1; |
|
break; |
|
case IPL_LIGHT_CURSE: |
|
item._iPLLight -= power.param1; |
|
break; |
|
case IPL_MULT_ARROWS: |
|
item._iFlags |= ISPL_MULT_ARROWS; |
|
break; |
|
case IPL_FIRE_ARROWS: |
|
item._iFlags |= ISPL_FIRE_ARROWS; |
|
item._iFlags &= ~ISPL_LIGHT_ARROWS; |
|
item._iFMinDam = power.param1; |
|
item._iFMaxDam = power.param2; |
|
item._iLMinDam = 0; |
|
item._iLMaxDam = 0; |
|
break; |
|
case IPL_LIGHT_ARROWS: |
|
item._iFlags |= ISPL_LIGHT_ARROWS; |
|
item._iFlags &= ~ISPL_FIRE_ARROWS; |
|
item._iLMinDam = power.param1; |
|
item._iLMaxDam = power.param2; |
|
item._iFMinDam = 0; |
|
item._iFMaxDam = 0; |
|
break; |
|
case IPL_FIREBALL: |
|
item._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS); |
|
item._iFMinDam = power.param1; |
|
item._iFMaxDam = power.param2; |
|
item._iLMinDam = 0; |
|
item._iLMaxDam = 0; |
|
break; |
|
case IPL_THORNS: |
|
item._iFlags |= ISPL_THORNS; |
|
break; |
|
case IPL_NOMANA: |
|
item._iFlags |= ISPL_NOMANA; |
|
drawmanaflag = true; |
|
break; |
|
case IPL_NOHEALPLR: |
|
item._iFlags |= ISPL_NOHEALPLR; |
|
break; |
|
case IPL_ABSHALFTRAP: |
|
item._iFlags |= ISPL_ABSHALFTRAP; |
|
break; |
|
case IPL_KNOCKBACK: |
|
item._iFlags |= ISPL_KNOCKBACK; |
|
break; |
|
case IPL_3XDAMVDEM: |
|
item._iFlags |= ISPL_3XDAMVDEM; |
|
break; |
|
case IPL_ALLRESZERO: |
|
item._iFlags |= ISPL_ALLRESZERO; |
|
break; |
|
case IPL_NOHEALMON: |
|
item._iFlags |= ISPL_NOHEALMON; |
|
break; |
|
case IPL_STEALMANA: |
|
if (power.param1 == 3) |
|
item._iFlags |= ISPL_STEALMANA_3; |
|
if (power.param1 == 5) |
|
item._iFlags |= ISPL_STEALMANA_5; |
|
drawmanaflag = true; |
|
break; |
|
case IPL_STEALLIFE: |
|
if (power.param1 == 3) |
|
item._iFlags |= ISPL_STEALLIFE_3; |
|
if (power.param1 == 5) |
|
item._iFlags |= ISPL_STEALLIFE_5; |
|
drawhpflag = true; |
|
break; |
|
case IPL_TARGAC: |
|
if (gbIsHellfire) |
|
item._iPLEnAc = power.param1; |
|
else |
|
item._iPLEnAc += r; |
|
break; |
|
case IPL_FASTATTACK: |
|
if (power.param1 == 1) |
|
item._iFlags |= ISPL_QUICKATTACK; |
|
if (power.param1 == 2) |
|
item._iFlags |= ISPL_FASTATTACK; |
|
if (power.param1 == 3) |
|
item._iFlags |= ISPL_FASTERATTACK; |
|
if (power.param1 == 4) |
|
item._iFlags |= ISPL_FASTESTATTACK; |
|
break; |
|
case IPL_FASTRECOVER: |
|
if (power.param1 == 1) |
|
item._iFlags |= ISPL_FASTRECOVER; |
|
if (power.param1 == 2) |
|
item._iFlags |= ISPL_FASTERRECOVER; |
|
if (power.param1 == 3) |
|
item._iFlags |= ISPL_FASTESTRECOVER; |
|
break; |
|
case IPL_FASTBLOCK: |
|
item._iFlags |= ISPL_FASTBLOCK; |
|
break; |
|
case IPL_DAMMOD: |
|
item._iPLDamMod += r; |
|
break; |
|
case IPL_RNDARROWVEL: |
|
item._iFlags |= ISPL_RNDARROWVEL; |
|
break; |
|
case IPL_SETDAM: |
|
item._iMinDam = power.param1; |
|
item._iMaxDam = power.param2; |
|
break; |
|
case IPL_SETDUR: |
|
item._iDurability = power.param1; |
|
item._iMaxDur = power.param1; |
|
break; |
|
case IPL_FASTSWING: |
|
item._iFlags |= ISPL_FASTERATTACK; |
|
break; |
|
case IPL_ONEHAND: |
|
item._iLoc = ILOC_ONEHAND; |
|
break; |
|
case IPL_DRAINLIFE: |
|
item._iFlags |= ISPL_DRAINLIFE; |
|
break; |
|
case IPL_RNDSTEALLIFE: |
|
item._iFlags |= ISPL_RNDSTEALLIFE; |
|
break; |
|
case IPL_INFRAVISION: |
|
item._iFlags |= ISPL_INFRAVISION; |
|
break; |
|
case IPL_NOMINSTR: |
|
item._iMinStr = 0; |
|
break; |
|
case IPL_INVCURS: |
|
item._iCurs = power.param1; |
|
break; |
|
case IPL_ADDACLIFE: |
|
item._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS); |
|
item._iFMinDam = power.param1; |
|
item._iFMaxDam = power.param2; |
|
item._iLMinDam = 1; |
|
item._iLMaxDam = 0; |
|
break; |
|
case IPL_ADDMANAAC: |
|
item._iFlags |= (ISPL_LIGHTDAM | ISPL_FIREDAM); |
|
item._iFMinDam = power.param1; |
|
item._iFMaxDam = power.param2; |
|
item._iLMinDam = 2; |
|
item._iLMaxDam = 0; |
|
break; |
|
case IPL_FIRERESCLVL: |
|
item._iPLFR = 30 - Players[MyPlayerId]._pLevel; |
|
item._iPLFR = std::max<int16_t>(item._iPLFR, 0); |
|
break; |
|
case IPL_FIRERES_CURSE: |
|
item._iPLFR -= r; |
|
break; |
|
case IPL_LIGHTRES_CURSE: |
|
item._iPLLR -= r; |
|
break; |
|
case IPL_MAGICRES_CURSE: |
|
item._iPLMR -= r; |
|
break; |
|
case IPL_ALLRES_CURSE: |
|
item._iPLFR -= r; |
|
item._iPLLR -= r; |
|
item._iPLMR -= r; |
|
break; |
|
case IPL_DEVASTATION: |
|
item._iDamAcFlags |= ISPLHF_DEVASTATION; |
|
break; |
|
case IPL_DECAY: |
|
item._iDamAcFlags |= ISPLHF_DECAY; |
|
item._iPLDam += r; |
|
break; |
|
case IPL_PERIL: |
|
item._iDamAcFlags |= ISPLHF_PERIL; |
|
break; |
|
case IPL_JESTERS: |
|
item._iDamAcFlags |= ISPLHF_JESTERS; |
|
break; |
|
case IPL_ACDEMON: |
|
item._iDamAcFlags |= ISPLHF_ACDEMON; |
|
break; |
|
case IPL_ACUNDEAD: |
|
item._iDamAcFlags |= ISPLHF_ACUNDEAD; |
|
break; |
|
case IPL_MANATOLIFE: { |
|
int portion = ((Players[MyPlayerId]._pMaxManaBase >> 6) * 50 / 100) << 6; |
|
item._iPLMana -= portion; |
|
item._iPLHP += portion; |
|
} break; |
|
case IPL_LIFETOMANA: { |
|
int portion = ((Players[MyPlayerId]._pMaxHPBase >> 6) * 40 / 100) << 6; |
|
item._iPLHP -= portion; |
|
item._iPLMana += portion; |
|
} break; |
|
default: |
|
break; |
|
} |
|
|
|
return r; |
|
} |
|
|
|
bool StringInPanel(const char *str) |
|
{ |
|
return GetLineWidth(str, GameFontSmall, 0) < 125; |
|
} |
|
|
|
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 SaveItemAffix(Item &item, const PLStruct &affix) |
|
{ |
|
auto power = affix.power; |
|
|
|
if (!gbIsHellfire) { |
|
if (power.type == IPL_TARGAC) { |
|
power.param1 = 1 << power.param1; |
|
power.param2 = 3 << power.param2; |
|
} |
|
} |
|
|
|
int value = SaveItemPower(item, power); |
|
|
|
value = PLVal(value, power.param1, power.param2, affix.minVal, affix.maxVal); |
|
if (item._iVAdd1 != 0 || item._iVMult1 != 0) { |
|
item._iVAdd2 = value; |
|
item._iVMult2 = affix.multVal; |
|
} else { |
|
item._iVAdd1 = value; |
|
item._iVMult1 = affix.multVal; |
|
} |
|
} |
|
|
|
void GetStaffPower(Item &item, int lvl, int bs, bool onlygood) |
|
{ |
|
int preidx = -1; |
|
if (GenerateRnd(10) == 0 || onlygood) { |
|
int nl = 0; |
|
int l[256]; |
|
for (int j = 0; ItemPrefixes[j].power.type != IPL_INVALID; j++) { |
|
if (!IsPrefixValidForItemType(j, PLT_STAFF) || ItemPrefixes[j].PLMinLvl > lvl) |
|
continue; |
|
if (onlygood && !ItemPrefixes[j].PLOk) |
|
continue; |
|
l[nl] = j; |
|
nl++; |
|
if (ItemPrefixes[j].PLDouble) { |
|
l[nl] = j; |
|
nl++; |
|
} |
|
} |
|
if (nl != 0) { |
|
preidx = l[GenerateRnd(nl)]; |
|
char istr[128]; |
|
sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), item._iIName); |
|
strcpy(item._iIName, istr); |
|
item._iMagical = ITEM_QUALITY_MAGIC; |
|
SaveItemAffix(item, ItemPrefixes[preidx]); |
|
item._iPrePower = ItemPrefixes[preidx].power.type; |
|
} |
|
} |
|
if (!StringInPanel(item._iIName)) { |
|
strcpy(item._iIName, _(AllItemsList[item.IDidx].iSName)); |
|
char istr[128]; |
|
if (preidx != -1) { |
|
sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), item._iIName); |
|
strcpy(item._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}"), item._iIName, _(spelldata[bs].sNameText)).c_str()); |
|
strcpy(item._iIName, istr); |
|
if (item._iMagical == ITEM_QUALITY_NORMAL) |
|
strcpy(item._iName, item._iIName); |
|
} |
|
CalcItemValue(item); |
|
} |
|
|
|
void GetItemPower(Item &item, 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; ItemPrefixes[j].power.type != IPL_INVALID; j++) { |
|
if (!IsPrefixValidForItemType(j, flgs)) |
|
continue; |
|
if (ItemPrefixes[j].PLMinLvl < minlvl || ItemPrefixes[j].PLMinLvl > maxlvl) |
|
continue; |
|
if (onlygood && !ItemPrefixes[j].PLOk) |
|
continue; |
|
if (flgs == PLT_STAFF && ItemPrefixes[j].power.type == IPL_CHARGES) |
|
continue; |
|
l[nt] = j; |
|
nt++; |
|
if (ItemPrefixes[j].PLDouble) { |
|
l[nt] = j; |
|
nt++; |
|
} |
|
} |
|
if (nt != 0) { |
|
preidx = l[GenerateRnd(nt)]; |
|
sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), item._iIName); |
|
strcpy(item._iIName, istr); |
|
item._iMagical = ITEM_QUALITY_MAGIC; |
|
SaveItemAffix(item, ItemPrefixes[preidx]); |
|
item._iPrePower = ItemPrefixes[preidx].power.type; |
|
goe = ItemPrefixes[preidx].PLGOE; |
|
} |
|
} |
|
if (post != 0) { |
|
int nl = 0; |
|
for (int j = 0; ItemSuffixes[j].power.type != IPL_INVALID; j++) { |
|
if (IsSuffixValidForItemType(j, flgs) |
|
&& ItemSuffixes[j].PLMinLvl >= minlvl && ItemSuffixes[j].PLMinLvl <= maxlvl |
|
&& !((goe == GOE_GOOD && ItemSuffixes[j].PLGOE == GOE_EVIL) || (goe == GOE_EVIL && ItemSuffixes[j].PLGOE == GOE_GOOD)) |
|
&& (!onlygood || ItemSuffixes[j].PLOk)) { |
|
l[nl] = j; |
|
nl++; |
|
} |
|
} |
|
if (nl != 0) { |
|
sufidx = l[GenerateRnd(nl)]; |
|
strcpy(istr, fmt::format(_("{:s} of {:s}"), item._iIName, _(ItemSuffixes[sufidx].PLName)).c_str()); |
|
strcpy(item._iIName, istr); |
|
item._iMagical = ITEM_QUALITY_MAGIC; |
|
SaveItemAffix(item, ItemSuffixes[sufidx]); |
|
item._iSufPower = ItemSuffixes[sufidx].power.type; |
|
} |
|
} |
|
if (!StringInPanel(item._iIName)) { |
|
int aii = item.IDidx; |
|
if (AllItemsList[aii].iSName != nullptr) |
|
strcpy(item._iIName, _(AllItemsList[aii].iSName)); |
|
else |
|
item._iName[0] = 0; |
|
|
|
if (preidx != -1) { |
|
sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), item._iIName); |
|
strcpy(item._iIName, istr); |
|
} |
|
if (sufidx != -1) { |
|
strcpy(istr, fmt::format(_("{:s} of {:s}"), item._iIName, _(ItemSuffixes[sufidx].PLName)).c_str()); |
|
strcpy(item._iIName, istr); |
|
} |
|
} |
|
if (preidx != -1 || sufidx != -1) |
|
CalcItemValue(item); |
|
} |
|
|
|
void GetStaffSpell(Item &item, int lvl, bool onlygood) |
|
{ |
|
if (!gbIsHellfire && GenerateRnd(4) == 0) { |
|
GetItemPower(item, lvl / 2, lvl, PLT_STAFF, onlygood); |
|
return; |
|
} |
|
|
|
int maxSpells = gbIsHellfire ? MAX_SPELLS : 37; |
|
int l = lvl / 2; |
|
if (l == 0) |
|
l = 1; |
|
int 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; |
|
} |
|
|
|
char istr[68]; |
|
if (!StringInPanel(istr)) |
|
strcpy(istr, fmt::format(_("{:s} of {:s}"), item._iName, _(spelldata[bs].sNameText)).c_str()); |
|
strcpy(istr, fmt::format(_("Staff of {:s}"), _(spelldata[bs].sNameText)).c_str()); |
|
strcpy(item._iName, istr); |
|
strcpy(item._iIName, istr); |
|
|
|
int minc = spelldata[bs].sStaffMin; |
|
int maxc = spelldata[bs].sStaffMax - minc + 1; |
|
item._iSpell = bs; |
|
item._iCharges = minc + GenerateRnd(maxc); |
|
item._iMaxCharges = item._iCharges; |
|
|
|
item._iMinMag = spelldata[bs].sMinInt; |
|
int v = item._iCharges * spelldata[bs].sStaffCost / 5; |
|
item._ivalue += v; |
|
item._iIvalue += v; |
|
GetStaffPower(item, lvl, bs, onlygood); |
|
} |
|
|
|
void GetOilType(Item &item, int maxLvl) |
|
{ |
|
int cnt = 2; |
|
int8_t rnd[32] = { 5, 6 }; |
|
|
|
if (!gbIsMultiplayer) { |
|
if (maxLvl == 0) |
|
maxLvl = 1; |
|
|
|
cnt = 0; |
|
for (size_t j = 0; j < sizeof(OilLevels) / sizeof(OilLevels[0]); j++) { |
|
if (OilLevels[j] <= maxLvl) { |
|
rnd[cnt] = j; |
|
cnt++; |
|
} |
|
} |
|
} |
|
|
|
int8_t t = rnd[GenerateRnd(cnt)]; |
|
|
|
strcpy(item._iName, _(OilNames[t])); |
|
strcpy(item._iIName, _(OilNames[t])); |
|
item._iMiscId = OilMagic[t]; |
|
item._ivalue = OilValues[t]; |
|
item._iIvalue = OilValues[t]; |
|
} |
|
|
|
void GetItemBonus(Item &item, int minlvl, int maxlvl, bool onlygood, bool allowspells) |
|
{ |
|
if (minlvl > 25) |
|
minlvl = 25; |
|
|
|
switch (item._itype) { |
|
case ITYPE_SWORD: |
|
case ITYPE_AXE: |
|
case ITYPE_MACE: |
|
GetItemPower(item, minlvl, maxlvl, PLT_WEAP, onlygood); |
|
break; |
|
case ITYPE_BOW: |
|
GetItemPower(item, minlvl, maxlvl, PLT_BOW, onlygood); |
|
break; |
|
case ITYPE_SHIELD: |
|
GetItemPower(item, minlvl, maxlvl, PLT_SHLD, onlygood); |
|
break; |
|
case ITYPE_LARMOR: |
|
case ITYPE_HELM: |
|
case ITYPE_MARMOR: |
|
case ITYPE_HARMOR: |
|
GetItemPower(item, minlvl, maxlvl, PLT_ARMO, onlygood); |
|
break; |
|
case ITYPE_STAFF: |
|
if (allowspells) |
|
GetStaffSpell(item, maxlvl, onlygood); |
|
else |
|
GetItemPower(item, minlvl, maxlvl, PLT_STAFF, onlygood); |
|
break; |
|
case ITYPE_RING: |
|
case ITYPE_AMULET: |
|
GetItemPower(item, minlvl, maxlvl, PLT_MISC, onlygood); |
|
break; |
|
case ITYPE_NONE: |
|
case ITYPE_MISC: |
|
case ITYPE_GOLD: |
|
break; |
|
} |
|
} |
|
|
|
int RndUItem(Monster *monster) |
|
{ |
|
if (monster != nullptr && (monster->MData->mTreasure & T_UNIQ) != 0 && !gbIsMultiplayer) |
|
return -((monster->MData->mTreasure & T_MASK) + 1); |
|
|
|
int ril[512]; |
|
|
|
int curlv = ItemsGetCurrlevel(); |
|
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 (monster != nullptr) { |
|
if (monster->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() |
|
{ |
|
if (GenerateRnd(100) > 25) |
|
return 0; |
|
|
|
int ril[512]; |
|
|
|
int curlv = ItemsGetCurrlevel(); |
|
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(Item &item, 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[item.IDidx].iItemId |
|
&& lvl >= UniqueItemList[j].UIMinLvl |
|
&& (recreate || !UniqueItemFlags[j] || gbIsMultiplayer)) { |
|
uok[j] = true; |
|
numu++; |
|
} |
|
} |
|
|
|
if (numu == 0) |
|
return UITEM_INVALID; |
|
|
|
AdvanceRndSeed(); |
|
uint8_t itemData = 0; |
|
while (numu > 0) { |
|
if (uok[itemData]) |
|
numu--; |
|
if (numu > 0) |
|
itemData = (itemData + 1) % 128; |
|
} |
|
|
|
return (_unique_items)itemData; |
|
} |
|
|
|
void GetUniqueItem(Item &item, _unique_items uid) |
|
{ |
|
UniqueItemFlags[uid] = true; |
|
|
|
for (const auto &power : UniqueItemList[uid].powers) { |
|
if (power.type == IPL_INVALID) |
|
break; |
|
SaveItemPower(item, power); |
|
} |
|
|
|
strcpy(item._iIName, _(UniqueItemList[uid].UIName)); |
|
item._iIvalue = UniqueItemList[uid].UIValue; |
|
|
|
if (item._iMiscId == IMISC_UNIQUE) |
|
item._iSeed = uid; |
|
|
|
item._iUid = uid; |
|
item._iMagical = ITEM_QUALITY_UNIQUE; |
|
item._iCreateInfo |= CF_UNIQUE; |
|
} |
|
|
|
void ItemRndDur(Item &item) |
|
{ |
|
if (item._iDurability > 0 && item._iDurability != DUR_INDESTRUCTIBLE) |
|
item._iDurability = GenerateRnd(item._iMaxDur / 2) + (item._iMaxDur / 4) + 1; |
|
} |
|
|
|
void SetupAllItems(Item &item, int idx, int iseed, int lvl, int uper, bool onlygood, bool recreate, bool pregen) |
|
{ |
|
int iblvl; |
|
|
|
item._iSeed = iseed; |
|
SetRndSeed(iseed); |
|
GetItemAttrs(item, idx, lvl / 2); |
|
item._iCreateInfo = lvl; |
|
|
|
if (pregen) |
|
item._iCreateInfo |= CF_PREGEN; |
|
if (onlygood) |
|
item._iCreateInfo |= CF_ONLYGOOD; |
|
|
|
if (uper == 15) |
|
item._iCreateInfo |= CF_UPER15; |
|
else if (uper == 1) |
|
item._iCreateInfo |= CF_UPER1; |
|
|
|
if (item._iMiscId != IMISC_UNIQUE) { |
|
iblvl = -1; |
|
if (GenerateRnd(100) <= 10 || GenerateRnd(100) <= lvl) { |
|
iblvl = lvl; |
|
} |
|
if (iblvl == -1 && item._iMiscId == IMISC_STAFF) { |
|
iblvl = lvl; |
|
} |
|
if (iblvl == -1 && item._iMiscId == IMISC_RING) { |
|
iblvl = lvl; |
|
} |
|
if (iblvl == -1 && item._iMiscId == IMISC_AMULET) { |
|
iblvl = lvl; |
|
} |
|
if (onlygood) |
|
iblvl = lvl; |
|
if (uper == 15) |
|
iblvl = lvl + 4; |
|
if (iblvl != -1) { |
|
_unique_items uid = CheckUnique(item, iblvl, uper, recreate); |
|
if (uid == UITEM_INVALID) { |
|
GetItemBonus(item, iblvl / 2, iblvl, onlygood, true); |
|
} else { |
|
GetUniqueItem(item, uid); |
|
} |
|
} |
|
if (item._iMagical != ITEM_QUALITY_UNIQUE) |
|
ItemRndDur(item); |
|
} else { |
|
if (item._iLoc != ILOC_UNEQUIPABLE) { |
|
GetUniqueItem(item, (_unique_items)iseed); // uid is stored in iseed for uniques |
|
} |
|
} |
|
SetupItem(item); |
|
} |
|
|
|
void SetupBaseItem(Point position, int idx, bool onlygood, bool sendmsg, bool delta) |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
GetSuperItemSpace(position, ii); |
|
int curlv = ItemsGetCurrlevel(); |
|
|
|
SetupAllItems(item, idx, AdvanceRndSeed(), 2 * curlv, 1, onlygood, false, delta); |
|
|
|
if (sendmsg) |
|
NetSendCmdDItem(false, ii); |
|
if (delta) |
|
DeltaAddItem(ii); |
|
} |
|
|
|
void SetupAllUseful(Item &item, int iseed, int lvl) |
|
{ |
|
int idx; |
|
|
|
item._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(item, idx, lvl); |
|
item._iCreateInfo = lvl | CF_USEFUL; |
|
SetupItem(item); |
|
} |
|
|
|
uint8_t Char2int(uint8_t 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, uint8_t *target) |
|
{ |
|
for (int i = 0; i < bytes; i++, src += 2) { |
|
target[i] = (Char2int(src[0]) << 4) | Char2int(src[1]); |
|
} |
|
} |
|
|
|
void SpawnRock() |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int oi; |
|
bool ostand = false; |
|
for (int i = 0; i < ActiveObjectCount && !ostand; i++) { |
|
oi = ActiveObjects[i]; |
|
ostand = Objects[oi]._otype == OBJ_STAND; |
|
} |
|
|
|
if (!ostand) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
|
|
item.position = Objects[oi].position; |
|
dItem[Objects[oi].position.x][Objects[oi].position.y] = ii + 1; |
|
int curlv = ItemsGetCurrlevel(); |
|
GetItemAttrs(item, IDI_ROCK, curlv); |
|
SetupItem(item); |
|
item._iSelFlag = 2; |
|
item._iPostDraw = true; |
|
item.AnimInfo.CurrentFrame = 11; |
|
} |
|
|
|
void ItemDoppel() |
|
{ |
|
if (!gbIsMultiplayer) |
|
return; |
|
|
|
static int idoppely = 16; |
|
|
|
for (int idoppelx = 16; idoppelx < 96; idoppelx++) { |
|
if (dItem[idoppelx][idoppely] != 0) { |
|
Item *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 RepairItem(Item &item, int lvl) |
|
{ |
|
if (item._iDurability == item._iMaxDur) { |
|
return; |
|
} |
|
|
|
if (item._iMaxDur <= 0) { |
|
item._itype = ITYPE_NONE; |
|
return; |
|
} |
|
|
|
int rep = 0; |
|
do { |
|
rep += lvl + GenerateRnd(lvl); |
|
item._iMaxDur -= std::max(item._iMaxDur / (lvl + 9), 1); |
|
if (item._iMaxDur == 0) { |
|
item._itype = ITYPE_NONE; |
|
return; |
|
} |
|
} while (rep + item._iDurability < item._iMaxDur); |
|
|
|
item._iDurability = std::min<int>(item._iDurability + rep, item._iMaxDur); |
|
} |
|
|
|
void RechargeItem(Item &item, int r) |
|
{ |
|
if (item._iCharges == item._iMaxCharges) |
|
return; |
|
|
|
do { |
|
item._iMaxCharges--; |
|
if (item._iMaxCharges == 0) { |
|
return; |
|
} |
|
item._iCharges += r; |
|
} while (item._iCharges < item._iMaxCharges); |
|
|
|
item._iCharges = std::min(item._iCharges, item._iMaxCharges); |
|
} |
|
|
|
bool ApplyOilToItem(Item &item, Player &player) |
|
{ |
|
int r; |
|
|
|
if (item._iClass == ICLASS_MISC) { |
|
return false; |
|
} |
|
if (item._iClass == ICLASS_GOLD) { |
|
return false; |
|
} |
|
if (item._iClass == ICLASS_QUEST) { |
|
return false; |
|
} |
|
|
|
switch (player._pOilType) { |
|
case IMISC_OILACC: |
|
case IMISC_OILMAST: |
|
case IMISC_OILSHARP: |
|
if (item._iClass == ICLASS_ARMOR) { |
|
return false; |
|
} |
|
break; |
|
case IMISC_OILDEATH: |
|
if (item._iClass == ICLASS_ARMOR) { |
|
return false; |
|
} |
|
if (item._itype == ITYPE_BOW) { |
|
return false; |
|
} |
|
break; |
|
case IMISC_OILHARD: |
|
case IMISC_OILIMP: |
|
if (item._iClass == ICLASS_WEAPON) { |
|
return false; |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
switch (player._pOilType) { |
|
case IMISC_OILACC: |
|
if (item._iPLToHit < 50) { |
|
item._iPLToHit += GenerateRnd(2) + 1; |
|
} |
|
break; |
|
case IMISC_OILMAST: |
|
if (item._iPLToHit < 100) { |
|
item._iPLToHit += GenerateRnd(3) + 3; |
|
} |
|
break; |
|
case IMISC_OILSHARP: |
|
if (item._iMaxDam - item._iMinDam < 30) { |
|
item._iMaxDam = item._iMaxDam + 1; |
|
} |
|
break; |
|
case IMISC_OILDEATH: |
|
if (item._iMaxDam - item._iMinDam < 30) { |
|
item._iMinDam = item._iMinDam + 1; |
|
item._iMaxDam = item._iMaxDam + 2; |
|
} |
|
break; |
|
case IMISC_OILSKILL: |
|
r = GenerateRnd(6) + 5; |
|
item._iMinStr = std::max(0, item._iMinStr - r); |
|
item._iMinMag = std::max(0, item._iMinMag - r); |
|
item._iMinDex = std::max(0, item._iMinDex - r); |
|
break; |
|
case IMISC_OILBSMTH: |
|
if (item._iMaxDur == 255) |
|
return true; |
|
if (item._iDurability < item._iMaxDur) { |
|
item._iDurability = (item._iMaxDur + 4) / 5 + item._iDurability; |
|
item._iDurability = std::min<int>(item._iDurability, item._iMaxDur); |
|
} else { |
|
if (item._iMaxDur >= 100) { |
|
return true; |
|
} |
|
item._iMaxDur++; |
|
item._iDurability = item._iMaxDur; |
|
} |
|
break; |
|
case IMISC_OILFORT: |
|
if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur < 200) { |
|
r = GenerateRnd(41) + 10; |
|
item._iMaxDur += r; |
|
item._iDurability += r; |
|
} |
|
break; |
|
case IMISC_OILPERM: |
|
item._iDurability = 255; |
|
item._iMaxDur = 255; |
|
break; |
|
case IMISC_OILHARD: |
|
if (item._iAC < 60) { |
|
item._iAC += GenerateRnd(2) + 1; |
|
} |
|
break; |
|
case IMISC_OILIMP: |
|
if (item._iAC < 120) { |
|
item._iAC += GenerateRnd(3) + 3; |
|
} |
|
break; |
|
default: |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
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: |
|
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, _("restore all life")); |
|
AddPanelString(tempstr); |
|
break; |
|
case IMISC_HEAL: |
|
strcpy(tempstr, _("restore some 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, _("restore some mana")); |
|
AddPanelString(tempstr); |
|
break; |
|
case IMISC_FULLMANA: |
|
strcpy(tempstr, _("restore all 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: |
|
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, _("restore some life and mana")); |
|
AddPanelString(tempstr); |
|
break; |
|
case IMISC_FULLREJUV: |
|
strcpy(tempstr, _("restore all life and mana")); |
|
AddPanelString(tempstr); |
|
break; |
|
} |
|
} |
|
|
|
void DrawUniqueInfoWindow(const Surface &out) |
|
{ |
|
CelDrawTo(out, GetPanelPosition(UiPanels::Inventory, { 24 - SPANEL_WIDTH, 327 }), *pSTextBoxCels, 1); |
|
DrawHalfTransparentRectTo(out, RightPanel.position.x - SPANEL_WIDTH + 27, RightPanel.position.y + 28, 265, 297); |
|
} |
|
|
|
void DrawUniqueInfoDevider(const Surface &out, int y) |
|
{ |
|
BYTE *src = out.at(26 + RightPanel.position.x - SPANEL_WIDTH, RightPanel.position.y + 25); |
|
BYTE *dst = out.at(26 + RightPanel.position.x - SPANEL_WIDTH, RightPanel.position.y + 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 PrintItemMisc(Item &item) |
|
{ |
|
if (item._iMiscId == IMISC_SCROLL) { |
|
strcpy(tempstr, _("Right-click to read")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId == IMISC_SCROLLT) { |
|
strcpy(tempstr, _("Right-click to read, then")); |
|
AddPanelString(tempstr); |
|
strcpy(tempstr, _("left-click to target")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId >= IMISC_USEFIRST && item._iMiscId <= IMISC_USELAST) { |
|
PrintItemOil(item._iMiscId); |
|
strcpy(tempstr, _("Right-click to use")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId > IMISC_OILFIRST && item._iMiscId < IMISC_OILLAST) { |
|
PrintItemOil(item._iMiscId); |
|
strcpy(tempstr, _("Right click to use")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId > IMISC_RUNEFIRST && item._iMiscId < IMISC_RUNELAST) { |
|
PrintItemOil(item._iMiscId); |
|
strcpy(tempstr, _("Right click to use")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId == IMISC_BOOK) { |
|
strcpy(tempstr, _("Right-click to read")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId == IMISC_NOTE) { |
|
strcpy(tempstr, _("Right click to read")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId == IMISC_MAPOFDOOM) { |
|
strcpy(tempstr, _("Right-click to view")); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId == IMISC_EAR) { |
|
strcpy(tempstr, fmt::format(_("Level: {:d}"), item._ivalue).c_str()); |
|
AddPanelString(tempstr); |
|
} |
|
if (item._iMiscId == IMISC_AURIC) { |
|
strcpy(tempstr, _("Doubles gold capacity")); |
|
AddPanelString(tempstr); |
|
} |
|
} |
|
|
|
void PrintItemInfo(Item &item) |
|
{ |
|
PrintItemMisc(item); |
|
uint8_t str = item._iMinStr; |
|
uint8_t dex = item._iMinDex; |
|
uint8_t mag = item._iMinMag; |
|
if (str != 0 || mag != 0 || dex != 0) { |
|
strcpy(tempstr, _("Required:")); |
|
if (str != 0) |
|
strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Str"), str).c_str()); |
|
if (mag != 0) |
|
strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Mag"), mag).c_str()); |
|
if (dex != 0) |
|
strcpy(tempstr + strlen(tempstr), fmt::format(_(" {:d} Dex"), dex).c_str()); |
|
AddPanelString(tempstr); |
|
} |
|
} |
|
|
|
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 != SPL_NULL)) |
|
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(Item *itemList) |
|
{ |
|
int count = 1; |
|
while (!itemList[count].isEmpty()) |
|
count++; |
|
|
|
auto cmp = [](const Item &a, const Item &b) { |
|
return a.IDidx < b.IDidx; |
|
}; |
|
|
|
std::sort(itemList, itemList + count, cmp); |
|
} |
|
|
|
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); |
|
} |
|
|
|
void SpawnOnePremium(int i, int plvl, int playerId) |
|
{ |
|
int itemValue = 0; |
|
bool keepGoing = false; |
|
Item tempItem = Items[0]; |
|
|
|
auto &player = Players[playerId]; |
|
|
|
int strength = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Strength), player._pStrength); |
|
int dexterity = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Dexterity), player._pDexterity); |
|
int magic = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Magic), player._pMagic); |
|
strength += strength / 5; |
|
dexterity += dexterity / 5; |
|
magic += magic / 5; |
|
|
|
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 itemType = RndPremiumItem(plvl / 4, plvl) - 1; |
|
GetItemAttrs(Items[0], itemType, plvl); |
|
GetItemBonus(Items[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 *const mostValuablePlayerArmor = player.GetMostValuableItem( |
|
[](const Item &item) { |
|
return item._itype == ITYPE_LARMOR |
|
|| item._itype == ITYPE_MARMOR |
|
|| item._itype == ITYPE_HARMOR; |
|
}); |
|
|
|
itemValue = 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 *const mostValuablePlayerItem = player.GetMostValuableItem( |
|
[](const Item &item) { return item._itype == Items[0]._itype; }); |
|
|
|
itemValue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue; |
|
break; |
|
} |
|
default: |
|
itemValue = 0; |
|
break; |
|
} |
|
itemValue = itemValue * 4 / 5; // avoids forced int > float > int conversion |
|
|
|
count++; |
|
} while (keepGoing |
|
|| (( |
|
Items[0]._iIvalue > 200000 |
|
|| Items[0]._iMinStr > strength |
|
|| Items[0]._iMinMag > magic |
|
|| Items[0]._iMinDex > dexterity |
|
|| Items[0]._iIvalue < itemValue) |
|
&& count < 150)); |
|
premiumitems[i] = Items[0]; |
|
premiumitems[i]._iCreateInfo = plvl | CF_SMITHPREMIUM; |
|
premiumitems[i]._iIdentified = true; |
|
premiumitems[i]._iStatFlag = StoreStatOk(premiumitems[i]); |
|
Items[0] = tempItem; |
|
} |
|
|
|
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); |
|
} |
|
|
|
int RndBoyItem(int lvl) |
|
{ |
|
return RndVendorItem<PremiumItemOk>(0, lvl); |
|
} |
|
|
|
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) { |
|
auto &myPlayer = Players[MyPlayerId]; |
|
|
|
if (AllItemsList[i].iMiscId == IMISC_ELIXSTR) |
|
return !gbIsHellfire || myPlayer._pBaseStr < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength); |
|
if (AllItemsList[i].iMiscId == IMISC_ELIXMAG) |
|
return !gbIsHellfire || myPlayer._pBaseMag < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic); |
|
if (AllItemsList[i].iMiscId == IMISC_ELIXDEX) |
|
return !gbIsHellfire || myPlayer._pBaseDex < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity); |
|
if (AllItemsList[i].iMiscId == IMISC_ELIXVIT) |
|
return !gbIsHellfire || myPlayer._pBaseVit < myPlayer.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 RecreateSmithItem(Item &item, int lvl, int iseed) |
|
{ |
|
SetRndSeed(iseed); |
|
int itype = RndSmithItem(lvl) - 1; |
|
GetItemAttrs(item, itype, lvl); |
|
|
|
item._iSeed = iseed; |
|
item._iCreateInfo = lvl | CF_SMITH; |
|
item._iIdentified = true; |
|
} |
|
|
|
void RecreatePremiumItem(Item &item, int plvl, int iseed) |
|
{ |
|
SetRndSeed(iseed); |
|
int itype = RndPremiumItem(plvl / 4, plvl) - 1; |
|
GetItemAttrs(item, itype, plvl); |
|
GetItemBonus(item, plvl / 2, plvl, true, !gbIsHellfire); |
|
|
|
item._iSeed = iseed; |
|
item._iCreateInfo = plvl | CF_SMITHPREMIUM; |
|
item._iIdentified = true; |
|
} |
|
|
|
void RecreateBoyItem(Item &item, int lvl, int iseed) |
|
{ |
|
SetRndSeed(iseed); |
|
int itype = RndBoyItem(lvl) - 1; |
|
GetItemAttrs(item, itype, lvl); |
|
GetItemBonus(item, lvl, 2 * lvl, true, true); |
|
|
|
item._iSeed = iseed; |
|
item._iCreateInfo = lvl | CF_BOY; |
|
item._iIdentified = true; |
|
} |
|
|
|
void RecreateWitchItem(Item &item, int idx, int lvl, int iseed) |
|
{ |
|
if (idx == IDI_MANA || idx == IDI_FULLMANA || idx == IDI_PORTAL) { |
|
GetItemAttrs(item, idx, lvl); |
|
} else if (gbIsHellfire && idx >= 114 && idx <= 117) { |
|
SetRndSeed(iseed); |
|
AdvanceRndSeed(); |
|
GetItemAttrs(item, idx, lvl); |
|
} else { |
|
SetRndSeed(iseed); |
|
int itype = RndWitchItem(lvl) - 1; |
|
GetItemAttrs(item, itype, lvl); |
|
int iblvl = -1; |
|
if (GenerateRnd(100) <= 5) |
|
iblvl = 2 * lvl; |
|
if (iblvl == -1 && item._iMiscId == IMISC_STAFF) |
|
iblvl = 2 * lvl; |
|
if (iblvl != -1) |
|
GetItemBonus(item, iblvl / 2, iblvl, true, true); |
|
} |
|
|
|
item._iSeed = iseed; |
|
item._iCreateInfo = lvl | CF_WITCH; |
|
item._iIdentified = true; |
|
} |
|
|
|
void RecreateHealerItem(Item &item, int idx, int lvl, int iseed) |
|
{ |
|
if (idx == IDI_HEAL || idx == IDI_FULLHEAL || idx == IDI_RESURRECT) { |
|
GetItemAttrs(item, idx, lvl); |
|
} else { |
|
SetRndSeed(iseed); |
|
int itype = RndHealerItem(lvl) - 1; |
|
GetItemAttrs(item, itype, lvl); |
|
} |
|
|
|
item._iSeed = iseed; |
|
item._iCreateInfo = lvl | CF_HEALER; |
|
item._iIdentified = true; |
|
} |
|
|
|
void RecreateTownItem(Item &item, int idx, uint16_t icreateinfo, int iseed) |
|
{ |
|
if ((icreateinfo & CF_SMITH) != 0) |
|
RecreateSmithItem(item, icreateinfo & CF_LEVEL, iseed); |
|
else if ((icreateinfo & CF_SMITHPREMIUM) != 0) |
|
RecreatePremiumItem(item, icreateinfo & CF_LEVEL, iseed); |
|
else if ((icreateinfo & CF_BOY) != 0) |
|
RecreateBoyItem(item, icreateinfo & CF_LEVEL, iseed); |
|
else if ((icreateinfo & CF_WITCH) != 0) |
|
RecreateWitchItem(item, idx, icreateinfo & CF_LEVEL, iseed); |
|
else if ((icreateinfo & CF_HEALER) != 0) |
|
RecreateHealerItem(item, idx, icreateinfo & CF_LEVEL, iseed); |
|
} |
|
|
|
void RecalcStoreStats() |
|
{ |
|
for (auto &item : smithitem) { |
|
if (!item.isEmpty()) { |
|
item._iStatFlag = StoreStatOk(item); |
|
} |
|
} |
|
for (auto &item : premiumitems) { |
|
if (!item.isEmpty()) { |
|
item._iStatFlag = StoreStatOk(item); |
|
} |
|
} |
|
for (int i = 0; i < 20; i++) { |
|
if (!witchitem[i].isEmpty()) { |
|
witchitem[i]._iStatFlag = StoreStatOk(witchitem[i]); |
|
} |
|
} |
|
for (auto &item : healitem) { |
|
if (!item.isEmpty()) { |
|
item._iStatFlag = StoreStatOk(item); |
|
} |
|
} |
|
boyitem._iStatFlag = StoreStatOk(boyitem); |
|
} |
|
|
|
void CreateMagicItem(Point position, int lvl, int imisc, int imid, int icurs, bool sendmsg, bool delta) |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
int idx = RndTypeItems(imisc, imid, lvl); |
|
|
|
while (true) { |
|
memset(&item, 0, sizeof(item)); |
|
SetupAllItems(item, idx, AdvanceRndSeed(), 2 * lvl, 1, true, false, delta); |
|
if (item._iCurs == icurs) |
|
break; |
|
|
|
idx = RndTypeItems(imisc, imid, lvl); |
|
} |
|
GetSuperItemSpace(position, ii); |
|
|
|
if (sendmsg) |
|
NetSendCmdDItem(false, ii); |
|
if (delta) |
|
DeltaAddItem(ii); |
|
} |
|
|
|
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; |
|
} |
|
|
|
} // namespace |
|
|
|
bool IsItemAvailable(int i) |
|
{ |
|
if (gbIsSpawn) { |
|
if (i >= 62 && i <= 71) |
|
return false; // Medium and heavy armors |
|
if (i == 105 || i == 107 || i == 108 || i == 110 || i == 111 || i == 113) |
|
return false; // Unavailable scrolls |
|
} |
|
|
|
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 Item &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; |
|
} |
|
|
|
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)); |
|
} |
|
|
|
void InitItems() |
|
{ |
|
memset(&Items[0], 0, sizeof(*Items)); |
|
GetItemAttrs(Items[0], IDI_GOLD, 1); |
|
golditem = Items[0]; |
|
golditem._iStatFlag = true; |
|
ActiveItemCount = 0; |
|
|
|
for (int i = 0; i < MAXITEMS; i++) { |
|
auto &item = Items[i]; |
|
item._itype = ITYPE_NONE; |
|
item.position = { 0, 0 }; |
|
item._iAnimFlag = false; |
|
item._iSelFlag = 0; |
|
item._iIdentified = false; |
|
item._iPostDraw = false; |
|
} |
|
|
|
for (int i = 0; i < MAXITEMS; i++) { |
|
AvailableItems[i] = i; |
|
ActiveItems[i] = 0; |
|
} |
|
|
|
if (!setlevel) { |
|
AdvanceRndSeed(); /* unused */ |
|
if (Quests[Q_ROCK].IsAvailable()) |
|
SpawnRock(); |
|
if (Quests[Q_ANVIL].IsAvailable()) |
|
SpawnQuestItem(IDI_ANVIL, { 2 * setpc_x + 27, 2 * setpc_y + 27 }, 0, 1); |
|
if (sgGameInitInfo.bCowQuest != 0 && currlevel == 20) |
|
SpawnQuestItem(IDI_BROWNSUIT, { 25, 25 }, 3, 1); |
|
if (sgGameInitInfo.bCowQuest != 0 && currlevel == 19) |
|
SpawnQuestItem(IDI_GREYSUIT, { 25, 25 }, 3, 1); |
|
if (currlevel > 0 && currlevel < 16) |
|
AddInitItems(); |
|
if (currlevel >= 21 && currlevel <= 23) |
|
SpawnNote(); |
|
} |
|
|
|
ShowUniqueItemInfoBox = false; |
|
|
|
// BUGFIX: item get records not reset when resetting items (fixed). |
|
initItemGetRecords(); |
|
} |
|
|
|
void CalcPlrItemVals(Player &player, bool loadgfx) |
|
{ |
|
int mind = 0; // min damage |
|
int maxd = 0; // max damage |
|
int tac = 0; // accuracy |
|
|
|
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 (auto &item : player.InvBody) { |
|
if (!item.isEmpty() && item._iStatFlag) { |
|
|
|
mind += item._iMinDam; |
|
maxd += item._iMaxDam; |
|
tac += item._iAC; |
|
|
|
if (item._iSpell != SPL_NULL) { |
|
spl |= GetSpellBitmask(item._iSpell); |
|
} |
|
|
|
if (item._iMagical == ITEM_QUALITY_NORMAL || item._iIdentified) { |
|
bdam += item._iPLDam; |
|
btohit += item._iPLToHit; |
|
if (item._iPLAC != 0) { |
|
int tmpac = item._iAC; |
|
tmpac *= item._iPLAC; |
|
tmpac /= 100; |
|
if (tmpac == 0) |
|
tmpac = math::Sign(item._iPLAC); |
|
bac += tmpac; |
|
} |
|
iflgs |= item._iFlags; |
|
pDamAcFlags |= item._iDamAcFlags; |
|
sadd += item._iPLStr; |
|
madd += item._iPLMag; |
|
dadd += item._iPLDex; |
|
vadd += item._iPLVit; |
|
fr += item._iPLFR; |
|
lr += item._iPLLR; |
|
mr += item._iPLMR; |
|
dmod += item._iPLDamMod; |
|
ghit += item._iPLGetHit; |
|
lrad += item._iPLLight; |
|
ihp += item._iPLHP; |
|
imana += item._iPLMana; |
|
spllvladd += item._iSplLvlAdd; |
|
enac += item._iPLEnAc; |
|
fmin += item._iFMinDam; |
|
fmax += item._iFMaxDam; |
|
lmin += item._iLMinDam; |
|
lmax += item._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 && &player == &Players[MyPlayerId]) { |
|
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 (&player == &Players[MyPlayerId] && (player._pHitPoints >> 6) <= 0) { |
|
SetPlayerHitPoints(player, 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; |
|
|
|
player._pInfraFlag = (iflgs & ISPL_INFRAVISION) != 0; |
|
|
|
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; |
|
} |
|
|
|
item_type weaponItemType = item_type::ITYPE_NONE; |
|
bool holdsShield = false; |
|
if (!player.InvBody[INVLOC_HAND_LEFT].isEmpty() |
|
&& player.InvBody[INVLOC_HAND_LEFT]._iClass == ICLASS_WEAPON |
|
&& player.InvBody[INVLOC_HAND_LEFT]._iStatFlag) { |
|
weaponItemType = 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) { |
|
weaponItemType = player.InvBody[INVLOC_HAND_RIGHT]._itype; |
|
} |
|
|
|
if (player.InvBody[INVLOC_HAND_LEFT]._itype == ITYPE_SHIELD && player.InvBody[INVLOC_HAND_LEFT]._iStatFlag) { |
|
player._pBlockFlag = true; |
|
holdsShield = true; |
|
} |
|
if (player.InvBody[INVLOC_HAND_RIGHT]._itype == ITYPE_SHIELD && player.InvBody[INVLOC_HAND_RIGHT]._iStatFlag) { |
|
player._pBlockFlag = true; |
|
holdsShield = true; |
|
} |
|
|
|
PlayerWeaponGraphic animWeaponId = holdsShield ? PlayerWeaponGraphic::UnarmedShield : PlayerWeaponGraphic::Unarmed; |
|
switch (weaponItemType) { |
|
case ITYPE_SWORD: |
|
animWeaponId = holdsShield ? PlayerWeaponGraphic::SwordShield : PlayerWeaponGraphic::Sword; |
|
break; |
|
case ITYPE_AXE: |
|
animWeaponId = PlayerWeaponGraphic::Axe; |
|
break; |
|
case ITYPE_BOW: |
|
animWeaponId = PlayerWeaponGraphic::Bow; |
|
break; |
|
case ITYPE_MACE: |
|
animWeaponId = holdsShield ? PlayerWeaponGraphic::MaceShield : PlayerWeaponGraphic::Mace; |
|
break; |
|
case ITYPE_STAFF: |
|
animWeaponId = PlayerWeaponGraphic::Staff; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
PlayerArmorGraphic animArmorId = PlayerArmorGraphic::Light; |
|
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; |
|
animArmorId = PlayerArmorGraphic::Heavy; |
|
} 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; |
|
} |
|
animArmorId = PlayerArmorGraphic::Medium; |
|
} else if (player._pClass == HeroClass::Monk) { |
|
player._pIAC += player._pLevel * 2; |
|
} |
|
|
|
int gfxNum = static_cast<int>(animWeaponId) | static_cast<int>(animArmorId); |
|
if (player._pgfxnum != gfxNum && loadgfx) { |
|
player._pgfxnum = gfxNum; |
|
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, 4); |
|
} else { |
|
LoadPlrGFX(player, player_graphic::Walk); |
|
player.AnimInfo.ChangeAnimationData(&*player.AnimationData[static_cast<size_t>(player_graphic::Walk)].CelSpritesForDirections[player._pdir], player._pWFrames, 1); |
|
} |
|
} else { |
|
player._pgfxnum = gfxNum; |
|
} |
|
|
|
if (player.InvBody[INVLOC_AMULET].isEmpty() || player.InvBody[INVLOC_AMULET].IDidx != IDI_AURIC) { |
|
int half = MaxGold; |
|
MaxGold = GOLD_MAX_LIMIT; |
|
|
|
if (half != MaxGold) |
|
StripTopGold(player); |
|
} else { |
|
MaxGold = GOLD_MAX_LIMIT * 2; |
|
} |
|
|
|
drawmanaflag = true; |
|
drawhpflag = true; |
|
} |
|
|
|
void CalcPlrInv(Player &player, bool loadgfx) |
|
{ |
|
CalcPlrItemMin(player); |
|
CalcSelfItems(player); |
|
CalcPlrItemVals(player, loadgfx); |
|
CalcPlrItemMin(player); |
|
if (&player == &Players[MyPlayerId]) { |
|
CalcPlrBookVals(player); |
|
player.CalcScrolls(); |
|
CalcPlrStaff(player); |
|
if (currlevel == 0) |
|
RecalcStoreStats(); |
|
} |
|
} |
|
|
|
void SetPlrHandItem(Item &item, int itemData) |
|
{ |
|
auto &pAllItem = AllItemsList[itemData]; |
|
|
|
// zero-initialize struct |
|
memset(&item, 0, sizeof(item)); |
|
|
|
item._itype = pAllItem.itype; |
|
item._iCurs = pAllItem.iCurs; |
|
strcpy(item._iName, _(pAllItem.iName)); |
|
strcpy(item._iIName, _(pAllItem.iName)); |
|
item._iLoc = pAllItem.iLoc; |
|
item._iClass = pAllItem.iClass; |
|
item._iMinDam = pAllItem.iMinDam; |
|
item._iMaxDam = pAllItem.iMaxDam; |
|
item._iAC = pAllItem.iMinAC; |
|
item._iMiscId = pAllItem.iMiscId; |
|
item._iSpell = pAllItem.iSpell; |
|
|
|
if (pAllItem.iMiscId == IMISC_STAFF) { |
|
item._iCharges = gbIsHellfire ? 18 : 40; |
|
} |
|
|
|
item._iMaxCharges = item._iCharges; |
|
item._iDurability = pAllItem.iDurability; |
|
item._iMaxDur = pAllItem.iDurability; |
|
item._iMinStr = pAllItem.iMinStr; |
|
item._iMinMag = pAllItem.iMinMag; |
|
item._iMinDex = pAllItem.iMinDex; |
|
item._ivalue = pAllItem.iValue; |
|
item._iIvalue = pAllItem.iValue; |
|
item._iPrePower = IPL_INVALID; |
|
item._iSufPower = IPL_INVALID; |
|
item._iMagical = ITEM_QUALITY_NORMAL; |
|
item.IDidx = static_cast<_item_indexes>(itemData); |
|
if (gbIsHellfire) |
|
item.dwBuff |= CF_HELLFIRE; |
|
} |
|
|
|
void GetPlrHandSeed(Item *h) |
|
{ |
|
h->_iSeed = AdvanceRndSeed(); |
|
} |
|
|
|
void SetGoldSeed(Player &player, Item &gold) |
|
{ |
|
int s = 0; |
|
|
|
bool doneflag; |
|
do { |
|
doneflag = true; |
|
s = AdvanceRndSeed(); |
|
for (int i = 0; i < ActiveItemCount; i++) { |
|
int ii = ActiveItems[i]; |
|
auto &item = Items[ii]; |
|
if (item._iSeed == s) |
|
doneflag = false; |
|
} |
|
if (&player == &Players[MyPlayerId]) { |
|
for (int i = 0; i < player._pNumInv; i++) { |
|
if (player.InvList[i]._iSeed == s) |
|
doneflag = false; |
|
} |
|
} |
|
} while (!doneflag); |
|
|
|
gold._iSeed = s; |
|
} |
|
|
|
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; |
|
} |
|
|
|
void SetPlrHandGoldCurs(Item &gold) |
|
{ |
|
gold._iCurs = GetGoldCursor(gold._ivalue); |
|
} |
|
|
|
void CreatePlrItems(int playerId) |
|
{ |
|
auto &player = Players[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]); |
|
|
|
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); |
|
|
|
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; |
|
|
|
CalcPlrItemVals(player, false); |
|
} |
|
|
|
bool ItemSpaceOk(Point position) |
|
{ |
|
int oi; |
|
|
|
// BUGFIX: Check `i + 1 >= MAXDUNX` and `j + 1 >= MAXDUNY` (applied) |
|
if (position.x < 0 || position.x + 1 >= MAXDUNX || position.y < 0 || position.y + 1 >= MAXDUNY) |
|
return false; |
|
|
|
if (dMonster[position.x][position.y] != 0) |
|
return false; |
|
|
|
if (dPlayer[position.x][position.y] != 0) |
|
return false; |
|
|
|
if (dItem[position.x][position.y] != 0) |
|
return false; |
|
|
|
if (dObject[position.x][position.y] != 0) { |
|
oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); |
|
if (Objects[oi]._oSolidFlag) |
|
return false; |
|
} |
|
|
|
if (dObject[position.x + 1][position.y + 1] > 0 && Objects[dObject[position.x + 1][position.y + 1] - 1]._oSelFlag != 0) |
|
return false; |
|
|
|
if (dObject[position.x + 1][position.y + 1] < 0 && Objects[-(dObject[position.x + 1][position.y + 1] + 1)]._oSelFlag != 0) |
|
return false; |
|
|
|
if (dObject[position.x + 1][position.y] > 0 |
|
&& dObject[position.x][position.y + 1] > 0 |
|
&& Objects[dObject[position.x + 1][position.y] - 1]._oSelFlag != 0 |
|
&& Objects[dObject[position.x][position.y + 1] - 1]._oSelFlag != 0) { |
|
return false; |
|
} |
|
|
|
return IsTileNotSolid(position); |
|
} |
|
|
|
int AllocateItem() |
|
{ |
|
int inum = AvailableItems[0]; |
|
AvailableItems[0] = AvailableItems[MAXITEMS - ActiveItemCount - 1]; |
|
ActiveItems[ActiveItemCount] = inum; |
|
ActiveItemCount++; |
|
|
|
memset(&Items[inum], 0, sizeof(*Items)); |
|
|
|
return inum; |
|
} |
|
|
|
Point GetSuperItemLoc(Point position) |
|
{ |
|
for (int k = 1; k < 50; k++) { |
|
for (int j = -k; j <= k; j++) { |
|
for (int i = -k; i <= k; i++) { |
|
Displacement offset = { i, j }; |
|
Point positionToCheck = position + offset; |
|
if (ItemSpaceOk(positionToCheck)) { |
|
return positionToCheck; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return { 0, 0 }; // TODO handle no space for dropping items |
|
} |
|
|
|
void GetItemAttrs(Item &item, int itemData, int lvl) |
|
{ |
|
item._itype = AllItemsList[itemData].itype; |
|
item._iCurs = AllItemsList[itemData].iCurs; |
|
strcpy(item._iName, _(AllItemsList[itemData].iName)); |
|
strcpy(item._iIName, _(AllItemsList[itemData].iName)); |
|
item._iLoc = AllItemsList[itemData].iLoc; |
|
item._iClass = AllItemsList[itemData].iClass; |
|
item._iMinDam = AllItemsList[itemData].iMinDam; |
|
item._iMaxDam = AllItemsList[itemData].iMaxDam; |
|
item._iAC = AllItemsList[itemData].iMinAC + GenerateRnd(AllItemsList[itemData].iMaxAC - AllItemsList[itemData].iMinAC + 1); |
|
item._iFlags = AllItemsList[itemData].iFlags; |
|
item._iMiscId = AllItemsList[itemData].iMiscId; |
|
item._iSpell = AllItemsList[itemData].iSpell; |
|
item._iMagical = ITEM_QUALITY_NORMAL; |
|
item._ivalue = AllItemsList[itemData].iValue; |
|
item._iIvalue = AllItemsList[itemData].iValue; |
|
item._iDurability = AllItemsList[itemData].iDurability; |
|
item._iMaxDur = AllItemsList[itemData].iDurability; |
|
item._iMinStr = AllItemsList[itemData].iMinStr; |
|
item._iMinMag = AllItemsList[itemData].iMinMag; |
|
item._iMinDex = AllItemsList[itemData].iMinDex; |
|
item.IDidx = static_cast<_item_indexes>(itemData); |
|
if (gbIsHellfire) |
|
item.dwBuff |= CF_HELLFIRE; |
|
item._iPrePower = IPL_INVALID; |
|
item._iSufPower = IPL_INVALID; |
|
|
|
if (item._iMiscId == IMISC_BOOK) |
|
GetBookSpell(item, lvl); |
|
|
|
if (gbIsHellfire && item._iMiscId == IMISC_OILOF) |
|
GetOilType(item, lvl); |
|
|
|
if (item._itype != ITYPE_GOLD) |
|
return; |
|
|
|
int rndv; |
|
int itemlevel = ItemsGetCurrlevel(); |
|
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; |
|
|
|
item._ivalue = std::min(rndv, GOLD_MAX_LIMIT); |
|
SetPlrHandGoldCurs(item); |
|
} |
|
|
|
void SetupItem(Item &item) |
|
{ |
|
item.SetNewAnimation(Players[MyPlayerId].pLvlLoad == 0); |
|
item._iIdentified = false; |
|
} |
|
|
|
int RndItem(const Monster &monster) |
|
{ |
|
if ((monster.MData->mTreasure & T_UNIQ) != 0) |
|
return -((monster.MData->mTreasure & T_MASK) + 1); |
|
|
|
if ((monster.MData->mTreasure & T_NODROP) != 0) |
|
return 0; |
|
|
|
if (GenerateRnd(100) > 40) |
|
return 0; |
|
|
|
if (GenerateRnd(100) > 25) |
|
return IDI_GOLD + 1; |
|
|
|
int ril[512]; |
|
|
|
int ri = 0; |
|
for (int i = 0; AllItemsList[i].iLoc != ILOC_INVALID; i++) { |
|
if (!IsItemAvailable(i)) |
|
continue; |
|
|
|
if (AllItemsList[i].iRnd == IDROP_DOUBLE && monster.mLevel >= AllItemsList[i].iMinMLvl |
|
&& ri < 512) { |
|
ril[ri] = i; |
|
ri++; |
|
} |
|
if (AllItemsList[i].iRnd != IDROP_NEVER && monster.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--; |
|
} |
|
|
|
int r = GenerateRnd(ri); |
|
return ril[r] + 1; |
|
} |
|
|
|
void SpawnUnique(_unique_items uid, Point position) |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
GetSuperItemSpace(position, ii); |
|
int curlv = ItemsGetCurrlevel(); |
|
|
|
int idx = 0; |
|
while (AllItemsList[idx].iItemId != UniqueItemList[uid].UIItemId) |
|
idx++; |
|
|
|
GetItemAttrs(item, idx, curlv); |
|
GetUniqueItem(item, uid); |
|
SetupItem(item); |
|
} |
|
|
|
void SpawnItem(Monster &monster, Point position, bool sendmsg) |
|
{ |
|
int idx; |
|
bool onlygood = true; |
|
|
|
if (monster._uniqtype != 0 || ((monster.MData->mTreasure & T_UNIQ) != 0 && gbIsMultiplayer)) { |
|
idx = RndUItem(&monster); |
|
if (idx < 0) { |
|
SpawnUnique((_unique_items) - (idx + 1), position); |
|
return; |
|
} |
|
onlygood = true; |
|
} else if (Quests[Q_MUSHROOM]._qactive != QUEST_ACTIVE || Quests[Q_MUSHROOM]._qvar1 != QS_MUSHGIVEN) { |
|
idx = RndItem(monster); |
|
if (idx == 0) |
|
return; |
|
if (idx > 0) { |
|
idx--; |
|
onlygood = false; |
|
} else { |
|
SpawnUnique((_unique_items) - (idx + 1), position); |
|
return; |
|
} |
|
} else { |
|
idx = IDI_BRAIN; |
|
Quests[Q_MUSHROOM]._qvar1 = QS_BRAINSPAWNED; |
|
} |
|
|
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
GetSuperItemSpace(position, ii); |
|
int uper = monster._uniqtype != 0 ? 15 : 1; |
|
|
|
int8_t mLevel = monster.MData->mLevel; |
|
if (!gbIsHellfire && monster.MType->mtype == MT_DIABLO) |
|
mLevel -= 15; |
|
|
|
SetupAllItems(item, idx, AdvanceRndSeed(), mLevel, uper, onlygood, false, false); |
|
|
|
if (sendmsg) |
|
NetSendCmdDItem(false, ii); |
|
} |
|
|
|
void CreateRndItem(Point position, bool onlygood, bool sendmsg, bool delta) |
|
{ |
|
int idx = onlygood ? RndUItem(nullptr) : RndAllItems(); |
|
|
|
SetupBaseItem(position, idx, onlygood, sendmsg, delta); |
|
} |
|
|
|
void CreateRndUseful(Point position, bool sendmsg) |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
GetSuperItemSpace(position, ii); |
|
int curlv = ItemsGetCurrlevel(); |
|
|
|
SetupAllUseful(item, 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 = ItemsGetCurrlevel(); |
|
if (itype != ITYPE_GOLD) |
|
idx = RndTypeItems(itype, imisc, curlv); |
|
else |
|
idx = IDI_GOLD; |
|
|
|
SetupBaseItem(position, idx, onlygood, sendmsg, delta); |
|
} |
|
|
|
void RecreateItem(Item &item, int idx, uint16_t icreateinfo, int iseed, int ivalue, bool isHellfire) |
|
{ |
|
bool tmpIsHellfire = gbIsHellfire; |
|
gbIsHellfire = isHellfire; |
|
|
|
if (idx == IDI_GOLD) { |
|
SetPlrHandItem(item, IDI_GOLD); |
|
item._iSeed = iseed; |
|
item._iCreateInfo = icreateinfo; |
|
item._ivalue = ivalue; |
|
SetPlrHandGoldCurs(item); |
|
gbIsHellfire = tmpIsHellfire; |
|
return; |
|
} |
|
|
|
if (icreateinfo == 0) { |
|
SetPlrHandItem(item, idx); |
|
SetPlrHandSeed(item, iseed); |
|
gbIsHellfire = tmpIsHellfire; |
|
return; |
|
} |
|
|
|
if ((icreateinfo & CF_UNIQUE) == 0) { |
|
if ((icreateinfo & CF_TOWN) != 0) { |
|
RecreateTownItem(item, idx, icreateinfo, iseed); |
|
gbIsHellfire = tmpIsHellfire; |
|
return; |
|
} |
|
|
|
if ((icreateinfo & CF_USEFUL) == CF_USEFUL) { |
|
SetupAllUseful(item, iseed, icreateinfo & CF_LEVEL); |
|
gbIsHellfire = tmpIsHellfire; |
|
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(item, idx, iseed, level, uper, onlygood, recreate, pregen); |
|
gbIsHellfire = tmpIsHellfire; |
|
} |
|
|
|
void RecreateEar(Item &item, uint16_t ic, int iseed, int id, int dur, int mdur, int ch, int mch, int ivalue, int ibuff) |
|
{ |
|
SetPlrHandItem(item, IDI_EAR); |
|
tempstr[0] = static_cast<char>((ic >> 8) & 0x7F); |
|
tempstr[1] = static_cast<char>(ic & 0x7F); |
|
tempstr[2] = static_cast<char>((iseed >> 24) & 0x7F); |
|
tempstr[3] = static_cast<char>((iseed >> 16) & 0x7F); |
|
tempstr[4] = static_cast<char>((iseed >> 8) & 0x7F); |
|
tempstr[5] = static_cast<char>(iseed & 0x7F); |
|
tempstr[6] = static_cast<char>(id & 0x7F); |
|
tempstr[7] = static_cast<char>(dur & 0x7F); |
|
tempstr[8] = static_cast<char>(mdur & 0x7F); |
|
tempstr[9] = static_cast<char>(ch & 0x7F); |
|
tempstr[10] = static_cast<char>(mch & 0x7F); |
|
tempstr[11] = static_cast<char>((ivalue >> 8) & 0x7F); |
|
tempstr[12] = static_cast<char>((ibuff >> 24) & 0x7F); |
|
tempstr[13] = static_cast<char>((ibuff >> 16) & 0x7F); |
|
tempstr[14] = static_cast<char>((ibuff >> 8) & 0x7F); |
|
tempstr[15] = static_cast<char>(ibuff & 0x7F); |
|
tempstr[16] = '\0'; |
|
strcpy(item._iName, fmt::format(_(/* TRANSLATORS: {:s} will be a Character Name */ "Ear of {:s}"), tempstr).c_str()); |
|
item._iCurs = ((ivalue >> 6) & 3) + ICURS_EAR_SORCERER; |
|
item._ivalue = ivalue & 0x3F; |
|
item._iCreateInfo = ic; |
|
item._iSeed = iseed; |
|
} |
|
|
|
void CornerstoneSave() |
|
{ |
|
if (!CornerStone.activated) |
|
return; |
|
if (!CornerStone.item.isEmpty()) { |
|
ItemPack id; |
|
PackItem(&id, &CornerStone.item); |
|
const auto *buffer = reinterpret_cast<char *>(&id); |
|
for (size_t i = 0; i < sizeof(ItemPack); i++) { |
|
sprintf(&sgOptions.Hellfire.szItem[i * 2], "%02X", buffer[i]); |
|
} |
|
} else { |
|
sgOptions.Hellfire.szItem[0] = '\0'; |
|
} |
|
} |
|
|
|
void CornerstoneLoad(Point position) |
|
{ |
|
ItemPack 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] != 0) { |
|
int ii = dItem[position.x][position.y] - 1; |
|
for (int i = 0; i < ActiveItemCount; i++) { |
|
if (ActiveItems[i] == ii) { |
|
DeleteItem(ii, i); |
|
break; |
|
} |
|
} |
|
dItem[position.x][position.y] = 0; |
|
} |
|
|
|
if (strlen(sgOptions.Hellfire.szItem) < sizeof(ItemPack) * 2) |
|
return; |
|
|
|
Hex2bin(sgOptions.Hellfire.szItem, sizeof(ItemPack), reinterpret_cast<uint8_t *>(&pkSItem)); |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
|
|
dItem[position.x][position.y] = ii + 1; |
|
|
|
UnPackItem(&pkSItem, &item, (pkSItem.dwBuff & CF_HELLFIRE) != 0); |
|
item.position = position; |
|
RespawnItem(&item, false); |
|
CornerStone.item = item; |
|
} |
|
|
|
void SpawnQuestItem(int itemid, Point position, int randarea, int selflag) |
|
{ |
|
if (randarea > 0) { |
|
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(position + Displacement { i, j }); |
|
} |
|
} |
|
if (!failed) |
|
break; |
|
} |
|
} |
|
|
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
|
|
item.position = position; |
|
|
|
dItem[position.x][position.y] = ii + 1; |
|
|
|
int curlv = ItemsGetCurrlevel(); |
|
GetItemAttrs(item, itemid, curlv); |
|
|
|
SetupItem(item); |
|
item._iSeed = AdvanceRndSeed(); |
|
SetRndSeed(item._iSeed); |
|
item._iPostDraw = true; |
|
if (selflag != 0) { |
|
item._iSelFlag = selflag; |
|
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames; |
|
item._iAnimFlag = false; |
|
} |
|
} |
|
|
|
void SpawnRewardItem(int itemid, Point position) |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
|
|
item.position = position; |
|
dItem[position.x][position.y] = ii + 1; |
|
int curlv = ItemsGetCurrlevel(); |
|
GetItemAttrs(item, itemid, curlv); |
|
item.SetNewAnimation(true); |
|
item._iSelFlag = 2; |
|
item._iPostDraw = true; |
|
item._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(Item *item, bool flipFlag) |
|
{ |
|
int it = ItemCAnimTbl[item->_iCurs]; |
|
item->SetNewAnimation(flipFlag); |
|
item->_iRequest = false; |
|
|
|
if (item->_iCurs == ICURS_MAGIC_ROCK) { |
|
item->_iSelFlag = 1; |
|
PlaySfxLoc(ItemDropSnds[it], item->position); |
|
} |
|
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) |
|
{ |
|
AvailableItems[MAXITEMS - ActiveItemCount] = ii; |
|
ActiveItemCount--; |
|
if (ActiveItemCount > 0 && i != ActiveItemCount) |
|
ActiveItems[i] = ActiveItems[ActiveItemCount]; |
|
if (pcursitem == ii) // Unselect item if player has it highlighted |
|
pcursitem = -1; |
|
} |
|
|
|
void ProcessItems() |
|
{ |
|
for (int i = 0; i < ActiveItemCount; i++) { |
|
int ii = ActiveItems[i]; |
|
auto &item = Items[ii]; |
|
if (!item._iAnimFlag) |
|
continue; |
|
item.AnimInfo.ProcessAnimation(); |
|
if (item._iCurs == ICURS_MAGIC_ROCK) { |
|
if (item._iSelFlag == 1 && item.AnimInfo.CurrentFrame == 11) |
|
item.AnimInfo.CurrentFrame = 1; |
|
if (item._iSelFlag == 2 && item.AnimInfo.CurrentFrame == 21) |
|
item.AnimInfo.CurrentFrame = 11; |
|
} else { |
|
if (item.AnimInfo.CurrentFrame == item.AnimInfo.NumberOfFrames / 2) |
|
PlaySfxLoc(ItemDropSnds[ItemCAnimTbl[item._iCurs]], item.position); |
|
|
|
if (item.AnimInfo.CurrentFrame >= item.AnimInfo.NumberOfFrames) { |
|
item.AnimInfo.CurrentFrame = item.AnimInfo.NumberOfFrames; |
|
item._iAnimFlag = false; |
|
item._iSelFlag = 1; |
|
} |
|
} |
|
} |
|
ItemDoppel(); |
|
} |
|
|
|
void FreeItemGFX() |
|
{ |
|
for (auto &itemanim : itemanims) { |
|
itemanim = std::nullopt; |
|
} |
|
} |
|
|
|
void GetItemFrm(Item &item) |
|
{ |
|
item.AnimInfo.pCelSprite = &*itemanims[ItemCAnimTbl[item._iCurs]]; |
|
} |
|
|
|
void GetItemStr(Item &item) |
|
{ |
|
if (item._itype != ITYPE_GOLD) { |
|
if (item._iIdentified) |
|
strcpy(infostr, item._iIName); |
|
else |
|
strcpy(infostr, item._iName); |
|
|
|
InfoColor = item.getTextColor(); |
|
} else { |
|
int nGold = item._ivalue; |
|
strcpy(infostr, fmt::format(ngettext("{:d} gold piece", "{:d} gold pieces", nGold), nGold).c_str()); |
|
} |
|
} |
|
|
|
void CheckIdentify(Player &player, int cii) |
|
{ |
|
Item *pi; |
|
|
|
if (cii >= NUM_INVLOC) |
|
pi = &player.InvList[cii - NUM_INVLOC]; |
|
else |
|
pi = &player.InvBody[cii]; |
|
|
|
pi->_iIdentified = true; |
|
CalcPlrInv(player, true); |
|
|
|
if (&player == &Players[MyPlayerId]) |
|
NewCursor(CURSOR_HAND); |
|
} |
|
|
|
void DoRepair(Player &player, int cii) |
|
{ |
|
Item *pi; |
|
|
|
PlaySfxLoc(IS_REPAIR, player.position.tile); |
|
|
|
if (cii >= NUM_INVLOC) { |
|
pi = &player.InvList[cii - NUM_INVLOC]; |
|
} else { |
|
pi = &player.InvBody[cii]; |
|
} |
|
|
|
RepairItem(*pi, player._pLevel); |
|
CalcPlrInv(player, true); |
|
|
|
if (&player == &Players[MyPlayerId]) |
|
NewCursor(CURSOR_HAND); |
|
} |
|
|
|
void DoRecharge(Player &player, int cii) |
|
{ |
|
Item *pi; |
|
|
|
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(player, true); |
|
} |
|
|
|
if (&player == &Players[MyPlayerId]) |
|
NewCursor(CURSOR_HAND); |
|
} |
|
|
|
void DoOil(Player &player, int cii) |
|
{ |
|
Item *pi; |
|
if (cii >= NUM_INVLOC) { |
|
pi = &player.InvList[cii - NUM_INVLOC]; |
|
} else { |
|
pi = &player.InvBody[cii]; |
|
} |
|
if (!ApplyOilToItem(*pi, player)) |
|
return; |
|
CalcPlrInv(player, true); |
|
if (&player == &Players[MyPlayerId]) |
|
NewCursor(CURSOR_HAND); |
|
} |
|
|
|
void PrintItemPower(char plidx, Item *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: |
|
/*xgettext:no-c-format*/ 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: |
|
/*xgettext:no-c-format*/ strcpy(tempstr, fmt::format(_("{:+d}% armor"), x->_iPLAC).c_str()); |
|
break; |
|
case IPL_SETAC: |
|
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: |
|
/*xgettext:no-c-format*/ strcpy(tempstr, fmt::format(_("+{:d}% light radius"), 10 * x->_iPLLight).c_str()); |
|
break; |
|
case IPL_LIGHT_CURSE: |
|
/*xgettext:no-c-format*/ 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: |
|
/*xgettext:no-c-format*/ 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: |
|
/*xgettext:no-c-format*/ 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; |
|
} |
|
} |
|
|
|
void DrawUniqueInfo(const Surface &out) |
|
{ |
|
if ((chrflag || QuestLogIsOpen) && LeftPanel.Contains({ RightPanel.position.x - SPANEL_WIDTH, RightPanel.position.y })) { |
|
return; |
|
} |
|
|
|
DrawUniqueInfoWindow(GlobalBackBuffer()); |
|
|
|
Rectangle rect { { 32 + RightPanel.position.x - SPANEL_WIDTH, 44 + RightPanel.position.y + 2 * 12 }, { 257, 0 } }; |
|
const UItemStruct &uitem = UniqueItemList[curruitem._iUid]; |
|
DrawString(out, _(uitem.UIName), rect, UiFlags::AlignCenter); |
|
|
|
DrawUniqueInfoDevider(out, 5); |
|
|
|
rect.position.y += (10 - uitem.UINumPL) * 12; |
|
assert(uitem.UINumPL <= sizeof(uitem.powers) / sizeof(*uitem.powers)); |
|
for (const auto &power : uitem.powers) { |
|
if (power.type == IPL_INVALID) |
|
break; |
|
rect.position.y += 2 * 12; |
|
PrintItemPower(power.type, &curruitem); |
|
DrawString(out, tempstr, rect, UiFlags::ColorSilver | UiFlags::AlignCenter); |
|
} |
|
} |
|
|
|
void PrintItemDetails(Item *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")); |
|
ShowUniqueItemInfoBox = true; |
|
curruitem = *x; |
|
} |
|
PrintItemInfo(*x); |
|
} |
|
|
|
void PrintItemDur(Item *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 > 0) { |
|
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 > 0) { |
|
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) |
|
{ |
|
auto &player = Players[p]; |
|
|
|
switch (mid) { |
|
case IMISC_HEAL: |
|
case IMISC_FOOD: { |
|
int j = player._pMaxHP >> 8; |
|
int l = ((j / 2) + GenerateRnd(j)) << 6; |
|
if (player._pClass == HeroClass::Warrior || player._pClass == HeroClass::Barbarian) |
|
l *= 2; |
|
if (player._pClass == HeroClass::Rogue || player._pClass == HeroClass::Monk || player._pClass == HeroClass::Bard) |
|
l += l / 2; |
|
player._pHitPoints = std::min(player._pHitPoints + l, player._pMaxHP); |
|
player._pHPBase = std::min(player._pHPBase + l, player._pMaxHPBase); |
|
drawhpflag = true; |
|
} break; |
|
case IMISC_FULLHEAL: |
|
player._pHitPoints = player._pMaxHP; |
|
player._pHPBase = player._pMaxHPBase; |
|
drawhpflag = true; |
|
break; |
|
case IMISC_MANA: { |
|
int j = player._pMaxMana >> 8; |
|
int l = ((j / 2) + GenerateRnd(j)) << 6; |
|
if (player._pClass == HeroClass::Sorcerer) |
|
l *= 2; |
|
if (player._pClass == HeroClass::Rogue || player._pClass == HeroClass::Monk || player._pClass == HeroClass::Bard) |
|
l += l / 2; |
|
if ((player._pIFlags & ISPL_NOMANA) == 0) { |
|
player._pMana = std::min(player._pMana + l, player._pMaxMana); |
|
player._pManaBase = std::min(player._pManaBase + l, player._pMaxManaBase); |
|
drawmanaflag = true; |
|
} |
|
} break; |
|
case IMISC_FULLMANA: |
|
if ((player._pIFlags & ISPL_NOMANA) == 0) { |
|
player._pMana = player._pMaxMana; |
|
player._pManaBase = player._pMaxManaBase; |
|
drawmanaflag = true; |
|
} |
|
break; |
|
case IMISC_ELIXSTR: |
|
ModifyPlrStr(p, 1); |
|
break; |
|
case IMISC_ELIXMAG: |
|
ModifyPlrMag(p, 1); |
|
if (gbIsHellfire) { |
|
player._pMana = player._pMaxMana; |
|
player._pManaBase = player._pMaxManaBase; |
|
drawmanaflag = true; |
|
} |
|
break; |
|
case IMISC_ELIXDEX: |
|
ModifyPlrDex(p, 1); |
|
break; |
|
case IMISC_ELIXVIT: |
|
ModifyPlrVit(p, 1); |
|
if (gbIsHellfire) { |
|
player._pHitPoints = player._pMaxHP; |
|
player._pHPBase = player._pMaxHPBase; |
|
drawhpflag = true; |
|
} |
|
break; |
|
case IMISC_REJUV: { |
|
int j = player._pMaxHP >> 8; |
|
int l = ((j / 2) + GenerateRnd(j)) << 6; |
|
if (player._pClass == HeroClass::Warrior || player._pClass == HeroClass::Barbarian) |
|
l *= 2; |
|
if (player._pClass == HeroClass::Rogue) |
|
l += l / 2; |
|
player._pHitPoints = std::min(player._pHitPoints + l, player._pMaxHP); |
|
player._pHPBase = std::min(player._pHPBase + l, player._pMaxHPBase); |
|
drawhpflag = true; |
|
j = player._pMaxMana >> 8; |
|
l = ((j / 2) + GenerateRnd(j)) << 6; |
|
if (player._pClass == HeroClass::Sorcerer) |
|
l *= 2; |
|
if (player._pClass == HeroClass::Rogue) |
|
l += l / 2; |
|
if ((player._pIFlags & ISPL_NOMANA) == 0) { |
|
player._pMana = std::min(player._pMana + l, player._pMaxMana); |
|
player._pManaBase = std::min(player._pManaBase + l, player._pMaxManaBase); |
|
drawmanaflag = true; |
|
} |
|
} break; |
|
case IMISC_FULLREJUV: |
|
player._pHitPoints = player._pMaxHP; |
|
player._pHPBase = player._pMaxHPBase; |
|
drawhpflag = true; |
|
if ((player._pIFlags & ISPL_NOMANA) == 0) { |
|
player._pMana = player._pMaxMana; |
|
player._pManaBase = player._pMaxManaBase; |
|
drawmanaflag = true; |
|
} |
|
break; |
|
case IMISC_SCROLL: |
|
if (spelldata[spl].sTargeted) { |
|
player._pTSpell = spl; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
} else { |
|
ClrPlrPath(player); |
|
player._pSpell = spl; |
|
player._pSplType = RSPLTYPE_INVALID; |
|
player._pSplFrom = 3; |
|
player.destAction = ACTION_SPELL; |
|
player.destParam1 = cursPosition.x; |
|
player.destParam2 = cursPosition.y; |
|
if (p == MyPlayerId && spl == SPL_NOVA) |
|
NetSendCmdLoc(MyPlayerId, true, CMD_NOVA, cursPosition); |
|
} |
|
break; |
|
case IMISC_SCROLLT: |
|
if (spelldata[spl].sTargeted) { |
|
player._pTSpell = spl; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
} else { |
|
ClrPlrPath(player); |
|
player._pSpell = spl; |
|
player._pSplType = RSPLTYPE_INVALID; |
|
player._pSplFrom = 3; |
|
player.destAction = ACTION_SPELL; |
|
player.destParam1 = cursPosition.x; |
|
player.destParam2 = cursPosition.y; |
|
} |
|
break; |
|
case IMISC_BOOK: |
|
player._pMemSpells |= GetSpellBitmask(spl); |
|
if (player._pSplLvl[spl] < MAX_SPELL_LEVEL) |
|
player._pSplLvl[spl]++; |
|
if ((player._pIFlags & ISPL_NOMANA) == 0) { |
|
player._pMana += spelldata[spl].sManaCost << 6; |
|
player._pMana = std::min(player._pMana, player._pMaxMana); |
|
player._pManaBase += spelldata[spl].sManaCost << 6; |
|
player._pManaBase = std::min(player._pManaBase, player._pMaxManaBase); |
|
} |
|
if (p == MyPlayerId) |
|
CalcPlrBookVals(player); |
|
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: |
|
player._pOilType = mid; |
|
if (p != MyPlayerId) { |
|
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: |
|
player._pTSpell = SPL_RUNEFIRE; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
break; |
|
case IMISC_RUNEL: |
|
player._pTSpell = SPL_RUNELIGHT; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
break; |
|
case IMISC_GR_RUNEL: |
|
player._pTSpell = SPL_RUNENOVA; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
break; |
|
case IMISC_GR_RUNEF: |
|
player._pTSpell = SPL_RUNEIMMOLAT; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
break; |
|
case IMISC_RUNES: |
|
player._pTSpell = SPL_RUNESTONE; |
|
if (p == MyPlayerId) |
|
NewCursor(CURSOR_TELEPORT); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
void SpawnSmith(int lvl) |
|
{ |
|
constexpr int PinnedItemCount = 0; |
|
|
|
Item holditem; |
|
holditem = Items[0]; |
|
|
|
int maxValue = 140000; |
|
int maxItems = 20; |
|
if (gbIsHellfire) { |
|
maxValue = 200000; |
|
maxItems = 25; |
|
} |
|
|
|
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 itemData = RndSmithItem(lvl) - 1; |
|
GetItemAttrs(Items[0], itemData, 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; |
|
} |
|
|
|
void SpawnPremium(int pnum) |
|
{ |
|
int8_t lvl = Players[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) { |
|
// Discard first 3 items and shift next 10 |
|
std::move(&premiumitems[3], &premiumitems[12] + 1, &premiumitems[0]); |
|
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 { |
|
// Discard first 2 items and shift next 3 |
|
std::move(&premiumitems[2], &premiumitems[4] + 1, &premiumitems[0]); |
|
SpawnOnePremium(3, premiumlevel + premiumlvladd[3], pnum); |
|
premiumitems[4] = premiumitems[5]; |
|
SpawnOnePremium(5, premiumlevel + premiumlvladd[5], pnum); |
|
} |
|
} |
|
} |
|
|
|
void SpawnWitch(int lvl) |
|
{ |
|
constexpr int PinnedItemCount = 3; |
|
|
|
int j = PinnedItemCount; |
|
|
|
memset(&Items[0], 0, sizeof(*Items)); |
|
GetItemAttrs(Items[0], IDI_MANA, 1); |
|
witchitem[0] = Items[0]; |
|
witchitem[0]._iCreateInfo = lvl; |
|
witchitem[0]._iStatFlag = true; |
|
memset(&Items[0], 0, sizeof(*Items)); |
|
GetItemAttrs(Items[0], IDI_FULLMANA, 1); |
|
witchitem[1] = Items[0]; |
|
witchitem[1]._iCreateInfo = lvl; |
|
witchitem[1]._iStatFlag = true; |
|
memset(&Items[0], 0, sizeof(*Items)); |
|
GetItemAttrs(Items[0], IDI_PORTAL, 1); |
|
witchitem[2] = Items[0]; |
|
witchitem[2]._iCreateInfo = lvl; |
|
witchitem[2]._iStatFlag = true; |
|
|
|
int maxValue = 140000; |
|
int reservedItems = 12; |
|
if (gbIsHellfire) { |
|
maxValue = 200000; |
|
reservedItems = 10; |
|
|
|
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); |
|
AdvanceRndSeed(); |
|
|
|
GetItemAttrs(Items[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++; |
|
} |
|
} |
|
int iCnt = GenerateRnd(WITCH_ITEMS - reservedItems) + 10; |
|
|
|
for (int i = j; i < iCnt; i++) { |
|
do { |
|
memset(&Items[0], 0, sizeof(*Items)); |
|
Items[0]._iSeed = AdvanceRndSeed(); |
|
SetRndSeed(Items[0]._iSeed); |
|
int itemData = RndWitchItem(lvl) - 1; |
|
GetItemAttrs(Items[0], itemData, lvl); |
|
int maxlvl = -1; |
|
if (GenerateRnd(100) <= 5) |
|
maxlvl = 2 * lvl; |
|
if (maxlvl == -1 && Items[0]._iMiscId == IMISC_STAFF) |
|
maxlvl = 2 * lvl; |
|
if (maxlvl != -1) |
|
GetItemBonus(Items[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); |
|
} |
|
|
|
void SpawnBoy(int lvl) |
|
{ |
|
int ivalue = 0; |
|
bool keepgoing = false; |
|
int count = 0; |
|
|
|
auto &myPlayer = Players[MyPlayerId]; |
|
|
|
HeroClass pc = myPlayer._pClass; |
|
int strength = std::max(myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength), myPlayer._pStrength); |
|
int dexterity = std::max(myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity), myPlayer._pDexterity); |
|
int magic = std::max(myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic), myPlayer._pMagic); |
|
strength += strength / 5; |
|
dexterity += dexterity / 5; |
|
magic += magic / 5; |
|
|
|
if (boylevel >= (lvl / 2) && !boyitem.isEmpty()) |
|
return; |
|
do { |
|
keepgoing = false; |
|
memset(&Items[0], 0, sizeof(*Items)); |
|
Items[0]._iSeed = AdvanceRndSeed(); |
|
SetRndSeed(Items[0]._iSeed); |
|
int itype = RndBoyItem(lvl) - 1; |
|
GetItemAttrs(Items[0], itype, lvl); |
|
GetItemBonus(Items[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 *const mostValuablePlayerArmor = myPlayer.GetMostValuableItem( |
|
[](const Item &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 *const mostValuablePlayerItem = myPlayer.GetMostValuableItem( |
|
[itemType](const Item &item) { return item._itype == itemType; }); |
|
|
|
ivalue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue; |
|
break; |
|
} |
|
} |
|
ivalue = ivalue * 4 / 5; // avoids forced int > float > int conversion |
|
|
|
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; |
|
} |
|
|
|
void SpawnHealer(int lvl) |
|
{ |
|
constexpr int PinnedItemCount = 2; |
|
|
|
int srnd; |
|
|
|
memset(&Items[0], 0, sizeof(*Items)); |
|
GetItemAttrs(Items[0], IDI_HEAL, 1); |
|
healitem[0] = Items[0]; |
|
healitem[0]._iCreateInfo = lvl; |
|
healitem[0]._iStatFlag = true; |
|
|
|
memset(&Items[0], 0, sizeof(*Items)); |
|
GetItemAttrs(Items[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(Items[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(Items[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(Items[0], IDI_GOLD, 1); |
|
golditem = Items[0]; |
|
golditem._iStatFlag = true; |
|
} |
|
|
|
int ItemNoFlippy() |
|
{ |
|
int r = ActiveItems[ActiveItemCount - 1]; |
|
Items[r].AnimInfo.CurrentFrame = Items[r].AnimInfo.NumberOfFrames; |
|
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 (ActiveItemCount >= MAXITEMS) |
|
return; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
|
|
while (true) { |
|
memset(&item, 0, sizeof(*Items)); |
|
SetupAllItems(item, idx, AdvanceRndSeed(), 2 * lvl, 1, true, false, delta); |
|
if (item._iMiscId == IMISC_BOOK && item._iSpell == ispell) |
|
break; |
|
} |
|
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 = ItemsGetCurrlevel(); |
|
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 = ItemsGetCurrlevel(); |
|
|
|
CreateMagicItem(position, curlv, imisc, imid, icurs, sendmsg, delta); |
|
} |
|
|
|
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; |
|
} |
|
} |
|
} |
|
|
|
#ifdef _DEBUG |
|
std::mt19937 BetterRng; |
|
std::string DebugSpawnItem(std::string itemName, bool unique) |
|
{ |
|
if (ActiveItemCount >= MAXITEMS) |
|
return "No space to generate the item!"; |
|
|
|
const int max_time = 3000; |
|
const int max_iter = 1000000; |
|
|
|
bool onlygood = true; |
|
|
|
int ii = AllocateItem(); |
|
auto &item = Items[ii]; |
|
Point pos = Players[MyPlayerId].position.tile; |
|
GetSuperItemSpace(pos, ii); |
|
std::transform(itemName.begin(), itemName.end(), itemName.begin(), [](unsigned char c) { return std::tolower(c); }); |
|
|
|
uint32_t begin = SDL_GetTicks(); |
|
Monster fake_m; |
|
fake_m.MData = &MonsterData[0]; |
|
fake_m._uniqtype = 0; |
|
int i = 0; |
|
for (;; i++) { |
|
// using a better rng here to seed the item to prevent getting stuck repeating same values using old one |
|
std::uniform_int_distribution<int32_t> dist(0, INT_MAX); |
|
SetRndSeed(dist(BetterRng)); |
|
if (SDL_GetTicks() - begin > max_time) |
|
return fmt::format("Item not found in {:d} seconds!", max_time / 1000); |
|
|
|
if (i > max_iter) |
|
return fmt::format("Item not found in {:d} tries!", max_iter); |
|
|
|
fake_m.mLevel = dist(BetterRng) % CF_LEVEL + 1; |
|
int idx = RndItem(fake_m); |
|
if (idx > 0) { |
|
idx--; |
|
onlygood = false; |
|
} else |
|
continue; |
|
|
|
int uper = (unique ? 15 : 1); |
|
|
|
Point bkp = item.position; |
|
memset(&item, 0, sizeof(Item)); |
|
item.position = bkp; |
|
memset(UniqueItemFlags, 0, sizeof(UniqueItemFlags)); |
|
SetupAllItems(item, idx, AdvanceRndSeed(), fake_m.mLevel, uper, onlygood, false, false); |
|
|
|
std::string tmp(item._iIName); |
|
std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c) { return std::tolower(c); }); |
|
if (tmp.find(itemName) != std::string::npos) |
|
break; |
|
|
|
if (unique) |
|
if (item._iMagical != ITEM_QUALITY_UNIQUE) |
|
continue; |
|
} |
|
|
|
item._iIdentified = true; |
|
NetSendCmdDItem(false, ii); |
|
return fmt::format("Item generated successfully - iterations: {:d}", i); |
|
} |
|
#endif |
|
|
|
void Item::SetNewAnimation(bool showAnimation) |
|
{ |
|
int it = ItemCAnimTbl[_iCurs]; |
|
int numberOfFrames = ItemAnimLs[it]; |
|
auto *pCelSprite = itemanims[it] ? &*itemanims[it] : nullptr; |
|
if (_iCurs != ICURS_MAGIC_ROCK) |
|
AnimInfo.SetNewAnimation(pCelSprite, numberOfFrames, 1, AnimationDistributionFlags::ProcessAnimationPending, 0, numberOfFrames); |
|
else |
|
AnimInfo.SetNewAnimation(pCelSprite, numberOfFrames, 1); |
|
_iPostDraw = false; |
|
_iRequest = false; |
|
if (showAnimation) { |
|
_iAnimFlag = true; |
|
_iSelFlag = 0; |
|
} else { |
|
AnimInfo.CurrentFrame = AnimInfo.NumberOfFrames; |
|
_iAnimFlag = false; |
|
_iSelFlag = 1; |
|
} |
|
} |
|
|
|
void initItemGetRecords() |
|
{ |
|
memset(itemrecord, 0, sizeof(itemrecord)); |
|
gnNumGetRecords = 0; |
|
} |
|
|
|
} // namespace devilution
|
|
|