diff --git a/Source/drlg_l1.cpp b/Source/drlg_l1.cpp index dedaddf92..493b7afd6 100644 --- a/Source/drlg_l1.cpp +++ b/Source/drlg_l1.cpp @@ -9,7 +9,6 @@ #include "engine/point.hpp" #include "engine/random.hpp" #include "gendung.h" -#include "lighting.h" #include "player.h" #include "quests.h" @@ -1375,37 +1374,43 @@ void AddWall() for (int j = 0; j < DMAXY; j++) { for (int i = 0; i < DMAXX; i++) { if (L5dflags[i][j] == 0) { - if (dungeon[i][j] == 3 && GenerateRnd(100) < WALL_CHANCE) { + if (dungeon[i][j] == 3) { + GenerateRnd(100); int x = HorizontalWallOk(i, j); if (x != -1) { HorizontalWall(i, j, 2, x); } } - if (dungeon[i][j] == 3 && GenerateRnd(100) < WALL_CHANCE) { + if (dungeon[i][j] == 3) { + GenerateRnd(100); int y = VerticalWallOk(i, j); if (y != -1) { VerticalWall(i, j, 1, y); } } - if (dungeon[i][j] == 6 && GenerateRnd(100) < WALL_CHANCE) { + if (dungeon[i][j] == 6) { + GenerateRnd(100); int x = HorizontalWallOk(i, j); if (x != -1) { HorizontalWall(i, j, 4, x); } } - if (dungeon[i][j] == 7 && GenerateRnd(100) < WALL_CHANCE) { + if (dungeon[i][j] == 7) { + GenerateRnd(100); int y = VerticalWallOk(i, j); if (y != -1) { VerticalWall(i, j, 4, y); } } - if (dungeon[i][j] == 2 && GenerateRnd(100) < WALL_CHANCE) { + if (dungeon[i][j] == 2) { + GenerateRnd(100); int x = HorizontalWallOk(i, j); if (x != -1) { HorizontalWall(i, j, 2, x); } } - if (dungeon[i][j] == 1 && GenerateRnd(100) < WALL_CHANCE) { + if (dungeon[i][j] == 1) { + GenerateRnd(100); int y = VerticalWallOk(i, j); if (y != -1) { VerticalWall(i, j, 1, y); @@ -2460,66 +2465,6 @@ void Pass3() } // namespace -void DRLG_LPass3(int lv) -{ - { - MegaTile mega = pMegaTiles[lv]; - int v1 = SDL_SwapLE16(mega.micro1) + 1; - int v2 = SDL_SwapLE16(mega.micro2) + 1; - int v3 = SDL_SwapLE16(mega.micro3) + 1; - int v4 = SDL_SwapLE16(mega.micro4) + 1; - - for (int j = 0; j < MAXDUNY; j += 2) { - for (int i = 0; i < MAXDUNX; i += 2) { - dPiece[i + 0][j + 0] = v1; - dPiece[i + 1][j + 0] = v2; - dPiece[i + 0][j + 1] = v3; - dPiece[i + 1][j + 1] = v4; - } - } - } - - int yy = 16; - for (int j = 0; j < DMAXY; j++) { - int xx = 16; - for (int i = 0; i < DMAXX; i++) { // NOLINT(modernize-loop-convert) - int v1 = 0; - int v2 = 0; - int v3 = 0; - int v4 = 0; - - int tileId = dungeon[i][j] - 1; - if (tileId >= 0) { - MegaTile mega = pMegaTiles[tileId]; - v1 = SDL_SwapLE16(mega.micro1) + 1; - v2 = SDL_SwapLE16(mega.micro2) + 1; - v3 = SDL_SwapLE16(mega.micro3) + 1; - v4 = SDL_SwapLE16(mega.micro4) + 1; - } - dPiece[xx + 0][yy + 0] = v1; - dPiece[xx + 1][yy + 0] = v2; - dPiece[xx + 0][yy + 1] = v3; - dPiece[xx + 1][yy + 1] = v4; - xx += 2; - } - yy += 2; - } -} - -void DRLG_Init_Globals() -{ - memset(dFlags, 0, sizeof(dFlags)); - memset(dPlayer, 0, sizeof(dPlayer)); - memset(dMonster, 0, sizeof(dMonster)); - memset(dDead, 0, sizeof(dDead)); - memset(dObject, 0, sizeof(dObject)); - memset(dItem, 0, sizeof(dItem)); - memset(dMissile, 0, sizeof(dMissile)); - memset(dSpecial, 0, sizeof(dSpecial)); - int8_t c = DisableLighting ? 0 : 15; - memset(dLight, c, sizeof(dLight)); -} - void LoadL1Dungeon(const char *path, int vx, int vy) { dminx = 16; diff --git a/Source/drlg_l1.h b/Source/drlg_l1.h index 5fa9c3f43..86e5580b5 100644 --- a/Source/drlg_l1.h +++ b/Source/drlg_l1.h @@ -9,16 +9,12 @@ namespace devilution { -#define WALL_CHANCE 100 - extern int UberRow; extern int UberCol; extern bool IsUberRoomOpened; extern bool IsUberLeverActivated; extern int UberDiabloMonsterIndex; -void DRLG_LPass3(int lv); -void DRLG_Init_Globals(); void LoadL1Dungeon(const char *path, int vx, int vy); void LoadPreL1Dungeon(const char *path); void CreateL5Dungeon(uint32_t rseed, lvl_entry entry); diff --git a/Source/drlg_l2.cpp b/Source/drlg_l2.cpp index 1b15a55a3..00bb8e84c 100644 --- a/Source/drlg_l2.cpp +++ b/Source/drlg_l2.cpp @@ -9,10 +9,9 @@ #include #include "diablo.h" -#include "drlg_l1.h" #include "engine/load_file.hpp" #include "engine/random.hpp" -#include "objects.h" +#include "gendung.h" #include "player.h" #include "quests.h" #include "setmaps.h" diff --git a/Source/drlg_l3.cpp b/Source/drlg_l3.cpp index f6d653111..e91451361 100644 --- a/Source/drlg_l3.cpp +++ b/Source/drlg_l3.cpp @@ -6,9 +6,9 @@ #include -#include "drlg_l1.h" #include "engine/load_file.hpp" #include "engine/random.hpp" +#include "gendung.h" #include "lighting.h" #include "monster.h" #include "objdat.h" diff --git a/Source/drlg_l4.cpp b/Source/drlg_l4.cpp index dc7d72613..a79604399 100644 --- a/Source/drlg_l4.cpp +++ b/Source/drlg_l4.cpp @@ -5,9 +5,9 @@ */ #include "drlg_l4.h" -#include "drlg_l1.h" #include "engine/load_file.hpp" #include "engine/random.hpp" +#include "gendung.h" #include "monster.h" #include "multi.h" #include "objdat.h" @@ -640,13 +640,15 @@ void AddWall() if (dflags[i][j] != 0) { continue; } - if (IsAnyOf(dungeon[i][j], 10, 12, 13, 15, 16, 21, 22) && GenerateRnd(100) < WALL_CHANCE) { + if (IsAnyOf(dungeon[i][j], 10, 12, 13, 15, 16, 21, 22)) { + GenerateRnd(100); int x = HorizontalWallOk(i, j); if (x != -1) { HorizontalWall(i, j, x); } } - if (IsAnyOf(dungeon[i][j], 8, 9, 11, 14, 15, 16, 21, 23) && GenerateRnd(100) < WALL_CHANCE) { + if (IsAnyOf(dungeon[i][j], 8, 9, 11, 14, 15, 16, 21, 23)) { + GenerateRnd(100); int y = VerticalWallOk(i, j); if (y != -1) { VerticalWall(i, j, y); diff --git a/Source/effects.cpp b/Source/effects.cpp index 4fd8a47f5..e938b0c5b 100644 --- a/Source/effects.cpp +++ b/Source/effects.cpp @@ -1394,7 +1394,7 @@ int GetSFXLength(int nSFX) #ifdef RUN_TESTS bool TestCalculatePosition(Point soundPosition, int *plVolume, int *plPan) { - CalculatePosition(soundPosition, plVolume, plPan); + return CalculatePosition(soundPosition, plVolume, plPan); } #endif diff --git a/Source/encrypt.cpp b/Source/encrypt.cpp index 540e1238f..d17e4b68f 100644 --- a/Source/encrypt.cpp +++ b/Source/encrypt.cpp @@ -11,6 +11,35 @@ namespace devilution { +namespace { + +static unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter) +{ + auto *pInfo = (TDataInfo *)param; + + uint32_t sSize; + if (*size >= pInfo->size - pInfo->srcOffset) { + sSize = pInfo->size - pInfo->srcOffset; + } else { + sSize = *size; + } + + memcpy(buf, pInfo->srcData + pInfo->srcOffset, sSize); + pInfo->srcOffset += sSize; + + return sSize; +} + +static void PkwareBufferWrite(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter) +{ + auto *pInfo = (TDataInfo *)param; + + memcpy(pInfo->destData + pInfo->destOffset, buf, *size); + pInfo->destOffset += *size; +} + +} // namespace + static uint32_t hashtable[5][256]; void Decrypt(uint32_t *castBlock, uint32_t size, uint32_t key) @@ -69,31 +98,6 @@ void InitHash() } } -static unsigned int PkwareBufferRead(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter) -{ - auto *pInfo = (TDataInfo *)param; - - uint32_t sSize; - if (*size >= pInfo->size - pInfo->srcOffset) { - sSize = pInfo->size - pInfo->srcOffset; - } else { - sSize = *size; - } - - memcpy(buf, pInfo->srcData + pInfo->srcOffset, sSize); - pInfo->srcOffset += sSize; - - return sSize; -} - -static void PkwareBufferWrite(char *buf, unsigned int *size, void *param) // NOLINT(readability-non-const-parameter) -{ - auto *pInfo = (TDataInfo *)param; - - memcpy(pInfo->destData + pInfo->destOffset, buf, *size); - pInfo->destOffset += *size; -} - uint32_t PkwareCompress(byte *srcData, uint32_t size) { std::unique_ptr ptr { new char[CMP_BUFFER_SIZE] }; diff --git a/Source/engine.cpp b/Source/engine.cpp index ae1eeadc8..8b81ebb1e 100644 --- a/Source/engine.cpp +++ b/Source/engine.cpp @@ -19,6 +19,35 @@ #include "options.h" namespace devilution { +namespace { + +static void DrawHalfTransparentBlendedRectTo(const Surface &out, int sx, int sy, int width, int height) +{ + BYTE *pix = out.at(sx, sy); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + *pix = paletteTransparencyLookup[0][*pix]; + pix++; + } + pix += out.pitch() - width; + } +} + +static void DrawHalfTransparentStippledRectTo(const Surface &out, int sx, int sy, int width, int height) +{ + BYTE *pix = out.at(sx, sy); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + if (((row & 1) != 0 && (col & 1) != 0) || ((row & 1) == 0 && (col & 1) == 0)) + *pix = 0; + pix++; + } + pix += out.pitch() - width; + } +} +} // namespace void DrawHorizontalLine(const Surface &out, Point from, int width, std::uint8_t colorIndex) { @@ -61,33 +90,6 @@ void UnsafeDrawVerticalLine(const Surface &out, Point from, int height, std::uin } } -static void DrawHalfTransparentBlendedRectTo(const Surface &out, int sx, int sy, int width, int height) -{ - BYTE *pix = out.at(sx, sy); - - for (int row = 0; row < height; row++) { - for (int col = 0; col < width; col++) { - *pix = paletteTransparencyLookup[0][*pix]; - pix++; - } - pix += out.pitch() - width; - } -} - -static void DrawHalfTransparentStippledRectTo(const Surface &out, int sx, int sy, int width, int height) -{ - BYTE *pix = out.at(sx, sy); - - for (int row = 0; row < height; row++) { - for (int col = 0; col < width; col++) { - if (((row & 1) != 0 && (col & 1) != 0) || ((row & 1) == 0 && (col & 1) == 0)) - *pix = 0; - pix++; - } - pix += out.pitch() - width; - } -} - void DrawHalfTransparentRectTo(const Surface &out, int sx, int sy, int width, int height) { if (sx + width < 0) @@ -179,19 +181,4 @@ int CalculateWidth2(int width) return (width - 64) / 2; } -/** - * @brief Fade to black and play a video - * @param pszMovie file path of movie - */ -void PlayInGameMovie(const char *pszMovie) -{ - PaletteFadeOut(8); - play_movie(pszMovie, false); - ClearScreenBuffer(); - force_redraw = 255; - scrollrt_draw_game_screen(); - PaletteFadeIn(8); - force_redraw = 255; -} - } // namespace devilution diff --git a/Source/engine.h b/Source/engine.h index 29bff58b2..9b8356d9a 100644 --- a/Source/engine.h +++ b/Source/engine.h @@ -120,6 +120,4 @@ Direction GetDirection(Point start, Point destination); */ int CalculateWidth2(int width); -void PlayInGameMovie(const char *pszMovie); - } // namespace devilution diff --git a/Source/error.cpp b/Source/error.cpp index e1ccaa74b..57b67dfac 100644 --- a/Source/error.cpp +++ b/Source/error.cpp @@ -13,11 +13,16 @@ namespace devilution { -diablo_message msgtable[MAX_SEND_STR_LEN]; DWORD msgdelay; diablo_message msgflag; + +namespace { + +diablo_message msgtable[MAX_SEND_STR_LEN]; uint8_t msgcnt; +} // namespace + /** Maps from error_id to error message. */ const char *const MsgStrings[] = { "", diff --git a/Source/gendung.cpp b/Source/gendung.cpp index 28ac88b20..f242c3c54 100644 --- a/Source/gendung.cpp +++ b/Source/gendung.cpp @@ -8,29 +8,21 @@ #include "engine/load_file.hpp" #include "engine/random.hpp" #include "init.h" +#include "lighting.h" #include "options.h" namespace devilution { -/** Contains the tile IDs of the map. */ uint8_t dungeon[DMAXX][DMAXY]; -/** Contains a backup of the tile IDs of the map. */ uint8_t pdungeon[DMAXX][DMAXY]; char dflags[DMAXX][DMAXY]; -/** Specifies the active set level X-coordinate of the map. */ int setpc_x; -/** Specifies the active set level Y-coordinate of the map. */ int setpc_y; -/** Specifies the width of the active set level of the map. */ int setpc_w; -/** Specifies the height of the active set level of the map. */ int setpc_h; -/** Contains the contents of the single player quest DUN file. */ std::unique_ptr pSetPiece; -/** Specifies whether a single player quest DUN has been loaded. */ bool setloadflag; std::optional pSpecialCels; -/** Specifies the tile definitions of the active dungeon type; (e.g. levels/l1data/l1.til). */ std::unique_ptr pMegaTiles; std::unique_ptr pLevelPieces; std::unique_ptr pDungeonCels; @@ -40,71 +32,39 @@ std::array nSolidTable; std::array nTransTable; std::array nMissileTable; std::array nTrapTable; -/** Specifies the minimum X-coordinate of the map. */ int dminx; -/** Specifies the minimum Y-coordinate of the map. */ int dminy; -/** Specifies the maximum X-coordinate of the map. */ int dmaxx; -/** Specifies the maximum Y-coordinate of the map. */ int dmaxy; -/** Specifies the active dungeon type of the current game. */ dungeon_type leveltype; -/** Specifies the active dungeon level of the current game. */ BYTE currlevel; bool setlevel; -/** Specifies the active quest level of the current game. */ _setlevels setlvlnum; -/** Level type of the active quest level */ dungeon_type setlvltype; -/** Specifies the player viewpoint X-coordinate of the map. */ int ViewX; -/** Specifies the player viewpoint Y-coordinate of the map. */ int ViewY; ScrollStruct ScrollInfo; int MicroTileLen; char TransVal; -/** Specifies the active transparency indices. */ bool TransList[256]; -/** Contains the piece IDs of each tile on the map. */ int dPiece[MAXDUNX][MAXDUNY]; -/** Specifies the dungeon piece information for a given coordinate and block number. */ MICROS dpiece_defs_map_2[MAXDUNX][MAXDUNY]; -/** Specifies the transparency at each coordinate of the map. */ int8_t dTransVal[MAXDUNX][MAXDUNY]; char dLight[MAXDUNX][MAXDUNY]; char dPreLight[MAXDUNX][MAXDUNY]; int8_t dFlags[MAXDUNX][MAXDUNY]; -/** Contains the player numbers (players array indices) of the map. */ int8_t dPlayer[MAXDUNX][MAXDUNY]; -/** - * Contains the NPC numbers of the map. The NPC number represents a - * towner number (towners array index) in Tristram and a monster number - * (monsters array index) in the dungeon. - */ int16_t dMonster[MAXDUNX][MAXDUNY]; -/** - * Contains the dead numbers (deads array indices) and dead direction of - * the map, encoded as specified by the pseudo-code below. - * dDead[x][y] & 0x1F - index of dead - * dDead[x][y] >> 0x5 - direction - */ int8_t dDead[MAXDUNX][MAXDUNY]; -/** Contains the object numbers (objects array indices) of the map. */ char dObject[MAXDUNX][MAXDUNY]; -/** Contains the item numbers (items array indices) of the map. */ int8_t dItem[MAXDUNX][MAXDUNY]; -/** Contains the missile numbers (missiles array indices) of the map. */ int8_t dMissile[MAXDUNX][MAXDUNY]; -/** - * Contains the arch frame numbers of the map from the special tileset - * (e.g. "levels/l1data/l1s.cel"). Note, the special tileset of Tristram (i.e. - * "levels/towndata/towns.cel") contains trees rather than arches. - */ char dSpecial[MAXDUNX][MAXDUNY]; int themeCount; THEME_LOC themeLoc[MAXTHEMES]; +namespace { + std::unique_ptr LoadLevelSOLData(size_t &tileCount) { switch (leveltype) { @@ -129,150 +89,6 @@ std::unique_ptr LoadLevelSOLData(size_t &tileCount) } } -void FillSolidBlockTbls() -{ - size_t tileCount; - auto pSBFile = LoadLevelSOLData(tileCount); - - for (unsigned i = 0; i < tileCount; i++) { - uint8_t bv = pSBFile[i]; - nSolidTable[i + 1] = (bv & 0x01) != 0; - nBlockTable[i + 1] = (bv & 0x02) != 0; - nMissileTable[i + 1] = (bv & 0x04) != 0; - nTransTable[i + 1] = (bv & 0x08) != 0; - nTrapTable[i + 1] = (bv & 0x80) != 0; - block_lvid[i + 1] = (bv & 0x70) >> 4; - } -} - -void SetDungeonMicros() -{ - MicroTileLen = 10; - int blocks = 10; - - if (leveltype == DTYPE_TOWN) { - MicroTileLen = 16; - blocks = 16; - } else if (leveltype == DTYPE_HELL) { - MicroTileLen = 12; - blocks = 16; - } - - for (int y = 0; y < MAXDUNY; y++) { - for (int x = 0; x < MAXDUNX; x++) { - int lv = dPiece[x][y]; - MICROS µs = dpiece_defs_map_2[x][y]; - if (lv != 0) { - lv--; - uint16_t *pieces = &pLevelPieces[blocks * lv]; - for (int i = 0; i < blocks; i++) - micros.mt[i] = SDL_SwapLE16(pieces[blocks - 2 + (i & 1) - (i & 0xE)]); - } else { - for (int i = 0; i < blocks; i++) - micros.mt[i] = 0; - } - } - } -} - -void DRLG_InitTrans() -{ - memset(dTransVal, 0, sizeof(dTransVal)); - memset(TransList, 0, sizeof(TransList)); - TransVal = 1; -} - -void DRLG_MRectTrans(int x1, int y1, int x2, int y2) -{ - x1 = 2 * x1 + 17; - y1 = 2 * y1 + 17; - x2 = 2 * x2 + 16; - y2 = 2 * y2 + 16; - - for (int j = y1; j <= y2; j++) { - for (int i = x1; i <= x2; i++) { - dTransVal[i][j] = TransVal; - } - } - - TransVal++; -} - -void DRLG_RectTrans(int x1, int y1, int x2, int y2) -{ - for (int j = y1; j <= y2; j++) { - for (int i = x1; i <= x2; i++) { - dTransVal[i][j] = TransVal; - } - } - TransVal++; -} - -void DRLG_CopyTrans(int sx, int sy, int dx, int dy) -{ - dTransVal[dx][dy] = dTransVal[sx][sy]; -} - -void DRLG_ListTrans(int num, BYTE *list) -{ - for (int i = 0; i < num; i++) { - uint8_t x1 = *list++; - uint8_t y1 = *list++; - uint8_t x2 = *list++; - uint8_t y2 = *list++; - DRLG_RectTrans(x1, y1, x2, y2); - } -} - -void DRLG_AreaTrans(int num, BYTE *list) -{ - for (int i = 0; i < num; i++) { - uint8_t x1 = *list++; - uint8_t y1 = *list++; - uint8_t x2 = *list++; - uint8_t y2 = *list++; - DRLG_RectTrans(x1, y1, x2, y2); - TransVal--; - } - TransVal++; -} - -void DRLG_InitSetPC() -{ - setpc_x = 0; - setpc_y = 0; - setpc_w = 0; - setpc_h = 0; -} - -void DRLG_SetPC() -{ - int w = 2 * setpc_w; - int h = 2 * setpc_h; - int x = 2 * setpc_x + 16; - int y = 2 * setpc_y + 16; - - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - dFlags[i + x][j + y] |= BFLAG_POPULATED; - } - } -} - -void Make_SetPC(int x, int y, int w, int h) -{ - int dw = 2 * w; - int dh = 2 * h; - int dx = 2 * x + 16; - int dy = 2 * y + 16; - - for (int j = 0; j < dh; j++) { - for (int i = 0; i < dw; i++) { - dFlags[i + dx][j + dy] |= BFLAG_POPULATED; - } - } -} - bool DRLG_WillThemeRoomFit(int floor, int x, int y, int minSize, int maxSize, int *width, int *height) { bool yFlag = true; @@ -451,6 +267,152 @@ void DRLG_CreateThemeRoom(int themeIndex) } } +} // namespace + +void FillSolidBlockTbls() +{ + size_t tileCount; + auto pSBFile = LoadLevelSOLData(tileCount); + + for (unsigned i = 0; i < tileCount; i++) { + uint8_t bv = pSBFile[i]; + nSolidTable[i + 1] = (bv & 0x01) != 0; + nBlockTable[i + 1] = (bv & 0x02) != 0; + nMissileTable[i + 1] = (bv & 0x04) != 0; + nTransTable[i + 1] = (bv & 0x08) != 0; + nTrapTable[i + 1] = (bv & 0x80) != 0; + block_lvid[i + 1] = (bv & 0x70) >> 4; + } +} + +void SetDungeonMicros() +{ + MicroTileLen = 10; + int blocks = 10; + + if (leveltype == DTYPE_TOWN) { + MicroTileLen = 16; + blocks = 16; + } else if (leveltype == DTYPE_HELL) { + MicroTileLen = 12; + blocks = 16; + } + + for (int y = 0; y < MAXDUNY; y++) { + for (int x = 0; x < MAXDUNX; x++) { + int lv = dPiece[x][y]; + MICROS µs = dpiece_defs_map_2[x][y]; + if (lv != 0) { + lv--; + uint16_t *pieces = &pLevelPieces[blocks * lv]; + for (int i = 0; i < blocks; i++) + micros.mt[i] = SDL_SwapLE16(pieces[blocks - 2 + (i & 1) - (i & 0xE)]); + } else { + for (int i = 0; i < blocks; i++) + micros.mt[i] = 0; + } + } + } +} + +void DRLG_InitTrans() +{ + memset(dTransVal, 0, sizeof(dTransVal)); + memset(TransList, 0, sizeof(TransList)); + TransVal = 1; +} + +void DRLG_MRectTrans(int x1, int y1, int x2, int y2) +{ + x1 = 2 * x1 + 17; + y1 = 2 * y1 + 17; + x2 = 2 * x2 + 16; + y2 = 2 * y2 + 16; + + for (int j = y1; j <= y2; j++) { + for (int i = x1; i <= x2; i++) { + dTransVal[i][j] = TransVal; + } + } + + TransVal++; +} + +void DRLG_RectTrans(int x1, int y1, int x2, int y2) +{ + for (int j = y1; j <= y2; j++) { + for (int i = x1; i <= x2; i++) { + dTransVal[i][j] = TransVal; + } + } + TransVal++; +} + +void DRLG_CopyTrans(int sx, int sy, int dx, int dy) +{ + dTransVal[dx][dy] = dTransVal[sx][sy]; +} + +void DRLG_ListTrans(int num, BYTE *list) +{ + for (int i = 0; i < num; i++) { + uint8_t x1 = *list++; + uint8_t y1 = *list++; + uint8_t x2 = *list++; + uint8_t y2 = *list++; + DRLG_RectTrans(x1, y1, x2, y2); + } +} + +void DRLG_AreaTrans(int num, BYTE *list) +{ + for (int i = 0; i < num; i++) { + uint8_t x1 = *list++; + uint8_t y1 = *list++; + uint8_t x2 = *list++; + uint8_t y2 = *list++; + DRLG_RectTrans(x1, y1, x2, y2); + TransVal--; + } + TransVal++; +} + +void DRLG_InitSetPC() +{ + setpc_x = 0; + setpc_y = 0; + setpc_w = 0; + setpc_h = 0; +} + +void DRLG_SetPC() +{ + int w = 2 * setpc_w; + int h = 2 * setpc_h; + int x = 2 * setpc_x + 16; + int y = 2 * setpc_y + 16; + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + dFlags[i + x][j + y] |= BFLAG_POPULATED; + } + } +} + +void Make_SetPC(int x, int y, int w, int h) +{ + int dw = 2 * w; + int dh = 2 * h; + int dx = 2 * x + 16; + int dy = 2 * y + 16; + + for (int j = 0; j < dh; j++) { + for (int i = 0; i < dw; i++) { + dFlags[i + dx][j + dy] |= BFLAG_POPULATED; + } + } +} + void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize) { themeCount = 0; @@ -502,6 +464,66 @@ void DRLG_HoldThemeRooms() } } +void DRLG_LPass3(int lv) +{ + { + MegaTile mega = pMegaTiles[lv]; + int v1 = SDL_SwapLE16(mega.micro1) + 1; + int v2 = SDL_SwapLE16(mega.micro2) + 1; + int v3 = SDL_SwapLE16(mega.micro3) + 1; + int v4 = SDL_SwapLE16(mega.micro4) + 1; + + for (int j = 0; j < MAXDUNY; j += 2) { + for (int i = 0; i < MAXDUNX; i += 2) { + dPiece[i + 0][j + 0] = v1; + dPiece[i + 1][j + 0] = v2; + dPiece[i + 0][j + 1] = v3; + dPiece[i + 1][j + 1] = v4; + } + } + } + + int yy = 16; + for (int j = 0; j < DMAXY; j++) { + int xx = 16; + for (int i = 0; i < DMAXX; i++) { // NOLINT(modernize-loop-convert) + int v1 = 0; + int v2 = 0; + int v3 = 0; + int v4 = 0; + + int tileId = dungeon[i][j] - 1; + if (tileId >= 0) { + MegaTile mega = pMegaTiles[tileId]; + v1 = SDL_SwapLE16(mega.micro1) + 1; + v2 = SDL_SwapLE16(mega.micro2) + 1; + v3 = SDL_SwapLE16(mega.micro3) + 1; + v4 = SDL_SwapLE16(mega.micro4) + 1; + } + dPiece[xx + 0][yy + 0] = v1; + dPiece[xx + 1][yy + 0] = v2; + dPiece[xx + 0][yy + 1] = v3; + dPiece[xx + 1][yy + 1] = v4; + xx += 2; + } + yy += 2; + } +} + +void DRLG_Init_Globals() +{ + memset(dFlags, 0, sizeof(dFlags)); + memset(dPlayer, 0, sizeof(dPlayer)); + memset(dMonster, 0, sizeof(dMonster)); + memset(dDead, 0, sizeof(dDead)); + memset(dObject, 0, sizeof(dObject)); + memset(dItem, 0, sizeof(dItem)); + memset(dMissile, 0, sizeof(dMissile)); + memset(dSpecial, 0, sizeof(dSpecial)); + int8_t c = DisableLighting ? 0 : 15; + memset(dLight, c, sizeof(dLight)); +} + bool SkipThemeRoom(int x, int y) { for (int i = 0; i < themeCount; i++) { diff --git a/Source/gendung.h b/Source/gendung.h index b64b45ee7..8e136e551 100644 --- a/Source/gendung.h +++ b/Source/gendung.h @@ -122,16 +122,25 @@ struct ShadowStruct { uint8_t nv3; }; +/** Contains the tile IDs of the map. */ extern uint8_t dungeon[DMAXX][DMAXY]; +/** Contains a backup of the tile IDs of the map. */ extern uint8_t pdungeon[DMAXX][DMAXY]; extern char dflags[DMAXX][DMAXY]; +/** Specifies the active set level X-coordinate of the map. */ extern int setpc_x; +/** Specifies the active set level Y-coordinate of the map. */ extern int setpc_y; +/** Specifies the width of the active set level of the map. */ extern int setpc_w; +/** Specifies the height of the active set level of the map. */ extern int setpc_h; +/** Contains the contents of the single player quest DUN file. */ extern std::unique_ptr pSetPiece; +/** Specifies whether a single player quest DUN has been loaded. */ extern bool setloadflag; extern std::optional pSpecialCels; +/** Specifies the tile definitions of the active dungeon type; (e.g. levels/l1data/l1.til). */ extern std::unique_ptr pMegaTiles; extern std::unique_ptr pLevelPieces; extern std::unique_ptr pDungeonCels; @@ -156,33 +165,67 @@ extern std::array nTransTable; */ extern std::array nMissileTable; extern std::array nTrapTable; +/** Specifies the minimum X-coordinate of the map. */ extern int dminx; +/** Specifies the minimum Y-coordinate of the map. */ extern int dminy; +/** Specifies the maximum X-coordinate of the map. */ extern int dmaxx; +/** Specifies the maximum Y-coordinate of the map. */ extern int dmaxy; +/** Specifies the active dungeon type of the current game. */ extern dungeon_type leveltype; +/** Specifies the active dungeon level of the current game. */ extern BYTE currlevel; extern bool setlevel; +/** Specifies the active quest level of the current game. */ extern _setlevels setlvlnum; +/** Specifies the player viewpoint X-coordinate of the map. */ extern dungeon_type setlvltype; +/** Specifies the player viewpoint X-coordinate of the map. */ extern int ViewX; +/** Specifies the player viewpoint Y-coordinate of the map. */ extern int ViewY; extern ScrollStruct ScrollInfo; extern int MicroTileLen; extern char TransVal; +/** Specifies the active transparency indices. */ extern bool TransList[256]; +/** Contains the piece IDs of each tile on the map. */ extern int dPiece[MAXDUNX][MAXDUNY]; +/** Specifies the dungeon piece information for a given coordinate and block number. */ extern MICROS dpiece_defs_map_2[MAXDUNX][MAXDUNY]; +/** Specifies the transparency at each coordinate of the map. */ extern int8_t dTransVal[MAXDUNX][MAXDUNY]; extern char dLight[MAXDUNX][MAXDUNY]; extern char dPreLight[MAXDUNX][MAXDUNY]; extern int8_t dFlags[MAXDUNX][MAXDUNY]; +/** Contains the player numbers (players array indices) of the map. */ extern int8_t dPlayer[MAXDUNX][MAXDUNY]; +/** + * Contains the NPC numbers of the map. The NPC number represents a + * towner number (towners array index) in Tristram and a monster number + * (monsters array index) in the dungeon. + */ extern int16_t dMonster[MAXDUNX][MAXDUNY]; +/** + * Contains the dead numbers (deads array indices) and dead direction of + * the map, encoded as specified by the pseudo-code below. + * dDead[x][y] & 0x1F - index of dead + * dDead[x][y] >> 0x5 - direction + */ extern int8_t dDead[MAXDUNX][MAXDUNY]; +/** Contains the object numbers (objects array indices) of the map. */ extern char dObject[MAXDUNX][MAXDUNY]; +/** Contains the item numbers (items array indices) of the map. */ extern int8_t dItem[MAXDUNX][MAXDUNY]; +/** Contains the missile numbers (missiles array indices) of the map. */ extern int8_t dMissile[MAXDUNX][MAXDUNY]; +/** + * Contains the arch frame numbers of the map from the special tileset + * (e.g. "levels/l1data/l1s.cel"). Note, the special tileset of Tristram (i.e. + * "levels/towndata/towns.cel") contains trees rather than arches. + */ extern char dSpecial[MAXDUNX][MAXDUNY]; extern int themeCount; extern THEME_LOC themeLoc[MAXTHEMES]; @@ -200,6 +243,8 @@ void DRLG_SetPC(); void Make_SetPC(int x, int y, int w, int h); void DRLG_PlaceThemeRooms(int minSize, int maxSize, int floor, int freq, bool rndSize); void DRLG_HoldThemeRooms(); +void DRLG_LPass3(int lv); +void DRLG_Init_Globals(); bool SkipThemeRoom(int x, int y); void InitLevels(); diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index 67c878655..b4f15ad93 100644 --- a/Source/gmenu.cpp +++ b/Source/gmenu.cpp @@ -19,61 +19,20 @@ #include "utils/ui_fwd.h" namespace devilution { + namespace { + std::optional optbar_cel; std::optional PentSpin_cel; std::optional option_cel; std::optional sgpLogo; -} // namespace - bool mouseNavigation; TMenuItem *sgpCurrItem; int LogoAnim_tick; BYTE LogoAnim_frame; void (*gmenu_current_option)(); -TMenuItem *sgpCurrentMenu; int sgCurrentMenuIdx; -void gmenu_draw_pause(const Surface &out) -{ - if (currlevel != 0) - RedBack(out); - if (sgpCurrentMenu == nullptr) { - LightTableIndex = 0; - DrawString(out, _("Pause"), Point { 0, PANEL_TOP / 2 }, UIS_HUGE | UIS_CENTER, 2); - } -} - -void FreeGMenu() -{ - sgpLogo = std::nullopt; - PentSpin_cel = std::nullopt; - option_cel = std::nullopt; - optbar_cel = std::nullopt; -} - -void gmenu_init_menu() -{ - LogoAnim_frame = 1; - sgpCurrentMenu = nullptr; - sgpCurrItem = nullptr; - gmenu_current_option = nullptr; - sgCurrentMenuIdx = 0; - mouseNavigation = false; - if (gbIsHellfire) - sgpLogo = LoadCel("Data\\hf_logo3.CEL", 430); - else - sgpLogo = LoadCel("Data\\Diabsmal.CEL", 296); - PentSpin_cel = LoadCel("Data\\PentSpin.CEL", 48); - option_cel = LoadCel("Data\\option.CEL", 27); - optbar_cel = LoadCel("Data\\optbar.CEL", 287); -} - -bool gmenu_is_active() -{ - return sgpCurrentMenu != nullptr; -} - static void GmenuUpDown(bool isDown) { if (sgpCurrItem == nullptr) { @@ -123,26 +82,6 @@ static void GmenuLeftRight(bool isRight) sgpCurrItem->fnMenu(false); } -void gmenu_set_items(TMenuItem *pItem, void (*gmFunc)()) -{ - PauseMode = 0; - mouseNavigation = false; - sgpCurrentMenu = pItem; - gmenu_current_option = gmFunc; - if (gmenu_current_option != nullptr) { - gmenu_current_option(); - } - sgCurrentMenuIdx = 0; - if (sgpCurrentMenu != nullptr) { - for (int i = 0; sgpCurrentMenu[i].fnMenu != nullptr; i++) { - sgCurrentMenuIdx++; - } - } - // BUGFIX: OOB access when sgCurrentMenuIdx is 0; should be set to NULL instead. (fixed) - sgpCurrItem = sgCurrentMenuIdx > 0 ? &sgpCurrentMenu[sgCurrentMenuIdx - 1] : nullptr; - GmenuUpDown(true); -} - static void GmenuClearBuffer(const Surface &out, int x, int y, int width, int height) { BYTE *i = out.at(x, y); @@ -192,6 +131,92 @@ static void GameMenuMove() GmenuUpDown(moveDir.y == AxisDirectionY_DOWN); } +static bool GmenuMouseNavigation() +{ + if (MousePosition.x < 282 + PANEL_LEFT) { + return false; + } + if (MousePosition.x > 538 + PANEL_LEFT) { + return false; + } + return true; +} + +static int GmenuGetMouseSlider() +{ + if (MousePosition.x < 282 + PANEL_LEFT) { + return 0; + } + if (MousePosition.x > 538 + PANEL_LEFT) { + return 256; + } + return MousePosition.x - 282 - PANEL_LEFT; +} + +} // namespace + +TMenuItem *sgpCurrentMenu; + +void gmenu_draw_pause(const Surface &out) +{ + if (currlevel != 0) + RedBack(out); + if (sgpCurrentMenu == nullptr) { + LightTableIndex = 0; + DrawString(out, _("Pause"), Point { 0, PANEL_TOP / 2 }, UIS_HUGE | UIS_CENTER, 2); + } +} + +void FreeGMenu() +{ + sgpLogo = std::nullopt; + PentSpin_cel = std::nullopt; + option_cel = std::nullopt; + optbar_cel = std::nullopt; +} + +void gmenu_init_menu() +{ + LogoAnim_frame = 1; + sgpCurrentMenu = nullptr; + sgpCurrItem = nullptr; + gmenu_current_option = nullptr; + sgCurrentMenuIdx = 0; + mouseNavigation = false; + if (gbIsHellfire) + sgpLogo = LoadCel("Data\\hf_logo3.CEL", 430); + else + sgpLogo = LoadCel("Data\\Diabsmal.CEL", 296); + PentSpin_cel = LoadCel("Data\\PentSpin.CEL", 48); + option_cel = LoadCel("Data\\option.CEL", 27); + optbar_cel = LoadCel("Data\\optbar.CEL", 287); +} + +bool gmenu_is_active() +{ + return sgpCurrentMenu != nullptr; +} + +void gmenu_set_items(TMenuItem *pItem, void (*gmFunc)()) +{ + PauseMode = 0; + mouseNavigation = false; + sgpCurrentMenu = pItem; + gmenu_current_option = gmFunc; + if (gmenu_current_option != nullptr) { + gmenu_current_option(); + } + sgCurrentMenuIdx = 0; + if (sgpCurrentMenu != nullptr) { + for (int i = 0; sgpCurrentMenu[i].fnMenu != nullptr; i++) { + sgCurrentMenuIdx++; + } + } + // BUGFIX: OOB access when sgCurrentMenuIdx is 0; should be set to NULL instead. (fixed) + sgpCurrItem = sgCurrentMenuIdx > 0 ? &sgpCurrentMenu[sgCurrentMenuIdx - 1] : nullptr; + GmenuUpDown(true); +} + void gmenu_draw(const Surface &out) { if (sgpCurrentMenu != nullptr) { @@ -253,28 +278,6 @@ bool gmenu_presskeys(int vkey) return true; } -static bool GmenuMouseNavigation() -{ - if (MousePosition.x < 282 + PANEL_LEFT) { - return false; - } - if (MousePosition.x > 538 + PANEL_LEFT) { - return false; - } - return true; -} - -static int GmenuGetMouseSlider() -{ - if (MousePosition.x < 282 + PANEL_LEFT) { - return 0; - } - if (MousePosition.x > 538 + PANEL_LEFT) { - return 256; - } - return MousePosition.x - 282 - PANEL_LEFT; -} - bool gmenu_on_mouse_move() { if (!mouseNavigation) diff --git a/Source/help.cpp b/Source/help.cpp index ec99cb2fb..14351d590 100644 --- a/Source/help.cpp +++ b/Source/help.cpp @@ -16,9 +16,12 @@ namespace devilution { -unsigned int SkipLines; bool HelpFlag; +namespace { + +unsigned int SkipLines; + const char *const HelpText[] = { N_("$Keyboard Shortcuts:"), N_("F1: Open Help Screen"), @@ -93,6 +96,8 @@ const char *const HelpText[] = { std::vector HelpTextLines; +} // namespace + void InitHelp() { HelpFlag = false; diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 258394fd1..7a068a61c 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -23,9 +23,10 @@ #include "utils/stdcompat/optional.hpp" namespace devilution { + namespace { + std::optional sgpBackCel; -} // namespace uint32_t sgdwProgress; int progress_id; @@ -187,6 +188,8 @@ static void DrawCutscene() RenderPresent(); } +} // namespace + void interface_msg_pump() { tagMSG msg; diff --git a/Source/inv.cpp b/Source/inv.cpp index 45954f26f..53b8adf28 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -25,13 +25,9 @@ #include "utils/stdcompat/optional.hpp" namespace devilution { -namespace { -std::optional pInvCels; -} // namespace bool invflag; bool drawsbarflag; -int sgdwLastTime; // check name /** * Maps from inventory slot to screen position. The inventory slots are @@ -132,35 +128,9 @@ const Point InvRect[] = { // clang-format on }; -/* data */ - -void FreeInvGFX() -{ - pInvCels = std::nullopt; -} - -void InitInv() -{ - switch (Players[MyPlayerId]._pClass) { - case HeroClass::Warrior: - case HeroClass::Barbarian: - pInvCels = LoadCel("Data\\Inv\\Inv.CEL", SPANEL_WIDTH); - break; - case HeroClass::Rogue: - case HeroClass::Bard: - pInvCels = LoadCel("Data\\Inv\\Inv_rog.CEL", SPANEL_WIDTH); - break; - case HeroClass::Sorcerer: - pInvCels = LoadCel("Data\\Inv\\Inv_Sor.CEL", SPANEL_WIDTH); - break; - case HeroClass::Monk: - pInvCels = LoadCel(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", SPANEL_WIDTH); - break; - } +namespace { - invflag = false; - drawsbarflag = false; -} +std::optional pInvCels; static void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size) { @@ -186,154 +156,6 @@ static void InvDrawSlotBack(const Surface &out, Point targetPosition, Size size) } } -void DrawInv(const Surface &out) -{ - CelDrawTo(out, { RIGHT_PANEL_X, 351 }, *pInvCels, 1); - - Size slotSize[] = { - { 2, 2 }, //head - { 1, 1 }, //left ring - { 1, 1 }, //right ring - { 1, 1 }, //amulet - { 2, 3 }, //left hand - { 2, 3 }, //right hand - { 2, 3 }, // chest - }; - - Point slotPos[] = { - { 133, 59 }, //head - { 48, 205 }, //left ring - { 249, 205 }, //right ring - { 205, 60 }, //amulet - { 17, 160 }, //left hand - { 248, 160 }, //right hand - { 133, 160 }, // chest - }; - - auto &myPlayer = Players[MyPlayerId]; - - for (int slot = INVLOC_HEAD; slot < NUM_INVLOC; slot++) { - if (!myPlayer.InvBody[slot].isEmpty()) { - int screenX = slotPos[slot].x; - int screenY = slotPos[slot].y; - InvDrawSlotBack(out, { RIGHT_PANEL_X + screenX, screenY }, { slotSize[slot].width * InventorySlotSizeInPixels.width, slotSize[slot].height * InventorySlotSizeInPixels.height }); - - int frame = myPlayer.InvBody[slot]._iCurs + CURSOR_FIRSTITEM; - - auto frameSize = GetInvItemSize(frame); - - // calc item offsets for weapons smaller than 2x3 slots - if (slot == INVLOC_HAND_LEFT) { - screenX += frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0; - screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX; - } else if (slot == INVLOC_HAND_RIGHT) { - screenX += frameSize.width == InventorySlotSizeInPixels.width ? (INV_SLOT_HALF_SIZE_PX - 1) : 1; - screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX; - } - - const auto &cel = GetInvItemSprite(frame); - const int celFrame = GetInvItemFrame(frame); - const Point position { RIGHT_PANEL_X + screenX, screenY }; - - if (pcursinvitem == slot) { - CelBlitOutlineTo(out, GetOutlineColor(myPlayer.InvBody[slot], true), position, cel, celFrame, false); - } - - CelDrawItem(myPlayer.InvBody[slot], out, position, cel, celFrame); - - if (slot == INVLOC_HAND_LEFT) { - if (myPlayer.InvBody[slot]._iLoc == ILOC_TWOHAND) { - if (myPlayer._pClass != HeroClass::Barbarian - || (myPlayer.InvBody[slot]._itype != ITYPE_SWORD - && myPlayer.InvBody[slot]._itype != ITYPE_MACE)) { - InvDrawSlotBack(out, { RIGHT_PANEL_X + slotPos[INVLOC_HAND_RIGHT].x, slotPos[INVLOC_HAND_RIGHT].y }, { slotSize[INVLOC_HAND_RIGHT].width * InventorySlotSizeInPixels.width, slotSize[INVLOC_HAND_RIGHT].height * InventorySlotSizeInPixels.height }); - LightTableIndex = 0; - cel_transparency_active = true; - - const int dstX = RIGHT_PANEL_X + slotPos[INVLOC_HAND_RIGHT].x + (frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0) - 1; - const int dstY = slotPos[INVLOC_HAND_RIGHT].y; - CelClippedBlitLightTransTo(out, { dstX, dstY }, cel, celFrame); - - cel_transparency_active = false; - } - } - } - } - } - - for (int i = 0; i < NUM_INV_GRID_ELEM; i++) { - if (myPlayer.InvGrid[i] != 0) { - InvDrawSlotBack( - out, - InvRect[i + SLOTXY_INV_FIRST] + Displacement { RIGHT_PANEL_X, -1 }, - InventorySlotSizeInPixels); - } - } - - for (int j = 0; j < NUM_INV_GRID_ELEM; j++) { - if (myPlayer.InvGrid[j] > 0) { // first slot of an item - int ii = myPlayer.InvGrid[j] - 1; - int frame = myPlayer.InvList[ii]._iCurs + CURSOR_FIRSTITEM; - - const auto &cel = GetInvItemSprite(frame); - const int celFrame = GetInvItemFrame(frame); - const Point position { InvRect[j + SLOTXY_INV_FIRST].x + RIGHT_PANEL_X, InvRect[j + SLOTXY_INV_FIRST].y - 1 }; - if (pcursinvitem == ii + INVITEM_INV_FIRST) { - CelBlitOutlineTo( - out, - GetOutlineColor(myPlayer.InvList[ii], true), - position, - cel, celFrame, false); - } - - CelDrawItem( - myPlayer.InvList[ii], - out, - position, - cel, celFrame); - } - } -} - -void DrawInvBelt(const Surface &out) -{ - if (talkflag) { - return; - } - - DrawPanelBox(out, { 205, 21, 232, 28 }, { PANEL_X + 205, PANEL_Y + 5 }); - - auto &myPlayer = Players[MyPlayerId]; - - for (int i = 0; i < MAXBELTITEMS; i++) { - if (myPlayer.SpdList[i].isEmpty()) { - continue; - } - - const Point position { InvRect[i + SLOTXY_BELT_FIRST].x + PANEL_X, InvRect[i + SLOTXY_BELT_FIRST].y + PANEL_Y - 1 }; - InvDrawSlotBack(out, position, InventorySlotSizeInPixels); - int frame = myPlayer.SpdList[i]._iCurs + CURSOR_FIRSTITEM; - - const auto &cel = GetInvItemSprite(frame); - const int celFrame = GetInvItemFrame(frame); - - if (pcursinvitem == i + INVITEM_BELT_FIRST) { - if (!sgbControllerActive || invflag) { - CelBlitOutlineTo(out, GetOutlineColor(myPlayer.SpdList[i], true), position, cel, celFrame, false); - } - } - - CelDrawItem(myPlayer.SpdList[i], out, position, cel, celFrame); - - if (AllItemsList[myPlayer.SpdList[i].IDidx].iUsable - && myPlayer.SpdList[i]._iStatFlag - && myPlayer.SpdList[i]._itype != ITYPE_GOLD) { - snprintf(tempstr, sizeof(tempstr) / sizeof(*tempstr), "%i", i + 1); - DrawString(out, tempstr, { position, InventorySlotSizeInPixels }, UIS_SILVER | UIS_RIGHT); - } - } -} - /** * @brief Adds an item to a player's InvGrid array * @param invGridIndex Item's position in InvGrid (this should be the item's topleft grid tile) @@ -391,36 +213,6 @@ bool CanBePlacedOnBelt(const ItemStruct &item) && AllItemsList[item.IDidx].iUsable; } -/** - * @brief Checks whether the given item can be placed on the specified player's belt. Returns 'True' when the item can be placed - * on belt slots and the player has at least one empty slot in his belt. - * If 'persistItem' is 'True', the item is also placed in the belt. - * @param player The player on whose belt will be checked. - * @param item The item to be checked. - * @param persistItem Pass 'True' to actually place the item in the belt. The default is 'False'. - * @return 'True' in case the item can be placed on the player's belt and 'False' otherwise. - */ -bool AutoPlaceItemInBelt(PlayerStruct &player, const ItemStruct &item, bool persistItem) -{ - if (!CanBePlacedOnBelt(item)) { - return false; - } - - for (auto &beltItem : player.SpdList) { - if (beltItem.isEmpty()) { - if (persistItem) { - beltItem = item; - player.CalcScrolls(); - drawsbarflag = true; - } - - return true; - } - } - - return false; -} - /** * @brief Checks whether the given item can be equipped. Since this overload doesn't take player information, it only considers * general aspects about the item, like if its requirements are met and if the item's target location is valid for the body. @@ -554,244 +346,16 @@ bool AutoEquip(int playerId, const ItemStruct &item, inv_body_loc bodyLocation, return true; } -/** - * @brief Automatically attempts to equip the specified item in the most appropriate location in the player's body. - * @note On success, this will broadcast an equipment_change event to let other players know about the equipment change. - * @param playerId The player number whose inventory will be checked for compatibility with the item. - * @param item The item to equip. - * @param persistItem Indicates whether or not the item should be persisted in the player's body. Pass 'False' to check - * whether the player can equip the item but you don't want the item to actually be equipped. 'True' by default. - * @return 'True' if the item was equipped and 'False' otherwise. - */ -bool AutoEquip(int playerId, const ItemStruct &item, bool persistItem) +int SwapItem(ItemStruct *a, ItemStruct *b) { - if (!CanEquip(item)) { - return false; - } - - for (int bodyLocation = INVLOC_HEAD; bodyLocation < NUM_INVLOC; bodyLocation++) { - if (AutoEquip(playerId, item, (inv_body_loc)bodyLocation, persistItem)) { - return true; - } - } + std::swap(*a, *b); - return false; + return b->_iCurs + CURSOR_FIRSTITEM; } -/** - * @brief Checks whether or not auto-equipping behavior is enabled for the given player and item. - * @param player The player to check. - * @param item The item to check. - * @return 'True' if auto-equipping behavior is enabled for the player and item and 'False' otherwise. - */ -bool AutoEquipEnabled(const PlayerStruct &player, const ItemStruct &item) +void CheckInvPaste(int pnum, Point cursorPosition) { - if (item.isWeapon()) { - // Monk can use unarmed attack as an encouraged option, thus we do not automatically equip weapons on him so as to not - // annoy players who prefer that playstyle. - return player._pClass != HeroClass::Monk && sgOptions.Gameplay.bAutoEquipWeapons; - } - - if (item.isArmor()) { - return sgOptions.Gameplay.bAutoEquipArmor; - } - - if (item.isHelm()) { - return sgOptions.Gameplay.bAutoEquipHelms; - } - - if (item.isShield()) { - return sgOptions.Gameplay.bAutoEquipShields; - } - - if (item.isJewelry()) { - return sgOptions.Gameplay.bAutoEquipJewelry; - } - - return true; -} - -/** - * @brief Checks whether the given item can be placed on the specified player's inventory. - * If 'persistItem' is 'True', the item is also placed in the inventory. - * @param item The item to be checked. - * @param persistItem Pass 'True' to actually place the item in the inventory. The default is 'False'. - * @return 'True' in case the item can be placed on the player's inventory and 'False' otherwise. - */ -bool AutoPlaceItemInInventory(PlayerStruct &player, const ItemStruct &item, bool persistItem) -{ - Size itemSize = GetInventorySize(item); - - if (itemSize.height == 1) { - for (int i = 30; i <= 39; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) - return true; - } - for (int x = 9; x >= 0; x--) { - for (int y = 2; y >= 0; y--) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) - return true; - } - } - return false; - } - - if (itemSize.height == 2) { - for (int x = 10 - itemSize.width; x >= 0; x -= itemSize.width) { - for (int y = 0; y < 3; y++) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) - return true; - } - } - if (itemSize.width == 2) { - for (int x = 7; x >= 0; x -= 2) { - for (int y = 0; y < 3; y++) { - if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) - return true; - } - } - } - return false; - } - - if (itemSize == Size { 1, 3 }) { - for (int i = 0; i < 20; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) - return true; - } - return false; - } - - if (itemSize == Size { 2, 3 }) { - for (int i = 0; i < 9; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) - return true; - } - - for (int i = 10; i < 19; i++) { - if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) - return true; - } - return false; - } - - app_fatal("Unknown item size: %ix%i", itemSize.width, itemSize.height); -} - -/** - * @brief Checks whether the given item can be placed on the specified player's inventory slot. - * If 'persistItem' is 'True', the item is also placed in the inventory slot. - * @param slotIndex The 0-based index of the slot to put the item on. - * @param item The item to be checked. - * @param persistItem Pass 'True' to actually place the item in the inventory slot. The default is 'False'. - * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. - */ -bool AutoPlaceItemInInventorySlot(PlayerStruct &player, int slotIndex, const ItemStruct &item, bool persistItem) -{ - int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; - - Size itemSize = GetInventorySize(item); - for (int j = 0; j < itemSize.height; j++) { - if (yy >= NUM_INV_GRID_ELEM) { - return false; - } - int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; - for (int i = 0; i < itemSize.width; i++) { - if (xx >= 10 || player.InvGrid[xx + yy] != 0) { - return false; - } - xx++; - } - yy += 10; - } - - if (persistItem) { - player.InvList[player._pNumInv] = player.HoldItem; - player._pNumInv++; - - AddItemToInvGrid(player, slotIndex, player._pNumInv, itemSize); - player.CalcScrolls(); - } - - return true; -} - -bool GoldAutoPlace(PlayerStruct &player) -{ - bool done = false; - - for (int i = 0; i < player._pNumInv && !done; i++) { - if (player.InvList[i]._itype != ITYPE_GOLD) - continue; - if (player.InvList[i]._ivalue >= MaxGold) - continue; - - player.InvList[i]._ivalue += player.HoldItem._ivalue; - if (player.InvList[i]._ivalue > MaxGold) { - player.HoldItem._ivalue = player.InvList[i]._ivalue - MaxGold; - SetPlrHandGoldCurs(&player.HoldItem); - player.InvList[i]._ivalue = MaxGold; - if (gbIsHellfire) - GetPlrHandSeed(&player.HoldItem); - } else { - player.HoldItem._ivalue = 0; - done = true; - } - - SetPlrHandGoldCurs(&player.InvList[i]); - player._pGold = CalculateGold(player); - } - - for (int i = 39; i >= 30 && !done; i--) { - done = GoldAutoPlaceInInventorySlot(player, i); - } - for (int x = 9; x >= 0 && !done; x--) { - for (int y = 2; y >= 0 && !done; y--) { - done = GoldAutoPlaceInInventorySlot(player, 10 * y + x); - } - } - - return done; -} - -bool GoldAutoPlaceInInventorySlot(PlayerStruct &player, int slotIndex) -{ - if (player.InvGrid[slotIndex] != 0) { - return false; - } - - int ii = player._pNumInv; - player.InvList[ii] = player.HoldItem; - player._pNumInv++; - player.InvGrid[slotIndex] = player._pNumInv; - GetPlrHandSeed(&player.InvList[ii]); - - int gold = player.HoldItem._ivalue; - if (gold > MaxGold) { - gold -= MaxGold; - player.HoldItem._ivalue = gold; - GetPlrHandSeed(&player.HoldItem); - player.InvList[ii]._ivalue = MaxGold; - return false; - } - - player.HoldItem._ivalue = 0; - player._pGold = CalculateGold(player); - NewCursor(CURSOR_HAND); - - return true; -} - -int SwapItem(ItemStruct *a, ItemStruct *b) -{ - std::swap(*a, *b); - - return b->_iCurs + CURSOR_FIRSTITEM; -} - -void CheckInvPaste(int pnum, Point cursorPosition) -{ - auto &player = Players[pnum]; + auto &player = Players[pnum]; SetICursor(player.HoldItem._iCurs + CURSOR_FIRSTITEM); int i = cursorPosition.x + (IsHardwareCursor() ? 0 : (icursW / 2)); @@ -1129,32 +693,6 @@ void CheckInvPaste(int pnum, Point cursorPosition) } } -void CheckInvSwap(int pnum, BYTE bLoc, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff) -{ - memset(&Items[MAXITEMS], 0, sizeof(*Items)); - RecreateItem(MAXITEMS, idx, wCI, seed, 0, (dwBuff & CF_HELLFIRE) != 0); - - auto &player = Players[pnum]; - - player.HoldItem = Items[MAXITEMS]; - - if (bId) { - player.HoldItem._iIdentified = true; - } - - if (bLoc < NUM_INVLOC) { - player.InvBody[bLoc] = player.HoldItem; - - if (bLoc == INVLOC_HAND_LEFT && player.HoldItem._iLoc == ILOC_TWOHAND) { - player.InvBody[INVLOC_HAND_RIGHT]._itype = ITYPE_NONE; - } else if (bLoc == INVLOC_HAND_RIGHT && player.HoldItem._iLoc == ILOC_TWOHAND) { - player.InvBody[INVLOC_HAND_LEFT]._itype = ITYPE_NONE; - } - } - - CalcPlrInv(pnum, true); -} - void CheckInvCut(int pnum, Point cursorPosition, bool automaticMove) { auto &player = Players[pnum]; @@ -1320,55 +858,704 @@ void CheckInvCut(int pnum, Point cursorPosition, bool automaticMove) } } - if (r >= SLOTXY_BELT_FIRST) { - ItemStruct &beltItem = player.SpdList[r - SLOTXY_BELT_FIRST]; - if (!beltItem.isEmpty()) { - holdItem = beltItem; - if (automaticMove) { - automaticallyMoved = AutoPlaceItemInInventory(player, holdItem, true); - } + if (r >= SLOTXY_BELT_FIRST) { + ItemStruct &beltItem = player.SpdList[r - SLOTXY_BELT_FIRST]; + if (!beltItem.isEmpty()) { + holdItem = beltItem; + if (automaticMove) { + automaticallyMoved = AutoPlaceItemInInventory(player, holdItem, true); + } + + if (!automaticMove || automaticallyMoved) { + beltItem._itype = ITYPE_NONE; + drawsbarflag = true; + } + } + } + + if (!holdItem.isEmpty()) { + if (holdItem._itype == ITYPE_GOLD) { + player._pGold = CalculateGold(player); + } + + CalcPlrInv(pnum, true); + CheckItemStats(player); + + if (pnum == MyPlayerId) { + if (automaticallyEquipped) { + PlaySFX(ItemInvSnds[ItemCAnimTbl[holdItem._iCurs]]); + } else if (!automaticMove || automaticallyMoved) { + PlaySFX(IS_IGRAB); + } + + if (automaticMove) { + if (!automaticallyMoved) { + if (CanBePlacedOnBelt(holdItem) || automaticallyUnequip) { + player.SaySpecific(HeroSpeech::IHaveNoRoom); + } else { + player.SaySpecific(HeroSpeech::ICantDoThat); + } + } + + holdItem._itype = ITYPE_NONE; + } else { + NewCursor(holdItem._iCurs + CURSOR_FIRSTITEM); + if (!IsHardwareCursor()) { + // For a hardware cursor, we set the "hot point" to the center of the item instead. + SetCursorPos(cursorPosition.x - (cursW / 2), cursorPosition.y - (cursH / 2)); + } + } + } + } +} + +static void CheckBookLevel(PlayerStruct &player) +{ + if (player.HoldItem._iMiscId != IMISC_BOOK) + return; + + player.HoldItem._iMinMag = spelldata[player.HoldItem._iSpell].sMinInt; + int8_t spellLevel = player._pSplLvl[player.HoldItem._iSpell]; + while (spellLevel != 0) { + player.HoldItem._iMinMag += 20 * player.HoldItem._iMinMag / 100; + spellLevel--; + if (player.HoldItem._iMinMag + 20 * player.HoldItem._iMinMag / 100 > 255) { + player.HoldItem._iMinMag = -1; + spellLevel = 0; + } + } +} + +static void CheckNaKrulNotes(PlayerStruct &player) +{ + int idx = player.HoldItem.IDidx; + _item_indexes notes[] = { IDI_NOTE1, IDI_NOTE2, IDI_NOTE3 }; + + if (idx != IDI_NOTE1 && idx != IDI_NOTE2 && idx != IDI_NOTE3) { + return; + } + + for (auto note : notes) { + if (idx != note && !player.HasItem(note)) { + return; // the player doesn't have all notes + } + } + + Players[MyPlayerId].Say(HeroSpeech::JustWhatIWasLookingFor, 10); + + for (auto note : notes) { + if (idx != note) { + player.TryRemoveInvItemById(note); + } + } + + int itemNum = ActiveItems[0]; + ItemStruct tmp = Items[itemNum]; + memset(&Items[itemNum], 0, sizeof(*Items)); + GetItemAttrs(itemNum, IDI_FULLNOTE, 16); + SetupItem(itemNum); + player.HoldItem = Items[itemNum]; + Items[itemNum] = tmp; +} + +static void CheckQuestItem(PlayerStruct &player) +{ + auto &myPlayer = Players[MyPlayerId]; + + if (player.HoldItem.IDidx == IDI_OPTAMULET && Quests[Q_BLIND]._qactive == QUEST_ACTIVE) + Quests[Q_BLIND]._qactive = QUEST_DONE; + + if (player.HoldItem.IDidx == IDI_MUSHROOM && Quests[Q_MUSHROOM]._qactive == QUEST_ACTIVE && Quests[Q_MUSHROOM]._qvar1 == QS_MUSHSPAWNED) { + player.Say(HeroSpeech::NowThatsOneBigMushroom, 10); // BUGFIX: Voice for this quest might be wrong in MP + Quests[Q_MUSHROOM]._qvar1 = QS_MUSHPICKED; + } + + if (player.HoldItem.IDidx == IDI_ANVIL && Quests[Q_ANVIL]._qactive != QUEST_NOTAVAIL) { + if (Quests[Q_ANVIL]._qactive == QUEST_INIT) { + Quests[Q_ANVIL]._qactive = QUEST_ACTIVE; + } + if (Quests[Q_ANVIL]._qlog) { + myPlayer.Say(HeroSpeech::INeedToGetThisToGriswold, 10); + } + } + + if (player.HoldItem.IDidx == IDI_GLDNELIX && Quests[Q_VEIL]._qactive != QUEST_NOTAVAIL) { + myPlayer.Say(HeroSpeech::INeedToGetThisToLachdanan, 30); + } + + if (player.HoldItem.IDidx == IDI_ROCK && Quests[Q_ROCK]._qactive != QUEST_NOTAVAIL) { + if (Quests[Q_ROCK]._qactive == QUEST_INIT) { + Quests[Q_ROCK]._qactive = QUEST_ACTIVE; + } + if (Quests[Q_ROCK]._qlog) { + myPlayer.Say(HeroSpeech::ThisMustBeWhatGriswoldWanted, 10); + } + } + + if (player.HoldItem.IDidx == IDI_ARMOFVAL && Quests[Q_BLOOD]._qactive == QUEST_ACTIVE) { + Quests[Q_BLOOD]._qactive = QUEST_DONE; + myPlayer.Say(HeroSpeech::MayTheSpiritOfArkaineProtectMe, 20); + } + + if (player.HoldItem.IDidx == IDI_MAPOFDOOM) { + Quests[Q_GRAVE]._qlog = false; + Quests[Q_GRAVE]._qactive = QUEST_ACTIVE; + Quests[Q_GRAVE]._qvar1 = 1; + Players[MyPlayerId].Say(HeroSpeech::UhHuh, 10); + } + + CheckNaKrulNotes(player); +} + +void CleanupItems(ItemStruct *item, int ii) +{ + dItem[item->position.x][item->position.y] = 0; + + if (currlevel == 21 && item->position == CornerStone.position) { + CornerStone.item._itype = ITYPE_NONE; + CornerStone.item._iSelFlag = 0; + CornerStone.item.position = { 0, 0 }; + CornerStone.item._iAnimFlag = false; + CornerStone.item._iIdentified = false; + CornerStone.item._iPostDraw = false; + } + + int i = 0; + while (i < ActiveItemCount) { + if (ActiveItems[i] == ii) { + DeleteItem(ActiveItems[i], i); + i = 0; + continue; + } + + i++; + } +} + +static bool PutItem(PlayerStruct &player, Point &position) +{ + if (ActiveItemCount >= MAXITEMS) + return false; + + Direction d = GetDirection(player.position.tile, position); + + if (position.WalkingDistance(player.position.tile) > 1) { + position = player.position.tile + d; + } + if (CanPut(position)) + return true; + + position = player.position.tile + left[d]; + if (CanPut(position)) + return true; + + position = player.position.tile + right[d]; + if (CanPut(position)) + return true; + + for (int l = 1; l < 50; l++) { + for (int j = -l; j <= l; j++) { + int yp = j + player.position.tile.y; + for (int i = -l; i <= l; i++) { + int xp = i + player.position.tile.x; + if (!CanPut({ xp, yp })) + continue; + + position = { xp, yp }; + return true; + } + } + } + + return false; +} + +static bool CanUseStaff(ItemStruct &staff, spell_id spell) +{ + return !staff.isEmpty() + && (staff._iMiscId == IMISC_STAFF || staff._iMiscId == IMISC_UNIQUE) + && staff._iSpell == spell + && staff._iCharges > 0; +} + +void StartGoldDrop() +{ + initialDropGoldIndex = pcursinvitem; + + auto &myPlayer = Players[MyPlayerId]; + + if (pcursinvitem <= INVITEM_INV_LAST) + initialDropGoldValue = myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]._ivalue; + else + initialDropGoldValue = myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]._ivalue; + + dropGoldFlag = true; + dropGoldValue = 0; + + if (talkflag) + control_reset_talk(); +} + +} // namespace + +void FreeInvGFX() +{ + pInvCels = std::nullopt; +} + +void InitInv() +{ + switch (Players[MyPlayerId]._pClass) { + case HeroClass::Warrior: + case HeroClass::Barbarian: + pInvCels = LoadCel("Data\\Inv\\Inv.CEL", SPANEL_WIDTH); + break; + case HeroClass::Rogue: + case HeroClass::Bard: + pInvCels = LoadCel("Data\\Inv\\Inv_rog.CEL", SPANEL_WIDTH); + break; + case HeroClass::Sorcerer: + pInvCels = LoadCel("Data\\Inv\\Inv_Sor.CEL", SPANEL_WIDTH); + break; + case HeroClass::Monk: + pInvCels = LoadCel(!gbIsSpawn ? "Data\\Inv\\Inv_Sor.CEL" : "Data\\Inv\\Inv.CEL", SPANEL_WIDTH); + break; + } + + invflag = false; + drawsbarflag = false; +} + +void DrawInv(const Surface &out) +{ + CelDrawTo(out, { RIGHT_PANEL_X, 351 }, *pInvCels, 1); + + Size slotSize[] = { + { 2, 2 }, //head + { 1, 1 }, //left ring + { 1, 1 }, //right ring + { 1, 1 }, //amulet + { 2, 3 }, //left hand + { 2, 3 }, //right hand + { 2, 3 }, // chest + }; + + Point slotPos[] = { + { 133, 59 }, //head + { 48, 205 }, //left ring + { 249, 205 }, //right ring + { 205, 60 }, //amulet + { 17, 160 }, //left hand + { 248, 160 }, //right hand + { 133, 160 }, // chest + }; + + auto &myPlayer = Players[MyPlayerId]; + + for (int slot = INVLOC_HEAD; slot < NUM_INVLOC; slot++) { + if (!myPlayer.InvBody[slot].isEmpty()) { + int screenX = slotPos[slot].x; + int screenY = slotPos[slot].y; + InvDrawSlotBack(out, { RIGHT_PANEL_X + screenX, screenY }, { slotSize[slot].width * InventorySlotSizeInPixels.width, slotSize[slot].height * InventorySlotSizeInPixels.height }); + + int frame = myPlayer.InvBody[slot]._iCurs + CURSOR_FIRSTITEM; + + auto frameSize = GetInvItemSize(frame); + + // calc item offsets for weapons smaller than 2x3 slots + if (slot == INVLOC_HAND_LEFT) { + screenX += frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0; + screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX; + } else if (slot == INVLOC_HAND_RIGHT) { + screenX += frameSize.width == InventorySlotSizeInPixels.width ? (INV_SLOT_HALF_SIZE_PX - 1) : 1; + screenY += frameSize.height == (3 * InventorySlotSizeInPixels.height) ? 0 : -INV_SLOT_HALF_SIZE_PX; + } + + const auto &cel = GetInvItemSprite(frame); + const int celFrame = GetInvItemFrame(frame); + const Point position { RIGHT_PANEL_X + screenX, screenY }; + + if (pcursinvitem == slot) { + CelBlitOutlineTo(out, GetOutlineColor(myPlayer.InvBody[slot], true), position, cel, celFrame, false); + } + + CelDrawItem(myPlayer.InvBody[slot], out, position, cel, celFrame); + + if (slot == INVLOC_HAND_LEFT) { + if (myPlayer.InvBody[slot]._iLoc == ILOC_TWOHAND) { + if (myPlayer._pClass != HeroClass::Barbarian + || (myPlayer.InvBody[slot]._itype != ITYPE_SWORD + && myPlayer.InvBody[slot]._itype != ITYPE_MACE)) { + InvDrawSlotBack(out, { RIGHT_PANEL_X + slotPos[INVLOC_HAND_RIGHT].x, slotPos[INVLOC_HAND_RIGHT].y }, { slotSize[INVLOC_HAND_RIGHT].width * InventorySlotSizeInPixels.width, slotSize[INVLOC_HAND_RIGHT].height * InventorySlotSizeInPixels.height }); + LightTableIndex = 0; + cel_transparency_active = true; + + const int dstX = RIGHT_PANEL_X + slotPos[INVLOC_HAND_RIGHT].x + (frameSize.width == InventorySlotSizeInPixels.width ? INV_SLOT_HALF_SIZE_PX : 0) - 1; + const int dstY = slotPos[INVLOC_HAND_RIGHT].y; + CelClippedBlitLightTransTo(out, { dstX, dstY }, cel, celFrame); + + cel_transparency_active = false; + } + } + } + } + } + + for (int i = 0; i < NUM_INV_GRID_ELEM; i++) { + if (myPlayer.InvGrid[i] != 0) { + InvDrawSlotBack( + out, + InvRect[i + SLOTXY_INV_FIRST] + Displacement { RIGHT_PANEL_X, -1 }, + InventorySlotSizeInPixels); + } + } + + for (int j = 0; j < NUM_INV_GRID_ELEM; j++) { + if (myPlayer.InvGrid[j] > 0) { // first slot of an item + int ii = myPlayer.InvGrid[j] - 1; + int frame = myPlayer.InvList[ii]._iCurs + CURSOR_FIRSTITEM; + + const auto &cel = GetInvItemSprite(frame); + const int celFrame = GetInvItemFrame(frame); + const Point position { InvRect[j + SLOTXY_INV_FIRST].x + RIGHT_PANEL_X, InvRect[j + SLOTXY_INV_FIRST].y - 1 }; + if (pcursinvitem == ii + INVITEM_INV_FIRST) { + CelBlitOutlineTo( + out, + GetOutlineColor(myPlayer.InvList[ii], true), + position, + cel, celFrame, false); + } + + CelDrawItem( + myPlayer.InvList[ii], + out, + position, + cel, celFrame); + } + } +} + +void DrawInvBelt(const Surface &out) +{ + if (talkflag) { + return; + } + + DrawPanelBox(out, { 205, 21, 232, 28 }, { PANEL_X + 205, PANEL_Y + 5 }); + + auto &myPlayer = Players[MyPlayerId]; + + for (int i = 0; i < MAXBELTITEMS; i++) { + if (myPlayer.SpdList[i].isEmpty()) { + continue; + } + + const Point position { InvRect[i + SLOTXY_BELT_FIRST].x + PANEL_X, InvRect[i + SLOTXY_BELT_FIRST].y + PANEL_Y - 1 }; + InvDrawSlotBack(out, position, InventorySlotSizeInPixels); + int frame = myPlayer.SpdList[i]._iCurs + CURSOR_FIRSTITEM; + + const auto &cel = GetInvItemSprite(frame); + const int celFrame = GetInvItemFrame(frame); + + if (pcursinvitem == i + INVITEM_BELT_FIRST) { + if (!sgbControllerActive || invflag) { + CelBlitOutlineTo(out, GetOutlineColor(myPlayer.SpdList[i], true), position, cel, celFrame, false); + } + } + + CelDrawItem(myPlayer.SpdList[i], out, position, cel, celFrame); + + if (AllItemsList[myPlayer.SpdList[i].IDidx].iUsable + && myPlayer.SpdList[i]._iStatFlag + && myPlayer.SpdList[i]._itype != ITYPE_GOLD) { + snprintf(tempstr, sizeof(tempstr) / sizeof(*tempstr), "%i", i + 1); + DrawString(out, tempstr, { position, InventorySlotSizeInPixels }, UIS_SILVER | UIS_RIGHT); + } + } +} + +/** + * @brief Checks whether the given item can be placed on the specified player's belt. Returns 'True' when the item can be placed + * on belt slots and the player has at least one empty slot in his belt. + * If 'persistItem' is 'True', the item is also placed in the belt. + * @param player The player on whose belt will be checked. + * @param item The item to be checked. + * @param persistItem Pass 'True' to actually place the item in the belt. The default is 'False'. + * @return 'True' in case the item can be placed on the player's belt and 'False' otherwise. + */ +bool AutoPlaceItemInBelt(PlayerStruct &player, const ItemStruct &item, bool persistItem) +{ + if (!CanBePlacedOnBelt(item)) { + return false; + } + + for (auto &beltItem : player.SpdList) { + if (beltItem.isEmpty()) { + if (persistItem) { + beltItem = item; + player.CalcScrolls(); + drawsbarflag = true; + } + + return true; + } + } + + return false; +} + +/** + * @brief Automatically attempts to equip the specified item in the most appropriate location in the player's body. + * @note On success, this will broadcast an equipment_change event to let other players know about the equipment change. + * @param playerId The player number whose inventory will be checked for compatibility with the item. + * @param item The item to equip. + * @param persistItem Indicates whether or not the item should be persisted in the player's body. Pass 'False' to check + * whether the player can equip the item but you don't want the item to actually be equipped. 'True' by default. + * @return 'True' if the item was equipped and 'False' otherwise. + */ +bool AutoEquip(int playerId, const ItemStruct &item, bool persistItem) +{ + if (!CanEquip(item)) { + return false; + } + + for (int bodyLocation = INVLOC_HEAD; bodyLocation < NUM_INVLOC; bodyLocation++) { + if (AutoEquip(playerId, item, (inv_body_loc)bodyLocation, persistItem)) { + return true; + } + } + + return false; +} + +/** + * @brief Checks whether or not auto-equipping behavior is enabled for the given player and item. + * @param player The player to check. + * @param item The item to check. + * @return 'True' if auto-equipping behavior is enabled for the player and item and 'False' otherwise. + */ +bool AutoEquipEnabled(const PlayerStruct &player, const ItemStruct &item) +{ + if (item.isWeapon()) { + // Monk can use unarmed attack as an encouraged option, thus we do not automatically equip weapons on him so as to not + // annoy players who prefer that playstyle. + return player._pClass != HeroClass::Monk && sgOptions.Gameplay.bAutoEquipWeapons; + } + + if (item.isArmor()) { + return sgOptions.Gameplay.bAutoEquipArmor; + } + + if (item.isHelm()) { + return sgOptions.Gameplay.bAutoEquipHelms; + } + + if (item.isShield()) { + return sgOptions.Gameplay.bAutoEquipShields; + } + + if (item.isJewelry()) { + return sgOptions.Gameplay.bAutoEquipJewelry; + } + + return true; +} + +/** + * @brief Checks whether the given item can be placed on the specified player's inventory. + * If 'persistItem' is 'True', the item is also placed in the inventory. + * @param item The item to be checked. + * @param persistItem Pass 'True' to actually place the item in the inventory. The default is 'False'. + * @return 'True' in case the item can be placed on the player's inventory and 'False' otherwise. + */ +bool AutoPlaceItemInInventory(PlayerStruct &player, const ItemStruct &item, bool persistItem) +{ + Size itemSize = GetInventorySize(item); + + if (itemSize.height == 1) { + for (int i = 30; i <= 39; i++) { + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + return true; + } + for (int x = 9; x >= 0; x--) { + for (int y = 2; y >= 0; y--) { + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + return true; + } + } + return false; + } + + if (itemSize.height == 2) { + for (int x = 10 - itemSize.width; x >= 0; x -= itemSize.width) { + for (int y = 0; y < 3; y++) { + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + return true; + } + } + if (itemSize.width == 2) { + for (int x = 7; x >= 0; x -= 2) { + for (int y = 0; y < 3; y++) { + if (AutoPlaceItemInInventorySlot(player, 10 * y + x, item, persistItem)) + return true; + } + } + } + return false; + } + + if (itemSize == Size { 1, 3 }) { + for (int i = 0; i < 20; i++) { + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + return true; + } + return false; + } + + if (itemSize == Size { 2, 3 }) { + for (int i = 0; i < 9; i++) { + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + return true; + } + + for (int i = 10; i < 19; i++) { + if (AutoPlaceItemInInventorySlot(player, i, item, persistItem)) + return true; + } + return false; + } + + app_fatal("Unknown item size: %ix%i", itemSize.width, itemSize.height); +} + +/** + * @brief Checks whether the given item can be placed on the specified player's inventory slot. + * If 'persistItem' is 'True', the item is also placed in the inventory slot. + * @param slotIndex The 0-based index of the slot to put the item on. + * @param item The item to be checked. + * @param persistItem Pass 'True' to actually place the item in the inventory slot. The default is 'False'. + * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. + */ +bool AutoPlaceItemInInventorySlot(PlayerStruct &player, int slotIndex, const ItemStruct &item, bool persistItem) +{ + int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; + + Size itemSize = GetInventorySize(item); + for (int j = 0; j < itemSize.height; j++) { + if (yy >= NUM_INV_GRID_ELEM) { + return false; + } + int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; + for (int i = 0; i < itemSize.width; i++) { + if (xx >= 10 || player.InvGrid[xx + yy] != 0) { + return false; + } + xx++; + } + yy += 10; + } + + if (persistItem) { + player.InvList[player._pNumInv] = player.HoldItem; + player._pNumInv++; + + AddItemToInvGrid(player, slotIndex, player._pNumInv, itemSize); + player.CalcScrolls(); + } + + return true; +} + +bool GoldAutoPlace(PlayerStruct &player) +{ + bool done = false; + + for (int i = 0; i < player._pNumInv && !done; i++) { + if (player.InvList[i]._itype != ITYPE_GOLD) + continue; + if (player.InvList[i]._ivalue >= MaxGold) + continue; + + player.InvList[i]._ivalue += player.HoldItem._ivalue; + if (player.InvList[i]._ivalue > MaxGold) { + player.HoldItem._ivalue = player.InvList[i]._ivalue - MaxGold; + SetPlrHandGoldCurs(&player.HoldItem); + player.InvList[i]._ivalue = MaxGold; + if (gbIsHellfire) + GetPlrHandSeed(&player.HoldItem); + } else { + player.HoldItem._ivalue = 0; + done = true; + } + + SetPlrHandGoldCurs(&player.InvList[i]); + player._pGold = CalculateGold(player); + } + + for (int i = 39; i >= 30 && !done; i--) { + done = GoldAutoPlaceInInventorySlot(player, i); + } + for (int x = 9; x >= 0 && !done; x--) { + for (int y = 2; y >= 0 && !done; y--) { + done = GoldAutoPlaceInInventorySlot(player, 10 * y + x); + } + } + + return done; +} + +bool GoldAutoPlaceInInventorySlot(PlayerStruct &player, int slotIndex) +{ + if (player.InvGrid[slotIndex] != 0) { + return false; + } + + int ii = player._pNumInv; + player.InvList[ii] = player.HoldItem; + player._pNumInv++; + player.InvGrid[slotIndex] = player._pNumInv; + GetPlrHandSeed(&player.InvList[ii]); + + int gold = player.HoldItem._ivalue; + if (gold > MaxGold) { + gold -= MaxGold; + player.HoldItem._ivalue = gold; + GetPlrHandSeed(&player.HoldItem); + player.InvList[ii]._ivalue = MaxGold; + return false; + } + + player.HoldItem._ivalue = 0; + player._pGold = CalculateGold(player); + NewCursor(CURSOR_HAND); - if (!automaticMove || automaticallyMoved) { - beltItem._itype = ITYPE_NONE; - drawsbarflag = true; - } - } - } + return true; +} - if (!holdItem.isEmpty()) { - if (holdItem._itype == ITYPE_GOLD) { - player._pGold = CalculateGold(player); - } +void CheckInvSwap(int pnum, BYTE bLoc, int idx, uint16_t wCI, int seed, bool bId, uint32_t dwBuff) +{ + memset(&Items[MAXITEMS], 0, sizeof(*Items)); + RecreateItem(MAXITEMS, idx, wCI, seed, 0, (dwBuff & CF_HELLFIRE) != 0); - CalcPlrInv(pnum, true); - CheckItemStats(player); + auto &player = Players[pnum]; - if (pnum == MyPlayerId) { - if (automaticallyEquipped) { - PlaySFX(ItemInvSnds[ItemCAnimTbl[holdItem._iCurs]]); - } else if (!automaticMove || automaticallyMoved) { - PlaySFX(IS_IGRAB); - } + player.HoldItem = Items[MAXITEMS]; - if (automaticMove) { - if (!automaticallyMoved) { - if (CanBePlacedOnBelt(holdItem) || automaticallyUnequip) { - player.SaySpecific(HeroSpeech::IHaveNoRoom); - } else { - player.SaySpecific(HeroSpeech::ICantDoThat); - } - } + if (bId) { + player.HoldItem._iIdentified = true; + } - holdItem._itype = ITYPE_NONE; - } else { - NewCursor(holdItem._iCurs + CURSOR_FIRSTITEM); - if (!IsHardwareCursor()) { - // For a hardware cursor, we set the "hot point" to the center of the item instead. - SetCursorPos(cursorPosition.x - (cursW / 2), cursorPosition.y - (cursH / 2)); - } - } + if (bLoc < NUM_INVLOC) { + player.InvBody[bLoc] = player.HoldItem; + + if (bLoc == INVLOC_HAND_LEFT && player.HoldItem._iLoc == ILOC_TWOHAND) { + player.InvBody[INVLOC_HAND_RIGHT]._itype = ITYPE_NONE; + } else if (bLoc == INVLOC_HAND_RIGHT && player.HoldItem._iLoc == ILOC_TWOHAND) { + player.InvBody[INVLOC_HAND_LEFT]._itype = ITYPE_NONE; } } + + CalcPlrInv(pnum, true); } void inv_update_rem_item(int pnum, BYTE iv) @@ -1415,129 +1602,6 @@ void CheckItemStats(PlayerStruct &player) } } -static void CheckBookLevel(PlayerStruct &player) -{ - if (player.HoldItem._iMiscId != IMISC_BOOK) - return; - - player.HoldItem._iMinMag = spelldata[player.HoldItem._iSpell].sMinInt; - int8_t spellLevel = player._pSplLvl[player.HoldItem._iSpell]; - while (spellLevel != 0) { - player.HoldItem._iMinMag += 20 * player.HoldItem._iMinMag / 100; - spellLevel--; - if (player.HoldItem._iMinMag + 20 * player.HoldItem._iMinMag / 100 > 255) { - player.HoldItem._iMinMag = -1; - spellLevel = 0; - } - } -} - -static void CheckNaKrulNotes(PlayerStruct &player) -{ - int idx = player.HoldItem.IDidx; - _item_indexes notes[] = { IDI_NOTE1, IDI_NOTE2, IDI_NOTE3 }; - - if (idx != IDI_NOTE1 && idx != IDI_NOTE2 && idx != IDI_NOTE3) { - return; - } - - for (auto note : notes) { - if (idx != note && !player.HasItem(note)) { - return; // the player doesn't have all notes - } - } - - Players[MyPlayerId].Say(HeroSpeech::JustWhatIWasLookingFor, 10); - - for (auto note : notes) { - if (idx != note) { - player.TryRemoveInvItemById(note); - } - } - - int itemNum = ActiveItems[0]; - ItemStruct tmp = Items[itemNum]; - memset(&Items[itemNum], 0, sizeof(*Items)); - GetItemAttrs(itemNum, IDI_FULLNOTE, 16); - SetupItem(itemNum); - player.HoldItem = Items[itemNum]; - Items[itemNum] = tmp; -} - -static void CheckQuestItem(PlayerStruct &player) -{ - auto &myPlayer = Players[MyPlayerId]; - - if (player.HoldItem.IDidx == IDI_OPTAMULET && Quests[Q_BLIND]._qactive == QUEST_ACTIVE) - Quests[Q_BLIND]._qactive = QUEST_DONE; - - if (player.HoldItem.IDidx == IDI_MUSHROOM && Quests[Q_MUSHROOM]._qactive == QUEST_ACTIVE && Quests[Q_MUSHROOM]._qvar1 == QS_MUSHSPAWNED) { - player.Say(HeroSpeech::NowThatsOneBigMushroom, 10); // BUGFIX: Voice for this quest might be wrong in MP - Quests[Q_MUSHROOM]._qvar1 = QS_MUSHPICKED; - } - - if (player.HoldItem.IDidx == IDI_ANVIL && Quests[Q_ANVIL]._qactive != QUEST_NOTAVAIL) { - if (Quests[Q_ANVIL]._qactive == QUEST_INIT) { - Quests[Q_ANVIL]._qactive = QUEST_ACTIVE; - } - if (Quests[Q_ANVIL]._qlog) { - myPlayer.Say(HeroSpeech::INeedToGetThisToGriswold, 10); - } - } - - if (player.HoldItem.IDidx == IDI_GLDNELIX && Quests[Q_VEIL]._qactive != QUEST_NOTAVAIL) { - myPlayer.Say(HeroSpeech::INeedToGetThisToLachdanan, 30); - } - - if (player.HoldItem.IDidx == IDI_ROCK && Quests[Q_ROCK]._qactive != QUEST_NOTAVAIL) { - if (Quests[Q_ROCK]._qactive == QUEST_INIT) { - Quests[Q_ROCK]._qactive = QUEST_ACTIVE; - } - if (Quests[Q_ROCK]._qlog) { - myPlayer.Say(HeroSpeech::ThisMustBeWhatGriswoldWanted, 10); - } - } - - if (player.HoldItem.IDidx == IDI_ARMOFVAL && Quests[Q_BLOOD]._qactive == QUEST_ACTIVE) { - Quests[Q_BLOOD]._qactive = QUEST_DONE; - myPlayer.Say(HeroSpeech::MayTheSpiritOfArkaineProtectMe, 20); - } - - if (player.HoldItem.IDidx == IDI_MAPOFDOOM) { - Quests[Q_GRAVE]._qlog = false; - Quests[Q_GRAVE]._qactive = QUEST_ACTIVE; - Quests[Q_GRAVE]._qvar1 = 1; - Players[MyPlayerId].Say(HeroSpeech::UhHuh, 10); - } - - CheckNaKrulNotes(player); -} - -void CleanupItems(ItemStruct *item, int ii) -{ - dItem[item->position.x][item->position.y] = 0; - - if (currlevel == 21 && item->position == CornerStone.position) { - CornerStone.item._itype = ITYPE_NONE; - CornerStone.item._iSelFlag = 0; - CornerStone.item.position = { 0, 0 }; - CornerStone.item._iAnimFlag = false; - CornerStone.item._iIdentified = false; - CornerStone.item._iPostDraw = false; - } - - int i = 0; - while (i < ActiveItemCount) { - if (ActiveItems[i] == ii) { - DeleteItem(ActiveItems[i], i); - i = 0; - continue; - } - - i++; - } -} - void InvGetItem(int pnum, ItemStruct *item, int ii) { if (dropGoldFlag) { @@ -1724,44 +1788,6 @@ bool TryInvPut() return CanPut(myPlayer.position.tile); } -static bool PutItem(PlayerStruct &player, Point &position) -{ - if (ActiveItemCount >= MAXITEMS) - return false; - - Direction d = GetDirection(player.position.tile, position); - - if (position.WalkingDistance(player.position.tile) > 1) { - position = player.position.tile + d; - } - if (CanPut(position)) - return true; - - position = player.position.tile + left[d]; - if (CanPut(position)) - return true; - - position = player.position.tile + right[d]; - if (CanPut(position)) - return true; - - for (int l = 1; l < 50; l++) { - for (int j = -l; j <= l; j++) { - int yp = j + player.position.tile.y; - for (int i = -l; i <= l; i++) { - int xp = i + player.position.tile.x; - if (!CanPut({ xp, yp })) - continue; - - position = { xp, yp }; - return true; - } - } - } - - return false; -} - int InvPutItem(PlayerStruct &player, Point position) { if (!PutItem(player, position)) @@ -1993,14 +2019,6 @@ bool UseScroll() return false; } -static bool CanUseStaff(ItemStruct &staff, spell_id spell) -{ - return !staff.isEmpty() - && (staff._iMiscId == IMISC_STAFF || staff._iMiscId == IMISC_UNIQUE) - && staff._iSpell == spell - && staff._iCharges > 0; -} - void UseStaffCharge(PlayerStruct &player) { auto &staff = player.InvBody[INVLOC_HAND_LEFT]; @@ -2023,24 +2041,6 @@ bool UseStaff() return CanUseStaff(myPlayer.InvBody[INVLOC_HAND_LEFT], myPlayer._pRSpell); } -void StartGoldDrop() -{ - initialDropGoldIndex = pcursinvitem; - - auto &myPlayer = Players[MyPlayerId]; - - if (pcursinvitem <= INVITEM_INV_LAST) - initialDropGoldValue = myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]._ivalue; - else - initialDropGoldValue = myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]._ivalue; - - dropGoldFlag = true; - dropGoldValue = 0; - - if (talkflag) - control_reset_talk(); -} - bool UseInvItem(int pnum, int cii) { int c; diff --git a/Source/items.cpp b/Source/items.cpp index 60e7d17c6..b957d0c7c 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -30,144 +30,826 @@ #include "utils/stdcompat/algorithm.hpp" namespace devilution { + +/** Contains the items on ground in the current game. */ +ItemStruct Items[MAXITEMS + 1]; +int ActiveItems[MAXITEMS]; +int ActiveItemCount; +int AvailableItems[MAXITEMS]; +bool ShowUniqueItemInfoBox; +CornerStoneStruct CornerStone; +bool UniqueItemFlags[128]; +int MaxGold = GOLD_MAX_LIMIT; + +/** Maps from item_cursor_graphic to in-memory item type. */ +BYTE ItemCAnimTbl[] = { + 20, 16, 16, 16, 4, 4, 4, 12, 12, 12, + 12, 12, 12, 12, 12, 21, 21, 25, 12, 28, + 28, 28, 38, 38, 38, 32, 38, 38, 38, 24, + 24, 26, 2, 25, 22, 23, 24, 25, 27, 27, + 29, 0, 0, 0, 12, 12, 12, 12, 12, 0, + 8, 8, 0, 8, 8, 8, 8, 8, 8, 6, + 8, 8, 8, 6, 8, 8, 6, 8, 8, 6, + 6, 6, 8, 8, 8, 5, 9, 13, 13, 13, + 5, 5, 5, 15, 5, 5, 18, 18, 18, 30, + 5, 5, 14, 5, 14, 13, 16, 18, 5, 5, + 7, 1, 3, 17, 1, 15, 10, 14, 3, 11, + 8, 0, 1, 7, 0, 7, 15, 7, 3, 3, + 3, 6, 6, 11, 11, 11, 31, 14, 14, 14, + 6, 6, 7, 3, 8, 14, 0, 14, 14, 0, + 33, 1, 1, 1, 1, 1, 7, 7, 7, 14, + 14, 17, 17, 17, 0, 34, 1, 0, 3, 17, + 8, 8, 6, 1, 3, 3, 11, 3, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 35, 39, 36, + 36, 36, 37, 38, 38, 38, 38, 38, 41, 42, + 8, 8, 8, 17, 0, 6, 8, 11, 11, 3, + 3, 1, 6, 6, 6, 1, 8, 6, 11, 3, + 6, 8, 1, 6, 6, 17, 40, 0, 0 +}; + +/** Maps of drop sounds effect of placing the item in the inventory. */ +_sfx_id ItemInvSnds[] = { + IS_IHARM, + IS_IAXE, + IS_IPOT, + IS_IBOW, + IS_GOLD, + IS_ICAP, + IS_ISWORD, + IS_ISHIEL, + IS_ISWORD, + IS_IROCK, + IS_IAXE, + IS_ISTAF, + IS_IRING, + IS_ICAP, + IS_ILARM, + IS_ISHIEL, + IS_ISCROL, + IS_IHARM, + IS_IBOOK, + IS_IHARM, + IS_IPOT, + IS_IPOT, + IS_IPOT, + IS_IPOT, + IS_IPOT, + IS_IPOT, + IS_IPOT, + IS_IPOT, + IS_IBODY, + IS_IBODY, + IS_IMUSH, + IS_ISIGN, + IS_IBLST, + IS_IANVL, + IS_ISTAF, + IS_IROCK, + IS_ISCROL, + IS_ISCROL, + IS_IROCK, + IS_IMUSH, + IS_IHARM, + IS_ILARM, + IS_ILARM, +}; + namespace { -std::optional itemanims[ITEMTYPES]; -int RndPL(int param1, int param2) -{ - return param1 + GenerateRnd(param2 - param1 + 1); -} +std::optional itemanims[ITEMTYPES]; -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; -} +enum anim_armor_id : uint8_t { + // clang-format off + AnimIdLightArmor = 0, + AnimIdMediumArmor = 1 << 4, + AnimIdHeavyArmor = 1 << 5, + // clang-format on +}; +ItemStruct curruitem; +ItemGetRecordStruct itemrecord[MAXITEMS]; +bool itemhold[3][3]; +int gnNumGetRecords; -void SaveItemPower(ItemStruct &item, const ItemPower &power) -{ - int r = RndPL(power.param1, power.param2); - int r2; +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") +}; - switch (power.type) { - case IPL_TOHIT: - item._iPLToHit += r; - break; - case IPL_TOHIT_CURSE: - item._iPLToHit -= r; - break; - case IPL_DAMP: - item._iPLDam += r; - break; - case IPL_DAMP_CURSE: - item._iPLDam -= r; - break; - case IPL_DOPPELGANGER: - item._iDamAcFlags |= ISPLHF_DOPPELGANGER; - [[fallthrough]]; - case IPL_TOHIT_DAMP: - r = RndPL(power.param1, power.param2); - item._iPLDam += r; - if (power.param1 == 20) - r2 = RndPL(1, 5); - if (power.param1 == 36) - r2 = RndPL(6, 10); - if (power.param1 == 51) - r2 = RndPL(11, 15); - if (power.param1 == 66) - r2 = RndPL(16, 20); - if (power.param1 == 81) - r2 = RndPL(21, 30); - if (power.param1 == 96) - r2 = RndPL(31, 40); - if (power.param1 == 111) - r2 = RndPL(41, 50); - if (power.param1 == 126) - r2 = RndPL(51, 75); - if (power.param1 == 151) - r2 = RndPL(76, 100); - item._iPLToHit += r2; - break; - case IPL_TOHIT_DAMP_CURSE: - item._iPLDam -= r; - if (power.param1 == 25) - r2 = RndPL(1, 5); - if (power.param1 == 50) - r2 = RndPL(6, 10); - item._iPLToHit -= r2; - break; - case IPL_ACP: - item._iPLAC += r; - break; - case IPL_ACP_CURSE: - item._iPLAC -= r; - break; - case IPL_SETAC: - item._iAC = r; - break; - case IPL_AC_CURSE: - item._iAC -= r; - break; - case IPL_FIRERES: - item._iPLFR += r; - break; - case IPL_LIGHTRES: - item._iPLLR += r; - break; - case IPL_MAGICRES: - item._iPLMR += r; - break; - case IPL_ALLRES: - item._iPLFR = std::max(item._iPLFR + r, 0); - item._iPLLR = std::max(item._iPLLR + r, 0); - item._iPLMR = std::max(item._iPLMR + r, 0); - break; - case IPL_SPLLVLADD: - item._iSplLvlAdd = r; - break; - case IPL_CHARGES: - item._iCharges *= power.param1; - item._iMaxCharges = item._iCharges; - break; - case IPL_SPELL: - item._iSpell = static_cast(power.param1); - item._iCharges = power.param2; - item._iMaxCharges = power.param2; - break; - case IPL_FIREDAM: - item._iFlags |= ISPL_FIREDAM; - item._iFlags &= ~ISPL_LIGHTDAM; - item._iFMinDam = power.param1; - item._iFMaxDam = power.param2; - item._iLMinDam = 0; - item._iLMaxDam = 0; - break; - case IPL_LIGHTDAM: - item._iFlags |= ISPL_LIGHTDAM; - item._iFlags &= ~ISPL_FIREDAM; - item._iLMinDam = power.param1; - item._iLMaxDam = power.param2; - item._iFMinDam = 0; - item._iFMaxDam = 0; - break; - case IPL_STR: - item._iPLStr += r; - break; - case IPL_STR_CURSE: - item._iPLStr -= r; - break; - case IPL_MAG: - item._iPLMag += r; - break; - case IPL_MAG_CURSE: - item._iPLMag -= r; - break; - case IPL_DEX: - item._iPLDex += r; - break; +/** Map of item type .cel file names. */ +const char *const ItemDropNames[] = { + "Armor2", + "Axe", + "FBttle", + "Bow", + "GoldFlip", + "Helmut", + "Mace", + "Shield", + "SwrdFlip", + "Rock", + "Cleaver", + "Staff", + "Ring", + "CrownF", + "LArmor", + "WShield", + "Scroll", + "FPlateAr", + "FBook", + "Food", + "FBttleBB", + "FBttleDY", + "FBttleOR", + "FBttleBR", + "FBttleBL", + "FBttleBY", + "FBttleWH", + "FBttleDB", + "FEar", + "FBrain", + "FMush", + "Innsign", + "Bldstn", + "Fanvil", + "FLazStaf", + "bombs1", + "halfps1", + "wholeps1", + "runes1", + "teddys1", + "cows1", + "donkys1", + "mooses1", +}; +/** Maps of item drop animation length. */ +BYTE ItemAnimLs[] = { + 15, + 13, + 16, + 13, + 10, + 13, + 13, + 13, + 13, + 10, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 13, + 1, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 13, + 12, + 12, + 13, + 13, + 13, + 8, + 10, + 16, + 16, + 10, + 10, + 15, + 15, + 15, +}; +/** Maps of drop sounds effect of dropping the item on ground. */ +_sfx_id ItemDropSnds[] = { + IS_FHARM, + IS_FAXE, + IS_FPOT, + IS_FBOW, + IS_GOLD, + IS_FCAP, + IS_FSWOR, + IS_FSHLD, + IS_FSWOR, + IS_FROCK, + IS_FAXE, + IS_FSTAF, + IS_FRING, + IS_FCAP, + IS_FLARM, + IS_FSHLD, + IS_FSCRL, + IS_FHARM, + IS_FBOOK, + IS_FLARM, + IS_FPOT, + IS_FPOT, + IS_FPOT, + IS_FPOT, + IS_FPOT, + IS_FPOT, + IS_FPOT, + IS_FPOT, + IS_FBODY, + IS_FBODY, + IS_FMUSH, + IS_ISIGN, + IS_FBLST, + IS_FANVL, + IS_FSTAF, + IS_FROCK, + IS_FSCRL, + IS_FSCRL, + IS_FROCK, + IS_FMUSH, + IS_FHARM, + IS_FLARM, + IS_FLARM, +}; +/** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */ +int premiumlvladd[] = { + // clang-format off + -1, + -1, + 0, + 0, + 1, + 2, + // clang-format on +}; +/** Maps from Griswold premium item number to a quality level delta as added to the base quality level. */ +int premiumLvlAddHellfire[] = { + // clang-format off + -1, + -1, + -1, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 3, + 3, + // clang-format on +}; + +static bool IsPrefixValidForItemType(int i, int flgs) +{ + int itemTypes = ItemPrefixes[i].PLIType; + + if (!gbIsHellfire) { + if (i > 82) + return false; + + if (i >= 12 && i <= 20) + itemTypes &= ~PLT_STAFF; + } + + return (flgs & itemTypes) != 0; +} + +static bool IsSuffixValidForItemType(int i, int flgs) +{ + int itemTypes = ItemSuffixes[i].PLIType; + + if (!gbIsHellfire) { + if (i > 94) + return false; + + if ((i >= 0 && i <= 1) + || (i >= 14 && i <= 15) + || (i >= 21 && i <= 22) + || (i >= 34 && i <= 36) + || (i >= 41 && i <= 44) + || (i >= 60 && i <= 63)) + itemTypes &= ~PLT_STAFF; + } + + return (flgs & itemTypes) != 0; +} + +int items_get_currlevel() +{ + int lvl = currlevel; + if (currlevel >= 17 && currlevel <= 20) + lvl = currlevel - 8; + if (currlevel >= 21 && currlevel <= 24) + lvl = currlevel - 7; + + return lvl; +} + +bool ItemPlace(Point position) +{ + if (dMonster[position.x][position.y] != 0) + return false; + if (dPlayer[position.x][position.y] != 0) + return false; + if (dItem[position.x][position.y] != 0) + return false; + if (dObject[position.x][position.y] != 0) + return false; + if ((dFlags[position.x][position.y] & BFLAG_POPULATED) != 0) + return false; + if (nSolidTable[dPiece[position.x][position.y]]) + return false; + + return true; +} + +Point GetRandomAvailableItemPosition() +{ + Point position = {}; + do { + position = Point { GenerateRnd(80), GenerateRnd(80) } + Displacement { 16, 16 }; + } while (!ItemPlace(position)); + + return position; +} + +void AddInitItems() +{ + int curlv = items_get_currlevel(); + int rnd = GenerateRnd(3) + 3; + for (int 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 SpawnNote() +{ + int id; + + switch (currlevel) { + case 22: + id = IDI_NOTE2; + break; + case 23: + id = IDI_NOTE3; + break; + default: + id = IDI_NOTE1; + break; + } + + Point position = GetRandomAvailableItemPosition(); + SpawnQuestItem(id, position, 0, 1); +} + +void CalcSelfItems(PlayerStruct &player) +{ + int sa = 0; + int ma = 0; + int da = 0; + ItemStruct *pi = player.InvBody; + for (int 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; + } + } + } + + bool changeflag; + do { + changeflag = false; + pi = player.InvBody; + for (int i = 0; i < NUM_INVLOC; i++, pi++) { + if (!pi->isEmpty() && pi->_iStatFlag) { + bool sf = true; + if (sa + player._pBaseStr < pi->_iMinStr) + sf = false; + if (ma + player._pBaseMag < pi->_iMinMag) + sf = false; + if (da + player._pBaseDex < pi->_iMinDex) + sf = false; + if (!sf) { + changeflag = true; + pi->_iStatFlag = false; + if (pi->_iIdentified) { + sa -= pi->_iPLStr; + ma -= pi->_iPLMag; + da -= pi->_iPLDex; + } + } + } + } + } while (changeflag); +} + +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 WitchBookLevel(int ii) +{ + if (witchitem[ii]._iMiscId != IMISC_BOOK) + return; + witchitem[ii]._iMinMag = spelldata[witchitem[ii]._iSpell].sMinInt; + int8_t spellLevel = Players[MyPlayerId]._pSplLvl[witchitem[ii]._iSpell]; + while (spellLevel > 0) { + witchitem[ii]._iMinMag += 20 * witchitem[ii]._iMinMag / 100; + spellLevel--; + if (witchitem[ii]._iMinMag + 20 * witchitem[ii]._iMinMag / 100 > 255) { + witchitem[ii]._iMinMag = 255; + spellLevel = 0; + } + } +} + +bool StoreStatOk(ItemStruct *h) +{ + const auto &myPlayer = Players[MyPlayerId]; + + if (myPlayer._pStrength < h->_iMinStr) + return false; + if (myPlayer._pMagic < h->_iMinMag) + return false; + if (myPlayer._pDexterity < h->_iMinDex) + return false; + + return true; +} + +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; + int8_t spellLevel = player._pSplLvl[player.InvList[i]._iSpell]; + + while (spellLevel != 0) { + player.InvList[i]._iMinMag += 20 * player.InvList[i]._iMinMag / 100; + spellLevel--; + if (player.InvList[i]._iMinMag + 20 * player.InvList[i]._iMinMag / 100 > 255) { + player.InvList[i]._iMinMag = 255; + spellLevel = 0; + } + } + player.InvList[i]._iStatFlag = ItemMinStats(player, &player.InvList[i]); + } + } +} + +void SetPlrHandSeed(ItemStruct *h, int iseed) +{ + h->_iSeed = iseed; +} + +static bool GetItemSpace(Point position, int8_t inum) +{ + int xx = 0; + int yy = 0; + for (int j = position.y - 1; j <= position.y + 1; j++) { + xx = 0; + for (int i = position.x - 1; i <= position.x + 1; i++) { + itemhold[xx][yy] = ItemSpaceOk({ i, j }); + xx++; + } + yy++; + } + + bool savail = false; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { // NOLINT(modernize-loop-convert) + if (itemhold[i][j]) + savail = true; + } + } + + int rs = GenerateRnd(15) + 1; + + if (!savail) + return false; + + xx = 0; + yy = 0; + while (rs > 0) { + if (itemhold[xx][yy]) + rs--; + if (rs <= 0) + continue; + xx++; + if (xx != 3) + continue; + xx = 0; + yy++; + if (yy == 3) + yy = 0; + } + + xx += position.x - 1; + yy += position.y - 1; + Items[inum].position = { xx, yy }; + dItem[xx][yy] = inum + 1; + + return true; +} + +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++) { + Displacement offset = { i, j }; + positionToCheck = position + offset; + if (!ItemSpaceOk(positionToCheck)) + continue; + Items[inum].position = positionToCheck; + dItem[positionToCheck.x][positionToCheck.y] = inum + 1; + return; + } + } + } +} + +void CalcItemValue(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(s)); + if (sLevel != -1 && lvl >= sLevel) { + rv--; + bs = static_cast(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; +} + +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(ItemStruct &item, const ItemPower &power) +{ + int r = RndPL(power.param1, power.param2); + int r2; + + switch (power.type) { + case IPL_TOHIT: + item._iPLToHit += r; + break; + case IPL_TOHIT_CURSE: + item._iPLToHit -= r; + break; + case IPL_DAMP: + item._iPLDam += r; + break; + case IPL_DAMP_CURSE: + item._iPLDam -= r; + break; + case IPL_DOPPELGANGER: + item._iDamAcFlags |= ISPLHF_DOPPELGANGER; + [[fallthrough]]; + case IPL_TOHIT_DAMP: + r = RndPL(power.param1, power.param2); + item._iPLDam += r; + if (power.param1 == 20) + r2 = RndPL(1, 5); + if (power.param1 == 36) + r2 = RndPL(6, 10); + if (power.param1 == 51) + r2 = RndPL(11, 15); + if (power.param1 == 66) + r2 = RndPL(16, 20); + if (power.param1 == 81) + r2 = RndPL(21, 30); + if (power.param1 == 96) + r2 = RndPL(31, 40); + if (power.param1 == 111) + r2 = RndPL(41, 50); + if (power.param1 == 126) + r2 = RndPL(51, 75); + if (power.param1 == 151) + r2 = RndPL(76, 100); + item._iPLToHit += r2; + break; + case IPL_TOHIT_DAMP_CURSE: + item._iPLDam -= r; + if (power.param1 == 25) + r2 = RndPL(1, 5); + if (power.param1 == 50) + r2 = RndPL(6, 10); + item._iPLToHit -= r2; + break; + case IPL_ACP: + item._iPLAC += r; + break; + case IPL_ACP_CURSE: + item._iPLAC -= r; + break; + case IPL_SETAC: + item._iAC = r; + break; + case IPL_AC_CURSE: + item._iAC -= r; + break; + case IPL_FIRERES: + item._iPLFR += r; + break; + case IPL_LIGHTRES: + item._iPLLR += r; + break; + case IPL_MAGICRES: + item._iPLMR += r; + break; + case IPL_ALLRES: + item._iPLFR = std::max(item._iPLFR + r, 0); + item._iPLLR = std::max(item._iPLLR + r, 0); + item._iPLMR = std::max(item._iPLMR + r, 0); + break; + case IPL_SPLLVLADD: + item._iSplLvlAdd = r; + break; + case IPL_CHARGES: + item._iCharges *= power.param1; + item._iMaxCharges = item._iCharges; + break; + case IPL_SPELL: + item._iSpell = static_cast(power.param1); + item._iCharges = power.param2; + item._iMaxCharges = power.param2; + break; + case IPL_FIREDAM: + item._iFlags |= ISPL_FIREDAM; + item._iFlags &= ~ISPL_LIGHTDAM; + item._iFMinDam = power.param1; + item._iFMaxDam = power.param2; + item._iLMinDam = 0; + item._iLMaxDam = 0; + break; + case IPL_LIGHTDAM: + item._iFlags |= ISPL_LIGHTDAM; + item._iFlags &= ~ISPL_FIREDAM; + item._iLMinDam = power.param1; + item._iLMaxDam = power.param2; + item._iFMinDam = 0; + item._iFMaxDam = 0; + break; + case IPL_STR: + item._iPLStr += r; + break; + case IPL_STR_CURSE: + item._iPLStr -= r; + break; + case IPL_MAG: + item._iPLMag += r; + break; + case IPL_MAG_CURSE: + item._iPLMag -= r; + break; + case IPL_DEX: + item._iPLDex += r; + break; case IPL_DEX_CURSE: item._iPLDex -= r; break; @@ -183,550 +865,1638 @@ void SaveItemPower(ItemStruct &item, const ItemPower &power) item._iPLDex += r; item._iPLVit += r; break; - case IPL_ATTRIBS_CURSE: - item._iPLStr -= r; - item._iPLMag -= r; - item._iPLDex -= r; - item._iPLVit -= r; + case IPL_ATTRIBS_CURSE: + item._iPLStr -= r; + item._iPLMag -= r; + item._iPLDex -= r; + item._iPLVit -= r; + break; + case IPL_GETHIT_CURSE: + item._iPLGetHit += r; + break; + case IPL_GETHIT: + item._iPLGetHit -= r; + break; + case IPL_LIFE: + item._iPLHP += r << 6; + break; + case IPL_LIFE_CURSE: + item._iPLHP -= r << 6; + break; + case IPL_MANA: + item._iPLMana += r << 6; + drawmanaflag = true; + break; + case IPL_MANA_CURSE: + item._iPLMana -= r << 6; + drawmanaflag = true; + break; + case IPL_DUR: + r2 = r * item._iMaxDur / 100; + item._iMaxDur += r2; + item._iDurability += r2; + break; + case IPL_CRYSTALLINE: + item._iPLDam += 140 + r * 2; + [[fallthrough]]; + case IPL_DUR_CURSE: + item._iMaxDur -= r * item._iMaxDur / 100; + item._iMaxDur = std::max(item._iMaxDur, 1); + + item._iDurability = item._iMaxDur; + break; + case IPL_INDESTRUCTIBLE: + item._iDurability = DUR_INDESTRUCTIBLE; + item._iMaxDur = DUR_INDESTRUCTIBLE; + break; + case IPL_LIGHT: + item._iPLLight += power.param1; + break; + case IPL_LIGHT_CURSE: + item._iPLLight -= power.param1; + break; + case IPL_MULT_ARROWS: + item._iFlags |= ISPL_MULT_ARROWS; + break; + case IPL_FIRE_ARROWS: + item._iFlags |= ISPL_FIRE_ARROWS; + item._iFlags &= ~ISPL_LIGHT_ARROWS; + item._iFMinDam = power.param1; + item._iFMaxDam = power.param2; + item._iLMinDam = 0; + item._iLMaxDam = 0; + break; + case IPL_LIGHT_ARROWS: + item._iFlags |= ISPL_LIGHT_ARROWS; + item._iFlags &= ~ISPL_FIRE_ARROWS; + item._iLMinDam = power.param1; + item._iLMaxDam = power.param2; + item._iFMinDam = 0; + item._iFMaxDam = 0; + break; + case IPL_FIREBALL: + item._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS); + item._iFMinDam = power.param1; + item._iFMaxDam = power.param2; + item._iLMinDam = 0; + item._iLMaxDam = 0; + break; + case IPL_THORNS: + item._iFlags |= ISPL_THORNS; + break; + case IPL_NOMANA: + item._iFlags |= ISPL_NOMANA; + drawmanaflag = true; + break; + case IPL_NOHEALPLR: + item._iFlags |= ISPL_NOHEALPLR; + break; + case IPL_ABSHALFTRAP: + item._iFlags |= ISPL_ABSHALFTRAP; + break; + case IPL_KNOCKBACK: + item._iFlags |= ISPL_KNOCKBACK; + break; + case IPL_3XDAMVDEM: + item._iFlags |= ISPL_3XDAMVDEM; + break; + case IPL_ALLRESZERO: + item._iFlags |= ISPL_ALLRESZERO; + break; + case IPL_NOHEALMON: + item._iFlags |= ISPL_NOHEALMON; + break; + case IPL_STEALMANA: + if (power.param1 == 3) + item._iFlags |= ISPL_STEALMANA_3; + if (power.param1 == 5) + item._iFlags |= ISPL_STEALMANA_5; + drawmanaflag = true; + break; + case IPL_STEALLIFE: + if (power.param1 == 3) + item._iFlags |= ISPL_STEALLIFE_3; + if (power.param1 == 5) + item._iFlags |= ISPL_STEALLIFE_5; + drawhpflag = true; + break; + case IPL_TARGAC: + if (gbIsHellfire) + item._iPLEnAc = power.param1; + else + item._iPLEnAc += r; + break; + case IPL_FASTATTACK: + if (power.param1 == 1) + item._iFlags |= ISPL_QUICKATTACK; + if (power.param1 == 2) + item._iFlags |= ISPL_FASTATTACK; + if (power.param1 == 3) + item._iFlags |= ISPL_FASTERATTACK; + if (power.param1 == 4) + item._iFlags |= ISPL_FASTESTATTACK; + break; + case IPL_FASTRECOVER: + if (power.param1 == 1) + item._iFlags |= ISPL_FASTRECOVER; + if (power.param1 == 2) + item._iFlags |= ISPL_FASTERRECOVER; + if (power.param1 == 3) + item._iFlags |= ISPL_FASTESTRECOVER; + break; + case IPL_FASTBLOCK: + item._iFlags |= ISPL_FASTBLOCK; + break; + case IPL_DAMMOD: + item._iPLDamMod += r; + break; + case IPL_RNDARROWVEL: + item._iFlags |= ISPL_RNDARROWVEL; + break; + case IPL_SETDAM: + item._iMinDam = power.param1; + item._iMaxDam = power.param2; + break; + case IPL_SETDUR: + item._iDurability = power.param1; + item._iMaxDur = power.param1; + break; + case IPL_FASTSWING: + item._iFlags |= ISPL_FASTERATTACK; + break; + case IPL_ONEHAND: + item._iLoc = ILOC_ONEHAND; + break; + case IPL_DRAINLIFE: + item._iFlags |= ISPL_DRAINLIFE; + break; + case IPL_RNDSTEALLIFE: + item._iFlags |= ISPL_RNDSTEALLIFE; + break; + case IPL_INFRAVISION: + item._iFlags |= ISPL_INFRAVISION; + break; + case IPL_NOMINSTR: + item._iMinStr = 0; + break; + case IPL_INVCURS: + item._iCurs = power.param1; + break; + case IPL_ADDACLIFE: + item._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS); + item._iFMinDam = power.param1; + item._iFMaxDam = power.param2; + item._iLMinDam = 1; + item._iLMaxDam = 0; + break; + case IPL_ADDMANAAC: + item._iFlags |= (ISPL_LIGHTDAM | ISPL_FIREDAM); + item._iFMinDam = power.param1; + item._iFMaxDam = power.param2; + item._iLMinDam = 2; + item._iLMaxDam = 0; + break; + case IPL_FIRERESCLVL: + item._iPLFR = 30 - Players[MyPlayerId]._pLevel; + item._iPLFR = std::max(item._iPLFR, 0); + break; + case IPL_FIRERES_CURSE: + item._iPLFR -= r; + break; + case IPL_LIGHTRES_CURSE: + item._iPLLR -= r; + break; + case IPL_MAGICRES_CURSE: + item._iPLMR -= r; + break; + case IPL_ALLRES_CURSE: + item._iPLFR -= r; + item._iPLLR -= r; + item._iPLMR -= r; + break; + case IPL_DEVASTATION: + item._iDamAcFlags |= ISPLHF_DEVASTATION; + break; + case IPL_DECAY: + item._iDamAcFlags |= ISPLHF_DECAY; + item._iPLDam += r; + break; + case IPL_PERIL: + item._iDamAcFlags |= ISPLHF_PERIL; + break; + case IPL_JESTERS: + item._iDamAcFlags |= ISPLHF_JESTERS; + break; + case IPL_ACDEMON: + item._iDamAcFlags |= ISPLHF_ACDEMON; + break; + case IPL_ACUNDEAD: + item._iDamAcFlags |= ISPLHF_ACUNDEAD; + break; + case IPL_MANATOLIFE: + r2 = ((Players[MyPlayerId]._pMaxManaBase >> 6) * 50 / 100); + item._iPLMana -= (r2 << 6); + item._iPLHP += (r2 << 6); + break; + case IPL_LIFETOMANA: + r2 = ((Players[MyPlayerId]._pMaxHPBase >> 6) * 40 / 100); + item._iPLHP -= (r2 << 6); + item._iPLMana += (r2 << 6); break; - case IPL_GETHIT_CURSE: - item._iPLGetHit += r; + default: + break; + } + if (item._iVAdd1 != 0 || item._iVMult1 != 0) { + item._iVAdd2 = PLVal(r, power.param1, power.param2, power.minval, power.maxval); + item._iVMult2 = power.multval; + } else { + item._iVAdd1 = PLVal(r, power.param1, power.param2, power.minval, power.maxval); + item._iVMult1 = power.multval; + } +} + +static bool StringInPanel(const char *str) +{ + return GetLineWidth(str, GameFontSmall, 0) < 125; +} + +static void SaveItemSuffix(int i, int sufidx) +{ + auto power = ItemSuffixes[sufidx].power; + + if (!gbIsHellfire) { + if (sufidx >= 84 && sufidx <= 86) { + power.param1 = 2 << power.param1; + power.param2 = 6 << power.param2; + } + } + + SaveItemPower(Items[i], power); +} + +void GetStaffPower(int i, int lvl, int bs, bool onlygood) +{ + int preidx = -1; + if (GenerateRnd(10) == 0 || onlygood) { + int nl = 0; + int l[256]; + for (int j = 0; ItemPrefixes[j].power.type != IPL_INVALID; j++) { + if (!IsPrefixValidForItemType(j, PLT_STAFF) || ItemPrefixes[j].PLMinLvl > lvl) + continue; + if (onlygood && !ItemPrefixes[j].PLOk) + continue; + l[nl] = j; + nl++; + if (ItemPrefixes[j].PLDouble) { + l[nl] = j; + nl++; + } + } + if (nl != 0) { + preidx = l[GenerateRnd(nl)]; + char istr[128]; + sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); + strcpy(Items[i]._iIName, istr); + Items[i]._iMagical = ITEM_QUALITY_MAGIC; + SaveItemPower(Items[i], ItemPrefixes[preidx].power); + Items[i]._iPrePower = ItemPrefixes[preidx].power.type; + } + } + if (!StringInPanel(Items[i]._iIName)) { + strcpy(Items[i]._iIName, _(AllItemsList[Items[i].IDidx].iSName)); + char istr[128]; + if (preidx != -1) { + sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); + strcpy(Items[i]._iIName, istr); + } + strcpy(istr, fmt::format(_(/* TRANSLATORS: Constructs item names. Format: of . 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 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; ItemPrefixes[j].power.type != IPL_INVALID; j++) { + if (!IsPrefixValidForItemType(j, flgs)) + continue; + if (ItemPrefixes[j].PLMinLvl < minlvl || ItemPrefixes[j].PLMinLvl > maxlvl) + continue; + if (onlygood && !ItemPrefixes[j].PLOk) + continue; + if (flgs == PLT_STAFF && ItemPrefixes[j].power.type == IPL_CHARGES) + continue; + l[nt] = j; + nt++; + if (ItemPrefixes[j].PLDouble) { + l[nt] = j; + nt++; + } + } + if (nt != 0) { + preidx = l[GenerateRnd(nt)]; + sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); + strcpy(Items[i]._iIName, istr); + Items[i]._iMagical = ITEM_QUALITY_MAGIC; + SaveItemPower(Items[i], ItemPrefixes[preidx].power); + Items[i]._iPrePower = ItemPrefixes[preidx].power.type; + goe = ItemPrefixes[preidx].PLGOE; + } + } + if (post != 0) { + int nl = 0; + for (int j = 0; ItemSuffixes[j].power.type != IPL_INVALID; j++) { + if (IsSuffixValidForItemType(j, flgs) + && ItemSuffixes[j].PLMinLvl >= minlvl && ItemSuffixes[j].PLMinLvl <= maxlvl + && !((goe == GOE_GOOD && ItemSuffixes[j].PLGOE == GOE_EVIL) || (goe == GOE_EVIL && ItemSuffixes[j].PLGOE == GOE_GOOD)) + && (!onlygood || ItemSuffixes[j].PLOk)) { + l[nl] = j; + nl++; + } + } + if (nl != 0) { + sufidx = l[GenerateRnd(nl)]; + strcpy(istr, fmt::format(_("{:s} of {:s}"), Items[i]._iIName, _(ItemSuffixes[sufidx].PLName)).c_str()); + strcpy(Items[i]._iIName, istr); + Items[i]._iMagical = ITEM_QUALITY_MAGIC; + SaveItemSuffix(i, sufidx); + Items[i]._iSufPower = ItemSuffixes[sufidx].power.type; + } + } + if (!StringInPanel(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", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); + strcpy(Items[i]._iIName, istr); + } + if (sufidx != -1) { + strcpy(istr, fmt::format(_("{:s} of {:s}"), Items[i]._iIName, _(ItemSuffixes[sufidx].PLName)).c_str()); + strcpy(Items[i]._iIName, istr); + } + } + if (preidx != -1 || sufidx != -1) + CalcItemValue(i); +} + +void GetStaffSpell(int i, int lvl, bool onlygood) +{ + if (!gbIsHellfire && GenerateRnd(4) == 0) { + GetItemPower(i, lvl / 2, lvl, PLT_STAFF, onlygood); + return; + } + + int maxSpells = gbIsHellfire ? MAX_SPELLS : 37; + int l = lvl / 2; + if (l == 0) + l = 1; + int rv = GenerateRnd(maxSpells) + 1; + + if (gbIsSpawn && lvl > 10) + lvl = 10; + + int s = SPL_FIREBOLT; + enum spell_id bs = SPL_NULL; + while (rv > 0) { + int sLevel = GetSpellStaffLevel(static_cast(s)); + if (sLevel != -1 && l >= sLevel) { + rv--; + bs = static_cast(s); + } + s++; + if (!gbIsMultiplayer && s == SPL_RESURRECT) + s = SPL_TELEKINESIS; + if (!gbIsMultiplayer && s == SPL_HEALOTHER) + s = SPL_FLARE; + if (s == maxSpells) + s = SPL_FIREBOLT; + } + + char istr[68]; + if (!StringInPanel(istr)) + strcpy(istr, fmt::format(_("{:s} of {:s}"), 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); + + int minc = spelldata[bs].sStaffMin; + int 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; + int 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 maxLvl) +{ + int cnt = 2; + int8_t rnd[32] = { 5, 6 }; + + if (!gbIsMultiplayer) { + if (maxLvl == 0) + maxLvl = 1; + + cnt = 0; + for (size_t j = 0; j < sizeof(OilLevels) / sizeof(OilLevels[0]); j++) { + if (OilLevels[j] <= maxLvl) { + rnd[cnt] = j; + cnt++; + } + } + } + + int8_t t = rnd[GenerateRnd(cnt)]; + + strcpy(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 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 IPL_GETHIT: - item._iPLGetHit -= r; + case ITYPE_BOW: + GetItemPower(i, minlvl, maxlvl, PLT_BOW, onlygood); break; - case IPL_LIFE: - item._iPLHP += r << 6; + case ITYPE_SHIELD: + GetItemPower(i, minlvl, maxlvl, PLT_SHLD, onlygood); break; - case IPL_LIFE_CURSE: - item._iPLHP -= r << 6; + case ITYPE_LARMOR: + case ITYPE_HELM: + case ITYPE_MARMOR: + case ITYPE_HARMOR: + GetItemPower(i, minlvl, maxlvl, PLT_ARMO, onlygood); break; - case IPL_MANA: - item._iPLMana += r << 6; - drawmanaflag = true; + case ITYPE_STAFF: + if (allowspells) + GetStaffSpell(i, maxlvl, onlygood); + else + GetItemPower(i, minlvl, maxlvl, PLT_STAFF, onlygood); break; - case IPL_MANA_CURSE: - item._iPLMana -= r << 6; - drawmanaflag = true; + case ITYPE_RING: + case ITYPE_AMULET: + GetItemPower(i, minlvl, maxlvl, PLT_MISC, onlygood); break; - case IPL_DUR: - r2 = r * item._iMaxDur / 100; - item._iMaxDur += r2; - item._iDurability += r2; + case ITYPE_NONE: + case ITYPE_MISC: + case ITYPE_GOLD: break; - case IPL_CRYSTALLINE: - item._iPLDam += 140 + r * 2; - [[fallthrough]]; - case IPL_DUR_CURSE: - item._iMaxDur -= r * item._iMaxDur / 100; - item._iMaxDur = std::max(item._iMaxDur, 1); + } +} + +int RndUItem(int m) +{ + if (m != -1 && (Monsters[m].MData->mTreasure & 0x8000) != 0 && !gbIsMultiplayer) + return -((Monsters[m].MData->mTreasure & 0xFFF) + 1); + + int ril[512]; + + int curlv = items_get_currlevel(); + 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 (m != -1) { + if (Monsters[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() +{ + if (GenerateRnd(100) > 25) + return 0; + + int ril[512]; + + 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(ItemStruct &item, _unique_items uid) +{ + UniqueItemFlags[uid] = true; + + for (int i = 0; i < UniqueItemList[uid].UINumPL; i++) { + SaveItemPower(item, UniqueItemList[uid].powers[i]); + } + + strcpy(item._iIName, _(UniqueItemList[uid].UIName)); + item._iIvalue = UniqueItemList[uid].UIValue; + + if (item._iMiscId == IMISC_UNIQUE) + item._iSeed = uid; + + item._iUid = uid; + item._iMagical = ITEM_QUALITY_UNIQUE; + item._iCreateInfo |= CF_UNIQUE; +} + +void ItemRndDur(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(Items[ii], uid); + } + } + if (Items[ii]._iMagical != ITEM_QUALITY_UNIQUE) + ItemRndDur(ii); + } else { + if (Items[ii]._iLoc != ILOC_UNEQUIPABLE) { + GetUniqueItem(Items[ii], (_unique_items)iseed); // uid is stored in iseed for uniques + } + } + SetupItem(ii); +} - item._iDurability = item._iMaxDur; - break; - case IPL_INDESTRUCTIBLE: - item._iDurability = DUR_INDESTRUCTIBLE; - item._iMaxDur = DUR_INDESTRUCTIBLE; - break; - case IPL_LIGHT: - item._iPLLight += power.param1; - break; - case IPL_LIGHT_CURSE: - item._iPLLight -= power.param1; - break; - case IPL_MULT_ARROWS: - item._iFlags |= ISPL_MULT_ARROWS; - break; - case IPL_FIRE_ARROWS: - item._iFlags |= ISPL_FIRE_ARROWS; - item._iFlags &= ~ISPL_LIGHT_ARROWS; - item._iFMinDam = power.param1; - item._iFMaxDam = power.param2; - item._iLMinDam = 0; - item._iLMaxDam = 0; - break; - case IPL_LIGHT_ARROWS: - item._iFlags |= ISPL_LIGHT_ARROWS; - item._iFlags &= ~ISPL_FIRE_ARROWS; - item._iLMinDam = power.param1; - item._iLMaxDam = power.param2; - item._iFMinDam = 0; - item._iFMaxDam = 0; +static void SetupBaseItem(Point position, int idx, bool onlygood, bool sendmsg, bool delta) +{ + if (ActiveItemCount >= 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 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); +} + +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 SpawnRock() +{ + if (ActiveItemCount >= MAXITEMS) + return; + + int oi; + bool ostand = false; + for (int i = 0; i < ActiveObjectCount && !ostand; i++) { + oi = ActiveObjects[i]; + ostand = Objects[oi]._otype == OBJ_STAND; + } + + if (!ostand) + return; + + int ii = AllocateItem(); + + Items[ii].position = Objects[oi].position; + dItem[Objects[oi].position.x][Objects[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 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; +} + +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(i->_iDurability + rep, i->_iMaxDur); +} + +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); +} + +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 IPL_FIREBALL: - item._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS); - item._iFMinDam = power.param1; - item._iFMaxDam = power.param2; - item._iLMinDam = 0; - item._iLMaxDam = 0; + case IMISC_OILDEATH: + if (x->_iClass == ICLASS_ARMOR) { + return false; + } + if (x->_itype == ITYPE_BOW) { + return false; + } break; - case IPL_THORNS: - item._iFlags |= ISPL_THORNS; + case IMISC_OILHARD: + case IMISC_OILIMP: + if (x->_iClass == ICLASS_WEAPON) { + return false; + } break; - case IPL_NOMANA: - item._iFlags |= ISPL_NOMANA; - drawmanaflag = true; + default: break; - case IPL_NOHEALPLR: - item._iFlags |= ISPL_NOHEALPLR; + } + + switch (player._pOilType) { + case IMISC_OILACC: + if (x->_iPLToHit < 50) { + x->_iPLToHit += GenerateRnd(2) + 1; + } break; - case IPL_ABSHALFTRAP: - item._iFlags |= ISPL_ABSHALFTRAP; + case IMISC_OILMAST: + if (x->_iPLToHit < 100) { + x->_iPLToHit += GenerateRnd(3) + 3; + } break; - case IPL_KNOCKBACK: - item._iFlags |= ISPL_KNOCKBACK; + case IMISC_OILSHARP: + if (x->_iMaxDam - x->_iMinDam < 30) { + x->_iMaxDam = x->_iMaxDam + 1; + } break; - case IPL_3XDAMVDEM: - item._iFlags |= ISPL_3XDAMVDEM; + case IMISC_OILDEATH: + if (x->_iMaxDam - x->_iMinDam < 30) { + x->_iMinDam = x->_iMinDam + 1; + x->_iMaxDam = x->_iMaxDam + 2; + } break; - case IPL_ALLRESZERO: - item._iFlags |= ISPL_ALLRESZERO; + 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 IPL_NOHEALMON: - item._iFlags |= ISPL_NOHEALMON; + 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(x->_iDurability, x->_iMaxDur); + } else { + if (x->_iMaxDur >= 100) { + return true; + } + x->_iMaxDur++; + x->_iDurability = x->_iMaxDur; + } break; - case IPL_STEALMANA: - if (power.param1 == 3) - item._iFlags |= ISPL_STEALMANA_3; - if (power.param1 == 5) - item._iFlags |= ISPL_STEALMANA_5; - drawmanaflag = true; + case IMISC_OILFORT: + if (x->_iMaxDur != DUR_INDESTRUCTIBLE && x->_iMaxDur < 200) { + r = GenerateRnd(41) + 10; + x->_iMaxDur += r; + x->_iDurability += r; + } break; - case IPL_STEALLIFE: - if (power.param1 == 3) - item._iFlags |= ISPL_STEALLIFE_3; - if (power.param1 == 5) - item._iFlags |= ISPL_STEALLIFE_5; - drawhpflag = true; + case IMISC_OILPERM: + x->_iDurability = 255; + x->_iMaxDur = 255; break; - case IPL_TARGAC: - if (gbIsHellfire) - item._iPLEnAc = power.param1; - else - item._iPLEnAc += r; + case IMISC_OILHARD: + if (x->_iAC < 60) { + x->_iAC += GenerateRnd(2) + 1; + } break; - case IPL_FASTATTACK: - if (power.param1 == 1) - item._iFlags |= ISPL_QUICKATTACK; - if (power.param1 == 2) - item._iFlags |= ISPL_FASTATTACK; - if (power.param1 == 3) - item._iFlags |= ISPL_FASTERATTACK; - if (power.param1 == 4) - item._iFlags |= ISPL_FASTESTATTACK; + case IMISC_OILIMP: + if (x->_iAC < 120) { + x->_iAC += GenerateRnd(3) + 3; + } break; - case IPL_FASTRECOVER: - if (power.param1 == 1) - item._iFlags |= ISPL_FASTRECOVER; - if (power.param1 == 2) - item._iFlags |= ISPL_FASTERRECOVER; - if (power.param1 == 3) - item._iFlags |= ISPL_FASTESTRECOVER; + default: + return false; + } + return true; +} + +void PrintItemOil(char iDidx) +{ + switch (iDidx) { + case IMISC_OILACC: + strcpy(tempstr, _("increases a weapon's")); + AddPanelString(tempstr); + strcpy(tempstr, _("chance to hit")); + AddPanelString(tempstr); break; - case IPL_FASTBLOCK: - item._iFlags |= ISPL_FASTBLOCK; + case IMISC_OILMAST: + strcpy(tempstr, _("greatly increases a")); + AddPanelString(tempstr); + strcpy(tempstr, _("weapon's chance to hit")); + AddPanelString(tempstr); break; - case IPL_DAMMOD: - item._iPLDamMod += r; + case IMISC_OILSHARP: + strcpy(tempstr, _("increases a weapon's")); + AddPanelString(tempstr); + strcpy(tempstr, _("damage potential")); + AddPanelString(tempstr); break; - case IPL_RNDARROWVEL: - item._iFlags |= ISPL_RNDARROWVEL; + case IMISC_OILDEATH: + strcpy(tempstr, _("greatly increases a weapon's")); + AddPanelString(tempstr); + strcpy(tempstr, _("damage potential - not bows")); + AddPanelString(tempstr); break; - case IPL_SETDAM: - item._iMinDam = power.param1; - item._iMaxDam = power.param2; + case IMISC_OILSKILL: + strcpy(tempstr, _("reduces attributes needed")); + AddPanelString(tempstr); + strcpy(tempstr, _("to use armor or weapons")); + AddPanelString(tempstr); break; - case IPL_SETDUR: - item._iDurability = power.param1; - item._iMaxDur = power.param1; + case IMISC_OILBSMTH: + /*xgettext:no-c-format*/ strcpy(tempstr, _("restores 20% of an")); + AddPanelString(tempstr); + strcpy(tempstr, _("item's durability")); + AddPanelString(tempstr); break; - case IPL_FASTSWING: - item._iFlags |= ISPL_FASTERATTACK; + case IMISC_OILFORT: + strcpy(tempstr, _("increases an item's")); + AddPanelString(tempstr); + strcpy(tempstr, _("current and max durability")); + AddPanelString(tempstr); break; - case IPL_ONEHAND: - item._iLoc = ILOC_ONEHAND; + case IMISC_OILPERM: + strcpy(tempstr, _("makes an item indestructible")); + AddPanelString(tempstr); break; - case IPL_DRAINLIFE: - item._iFlags |= ISPL_DRAINLIFE; + case IMISC_OILHARD: + strcpy(tempstr, _("increases the armor class")); + AddPanelString(tempstr); + strcpy(tempstr, _("of armor and shields")); + AddPanelString(tempstr); break; - case IPL_RNDSTEALLIFE: - item._iFlags |= ISPL_RNDSTEALLIFE; + case IMISC_OILIMP: + strcpy(tempstr, _("greatly increases the armor")); + AddPanelString(tempstr); + strcpy(tempstr, _("class of armor and shields")); + AddPanelString(tempstr); break; - case IPL_INFRAVISION: - item._iFlags |= ISPL_INFRAVISION; + case IMISC_RUNEF: + strcpy(tempstr, _("sets fire trap")); + AddPanelString(tempstr); break; - case IPL_NOMINSTR: - item._iMinStr = 0; + case IMISC_RUNEL: + case IMISC_GR_RUNEL: + strcpy(tempstr, _("sets lightning trap")); + AddPanelString(tempstr); break; - case IPL_INVCURS: - item._iCurs = power.param1; + case IMISC_GR_RUNEF: + strcpy(tempstr, _("sets fire trap")); + AddPanelString(tempstr); break; - case IPL_ADDACLIFE: - item._iFlags |= (ISPL_LIGHT_ARROWS | ISPL_FIRE_ARROWS); - item._iFMinDam = power.param1; - item._iFMaxDam = power.param2; - item._iLMinDam = 1; - item._iLMaxDam = 0; + case IMISC_RUNES: + strcpy(tempstr, _("sets petrification trap")); + AddPanelString(tempstr); break; - case IPL_ADDMANAAC: - item._iFlags |= (ISPL_LIGHTDAM | ISPL_FIREDAM); - item._iFMinDam = power.param1; - item._iFMaxDam = power.param2; - item._iLMinDam = 2; - item._iLMaxDam = 0; + case IMISC_FULLHEAL: + strcpy(tempstr, _("restore all life")); + AddPanelString(tempstr); break; - case IPL_FIRERESCLVL: - item._iPLFR = 30 - Players[MyPlayerId]._pLevel; - item._iPLFR = std::max(item._iPLFR, 0); + case IMISC_HEAL: + strcpy(tempstr, _("restore some life")); + AddPanelString(tempstr); break; - case IPL_FIRERES_CURSE: - item._iPLFR -= r; + case IMISC_OLDHEAL: + strcpy(tempstr, _("recover life")); + AddPanelString(tempstr); break; - case IPL_LIGHTRES_CURSE: - item._iPLLR -= r; + case IMISC_DEADHEAL: + strcpy(tempstr, _("deadly heal")); + AddPanelString(tempstr); break; - case IPL_MAGICRES_CURSE: - item._iPLMR -= r; + case IMISC_MANA: + strcpy(tempstr, _("restore some mana")); + AddPanelString(tempstr); break; - case IPL_ALLRES_CURSE: - item._iPLFR -= r; - item._iPLLR -= r; - item._iPLMR -= r; + case IMISC_FULLMANA: + strcpy(tempstr, _("restore all mana")); + AddPanelString(tempstr); break; - case IPL_DEVASTATION: - item._iDamAcFlags |= ISPLHF_DEVASTATION; + case IMISC_ELIXSTR: + strcpy(tempstr, _("increase strength")); + AddPanelString(tempstr); break; - case IPL_DECAY: - item._iDamAcFlags |= ISPLHF_DECAY; - item._iPLDam += r; + case IMISC_ELIXMAG: + strcpy(tempstr, _("increase magic")); + AddPanelString(tempstr); break; - case IPL_PERIL: - item._iDamAcFlags |= ISPLHF_PERIL; + case IMISC_ELIXDEX: + strcpy(tempstr, _("increase dexterity")); + AddPanelString(tempstr); break; - case IPL_JESTERS: - item._iDamAcFlags |= ISPLHF_JESTERS; + case IMISC_ELIXVIT: + strcpy(tempstr, _("increase vitality")); + AddPanelString(tempstr); break; - case IPL_ACDEMON: - item._iDamAcFlags |= ISPLHF_ACDEMON; + case IMISC_ELIXWEAK: + case IMISC_ELIXDIS: + strcpy(tempstr, _("decrease strength")); + AddPanelString(tempstr); break; - case IPL_ACUNDEAD: - item._iDamAcFlags |= ISPLHF_ACUNDEAD; + case IMISC_ELIXCLUM: + strcpy(tempstr, _("decrease dexterity")); + AddPanelString(tempstr); break; - case IPL_MANATOLIFE: - r2 = ((Players[MyPlayerId]._pMaxManaBase >> 6) * 50 / 100); - item._iPLMana -= (r2 << 6); - item._iPLHP += (r2 << 6); + case IMISC_ELIXSICK: + strcpy(tempstr, _("decrease vitality")); + AddPanelString(tempstr); break; - case IPL_LIFETOMANA: - r2 = ((Players[MyPlayerId]._pMaxHPBase >> 6) * 40 / 100); - item._iPLHP -= (r2 << 6); - item._iPLMana += (r2 << 6); + case IMISC_REJUV: + strcpy(tempstr, _("restore some life and mana")); + AddPanelString(tempstr); break; - default: + case IMISC_FULLREJUV: + strcpy(tempstr, _("restore all life and mana")); + AddPanelString(tempstr); break; } - if (item._iVAdd1 != 0 || item._iVMult1 != 0) { - item._iVAdd2 = PLVal(r, power.param1, power.param2, power.minval, power.maxval); - item._iVMult2 = power.multval; +} + +static void DrawUTextBack(const Surface &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 Surface &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 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; +} + +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 +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(0, lvl); +} + +void SortVendor(ItemStruct *itemList) +{ + int count = 1; + while (!itemList[count].isEmpty()) + count++; + + auto cmp = [](const ItemStruct &a, const ItemStruct &b) { + return a.IDidx < b.IDidx; + }; + + std::sort(itemList, itemList + count, cmp); +} + +bool PremiumItemOk(int i) +{ + if (AllItemsList[i].itype == ITYPE_MISC) + return false; + if (AllItemsList[i].itype == ITYPE_GOLD) + return false; + if (!gbIsHellfire && AllItemsList[i].itype == ITYPE_STAFF) + return false; + + if (gbIsMultiplayer) { + if (AllItemsList[i].iMiscId == IMISC_OILOF) + return false; + if (AllItemsList[i].itype == ITYPE_RING) + return false; + if (AllItemsList[i].itype == ITYPE_AMULET) + return false; + } + + return true; +} + +int RndPremiumItem(int minlvl, int maxlvl) +{ + return RndVendorItem(minlvl, maxlvl); +} + +static void SpawnOnePremium(int i, int plvl, int playerId) +{ + int itemValue = 0; + bool keepGoing = false; + ItemStruct tempItem = Items[0]; + + auto &player = Players[playerId]; + + int strength = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Strength), player._pStrength); + int dexterity = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Dexterity), player._pDexterity); + int magic = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Magic), player._pMagic); + strength += strength / 5; + dexterity += dexterity / 5; + magic += magic / 5; + + plvl = clamp(plvl, 1, 30); + + int count = 0; + + do { + keepGoing = false; + memset(&Items[0], 0, sizeof(*Items)); + Items[0]._iSeed = AdvanceRndSeed(); + int itemType = RndPremiumItem(plvl / 4, plvl) - 1; + GetItemAttrs(0, itemType, 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 = player.GetMostValuableItem( + [](const ItemStruct &item) { + return item._itype == ITYPE_LARMOR + || item._itype == ITYPE_MARMOR + || item._itype == ITYPE_HARMOR; + }); + + itemValue = mostValuablePlayerArmor == nullptr ? 0 : mostValuablePlayerArmor->_iIvalue; + break; + } + case ITYPE_SHIELD: + case ITYPE_AXE: + case ITYPE_BOW: + case ITYPE_MACE: + case ITYPE_SWORD: + case ITYPE_HELM: + case ITYPE_STAFF: + case ITYPE_RING: + case ITYPE_AMULET: { + const auto *const mostValuablePlayerItem = player.GetMostValuableItem( + [](const ItemStruct &item) { return item._itype == Items[0]._itype; }); + + itemValue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue; + break; + } + default: + itemValue = 0; + break; + } + itemValue = itemValue * 4 / 5; // avoids forced int > float > int conversion + + count++; + } while (keepGoing + || (( + Items[0]._iIvalue > 200000 + || Items[0]._iMinStr > strength + || Items[0]._iMinMag > magic + || Items[0]._iMinDex > dexterity + || Items[0]._iIvalue < itemValue) + && count < 150)); + premiumitems[i] = Items[0]; + premiumitems[i]._iCreateInfo = plvl | CF_SMITHPREMIUM; + premiumitems[i]._iIdentified = true; + premiumitems[i]._iStatFlag = StoreStatOk(&premiumitems[i]); + Items[0] = tempItem; +} + +bool WitchItemOk(int i) +{ + if (AllItemsList[i].itype != ITYPE_MISC && AllItemsList[i].itype != ITYPE_STAFF) + return false; + if (AllItemsList[i].iMiscId == IMISC_MANA) + return false; + if (AllItemsList[i].iMiscId == IMISC_FULLMANA) + return false; + if (AllItemsList[i].iSpell == SPL_TOWN) + return false; + if (AllItemsList[i].iMiscId == IMISC_FULLHEAL) + return false; + if (AllItemsList[i].iMiscId == IMISC_HEAL) + return false; + if (AllItemsList[i].iMiscId > IMISC_OILFIRST && AllItemsList[i].iMiscId < IMISC_OILLAST) + return false; + if (AllItemsList[i].iSpell == SPL_RESURRECT && !gbIsMultiplayer) + return false; + if (AllItemsList[i].iSpell == SPL_HEALOTHER && !gbIsMultiplayer) + return false; + + return true; +} + +int RndWitchItem(int lvl) +{ + return RndVendorItem(0, lvl); +} + +int RndBoyItem(int lvl) +{ + return RndVendorItem(0, lvl); +} + +bool HealerItemOk(int i) +{ + if (AllItemsList[i].itype != ITYPE_MISC) + return false; + + if (AllItemsList[i].iMiscId == IMISC_SCROLL) + return AllItemsList[i].iSpell == SPL_HEAL; + if (AllItemsList[i].iMiscId == IMISC_SCROLLT) + return AllItemsList[i].iSpell == SPL_HEALOTHER && gbIsMultiplayer; + + if (!gbIsMultiplayer) { + auto &myPlayer = Players[MyPlayerId]; + + if (AllItemsList[i].iMiscId == IMISC_ELIXSTR) + return !gbIsHellfire || myPlayer._pBaseStr < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength); + if (AllItemsList[i].iMiscId == IMISC_ELIXMAG) + return !gbIsHellfire || myPlayer._pBaseMag < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic); + if (AllItemsList[i].iMiscId == IMISC_ELIXDEX) + return !gbIsHellfire || myPlayer._pBaseDex < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity); + if (AllItemsList[i].iMiscId == IMISC_ELIXVIT) + return !gbIsHellfire || myPlayer._pBaseVit < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Vitality); + } + + if (AllItemsList[i].iMiscId == IMISC_REJUV) + return true; + if (AllItemsList[i].iMiscId == IMISC_FULLREJUV) + return true; + + return false; +} + +int RndHealerItem(int lvl) +{ + return RndVendorItem(0, lvl); +} + +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 { - item._iVAdd1 = PLVal(r, power.param1, power.param2, power.minval, power.maxval); - item._iVMult1 = power.multval; + 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); } -} -} // namespace + Items[ii]._iSeed = iseed; + Items[ii]._iCreateInfo = lvl | CF_WITCH; + Items[ii]._iIdentified = true; +} -enum anim_armor_id : uint8_t { - // clang-format off - AnimIdLightArmor = 0, - AnimIdMediumArmor = 1 << 4, - AnimIdHeavyArmor = 1 << 5, - // clang-format on -}; +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); + } -int ActiveItems[MAXITEMS]; -bool ShowUniqueItemInfoBox; -int AvailableItems[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 ActiveItemCount; -int gnNumGetRecords; + Items[ii]._iSeed = iseed; + Items[ii]._iCreateInfo = lvl | CF_HEALER; + Items[ii]._iIdentified = true; +} -/* data */ +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); +} -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; +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); +} -/** 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 -}; +static void CreateMagicItem(Point position, int lvl, int imisc, int imid, int icurs, bool sendmsg, bool delta) +{ + if (ActiveItemCount >= 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); +} + +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; +} + +} // namespace bool IsItemAvailable(int i) { @@ -774,52 +2544,6 @@ bool IsUniqueAvailable(int i) return gbIsHellfire || i <= 89; } -static bool IsPrefixValidForItemType(int i, int flgs) -{ - int itemTypes = ItemPrefixes[i].PLIType; - - if (!gbIsHellfire) { - if (i > 82) - return false; - - if (i >= 12 && i <= 20) - itemTypes &= ~PLT_STAFF; - } - - return (flgs & itemTypes) != 0; -} - -static bool IsSuffixValidForItemType(int i, int flgs) -{ - int itemTypes = ItemSuffixes[i].PLIType; - - if (!gbIsHellfire) { - if (i > 94) - return false; - - if ((i >= 0 && i <= 1) - || (i >= 14 && i <= 15) - || (i >= 21 && i <= 22) - || (i >= 34 && i <= 36) - || (i >= 41 && i <= 44) - || (i >= 60 && i <= 63)) - itemTypes &= ~PLT_STAFF; - } - - return (flgs & itemTypes) != 0; -} - -int items_get_currlevel() -{ - int 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]; @@ -832,82 +2556,6 @@ void InitItemGFX() 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 { GenerateRnd(80), GenerateRnd(80) } + Displacement { 16, 16 }; - } while (!ItemPlace(position)); - - return position; -} - -void AddInitItems() -{ - int curlv = items_get_currlevel(); - int rnd = GenerateRnd(3) + 3; - for (int 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 SpawnNote() -{ - int id; - - switch (currlevel) { - case 22: - id = IDI_NOTE2; - break; - case 23: - id = IDI_NOTE3; - break; - default: - id = IDI_NOTE1; - break; - } - - Point position = GetRandomAvailableItemPosition(); - SpawnQuestItem(id, position, 0, 1); -} - void InitItems() { memset(&Items[0], 0, sizeof(*Items)); @@ -1303,111 +2951,12 @@ void CalcPlrItemVals(int playerId, bool loadgfx) if (half != MaxGold) StripTopGold(playerId); - } else { - MaxGold = GOLD_MAX_LIMIT * 2; - } - - drawmanaflag = true; - drawhpflag = true; -} - -void CalcSelfItems(PlayerStruct &player) -{ - int sa = 0; - int ma = 0; - int da = 0; - ItemStruct *pi = player.InvBody; - for (int 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; - } - } - } - - bool changeflag; - do { - changeflag = false; - pi = player.InvBody; - for (int i = 0; i < NUM_INVLOC; i++, pi++) { - if (!pi->isEmpty() && pi->_iStatFlag) { - bool sf = true; - if (sa + player._pBaseStr < pi->_iMinStr) - sf = false; - if (ma + player._pBaseMag < pi->_iMinMag) - sf = false; - if (da + player._pBaseDex < pi->_iMinDex) - sf = false; - if (!sf) { - changeflag = true; - pi->_iStatFlag = false; - if (pi->_iIdentified) { - sa -= pi->_iPLStr; - ma -= pi->_iPLMag; - da -= pi->_iPLDex; - } - } - } - } - } while (changeflag); -} - -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; - int8_t spellLevel = player._pSplLvl[player.InvList[i]._iSpell]; - - while (spellLevel != 0) { - player.InvList[i]._iMinMag += 20 * player.InvList[i]._iMinMag / 100; - spellLevel--; - if (player.InvList[i]._iMinMag + 20 * player.InvList[i]._iMinMag / 100 > 255) { - player.InvList[i]._iMinMag = 255; - spellLevel = 0; - } - } - player.InvList[i]._iStatFlag = ItemMinStats(player, &player.InvList[i]); - } + } else { + MaxGold = GOLD_MAX_LIMIT * 2; } + + drawmanaflag = true; + drawhpflag = true; } void CalcPlrInv(int playerId, bool loadgfx) @@ -1502,11 +3051,6 @@ void GetGoldSeed(int pnum, ItemStruct *h) h->_iSeed = s; } -void SetPlrHandSeed(ItemStruct *h, int iseed) -{ - h->_iSeed = iseed; -} - int GetGoldCursor(int value) { if (value >= GOLD_MEDIUM_LIMIT) @@ -1663,323 +3207,64 @@ bool ItemSpaceOk(Point position) if (position.x < 0 || position.x + 1 >= MAXDUNX || position.y < 0 || position.y + 1 >= MAXDUNY) return false; - if (dMonster[position.x][position.y] != 0) - return false; - - if (dPlayer[position.x][position.y] != 0) - return false; - - if (dItem[position.x][position.y] != 0) - return false; - - if (dObject[position.x][position.y] != 0) { - oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); - if (Objects[oi]._oSolidFlag) - return false; - } - - if (dObject[position.x + 1][position.y + 1] > 0 && Objects[dObject[position.x + 1][position.y + 1] - 1]._oSelFlag != 0) - return false; - - if (dObject[position.x + 1][position.y + 1] < 0 && Objects[-(dObject[position.x + 1][position.y + 1] + 1)]._oSelFlag != 0) - return false; - - if (dObject[position.x + 1][position.y] > 0 - && dObject[position.x][position.y + 1] > 0 - && Objects[dObject[position.x + 1][position.y] - 1]._oSelFlag != 0 - && Objects[dObject[position.x][position.y + 1] - 1]._oSelFlag != 0) { - return false; - } - - return !nSolidTable[dPiece[position.x][position.y]]; -} - -static bool GetItemSpace(Point position, int8_t inum) -{ - int xx = 0; - int yy = 0; - for (int j = position.y - 1; j <= position.y + 1; j++) { - xx = 0; - for (int i = position.x - 1; i <= position.x + 1; i++) { - itemhold[xx][yy] = ItemSpaceOk({ i, j }); - xx++; - } - yy++; - } - - bool savail = false; - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 3; i++) { // NOLINT(modernize-loop-convert) - if (itemhold[i][j]) - savail = true; - } - } - - int rs = GenerateRnd(15) + 1; - - if (!savail) - return false; - - xx = 0; - yy = 0; - while (rs > 0) { - if (itemhold[xx][yy]) - rs--; - if (rs <= 0) - continue; - xx++; - if (xx != 3) - continue; - xx = 0; - yy++; - if (yy == 3) - yy = 0; - } - - xx += position.x - 1; - yy += position.y - 1; - Items[inum].position = { xx, yy }; - dItem[xx][yy] = inum + 1; - - return true; -} - -int AllocateItem() -{ - int inum = AvailableItems[0]; - AvailableItems[0] = AvailableItems[MAXITEMS - ActiveItemCount - 1]; - ActiveItems[ActiveItemCount] = inum; - ActiveItemCount++; - - 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++) { - Displacement 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++) { - Displacement 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(s)); - if (sLevel != -1 && lvl >= sLevel) { - rv--; - bs = static_cast(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 StringInPanel(const char *str) -{ - return GetLineWidth(str, GameFontSmall, 0) < 125; -} - -void GetStaffPower(int i, int lvl, int bs, bool onlygood) -{ - int preidx = -1; - if (GenerateRnd(10) == 0 || onlygood) { - int nl = 0; - int l[256]; - for (int j = 0; ItemPrefixes[j].power.type != IPL_INVALID; j++) { - if (!IsPrefixValidForItemType(j, PLT_STAFF) || ItemPrefixes[j].PLMinLvl > lvl) - continue; - if (onlygood && !ItemPrefixes[j].PLOk) - continue; - l[nl] = j; - nl++; - if (ItemPrefixes[j].PLDouble) { - l[nl] = j; - nl++; - } - } - if (nl != 0) { - preidx = l[GenerateRnd(nl)]; - char istr[128]; - sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); - strcpy(Items[i]._iIName, istr); - Items[i]._iMagical = ITEM_QUALITY_MAGIC; - SaveItemPower(Items[i], ItemPrefixes[preidx].power); - Items[i]._iPrePower = ItemPrefixes[preidx].power.type; - } - } - if (!StringInPanel(Items[i]._iIName)) { - strcpy(Items[i]._iIName, _(AllItemsList[Items[i].IDidx].iSName)); - char istr[128]; - if (preidx != -1) { - sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); - strcpy(Items[i]._iIName, istr); - } - strcpy(istr, fmt::format(_(/* TRANSLATORS: Constructs item names. Format: of . 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) -{ - if (!gbIsHellfire && GenerateRnd(4) == 0) { - GetItemPower(i, lvl / 2, lvl, PLT_STAFF, onlygood); - return; - } - - int maxSpells = gbIsHellfire ? MAX_SPELLS : 37; - int l = lvl / 2; - if (l == 0) - l = 1; - int rv = GenerateRnd(maxSpells) + 1; + if (dMonster[position.x][position.y] != 0) + return false; - if (gbIsSpawn && lvl > 10) - lvl = 10; + if (dPlayer[position.x][position.y] != 0) + return false; - int s = SPL_FIREBOLT; - enum spell_id bs = SPL_NULL; - while (rv > 0) { - int sLevel = GetSpellStaffLevel(static_cast(s)); - if (sLevel != -1 && l >= sLevel) { - rv--; - bs = static_cast(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 (dItem[position.x][position.y] != 0) + return false; + + if (dObject[position.x][position.y] != 0) { + oi = dObject[position.x][position.y] > 0 ? dObject[position.x][position.y] - 1 : -(dObject[position.x][position.y] + 1); + if (Objects[oi]._oSolidFlag) + return false; } - char istr[68]; - if (!StringInPanel(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); + if (dObject[position.x + 1][position.y + 1] > 0 && Objects[dObject[position.x + 1][position.y + 1] - 1]._oSelFlag != 0) + return false; - int minc = spelldata[bs].sStaffMin; - int maxc = spelldata[bs].sStaffMax - minc + 1; - Items[i]._iSpell = bs; - Items[i]._iCharges = minc + GenerateRnd(maxc); - Items[i]._iMaxCharges = Items[i]._iCharges; + if (dObject[position.x + 1][position.y + 1] < 0 && Objects[-(dObject[position.x + 1][position.y + 1] + 1)]._oSelFlag != 0) + return false; - Items[i]._iMinMag = spelldata[bs].sMinInt; - int v = Items[i]._iCharges * spelldata[bs].sStaffCost / 5; - Items[i]._ivalue += v; - Items[i]._iIvalue += v; - GetStaffPower(i, lvl, bs, onlygood); + if (dObject[position.x + 1][position.y] > 0 + && dObject[position.x][position.y + 1] > 0 + && Objects[dObject[position.x + 1][position.y] - 1]._oSelFlag != 0 + && Objects[dObject[position.x][position.y + 1] - 1]._oSelFlag != 0) { + return false; + } + + return !nSolidTable[dPiece[position.x][position.y]]; } -void GetOilType(int i, int maxLvl) +int AllocateItem() { - int cnt = 2; - int8_t rnd[32] = { 5, 6 }; + int inum = AvailableItems[0]; + AvailableItems[0] = AvailableItems[MAXITEMS - ActiveItemCount - 1]; + ActiveItems[ActiveItemCount] = inum; + ActiveItemCount++; - if (!gbIsMultiplayer) { - if (maxLvl == 0) - maxLvl = 1; + memset(&Items[inum], 0, sizeof(*Items)); - cnt = 0; - for (size_t j = 0; j < sizeof(OilLevels) / sizeof(OilLevels[0]); j++) { - if (OilLevels[j] <= maxLvl) { - rnd[cnt] = j; - cnt++; + return inum; +} + +Point GetSuperItemLoc(Point position) +{ + for (int k = 1; k < 50; k++) { + for (int j = -k; j <= k; j++) { + for (int i = -k; i <= k; i++) { + Displacement offset = { i, j }; + Point positionToCheck = position + offset; + if (ItemSpaceOk(positionToCheck)) { + return positionToCheck; + } } } } - int8_t t = rnd[GenerateRnd(cnt)]; - - 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]; + return { 0, 0 }; // TODO handle no space for dropping items } void GetItemAttrs(int i, int idata, int lvl) @@ -2011,173 +3296,32 @@ void GetItemAttrs(int i, int idata, int lvl) 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]); -} - -static void SaveItemSuffix(int i, int sufidx) -{ - auto power = ItemSuffixes[sufidx].power; - - if (!gbIsHellfire) { - if (sufidx >= 84 && sufidx <= 86) { - power.param1 = 2 << power.param1; - power.param2 = 6 << power.param2; - } - } - - SaveItemPower(Items[i], power); -} - -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; ItemPrefixes[j].power.type != IPL_INVALID; j++) { - if (!IsPrefixValidForItemType(j, flgs)) - continue; - if (ItemPrefixes[j].PLMinLvl < minlvl || ItemPrefixes[j].PLMinLvl > maxlvl) - continue; - if (onlygood && !ItemPrefixes[j].PLOk) - continue; - if (flgs == PLT_STAFF && ItemPrefixes[j].power.type == IPL_CHARGES) - continue; - l[nt] = j; - nt++; - if (ItemPrefixes[j].PLDouble) { - l[nt] = j; - nt++; - } - } - if (nt != 0) { - preidx = l[GenerateRnd(nt)]; - sprintf(istr, "%s %s", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); - strcpy(Items[i]._iIName, istr); - Items[i]._iMagical = ITEM_QUALITY_MAGIC; - SaveItemPower(Items[i], ItemPrefixes[preidx].power); - Items[i]._iPrePower = ItemPrefixes[preidx].power.type; - goe = ItemPrefixes[preidx].PLGOE; - } - } - if (post != 0) { - int nl = 0; - for (int j = 0; ItemSuffixes[j].power.type != IPL_INVALID; j++) { - if (IsSuffixValidForItemType(j, flgs) - && ItemSuffixes[j].PLMinLvl >= minlvl && ItemSuffixes[j].PLMinLvl <= maxlvl - && !((goe == GOE_GOOD && ItemSuffixes[j].PLGOE == GOE_EVIL) || (goe == GOE_EVIL && ItemSuffixes[j].PLGOE == GOE_GOOD)) - && (!onlygood || ItemSuffixes[j].PLOk)) { - l[nl] = j; - nl++; - } - } - if (nl != 0) { - sufidx = l[GenerateRnd(nl)]; - strcpy(istr, fmt::format(_("{:s} of {:s}"), Items[i]._iIName, _(ItemSuffixes[sufidx].PLName)).c_str()); - strcpy(Items[i]._iIName, istr); - Items[i]._iMagical = ITEM_QUALITY_MAGIC; - SaveItemSuffix(i, sufidx); - Items[i]._iSufPower = ItemSuffixes[sufidx].power.type; - } - } - if (!StringInPanel(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", _(ItemPrefixes[preidx].PLName), Items[i]._iIName); - strcpy(Items[i]._iIName, istr); - } - if (sufidx != -1) { - strcpy(istr, fmt::format(_("{:s} of {:s}"), Items[i]._iIName, _(ItemSuffixes[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); + 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 ITYPE_RING: - case ITYPE_AMULET: - GetItemPower(i, minlvl, maxlvl, PLT_MISC, onlygood); + case DIFF_NIGHTMARE: + rndv = 5 * (itemlevel + 16) + GenerateRnd(10 * (itemlevel + 16)); break; - case ITYPE_NONE: - case ITYPE_MISC: - case ITYPE_GOLD: + 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]); } void SetupItem(int i) @@ -2223,234 +3367,25 @@ int RndItem(int m) ri--; } - int r = GenerateRnd(ri); - return ril[r] + 1; -} - -int RndUItem(int m) -{ - if (m != -1 && (Monsters[m].MData->mTreasure & 0x8000) != 0 && !gbIsMultiplayer) - return -((Monsters[m].MData->mTreasure & 0xFFF) + 1); - - int ril[512]; - - int curlv = items_get_currlevel(); - 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 (m != -1) { - if (Monsters[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() -{ - if (GenerateRnd(100) > 25) - return 0; - - int ril[512]; - - 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(ItemStruct &item, _unique_items uid) -{ - UniqueItemFlags[uid] = true; - - for (int i = 0; i < UniqueItemList[uid].UINumPL; i++) { - SaveItemPower(item, UniqueItemList[uid].powers[i]); - } - - strcpy(item._iIName, _(UniqueItemList[uid].UIName)); - item._iIvalue = UniqueItemList[uid].UIValue; - - if (item._iMiscId == IMISC_UNIQUE) - item._iSeed = uid; - - item._iUid = uid; - item._iMagical = ITEM_QUALITY_UNIQUE; - item._iCreateInfo |= CF_UNIQUE; -} - -void SpawnUnique(_unique_items uid, Point position) -{ - if (ActiveItemCount >= 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(Items[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(Items[ii], uid); - } - } - if (Items[ii]._iMagical != ITEM_QUALITY_UNIQUE) - ItemRndDur(ii); - } else { - if (Items[ii]._iLoc != ILOC_UNEQUIPABLE) { - GetUniqueItem(Items[ii], (_unique_items)iseed); // uid is stored in iseed for uniques - } - } + int r = GenerateRnd(ri); + return ril[r] + 1; +} + +void SpawnUnique(_unique_items uid, Point position) +{ + if (ActiveItemCount >= 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(Items[ii], uid); SetupItem(ii); } @@ -2499,23 +3434,6 @@ void SpawnItem(int m, Point position, bool sendmsg) NetSendCmdDItem(false, ii); } -static void SetupBaseItem(Point position, int idx, bool onlygood, bool sendmsg, bool delta) -{ - if (ActiveItemCount >= 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(); @@ -2523,53 +3441,6 @@ void CreateRndItem(Point position, bool onlygood, bool sendmsg, bool delta) 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 (ActiveItemCount >= MAXITEMS) @@ -2692,22 +3563,6 @@ void CornerstoneSave() } } -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 CornerstoneLoad(Point position) { PkItemStruct pkSItem; @@ -2789,33 +3644,6 @@ void SpawnQuestItem(int itemid, Point position, int randarea, int selflag) } } -void SpawnRock() -{ - if (ActiveItemCount >= MAXITEMS) - return; - - int oi; - bool ostand = false; - for (int i = 0; i < ActiveObjectCount && !ostand; i++) { - oi = ActiveObjects[i]; - ostand = Objects[oi]._otype == OBJ_STAND; - } - - if (!ostand) - return; - - int ii = AllocateItem(); - - Items[ii].position = Objects[oi].position; - dItem[Objects[oi].position.x][Objects[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 (ActiveItemCount >= MAXITEMS) @@ -2872,26 +3700,6 @@ void DeleteItem(int ii, int i) ActiveItems[i] = ActiveItems[ActiveItemCount]; } -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 < ActiveItemCount; i++) { @@ -2961,30 +3769,6 @@ void CheckIdentify(int pnum, int cii) 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(i->_iDurability + rep, i->_iMaxDur); -} - void DoRepair(int pnum, int cii) { ItemStruct *pi; @@ -2995,161 +3779,36 @@ void DoRepair(int pnum, int cii) if (cii >= NUM_INVLOC) { pi = &player.InvList[cii - NUM_INVLOC]; - } else { - pi = &player.InvBody[cii]; - } - - RepairItem(pi, player._pLevel); - CalcPlrInv(pnum, true); - - if (pnum == MyPlayerId) - 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 = Players[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 == MyPlayerId) - 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; + } else { + pi = &player.InvBody[cii]; } - 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(x->_iDurability, x->_iMaxDur); - } else { - if (x->_iMaxDur >= 100) { - return true; - } - x->_iMaxDur++; - x->_iDurability = x->_iMaxDur; - } - break; - case IMISC_OILFORT: - if (x->_iMaxDur != DUR_INDESTRUCTIBLE && 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; + RepairItem(pi, player._pLevel); + CalcPlrInv(pnum, true); + + if (pnum == MyPlayerId) + NewCursor(CURSOR_HAND); +} + +void DoRecharge(int pnum, int cii) +{ + ItemStruct *pi; + + auto &player = Players[pnum]; + if (cii >= NUM_INVLOC) { + pi = &player.InvList[cii - NUM_INVLOC]; + } else { + pi = &player.InvBody[cii]; } - return true; + 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 == MyPlayerId) + NewCursor(CURSOR_HAND); } void DoOil(int pnum, int cii) @@ -3165,148 +3824,6 @@ void DoOil(int pnum, int cii) } } -void PrintItemOil(char iDidx) -{ - switch (iDidx) { - case IMISC_OILACC: - strcpy(tempstr, _("increases a weapon's")); - AddPanelString(tempstr); - strcpy(tempstr, _("chance to hit")); - AddPanelString(tempstr); - break; - case IMISC_OILMAST: - strcpy(tempstr, _("greatly increases a")); - AddPanelString(tempstr); - strcpy(tempstr, _("weapon's chance to hit")); - AddPanelString(tempstr); - break; - case IMISC_OILSHARP: - strcpy(tempstr, _("increases a weapon's")); - AddPanelString(tempstr); - strcpy(tempstr, _("damage potential")); - AddPanelString(tempstr); - break; - case IMISC_OILDEATH: - strcpy(tempstr, _("greatly increases a weapon's")); - AddPanelString(tempstr); - strcpy(tempstr, _("damage potential - not bows")); - AddPanelString(tempstr); - break; - case IMISC_OILSKILL: - strcpy(tempstr, _("reduces attributes needed")); - AddPanelString(tempstr); - strcpy(tempstr, _("to use armor or weapons")); - AddPanelString(tempstr); - break; - case IMISC_OILBSMTH: - /*xgettext:no-c-format*/ strcpy(tempstr, _("restores 20% of an")); - AddPanelString(tempstr); - strcpy(tempstr, _("item's durability")); - AddPanelString(tempstr); - break; - case IMISC_OILFORT: - strcpy(tempstr, _("increases an item's")); - AddPanelString(tempstr); - strcpy(tempstr, _("current and max durability")); - AddPanelString(tempstr); - break; - case IMISC_OILPERM: - strcpy(tempstr, _("makes an item indestructible")); - AddPanelString(tempstr); - break; - case IMISC_OILHARD: - strcpy(tempstr, _("increases the armor class")); - AddPanelString(tempstr); - strcpy(tempstr, _("of armor and shields")); - AddPanelString(tempstr); - break; - case IMISC_OILIMP: - strcpy(tempstr, _("greatly increases the armor")); - AddPanelString(tempstr); - strcpy(tempstr, _("class of armor and shields")); - AddPanelString(tempstr); - break; - case IMISC_RUNEF: - strcpy(tempstr, _("sets fire trap")); - AddPanelString(tempstr); - break; - case IMISC_RUNEL: - case IMISC_GR_RUNEL: - strcpy(tempstr, _("sets lightning trap")); - AddPanelString(tempstr); - break; - case IMISC_GR_RUNEF: - strcpy(tempstr, _("sets fire trap")); - AddPanelString(tempstr); - break; - case IMISC_RUNES: - strcpy(tempstr, _("sets petrification trap")); - AddPanelString(tempstr); - break; - case IMISC_FULLHEAL: - strcpy(tempstr, _("restore all life")); - AddPanelString(tempstr); - break; - case IMISC_HEAL: - strcpy(tempstr, _("restore some life")); - AddPanelString(tempstr); - break; - case IMISC_OLDHEAL: - strcpy(tempstr, _("recover life")); - AddPanelString(tempstr); - break; - case IMISC_DEADHEAL: - strcpy(tempstr, _("deadly heal")); - AddPanelString(tempstr); - break; - case IMISC_MANA: - strcpy(tempstr, _("restore some mana")); - AddPanelString(tempstr); - break; - case IMISC_FULLMANA: - strcpy(tempstr, _("restore all mana")); - AddPanelString(tempstr); - break; - case IMISC_ELIXSTR: - strcpy(tempstr, _("increase strength")); - AddPanelString(tempstr); - break; - case IMISC_ELIXMAG: - strcpy(tempstr, _("increase magic")); - AddPanelString(tempstr); - break; - case IMISC_ELIXDEX: - strcpy(tempstr, _("increase dexterity")); - AddPanelString(tempstr); - break; - case IMISC_ELIXVIT: - strcpy(tempstr, _("increase vitality")); - AddPanelString(tempstr); - break; - case IMISC_ELIXWEAK: - case IMISC_ELIXDIS: - strcpy(tempstr, _("decrease strength")); - AddPanelString(tempstr); - break; - case IMISC_ELIXCLUM: - strcpy(tempstr, _("decrease dexterity")); - AddPanelString(tempstr); - break; - case IMISC_ELIXSICK: - strcpy(tempstr, _("decrease vitality")); - AddPanelString(tempstr); - break; - case IMISC_REJUV: - strcpy(tempstr, _("restore some life and mana")); - AddPanelString(tempstr); - break; - case IMISC_FULLREJUV: - strcpy(tempstr, _("restore all life and mana")); - AddPanelString(tempstr); - break; - } -} - void PrintItemPower(char plidx, ItemStruct *x) { switch (plidx) { @@ -3588,27 +4105,12 @@ void PrintItemPower(char plidx, ItemStruct *x) /*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 Surface &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 Surface &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) + /*xgettext:no-c-format*/ strcpy(tempstr, _("40% Health moved to Mana")); + break; + default: + strcpy(tempstr, _("Another ability (NW)")); + break; + } } void DrawUniqueInfo(const Surface &out) @@ -3655,74 +4157,6 @@ void DrawUniqueInfo(const Surface &out) } } -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) { @@ -4013,87 +4447,6 @@ void UseItem(int p, item_misc_id mid, spell_id spl) } } -bool StoreStatOk(ItemStruct *h) -{ - const auto &myPlayer = Players[MyPlayerId]; - - 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 -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(0, lvl); -} - -void SortVendor(ItemStruct *itemList) -{ - int count = 1; - while (!itemList[count].isEmpty()) - count++; - - auto cmp = [](const ItemStruct &a, const ItemStruct &b) { - return a.IDidx < b.IDidx; - }; - - std::sort(itemList, itemList + count, cmp); -} - void SpawnSmith(int lvl) { constexpr int PinnedItemCount = 0; @@ -4122,122 +4475,10 @@ void SpawnSmith(int lvl) 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(minlvl, maxlvl); -} - -static void SpawnOnePremium(int i, int plvl, int playerId) -{ - int itemValue = 0; - bool keepGoing = false; - ItemStruct tempItem = Items[0]; - - auto &player = Players[playerId]; - - int strength = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Strength), player._pStrength); - int dexterity = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Dexterity), player._pDexterity); - int magic = std::max(player.GetMaximumAttributeValue(CharacterAttribute::Magic), player._pMagic); - strength += strength / 5; - dexterity += dexterity / 5; - magic += magic / 5; - - plvl = clamp(plvl, 1, 30); - - int count = 0; - - do { - keepGoing = false; - memset(&Items[0], 0, sizeof(*Items)); - Items[0]._iSeed = AdvanceRndSeed(); - int itemType = RndPremiumItem(plvl / 4, plvl) - 1; - GetItemAttrs(0, itemType, 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 = player.GetMostValuableItem( - [](const ItemStruct &item) { - return item._itype == ITYPE_LARMOR - || item._itype == ITYPE_MARMOR - || item._itype == ITYPE_HARMOR; - }); - - itemValue = mostValuablePlayerArmor == nullptr ? 0 : mostValuablePlayerArmor->_iIvalue; - break; - } - case ITYPE_SHIELD: - case ITYPE_AXE: - case ITYPE_BOW: - case ITYPE_MACE: - case ITYPE_SWORD: - case ITYPE_HELM: - case ITYPE_STAFF: - case ITYPE_RING: - case ITYPE_AMULET: { - const auto *const mostValuablePlayerItem = player.GetMostValuableItem( - [](const ItemStruct &item) { return item._itype == Items[0]._itype; }); - - itemValue = mostValuablePlayerItem == nullptr ? 0 : mostValuablePlayerItem->_iIvalue; - break; - } - default: - itemValue = 0; - break; - } - itemValue = itemValue * 4 / 5; // avoids forced int > float > int conversion + smithitem[i]._itype = ITYPE_NONE; - count++; - } while (keepGoing - || (( - Items[0]._iIvalue > 200000 - || Items[0]._iMinStr > strength - || Items[0]._iMinMag > magic - || Items[0]._iMinDex > dexterity - || Items[0]._iIvalue < itemValue) - && count < 150)); - premiumitems[i] = Items[0]; - premiumitems[i]._iCreateInfo = plvl | CF_SMITHPREMIUM; - premiumitems[i]._iIdentified = true; - premiumitems[i]._iStatFlag = StoreStatOk(&premiumitems[i]); - Items[0] = tempItem; + SortVendor(smithitem + PinnedItemCount); + Items[0] = holditem; } void SpawnPremium(int pnum) @@ -4273,51 +4514,6 @@ void SpawnPremium(int 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(0, lvl); -} - -void WitchBookLevel(int ii) -{ - if (witchitem[ii]._iMiscId != IMISC_BOOK) - return; - witchitem[ii]._iMinMag = spelldata[witchitem[ii]._iSpell].sMinInt; - int8_t spellLevel = Players[MyPlayerId]._pSplLvl[witchitem[ii]._iSpell]; - while (spellLevel > 0) { - witchitem[ii]._iMinMag += 20 * witchitem[ii]._iMinMag / 100; - spellLevel--; - if (witchitem[ii]._iMinMag + 20 * witchitem[ii]._iMinMag / 100 > 255) { - witchitem[ii]._iMinMag = 255; - spellLevel = 0; - } - } -} - void SpawnWitch(int lvl) { constexpr int PinnedItemCount = 3; @@ -4396,11 +4592,6 @@ void SpawnWitch(int lvl) SortVendor(witchitem + PinnedItemCount); } -int RndBoyItem(int lvl) -{ - return RndVendorItem(0, lvl); -} - void SpawnBoy(int lvl) { int ivalue = 0; @@ -4516,42 +4707,6 @@ void SpawnBoy(int lvl) 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 = Players[MyPlayerId]; - - if (AllItemsList[i].iMiscId == IMISC_ELIXSTR) - return !gbIsHellfire || myPlayer._pBaseStr < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Strength); - if (AllItemsList[i].iMiscId == IMISC_ELIXMAG) - return !gbIsHellfire || myPlayer._pBaseMag < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Magic); - if (AllItemsList[i].iMiscId == IMISC_ELIXDEX) - return !gbIsHellfire || myPlayer._pBaseDex < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Dexterity); - if (AllItemsList[i].iMiscId == IMISC_ELIXVIT) - return !gbIsHellfire || myPlayer._pBaseVit < myPlayer.GetMaximumAttributeValue(CharacterAttribute::Vitality); - } - - if (AllItemsList[i].iMiscId == IMISC_REJUV) - return true; - if (AllItemsList[i].iMiscId == IMISC_FULLREJUV) - return true; - - return false; -} - -int RndHealerItem(int lvl) -{ - return RndVendorItem(0, lvl); -} - void SpawnHealer(int lvl) { constexpr int PinnedItemCount = 2; @@ -4606,121 +4761,6 @@ void SpawnStoreGold() 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 = ActiveItems[ActiveItemCount - 1]; @@ -4762,30 +4802,6 @@ void CreateSpellBook(Point position, spell_id ispell, bool sendmsg, bool delta) DeltaAddItem(ii); } -static void CreateMagicItem(Point position, int lvl, int imisc, int imid, int icurs, bool sendmsg, bool delta) -{ - if (ActiveItemCount >= 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(); @@ -4808,20 +4824,6 @@ void CreateMagicWeapon(Point position, int imisc, int icurs, bool sendmsg, bool 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(); diff --git a/Source/items.h b/Source/items.h index cf4f1a8f8..84b934033 100644 --- a/Source/items.h +++ b/Source/items.h @@ -420,7 +420,6 @@ bool ItemSpaceOk(Point position); int AllocateItem(); Point GetSuperItemLoc(Point position); void GetItemAttrs(int i, int idata, int lvl); -void GetItemPower(int i, int minlvl, int maxlvl, affix_item_type flgs, bool onlygood); void SetupItem(int i); int RndItem(int m); void SpawnUnique(_unique_items uid, Point position); @@ -433,7 +432,6 @@ void RecreateEar(int ii, uint16_t ic, int iseed, int Id, int dur, int mdur, int void CornerstoneSave(); void CornerstoneLoad(Point position); void SpawnQuestItem(int itemid, Point position, int randarea, int selflag); -void SpawnRock(); void SpawnRewardItem(int itemid, Point position); void SpawnMapOfDoom(Point position); void SpawnRuneBomb(Point position); @@ -453,16 +451,12 @@ void DrawUniqueInfo(const Surface &out); void PrintItemDetails(ItemStruct *x); void PrintItemDur(ItemStruct *x); void UseItem(int p, item_misc_id Mid, spell_id spl); -bool StoreStatOk(ItemStruct *h); void SpawnSmith(int lvl); void SpawnPremium(int pnum); -void WitchBookLevel(int ii); void SpawnWitch(int lvl); void SpawnBoy(int lvl); void SpawnHealer(int lvl); void SpawnStoreGold(); -void RecreateTownItem(int ii, int idx, uint16_t icreateinfo, int iseed); -void RecalcStoreStats(); int ItemNoFlippy(); void CreateSpellBook(Point position, spell_id ispell, bool sendmsg, bool delta); void CreateMagicArmor(Point position, int imisc, int icurs, bool sendmsg, bool delta); diff --git a/Source/movie.cpp b/Source/movie.cpp index 721e79095..048e01dd6 100644 --- a/Source/movie.cpp +++ b/Source/movie.cpp @@ -68,4 +68,19 @@ void play_movie(const char *pszMovie, bool userCanClose) OutputToLogical(&MousePosition.x, &MousePosition.y); } +/** + * @brief Fade to black and play a video + * @param pszMovie file path of movie + */ +void PlayInGameMovie(const char *pszMovie) +{ + PaletteFadeOut(8); + play_movie(pszMovie, false); + ClearScreenBuffer(); + force_redraw = 255; + scrollrt_draw_game_screen(); + PaletteFadeIn(8); + force_redraw = 255; +} + } // namespace devilution diff --git a/Source/movie.h b/Source/movie.h index de0d4a692..cad3426c7 100644 --- a/Source/movie.h +++ b/Source/movie.h @@ -11,5 +11,6 @@ extern bool movie_playing; extern bool loop_movie; void play_movie(const char *pszMovie, bool user_can_close); +void PlayInGameMovie(const char *pszMovie); } // namespace devilution