diff --git a/Source/control.cpp b/Source/control.cpp index f689e3660..47c3dde4f 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -383,9 +383,34 @@ std::string TextCmdArena(const string_view parameter) return ret; } +std::string TextCmdArenaPot(const string_view parameter) +{ + std::string ret; + if (!gbIsMultiplayer) { + StrAppend(ret, _("Arenas are only supported in multiplayer.")); + return ret; + } + + Player &myPlayer = *MyPlayer; + + for (int potNumber = std::max(1, atoi(parameter.data())); potNumber > 0; potNumber--) { + Item &item = myPlayer.InvList[myPlayer._pNumInv]; + InitializeItem(item, IDI_ARENAPOT); + GenerateNewSeed(item); + item.updateRequiredStatsCacheForPlayer(myPlayer); + + if (!AutoPlaceItemInBelt(myPlayer, item, true) && !AutoPlaceItemInInventory(myPlayer, item, true)) { + break; // inventory is full + } + } + + return ret; +} + std::vector TextCmdList = { { N_("/help"), N_("Prints help overview or help for a specific command."), N_("({command})"), &TextCmdHelp }, - { N_("/arena"), N_("Enter a PvP Arena."), N_("{arena-number}"), &TextCmdArena } + { N_("/arena"), N_("Enter a PvP Arena."), N_("{arena-number}"), &TextCmdArena }, + { N_("/arenapot"), N_("Gives Arena Potions."), N_("{number}"), &TextCmdArenaPot }, }; bool CheckTextCommand(const string_view text) diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index c32d53b7a..e5e100b5a 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1804,7 +1804,7 @@ void UseBeltItem(int type) continue; } - bool isRejuvenation = IsAnyOf(item._iMiscId, IMISC_REJUV, IMISC_FULLREJUV); + bool isRejuvenation = IsAnyOf(item._iMiscId, IMISC_REJUV, IMISC_FULLREJUV) || (item._iMiscId == IMISC_ARENAPOT && MyPlayer->isOnArenaLevel()); bool isHealing = isRejuvenation || IsAnyOf(item._iMiscId, IMISC_HEAL, IMISC_FULLHEAL) || item.isScrollOf(SpellID::Healing); bool isMana = isRejuvenation || IsAnyOf(item._iMiscId, IMISC_MANA, IMISC_FULLMANA); diff --git a/Source/inv.cpp b/Source/inv.cpp index 8928b5b9a..98ce6ff8c 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -2078,6 +2078,11 @@ bool UseInvItem(size_t pnum, int cii) return true; } + if (item->_iMiscId == IMISC_ARENAPOT && !player.isOnArenaLevel()) { + player.Say(HeroSpeech::ThatWontWorkHere); + return true; + } + int idata = ItemCAnimTbl[item->_iCurs]; if (item->_iMiscId == IMISC_BOOK) PlaySFX(IS_RBOOK); diff --git a/Source/itemdat.cpp b/Source/itemdat.cpp index 40944ed28..c34091a03 100644 --- a/Source/itemdat.cpp +++ b/Source/itemdat.cpp @@ -217,6 +217,7 @@ const ItemData AllItemsList[] = { /* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_GREATER_RUNE_OF_LIGHTNING, ItemType::Misc, UITYPE_NONE, N_("Greater Rune of Lightning"), N_("Rune"), 7, 0, 0, 0, 0, 0, 0, 42, 0, ItemSpecialEffect::None, IMISC_GR_RUNEL, SpellID::Null, true, 500 }, /* */ { IDROP_REGULAR, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_RUNE_OF_STONE, ItemType::Misc, UITYPE_NONE, N_("Rune of Stone"), N_("Rune"), 7, 0, 0, 0, 0, 0, 0, 25, 0, ItemSpecialEffect::None, IMISC_RUNES, SpellID::Null, true, 300 }, /*IDI_SORCERER */ { IDROP_NEVER, ICLASS_WEAPON, ILOC_TWOHAND, ICURS_SHORT_STAFF, ItemType::Staff, UITYPE_NONE, N_("Short Staff of Charged Bolt"), nullptr, 1, 25, 2, 4, 0, 0, 0, 20, 0, ItemSpecialEffect::None, IMISC_STAFF, SpellID::ChargedBolt, false, 520 }, +/*IDI_ARENAPOT */ { IDROP_NEVER, ICLASS_MISC, ILOC_UNEQUIPABLE, ICURS_ARENA_POTION, ItemType::Misc, UITYPE_NONE, N_("Arena Potion"), nullptr, 7, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_ARENAPOT, SpellID::Null, true, 0 }, /* */ { IDROP_NEVER, ICLASS_NONE, ILOC_INVALID, ICURS_POTION_OF_FULL_MANA, ItemType::Misc, UITYPE_NONE, nullptr, nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0, ItemSpecialEffect::None, IMISC_NONE, SpellID::Null, false, 0 }, // clang-format on }; diff --git a/Source/itemdat.h b/Source/itemdat.h index fbee854bf..24dc2efd5 100644 --- a/Source/itemdat.h +++ b/Source/itemdat.h @@ -72,8 +72,9 @@ enum _item_indexes : int16_t { // TODO defines all indexes in AllItemsList IDI_BARBARIAN = 139, IDI_RUNEOFSTONE = 165, IDI_SORCERER_DIABLO, + IDI_ARENAPOT, - IDI_LAST = IDI_SORCERER_DIABLO, + IDI_LAST = IDI_ARENAPOT, IDI_NONE = -1, }; @@ -116,6 +117,7 @@ enum item_cursor_graphic : uint8_t { ICURS_RING_OF_TRUTH = 10, ICURS_RING = 12, ICURS_SPECTRAL_ELIXIR = 15, + ICURS_ARENA_POTION = 16, ICURS_GOLDEN_ELIXIR = 17, ICURS_EMPYREAN_BAND = 18, ICURS_EAR_SORCERER = 19, @@ -430,6 +432,7 @@ enum item_misc_id : int8_t { IMISC_RUNELAST, IMISC_AURIC, IMISC_NOTE, + IMISC_ARENAPOT, IMISC_INVALID = -1, }; diff --git a/Source/items.cpp b/Source/items.cpp index 8375073bd..7ef181b78 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -1769,6 +1769,10 @@ void PrintItemOil(char iDidx) case IMISC_FULLREJUV: AddPanelString(_("restore all life and mana")); break; + case IMISC_ARENAPOT: + AddPanelString(_("restore all life and mana")); + AddPanelString(_("(works only in arenas)")); + break; } } @@ -1860,7 +1864,8 @@ void PrintItemMisc(const Item &item) } const bool isOil = (item._iMiscId >= IMISC_USEFIRST && item._iMiscId <= IMISC_USELAST) || (item._iMiscId > IMISC_OILFIRST && item._iMiscId < IMISC_OILLAST) - || (item._iMiscId > IMISC_RUNEFIRST && item._iMiscId < IMISC_RUNELAST); + || (item._iMiscId > IMISC_RUNEFIRST && item._iMiscId < IMISC_RUNELAST) + || item._iMiscId == IMISC_ARENAPOT; const bool isCastOnTarget = (item._iMiscId == IMISC_SCROLLT && item._iSpell != SpellID::Flash) || (item._iMiscId == IMISC_SCROLL && IsAnyOf(item._iSpell, SpellID::TownPortal, SpellID::Identify)); @@ -4028,6 +4033,7 @@ void UseItem(size_t pnum, item_misc_id mid, SpellID spl) } } break; case IMISC_FULLREJUV: + case IMISC_ARENAPOT: player.RestoreFullLife(); player.RestoreFullMana(); if (&player == MyPlayer) { diff --git a/Source/pack.cpp b/Source/pack.cpp index 0ba3ce938..5245fa101 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -39,7 +39,8 @@ void VerifyGoldSeeds(Player &player) void PackItem(ItemPack &packedItem, const Item &item, bool isHellfire) { packedItem = {}; - if (item.isEmpty()) { + // Arena potions don't exist in vanilla so don't save them to stay backward compatible + if (item.isEmpty() || item._iMiscId == IMISC_ARENAPOT) { packedItem.idx = 0xFFFF; } else { auto idx = item.IDidx; diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 85321e3ad..a4bdd7d78 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -83,6 +83,11 @@ Point FindSlotUnderCursor(Point cursorPosition) return InvalidStashPoint; } +bool IsItemAllowedInStash(const Item &item) +{ + return item._iMiscId != IMISC_ARENAPOT; +} + void CheckStashPaste(Point cursorPosition) { Player &player = *MyPlayer; @@ -95,6 +100,9 @@ void CheckStashPaste(Point cursorPosition) cursorPosition -= hotPixelOffset; } + if (!IsItemAllowedInStash(player.HoldItem)) + return; + if (player.HoldItem._itype == ItemType::Gold) { if (Stash.gold > std::numeric_limits::max() - player.HoldItem._ivalue) return; @@ -669,6 +677,9 @@ void GoldWithdrawNewText(string_view text) bool AutoPlaceItemInStash(Player &player, const Item &item, bool persistItem) { + if (!IsItemAllowedInStash(item)) + return false; + if (item._itype == ItemType::Gold) { if (Stash.gold > std::numeric_limits::max() - item._ivalue) return false;