/**
* @ 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/compile.h>
# include <fmt/format.h>
# include "DiabloUI/ui_flags.hpp"
# include "controls/plrctrls.h"
# include "cursor.h"
# include "doom.h"
# include "engine/cel_sprite.hpp"
# include "engine/dx.h"
# 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 "inv_iterators.hpp"
# include "levels/town.h"
# include "lighting.h"
# include "missiles.h"
# include "options.h"
# include "panels/info_box.hpp"
# include "panels/ui_panels.hpp"
# include "player.h"
# include "qol/stash.h"
# include "spells.h"
# include "stores.h"
# include "utils/format_int.hpp"
# include "utils/language.h"
# include "utils/math.h"
# include "utils/stdcompat/algorithm.hpp"
# include "utils/utf8.hpp"
namespace devilution {
Item Items [ MAXITEMS + 1 ] ;
uint8_t ActiveItems [ MAXITEMS ] ;
uint8_t ActiveItemCount ;
int8_t dItem [ MAXDUNX ] [ MAXDUNY ] ;
bool ShowUniqueItemInfoBox ;
CornerStoneStruct CornerStone ;
bool UniqueItemFlags [ 128 ] ;
int MaxGold = GOLD_MAX_LIMIT ;
/** Maps from item_cursor_graphic to in-memory item type. */
int8_t 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 {
OptionalOwnedCelSprite itemanims [ ITEMTYPES ] ;
enum class PlayerArmorGraphic : uint8_t {
// clang-format off
Light = 0 ,
Medium = 1 < < 4 ,
Heavy = 1 < < 5 ,
// clang-format on
} ;
Item curruitem ;
items: add BUGFIX, reset item get records when resetting items (#2691)
* items: add BUGFIX, reset item get records when resetting items
The item get record array tracks items being recently looted in an
effort to prevent the same item from being looted more than once.
Prior to this commit, the item get record array (and corresponding
item get record array length) variables were not cleared when
creating a new game. Therefore, the item get record array of a
previous game could remain in between games and prevent an item
from being looted (if it was looted in a previous), even if it was
never looted in the current game. In practice this almost never
shows up, since each item get record is valid for a total of 6
seconds before being cleared. So, you would either have to save
a game, quickly loot an item, when load the game and try to loot
the same item before 6 seconds pass. OR, you could use the demo
replay functionality to run test cases, and speed up execution to
run e.g. 10'000'000 logic ticks per second. Both would exhibit the
bug and prevent the item from being looted.
This commit fixes such issues by explicitly clearing the item get
record array and item get record array length variables whenever
items are initialized.
5 years ago
/** 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 ] ;
items: add BUGFIX, reset item get records when resetting items (#2691)
* items: add BUGFIX, reset item get records when resetting items
The item get record array tracks items being recently looted in an
effort to prevent the same item from being looted more than once.
Prior to this commit, the item get record array (and corresponding
item get record array length) variables were not cleared when
creating a new game. Therefore, the item get record array of a
previous game could remain in between games and prevent an item
from being looted (if it was looted in a previous), even if it was
never looted in the current game. In practice this almost never
shows up, since each item get record is valid for a total of 6
seconds before being cleared. So, you would either have to save
a game, quickly loot an item, when load the game and try to loot
the same item before 6 seconds pass. OR, you could use the demo
replay functionality to run test cases, and speed up execution to
run e.g. 10'000'000 logic ticks per second. Both would exhibit the
bug and prevent the item from being looted.
This commit fixes such issues by explicitly clearing the item get
record array and item get record array length variables whenever
items are initialized.
5 years ago
bool itemhold [ 3 ] [ 3 ] ;
items: add BUGFIX, reset item get records when resetting items (#2691)
* items: add BUGFIX, reset item get records when resetting items
The item get record array tracks items being recently looted in an
effort to prevent the same item from being looted more than once.
Prior to this commit, the item get record array (and corresponding
item get record array length) variables were not cleared when
creating a new game. Therefore, the item get record array of a
previous game could remain in between games and prevent an item
from being looted (if it was looted in a previous), even if it was
never looted in the current game. In practice this almost never
shows up, since each item get record is valid for a total of 6
seconds before being cleared. So, you would either have to save
a game, quickly loot an item, when load the game and try to loot
the same item before 6 seconds pass. OR, you could use the demo
replay functionality to run test cases, and speed up execution to
run e.g. 10'000'000 logic ticks per second. Both would exhibit the
bug and prevent the item from being looted.
This commit fixes such issues by explicitly clearing the item get
record array and item get record array length variables whenever
items are initialized.
5 years ago
/** 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. */
int8_t 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 , AffixItemType flgs )
{
AffixItemType itemTypes = ItemPrefixes [ i ] . PLIType ;
if ( ! gbIsHellfire ) {
if ( i > 82 )
return false ;
if ( i > = 12 & & i < = 20 )
itemTypes & = ~ AffixItemType : : Staff ;
}
return HasAnyOf ( flgs , itemTypes ) ;
}
bool IsSuffixValidForItemType ( int i , AffixItemType flgs )
{
AffixItemType 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 & = ~ AffixItemType : : Staff ;
}
return HasAnyOf ( flgs , itemTypes ) ;
}
int ItemsGetCurrlevel ( )
{
if ( leveltype = = DTYPE_NEST )
return currlevel - 8 ;
if ( leveltype = = DTYPE_CRYPT )
return currlevel - 7 ;
return currlevel ;
}
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 ( IsObjectAtPosition ( position ) )
return false ;
if ( TileContainsSetPiece ( position ) )
return false ;
if ( IsTileSolid ( position ) )
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 ( FlipCoin ( ) )
GetItemAttrs ( item , IDI_HEAL , curlv ) ;
else
GetItemAttrs ( item , IDI_MANA , curlv ) ;
item . _iCreateInfo = curlv | CF_PREGEN ;
SetupItem ( item ) ;
item . AnimInfo . currentFrame = item . AnimInfo . numberOfFrames - 1 ;
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 ;
// first iteration is used for collecting stat bonuses from items
for ( Item & equipment : EquippedPlayerItemsRange ( player ) ) {
equipment . _iStatFlag = true ;
if ( equipment . _iIdentified ) {
sa + = equipment . _iPLStr ;
ma + = equipment . _iPLMag ;
da + = equipment . _iPLDex ;
}
}
bool changeflag ;
do {
// cap stats to 0
const int currstr = std : : max ( 0 , sa + player . _pBaseStr ) ;
const int currmag = std : : max ( 0 , ma + player . _pBaseMag ) ;
const int currdex = std : : max ( 0 , da + player . _pBaseDex ) ;
changeflag = false ;
for ( Item & equipment : EquippedPlayerItemsRange ( player ) ) {
if ( ! equipment . _iStatFlag )
continue ;
if ( currstr > = equipment . _iMinStr
& & currmag > = equipment . _iMinMag
& & currdex > = equipment . _iMinDex )
continue ;
changeflag = true ;
equipment . _iStatFlag = false ;
if ( equipment . _iIdentified ) {
sa - = equipment . _iPLStr ;
ma - = equipment . _iPLMag ;
da - = equipment . _iPLDex ;
}
}
} while ( changeflag ) ;
}
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 ;
}
const string_view spellName = pgettext ( " spell " , spelldata [ bs ] . sNameText ) ;
const size_t iNameLen = string_view ( item . _iName ) . size ( ) ;
const size_t iINameLen = string_view ( item . _iIName ) . size ( ) ;
CopyUtf8 ( item . _iName + iNameLen , spellName , sizeof ( item . _iName ) - iNameLen ) ;
CopyUtf8 ( item . _iIName + iINameLen , spellName , sizeof ( item . _iIName ) - iINameLen ) ;
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 , ItemPower & power )
{
if ( ! gbIsHellfire ) {
if ( power . type = = IPL_TARGAC ) {
power . param1 = 1 < < power . param1 ;
power . param2 = 3 < < power . param2 ;
}
}
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 | = ItemSpecialEffectHf : : 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 | = ItemSpecialEffect : : FireDamage ;
item . _iFlags & = ~ ItemSpecialEffect : : LightningDamage ;
item . _iFMinDam = power . param1 ;
item . _iFMaxDam = power . param2 ;
item . _iLMinDam = 0 ;
item . _iLMaxDam = 0 ;
break ;
case IPL_LIGHTDAM :
item . _iFlags | = ItemSpecialEffect : : LightningDamage ;
item . _iFlags & = ~ ItemSpecialEffect : : FireDamage ;
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 | = ItemSpecialEffect : : MultipleArrows ;
break ;
case IPL_FIRE_ARROWS :
item . _iFlags | = ItemSpecialEffect : : FireArrows ;
item . _iFlags & = ~ ItemSpecialEffect : : LightningArrows ;
item . _iFMinDam = power . param1 ;
item . _iFMaxDam = power . param2 ;
item . _iLMinDam = 0 ;
item . _iLMaxDam = 0 ;
break ;
case IPL_LIGHT_ARROWS :
item . _iFlags | = ItemSpecialEffect : : LightningArrows ;
item . _iFlags & = ~ ItemSpecialEffect : : FireArrows ;
item . _iLMinDam = power . param1 ;
item . _iLMaxDam = power . param2 ;
item . _iFMinDam = 0 ;
item . _iFMaxDam = 0 ;
break ;
case IPL_FIREBALL :
item . _iFlags | = ( ItemSpecialEffect : : LightningArrows | ItemSpecialEffect : : FireArrows ) ;
item . _iFMinDam = power . param1 ;
item . _iFMaxDam = power . param2 ;
item . _iLMinDam = 0 ;
item . _iLMaxDam = 0 ;
break ;
case IPL_THORNS :
item . _iFlags | = ItemSpecialEffect : : Thorns ;
break ;
case IPL_NOMANA :
item . _iFlags | = ItemSpecialEffect : : NoMana ;
drawmanaflag = true ;
break ;
case IPL_NOHEALPLR :
item . _iFlags | = ItemSpecialEffect : : NoHealOnPlayer ;
break ;
case IPL_ABSHALFTRAP :
item . _iFlags | = ItemSpecialEffect : : HalfTrapDamage ;
break ;
case IPL_KNOCKBACK :
item . _iFlags | = ItemSpecialEffect : : Knockback ;
break ;
case IPL_3XDAMVDEM :
item . _iFlags | = ItemSpecialEffect : : TripleDemonDamage ;
break ;
case IPL_ALLRESZERO :
item . _iFlags | = ItemSpecialEffect : : ZeroResistance ;
break ;
case IPL_NOHEALMON :
item . _iFlags | = ItemSpecialEffect : : NoHealOnMonsters ;
break ;
case IPL_STEALMANA :
if ( power . param1 = = 3 )
item . _iFlags | = ItemSpecialEffect : : StealMana3 ;
if ( power . param1 = = 5 )
item . _iFlags | = ItemSpecialEffect : : StealMana5 ;
drawmanaflag = true ;
break ;
case IPL_STEALLIFE :
if ( power . param1 = = 3 )
item . _iFlags | = ItemSpecialEffect : : StealLife3 ;
if ( power . param1 = = 5 )
item . _iFlags | = ItemSpecialEffect : : StealLife5 ;
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 | = ItemSpecialEffect : : QuickAttack ;
if ( power . param1 = = 2 )
item . _iFlags | = ItemSpecialEffect : : FastAttack ;
if ( power . param1 = = 3 )
item . _iFlags | = ItemSpecialEffect : : FasterAttack ;
if ( power . param1 = = 4 )
item . _iFlags | = ItemSpecialEffect : : FastestAttack ;
break ;
case IPL_FASTRECOVER :
if ( power . param1 = = 1 )
item . _iFlags | = ItemSpecialEffect : : FastHitRecovery ;
if ( power . param1 = = 2 )
item . _iFlags | = ItemSpecialEffect : : FasterHitRecovery ;
if ( power . param1 = = 3 )
item . _iFlags | = ItemSpecialEffect : : FastestHitRecovery ;
break ;
case IPL_FASTBLOCK :
item . _iFlags | = ItemSpecialEffect : : FastBlock ;
break ;
case IPL_DAMMOD :
item . _iPLDamMod + = r ;
break ;
case IPL_RNDARROWVEL :
item . _iFlags | = ItemSpecialEffect : : RandomArrowVelocity ;
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 | = ItemSpecialEffect : : FasterAttack ;
break ;
case IPL_ONEHAND :
item . _iLoc = ILOC_ONEHAND ;
break ;
case IPL_DRAINLIFE :
item . _iFlags | = ItemSpecialEffect : : DrainLife ;
break ;
case IPL_RNDSTEALLIFE :
item . _iFlags | = ItemSpecialEffect : : RandomStealLife ;
break ;
case IPL_INFRAVISION :
item . _iFlags | = ItemSpecialEffect : : Infravision ;
break ;
case IPL_NOMINSTR :
item . _iMinStr = 0 ;
break ;
case IPL_INVCURS :
item . _iCurs = power . param1 ;
break ;
case IPL_ADDACLIFE :
item . _iFlags | = ( ItemSpecialEffect : : LightningArrows | ItemSpecialEffect : : FireArrows ) ;
item . _iFMinDam = power . param1 ;
item . _iFMaxDam = power . param2 ;
item . _iLMinDam = 1 ;
item . _iLMaxDam = 0 ;
break ;
case IPL_ADDMANAAC :
item . _iFlags | = ( ItemSpecialEffect : : LightningDamage | ItemSpecialEffect : : FireDamage ) ;
item . _iFMinDam = power . param1 ;
item . _iFMaxDam = power . param2 ;
item . _iLMinDam = 2 ;
item . _iLMaxDam = 0 ;
break ;
case IPL_FIRERESCLVL :
item . _iPLFR = 30 - MyPlayer - > _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 | = ItemSpecialEffectHf : : Devastation ;
break ;
case IPL_DECAY :
item . _iDamAcFlags | = ItemSpecialEffectHf : : Decay ;
item . _iPLDam + = r ;
break ;
case IPL_PERIL :
item . _iDamAcFlags | = ItemSpecialEffectHf : : Peril ;
break ;
case IPL_JESTERS :
item . _iDamAcFlags | = ItemSpecialEffectHf : : Jesters ;
break ;
case IPL_ACDEMON :
item . _iDamAcFlags | = ItemSpecialEffectHf : : ACAgainstDemons ;
break ;
case IPL_ACUNDEAD :
item . _iDamAcFlags | = ItemSpecialEffectHf : : ACAgainstUndead ;
break ;
case IPL_MANATOLIFE : {
int portion = ( ( MyPlayer - > _pMaxManaBase > > 6 ) * 50 / 100 ) < < 6 ;
item . _iPLMana - = portion ;
item . _iPLHP + = portion ;
} break ;
case IPL_LIFETOMANA : {
int portion = ( ( MyPlayer - > _pMaxHPBase > > 6 ) * 40 / 100 ) < < 6 ;
item . _iPLHP - = portion ;
item . _iPLMana + = portion ;
} break ;
default :
break ;
}
return r ;
}
bool StringInPanel ( const char * str )
{
return GetLineWidth ( str , GameFont12 , 2 ) < 254 ;
}
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 ;
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 , AffixItemType : : 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 ) ] ;
item . _iMagical = ITEM_QUALITY_MAGIC ;
SaveItemAffix ( item , ItemPrefixes [ preidx ] ) ;
item . _iPrePower = ItemPrefixes [ preidx ] . power . type ;
}
}
string_view baseName = _ ( AllItemsList [ item . IDidx ] . iName ) ;
string_view shortName = _ ( AllItemsList [ item . IDidx ] . iSName ) ;
string_view spellName = pgettext ( " spell " , spelldata [ bs ] . sNameText ) ;
string_view normalFmt = pgettext ( " spell " , /* TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall */ " {0} of {1} " ) ;
CopyUtf8 ( item . _iName , fmt : : format ( fmt : : runtime ( normalFmt ) , baseName , spellName ) , sizeof ( item . _iName ) ) ;
if ( ! StringInPanel ( item . _iName ) ) {
CopyUtf8 ( item . _iName , fmt : : format ( fmt : : runtime ( normalFmt ) , shortName , spellName ) , sizeof ( item . _iName ) ) ;
}
if ( preidx ! = - 1 ) {
string_view magicFmt = pgettext ( " spell " , /* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Spell}. Example: King's War Staff of Firewall */ " {0} {1} of {2} " ) ;
string_view prefixName = _ ( ItemPrefixes [ preidx ] . PLName ) ;
CopyUtf8 ( item . _iIName , fmt : : format ( fmt : : runtime ( magicFmt ) , prefixName , baseName , spellName ) , sizeof ( item . _iIName ) ) ;
if ( ! StringInPanel ( item . _iIName ) ) {
CopyUtf8 ( item . _iIName , fmt : : format ( fmt : : runtime ( magicFmt ) , prefixName , shortName , spellName ) , sizeof ( item . _iIName ) ) ;
}
} else {
CopyUtf8 ( item . _iIName , item . _iName , sizeof ( item . _iIName ) ) ;
}
CalcItemValue ( item ) ;
}
namespace {
std : : string GenerateMagicItemName ( const string_view & baseNamel , int preidx , int sufidx )
{
if ( preidx ! = - 1 & & sufidx ! = - 1 ) {
string_view fmt = _ ( /* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale */ " {0} {1} of {2} " ) ;
return fmt : : format ( fmt : : runtime ( fmt ) , _ ( ItemPrefixes [ preidx ] . PLName ) , baseNamel , _ ( ItemSuffixes [ sufidx ] . PLName ) ) ;
} else if ( preidx ! = - 1 ) {
string_view fmt = _ ( /* TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword */ " {0} {1} " ) ;
return fmt : : format ( fmt : : runtime ( fmt ) , _ ( ItemPrefixes [ preidx ] . PLName ) , baseNamel ) ;
} else if ( sufidx ! = - 1 ) {
string_view fmt = _ ( /* TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale */ " {0} of {1} " ) ;
return fmt : : format ( fmt : : runtime ( fmt ) , baseNamel , _ ( ItemSuffixes [ sufidx ] . PLName ) ) ;
}
return std : : string ( baseNamel ) ;
}
} // namespace
void GetItemPower ( Item & item , int minlvl , int maxlvl , AffixItemType flgs , bool onlygood )
{
int l [ 256 ] ;
goodorevil goe ;
int pre = GenerateRnd ( 4 ) ;
int post = GenerateRnd ( 3 ) ;
if ( pre ! = 0 & & post = = 0 ) {
if ( FlipCoin ( ) )
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 ( HasAnyOf ( flgs , AffixItemType : : 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 ) ] ;
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 ) ] ;
item . _iMagical = ITEM_QUALITY_MAGIC ;
SaveItemAffix ( item , ItemSuffixes [ sufidx ] ) ;
item . _iSufPower = ItemSuffixes [ sufidx ] . power . type ;
}
}
CopyUtf8 ( item . _iIName , GenerateMagicItemName ( item . _iName , preidx , sufidx ) , sizeof ( item . _iIName ) ) ;
if ( ! StringInPanel ( item . _iIName ) ) {
CopyUtf8 ( item . _iIName , GenerateMagicItemName ( _ ( AllItemsList [ item . IDidx ] . iSName ) , preidx , sufidx ) , sizeof ( item . _iIName ) ) ;
}
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 , AffixItemType : : 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 ;
}
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 ) ] ;
CopyUtf8 ( item . _iName , _ ( OilNames [ t ] ) , sizeof ( item . _iName ) ) ;
CopyUtf8 ( item . _iIName , _ ( OilNames [ t ] ) , sizeof ( item . _iIName ) ) ;
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 ItemType : : Sword :
case ItemType : : Axe :
case ItemType : : Mace :
GetItemPower ( item , minlvl , maxlvl , AffixItemType : : Weapon , onlygood ) ;
break ;
case ItemType : : Bow :
GetItemPower ( item , minlvl , maxlvl , AffixItemType : : Bow , onlygood ) ;
break ;
case ItemType : : Shield :
GetItemPower ( item , minlvl , maxlvl , AffixItemType : : Shield , onlygood ) ;
break ;
case ItemType : : LightArmor :
case ItemType : : Helm :
case ItemType : : MediumArmor :
case ItemType : : HeavyArmor :
GetItemPower ( item , minlvl , maxlvl , AffixItemType : : Armor , onlygood ) ;
break ;
case ItemType : : Staff :
if ( allowspells )
GetStaffSpell ( item , maxlvl , onlygood ) ;
else
GetItemPower ( item , minlvl , maxlvl , AffixItemType : : Staff , onlygood ) ;
break ;
case ItemType : : Ring :
case ItemType : : Amulet :
GetItemPower ( item , minlvl , maxlvl , AffixItemType : : Misc , onlygood ) ;
break ;
case ItemType : : None :
case ItemType : : Misc :
case ItemType : : Gold :
break ;
}
}
int RndUItem ( Monster * monster )
{
if ( monster ! = nullptr & & ( monster - > data ( ) . mTreasure & T_UNIQ ) ! = 0 & & ! gbIsMultiplayer )
return - ( ( monster - > data ( ) . 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 - > level < AllItemsList [ i ] . iMinMLvl )
okflag = false ;
} else {
if ( 2 * curlv < AllItemsList [ i ] . iMinMLvl )
okflag = false ;
}
if ( AllItemsList [ i ] . itype = = ItemType : : Misc )
okflag = false ;
if ( AllItemsList [ i ] . itype = = ItemType : : 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 ( ItemType itemType , 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 ! = itemType )
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 ; UniqueItems [ j ] . UIItemId ! = UITYPE_INVALID ; j + + ) {
if ( ! IsUniqueAvailable ( j ) )
break ;
if ( UniqueItems [ j ] . UIItemId = = AllItemsList [ item . IDidx ] . iItemId
& & lvl > = UniqueItems [ 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 ( auto power : UniqueItems [ uid ] . powers ) {
if ( power . type = = IPL_INVALID )
break ;
SaveItemPower ( item , power ) ;
}
CopyUtf8 ( item . _iIName , _ ( UniqueItems [ uid ] . UIName ) , sizeof ( item . _iIName ) ) ;
item . _iIvalue = UniqueItems [ 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 )
{
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 ) {
int 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 )
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
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 ( FlipCoin ( ) )
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 = 10 ;
}
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 PrintItemOil ( char iDidx )
{
switch ( iDidx ) {
case IMISC_OILACC :
AddPanelString ( _ ( " increases a weapon's " ) ) ;
AddPanelString ( _ ( " chance to hit " ) ) ;
break ;
case IMISC_OILMAST :
AddPanelString ( _ ( " greatly increases a " ) ) ;
AddPanelString ( _ ( " weapon's chance to hit " ) ) ;
break ;
case IMISC_OILSHARP :
AddPanelString ( _ ( " increases a weapon's " ) ) ;
AddPanelString ( _ ( " damage potential " ) ) ;
break ;
case IMISC_OILDEATH :
AddPanelString ( _ ( " greatly increases a weapon's " ) ) ;
AddPanelString ( _ ( " damage potential - not bows " ) ) ;
break ;
case IMISC_OILSKILL :
AddPanelString ( _ ( " reduces attributes needed " ) ) ;
AddPanelString ( _ ( " to use armor or weapons " ) ) ;
break ;
case IMISC_OILBSMTH :
AddPanelString ( /*xgettext:no-c-format*/ _ ( " restores 20% of an " ) ) ;
AddPanelString ( _ ( " item's durability " ) ) ;
break ;
case IMISC_OILFORT :
AddPanelString ( _ ( " increases an item's " ) ) ;
AddPanelString ( _ ( " current and max durability " ) ) ;
break ;
case IMISC_OILPERM :
AddPanelString ( _ ( " makes an item indestructible " ) ) ;
break ;
case IMISC_OILHARD :
AddPanelString ( _ ( " increases the armor class " ) ) ;
AddPanelString ( _ ( " of armor and shields " ) ) ;
break ;
case IMISC_OILIMP :
AddPanelString ( _ ( " greatly increases the armor " ) ) ;
AddPanelString ( _ ( " class of armor and shields " ) ) ;
break ;
case IMISC_RUNEF :
AddPanelString ( _ ( " sets fire trap " ) ) ;
break ;
case IMISC_RUNEL :
case IMISC_GR_RUNEL :
AddPanelString ( _ ( " sets lightning trap " ) ) ;
break ;
case IMISC_GR_RUNEF :
AddPanelString ( _ ( " sets fire trap " ) ) ;
break ;
case IMISC_RUNES :
AddPanelString ( _ ( " sets petrification trap " ) ) ;
break ;
case IMISC_FULLHEAL :
AddPanelString ( _ ( " restore all life " ) ) ;
break ;
case IMISC_HEAL :
AddPanelString ( _ ( " restore some life " ) ) ;
break ;
case IMISC_OLDHEAL :
AddPanelString ( _ ( " recover life " ) ) ;
break ;
case IMISC_DEADHEAL :
AddPanelString ( _ ( " deadly heal " ) ) ;
break ;
case IMISC_MANA :
AddPanelString ( _ ( " restore some mana " ) ) ;
break ;
case IMISC_FULLMANA :
AddPanelString ( _ ( " restore all mana " ) ) ;
break ;
case IMISC_ELIXSTR :
AddPanelString ( _ ( " increase strength " ) ) ;
break ;
case IMISC_ELIXMAG :
AddPanelString ( _ ( " increase magic " ) ) ;
break ;
case IMISC_ELIXDEX :
AddPanelString ( _ ( " increase dexterity " ) ) ;
break ;
case IMISC_ELIXVIT :
AddPanelString ( _ ( " increase vitality " ) ) ;
break ;
case IMISC_ELIXWEAK :
case IMISC_ELIXDIS :
AddPanelString ( _ ( " decrease strength " ) ) ;
break ;
case IMISC_ELIXCLUM :
AddPanelString ( _ ( " decrease dexterity " ) ) ;
break ;
case IMISC_ELIXSICK :
AddPanelString ( _ ( " decrease vitality " ) ) ;
break ;
case IMISC_REJUV :
AddPanelString ( _ ( " restore some life and mana " ) ) ;
break ;
case IMISC_FULLREJUV :
AddPanelString ( _ ( " restore all life and mana " ) ) ;
break ;
}
}
void DrawUniqueInfoWindow ( const Surface & out )
{
CelDrawTo ( out , GetPanelPosition ( UiPanels : : Inventory , { 24 - SidePanelSize . width , 327 } ) , CelSprite { * pSTextBoxCels } , 0 ) ;
DrawHalfTransparentRectTo ( out , GetRightPanel ( ) . position . x - SidePanelSize . width + 27 , GetRightPanel ( ) . position . y + 28 , 265 , 297 ) ;
}
void PrintItemMisc ( const Item & item )
{
if ( item . _iMiscId = = IMISC_SCROLL ) {
if ( ControlMode = = ControlTypes : : KeyboardAndMouse ) {
AddPanelString ( _ ( " Right-click to read " ) ) ;
} else {
if ( ! invflag ) {
AddPanelString ( _ ( " Open inventory to use " ) ) ;
} else {
AddPanelString ( _ ( " Activate to read " ) ) ;
}
}
}
if ( item . _iMiscId = = IMISC_SCROLLT ) {
if ( ControlMode = = ControlTypes : : KeyboardAndMouse ) {
AddPanelString ( _ ( " Right-click to read, then " ) ) ;
AddPanelString ( _ ( " left-click to target " ) ) ;
} else {
if ( TargetsMonster ( item . _iSpell ) ) {
AddPanelString ( _ ( " Select from spell book, then " ) ) ;
AddPanelString ( _ ( " cast spell to read " ) ) ;
} else if ( ! invflag ) {
AddPanelString ( _ ( " Open inventory to use " ) ) ;
} else {
AddPanelString ( _ ( " Activate to read " ) ) ;
}
}
}
if ( ( item . _iMiscId > = IMISC_USEFIRST & & item . _iMiscId < = IMISC_USELAST )
| | ( item . _iMiscId > IMISC_OILFIRST & & item . _iMiscId < IMISC_OILLAST )
| | ( item . _iMiscId > IMISC_RUNEFIRST & & item . _iMiscId < IMISC_RUNELAST ) ) {
PrintItemOil ( item . _iMiscId ) ;
if ( ControlMode = = ControlTypes : : KeyboardAndMouse ) {
AddPanelString ( _ ( " Right-click to use " ) ) ;
} else {
if ( ! invflag ) {
AddPanelString ( _ ( " Open inventory to use " ) ) ;
} else {
AddPanelString ( _ ( " Activate to use " ) ) ;
}
}
}
if ( IsAnyOf ( item . _iMiscId , IMISC_BOOK , IMISC_NOTE ) ) {
if ( ControlMode = = ControlTypes : : KeyboardAndMouse ) {
AddPanelString ( _ ( " Right-click to read " ) ) ;
} else {
AddPanelString ( _ ( " Activate to read " ) ) ;
}
}
if ( item . _iMiscId = = IMISC_MAPOFDOOM ) {
if ( ControlMode = = ControlTypes : : KeyboardAndMouse ) {
AddPanelString ( _ ( " Right-click to view " ) ) ;
} else {
AddPanelString ( _ ( " Activate to view " ) ) ;
}
}
if ( item . _iMiscId = = IMISC_EAR ) {
AddPanelString ( fmt : : format ( fmt : : runtime ( pgettext ( " player " , " Level: {:d} " ) ) , item . _ivalue ) ) ;
}
if ( item . _iMiscId = = IMISC_AURIC ) {
AddPanelString ( _ ( " Doubles gold capacity " ) ) ;
}
}
void PrintItemInfo ( const 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 ) {
std : : string text = std : : string ( _ ( " Required: " ) ) ;
if ( str ! = 0 )
text . append ( fmt : : format ( fmt : : runtime ( _ ( " {:d} Str " ) ) , str ) ) ;
if ( mag ! = 0 )
text . append ( fmt : : format ( fmt : : runtime ( _ ( " {:d} Mag " ) ) , mag ) ) ;
if ( dex ! = 0 )
text . append ( fmt : : format ( fmt : : runtime ( _ ( " {:d} Dex " ) ) , dex ) ) ;
AddPanelString ( text ) ;
}
}
bool SmithItemOk ( int i )
{
if ( AllItemsList [ i ] . itype = = ItemType : : Misc )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : Gold )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : Staff & & ( ! gbIsHellfire | | AllItemsList [ i ] . iSpell ! = SPL_NULL ) )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : Ring )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : 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 = = ItemType : : Misc )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : Gold )
return false ;
if ( ! gbIsHellfire & & AllItemsList [ i ] . itype = = ItemType : : Staff )
return false ;
if ( gbIsMultiplayer ) {
if ( AllItemsList [ i ] . iMiscId = = IMISC_OILOF )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : Ring )
return false ;
if ( AllItemsList [ i ] . itype = = ItemType : : Amulet )
return false ;
}
return true ;
}
int RndPremiumItem ( int minlvl , int maxlvl )
{
return RndVendorItem < PremiumItemOk > ( minlvl , maxlvl ) ;
}
void SpawnOnePremium ( Item & premiumItem , int plvl , int playerId )
{
int itemValue = 0 ;
bool keepGoing = false ;
Player & 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 ;
premiumItem = { } ;
premiumItem . _iSeed = AdvanceRndSeed ( ) ;
SetRndSeed ( premiumItem . _iSeed ) ;
int itemType = RndPremiumItem ( plvl / 4 , plvl ) - 1 ;
GetItemAttrs ( premiumItem , itemType , plvl ) ;
GetItemBonus ( premiumItem , plvl / 2 , plvl , true , ! gbIsHellfire ) ;
if ( ! gbIsHellfire ) {
if ( premiumItem . _iIvalue > 140000 ) {
keepGoing = true ; // prevent breaking the do/while loop too early by failing hellfire's condition in while
continue ;
}
break ;
}
switch ( premiumItem . _itype ) {
case ItemType : : LightArmor :
case ItemType : : MediumArmor :
case ItemType : : HeavyArmor : {
const auto * const mostValuablePlayerArmor = player . GetMostValuableItem (
[ ] ( const Item & item ) {
return IsAnyOf ( item . _itype , ItemType : : LightArmor , ItemType : : MediumArmor , ItemType : : HeavyArmor ) ;
} ) ;
itemValue = mostValuablePlayerArmor = = nullptr ? 0 : mostValuablePlayerArmor - > _iIvalue ;
break ;
}
case ItemType : : Shield :
case ItemType : : Axe :
case ItemType : : Bow :
case ItemType : : Mace :
case ItemType : : Sword :
case ItemType : : Helm :
case ItemType : : Staff :
case ItemType : : Ring :
case ItemType : : Amulet : {
const auto * const mostValuablePlayerItem = player . GetMostValuableItem (
[ filterType = premiumItem . _itype ] ( const Item & item ) { return item . _itype = = filterType ; } ) ;
itemValue = mostValuablePlayerItem = = nullptr ? 0 : mostValuablePlayerItem - > _iIvalue ;
break ;
}
default :
itemValue = 0 ;
break ;
}
itemValue = itemValue * 4 / 5 ; // avoids forced int > float > int conversion
count + + ;
} while ( keepGoing
| | ( (
premiumItem . _iIvalue > 200000
| | premiumItem . _iMinStr > strength
| | premiumItem . _iMinMag > magic
| | premiumItem . _iMinDex > dexterity
| | premiumItem . _iIvalue < itemValue )
& & count < 150 ) ) ;
premiumItem . _iCreateInfo = plvl | CF_SMITHPREMIUM ;
premiumItem . _iIdentified = true ;
premiumItem . _iStatFlag = player . CanUseItem ( premiumItem ) ;
}
bool WitchItemOk ( int i )
{
if ( IsNoneOf ( AllItemsList [ i ] . itype , ItemType : : Misc , ItemType : : 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 ! = ItemType : : 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 ) {
Player & myPlayer = * MyPlayer ;
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 ( IsAnyOf ( idx , IDI_MANA , IDI_FULLMANA , 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 ( IsAnyOf ( idx , IDI_HEAL , IDI_FULLHEAL , 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 CreateMagicItem ( Point position , int lvl , ItemType itemType , int imid , int icurs , bool sendmsg , bool delta )
{
if ( ActiveItemCount > = MAXITEMS )
return ;
int ii = AllocateItem ( ) ;
auto & item = Items [ ii ] ;
int idx = RndTypeItems ( itemType , imid , lvl ) ;
while ( true ) {
item = { } ;
SetupAllItems ( item , idx , AdvanceRndSeed ( ) , 2 * lvl , 1 , true , false , delta ) ;
if ( item . _iCurs = = icurs )
break ;
idx = RndTypeItems ( itemType , imid , lvl ) ;
}
GetSuperItemSpace ( position , ii ) ;
if ( sendmsg )
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
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 ( i < 0 | | i > IDI_LAST )
return false ;
if ( gbIsSpawn ) {
if ( i > = 62 & & i < = 71 )
return false ; // Medium and heavy armors
if ( IsAnyOf ( i , 105 , 107 , 108 , 110 , 111 , 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 . testBard & & IsAnyOf ( i , IDI_BARDSWORD , IDI_BARDDAGGER ) ) ;
}
BYTE GetOutlineColor ( const Item & item , bool checkReq )
{
if ( checkReq & & ! item . _iStatFlag )
return ICOL_RED ;
if ( item . _itype = = ItemType : : 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 + + ) {
* fmt : : format_to ( arglist , FMT_COMPILE ( R " (Items \ {}.CEL) " ) , ItemDropNames [ i ] ) = ' \0 ' ;
itemanims [ i ] = LoadCel ( arglist , ItemAnimWidth ) ;
}
memset ( UniqueItemFlags , 0 , sizeof ( UniqueItemFlags ) ) ;
}
void InitItems ( )
{
ActiveItemCount = 0 ;
memset ( dItem , 0 , sizeof ( dItem ) ) ;
for ( auto & item : Items ) {
item . clear ( ) ;
item . position = { 0 , 0 } ;
item . _iAnimFlag = false ;
item . _iSelFlag = 0 ;
item . _iIdentified = false ;
item . _iPostDraw = false ;
}
for ( uint8_t i = 0 ; i < MAXITEMS ; i + + ) {
ActiveItems [ i ] = i ;
}
if ( ! setlevel ) {
AdvanceRndSeed ( ) ; /* unused */
if ( Quests [ Q_ROCK ] . IsAvailable ( ) )
SpawnRock ( ) ;
if ( Quests [ Q_ANVIL ] . IsAvailable ( ) )
SpawnQuestItem ( IDI_ANVIL , SetPiece . position . megaToWorld ( ) + Displacement { 11 , 11 } , 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 ;
items: add BUGFIX, reset item get records when resetting items (#2691)
* items: add BUGFIX, reset item get records when resetting items
The item get record array tracks items being recently looted in an
effort to prevent the same item from being looted more than once.
Prior to this commit, the item get record array (and corresponding
item get record array length) variables were not cleared when
creating a new game. Therefore, the item get record array of a
previous game could remain in between games and prevent an item
from being looted (if it was looted in a previous), even if it was
never looted in the current game. In practice this almost never
shows up, since each item get record is valid for a total of 6
seconds before being cleared. So, you would either have to save
a game, quickly loot an item, when load the game and try to loot
the same item before 6 seconds pass. OR, you could use the demo
replay functionality to run test cases, and speed up execution to
run e.g. 10'000'000 logic ticks per second. Both would exhibit the
bug and prevent the item from being looted.
This commit fixes such issues by explicitly clearing the item get
record array and item get record array length variables whenever
items are initialized.
5 years ago
// 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
ItemSpecialEffect iflgs = ItemSpecialEffect : : None ; // item_special_effect flags
ItemSpecialEffectHf pDamAcFlags = ItemSpecialEffectHf : : None ;
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 = = ItemType : : Shield & & player . InvBody [ INVLOC_HAND_LEFT ] . _iStatFlag ) {
maxd = 3 ;
}
if ( player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : 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 ( HasAnyOf ( player . _pSpellFlags , SpellFlag : : RageActive ) ) {
sadd + = 2 * player . _pLevel ;
dadd + = player . _pLevel + player . _pLevel / 2 ;
vadd + = 2 * player . _pLevel ;
}
if ( HasAnyOf ( player . _pSpellFlags , SpellFlag : : RageCooldown ) ) {
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 ) {
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 ! = ItemType : : Staff ) {
if ( player . InvBody [ INVLOC_HAND_RIGHT ] . _itype ! = ItemType : : 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 = = ItemType : : Sword | | player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Sword )
player . _pDamageMod = player . _pLevel * ( player . _pStrength + player . _pDexterity ) / 150 ;
else if ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype = = ItemType : : Bow | | player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : 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 = = ItemType : : Axe | | player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Axe ) {
player . _pDamageMod = player . _pLevel * player . _pStrength / 75 ;
} else if ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype = = ItemType : : Mace | | player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Mace ) {
player . _pDamageMod = player . _pLevel * player . _pStrength / 75 ;
} else if ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype = = ItemType : : Bow | | player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Bow ) {
player . _pDamageMod = player . _pLevel * player . _pStrength / 300 ;
} else {
player . _pDamageMod = player . _pLevel * player . _pStrength / 100 ;
}
if ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype = = ItemType : : Shield | | player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Shield ) {
if ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype = = ItemType : : Shield )
player . _pIAC - = player . InvBody [ INVLOC_HAND_LEFT ] . _iAC / 2 ;
else if ( player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Shield )
player . _pIAC - = player . InvBody [ INVLOC_HAND_RIGHT ] . _iAC / 2 ;
} else if ( IsNoneOf ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype , ItemType : : Staff , ItemType : : Bow ) & & IsNoneOf ( player . InvBody [ INVLOC_HAND_RIGHT ] . _itype , ItemType : : Staff , ItemType : : 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 ( HasAnyOf ( player . _pSpellFlags , SpellFlag : : RageCooldown ) ) {
mr - = player . _pLevel ;
fr - = player . _pLevel ;
lr - = player . _pLevel ;
}
if ( HasAnyOf ( iflgs , ItemSpecialEffect : : ZeroResistance ) ) {
// reset resistances to zero if the respective special effect is active
mr = 0 ;
fr = 0 ;
lr = 0 ;
}
player . _pMagResist = clamp ( mr , 0 , MaxResistance ) ;
player . _pFireResist = clamp ( fr , 0 , MaxResistance ) ;
player . _pLghtResist = clamp ( lr , 0 , MaxResistance ) ;
if ( player . _pClass = = HeroClass : : Warrior ) {
vadd * = 2 ;
} else if ( player . _pClass = = HeroClass : : Barbarian ) {
vadd + = vadd ;
vadd + = ( vadd / 4 ) ;
} else if ( IsAnyOf ( player . _pClass , HeroClass : : Rogue , HeroClass : : Monk , 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 ( IsAnyOf ( player . _pClass , HeroClass : : Rogue , 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 = = MyPlayer & & ( 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 = HasAnyOf ( iflgs , ItemSpecialEffect : : Infravision ) ;
player . _pBlockFlag = false ;
if ( player . _pClass = = HeroClass : : Monk ) {
if ( player . InvBody [ INVLOC_HAND_LEFT ] . _itype = = ItemType : : Staff & & player . InvBody [ INVLOC_HAND_LEFT ] . _iStatFlag ) {
player . _pBlockFlag = true ;
player . _pIFlags | = ItemSpecialEffect : : FastBlock ;
}
if ( player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Staff & & player . InvBody [ INVLOC_HAND_RIGHT ] . _iStatFlag ) {
player . _pBlockFlag = true ;
player . _pIFlags | = ItemSpecialEffect : : 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 . GetItemLocation ( player . InvBody [ INVLOC_HAND_LEFT ] ) ! = ILOC_TWOHAND & & player . InvBody [ INVLOC_HAND_RIGHT ] . isEmpty ( ) )
player . _pBlockFlag = true ;
if ( player . InvBody [ INVLOC_HAND_RIGHT ] . _iClass = = ICLASS_WEAPON & & player . GetItemLocation ( player . InvBody [ INVLOC_HAND_RIGHT ] ) ! = ILOC_TWOHAND & & player . InvBody [ INVLOC_HAND_LEFT ] . isEmpty ( ) )
player . _pBlockFlag = true ;
}
ItemType weaponItemType = ItemType : : 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 = = ItemType : : Shield & & player . InvBody [ INVLOC_HAND_LEFT ] . _iStatFlag ) {
player . _pBlockFlag = true ;
holdsShield = true ;
}
if ( player . InvBody [ INVLOC_HAND_RIGHT ] . _itype = = ItemType : : Shield & & player . InvBody [ INVLOC_HAND_RIGHT ] . _iStatFlag ) {
player . _pBlockFlag = true ;
holdsShield = true ;
}
PlayerWeaponGraphic animWeaponId = holdsShield ? PlayerWeaponGraphic : : UnarmedShield : PlayerWeaponGraphic : : Unarmed ;
switch ( weaponItemType ) {
case ItemType : : Sword :
animWeaponId = holdsShield ? PlayerWeaponGraphic : : SwordShield : PlayerWeaponGraphic : : Sword ;
break ;
case ItemType : : Axe :
animWeaponId = PlayerWeaponGraphic : : Axe ;
break ;
case ItemType : : Bow :
animWeaponId = PlayerWeaponGraphic : : Bow ;
break ;
case ItemType : : Mace :
animWeaponId = holdsShield ? PlayerWeaponGraphic : : MaceShield : PlayerWeaponGraphic : : Mace ;
break ;
case ItemType : : Staff :
animWeaponId = PlayerWeaponGraphic : : Staff ;
break ;
default :
break ;
}
PlayerArmorGraphic animArmorId = PlayerArmorGraphic : : Light ;
if ( player . InvBody [ INVLOC_CHEST ] . _itype = = ItemType : : HeavyArmor & & 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 = = ItemType : : MediumArmor & & 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 ) ;
player . previewCelSprite = std : : nullopt ;
if ( player . _pmode = = PM_STAND ) {
LoadPlrGFX ( player , player_graphic : : Stand ) ;
player . AnimInfo . changeAnimationData ( player . AnimationData [ static_cast < size_t > ( player_graphic : : Stand ) ] . GetCelSpritesForDirection ( player . _pdir ) , player . _pNFrames , 4 ) ;
} else {
LoadPlrGFX ( player , player_graphic : : Walk ) ;
player . AnimInfo . changeAnimationData ( player . AnimationData [ static_cast < size_t > ( player_graphic : : Walk ) ] . GetCelSpritesForDirection ( player . _pdir ) , player . _pWFrames , 1 ) ;
}
} else {
player . _pgfxnum = gfxNum ;
}
if ( & player = = MyPlayer ) {
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 )
{
// Determine the players current stats, this updates the statFlag on all equipped items that became unusable after
// a change in equipment.
CalcSelfItems ( player ) ;
// Determine the current item bonuses gained from usable equipped items
CalcPlrItemVals ( player , loadgfx ) ;
if ( & player = = MyPlayer ) {
// Now that stat gains from equipped items have been calculated, mark unusable scrolls etc
for ( Item & item : InventoryAndBeltPlayerItemsRange { player } ) {
item . updateRequiredStatsCacheForPlayer ( player ) ;
}
player . CalcScrolls ( ) ;
CalcPlrStaff ( player ) ;
if ( IsStashOpen ) {
// If stash is open, ensure the items are displayed correctly
Stash . RefreshItemStatFlags ( ) ;
}
}
}
void InitializeItem ( Item & item , int itemData )
{
auto & pAllItem = AllItemsList [ itemData ] ;
// zero-initialize struct
item = { } ;
item . _itype = pAllItem . itype ;
item . _iCurs = pAllItem . iCurs ;
CopyUtf8 ( item . _iName , _ ( pAllItem . iName ) , sizeof ( item . _iName ) ) ;
CopyUtf8 ( item . _iIName , _ ( pAllItem . iName ) , sizeof ( item . _iIName ) ) ;
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 GenerateNewSeed ( Item & item )
{
item . _iSeed = AdvanceRndSeed ( ) ;
}
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 ( Player & player )
{
for ( auto & item : player . InvBody ) {
item . clear ( ) ;
}
// 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 . clear ( ) ;
}
player . _pNumInv = 0 ;
for ( auto & item : player . SpdList ) {
item . clear ( ) ;
}
switch ( player . _pClass ) {
case HeroClass : : Warrior :
InitializeItem ( player . InvBody [ INVLOC_HAND_LEFT ] , IDI_WARRIOR ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_LEFT ] ) ;
InitializeItem ( player . InvBody [ INVLOC_HAND_RIGHT ] , IDI_WARRSHLD ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_RIGHT ] ) ;
{
Item club ;
InitializeItem ( club , IDI_WARRCLUB ) ;
GenerateNewSeed ( club ) ;
AutoPlaceItemInInventorySlot ( player , 0 , club , true ) ;
}
InitializeItem ( player . SpdList [ 0 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 0 ] ) ;
InitializeItem ( player . SpdList [ 1 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 1 ] ) ;
break ;
case HeroClass : : Rogue :
InitializeItem ( player . InvBody [ INVLOC_HAND_LEFT ] , IDI_ROGUE ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_LEFT ] ) ;
InitializeItem ( player . SpdList [ 0 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 0 ] ) ;
InitializeItem ( player . SpdList [ 1 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 1 ] ) ;
break ;
case HeroClass : : Sorcerer :
InitializeItem ( player . InvBody [ INVLOC_HAND_LEFT ] , gbIsHellfire ? IDI_SORCERER : 166 ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_LEFT ] ) ;
InitializeItem ( player . SpdList [ 0 ] , gbIsHellfire ? IDI_HEAL : IDI_MANA ) ;
GenerateNewSeed ( player . SpdList [ 0 ] ) ;
InitializeItem ( player . SpdList [ 1 ] , gbIsHellfire ? IDI_HEAL : IDI_MANA ) ;
GenerateNewSeed ( player . SpdList [ 1 ] ) ;
break ;
case HeroClass : : Monk :
InitializeItem ( player . InvBody [ INVLOC_HAND_LEFT ] , IDI_SHORTSTAFF ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_LEFT ] ) ;
InitializeItem ( player . SpdList [ 0 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 0 ] ) ;
InitializeItem ( player . SpdList [ 1 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 1 ] ) ;
break ;
case HeroClass : : Bard :
InitializeItem ( player . InvBody [ INVLOC_HAND_LEFT ] , IDI_BARDSWORD ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_LEFT ] ) ;
InitializeItem ( player . InvBody [ INVLOC_HAND_RIGHT ] , IDI_BARDDAGGER ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_RIGHT ] ) ;
InitializeItem ( player . SpdList [ 0 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 0 ] ) ;
InitializeItem ( player . SpdList [ 1 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 1 ] ) ;
break ;
case HeroClass : : Barbarian :
InitializeItem ( player . InvBody [ INVLOC_HAND_LEFT ] , 139 ) ; // TODO: add more enums to items
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_LEFT ] ) ;
InitializeItem ( player . InvBody [ INVLOC_HAND_RIGHT ] , IDI_WARRSHLD ) ;
GenerateNewSeed ( player . InvBody [ INVLOC_HAND_RIGHT ] ) ;
InitializeItem ( player . SpdList [ 0 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 0 ] ) ;
InitializeItem ( player . SpdList [ 1 ] , IDI_HEAL ) ;
GenerateNewSeed ( player . SpdList [ 1 ] ) ;
break ;
}
Item & goldItem = player . InvList [ player . _pNumInv ] ;
MakeGoldStack ( goldItem , 100 ) ;
player . _pNumInv + + ;
player . InvGrid [ 30 ] = player . _pNumInv ;
player . _pGold = goldItem . _ivalue ;
CalcPlrItemVals ( player , false ) ;
}
bool ItemSpaceOk ( Point position )
{
if ( ! InDungeonBounds ( position ) ) {
return false ;
}
if ( IsTileSolid ( position ) ) {
return false ;
}
if ( dItem [ position . x ] [ position . y ] ! = 0 ) {
return false ;
}
if ( dMonster [ position . x ] [ position . y ] ! = 0 ) {
return false ;
}
if ( dPlayer [ position . x ] [ position . y ] ! = 0 ) {
return false ;
}
if ( IsItemBlockingObjectAtPosition ( position ) ) {
return false ;
}
return true ;
}
int AllocateItem ( )
{
assert ( ActiveItemCount < MAXITEMS ) ;
int inum = ActiveItems [ ActiveItemCount ] ;
ActiveItemCount + + ;
Items [ inum ] = { } ;
return inum ;
}
Point GetSuperItemLoc ( Point position )
{
std : : optional < Point > itemPosition = FindClosestValidPosition ( ItemSpaceOk , position , 1 , 50 ) ;
return itemPosition . value_or ( Point { 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 ;
CopyUtf8 ( item . _iName , _ ( AllItemsList [ itemData ] . iName ) , sizeof ( item . _iName ) ) ;
CopyUtf8 ( item . _iIName , _ ( AllItemsList [ itemData ] . iName ) , sizeof ( item . _iIName ) ) ;
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 ! = ItemType : : 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 ( MyPlayer - > pLvlLoad = = 0 ) ;
item . _iIdentified = false ;
}
int RndItem ( const Monster & monster )
{
if ( ( monster . data ( ) . mTreasure & T_UNIQ ) ! = 0 )
return - ( ( monster . data ( ) . mTreasure & T_MASK ) + 1 ) ;
if ( ( monster . data ( ) . 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 . level > = AllItemsList [ i ] . iMinMLvl
& & ri < 512 ) {
ril [ ri ] = i ;
ri + + ;
}
if ( AllItemsList [ i ] . iRnd ! = IDROP_NEVER & & monster . level > = 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 ! = UniqueItems [ 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 . data ( ) . 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 . data ( ) . mLevel ;
if ( ! gbIsHellfire & & monster . type ( ) . type = = MT_DIABLO )
mLevel - = 15 ;
SetupAllItems ( item , idx , AdvanceRndSeed ( ) , mLevel , uper , onlygood , false , false ) ;
if ( sendmsg )
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
}
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 )
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
}
void CreateTypeItem ( Point position , bool onlygood , ItemType itemType , int imisc , bool sendmsg , bool delta )
{
int idx ;
int curlv = ItemsGetCurrlevel ( ) ;
if ( itemType ! = ItemType : : Gold )
idx = RndTypeItems ( itemType , 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 ) {
InitializeItem ( item , IDI_GOLD ) ;
item . _iSeed = iseed ;
item . _iCreateInfo = icreateinfo ;
item . _ivalue = ivalue ;
SetPlrHandGoldCurs ( item ) ;
gbIsHellfire = tmpIsHellfire ;
return ;
}
if ( icreateinfo = = 0 ) {
InitializeItem ( item , idx ) ;
item . _iSeed = 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 )
{
InitializeItem ( item , IDI_EAR ) ;
char heroName [ 17 ] ;
heroName [ 0 ] = static_cast < char > ( ( ic > > 8 ) & 0x7F ) ;
heroName [ 1 ] = static_cast < char > ( ic & 0x7F ) ;
heroName [ 2 ] = static_cast < char > ( ( iseed > > 24 ) & 0x7F ) ;
heroName [ 3 ] = static_cast < char > ( ( iseed > > 16 ) & 0x7F ) ;
heroName [ 4 ] = static_cast < char > ( ( iseed > > 8 ) & 0x7F ) ;
heroName [ 5 ] = static_cast < char > ( iseed & 0x7F ) ;
heroName [ 6 ] = static_cast < char > ( id & 0x7F ) ;
heroName [ 7 ] = static_cast < char > ( dur & 0x7F ) ;
heroName [ 8 ] = static_cast < char > ( mdur & 0x7F ) ;
heroName [ 9 ] = static_cast < char > ( ch & 0x7F ) ;
heroName [ 10 ] = static_cast < char > ( mch & 0x7F ) ;
heroName [ 11 ] = static_cast < char > ( ( ivalue > > 8 ) & 0x7F ) ;
heroName [ 12 ] = static_cast < char > ( ( ibuff > > 24 ) & 0x7F ) ;
heroName [ 13 ] = static_cast < char > ( ( ibuff > > 16 ) & 0x7F ) ;
heroName [ 14 ] = static_cast < char > ( ( ibuff > > 8 ) & 0x7F ) ;
heroName [ 15 ] = static_cast < char > ( ibuff & 0x7F ) ;
heroName [ 16 ] = ' \0 ' ;
std : : string itemName = fmt : : format ( fmt : : runtime ( _ ( /* TRANSLATORS: {:s} will be a Character Name */ " Ear of {:s} " ) ) , heroName ) ;
CopyUtf8 ( item . _iName , itemName , sizeof ( item . _iName ) ) ;
CopyUtf8 ( item . _iIName , heroName , sizeof ( item . _iIName ) ) ;
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 , ( CornerStone . item . dwBuff & CF_HELLFIRE ) ! = 0 ) ;
const auto * buffer = reinterpret_cast < uint8_t * > ( & id ) ;
for ( size_t i = 0 ; i < sizeof ( ItemPack ) ; i + + ) {
fmt : : format_to ( & sgOptions . Hellfire . szItem [ i * 2 ] , FMT_COMPILE ( " {:02X} " ) , buffer [ i ] ) ;
}
sgOptions . Hellfire . szItem [ sizeof ( sgOptions . Hellfire . szItem ) - 1 ] = ' \0 ' ;
} else {
sgOptions . Hellfire . szItem [ 0 ] = ' \0 ' ;
}
}
void CornerstoneLoad ( Point position )
{
ItemPack pkSItem ;
if ( CornerStone . activated | | position . x = = 0 | | position . y = = 0 ) {
return ;
}
CornerStone . item . clear ( ) ;
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 ( 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 - 1 ;
item . _iAnimFlag = false ;
}
}
void SpawnRewardItem ( int itemid , Point position , bool sendmsg )
{
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 ;
GenerateNewSeed ( item ) ;
if ( sendmsg ) {
NetSendCmdPItem ( true , CMD_SPAWNITEM , item . position , item ) ;
}
}
void SpawnMapOfDoom ( Point position , bool sendmsg )
{
SpawnRewardItem ( IDI_MAPOFDOOM , position , sendmsg ) ;
}
void SpawnRuneBomb ( Point position , bool sendmsg )
{
SpawnRewardItem ( IDI_RUNEBOMB , position , sendmsg ) ;
}
void SpawnTheodore ( Point position , bool sendmsg )
{
SpawnRewardItem ( IDI_THEODORE , position , sendmsg ) ;
}
void RespawnItem ( Item & item , bool flipFlag )
{
int it = ItemCAnimTbl [ item . _iCurs ] ;
item . setNewAnimation ( flipFlag ) ;
item . _iRequest = false ;
if ( IsAnyOf ( item . _iCurs , ICURS_MAGIC_ROCK , ICURS_TAVERN_SIGN , ICURS_ANVIL_OF_FURY ) )
item . _iSelFlag = 1 ;
else if ( IsAnyOf ( item . _iCurs , ICURS_MAP_OF_THE_STARS , ICURS_RUNE_BOMB , ICURS_THEODORE , ICURS_AURIC_AMULET ) )
item . _iSelFlag = 2 ;
if ( item . _iCurs = = ICURS_MAGIC_ROCK ) {
PlaySfxLoc ( ItemDropSnds [ it ] , item . position ) ;
}
}
void DeleteItem ( int i )
{
if ( ActiveItemCount > 0 )
ActiveItemCount - - ;
assert ( i > = 0 & & i < MAXITEMS & & ActiveItemCount < MAXITEMS ) ;
if ( pcursitem = = ActiveItems [ i ] ) // Unselect item if player has it highlighted
pcursitem = - 1 ;
if ( i < ActiveItemCount ) {
// If the deleted item was not already at the end of the active list, swap the indexes around to make the next item allocation simpler.
std : : swap ( ActiveItems [ i ] , ActiveItems [ ActiveItemCount ] ) ;
}
}
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 = = 10 )
item . AnimInfo . currentFrame = 0 ;
if ( item . _iSelFlag = = 2 & & item . AnimInfo . currentFrame = = 20 )
item . AnimInfo . currentFrame = 10 ;
} else {
if ( item . AnimInfo . currentFrame = = ( item . AnimInfo . numberOfFrames - 1 ) / 2 )
PlaySfxLoc ( ItemDropSnds [ ItemCAnimTbl [ item . _iCurs ] ] , item . position ) ;
if ( item . AnimInfo . currentFrame > = item . AnimInfo . numberOfFrames - 1 ) {
item . AnimInfo . currentFrame = item . AnimInfo . numberOfFrames - 1 ;
item . _iAnimFlag = false ;
item . _iSelFlag = 1 ;
}
}
}
ItemDoppel ( ) ;
}
void FreeItemGFX ( )
{
for ( auto & itemanim : itemanims ) {
itemanim = std : : nullopt ;
}
}
void GetItemFrm ( Item & item )
{
item . AnimInfo . celSprite . emplace ( * itemanims [ ItemCAnimTbl [ item . _iCurs ] ] ) ;
}
void GetItemStr ( Item & item )
{
if ( item . _itype ! = ItemType : : Gold ) {
if ( item . _iIdentified )
InfoString = string_view ( item . _iIName ) ;
else
InfoString = string_view ( item . _iName ) ;
InfoColor = item . getTextColor ( ) ;
} else {
int nGold = item . _ivalue ;
InfoString = fmt : : format ( fmt : : runtime ( ngettext ( " {:s} gold piece " , " {:s} gold pieces " , nGold ) ) , FormatInteger ( nGold ) ) ;
}
}
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 ) ;
}
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 ) ;
}
void DoRecharge ( Player & player , int cii )
{
Item * pi ;
if ( cii > = NUM_INVLOC ) {
pi = & player . InvList [ cii - NUM_INVLOC ] ;
} else {
pi = & player . InvBody [ cii ] ;
}
RechargeItem ( * pi , player ) ;
CalcPlrInv ( player , true ) ;
}
bool 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 false ;
CalcPlrInv ( player , true ) ;
return true ;
}
[[nodiscard]] StringOrView PrintItemPower ( char plidx , const Item & item )
{
switch ( plidx ) {
case IPL_TOHIT :
case IPL_TOHIT_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " chance to hit: {:+d}% " ) ) , item . _iPLToHit ) ;
case IPL_DAMP :
case IPL_DAMP_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( /*xgettext:no-c-format*/ " {:+d}% damage " ) ) , item . _iPLDam ) ;
case IPL_TOHIT_DAMP :
case IPL_TOHIT_DAMP_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " to hit: {:+d}%, {:+d}% damage " ) ) , item . _iPLToHit , item . _iPLDam ) ;
case IPL_ACP :
case IPL_ACP_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( /*xgettext:no-c-format*/ " {:+d}% armor " ) ) , item . _iPLAC ) ;
case IPL_SETAC :
case IPL_AC_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " armor class: {:d} " ) ) , item . _iAC ) ;
case IPL_FIRERES :
case IPL_FIRERES_CURSE :
if ( item . _iPLFR < MaxResistance )
return fmt : : format ( fmt : : runtime ( _ ( " Resist Fire: {:+d}% " ) ) , item . _iPLFR ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " Resist Fire: {:+d}% MAX " ) ) , MaxResistance ) ;
case IPL_LIGHTRES :
case IPL_LIGHTRES_CURSE :
if ( item . _iPLLR < MaxResistance )
return fmt : : format ( fmt : : runtime ( _ ( " Resist Lightning: {:+d}% " ) ) , item . _iPLLR ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " Resist Lightning: {:+d}% MAX " ) ) , MaxResistance ) ;
case IPL_MAGICRES :
case IPL_MAGICRES_CURSE :
if ( item . _iPLMR < MaxResistance )
return fmt : : format ( fmt : : runtime ( _ ( " Resist Magic: {:+d}% " ) ) , item . _iPLMR ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " Resist Magic: {:+d}% MAX " ) ) , MaxResistance ) ;
case IPL_ALLRES :
case IPL_ALLRES_CURSE :
if ( item . _iPLFR < MaxResistance )
return fmt : : format ( fmt : : runtime ( _ ( " Resist All: {:+d}% " ) ) , item . _iPLFR ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " Resist All: {:+d}% MAX " ) ) , MaxResistance ) ;
case IPL_SPLLVLADD :
if ( item . _iSplLvlAdd > 0 )
return fmt : : format ( fmt : : runtime ( ngettext ( " spells are increased {:d} level " , " spells are increased {:d} levels " , item . _iSplLvlAdd ) ) , item . _iSplLvlAdd ) ;
else if ( item . _iSplLvlAdd < 0 )
return fmt : : format ( fmt : : runtime ( ngettext ( " spells are decreased {:d} level " , " spells are decreased {:d} levels " , - item . _iSplLvlAdd ) ) , - item . _iSplLvlAdd ) ;
else
return _ ( " spell levels unchanged (?) " ) ;
case IPL_CHARGES :
return _ ( " Extra charges " ) ;
case IPL_SPELL :
return fmt : : format ( fmt : : runtime ( ngettext ( " {:d} {:s} charge " , " {:d} {:s} charges " , item . _iMaxCharges ) ) , item . _iMaxCharges , pgettext ( " spell " , spelldata [ item . _iSpell ] . sNameText ) ) ;
case IPL_FIREDAM :
if ( item . _iFMinDam = = item . _iFMaxDam )
return fmt : : format ( fmt : : runtime ( _ ( " Fire hit damage: {:d} " ) ) , item . _iFMinDam ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " Fire hit damage: {:d}-{:d} " ) ) , item . _iFMinDam , item . _iFMaxDam ) ;
case IPL_LIGHTDAM :
if ( item . _iLMinDam = = item . _iLMaxDam )
return fmt : : format ( fmt : : runtime ( _ ( " Lightning hit damage: {:d} " ) ) , item . _iLMinDam ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " Lightning hit damage: {:d}-{:d} " ) ) , item . _iLMinDam , item . _iLMaxDam ) ;
case IPL_STR :
case IPL_STR_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " {:+d} to strength " ) ) , item . _iPLStr ) ;
case IPL_MAG :
case IPL_MAG_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " {:+d} to magic " ) ) , item . _iPLMag ) ;
case IPL_DEX :
case IPL_DEX_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " {:+d} to dexterity " ) ) , item . _iPLDex ) ;
case IPL_VIT :
case IPL_VIT_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " {:+d} to vitality " ) ) , item . _iPLVit ) ;
case IPL_ATTRIBS :
case IPL_ATTRIBS_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " {:+d} to all attributes " ) ) , item . _iPLStr ) ;
case IPL_GETHIT_CURSE :
case IPL_GETHIT :
return fmt : : format ( fmt : : runtime ( _ ( " {:+d} damage from enemies " ) ) , item . _iPLGetHit ) ;
case IPL_LIFE :
case IPL_LIFE_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " Hit Points: {:+d} " ) ) , item . _iPLHP > > 6 ) ;
case IPL_MANA :
case IPL_MANA_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( " Mana: {:+d} " ) ) , item . _iPLMana > > 6 ) ;
case IPL_DUR :
return _ ( " high durability " ) ;
case IPL_DUR_CURSE :
return _ ( " decreased durability " ) ;
case IPL_INDESTRUCTIBLE :
return _ ( " indestructible " ) ;
case IPL_LIGHT :
return fmt : : format ( fmt : : runtime ( _ ( /*xgettext:no-c-format*/ " +{:d}% light radius " ) ) , 10 * item . _iPLLight ) ;
case IPL_LIGHT_CURSE :
return fmt : : format ( fmt : : runtime ( _ ( /*xgettext:no-c-format*/ " -{:d}% light radius " ) ) , - 10 * item . _iPLLight ) ;
case IPL_MULT_ARROWS :
return _ ( " multiple arrows per shot " ) ;
case IPL_FIRE_ARROWS :
if ( item . _iFMinDam = = item . _iFMaxDam )
return fmt : : format ( fmt : : runtime ( _ ( " fire arrows damage: {:d} " ) ) , item . _iFMinDam ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " fire arrows damage: {:d}-{:d} " ) ) , item . _iFMinDam , item . _iFMaxDam ) ;
case IPL_LIGHT_ARROWS :
if ( item . _iLMinDam = = item . _iLMaxDam )
return fmt : : format ( fmt : : runtime ( _ ( " lightning arrows damage {:d} " ) ) , item . _iLMinDam ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " lightning arrows damage {:d}-{:d} " ) ) , item . _iLMinDam , item . _iLMaxDam ) ;
case IPL_FIREBALL :
if ( item . _iFMinDam = = item . _iFMaxDam )
return fmt : : format ( fmt : : runtime ( _ ( " fireball damage: {:d} " ) ) , item . _iFMinDam ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " fireball damage: {:d}-{:d} " ) ) , item . _iFMinDam , item . _iFMaxDam ) ;
case IPL_THORNS :
return _ ( " attacker takes 1-3 damage " ) ;
case IPL_NOMANA :
return _ ( " user loses all mana " ) ;
case IPL_NOHEALPLR :
return _ ( " you can't heal " ) ;
case IPL_ABSHALFTRAP :
return _ ( " absorbs half of trap damage " ) ;
case IPL_KNOCKBACK :
return _ ( " knocks target back " ) ;
case IPL_3XDAMVDEM :
return _ ( /*xgettext:no-c-format*/ " +200% damage vs. demons " ) ;
case IPL_ALLRESZERO :
return _ ( " All Resistance equals 0 " ) ;
case IPL_NOHEALMON :
return _ ( " hit monster doesn't heal " ) ;
case IPL_STEALMANA :
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : StealMana3 ) )
return _ ( /*xgettext:no-c-format*/ " hit steals 3% mana " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : StealMana5 ) )
return _ ( /*xgettext:no-c-format*/ " hit steals 5% mana " ) ;
return { } ;
case IPL_STEALLIFE :
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : StealLife3 ) )
return _ ( /*xgettext:no-c-format*/ " hit steals 3% life " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : StealLife5 ) )
return _ ( /*xgettext:no-c-format*/ " hit steals 5% life " ) ;
return { } ;
case IPL_TARGAC :
return _ ( " penetrates target's armor " ) ;
case IPL_FASTATTACK :
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : QuickAttack ) )
return _ ( " quick attack " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : FastAttack ) )
return _ ( " fast attack " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : FasterAttack ) )
return _ ( " faster attack " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : FastestAttack ) )
return _ ( " fastest attack " ) ;
return _ ( " Another ability (NW) " ) ;
case IPL_FASTRECOVER :
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : FastHitRecovery ) )
return _ ( " fast hit recovery " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : FasterHitRecovery ) )
return _ ( " faster hit recovery " ) ;
if ( HasAnyOf ( item . _iFlags , ItemSpecialEffect : : FastestHitRecovery ) )
return _ ( " fastest hit recovery " ) ;
return _ ( " Another ability (NW) " ) ;
case IPL_FASTBLOCK :
return _ ( " fast block " ) ;
case IPL_DAMMOD :
return fmt : : format ( fmt : : runtime ( ngettext ( " adds {:d} point to damage " , " adds {:d} points to damage " , item . _iPLDamMod ) ) , item . _iPLDamMod ) ;
case IPL_RNDARROWVEL :
return _ ( " fires random speed arrows " ) ;
case IPL_SETDAM :
return _ ( " unusual item damage " ) ;
case IPL_SETDUR :
return _ ( " altered durability " ) ;
case IPL_FASTSWING :
return _ ( " Faster attack swing " ) ;
case IPL_ONEHAND :
return _ ( " one handed sword " ) ;
case IPL_DRAINLIFE :
return _ ( " constantly lose hit points " ) ;
case IPL_RNDSTEALLIFE :
return _ ( " life stealing " ) ;
case IPL_NOMINSTR :
return _ ( " no strength requirement " ) ;
case IPL_INFRAVISION :
return _ ( " see with infravision " ) ;
case IPL_INVCURS :
return { string_view ( " " ) } ;
case IPL_ADDACLIFE :
if ( item . _iFMinDam = = item . _iFMaxDam )
return fmt : : format ( fmt : : runtime ( _ ( " lightning damage: {:d} " ) ) , item . _iFMinDam ) ;
else
return fmt : : format ( fmt : : runtime ( _ ( " lightning damage: {:d}-{:d} " ) ) , item . _iFMinDam , item . _iFMaxDam ) ;
case IPL_ADDMANAAC :
return _ ( " charged bolts on hits " ) ;
case IPL_FIRERESCLVL :
if ( item . _iPLFR > 0 )
return fmt : : format ( fmt : : runtime ( _ ( " Resist Fire: {:+d}% " ) ) , item . _iPLFR ) ;
else
return { string_view ( " " ) } ;
case IPL_DEVASTATION :
return _ ( " occasional triple damage " ) ;
case IPL_DECAY :
return fmt : : format ( fmt : : runtime ( _ ( /*xgettext:no-c-format*/ " decaying {:+d}% damage " ) ) , item . _iPLDam ) ;
case IPL_PERIL :
return _ ( " 2x dmg to monst, 1x to you " ) ;
case IPL_JESTERS :
return std : : string ( _ ( /*xgettext:no-c-format*/ " Random 0 - 500% damage " ) ) ;
case IPL_CRYSTALLINE :
return fmt : : format ( fmt : : runtime ( _ ( /*xgettext:no-c-format*/ " low dur, {:+d}% damage " ) ) , item . _iPLDam ) ;
case IPL_DOPPELGANGER :
return fmt : : format ( fmt : : runtime ( _ ( " to hit: {:+d}%, {:+d}% damage " ) ) , item . _iPLToHit , item . _iPLDam ) ;
case IPL_ACDEMON :
return _ ( " extra AC vs demons " ) ;
case IPL_ACUNDEAD :
return _ ( " extra AC vs undead " ) ;
case IPL_MANATOLIFE :
return _ ( " 50% Mana moved to Health " ) ;
case IPL_LIFETOMANA :
return _ ( " 40% Health moved to Mana " ) ;
default :
return _ ( " Another ability (NW) " ) ;
}
}
void DrawUniqueInfo ( const Surface & out )
{
const Point position = GetRightPanel ( ) . position - Displacement { SidePanelSize . width , 0 } ;
if ( IsLeftPanelOpen ( ) & & GetLeftPanel ( ) . contains ( position ) ) {
return ;
}
DrawUniqueInfoWindow ( out ) ;
Rectangle rect { position + Displacement { 32 , 56 } , { 257 , 0 } } ;
const UniqueItem & uitem = UniqueItems [ curruitem . _iUid ] ;
DrawString ( out , _ ( uitem . UIName ) , rect , UiFlags : : AlignCenter ) ;
const Rectangle dividerLineRect { position + Displacement { 26 , 25 } , { 267 , 3 } } ;
out . BlitFrom ( out , MakeSdlRect ( dividerLineRect ) , dividerLineRect . position + Displacement { 0 , 5 * 12 + 13 } ) ;
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 ;
DrawString ( out , PrintItemPower ( power . type , curruitem ) , rect , UiFlags : : ColorWhite | UiFlags : : AlignCenter ) ;
}
}
void PrintItemDetails ( const Item & item )
{
if ( item . _iClass = = ICLASS_WEAPON ) {
if ( item . _iMinDam = = item . _iMaxDam ) {
if ( item . _iMaxDur = = DUR_INDESTRUCTIBLE )
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " damage: {:d} Indestructible " ) ) , item . _iMinDam ) ) ;
else
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( /* TRANSLATORS: Dur: is durability */ " damage: {:d} Dur: {:d}/{:d} " ) ) , item . _iMinDam , item . _iDurability , item . _iMaxDur ) ) ;
} else {
if ( item . _iMaxDur = = DUR_INDESTRUCTIBLE )
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " damage: {:d}-{:d} Indestructible " ) ) , item . _iMinDam , item . _iMaxDam ) ) ;
else
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( /* TRANSLATORS: Dur: is durability */ " damage: {:d}-{:d} Dur: {:d}/{:d} " ) ) , item . _iMinDam , item . _iMaxDam , item . _iDurability , item . _iMaxDur ) ) ;
}
}
if ( item . _iClass = = ICLASS_ARMOR ) {
if ( item . _iMaxDur = = DUR_INDESTRUCTIBLE )
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " armor: {:d} Indestructible " ) ) , item . _iAC ) ) ;
else
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( /* TRANSLATORS: Dur: is durability */ " armor: {:d} Dur: {:d}/{:d} " ) ) , item . _iAC , item . _iDurability , item . _iMaxDur ) ) ;
}
if ( item . _iMiscId = = IMISC_STAFF & & item . _iMaxCharges ! = 0 ) {
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " Charges: {:d}/{:d} " ) ) , item . _iCharges , item . _iMaxCharges ) ) ;
}
if ( item . _iPrePower ! = - 1 ) {
AddPanelString ( PrintItemPower ( item . _iPrePower , item ) ) ;
}
if ( item . _iSufPower ! = - 1 ) {
AddPanelString ( PrintItemPower ( item . _iSufPower , item ) ) ;
}
if ( item . _iMagical = = ITEM_QUALITY_UNIQUE ) {
AddPanelString ( _ ( " unique item " ) ) ;
ShowUniqueItemInfoBox = true ;
curruitem = item ;
}
PrintItemInfo ( item ) ;
}
void PrintItemDur ( const Item & item )
{
if ( item . _iClass = = ICLASS_WEAPON ) {
if ( item . _iMinDam = = item . _iMaxDam ) {
if ( item . _iMaxDur = = DUR_INDESTRUCTIBLE )
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " damage: {:d} Indestructible " ) ) , item . _iMinDam ) ) ;
else
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " damage: {:d} Dur: {:d}/{:d} " ) ) , item . _iMinDam , item . _iDurability , item . _iMaxDur ) ) ;
} else {
if ( item . _iMaxDur = = DUR_INDESTRUCTIBLE )
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " damage: {:d}-{:d} Indestructible " ) ) , item . _iMinDam , item . _iMaxDam ) ) ;
else
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " damage: {:d}-{:d} Dur: {:d}/{:d} " ) ) , item . _iMinDam , item . _iMaxDam , item . _iDurability , item . _iMaxDur ) ) ;
}
if ( item . _iMiscId = = IMISC_STAFF & & item . _iMaxCharges > 0 ) {
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " Charges: {:d}/{:d} " ) ) , item . _iCharges , item . _iMaxCharges ) ) ;
}
if ( item . _iMagical ! = ITEM_QUALITY_NORMAL )
AddPanelString ( _ ( " Not Identified " ) ) ;
}
if ( item . _iClass = = ICLASS_ARMOR ) {
if ( item . _iMaxDur = = DUR_INDESTRUCTIBLE )
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " armor: {:d} Indestructible " ) ) , item . _iAC ) ) ;
else
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " armor: {:d} Dur: {:d}/{:d} " ) ) , item . _iAC , item . _iDurability , item . _iMaxDur ) ) ;
if ( item . _iMagical ! = ITEM_QUALITY_NORMAL )
AddPanelString ( _ ( " Not Identified " ) ) ;
if ( item . _iMiscId = = IMISC_STAFF & & item . _iMaxCharges > 0 ) {
AddPanelString ( fmt : : format ( fmt : : runtime ( _ ( " Charges: {:d}/{:d} " ) ) , item . _iCharges , item . _iMaxCharges ) ) ;
}
}
if ( IsAnyOf ( item . _itype , ItemType : : Ring , ItemType : : Amulet ) )
AddPanelString ( _ ( " Not Identified " ) ) ;
PrintItemInfo ( item ) ;
}
void UseItem ( int pnum , item_misc_id mid , spell_id spl )
{
Player & player = Players [ pnum ] ;
switch ( mid ) {
case IMISC_HEAL :
case IMISC_FOOD :
player . RestorePartialLife ( ) ;
if ( & player = = MyPlayer ) {
drawhpflag = true ;
}
break ;
case IMISC_FULLHEAL :
player . RestoreFullLife ( ) ;
if ( & player = = MyPlayer ) {
drawhpflag = true ;
}
break ;
case IMISC_MANA :
player . RestorePartialMana ( ) ;
if ( & player = = MyPlayer ) {
drawmanaflag = true ;
}
break ;
case IMISC_FULLMANA :
player . RestoreFullMana ( ) ;
if ( & player = = MyPlayer ) {
drawmanaflag = true ;
}
break ;
case IMISC_ELIXSTR :
ModifyPlrStr ( player , 1 ) ;
break ;
case IMISC_ELIXMAG :
ModifyPlrMag ( player , 1 ) ;
if ( gbIsHellfire ) {
player . RestoreFullMana ( ) ;
if ( & player = = MyPlayer ) {
drawmanaflag = true ;
}
}
break ;
case IMISC_ELIXDEX :
ModifyPlrDex ( player , 1 ) ;
break ;
case IMISC_ELIXVIT :
ModifyPlrVit ( player , 1 ) ;
if ( gbIsHellfire ) {
player . RestoreFullLife ( ) ;
if ( & player = = MyPlayer ) {
drawhpflag = true ;
}
}
break ;
case IMISC_REJUV : {
player . RestorePartialLife ( ) ;
player . RestorePartialMana ( ) ;
if ( & player = = MyPlayer ) {
drawhpflag = true ;
drawmanaflag = true ;
}
} break ;
case IMISC_FULLREJUV :
player . RestoreFullLife ( ) ;
player . RestoreFullMana ( ) ;
if ( & player = = MyPlayer ) {
drawhpflag = true ;
drawmanaflag = true ;
}
break ;
case IMISC_SCROLL :
if ( ControlMode = = ControlTypes : : KeyboardAndMouse & & spelldata [ spl ] . sTargeted ) {
player . _pTSpell = spl ;
if ( pnum = = 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 ( pnum = = MyPlayerId & & spl = = SPL_NOVA )
NetSendCmdLoc ( pnum , true , CMD_NOVA , cursPosition ) ;
}
break ;
case IMISC_SCROLLT :
if ( ControlMode = = ControlTypes : : KeyboardAndMouse & & spelldata [ spl ] . sTargeted ) {
player . _pTSpell = spl ;
if ( pnum = = 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 ] < MaxSpellLevel )
player . _pSplLvl [ spl ] + + ;
if ( HasNoneOf ( player . _pIFlags , ItemSpecialEffect : : NoMana ) ) {
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 ( & player = = MyPlayer ) {
for ( Item & item : InventoryPlayerItemsRange { player } ) {
item . updateRequiredStatsCacheForPlayer ( player ) ;
}
if ( IsStashOpen ) {
Stash . RefreshItemStatFlags ( ) ;
}
}
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 ( pnum ! = MyPlayerId ) {
return ;
}
if ( sbookflag ) {
sbookflag = false ;
}
if ( ! invflag ) {
invflag = true ;
}
NewCursor ( CURSOR_OIL ) ;
break ;
case IMISC_SPECELIX :
ModifyPlrStr ( player , 3 ) ;
ModifyPlrMag ( player , 3 ) ;
ModifyPlrDex ( player , 3 ) ;
ModifyPlrVit ( player , 3 ) ;
break ;
case IMISC_RUNEF :
player . _pTSpell = SPL_RUNEFIRE ;
if ( pnum = = MyPlayerId )
NewCursor ( CURSOR_TELEPORT ) ;
break ;
case IMISC_RUNEL :
player . _pTSpell = SPL_RUNELIGHT ;
if ( pnum = = MyPlayerId )
NewCursor ( CURSOR_TELEPORT ) ;
break ;
case IMISC_GR_RUNEL :
player . _pTSpell = SPL_RUNENOVA ;
if ( pnum = = MyPlayerId )
NewCursor ( CURSOR_TELEPORT ) ;
break ;
case IMISC_GR_RUNEF :
player . _pTSpell = SPL_RUNEIMMOLAT ;
if ( pnum = = MyPlayerId )
NewCursor ( CURSOR_TELEPORT ) ;
break ;
case IMISC_RUNES :
player . _pTSpell = SPL_RUNESTONE ;
if ( pnum = = MyPlayerId )
NewCursor ( CURSOR_TELEPORT ) ;
break ;
default :
break ;
}
}
bool UseItemOpensHive ( const Item & item , Point position )
{
if ( item . IDidx ! = IDI_RUNEBOMB )
return false ;
for ( auto dir : PathDirs ) {
Point adjacentPosition = position + dir ;
if ( OpensHive ( adjacentPosition ) )
return true ;
}
return false ;
}
bool UseItemOpensCrypt ( const Item & item , Point position )
{
if ( item . IDidx ! = IDI_MAPOFDOOM )
return false ;
for ( auto dir : PathDirs ) {
Point adjacentPosition = position + dir ;
if ( OpensGrave ( adjacentPosition ) )
return true ;
}
return false ;
}
void SpawnSmith ( int lvl )
{
constexpr int PinnedItemCount = 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 + + ) {
Item & newItem = smithitem [ i ] ;
do {
newItem = { } ;
newItem . _iSeed = AdvanceRndSeed ( ) ;
SetRndSeed ( newItem . _iSeed ) ;
int itemData = RndSmithItem ( lvl ) - 1 ;
GetItemAttrs ( newItem , itemData , lvl ) ;
} while ( newItem . _iIvalue > maxValue ) ;
newItem . _iCreateInfo = lvl | CF_SMITH ;
newItem . _iIdentified = true ;
}
for ( int i = iCnt ; i < SMITH_ITEMS ; i + + )
smithitem [ i ] . clear ( ) ;
SortVendor ( smithitem + PinnedItemCount ) ;
}
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 ( premiumitems [ 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 ( premiumitems [ 10 ] , premiumlevel + premiumLvlAddHellfire [ 10 ] , pnum ) ;
premiumitems [ 11 ] = premiumitems [ 13 ] ;
SpawnOnePremium ( premiumitems [ 12 ] , premiumlevel + premiumLvlAddHellfire [ 12 ] , pnum ) ;
premiumitems [ 13 ] = premiumitems [ 14 ] ;
SpawnOnePremium ( premiumitems [ 14 ] , premiumlevel + premiumLvlAddHellfire [ 14 ] , pnum ) ;
} else {
// Discard first 2 items and shift next 3
std : : move ( & premiumitems [ 2 ] , & premiumitems [ 4 ] + 1 , & premiumitems [ 0 ] ) ;
SpawnOnePremium ( premiumitems [ 3 ] , premiumlevel + premiumlvladd [ 3 ] , pnum ) ;
premiumitems [ 4 ] = premiumitems [ 5 ] ;
SpawnOnePremium ( premiumitems [ 5 ] , premiumlevel + premiumlvladd [ 5 ] , pnum ) ;
}
}
}
void SpawnWitch ( int lvl )
{
constexpr int PinnedItemCount = 3 ;
constexpr std : : array < int , PinnedItemCount > PinnedItemTypes = { IDI_MANA , IDI_FULLMANA , IDI_PORTAL } ;
constexpr int MaxPinnedBookCount = 4 ;
constexpr std : : array < int , MaxPinnedBookCount > PinnedBookTypes = { 114 , 115 , 116 , 117 } ;
int bookCount = 0 ;
const int pinnedBookCount = gbIsHellfire ? GenerateRnd ( MaxPinnedBookCount ) : 0 ;
const int reservedItems = gbIsHellfire ? 10 : 17 ;
const int itemCount = GenerateRnd ( WITCH_ITEMS - reservedItems ) + 10 ;
const int maxValue = gbIsHellfire ? 200000 : 140000 ;
for ( int i = 0 ; i < WITCH_ITEMS ; i + + ) {
Item & item = witchitem [ i ] ;
item = { } ;
if ( i < PinnedItemCount ) {
item . _iSeed = AdvanceRndSeed ( ) ;
GetItemAttrs ( item , PinnedItemTypes [ i ] , 1 ) ;
item . _iCreateInfo = lvl ;
item . _iStatFlag = true ;
continue ;
}
if ( gbIsHellfire ) {
if ( i < PinnedItemCount + MaxPinnedBookCount & & bookCount < pinnedBookCount ) {
int bookType = PinnedBookTypes [ i - PinnedItemCount ] ;
if ( lvl > = AllItemsList [ bookType ] . iMinMLvl ) {
item . _iSeed = AdvanceRndSeed ( ) ;
SetRndSeed ( item . _iSeed ) ;
GetItemAttrs ( item , bookType , lvl ) ;
item . _iCreateInfo = lvl | CF_WITCH ;
item . _iIdentified = true ;
bookCount + + ;
continue ;
}
}
}
if ( i > = itemCount ) {
item . clear ( ) ;
continue ;
}
do {
item = { } ;
item . _iSeed = AdvanceRndSeed ( ) ;
SetRndSeed ( item . _iSeed ) ;
int itemData = RndWitchItem ( lvl ) - 1 ;
GetItemAttrs ( item , itemData , lvl ) ;
int maxlvl = - 1 ;
if ( GenerateRnd ( 100 ) < = 5 )
maxlvl = 2 * lvl ;
if ( maxlvl = = - 1 & & item . _iMiscId = = IMISC_STAFF )
maxlvl = 2 * lvl ;
if ( maxlvl ! = - 1 )
GetItemBonus ( item , maxlvl / 2 , maxlvl , true , true ) ;
} while ( item . _iIvalue > maxValue ) ;
item . _iCreateInfo = lvl | CF_WITCH ;
item . _iIdentified = true ;
}
SortVendor ( witchitem + PinnedItemCount ) ;
}
void SpawnBoy ( int lvl )
{
int ivalue = 0 ;
bool keepgoing = false ;
int count = 0 ;
Player & myPlayer = * MyPlayer ;
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 ;
boyitem = { } ;
boyitem . _iSeed = AdvanceRndSeed ( ) ;
SetRndSeed ( boyitem . _iSeed ) ;
int itype = RndBoyItem ( lvl ) - 1 ;
GetItemAttrs ( boyitem , itype , lvl ) ;
GetItemBonus ( boyitem , lvl , 2 * lvl , true , true ) ;
if ( ! gbIsHellfire ) {
if ( boyitem . _iIvalue > 90000 ) {
keepgoing = true ; // prevent breaking the do/while loop too early by failing hellfire's condition in while
continue ;
}
break ;
}
ivalue = 0 ;
ItemType itemType = boyitem . _itype ;
switch ( itemType ) {
case ItemType : : LightArmor :
case ItemType : : MediumArmor :
case ItemType : : HeavyArmor : {
const auto * const mostValuablePlayerArmor = myPlayer . GetMostValuableItem (
[ ] ( const Item & item ) {
return IsAnyOf ( item . _itype , ItemType : : LightArmor , ItemType : : MediumArmor , ItemType : : HeavyArmor ) ;
} ) ;
ivalue = mostValuablePlayerArmor = = nullptr ? 0 : mostValuablePlayerArmor - > _iIvalue ;
break ;
}
case ItemType : : Shield :
case ItemType : : Axe :
case ItemType : : Bow :
case ItemType : : Mace :
case ItemType : : Sword :
case ItemType : : Helm :
case ItemType : : Staff :
case ItemType : : Ring :
case ItemType : : Amulet : {
const auto * const mostValuablePlayerItem = myPlayer . GetMostValuableItem (
[ itemType ] ( const Item & item ) { return item . _itype = = itemType ; } ) ;
ivalue = mostValuablePlayerItem = = nullptr ? 0 : mostValuablePlayerItem - > _iIvalue ;
break ;
}
default :
app_fatal ( " Invalid item spawn " ) ;
}
ivalue = ivalue * 4 / 5 ; // avoids forced int > float > int conversion
count + + ;
if ( count < 200 ) {
switch ( pc ) {
case HeroClass : : Warrior :
if ( IsAnyOf ( itemType , ItemType : : Bow , ItemType : : Staff ) )
ivalue = INT_MAX ;
break ;
case HeroClass : : Rogue :
if ( IsAnyOf ( itemType , ItemType : : Sword , ItemType : : Staff , ItemType : : Axe , ItemType : : Mace , ItemType : : Shield ) )
ivalue = INT_MAX ;
break ;
case HeroClass : : Sorcerer :
if ( IsAnyOf ( itemType , ItemType : : Staff , ItemType : : Axe , ItemType : : Bow , ItemType : : Mace ) )
ivalue = INT_MAX ;
break ;
case HeroClass : : Monk :
if ( IsAnyOf ( itemType , ItemType : : Bow , ItemType : : MediumArmor , ItemType : : Shield , ItemType : : Mace ) )
ivalue = INT_MAX ;
break ;
case HeroClass : : Bard :
if ( IsAnyOf ( itemType , ItemType : : Axe , ItemType : : Mace , ItemType : : Staff ) )
ivalue = INT_MAX ;
break ;
case HeroClass : : Barbarian :
if ( IsAnyOf ( itemType , ItemType : : Bow , ItemType : : Staff ) )
ivalue = INT_MAX ;
break ;
}
}
} while ( keepgoing
| | ( (
boyitem . _iIvalue > 200000
| | boyitem . _iMinStr > strength
| | boyitem . _iMinMag > magic
| | boyitem . _iMinDex > dexterity
| | boyitem . _iIvalue < ivalue )
& & count < 250 ) ) ;
boyitem . _iCreateInfo = lvl | CF_BOY ;
boyitem . _iIdentified = true ;
boylevel = lvl / 2 ;
}
void SpawnHealer ( int lvl )
{
constexpr int PinnedItemCount = 2 ;
constexpr std : : array < int , PinnedItemCount + 1 > PinnedItemTypes = { IDI_HEAL , IDI_FULLHEAL , IDI_RESURRECT } ;
const int itemCount = GenerateRnd ( gbIsHellfire ? 10 : 8 ) + 10 ;
for ( int i = 0 ; i < 20 ; i + + ) {
Item & item = healitem [ i ] ;
item = { } ;
if ( i < PinnedItemCount | | ( gbIsMultiplayer & & i = = PinnedItemCount ) ) {
item . _iSeed = AdvanceRndSeed ( ) ;
GetItemAttrs ( item , PinnedItemTypes [ i ] , 1 ) ;
item . _iCreateInfo = lvl ;
item . _iStatFlag = true ;
continue ;
}
if ( i > = itemCount ) {
item . clear ( ) ;
continue ;
}
item . _iSeed = AdvanceRndSeed ( ) ;
SetRndSeed ( item . _iSeed ) ;
int itype = RndHealerItem ( lvl ) - 1 ;
GetItemAttrs ( item , itype , lvl ) ;
item . _iCreateInfo = lvl | CF_HEALER ;
item . _iIdentified = true ;
}
SortVendor ( healitem + PinnedItemCount ) ;
}
void MakeGoldStack ( Item & goldItem , int value )
{
InitializeItem ( goldItem , IDI_GOLD ) ;
GenerateNewSeed ( goldItem ) ;
goldItem . _iStatFlag = true ;
goldItem . _ivalue = value ;
SetPlrHandGoldCurs ( goldItem ) ;
}
int ItemNoFlippy ( )
{
int r = ActiveItems [ ActiveItemCount - 1 ] ;
Items [ r ] . AnimInfo . currentFrame = Items [ r ] . AnimInfo . numberOfFrames - 1 ;
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 ( ItemType : : Misc , IMISC_BOOK , lvl ) ;
if ( ActiveItemCount > = MAXITEMS )
return ;
int ii = AllocateItem ( ) ;
auto & item = Items [ ii ] ;
while ( true ) {
item = { } ;
SetupAllItems ( item , idx , AdvanceRndSeed ( ) , 2 * lvl , 1 , true , false , delta ) ;
if ( item . _iMiscId = = IMISC_BOOK & & item . _iSpell = = ispell )
break ;
}
GetSuperItemSpace ( position , ii ) ;
if ( sendmsg )
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
if ( delta )
DeltaAddItem ( ii ) ;
}
void CreateMagicArmor ( Point position , ItemType itemType , int icurs , bool sendmsg , bool delta )
{
int lvl = ItemsGetCurrlevel ( ) ;
CreateMagicItem ( position , lvl , itemType , IMISC_NONE , icurs , sendmsg , delta ) ;
}
void CreateAmulet ( Point position , int lvl , bool sendmsg , bool delta )
{
CreateMagicItem ( position , lvl , ItemType : : Amulet , IMISC_AMULET , ICURS_AMULET , sendmsg , delta ) ;
}
void CreateMagicWeapon ( Point position , ItemType itemType , int icurs , bool sendmsg , bool delta )
{
int imid = IMISC_NONE ;
if ( itemType = = ItemType : : Staff )
imid = IMISC_STAFF ;
int curlv = ItemsGetCurrlevel ( ) ;
CreateMagicItem ( position , curlv , itemType , 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 )
{
if ( ActiveItemCount > = MAXITEMS )
return " No space to generate the item! " ;
const int max_time = 3000 ;
const int max_iter = 1000000 ;
std : : transform ( itemName . begin ( ) , itemName . end ( ) , itemName . begin ( ) , [ ] ( unsigned char c ) { return std : : tolower ( c ) ; } ) ;
int ii = AllocateItem ( ) ;
auto & item = Items [ ii ] ;
Point pos = MyPlayer - > position . tile ;
GetSuperItemSpace ( pos , ii ) ;
uint32_t begin = SDL_GetTicks ( ) ;
Monster fake_m ;
fake_m . levelType = 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 . level = dist ( BetterRng ) % CF_LEVEL + 1 ;
int idx = RndItem ( fake_m ) ;
if ( idx > 1 ) {
idx - - ;
} else
continue ;
Point bkp = item . position ;
item = { } ;
item . position = bkp ;
SetupAllItems ( item , idx , AdvanceRndSeed ( ) , fake_m . level , 1 , false , 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 ;
}
item . _iIdentified = true ;
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
return fmt : : format ( " Item generated successfully - iterations: {:d} " , i ) ;
}
std : : string DebugSpawnUniqueItem ( std : : string itemName )
{
if ( ActiveItemCount > = MAXITEMS )
return " No space to generate the item! " ;
const int max_time = 3000 ;
const int max_iter = 1000000 ;
std : : transform ( itemName . begin ( ) , itemName . end ( ) , itemName . begin ( ) , [ ] ( unsigned char c ) { return std : : tolower ( c ) ; } ) ;
UniqueItem uniqueItem ;
bool foundUnique = false ;
int uniqueBaseIndex = 0 ;
int uniqueIndex = 0 ;
for ( int j = 0 ; UniqueItems [ j ] . UIItemId ! = UITYPE_INVALID ; j + + ) {
if ( ! IsUniqueAvailable ( j ) )
break ;
std : : string tmp ( UniqueItems [ j ] . UIName ) ;
std : : transform ( tmp . begin ( ) , tmp . end ( ) , tmp . begin ( ) , [ ] ( unsigned char c ) { return std : : tolower ( c ) ; } ) ;
if ( tmp . find ( itemName ) ! = std : : string : : npos ) {
itemName = tmp ;
uniqueItem = UniqueItems [ j ] ;
uniqueIndex = j ;
foundUnique = true ;
break ;
}
}
if ( ! foundUnique )
return " No unique found! " ;
for ( int j = 0 ; AllItemsList [ j ] . iLoc ! = ILOC_INVALID ; j + + ) {
if ( ! IsItemAvailable ( j ) )
continue ;
if ( AllItemsList [ j ] . iItemId = = uniqueItem . UIItemId )
uniqueBaseIndex = j ;
}
int ii = AllocateItem ( ) ;
auto & item = Items [ ii ] ;
Point pos = MyPlayer - > position . tile ;
GetSuperItemSpace ( pos , ii ) ;
int i = 0 ;
for ( uint32_t begin = SDL_GetTicks ( ) ; ; i + + ) {
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 ) ;
Point bkp = item . position ;
item = { } ;
item . position = bkp ;
std : : uniform_int_distribution < int32_t > dist ( 0 , INT_MAX ) ;
SetRndSeed ( dist ( BetterRng ) ) ;
for ( auto & flag : UniqueItemFlags )
flag = true ;
UniqueItemFlags [ uniqueIndex ] = false ;
SetupAllItems ( item , uniqueBaseIndex , AdvanceRndSeed ( ) , uniqueItem . UIMinLvl , 1 , false , false , false ) ;
for ( auto & flag : UniqueItemFlags )
flag = false ;
if ( item . _iMagical ! = ITEM_QUALITY_UNIQUE )
continue ;
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 ;
return " Impossible to generate! " ;
}
item . _iIdentified = true ;
NetSendCmdPItem ( false , CMD_DROPITEM , item . position , item ) ;
return fmt : : format ( " Item generated successfully - iterations: {:d} " , i ) ;
}
# endif
void Item : : setNewAnimation ( bool showAnimation )
{
int8_t it = ItemCAnimTbl [ _iCurs ] ;
int8_t numberOfFrames = ItemAnimLs [ it ] ;
auto celSprite = itemanims [ it ] ? OptionalCelSprite { * itemanims [ it ] } : std : : nullopt ;
if ( _iCurs ! = ICURS_MAGIC_ROCK )
AnimInfo . setNewAnimation ( celSprite , numberOfFrames , 1 , AnimationDistributionFlags : : ProcessAnimationPending , 0 , numberOfFrames ) ;
else
AnimInfo . setNewAnimation ( celSprite , numberOfFrames , 1 ) ;
_iPostDraw = false ;
_iRequest = false ;
if ( showAnimation ) {
_iAnimFlag = true ;
_iSelFlag = 0 ;
} else {
AnimInfo . currentFrame = AnimInfo . numberOfFrames - 1 ;
_iAnimFlag = false ;
_iSelFlag = 1 ;
}
}
void Item : : updateRequiredStatsCacheForPlayer ( const Player & player )
{
if ( _itype = = ItemType : : Misc & & _iMiscId = = IMISC_BOOK ) {
_iMinMag = spelldata [ _iSpell ] . sMinInt ;
int8_t spellLevel = player . _pSplLvl [ _iSpell ] ;
while ( spellLevel ! = 0 ) {
_iMinMag + = 20 * _iMinMag / 100 ;
spellLevel - - ;
if ( _iMinMag + 20 * _iMinMag / 100 > 255 ) {
_iMinMag = 255 ;
spellLevel = 0 ;
}
}
}
_iStatFlag = player . CanUseItem ( * this ) ;
}
items: add BUGFIX, reset item get records when resetting items (#2691)
* items: add BUGFIX, reset item get records when resetting items
The item get record array tracks items being recently looted in an
effort to prevent the same item from being looted more than once.
Prior to this commit, the item get record array (and corresponding
item get record array length) variables were not cleared when
creating a new game. Therefore, the item get record array of a
previous game could remain in between games and prevent an item
from being looted (if it was looted in a previous), even if it was
never looted in the current game. In practice this almost never
shows up, since each item get record is valid for a total of 6
seconds before being cleared. So, you would either have to save
a game, quickly loot an item, when load the game and try to loot
the same item before 6 seconds pass. OR, you could use the demo
replay functionality to run test cases, and speed up execution to
run e.g. 10'000'000 logic ticks per second. Both would exhibit the
bug and prevent the item from being looted.
This commit fixes such issues by explicitly clearing the item get
record array and item get record array length variables whenever
items are initialized.
5 years ago
void initItemGetRecords ( )
{
memset ( itemrecord , 0 , sizeof ( itemrecord ) ) ;
gnNumGetRecords = 0 ;
}
void RepairItem ( Item & item , int lvl )
{
if ( item . _iDurability = = item . _iMaxDur ) {
return ;
}
if ( item . _iMaxDur < = 0 ) {
item . clear ( ) ;
return ;
}
int rep = 0 ;
do {
rep + = lvl + GenerateRnd ( lvl ) ;
item . _iMaxDur - = std : : max ( item . _iMaxDur / ( lvl + 9 ) , 1 ) ;
if ( item . _iMaxDur = = 0 ) {
item . clear ( ) ;
return ;
}
} while ( rep + item . _iDurability < item . _iMaxDur ) ;
item . _iDurability = std : : min < int > ( item . _iDurability + rep , item . _iMaxDur ) ;
}
void RechargeItem ( Item & item , Player & player )
{
if ( item . _itype ! = ItemType : : Staff | | item . _iSpell = = SPL_NULL )
return ;
if ( item . _iCharges = = item . _iMaxCharges )
return ;
int r = GetSpellBookLevel ( item . _iSpell ) ;
r = GenerateRnd ( player . _pLevel / r ) + 1 ;
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 = = ItemType : : 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 = = DUR_INDESTRUCTIBLE )
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 = DUR_INDESTRUCTIBLE ;
item . _iMaxDur = DUR_INDESTRUCTIBLE ;
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 ;
}
} // namespace devilution