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