From 14215930ed32d9c07ab8b2d9eb130c4183d41c54 Mon Sep 17 00:00:00 2001 From: sruon Date: Sat, 25 Apr 2026 21:42:29 -0600 Subject: [PATCH 1/2] Route all item creation/lookups through xi::items --- src/common/types/badge.h | 45 +++ src/map/daily_system.cpp | 2 +- src/map/entities/mobentity.cpp | 8 +- src/map/guild.cpp | 2 +- src/map/guild.h | 8 +- src/map/item_container.cpp | 94 +++--- src/map/item_container.h | 18 +- src/map/items/item.cpp | 31 +- src/map/items/item.h | 13 +- src/map/items/item_currency.cpp | 5 + src/map/items/item_currency.h | 2 + src/map/items/item_equipment.cpp | 23 +- src/map/items/item_equipment.h | 5 +- src/map/items/item_flowerpot.cpp | 5 + src/map/items/item_flowerpot.h | 1 + src/map/items/item_furnishing.cpp | 16 +- src/map/items/item_furnishing.h | 5 +- src/map/items/item_general.cpp | 5 + src/map/items/item_general.h | 3 +- src/map/items/item_linkshell.cpp | 7 +- src/map/items/item_linkshell.h | 5 +- src/map/items/item_puppet.cpp | 7 + src/map/items/item_puppet.h | 2 + src/map/items/item_shop.cpp | 9 + src/map/items/item_shop.h | 4 +- src/map/items/item_usable.cpp | 17 +- src/map/items/item_usable.h | 3 +- src/map/items/item_weapon.cpp | 22 +- src/map/items/item_weapon.h | 3 +- src/map/linkshell.cpp | 9 +- src/map/lua/lua_baseentity.cpp | 247 ++++++++------- src/map/lua/lua_baseentity.h | 2 +- src/map/lua/lua_item.cpp | 157 ++++++---- src/map/lua/lua_item.h | 10 +- src/map/lua/luautils.cpp | 20 +- src/map/lua/luautils.h | 14 +- src/map/packets/c2s/0x029_item_move.cpp | 15 +- src/map/packets/c2s/0x053_lockstyle.cpp | 4 +- src/map/packets/c2s/0x083_shop_buy.cpp | 2 +- src/map/packets/c2s/0x0aa_guild_buy.cpp | 2 +- .../packets/c2s/0x0c3_group_comlink_make.cpp | 7 +- .../c2s/0x0c4_group_comlink_active.cpp | 11 +- .../packets/c2s/0x0fe_myroom_plant_crop.cpp | 2 +- src/map/packets/c2s/0x106_bazaar_buy.cpp | 13 +- src/map/packets/s2c/0x116_equipset_valid.cpp | 18 +- src/map/status_effect_container.cpp | 6 +- src/map/treasure_pool.cpp | 10 +- src/map/utils/auctionutils.cpp | 4 +- src/map/utils/charutils.cpp | 291 +++++++++--------- src/map/utils/charutils.h | 4 +- src/map/utils/dboxutils.cpp | 10 +- src/map/utils/fishingutils.cpp | 15 +- src/map/utils/fishingutils.h | 2 +- src/map/utils/guildutils.cpp | 6 +- src/map/utils/itemutils.cpp | 245 ++++++--------- src/map/utils/itemutils.h | 30 +- src/map/utils/puppetutils.cpp | 30 +- src/map/utils/puppetutils.h | 4 +- src/map/utils/synthutils.cpp | 2 +- 59 files changed, 888 insertions(+), 674 deletions(-) create mode 100644 src/common/types/badge.h diff --git a/src/common/types/badge.h b/src/common/types/badge.h new file mode 100644 index 00000000000..776939c04ad --- /dev/null +++ b/src/common/types/badge.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + + Copyright (c) 2026 LandSandBoat Dev Teams + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ + +=========================================================================== +*/ + +#pragma once + +#include "common/macros.h" + +// Badge +// +// Pass-key. +// Only T can construct a Badge, so any method that takes one as a parameter is effectively private to T. +// +// See: https://awesomekling.github.io/Serenity-C++-patterns-The-Badge/ + +namespace xi +{ + +template +class Badge +{ +private: + friend T; + Badge() = default; + DISALLOW_COPY_AND_MOVE(Badge); +}; + +} // namespace xi diff --git a/src/map/daily_system.cpp b/src/map/daily_system.cpp index 9be0001a49f..8158b7b3231 100644 --- a/src/map/daily_system.cpp +++ b/src/map/daily_system.cpp @@ -86,7 +86,7 @@ uint16 SelectItem(CCharEntity* player, uint8 dial) uint16 selection = xirand::GetRandomElement(dialItems.get()); // Check if Rare item is already owned and substitute with Goblin trash item. - if (itemutils::GetItem(selection)->hasFlag(ItemFlag::Rare) && charutils::HasItem(player, selection)) + if (xi::items::lookup(selection)->hasFlag(ItemFlag::Rare) && charutils::HasItem(player, selection)) { dialItems = gobbieJunk; selection = xirand::GetRandomElement(dialItems.get()); diff --git a/src/map/entities/mobentity.cpp b/src/map/entities/mobentity.cpp index ec096f11691..913df9f2ea7 100644 --- a/src/map/entities/mobentity.cpp +++ b/src/map/entities/mobentity.cpp @@ -161,10 +161,10 @@ CMobEntity::CMobEntity() PEnmityContainer = new CEnmityContainer(this); SpellContainer = new CMobSpellContainer(this); - m_Weapons[SLOT_MAIN] = new CItemWeapon(0); - m_Weapons[SLOT_SUB] = new CItemWeapon(0); - m_Weapons[SLOT_RANGED] = new CItemWeapon(0); - m_Weapons[SLOT_AMMO] = new CItemWeapon(0); + m_Weapons[SLOT_MAIN] = std::make_unique(0).release(); + m_Weapons[SLOT_SUB] = std::make_unique(0).release(); + m_Weapons[SLOT_RANGED] = std::make_unique(0).release(); + m_Weapons[SLOT_AMMO] = std::make_unique(0).release(); PAI = std::make_unique(this, std::make_unique(this), std::make_unique(this), std::make_unique(this)); } diff --git a/src/map/guild.cpp b/src/map/guild.cpp index 13bf5dae99f..6d2825dfe99 100644 --- a/src/map/guild.cpp +++ b/src/map/guild.cpp @@ -73,7 +73,7 @@ void CGuild::updateGuildPointsPattern(uint8 pattern) const auto points = rset->get("points"); const auto maxPoints = rset->get("max_points"); - m_GPItems[i].emplace_back(itemutils::GetItemPointer(itemId), maxPoints, points); + m_GPItems[i].emplace_back(xi::items::lookup(itemId), maxPoints, points); } } } diff --git a/src/map/guild.h b/src/map/guild.h index 36efe86a151..d24c949325b 100644 --- a/src/map/guild.h +++ b/src/map/guild.h @@ -35,11 +35,11 @@ class CCharEntity; struct GPItem_t { - CItem* item; - uint16 maxpoints; - uint16 points; + const CItem* item; + uint16 maxpoints; + uint16 points; - GPItem_t(CItem* _item, uint16 _maxpoints, uint16 _points) + GPItem_t(const CItem* _item, uint16 _maxpoints, uint16 _points) : item(_item) , maxpoints(_maxpoints) , points(_points) diff --git a/src/map/item_container.cpp b/src/map/item_container.cpp index e9712072945..e69e967a9f4 100644 --- a/src/map/item_container.cpp +++ b/src/map/item_container.cpp @@ -21,8 +21,6 @@ #include "common/logging.h" -#include - #include "item_container.h" #include "utils/itemutils.h" @@ -34,16 +32,9 @@ CItemContainer::CItemContainer(uint16 LocationID) , m_size(0) , m_count(0) { - std::memset(m_ItemList, 0, sizeof(m_ItemList)); } -CItemContainer::~CItemContainer() -{ - for (uint8 SlotID = 0; SlotID <= m_size; ++SlotID) - { - destroy(m_ItemList[SlotID]); - } -} +CItemContainer::~CItemContainer() = default; uint16 CItemContainer::GetID() const { @@ -121,7 +112,7 @@ uint8 CItemContainer::AddSize(int8 size) return -1; } -uint8 CItemContainer::InsertItem(CItem* PItem) +auto CItemContainer::InsertItem(std::unique_ptr PItem) -> uint8 { if (PItem == nullptr) { @@ -138,55 +129,85 @@ uint8 CItemContainer::InsertItem(CItem* PItem) PItem->setSlotID(SlotID); PItem->setLocationID((uint8)m_id); - m_ItemList[SlotID] = PItem; + m_ItemList[SlotID] = std::move(PItem); return SlotID; } } ShowDebug("ItemContainer: Container is full"); - - // destroy(PItem); //TODO: what if the item is a valid item?? return ERROR_SLOTID; } /************************************************************************ * * - * Add an item to the specified cell. nullptr removes an item * + * Add an item to the specified cell. * * * ************************************************************************/ -uint8 CItemContainer::InsertItem(CItem* PItem, uint8 SlotID) +auto CItemContainer::InsertItem(std::unique_ptr PItem, uint8 SlotID) -> uint8 { - if (SlotID <= m_size) + if (SlotID > m_size) { - if (PItem != nullptr) - { - PItem->setSlotID(SlotID); - PItem->setLocationID((uint8)m_id); + ShowDebug("ItemContainer: SlotID %i is out of range", SlotID); + return ERROR_SLOTID; + } - if (m_ItemList[SlotID] == nullptr && SlotID != 0) - { - m_count++; - } - } - else if (m_ItemList[SlotID] != nullptr && SlotID != 0) + PItem->setSlotID(SlotID); + PItem->setLocationID((uint8)m_id); + + if (m_ItemList[SlotID] == nullptr && SlotID != 0) + { + m_count++; + } + + m_ItemList[SlotID] = std::move(PItem); + return SlotID; +} + +auto CItemContainer::RemoveItem(uint8 SlotID) -> std::unique_ptr +{ + if (SlotID > m_size) + { + return nullptr; + } + + if (m_ItemList[SlotID] != nullptr && SlotID != 0) + { + m_count--; + } + + return std::move(m_ItemList[SlotID]); +} + +auto CItemContainer::MoveItemTo(uint8 fromSlot, CItemContainer& dst, std::optional dstSlot) -> uint8 +{ + if (dstSlot.has_value()) + { + if (*dstSlot > dst.m_size || dst.m_ItemList[*dstSlot] != nullptr) { - m_count--; + return ERROR_SLOTID; } + } + else if (dst.GetFreeSlotsCount() == 0) + { + return ERROR_SLOTID; + } - m_ItemList[SlotID] = PItem; - return SlotID; + auto PItem = RemoveItem(fromSlot); + if (PItem == nullptr) + { + return ERROR_SLOTID; } - ShowDebug("ItemContainer: SlotID %i is out of range", SlotID); - destroy(PItem); - return ERROR_SLOTID; + return dstSlot.has_value() + ? dst.InsertItem(std::move(PItem), *dstSlot) + : dst.InsertItem(std::move(PItem)); } -CItem* CItemContainer::GetItem(uint8 slotID) const +auto CItemContainer::GetItem(uint8 slotID) const -> CItem* { if (slotID <= m_size) { - return m_ItemList[slotID]; + return m_ItemList[slotID].get(); } return nullptr; @@ -237,7 +258,6 @@ void CItemContainer::Clear() { for (uint8 SlotID = 0; SlotID <= m_size; ++SlotID) { - destroy(m_ItemList[SlotID]); - m_ItemList[SlotID] = nullptr; + m_ItemList[SlotID].reset(); } } diff --git a/src/map/item_container.h b/src/map/item_container.h index cee4954d2ac..e83c645d928 100644 --- a/src/map/item_container.h +++ b/src/map/item_container.h @@ -26,6 +26,10 @@ #include "common/logging.h" #include "common/timer.h" +#include +#include +#include + // TODO: Enum class enum CONTAINER_ID : uint8 { @@ -72,14 +76,16 @@ class CItemContainer auto SearchItems(uint16 itemId) const -> std::vector; uint8 SearchItemWithSpace(uint16 ItemID, uint32 quantity); // search for item that has space to accomodate x items added - uint8 InsertItem(CItem* PItem); // add a pre-created item to a free cell - uint8 InsertItem(CItem* PItem, uint8 slotID); // add a pre-created item to the selected cell + auto InsertItem(std::unique_ptr PItem) -> uint8; + auto InsertItem(std::unique_ptr PItem, uint8 slotID) -> uint8; + auto RemoveItem(uint8 slotID) -> std::unique_ptr; + auto MoveItemTo(uint8 fromSlot, CItemContainer& dst, std::optional dstSlot = std::nullopt) -> uint8; uint32 SortingPacket; // number of sort requests per clock timer::time_point LastSortingTime; - CItem* GetItem(uint8 slotID) const; // get a pointer to the object located in the specified cell. - void Clear(); // remove all items from container + auto GetItem(uint8 slotID) const -> CItem*; + void Clear(); template void ForEachItem(F func, Args&&... args) @@ -88,7 +94,7 @@ class CItemContainer { if (m_ItemList[SlotID]) { - func(m_ItemList[SlotID], std::forward(args)...); + func(m_ItemList[SlotID].get(), std::forward(args)...); } } } @@ -99,7 +105,7 @@ class CItemContainer uint8 m_size; uint8 m_count; - CItem* m_ItemList[MAX_CONTAINER_SIZE + 1]{}; + std::array, MAX_CONTAINER_SIZE + 1> m_ItemList{}; }; #endif diff --git a/src/map/items/item.cpp b/src/map/items/item.cpp index a6c8693ad65..b6ea95d7a2c 100644 --- a/src/map/items/item.cpp +++ b/src/map/items/item.cpp @@ -51,6 +51,29 @@ CItem::CItem(uint16 id) std::memset(m_extra, 0, sizeof(m_extra)); } +CItem::CItem(const CItem& other) +: m_id(other.m_id) +, m_subid(other.m_subid) +, m_type(other.m_type) +, m_subtype(other.m_subtype) +, m_quantity(other.m_quantity) +, m_reserve(other.m_reserve) +, m_stackSize(other.m_stackSize) +, m_BasePrice(other.m_BasePrice) +, m_CharPrice(other.m_CharPrice) +, m_ahCat(other.m_ahCat) +, m_flag(other.m_flag) +, m_slotID(other.m_slotID) +, m_locationID(other.m_locationID) +, m_sent(other.m_sent) +, dirty_(other.dirty_) +, m_name(other.m_name) +, m_send(other.m_send) +, m_recv(other.m_recv) +{ + std::memcpy(m_extra, other.m_extra, sizeof(m_extra)); +} + CItem::~CItem() = default; /************************************************************************ @@ -259,7 +282,7 @@ uint32 CItem::getCharPrice() const * * ************************************************************************/ -const std::string& CItem::getName() +const std::string& CItem::getName() const { return m_name; } @@ -275,7 +298,7 @@ void CItem::setName(const std::string& name) * * ************************************************************************/ -const std::string& CItem::getSender() +const std::string& CItem::getSender() const { return m_send; } @@ -291,7 +314,7 @@ void CItem::setSender(const std::string& sender) * * ************************************************************************/ -const std::string& CItem::getReceiver() +const std::string& CItem::getReceiver() const { return m_recv; } @@ -307,7 +330,7 @@ void CItem::setReceiver(const std::string& receiver) * * ************************************************************************/ -const std::string CItem::getSignature() +auto CItem::getSignature() const -> const std::string { return Exdata::decodeSignature(this->exdata().Signature); } diff --git a/src/map/items/item.h b/src/map/items/item.h index e18d7e64112..ae5a9ae4006 100644 --- a/src/map/items/item.h +++ b/src/map/items/item.h @@ -55,6 +55,9 @@ class CItem { public: CItem(uint16 id); + CItem(const CItem& other); + auto operator=(const CItem&) -> CItem& = delete; + virtual ~CItem(); uint16 getID() const; @@ -91,17 +94,17 @@ class CItem void setSlotID(uint8 SlotID); void setSent(bool sent); - const std::string& getName(); + const std::string& getName() const; void setName(const std::string& name); - const std::string& getSender(); + const std::string& getSender() const; void setSender(const std::string& sender); - const std::string& getReceiver(); + const std::string& getReceiver() const; void setReceiver(const std::string& receiver); - virtual const std::string getSignature(); - virtual void setSignature(const std::string& signature); + virtual auto getSignature() const -> const std::string; + virtual void setSignature(const std::string& signature); auto isDirty() const -> bool; void setDirty(bool dirty); diff --git a/src/map/items/item_currency.cpp b/src/map/items/item_currency.cpp index 821fa46c999..584021fb908 100644 --- a/src/map/items/item_currency.cpp +++ b/src/map/items/item_currency.cpp @@ -29,3 +29,8 @@ CItemCurrency::CItemCurrency(uint16 id) } CItemCurrency::~CItemCurrency() = default; + +CItemCurrency::CItemCurrency(const CItemCurrency& other) +: CItem(other) +{ +} diff --git a/src/map/items/item_currency.h b/src/map/items/item_currency.h index 2965f4c9bbf..601c6afac37 100644 --- a/src/map/items/item_currency.h +++ b/src/map/items/item_currency.h @@ -30,6 +30,8 @@ class CItemCurrency : public CItem { public: CItemCurrency(uint16); + + CItemCurrency(const CItemCurrency& other); virtual ~CItemCurrency(); private: diff --git a/src/map/items/item_equipment.cpp b/src/map/items/item_equipment.cpp index e36ad8916e1..b69fa96f30e 100644 --- a/src/map/items/item_equipment.cpp +++ b/src/map/items/item_equipment.cpp @@ -98,6 +98,25 @@ CItemEquipment::CItemEquipment(uint16 id) m_superiorLevel = 0; } +CItemEquipment::CItemEquipment(const CItemEquipment& other) +: CItemUsable(other) +, modList(other.modList) +, petModList(other.petModList) +, latentList(other.latentList) +, m_reqLvl(other.m_reqLvl) +, m_iLvl(other.m_iLvl) +, m_jobs(other.m_jobs) +, m_modelID(other.m_modelID) +, m_scriptType(other.m_scriptType) +, m_shieldSize(other.m_shieldSize) +, m_absorption(other.m_absorption) +, m_equipSlotID(other.m_equipSlotID) +, m_removeSlotID(other.m_removeSlotID) +, m_removeSlotLookID(other.m_removeSlotLookID) +, m_superiorLevel(other.m_superiorLevel) +{ +} + CItemEquipment::~CItemEquipment() { } @@ -376,7 +395,7 @@ void CItemEquipment::setTrialNumber(uint16 trial) trialData.TrialId = trial; } -uint16 CItemEquipment::getTrialNumber() +auto CItemEquipment::getTrialNumber() const -> uint16 { return this->exdata().TrialId; } @@ -476,7 +495,7 @@ void CItemEquipment::SetAugmentMod(uint16 type, uint8 value) } } -uint16 CItemEquipment::getAugment(uint8 slot) +auto CItemEquipment::getAugment(uint8 slot) const -> uint16 { auto& augData = this->exdata(); uint16 result = 0; diff --git a/src/map/items/item_equipment.h b/src/map/items/item_equipment.h index 46b45a3f2c5..3f943cf4881 100644 --- a/src/map/items/item_equipment.h +++ b/src/map/items/item_equipment.h @@ -54,6 +54,7 @@ class CItemEquipment : public CItemUsable { public: CItemEquipment(uint16); + CItemEquipment(const CItemEquipment& other); virtual ~CItemEquipment(); struct itemLatent @@ -76,8 +77,8 @@ class CItemEquipment : public CItemUsable uint8 getShieldAbsorption() const; int16 getModifier(Mod mod) const; uint8 getSlotType() const; - uint16 getAugment(uint8 slot); - uint16 getTrialNumber(); + auto getAugment(uint8 slot) const -> uint16; + auto getTrialNumber() const -> uint16; uint8 getSuperiorLevel(); bool IsShield() const; diff --git a/src/map/items/item_flowerpot.cpp b/src/map/items/item_flowerpot.cpp index 3ab7f153c1c..9f5f4f54a00 100644 --- a/src/map/items/item_flowerpot.cpp +++ b/src/map/items/item_flowerpot.cpp @@ -28,6 +28,11 @@ CItemFlowerpot::CItemFlowerpot(uint16 id) { } +CItemFlowerpot::CItemFlowerpot(const CItemFlowerpot& other) +: CItemFurnishing(other) +{ +} + CItemFlowerpot::~CItemFlowerpot() = default; void CItemFlowerpot::cleanPot() diff --git a/src/map/items/item_flowerpot.h b/src/map/items/item_flowerpot.h index 83b45b3cde2..33f6d6e5f12 100644 --- a/src/map/items/item_flowerpot.h +++ b/src/map/items/item_flowerpot.h @@ -84,6 +84,7 @@ class CItemFlowerpot : public CItemFurnishing { public: CItemFlowerpot(uint16 id); + CItemFlowerpot(const CItemFlowerpot& other); virtual ~CItemFlowerpot(); void cleanPot(); diff --git a/src/map/items/item_furnishing.cpp b/src/map/items/item_furnishing.cpp index cdd3d8a9713..7d88738772c 100644 --- a/src/map/items/item_furnishing.cpp +++ b/src/map/items/item_furnishing.cpp @@ -37,6 +37,18 @@ CItemFurnishing::CItemFurnishing(uint16 id) setType(ITEM_FURNISHING); } +CItemFurnishing::CItemFurnishing(const CItemFurnishing& other) +: CItem(other) +, m_storage(other.m_storage) +, m_moghancement(other.m_moghancement) +, m_element(other.m_element) +, m_aura(other.m_aura) +, size_(other.size_) +, height_(other.height_) +, placement_(other.placement_) +{ +} + CItemFurnishing::~CItemFurnishing() = default; void CItemFurnishing::setInstalled(bool installed) @@ -44,7 +56,7 @@ void CItemFurnishing::setInstalled(bool installed) this->exdata().Installed = installed ? 1 : 0; } -bool CItemFurnishing::isInstalled() +auto CItemFurnishing::isInstalled() const -> bool { return this->exdata().Installed; } @@ -199,7 +211,7 @@ bool CItemFurnishing::getOn2ndFloor() return this->exdata().On2ndFloor; } -auto CItemFurnishing::getSignature() -> const std::string +auto CItemFurnishing::getSignature() const -> const std::string { return Exdata::decodeSignature(this->exdata().Signature); } diff --git a/src/map/items/item_furnishing.h b/src/map/items/item_furnishing.h index 3bd1fc19200..34460695deb 100644 --- a/src/map/items/item_furnishing.h +++ b/src/map/items/item_furnishing.h @@ -105,6 +105,7 @@ class CItemFurnishing : public CItem { public: CItemFurnishing(uint16); + CItemFurnishing(const CItemFurnishing& other); virtual ~CItemFurnishing(); uint8 getStorage() const; @@ -115,7 +116,7 @@ class CItemFurnishing : public CItem auto height() const -> uint16; auto placement() const -> FurnishingPlacement; - bool isInstalled(); + auto isInstalled() const -> bool; uint8 getCol(); uint8 getRow(); uint8 getLevel(); @@ -145,7 +146,7 @@ class CItemFurnishing : public CItem void setOn2ndFloor(bool on2ndFloor); bool getOn2ndFloor(); - auto getSignature() -> const std::string override; + auto getSignature() const -> const std::string override; void setSignature(const std::string& signature) override; bool isGardeningPot() const; diff --git a/src/map/items/item_general.cpp b/src/map/items/item_general.cpp index 1c408f649d4..ac9140c5534 100644 --- a/src/map/items/item_general.cpp +++ b/src/map/items/item_general.cpp @@ -27,4 +27,9 @@ CItemGeneral::CItemGeneral(uint16 id) setType(ITEM_GENERAL); } +CItemGeneral::CItemGeneral(const CItemGeneral& other) +: CItem(other) +{ +} + CItemGeneral::~CItemGeneral() = default; diff --git a/src/map/items/item_general.h b/src/map/items/item_general.h index 609115563d8..715c21fc8f1 100644 --- a/src/map/items/item_general.h +++ b/src/map/items/item_general.h @@ -30,9 +30,8 @@ class CItemGeneral : public CItem { public: CItemGeneral(uint16); + CItemGeneral(const CItemGeneral& other); virtual ~CItemGeneral(); - -private: }; #endif diff --git a/src/map/items/item_linkshell.cpp b/src/map/items/item_linkshell.cpp index 8a4f0187cdb..dd6684b7416 100644 --- a/src/map/items/item_linkshell.cpp +++ b/src/map/items/item_linkshell.cpp @@ -30,6 +30,11 @@ CItemLinkshell::CItemLinkshell(const uint16 id) setType(ITEM_LINKSHELL); } +CItemLinkshell::CItemLinkshell(const CItemLinkshell& other) +: CItem(other) +{ +} + CItemLinkshell::~CItemLinkshell() = default; uint32 CItemLinkshell::GetLSID() @@ -64,7 +69,7 @@ void CItemLinkshell::SetLSColor(const uint16 color) std::memcpy(&this->exdata().Color, &color, sizeof(color)); } -const std::string CItemLinkshell::getSignature() +auto CItemLinkshell::getSignature() const -> const std::string { auto& name = this->exdata().Name; char decoded[LinkshellStringLength] = {}; diff --git a/src/map/items/item_linkshell.h b/src/map/items/item_linkshell.h index 7abf6da41b6..3be17ca6415 100644 --- a/src/map/items/item_linkshell.h +++ b/src/map/items/item_linkshell.h @@ -42,6 +42,7 @@ class CItemLinkshell : public CItem { public: CItemLinkshell(uint16); + CItemLinkshell(const CItemLinkshell& other); virtual ~CItemLinkshell(); uint32 GetLSID(); @@ -50,11 +51,9 @@ class CItemLinkshell : public CItem uint16 GetLSRawColor(); void SetLSID(uint32 lsid); void SetLSColor(uint16 color); - const std::string getSignature() override; + auto getSignature() const -> const std::string override; void setSignature(const std::string& signature) override; void SetLSType(LSTYPE value); - -private: }; #endif diff --git a/src/map/items/item_puppet.cpp b/src/map/items/item_puppet.cpp index 792fb6fe828..f3dd28b8595 100644 --- a/src/map/items/item_puppet.cpp +++ b/src/map/items/item_puppet.cpp @@ -31,6 +31,13 @@ CItemPuppet::CItemPuppet(uint16 id) CItemPuppet::~CItemPuppet() = default; +CItemPuppet::CItemPuppet(const CItemPuppet& other) +: CItem(other) +, m_equipSlot(other.m_equipSlot) +, m_elementSlots(other.m_elementSlots) +{ +} + uint8 CItemPuppet::getEquipSlot() const { return m_equipSlot; diff --git a/src/map/items/item_puppet.h b/src/map/items/item_puppet.h index e8c0f286196..6e068819e32 100644 --- a/src/map/items/item_puppet.h +++ b/src/map/items/item_puppet.h @@ -37,6 +37,8 @@ class CItemPuppet : public CItem { public: CItemPuppet(uint16); + + CItemPuppet(const CItemPuppet& other); virtual ~CItemPuppet(); uint8 getEquipSlot() const; diff --git a/src/map/items/item_shop.cpp b/src/map/items/item_shop.cpp index f9605cf35dd..76b19b92ad8 100644 --- a/src/map/items/item_shop.cpp +++ b/src/map/items/item_shop.cpp @@ -32,6 +32,15 @@ CItemShop::CItemShop(uint16 id) CItemShop::~CItemShop() = default; +CItemShop::CItemShop(const CItemShop& other) +: CItem(other) +, m_MinPrice(other.m_MinPrice) +, m_MaxPrice(other.m_MaxPrice) +, m_DailyIncrease(other.m_DailyIncrease) +, m_InitialQuantity(other.m_InitialQuantity) +{ +} + uint32 CItemShop::getMinPrice() const { return m_MinPrice; diff --git a/src/map/items/item_shop.h b/src/map/items/item_shop.h index 324f21ed654..237796f902b 100644 --- a/src/map/items/item_shop.h +++ b/src/map/items/item_shop.h @@ -29,7 +29,9 @@ class CItemShop : public CItem { public: - CItemShop(uint16 id); + CItemShop(uint16); + + CItemShop(const CItemShop& other); virtual ~CItemShop(); uint32 getMinPrice() const; diff --git a/src/map/items/item_usable.cpp b/src/map/items/item_usable.cpp index 576b53942ab..567d1f05340 100644 --- a/src/map/items/item_usable.cpp +++ b/src/map/items/item_usable.cpp @@ -42,6 +42,21 @@ CItemUsable::CItemUsable(uint16 id) m_AoE = 0; } +CItemUsable::CItemUsable(const CItemUsable& other) +: CItem(other) +, m_UseDelay(other.m_UseDelay) +, m_MaxCharges(other.m_MaxCharges) +, m_Animation(other.m_Animation) +, m_AnimationTime(other.m_AnimationTime) +, m_ActivationTime(other.m_ActivationTime) +, m_ValidTarget(other.m_ValidTarget) +, m_ReuseDelay(other.m_ReuseDelay) +, m_AssignTime(other.m_AssignTime) +, m_LastUseTime(other.m_LastUseTime) +, m_AoE(other.m_AoE) +{ +} + CItemUsable::~CItemUsable() = default; void CItemUsable::setUseDelay(timer::duration UseDelay) @@ -85,7 +100,7 @@ void CItemUsable::setCurrentCharges(uint8 CurrCharges) this->exdata().RemainingCharges = std::clamp(CurrCharges, 0, m_MaxCharges); } -uint8 CItemUsable::getCurrentCharges() +auto CItemUsable::getCurrentCharges() const -> uint8 { return this->exdata().RemainingCharges; } diff --git a/src/map/items/item_usable.h b/src/map/items/item_usable.h index faacc6146a9..b06028a0e0f 100644 --- a/src/map/items/item_usable.h +++ b/src/map/items/item_usable.h @@ -31,10 +31,11 @@ class CItemUsable : public CItem { public: CItemUsable(uint16); + CItemUsable(const CItemUsable& other); virtual ~CItemUsable(); timer::duration getUseDelay() const; - uint8 getCurrentCharges(); + auto getCurrentCharges() const -> uint8; uint8 getMaxCharges() const; auto getAnimationID() const -> ActionAnimation; timer::duration getAnimationTime() const; diff --git a/src/map/items/item_weapon.cpp b/src/map/items/item_weapon.cpp index b854ec8daca..e5721bad5e5 100644 --- a/src/map/items/item_weapon.cpp +++ b/src/map/items/item_weapon.cpp @@ -53,6 +53,26 @@ CItemWeapon::CItemWeapon(uint16 id) m_wsunlockpoints = 0; } +CItemWeapon::CItemWeapon(const CItemWeapon& other) +: CItemEquipment(other) +, m_skillType(other.m_skillType) +, m_subSkillType(other.m_subSkillType) +, m_iLvlSkill(other.m_iLvlSkill) +, m_iLvlParry(other.m_iLvlParry) +, m_iLvlMacc(other.m_iLvlMacc) +, m_damage(other.m_damage) +, m_delay(other.m_delay) +, m_baseDelay(other.m_baseDelay) +, m_dmgType(other.m_dmgType) +, m_effect(other.m_effect) +, m_maxHit(other.m_maxHit) +, m_DPS(other.m_DPS) +, m_wsunlockpoints(other.m_wsunlockpoints) +, m_ranged(other.m_ranged) +, m_twoHanded(other.m_twoHanded) +{ +} + CItemWeapon::~CItemWeapon() = default; /************************************************************************ @@ -364,7 +384,7 @@ uint16 CItemWeapon::getTotalUnlockPointsNeeded() const * * ************************************************************************/ -auto CItemWeapon::getCurrentUnlockPoints() -> uint16 +auto CItemWeapon::getCurrentUnlockPoints() const -> uint16 { return this->exdata().UnlockPoints; } diff --git a/src/map/items/item_weapon.h b/src/map/items/item_weapon.h index 2be93a92c75..c5e92827b29 100644 --- a/src/map/items/item_weapon.h +++ b/src/map/items/item_weapon.h @@ -32,6 +32,7 @@ class CItemWeapon : public CItemEquipment { public: CItemWeapon(uint16); + CItemWeapon(const CItemWeapon& other); virtual ~CItemWeapon(); uint8 getSkillType() const; @@ -47,7 +48,7 @@ class CItemWeapon : public CItemEquipment uint8 getHitCount() const; double getDPS() const; uint16 getTotalUnlockPointsNeeded() const; - uint16 getCurrentUnlockPoints(); + auto getCurrentUnlockPoints() const -> uint16; void resetDelay(); bool addWsPoints(uint16 points); diff --git a/src/map/linkshell.cpp b/src/map/linkshell.cpp index d8d09750783..4fa349bc569 100644 --- a/src/map/linkshell.cpp +++ b/src/map/linkshell.cpp @@ -203,21 +203,22 @@ void CLinkshell::ChangeMemberRank(const std::string& MemberName, const uint8 req if (PItemLinkshell != nullptr && PItemLinkshell->isType(ITEM_LINKSHELL) && PItemLinkshell->GetLSID() == m_id) { - CItemLinkshell* newShellItem = (CItemLinkshell*)itemutils::GetItem(newId); - if (newShellItem == nullptr) + auto PNewItem = xi::items::spawn(newId); + if (PNewItem == nullptr) { return; } + auto* newShellItem = static_cast(PNewItem.get()); newShellItem->setQuantity(1); std::memcpy(newShellItem->m_extra, PItemLinkshell->m_extra, 24); newShellItem->SetLSType(newId == ITEMID::PEARLSACK ? LSTYPE_PEARLSACK : LSTYPE_LINKPEARL); newShellItem->setSubType(ITEM_LOCKED); uint8 LocationID = PItemLinkshell->getLocationID(); uint8 SlotID = PItemLinkshell->getSlotID(); - destroy(PItemLinkshell); + PMember->getStorage(LocationID)->RemoveItem(SlotID); PItemLinkshell = newShellItem; - PMember->getStorage(LocationID)->InsertItem(PItemLinkshell, SlotID); + PMember->getStorage(LocationID)->InsertItem(std::move(PNewItem), SlotID); db::preparedStmt("UPDATE char_inventory SET itemid = ?, extra = ? WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", PItemLinkshell->getID(), PItemLinkshell->m_extra, diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 55f5b30b207..50989c36140 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -4192,66 +4192,65 @@ auto CLuaBaseEntity::addItem(sol::variadic_args va) const -> CItem* while (PChar->getStorage(LOC_INVENTORY)->GetFreeSlotsCount() != 0 && quantity > 0) { - if (CItem* PItem = itemutils::GetItem(id)) + auto PItem = xi::items::spawn(id); + if (PItem == nullptr) { - PItem->setQuantity(quantity); - quantity -= PItem->getStackSize(); + ShowWarning("AddItem: Item <%i> is not found in a database", id); + break; + } - bool silent = table.get_or("silent", false); + PItem->setQuantity(quantity); + quantity -= PItem->getStackSize(); - std::string signature; - sol::object signatureObj = table["signature"]; - if (signatureObj.valid() && signatureObj.is()) - { - signature = signatureObj.as(); - } + bool silent = table.get_or("silent", false); - if (!signature.empty()) - { - PItem->setSignature(signature); - } + std::string signature; + sol::object signatureObj = table["signature"]; + if (signatureObj.valid() && signatureObj.is()) + { + signature = signatureObj.as(); + } - sol::object appraisalObj = table["appraisal"]; - if (appraisalObj.get_type() == sol::type::number) - { - PItem->setAppraisalID(appraisalObj.as()); - } + if (!signature.empty()) + { + PItem->setSignature(signature); + } + + sol::object appraisalObj = table["appraisal"]; + if (appraisalObj.get_type() == sol::type::number) + { + PItem->setAppraisalID(appraisalObj.as()); + } - sol::object exdataObj = table["exdata"]; - if (exdataObj.is()) + sol::object exdataObj = table["exdata"]; + if (exdataObj.is()) + { + auto exdataTable = exdataObj.as(); + if (!Exdata::fromTable(PItem.get(), exdataTable)) { - auto exdataTable = exdataObj.as(); - if (!Exdata::fromTable(PItem, exdataTable)) + for (const auto& [keyObj, valObj] : exdataTable) { - for (const auto& [keyObj, valObj] : exdataTable) - { - uint8 index = keyObj.as(); - uint8 value = valObj.as(); + uint8 index = keyObj.as(); + uint8 value = valObj.as(); - if (index < CItem::extra_size) - { - PItem->m_extra[index] = value; - } - else - { - ShowWarning("AddItem: Trying to write to invalid exdata index: <%i>", index); - } + if (index < CItem::extra_size) + { + PItem->m_extra[index] = value; + } + else + { + ShowWarning("AddItem: Trying to write to invalid exdata index: <%i>", index); } } } - - SlotID = charutils::AddItem(PChar, LOC_INVENTORY, PItem, silent); - if (SlotID == ERROR_SLOTID) - { - break; - } - AddedItem = PItem; } - else + + SlotID = charutils::AddItem(PChar, LOC_INVENTORY, std::move(PItem), silent); + if (SlotID == ERROR_SLOTID) { - ShowWarning("AddItem: Item <%i> is not found in a database", id); break; } + AddedItem = PChar->getStorage(LOC_INVENTORY)->GetItem(SlotID); } } else @@ -4283,25 +4282,24 @@ auto CLuaBaseEntity::addItem(sol::variadic_args va) const -> CItem* while (PChar->getStorage(LOC_INVENTORY)->GetFreeSlotsCount() != 0 && quantity > 0) { - if (CItem* PItem = itemutils::GetItem(itemID)) + auto PItem = xi::items::spawn(itemID); + if (PItem == nullptr) { - PItem->setQuantity(quantity); - quantity -= PItem->getStackSize(); + ShowWarning("AddItem: Item <%i> is not found in a database", itemID); + break; + } - SlotID = charutils::AddItem(PChar, LOC_INVENTORY, PItem, silence); + PItem->setQuantity(quantity); + quantity -= PItem->getStackSize(); - // Paranoid check - if (SlotID == ERROR_SLOTID) - { - break; - } - AddedItem = PItem; - } - else + SlotID = charutils::AddItem(PChar, LOC_INVENTORY, std::move(PItem), silence); + + // Paranoid check + if (SlotID == ERROR_SLOTID) { - ShowWarning("AddItem: Item <%i> is not found in a database", itemID); break; } + AddedItem = PChar->getStorage(LOC_INVENTORY)->GetItem(SlotID); } } @@ -4449,26 +4447,21 @@ bool CLuaBaseEntity::addUsedItem(uint16 itemID) if (PChar->getStorage(LOC_INVENTORY)->GetFreeSlotsCount() != 0) { - CItem* PItem = itemutils::GetItem(itemID); - - if (PItem != nullptr) + auto PItem = xi::items::spawn(itemID); + if (PItem == nullptr) { - if (PItem->isSubType(ITEM_CHARGED)) - { - auto* PUsable = static_cast(PItem); - PUsable->setQuantity(1); - PUsable->setLastUseTime(timer::now()); - SlotID = charutils::AddItem(PChar, LOC_INVENTORY, PUsable, false); - } - else - { - ShowWarning("addUsedItem: tried to setLastUseTime but itemID <%i> is not type ITEM_CHARGED", itemID); - destroy(PItem); - } + ShowWarning("AddItem: Item <%i> is not found in a database", itemID); + } + else if (!PItem->isSubType(ITEM_CHARGED)) + { + ShowWarning("addUsedItem: tried to setLastUseTime but itemID <%i> is not type ITEM_CHARGED", itemID); } else { - ShowWarning("AddItem: Item <%i> is not found in a database", itemID); + auto* PUsable = static_cast(PItem.get()); + PUsable->setQuantity(1); + PUsable->setLastUseTime(timer::now()); + SlotID = charutils::AddItem(PChar, LOC_INVENTORY, std::move(PItem), false); } } @@ -4549,17 +4542,15 @@ bool CLuaBaseEntity::addTempItem(uint16 itemID, const sol::object& arg1) if (PChar->getStorage(LOC_TEMPITEMS)->GetFreeSlotsCount() != 0 && quantity != 0) { - CItem* PItem = itemutils::GetItem(itemID); - - if (PItem != nullptr) + auto PItem = xi::items::spawn(itemID); + if (PItem == nullptr) { - PItem->setQuantity(quantity); - - SlotID = charutils::AddItem(PChar, LOC_TEMPITEMS, PItem); + ShowWarning("AddItem: Item <%i> is not found in a database", itemID); } else { - ShowWarning("AddItem: Item <%i> is not found in a database", itemID); + PItem->setQuantity(quantity); + SlotID = charutils::AddItem(PChar, LOC_TEMPITEMS, std::move(PItem)); } } @@ -4837,46 +4828,48 @@ bool CLuaBaseEntity::addLinkpearl(const std::string& lsname, bool equip) return false; } - CCharEntity* PChar = (CCharEntity*)m_PBaseEntity; - CItemLinkshell* PItemLinkPearl = PChar->m_GMlevel > 0 ? (CItemLinkshell*)itemutils::GetItem(514) : (CItemLinkshell*)itemutils::GetItem(515); - LSTYPE lstype = PChar->m_GMlevel > 0 ? LSTYPE_PEARLSACK : LSTYPE_LINKPEARL; - if (PItemLinkPearl != nullptr) + CCharEntity* PChar = (CCharEntity*)m_PBaseEntity; + auto PItem = xi::items::spawn(PChar->m_GMlevel > 0 ? 514 : 515); + LSTYPE lstype = PChar->m_GMlevel > 0 ? LSTYPE_PEARLSACK : LSTYPE_LINKPEARL; + if (PItem == nullptr) { - const auto rset = db::preparedStmt("SELECT linkshellid, color FROM linkshells WHERE name = ? AND broken = 0", lsname); - if (rset && rset->rowsCount() && rset->next()) - { - // build linkpearl - PItemLinkPearl->setSignature(lsname); - PItemLinkPearl->SetLSID(rset->get("linkshellid")); - PItemLinkPearl->SetLSColor(rset->get("color")); - PItemLinkPearl->SetLSType(lstype); - PItemLinkPearl->setQuantity(1); - if (charutils::AddItem(PChar, LOC_INVENTORY, PItemLinkPearl) != ERROR_SLOTID) - { - // equip linkpearl to slot 2 - if (equip) - { - linkshell::AddOnlineMember(PChar, PItemLinkPearl, 2); - PItemLinkPearl->setSubType(ITEM_LOCKED); - PChar->equip[SLOT_LINK2] = PItemLinkPearl->getSlotID(); - PChar->equipLoc[SLOT_LINK2] = LOC_INVENTORY; - PChar->pushPacket(PItemLinkPearl, ItemLockFlg::Linkshell); - charutils::SaveCharEquip(PChar); - PChar->pushPacket(PChar, PItemLinkPearl->GetLSID()); - PChar->pushPacket(PItemLinkPearl, LOC_INVENTORY, PItemLinkPearl->getSlotID()); - PChar->pushPacket(PChar); - charutils::LoadInventory(PChar); - } - return true; - } - } - else - { - // Linkshell not found, clean up - destroy(PItemLinkPearl); - } + return false; } - return false; + auto* PItemLinkPearl = static_cast(PItem.get()); + + const auto rset = db::preparedStmt("SELECT linkshellid, color FROM linkshells WHERE name = ? AND broken = 0", lsname); + if (!rset || !rset->rowsCount() || !rset->next()) + { + return false; + } + + PItemLinkPearl->setSignature(lsname); + PItemLinkPearl->SetLSID(rset->get("linkshellid")); + PItemLinkPearl->SetLSColor(rset->get("color")); + PItemLinkPearl->SetLSType(lstype); + PItemLinkPearl->setQuantity(1); + + const uint8 slotID = charutils::AddItem(PChar, LOC_INVENTORY, std::move(PItem)); + if (slotID == ERROR_SLOTID) + { + return false; + } + + if (equip) + { + auto* PInserted = static_cast(PChar->getStorage(LOC_INVENTORY)->GetItem(slotID)); + linkshell::AddOnlineMember(PChar, PInserted, 2); + PInserted->setSubType(ITEM_LOCKED); + PChar->equip[SLOT_LINK2] = PInserted->getSlotID(); + PChar->equipLoc[SLOT_LINK2] = LOC_INVENTORY; + PChar->pushPacket(PInserted, ItemLockFlg::Linkshell); + charutils::SaveCharEquip(PChar); + PChar->pushPacket(PChar, PInserted->GetLSID()); + PChar->pushPacket(PInserted, LOC_INVENTORY, PInserted->getSlotID()); + PChar->pushPacket(PChar); + charutils::LoadInventory(PChar); + } + return true; } /************************************************************************ @@ -5065,7 +5058,7 @@ bool CLuaBaseEntity::canEquipItem(uint16 itemID, const sol::object& chkLevel) bool checkLevel = (chkLevel != sol::lua_nil) ? chkLevel.as() : false; - auto* PItem = static_cast(itemutils::GetItemPointer(itemID)); + auto* PItem = xi::items::lookup(itemID); auto* PChar = static_cast(m_PBaseEntity); if (PItem == nullptr) @@ -5530,11 +5523,11 @@ void CLuaBaseEntity::retrieveItemFromSlip(uint16 slipId, uint16 itemId, uint16 e db::preparedStmt(Query, slip->m_extra, PChar->id, slip->getLocationID(), slip->getSlotID()); - auto* item = itemutils::GetItem(itemId); + auto item = xi::items::spawn(itemId); if (item) { item->setQuantity(1); - charutils::AddItem(PChar, LOC_INVENTORY, item); + charutils::AddItem(PChar, LOC_INVENTORY, std::move(item)); } else { @@ -6706,7 +6699,7 @@ void CLuaBaseEntity::changeJob(uint8 newJob) PMob->SetMJob(newJob); // Change weapon type based on new job - CItemWeapon* PWeapon = new CItemWeapon(0); + CItemWeapon* PWeapon = std::make_unique(0).release(); PWeapon->setDelay(4000); PWeapon->setBaseDelay(4000); @@ -16489,7 +16482,7 @@ auto CLuaBaseEntity::hasAttachment(const uint16 itemID) const -> bool return false; } - CItem* PItem = itemutils::GetItemPointer(itemID); + const CItem* PItem = xi::items::lookup(itemID); return puppetutils::HasAttachment(static_cast(m_PBaseEntity), PItem); } @@ -16638,7 +16631,7 @@ auto CLuaBaseEntity::unlockAttachment(const uint16 itemID) const -> bool return false; } - CItem* PItem = itemutils::GetItemPointer(itemID); + const CItem* PItem = xi::items::lookup(itemID); return puppetutils::UnlockAttachment(static_cast(m_PBaseEntity), PItem); } @@ -16705,7 +16698,7 @@ void CLuaBaseEntity::removeAllManeuvers() const * Example : pet:getAttachment(1) ************************************************************************/ -auto CLuaBaseEntity::getAttachment(const uint8 slotId) const -> CItem* +auto CLuaBaseEntity::getAttachment(const uint8 slotId) const -> const CItem* { auto* PAutomaton = dynamic_cast(m_PBaseEntity); @@ -16718,7 +16711,7 @@ auto CLuaBaseEntity::getAttachment(const uint8 slotId) const -> CItem* uint8 slotItem = PAutomaton->getAttachment(slotId); if (slotItem != 0) { - return itemutils::GetItemPointer(0x2100 + slotItem); // TODO: Stop storing by offset + return xi::items::lookup(0x2100 + slotItem); // TODO: Stop storing by offset } return nullptr; @@ -16768,7 +16761,7 @@ auto CLuaBaseEntity::getAttachments() const -> sol::table if (attachmentItemId != 0) { - attachmentTable[attachmentSlot] = CLuaItem(itemutils::GetItemPointer(0x2100 + attachmentItemId)); + attachmentTable[attachmentSlot] = CLuaItem(xi::items::lookup(0x2100 + attachmentItemId)); } } diff --git a/src/map/lua/lua_baseentity.h b/src/map/lua/lua_baseentity.h index a3913127159..31a8eafb51b 100644 --- a/src/map/lua/lua_baseentity.h +++ b/src/map/lua/lua_baseentity.h @@ -819,7 +819,7 @@ class CLuaBaseEntity auto getActiveManeuverCount() const -> uint8; void removeOldestManeuver() const; void removeAllManeuvers() const; - auto getAttachment(uint8 slotId) const -> CItem*; + auto getAttachment(uint8 slotId) const -> const CItem*; auto getAttachments() const -> sol::table; void setAttachment(uint8 attachmentItemID, uint8 slotID) const; void updateAttachments() const; diff --git a/src/map/lua/lua_item.cpp b/src/map/lua/lua_item.cpp index 0c9c1144f54..809eb39d01f 100644 --- a/src/map/lua/lua_item.cpp +++ b/src/map/lua/lua_item.cpp @@ -35,7 +35,18 @@ #include "utils/itemutils.h" CLuaItem::CLuaItem(CItem* PItem) -: m_PLuaItem(PItem) +: m_readItem(PItem) +, m_writeItem(PItem) +{ + if (PItem == nullptr) + { + ShowError("CLuaItem created with nullptr instead of valid CItem*!"); + } +} + +CLuaItem::CLuaItem(const CItem* PItem) +: m_readItem(PItem) +, m_writeItem(nullptr) { if (PItem == nullptr) { @@ -45,98 +56,108 @@ CLuaItem::CLuaItem(CItem* PItem) uint16 CLuaItem::getID() { - return m_PLuaItem->getID(); + return m_readItem->getID(); } uint16 CLuaItem::getSubID() { - return m_PLuaItem->getSubID(); + return m_readItem->getSubID(); } auto CLuaItem::getFlag() const -> ItemFlag { - return m_PLuaItem->getFlag(); + return m_readItem->getFlag(); } uint8 CLuaItem::getAHCat() { - return m_PLuaItem->getAHCat(); + return m_readItem->getAHCat(); } uint32 CLuaItem::getQuantity() { - return m_PLuaItem->getQuantity(); + return m_readItem->getQuantity(); } uint32 CLuaItem::getBasePrice() { - return m_PLuaItem->getBasePrice(); + return m_readItem->getBasePrice(); } uint8 CLuaItem::getLocationID() { - return m_PLuaItem->getLocationID(); + return m_readItem->getLocationID(); } uint8 CLuaItem::getSlotID() { - return m_PLuaItem->getSlotID(); + return m_readItem->getSlotID(); } uint16 CLuaItem::getTrialNumber() { - return static_cast(m_PLuaItem)->getTrialNumber(); + return static_cast(m_readItem)->getTrialNumber(); } uint8 CLuaItem::getWornUses() { - return m_PLuaItem->exdata().UseCount; + return m_readItem->exdata().UseCount; } bool CLuaItem::isType(uint8 type) { - return m_PLuaItem->isType(static_cast(type)); + return m_readItem->isType(static_cast(type)); } void CLuaItem::setSubType(uint8 subtype) { - m_PLuaItem->setSubType(static_cast(subtype)); + if (!m_writeItem) + { + return; + } + + m_writeItem->setSubType(static_cast(subtype)); } bool CLuaItem::isSubType(uint8 subtype) { - return m_PLuaItem->isSubType(static_cast(subtype)); + return m_readItem->isSubType(static_cast(subtype)); } void CLuaItem::setReservedValue(uint8 reserved) { - m_PLuaItem->setReserve(reserved); + if (!m_writeItem) + { + return; + } + + m_writeItem->setReserve(reserved); } uint8 CLuaItem::getReservedValue() { - return m_PLuaItem->getReserve(); + return m_readItem->getReserve(); } auto CLuaItem::getName() -> std::string { // TODO: Fix c-style cast - return m_PLuaItem->getName(); + return m_readItem->getName(); } uint16 CLuaItem::getILvl() { - return static_cast(m_PLuaItem)->getILvl(); + return static_cast(m_readItem)->getILvl(); } uint16 CLuaItem::getReqLvl() { - return static_cast(m_PLuaItem)->getReqLvl(); + return static_cast(m_readItem)->getReqLvl(); } int16 CLuaItem::getMod(uint16 modID) { - auto* PItem = static_cast(m_PLuaItem); + auto* PItem = static_cast(m_readItem); Mod mod = static_cast(modID); return PItem->getModifier(mod); @@ -144,23 +165,25 @@ int16 CLuaItem::getMod(uint16 modID) void CLuaItem::addMod(uint16 modID, int16 power) { - auto* PItem = static_cast(m_PLuaItem); - - // Checks if this item is just a pointer created by GetItem() - // All item-modifying functions in this file should check this! - if (itemutils::IsItemPointer(PItem)) + if (!m_writeItem) { return; } - Mod mod = static_cast(modID); + auto* PItem = static_cast(m_writeItem); + Mod mod = static_cast(modID); PItem->addModifier(CModifier(mod, power)); } void CLuaItem::delMod(uint16 modID, int16 power) { - auto* PItem = static_cast(m_PLuaItem); + if (!m_writeItem) + { + return; + } + + auto* PItem = static_cast(m_writeItem); Mod mod = static_cast(modID); PItem->addModifier(CModifier(mod, -power)); @@ -168,7 +191,7 @@ void CLuaItem::delMod(uint16 modID, int16 power) auto CLuaItem::getAugment(uint8 slot) -> sol::table { - auto* PItem = static_cast(m_PLuaItem); + auto* PItem = static_cast(m_readItem); uint16 augment = PItem->getAugment(slot); uint16 augmentId = (uint16)unpackBitsBE((uint8*)(&augment), 0, 11); @@ -183,13 +206,13 @@ auto CLuaItem::getAugment(uint8 slot) -> sol::table uint8 CLuaItem::getSkillType() { - auto* PItem = dynamic_cast(m_PLuaItem); + auto* PItem = dynamic_cast(m_readItem); return PItem ? PItem->getSkillType() : -1; } uint16 CLuaItem::getWeaponskillPoints() { - auto* PItem = dynamic_cast(m_PLuaItem); + auto* PItem = dynamic_cast(m_readItem); if (PItem) { @@ -201,7 +224,12 @@ uint16 CLuaItem::getWeaponskillPoints() void CLuaItem::setWeaponskillPointsNeeded(uint16 points) { - auto* PItem = dynamic_cast(m_PLuaItem); + if (!m_writeItem) + { + return; + } + + auto* PItem = dynamic_cast(m_writeItem); if (PItem) { @@ -211,7 +239,7 @@ void CLuaItem::setWeaponskillPointsNeeded(uint16 points) uint16 CLuaItem::getWeaponskillPointsNeeded() { - auto* PItem = dynamic_cast(m_PLuaItem); + auto* PItem = dynamic_cast(m_readItem); if (PItem) { @@ -223,7 +251,7 @@ uint16 CLuaItem::getWeaponskillPointsNeeded() bool CLuaItem::isTwoHanded() { - if (CItemWeapon* PWeapon = dynamic_cast(m_PLuaItem)) + if (const CItemWeapon* PWeapon = dynamic_cast(m_readItem)) { return PWeapon->isTwoHanded(); } @@ -237,7 +265,7 @@ bool CLuaItem::isTwoHanded() bool CLuaItem::isHandToHand() { - if (CItemWeapon* PWeapon = dynamic_cast(m_PLuaItem)) + if (const CItemWeapon* PWeapon = dynamic_cast(m_readItem)) { return PWeapon->isHandToHand(); } @@ -251,7 +279,7 @@ bool CLuaItem::isHandToHand() bool CLuaItem::isShield() { - if (CItemEquipment* PArmor = dynamic_cast(m_PLuaItem)) + if (const CItemEquipment* PArmor = dynamic_cast(m_readItem)) { return PArmor->IsShield(); } @@ -265,7 +293,7 @@ bool CLuaItem::isShield() uint8 CLuaItem::getShieldSize() { - if (CItemEquipment* PArmor = dynamic_cast(m_PLuaItem)) + if (const CItemEquipment* PArmor = dynamic_cast(m_readItem)) { if (PArmor->IsShield()) { @@ -286,7 +314,7 @@ uint8 CLuaItem::getShieldSize() uint8 CLuaItem::getShieldAbsorptionRate() { - if (CItemEquipment* PArmor = dynamic_cast(m_PLuaItem)) + if (const CItemEquipment* PArmor = dynamic_cast(m_readItem)) { if (PArmor->IsShield()) { @@ -307,12 +335,12 @@ uint8 CLuaItem::getShieldAbsorptionRate() auto CLuaItem::getSignature() -> std::string { - return m_PLuaItem->getSignature(); + return m_readItem->getSignature(); } uint8 CLuaItem::getCurrentCharges() { - if (auto* PUsableItem = dynamic_cast(m_PLuaItem)) + if (auto* PUsableItem = dynamic_cast(m_readItem)) { return PUsableItem->getCurrentCharges(); } @@ -322,21 +350,26 @@ uint8 CLuaItem::getCurrentCharges() uint8 CLuaItem::getAppraisalID() { - return m_PLuaItem->getAppraisalID(); + return m_readItem->getAppraisalID(); } void CLuaItem::setAppraisalID(uint8 id) { - m_PLuaItem->setAppraisalID(id); + if (!m_writeItem) + { + return; + } + + m_writeItem->setAppraisalID(id); } bool CLuaItem::isInstalled() { - if (!m_PLuaItem->isType(ITEM_FURNISHING)) + if (!m_readItem->isType(ITEM_FURNISHING)) { return false; } - auto* PFurnishing = static_cast(m_PLuaItem); + auto* PFurnishing = static_cast(m_readItem); return PFurnishing->isInstalled(); } @@ -349,14 +382,14 @@ auto CLuaItem::getExData() const -> sol::table { sol::table table = lua.create_table(); - if (Exdata::toTable(m_PLuaItem, table)) + if (Exdata::toTable(m_readItem, table)) { return table; } - for (std::size_t idx = 0; idx < m_PLuaItem->extra_size; ++idx) + for (std::size_t idx = 0; idx < m_readItem->extra_size; ++idx) { - table[idx] = m_PLuaItem->m_extra[idx]; + table[idx] = m_readItem->m_extra[idx]; } return table; } @@ -368,9 +401,14 @@ auto CLuaItem::getExData() const -> sol::table ************************************************************************/ void CLuaItem::setExData(const sol::table& data) const { - if (Exdata::fromTable(m_PLuaItem, data)) + if (!m_writeItem) + { + return; + } + + if (Exdata::fromTable(m_writeItem, data)) { - m_PLuaItem->setDirty(true); + m_writeItem->setDirty(true); return; } @@ -381,14 +419,14 @@ void CLuaItem::setExData(const sol::table& data) const if (key >= CItem::extra_size) { - ShowWarning("setExData: key too large for exdata array: %s[%i]", m_PLuaItem->getName(), key); + ShowWarning("setExData: key too large for exdata array: %s[%i]", m_writeItem->getName(), key); continue; } - m_PLuaItem->m_extra[key] = val; + m_writeItem->m_extra[key] = val; } - m_PLuaItem->setDirty(true); + m_writeItem->setDirty(true); } /************************************************************************ @@ -400,9 +438,9 @@ void CLuaItem::setExData(const sol::table& data) const auto CLuaItem::getExDataRaw() const -> sol::table { sol::table table = lua.create_table(); - for (std::size_t idx = 0; idx < m_PLuaItem->extra_size; ++idx) + for (std::size_t idx = 0; idx < m_readItem->extra_size; ++idx) { - table[idx] = m_PLuaItem->m_extra[idx]; + table[idx] = m_readItem->m_extra[idx]; } return table; } @@ -415,6 +453,11 @@ auto CLuaItem::getExDataRaw() const -> sol::table ************************************************************************/ void CLuaItem::setExDataRaw(const sol::table& data) const { + if (!m_writeItem) + { + return; + } + for (const auto& [keyObj, valObj] : data) { uint8 key = keyObj.as(); @@ -422,14 +465,14 @@ void CLuaItem::setExDataRaw(const sol::table& data) const if (key >= CItem::extra_size) { - ShowWarning("setExDataRaw: key too large for exdata array: %s[%i]", m_PLuaItem->getName(), key); + ShowWarning("setExDataRaw: key too large for exdata array: %s[%i]", m_writeItem->getName(), key); continue; } - m_PLuaItem->m_extra[key] = val; + m_writeItem->m_extra[key] = val; } - m_PLuaItem->setDirty(true); + m_writeItem->setDirty(true); } //==========================================================// @@ -481,7 +524,7 @@ void CLuaItem::Register() std::ostream& operator<<(std::ostream& os, const CLuaItem& item) { - std::string id = item.m_PLuaItem ? std::to_string(item.m_PLuaItem->getID()) : "nullptr"; + std::string id = item.m_readItem ? std::to_string(item.m_readItem->getID()) : "nullptr"; return os << "CLuaItem(" << id << ")"; } diff --git a/src/map/lua/lua_item.h b/src/map/lua/lua_item.h index b366a4df343..9347eeccee8 100644 --- a/src/map/lua/lua_item.h +++ b/src/map/lua/lua_item.h @@ -28,14 +28,16 @@ class CItem; class CLuaItem { - CItem* m_PLuaItem; + const CItem* m_readItem; // observer; always set + CItem* m_writeItem; // null when wrapping a read-only template public: CLuaItem(CItem*); + CLuaItem(const CItem*); - CItem* GetItem() const + auto GetItem() const -> const CItem* { - return m_PLuaItem; + return m_readItem; } friend std::ostream& operator<<(std::ostream& out, const CLuaItem& item); @@ -99,7 +101,7 @@ class CLuaItem bool operator==(const CLuaItem& other) const { - return this->m_PLuaItem == other.m_PLuaItem; + return this->m_readItem == other.m_readItem; } static void Register(); diff --git a/src/map/lua/luautils.cpp b/src/map/lua/luautils.cpp index 189213a47d1..046dbb8d872 100644 --- a/src/map/lua/luautils.cpp +++ b/src/map/lua/luautils.cpp @@ -1253,11 +1253,11 @@ void SendEntityVisualPacket(const uint32 npcId, const char* command) } } -CItem* GetItemByID(uint32 itemId) +auto GetItemByID(uint32 itemId) -> const CItem* { TracyZoneScoped; - return itemutils::GetItemPointer(itemId); + return xi::items::lookup(itemId); } CBaseEntity* GetNPCByID(uint32 npcid, const sol::object& instanceObj) @@ -2562,7 +2562,7 @@ void OnEffectLose(CBattleEntity* PEntity, CStatusEffect* PStatusEffect) } } -void OnAttachmentEquip(CBattleEntity* PEntity, CItemPuppet* attachment) +void OnAttachmentEquip(CBattleEntity* PEntity, const CItemPuppet* attachment) { TracyZoneScoped; @@ -2582,7 +2582,7 @@ void OnAttachmentEquip(CBattleEntity* PEntity, CItemPuppet* attachment) } } -void OnAttachmentUnequip(CBattleEntity* PEntity, CItemPuppet* attachment) +void OnAttachmentUnequip(CBattleEntity* PEntity, const CItemPuppet* attachment) { TracyZoneScoped; @@ -2602,7 +2602,7 @@ void OnAttachmentUnequip(CBattleEntity* PEntity, CItemPuppet* attachment) } } -void OnManeuverGain(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneuvers) +void OnManeuverGain(CBattleEntity* PEntity, const CItemPuppet* attachment, uint8 maneuvers) { TracyZoneScoped; @@ -2622,7 +2622,7 @@ void OnManeuverGain(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneu } } -void OnManeuverLose(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneuvers) +void OnManeuverLose(CBattleEntity* PEntity, const CItemPuppet* attachment, uint8 maneuvers) { TracyZoneScoped; @@ -2642,7 +2642,7 @@ void OnManeuverLose(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneu } } -void OnUpdateAttachment(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneuvers) +void OnUpdateAttachment(CBattleEntity* PEntity, const CItemPuppet* attachment, uint8 maneuvers) { TracyZoneScoped; @@ -5053,11 +5053,11 @@ std::string GetServerMessage(uint8 language) * * ************************************************************************/ -CItem* GetReadOnlyItem(uint32 id) +auto GetReadOnlyItem(uint32 id) -> const CItem* { TracyZoneScoped; - return itemutils::GetItemPointer(id); + return xi::items::lookup(id); } CAbility* GetAbility(uint16 id) @@ -5468,7 +5468,7 @@ SendToDBoxReturnCode SendItemToDeliveryBox(const std::string& playerName, uint16 // Check to confirm that the item legitimately exists // exclude gil as gil does not have an item pointer - auto* PItem = itemutils::GetItemPointer(itemId); + auto* PItem = xi::items::lookup(itemId); if (PItem == nullptr && !isGil) { return SendToDBoxReturnCode::ITEM_NOT_FOUND; diff --git a/src/map/lua/luautils.h b/src/map/lua/luautils.h index 9810a48e61d..f7007e4e06d 100644 --- a/src/map/lua/luautils.h +++ b/src/map/lua/luautils.h @@ -216,7 +216,7 @@ void PopulateIDLookupsByZone(Maybe maybeZoneId = std::nullopt); void SendEntityVisualPacket(uint32 npcId, const char* command); void InitInteractionGlobal(); auto GetZone(uint16 zoneId) -> CZone*; -auto GetItemByID(uint32 itemId) -> CItem*; +auto GetItemByID(uint32 itemId) -> const CItem*; auto GetNPCByID(uint32 npcid, const sol::object& instanceObj) -> CBaseEntity*; auto GetMobByID(uint32 mobid, const sol::object& instanceObj) -> CBaseEntity*; auto GetEntityByID(uint32 mobid, const sol::object& instanceObj, const sol::object& arg3) -> CBaseEntity*; @@ -233,7 +233,7 @@ void SendLuaFuncStringToZone(uint16 requestingZoneId, uint16 executorZoneId, co void UpdateSanrakusMobs(); // Update sanraku's (ZNM) subject of interest and recommended fauna void ZNMPopPriceDecay(); // Price of ZNM pop items decay over time -auto GetReadOnlyItem(uint32 id) -> CItem*; // Returns a read only lookup item object of the specified ID +auto GetReadOnlyItem(uint32 id) -> const CItem*; // Returns a read only lookup item object of the specified ID auto GetAbility(uint16 id) -> CAbility*; auto GetSpell(uint16 id) -> CSpell*; @@ -323,11 +323,11 @@ void OnEffectGain(CBattleEntity* PEntity, CStatusEffect* StatusEffect); void OnEffectTick(CBattleEntity* PEntity, CStatusEffect* StatusEffect); void OnEffectLose(CBattleEntity* PEntity, CStatusEffect* StatusEffect); -void OnAttachmentEquip(CBattleEntity* PEntity, CItemPuppet* attachment); -void OnAttachmentUnequip(CBattleEntity* PEntity, CItemPuppet* attachment); -void OnManeuverGain(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneuvers); -void OnManeuverLose(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneuvers); -void OnUpdateAttachment(CBattleEntity* PEntity, CItemPuppet* attachment, uint8 maneuvers); +void OnAttachmentEquip(CBattleEntity* PEntity, const CItemPuppet* attachment); +void OnAttachmentUnequip(CBattleEntity* PEntity, const CItemPuppet* attachment); +void OnManeuverGain(CBattleEntity* PEntity, const CItemPuppet* attachment, uint8 maneuvers); +void OnManeuverLose(CBattleEntity* PEntity, const CItemPuppet* attachment, uint8 maneuvers); +void OnUpdateAttachment(CBattleEntity* PEntity, const CItemPuppet* attachment, uint8 maneuvers); int32 OnItemUse(CBaseEntity* PUser, CBaseEntity* PTarget, CItem* PItem, action_t& action); auto OnItemCheck(CBaseEntity* PTarget, CItem* PItem, ITEMCHECK param = ITEMCHECK::NONE, CBaseEntity* PCaster = nullptr) -> std::tuple; diff --git a/src/map/packets/c2s/0x029_item_move.cpp b/src/map/packets/c2s/0x029_item_move.cpp index df96be2d785..87d952d0b00 100644 --- a/src/map/packets/c2s/0x029_item_move.cpp +++ b/src/map/packets/c2s/0x029_item_move.cpp @@ -225,7 +225,10 @@ void GP_CLI_COMMAND_ITEM_MOVE::process(MapSession* PSession, CCharEntity* PChar) return; } - if (uint8 newSlotId = PChar->getStorage(this->Category2)->InsertItem(PItem); newSlotId != ERROR_SLOTID) + auto* PSrc = PChar->getStorage(this->Category1); + auto* PDst = PChar->getStorage(this->Category2); + uint8 newSlotId = PSrc->MoveItemTo(this->ItemIndex1, *PDst); + if (newSlotId != ERROR_SLOTID) { const auto rset = db::preparedStmt("UPDATE char_inventory SET location = ?, slot = ? WHERE charid = ? AND location = ? AND slot = ?", this->Category2, @@ -235,15 +238,13 @@ void GP_CLI_COMMAND_ITEM_MOVE::process(MapSession* PSession, CCharEntity* PChar) this->ItemIndex1); if (rset && rset->rowsAffected()) { - PChar->getStorage(this->Category1)->InsertItem(nullptr, this->ItemIndex1); - - PChar->pushPacket(nullptr, static_cast(this->Category1), this->ItemIndex1, PItem); - PChar->pushPacket(PItem, static_cast(this->Category2), newSlotId); + auto* PInserted = PDst->GetItem(newSlotId); + PChar->pushPacket(nullptr, static_cast(this->Category1), this->ItemIndex1, PInserted); + PChar->pushPacket(PInserted, static_cast(this->Category2), newSlotId); } else { - PChar->getStorage(this->Category2)->InsertItem(nullptr, newSlotId); - PChar->getStorage(this->Category1)->InsertItem(PItem, this->ItemIndex1); + PDst->MoveItemTo(newSlotId, *PSrc, this->ItemIndex1); } } else diff --git a/src/map/packets/c2s/0x053_lockstyle.cpp b/src/map/packets/c2s/0x053_lockstyle.cpp index eb37c78c9db..8518b0cbae0 100644 --- a/src/map/packets/c2s/0x053_lockstyle.cpp +++ b/src/map/packets/c2s/0x053_lockstyle.cpp @@ -89,7 +89,7 @@ void GP_CLI_COMMAND_LOCKSTYLE::process(MapSession* PSession, CCharEntity* PChar) continue; } - const auto* PItem = dynamic_cast(itemutils::GetItemPointer(itemId)); + const auto* PItem = xi::items::lookup(itemId); if (!PItem || !(PItem->isType(ITEM_WEAPON) || PItem->isType(ITEM_EQUIPMENT))) { itemId = 0; @@ -105,7 +105,7 @@ void GP_CLI_COMMAND_LOCKSTYLE::process(MapSession* PSession, CCharEntity* PChar) // Check if we need to remove conflicting slots. Essentially, packet injection shenanigan detector. for (int i = 0; i < 10; i++) { - if (const auto* PItemEquipment = dynamic_cast(itemutils::GetItemPointer(PChar->styleItems[i]))) + if (const auto* PItemEquipment = xi::items::lookup(PChar->styleItems[i])) { const auto removeSlotID = PItemEquipment->getRemoveSlotId(); diff --git a/src/map/packets/c2s/0x083_shop_buy.cpp b/src/map/packets/c2s/0x083_shop_buy.cpp index 5791b431442..45dce3da91b 100644 --- a/src/map/packets/c2s/0x083_shop_buy.cpp +++ b/src/map/packets/c2s/0x083_shop_buy.cpp @@ -49,7 +49,7 @@ void GP_CLI_COMMAND_SHOP_BUY::process(MapSession* PSession, CCharEntity* PChar) const uint16 itemId = PChar->Container->getItemID(this->ShopItemIndex); const uint32 price = PChar->Container->getQuantity(this->ShopItemIndex); // We used the "quantity" to store the item's sale price - const CItem* PItem = itemutils::GetItemPointer(itemId); + const CItem* PItem = xi::items::lookup(itemId); if (!PItem) { ShowWarning("User '%s' attempting to buy an invalid item from vendor!", PChar->getName()); diff --git a/src/map/packets/c2s/0x0aa_guild_buy.cpp b/src/map/packets/c2s/0x0aa_guild_buy.cpp index e8e4b9a7f8b..67f5f289bbd 100644 --- a/src/map/packets/c2s/0x0aa_guild_buy.cpp +++ b/src/map/packets/c2s/0x0aa_guild_buy.cpp @@ -42,7 +42,7 @@ void GP_CLI_COMMAND_GUILD_BUY::process(MapSession* PSession, CCharEntity* PChar) { uint8 quantity = this->ItemNum; - const CItem* PItem = itemutils::GetItemPointer(this->ItemNo); + const CItem* PItem = xi::items::lookup(this->ItemNo); if (!PItem) { ShowWarning("User '%s' attempting to buy an invalid item from guild vendor!", PChar->getName()); diff --git a/src/map/packets/c2s/0x0c3_group_comlink_make.cpp b/src/map/packets/c2s/0x0c3_group_comlink_make.cpp index 750ddde33b3..a96197b3fa7 100644 --- a/src/map/packets/c2s/0x0c3_group_comlink_make.cpp +++ b/src/map/packets/c2s/0x0c3_group_comlink_make.cpp @@ -56,12 +56,13 @@ void GP_CLI_COMMAND_GROUP_COMLINK_MAKE::process(MapSession* PSession, CCharEntit } // Make a new Linkpearl - auto* PItemLinkPearl = static_cast(itemutils::GetItem(ITEMID::LINKPEARL)); - if (PItemLinkPearl) + auto PItem = xi::items::spawn(ITEMID::LINKPEARL); + if (PItem) { + auto* PItemLinkPearl = static_cast(PItem.get()); PItemLinkPearl->setQuantity(1); std::memcpy(PItemLinkPearl->m_extra, PItemLinkshell->m_extra, 24); PItemLinkPearl->SetLSType(LSTYPE_LINKPEARL); - charutils::AddItem(PChar, LOC_INVENTORY, PItemLinkPearl); + charutils::AddItem(PChar, LOC_INVENTORY, std::move(PItem)); } } diff --git a/src/map/packets/c2s/0x0c4_group_comlink_active.cpp b/src/map/packets/c2s/0x0c4_group_comlink_active.cpp index 65525abf4b2..331ff9d19a7 100644 --- a/src/map/packets/c2s/0x0c4_group_comlink_active.cpp +++ b/src/map/packets/c2s/0x0c4_group_comlink_active.cpp @@ -54,15 +54,18 @@ const auto createLinkshell = [](CCharEntity* PChar, CItemLinkshell* PItemLinkshe if (linkshellId != 0) { - destroy(PItemLinkshell); - PItemLinkshell = static_cast(itemutils::GetItem(ITEMID::LINKSHELL)); - if (PItemLinkshell == nullptr) + PChar->getStorage(data.Category)->RemoveItem(data.ItemIndex); + PItemLinkshell = nullptr; + + auto PItem = xi::items::spawn(ITEMID::LINKSHELL); + if (PItem == nullptr) { return; } + PItemLinkshell = static_cast(PItem.get()); PItemLinkshell->setQuantity(1); - PChar->getStorage(data.Category)->InsertItem(PItemLinkshell, data.ItemIndex); + PChar->getStorage(data.Category)->InsertItem(std::move(PItem), data.ItemIndex); PItemLinkshell->SetLSID(linkshellId); PItemLinkshell->SetLSType(LSTYPE_LINKSHELL); PItemLinkshell->setSignature(DecodedName); diff --git a/src/map/packets/c2s/0x0fe_myroom_plant_crop.cpp b/src/map/packets/c2s/0x0fe_myroom_plant_crop.cpp index 93fb7bb0a19..62f5d1c7764 100644 --- a/src/map/packets/c2s/0x0fe_myroom_plant_crop.cpp +++ b/src/map/packets/c2s/0x0fe_myroom_plant_crop.cpp @@ -73,7 +73,7 @@ void GP_CLI_COMMAND_MYROOM_PLANT_CROP::process(MapSession* PSession, CCharEntity uint16 resultID = 0; uint8 totalQuantity = 0; std::tie(resultID, totalQuantity) = gardenutils::CalculateResults(PChar, PItem); - const uint8 stackSize = itemutils::GetItemPointer(resultID)->getStackSize(); + const uint8 stackSize = xi::items::lookup(resultID)->getStackSize(); const uint8 requiredSlots = (uint8)ceil(float(totalQuantity) / stackSize); const uint8 totalFreeSlots = PChar->getStorage(LOC_MOGSAFE)->GetFreeSlotsCount() + PChar->getStorage(LOC_MOGSAFE2)->GetFreeSlotsCount(); if (requiredSlots > totalFreeSlots || totalQuantity == 0) diff --git a/src/map/packets/c2s/0x106_bazaar_buy.cpp b/src/map/packets/c2s/0x106_bazaar_buy.cpp index 201395698c3..3e9cb9e5fa5 100644 --- a/src/map/packets/c2s/0x106_bazaar_buy.cpp +++ b/src/map/packets/c2s/0x106_bazaar_buy.cpp @@ -123,16 +123,17 @@ void GP_CLI_COMMAND_BAZAAR_BUY::process(MapSession* PSession, CCharEntity* PChar return; } - CItem* PItem = itemutils::GetItem(PBazaarItem); + auto PItemOwn = xi::items::clone(*PBazaarItem); + PItemOwn->setCharPrice(0); + PItemOwn->setQuantity(this->BuyNum); + PItemOwn->setSubType(ITEM_UNLOCKED); - PItem->setCharPrice(0); - PItem->setQuantity(this->BuyNum); - PItem->setSubType(ITEM_UNLOCKED); - - if (charutils::AddItem(PChar, LOC_INVENTORY, PItem) == ERROR_SLOTID) + const uint8 newSlotID = charutils::AddItem(PChar, LOC_INVENTORY, std::move(PItemOwn)); + if (newSlotID == ERROR_SLOTID) { return; } + CItem* PItem = PBuyerInventory->GetItem(newSlotID); if (settings::get("map.AUDIT_PLAYER_BAZAAR")) { diff --git a/src/map/packets/s2c/0x116_equipset_valid.cpp b/src/map/packets/s2c/0x116_equipset_valid.cpp index 3df0988f168..e9a5f678ad1 100644 --- a/src/map/packets/s2c/0x116_equipset_valid.cpp +++ b/src/map/packets/s2c/0x116_equipset_valid.cpp @@ -41,10 +41,10 @@ GP_SERV_COMMAND_EQUIPSET_VALID::GP_SERV_COMMAND_EQUIPSET_VALID(const CCharEntity { auto& packet = this->data(); - CItemEquipment* equipSet[16] = {}; - uint8 equipSetBag[16] = {}; - uint8 equipSetBagIndex[16] = {}; - bool equipSetDisabled[16] = {}; + const CItemEquipment* equipSet[16] = {}; + uint8 equipSetBag[16] = {}; + uint8 equipSetBagIndex[16] = {}; + bool equipSetDisabled[16] = {}; const uint8 equippedIndex = data.EquipKind; // Where packet claims the new item is going const bool newItemSlotDisabled = data.ItemChange.RemoveItemFlg; // single bit, indicates this slot is intentionally disabled @@ -63,7 +63,7 @@ GP_SERV_COMMAND_EQUIPSET_VALID::GP_SERV_COMMAND_EQUIPSET_VALID(const CCharEntity // Retail doesn't do strict checking on items already in the set // This is probably due to the fact you can move items later // The actual equip command should handle erroneous input and/or missing items - auto* PItem = dynamic_cast(itemutils::GetItemPointer(itemID)); + auto* PItem = xi::items::lookup(itemID); equipSet[i] = PItem; equipSetBag[i] = bag; @@ -101,10 +101,10 @@ GP_SERV_COMMAND_EQUIPSET_VALID::GP_SERV_COMMAND_EQUIPSET_VALID(const CCharEntity } } - const CItemWeapon* PWeapon = dynamic_cast(equipSet[SLOT_MAIN]); - const CItemWeapon* PSub = dynamic_cast(equipSet[SLOT_SUB]); - const CItemWeapon* PRanged = dynamic_cast(equipSet[SLOT_RANGED]); - const CItemWeapon* PAmmo = dynamic_cast(equipSet[SLOT_AMMO]); + const CItemWeapon* PWeapon = dynamic_cast(equipSet[SLOT_MAIN]); + const CItemWeapon* PSub = dynamic_cast(equipSet[SLOT_SUB]); + const CItemWeapon* PRanged = dynamic_cast(equipSet[SLOT_RANGED]); + const CItemWeapon* PAmmo = dynamic_cast(equipSet[SLOT_AMMO]); if (const CItemWeapon* newItemWeapon = dynamic_cast(newItem)) { diff --git a/src/map/status_effect_container.cpp b/src/map/status_effect_container.cpp index aed30606ec1..54e76549d4e 100644 --- a/src/map/status_effect_container.cpp +++ b/src/map/status_effect_container.cpp @@ -1589,7 +1589,7 @@ auto CStatusEffectContainer::SetEffectParams(CStatusEffect* StatusEffect) -> voi { if (effectSourceType == EffectSourceType::SOURCE_EQUIPPED_ITEM) { - auto PItem = itemutils::GetItemPointer(effectSourceTypeParam); + auto PItem = xi::items::lookup(effectSourceTypeParam); if (PItem != nullptr) { // get the item lua script and check if it has valid functions @@ -1610,7 +1610,7 @@ auto CStatusEffectContainer::SetEffectParams(CStatusEffect* StatusEffect) -> voi } else if (effectSourceType == EffectSourceType::SOURCE_FOOD) { - auto PItem = itemutils::GetItemPointer(StatusEffect->GetSourceTypeParam()); + auto PItem = xi::items::lookup(StatusEffect->GetSourceTypeParam()); if (PItem != nullptr) { // get the item lua script and check if it has valid functions @@ -1647,7 +1647,7 @@ auto CStatusEffectContainer::SetEffectParams(CStatusEffect* StatusEffect) -> voi // Known use cases: Enchantments without an effect source. else { - CItem* Ptem = itemutils::GetItemPointer(subType); + const CItem* Ptem = xi::items::lookup(subType); if (Ptem != nullptr && subType > 0) { name.insert(0, "items/"); diff --git a/src/map/treasure_pool.cpp b/src/map/treasure_pool.cpp index 5b9705ae5be..e88eb4c6d61 100644 --- a/src/map/treasure_pool.cpp +++ b/src/map/treasure_pool.cpp @@ -168,7 +168,7 @@ uint8 CTreasurePool::addItem(uint16 ItemID, CBaseEntity* PEntity) uint8 SlotID = 0; uint8 FreeSlotID = -1; timer::time_point oldest = timer::time_point::max(); - CItem* PNewItem = itemutils::GetItemPointer(ItemID); + const CItem* PNewItem = xi::items::lookup(ItemID); if (!PNewItem) { @@ -213,7 +213,7 @@ uint8 CTreasurePool::addItem(uint16 ItemID, CBaseEntity* PEntity) // find the oldest non-rare and non-ex item for (SlotID = 0; SlotID < 10; ++SlotID) { - CItem* PItem = itemutils::GetItemPointer(m_PoolItems[SlotID].ID); + const CItem* PItem = xi::items::lookup(m_PoolItems[SlotID].ID); if (PItem != nullptr && !PItem->hasFlag(ItemFlag::Rare | ItemFlag::Exclusive) && m_PoolItems[SlotID].TimeStamp < oldest) { FreeSlotID = SlotID; @@ -225,7 +225,7 @@ uint8 CTreasurePool::addItem(uint16 ItemID, CBaseEntity* PEntity) // find the oldest non-ex item for (SlotID = 0; SlotID < 10; ++SlotID) { - CItem* PItem = itemutils::GetItemPointer(m_PoolItems[SlotID].ID); + const CItem* PItem = xi::items::lookup(m_PoolItems[SlotID].ID); if (PItem != nullptr && !PItem->hasFlag(ItemFlag::Exclusive) && m_PoolItems[SlotID].TimeStamp < oldest) { FreeSlotID = SlotID; @@ -328,7 +328,7 @@ void CTreasurePool::lotItem(CCharEntity* PChar, uint8 SlotID, uint16 Lot) return; } - CItem* PItem = itemutils::GetItemPointer(m_PoolItems[SlotID].ID); + const CItem* PItem = xi::items::lookup(m_PoolItems[SlotID].ID); if (PItem == nullptr) { ShowWarning(fmt::format("Player {} is trying to lot on an item that doesn't exist (PItem was nullptr) (Packet injection?)!", PChar->getName()).c_str()); @@ -540,7 +540,7 @@ void CTreasurePool::checkTreasureItem(timer::time_point tick, uint8 SlotID) std::vector candidates; for (auto& member : m_Members) { - if (charutils::HasItem(member, m_PoolItems[SlotID].ID) && itemutils::GetItemPointer(m_PoolItems[SlotID].ID)->hasFlag(ItemFlag::Rare)) + if (charutils::HasItem(member, m_PoolItems[SlotID].ID) && xi::items::lookup(m_PoolItems[SlotID].ID)->hasFlag(ItemFlag::Rare)) { continue; } diff --git a/src/map/utils/auctionutils.cpp b/src/map/utils/auctionutils.cpp index f938161fe9f..c7920248f42 100644 --- a/src/map/utils/auctionutils.cpp +++ b/src/map/utils/auctionutils.cpp @@ -233,7 +233,7 @@ auto auctionutils::PurchasingItems(CCharEntity* PChar, GP_AUC_PARAM_BID param) - } else { - const CItem* PItem = itemutils::GetItemPointer(param.ItemNo); + const CItem* PItem = xi::items::lookup(param.ItemNo); if (PItem != nullptr) { @@ -310,7 +310,7 @@ void auctionutils::CancelSale(CCharEntity* PChar, int8_t AucWorkIndex) canceledItem.price); if (rset && rset->rowsAffected()) { - if (const CItem* PDelItem = itemutils::GetItemPointer(canceledItem.itemid)) + if (const CItem* PDelItem = xi::items::lookup(canceledItem.itemid)) { if (charutils::AddItem(PChar, LOC_INVENTORY, canceledItem.itemid, (canceledItem.stack != 0 ? PDelItem->getStackSize() : 1), true) != ERROR_SLOTID) { diff --git a/src/map/utils/charutils.cpp b/src/map/utils/charutils.cpp index 562a0f803d1..7fdd0b68f6f 100644 --- a/src/map/utils/charutils.cpp +++ b/src/map/utils/charutils.cpp @@ -1056,7 +1056,7 @@ void LoadInventory(CCharEntity* PChar) { while (rset->next()) { - CItem* PItem = itemutils::GetItem(rset->get("itemid")); + auto PItem = xi::items::spawn(rset->get("itemid")); if (PItem != nullptr) { PItem->setLocationID(rset->get("location")); @@ -1073,9 +1073,10 @@ void LoadInventory(CCharEntity* PChar) if (PItem->isType(ITEM_LINKSHELL)) { - if (static_cast(PItem)->GetLSType() == 0) + auto* PLink = static_cast(PItem.get()); + if (PLink->GetLSType() == 0) { - static_cast(PItem)->SetLSType((LSTYPE)(PItem->getID() - 0x200)); + PLink->SetLSType((LSTYPE)(PItem->getID() - 0x200)); } PItem->setSignature(rset->get("signature")); } @@ -1084,7 +1085,7 @@ void LoadInventory(CCharEntity* PChar) PItem->setSignature(rset->get("signature")); } - if (auto PItemUsable = dynamic_cast(PItem)) + if (auto* PItemUsable = dynamic_cast(PItem.get())) { uint32 useTime = 0; std::memcpy(&useTime, PItemUsable->m_extra + 0x04, sizeof(useTime)); @@ -1096,12 +1097,14 @@ void LoadInventory(CCharEntity* PChar) if (PItem->isType(ITEM_FURNISHING) && (PItem->getLocationID() == LOC_MOGSAFE || PItem->getLocationID() == LOC_MOGSAFE2)) { - if (((CItemFurnishing*)PItem)->isInstalled()) // Check if furniture (furnishing) item is actually installed + if (static_cast(PItem.get())->isInstalled()) // Check if furniture (furnishing) item is actually installed { - PChar->getStorage(LOC_STORAGE)->AddBuff(((CItemFurnishing*)PItem)->getStorage()); + PChar->getStorage(LOC_STORAGE)->AddBuff(static_cast(PItem.get())->getStorage()); } } - PChar->getStorage(PItem->getLocationID())->InsertItem(PItem, PItem->getSlotID()); + const uint8 locID = PItem->getLocationID(); + const uint8 slotID = PItem->getSlotID(); + PChar->getStorage(locID)->InsertItem(std::move(PItem), slotID); } } } @@ -1678,15 +1681,15 @@ uint8 AddItem(CCharEntity* PChar, uint8 LocationID, uint16 ItemID, uint32 quanti return ERROR_SLOTID; } - CItem* PItem = itemutils::GetItem(ItemID); - - if (PItem != nullptr) + auto PItem = xi::items::spawn(ItemID); + if (PItem == nullptr) { - PItem->setQuantity(quantity); - return AddItem(PChar, LocationID, PItem, silence); + ShowWarning("AddItem: Item <%i> is not found in a database", ItemID); + return ERROR_SLOTID; } - ShowWarning("AddItem: Item <%i> is not found in a database", ItemID); - return ERROR_SLOTID; + + PItem->setQuantity(quantity); + return AddItem(PChar, LocationID, std::move(PItem), silence); } /************************************************************************ @@ -1695,59 +1698,54 @@ uint8 AddItem(CCharEntity* PChar, uint8 LocationID, uint16 ItemID, uint32 quanti * * ************************************************************************/ -uint8 AddItem(CCharEntity* PChar, uint8 LocationID, CItem* PItem, bool silence) +auto AddItem(CCharEntity* PChar, uint8 LocationID, std::unique_ptr PItem, bool silence) -> uint8 { if (PItem->isType(ITEM_CURRENCY)) { UpdateItem(PChar, LocationID, 0, PItem->getQuantity()); - destroy(PItem); return 0; } - if (PItem->hasFlag(ItemFlag::Rare)) + if (PItem->hasFlag(ItemFlag::Rare) && HasItem(PChar, PItem->getID())) { - if (HasItem(PChar, PItem->getID())) + if (!silence) { - if (!silence) - { - PChar->pushPacket(PChar, PItem->getID(), 0, MsgStd::ItemEx); - } - destroy(PItem); - return ERROR_SLOTID; + PChar->pushPacket(PChar, PItem->getID(), 0, MsgStd::ItemEx); } + return ERROR_SLOTID; } - uint8 SlotID = PChar->getStorage(LocationID)->InsertItem(PItem); - - if (SlotID != ERROR_SLOTID) + auto* PStorage = PChar->getStorage(LocationID); + uint8 SlotID = PStorage->InsertItem(std::move(PItem)); + if (SlotID == ERROR_SLOTID) { - const char* Query = "INSERT INTO char_inventory(" - "charid, " - "location, " - "slot, " - "itemId, " - "quantity, " - "signature, " - "extra) " - "VALUES(?, ?, ?, ?, ?, ?, ?) " - "LIMIT 1"; + ShowDebug("AddItem: Location %i is full", LocationID); + return SlotID; + } - if (!db::preparedStmt(Query, PChar->id, LocationID, SlotID, PItem->getID(), PItem->getQuantity(), PItem->getSignature(), PItem->m_extra)) - { - ShowError("AddItem: Cannot insert item to database"); - PChar->getStorage(LocationID)->InsertItem(nullptr, SlotID); - destroy(PItem); - return ERROR_SLOTID; - } + auto* PInserted = PStorage->GetItem(SlotID); - PChar->pushPacket(PItem, static_cast(LocationID), SlotID); - PChar->pushPacket(PChar); - } - else + const char* Query = "INSERT INTO char_inventory(" + "charid, " + "location, " + "slot, " + "itemId, " + "quantity, " + "signature, " + "extra) " + "VALUES(?, ?, ?, ?, ?, ?, ?) " + "LIMIT 1"; + + if (!db::preparedStmt(Query, PChar->id, LocationID, SlotID, PInserted->getID(), PInserted->getQuantity(), PInserted->getSignature(), PInserted->m_extra)) { - ShowDebug("AddItem: Location %i is full", LocationID); - destroy(PItem); + ShowError("AddItem: Cannot insert item to database"); + PStorage->RemoveItem(SlotID); + return ERROR_SLOTID; } + + PChar->pushPacket(PInserted, static_cast(LocationID), SlotID); + PChar->pushPacket(PChar); + return SlotID; } @@ -1839,42 +1837,53 @@ uint8 MoveItem(CCharEntity* PChar, uint8 LocationID, uint8 SlotID, uint8 NewSlot { CItemContainer* PItemContainer = PChar->getStorage(LocationID); - if (PItemContainer->GetFreeSlotsCount() != 0) + if (PItemContainer->GetFreeSlotsCount() == 0) { - if (NewSlotID == ERROR_SLOTID) - { - NewSlotID = PItemContainer->InsertItem(PItemContainer->GetItem(SlotID)); - } - else - { - if (PItemContainer->GetItem(NewSlotID) != nullptr) - { - NewSlotID = ERROR_SLOTID; - } - } - if (NewSlotID != ERROR_SLOTID) - { - const auto rset = db::preparedStmt("UPDATE char_inventory " - "SET slot = ? " - "WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", - NewSlotID, - PChar->id, - LocationID, - SlotID); + ShowError("charutils::MoveItem: item can't be moved"); + return ERROR_SLOTID; + } - if (rset && rset->rowsAffected()) - { - PItemContainer->InsertItem(nullptr, SlotID); + if (NewSlotID != ERROR_SLOTID && PItemContainer->GetItem(NewSlotID) != nullptr) + { + ShowError("charutils::MoveItem: item can't be moved"); + return ERROR_SLOTID; + } - PChar->pushPacket(nullptr, static_cast(LocationID), SlotID, PItemContainer->GetItem(NewSlotID)); - PChar->pushPacket(PItemContainer->GetItem(NewSlotID), static_cast(LocationID), NewSlotID); - return NewSlotID; - } - PItemContainer->InsertItem(nullptr, NewSlotID); // We cancel all changes in the container - } + auto PMoving = PItemContainer->RemoveItem(SlotID); + if (PMoving == nullptr) + { + ShowError("charutils::MoveItem: item can't be moved"); + return ERROR_SLOTID; } - ShowError("charutils::MoveItem: item can't be moved"); - return ERROR_SLOTID; + + NewSlotID = (NewSlotID == ERROR_SLOTID) + ? PItemContainer->InsertItem(std::move(PMoving)) + : PItemContainer->InsertItem(std::move(PMoving), NewSlotID); + + if (NewSlotID == ERROR_SLOTID) + { + ShowError("charutils::MoveItem: item can't be moved"); + return ERROR_SLOTID; + } + + const auto rset = db::preparedStmt("UPDATE char_inventory " + "SET slot = ? " + "WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", + NewSlotID, + PChar->id, + LocationID, + SlotID); + + if (!rset || !rset->rowsAffected()) + { + PItemContainer->MoveItemTo(NewSlotID, *PItemContainer, SlotID); + ShowError("charutils::MoveItem: item can't be moved"); + return ERROR_SLOTID; + } + + PChar->pushPacket(nullptr, static_cast(LocationID), SlotID, PItemContainer->GetItem(NewSlotID)); + PChar->pushPacket(PItemContainer->GetItem(NewSlotID), static_cast(LocationID), NewSlotID); + return NewSlotID; } /************************************************************************ @@ -1939,7 +1948,8 @@ uint32 UpdateItem(CCharEntity* PChar, uint8 LocationID, uint8 slotID, int32 quan PChar->id, LocationID, slotID); - PChar->getStorage(LocationID)->InsertItem(nullptr, slotID); + // Hold the extracted item alive until end of scope + auto PRemoved = PChar->getStorage(LocationID)->RemoveItem(slotID); PChar->pushPacket(nullptr, static_cast(LocationID), slotID); if (PChar->getStyleLocked() && !HasItem(PChar, ItemID)) @@ -1974,7 +1984,6 @@ uint32 UpdateItem(CCharEntity* PChar, uint8 LocationID, uint8 slotID, int32 quan } } luautils::OnItemDrop(PChar, PItem); - destroy(PItem); } return ItemID; } @@ -1984,7 +1993,7 @@ void DropItem(CCharEntity* PChar, uint8 container, uint8 slotID, int32 quantity, { if (charutils::UpdateItem(PChar, container, slotID, -quantity) != 0) { - ShowInfo("Player %s DROPPING itemID: %s (%u) quantity: %u", PChar->getName(), itemutils::GetItemPointer(ItemID)->getName(), ItemID, quantity); + ShowInfo("Player %s DROPPING itemID: %s (%u) quantity: %u", PChar->getName(), xi::items::lookup(ItemID)->getName(), ItemID, quantity); PChar->pushPacket(nullptr, ItemID, quantity, MsgStd::ThrowAway); PChar->pushPacket(PChar); } @@ -2043,10 +2052,10 @@ void DoTrade(CCharEntity* PChar, CCharEntity* PTarget) { if (PItem->getStackSize() == 1 && PItem->getReserve() == 1) { - CItem* PNewItem = itemutils::GetItem(PItem); + auto PNewItem = xi::items::clone(*PItem); ShowDebug("Adding %s to %s inventory stacksize 1", PNewItem->getName(), PTarget->getName()); PNewItem->setReserve(0); - AddItem(PTarget, LOC_INVENTORY, PNewItem); + AddItem(PTarget, LOC_INVENTORY, std::move(PNewItem)); } else { @@ -2191,7 +2200,7 @@ void UnequipItem(CCharEntity* PChar, uint8 equipSlotID, Recalculate recalculate) case SLOT_SUB: { PChar->look.sub = 0; - PChar->m_Weapons[SLOT_SUB] = itemutils::GetUnarmedItem(); // << equips "nothing" in the sub slot to prevent multi attack exploit + PChar->m_Weapons[SLOT_SUB] = xi::items::unarmed(); // << equips "nothing" in the sub slot to prevent multi attack exploit PChar->health.tp = 0; PChar->StatusEffectContainer->DelStatusEffect(EFFECT_AFTERMATH); BuildingCharWeaponSkills(PChar); @@ -2588,7 +2597,7 @@ bool EquipArmor(CCharEntity* PChar, uint8 slotID, uint8 equipSlotID, uint8 conta return true; } -bool canEquipItemOnAnyJob(CCharEntity* PChar, CItemEquipment* PItem) +auto canEquipItemOnAnyJob(CCharEntity* PChar, const CItemEquipment* PItem) -> bool { if (PItem == nullptr) { @@ -2606,7 +2615,7 @@ bool canEquipItemOnAnyJob(CCharEntity* PChar, CItemEquipment* PItem) return false; } -bool hasValidStyle(CCharEntity* PChar, CItemEquipment* PItem, CItemEquipment* AItem) +auto hasValidStyle(CCharEntity* PChar, const CItemEquipment* PItem, const CItemEquipment* AItem) -> bool { if (AItem && PItem) { @@ -2616,8 +2625,8 @@ bool hasValidStyle(CCharEntity* PChar, CItemEquipment* PItem, CItemEquipment* AI return HasItem(PChar, AItem->getID()) && canEquipItemOnAnyJob(PChar, AItem); } - CItemWeapon* PWeapon = dynamic_cast(PItem); - CItemWeapon* AWeapon = dynamic_cast(AItem); + const auto* PWeapon = dynamic_cast(PItem); + const auto* AWeapon = dynamic_cast(AItem); // Marvelous Cheer special case // It is not technically a Wind Instrument, but it can lockstyle one. @@ -2667,8 +2676,8 @@ void UpdateWeaponStyle(CCharEntity* PChar, uint8 equipSlotID, CItemEquipment* PI return; } - CItemEquipment* appearance = dynamic_cast(itemutils::GetItemPointer(PChar->styleItems[equipSlotID])); - uint16 appearanceModel = 0; + const CItemEquipment* appearance = xi::items::lookup(PChar->styleItems[equipSlotID]); + uint16 appearanceModel = 0; if (appearance) { appearanceModel = appearance->getModelId(); @@ -2745,9 +2754,9 @@ void UpdateArmorStyle(CCharEntity* PChar, uint8 equipSlotID) return; } - uint16 itemID = PChar->styleItems[equipSlotID]; - CItemEquipment* appearance = dynamic_cast(itemutils::GetItemPointer(itemID)); - uint16 appearanceModel = 0; + uint16 itemID = PChar->styleItems[equipSlotID]; + const CItemEquipment* appearance = xi::items::lookup(itemID); + uint16 appearanceModel = 0; if (appearance && HasItem(PChar, itemID)) { @@ -2800,7 +2809,7 @@ void UpdateRemovedSlotsLookForLockStyle(CCharEntity* PChar) continue; } - auto PItem = dynamic_cast(itemutils::GetItem(items[i])); + const auto* PItem = xi::items::lookup(items[i]); if (!PItem) { continue; @@ -2891,48 +2900,48 @@ void UpdateRemovedSlotsLook(CCharEntity* PChar) void AddItemToRecycleBin(CCharEntity* PChar, uint32 container, uint8 slotID, uint8 quantity) { - CItem* PItem = PChar->getStorage(container)->GetItem(slotID); - auto* RecycleBin = PChar->getStorage(LOC_RECYCLEBIN); - auto* OtherContainer = PChar->getStorage(container); + auto* RecycleBin = PChar->getStorage(LOC_RECYCLEBIN); + auto* OtherContainer = PChar->getStorage(container); - if (PItem == nullptr) + auto* PSrcItem = OtherContainer->GetItem(slotID); + if (PSrcItem == nullptr) { return; } - // Try and insert - uint8 NewSlotID = PChar->getStorage(LOC_RECYCLEBIN)->InsertItem(PItem); - if (NewSlotID != ERROR_SLOTID) + const uint16 itemID = PSrcItem->getID(); + const auto itemName = PSrcItem->getName(); + + if (RecycleBin->GetFreeSlotsCount() > 0) { + const uint8 NewSlotID = OtherContainer->MoveItemTo(slotID, *RecycleBin); + if (NewSlotID == ERROR_SLOTID) + { + return; + } + const auto rset = db::preparedStmt("UPDATE char_inventory SET location = ?, slot = ? WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", LOC_RECYCLEBIN, NewSlotID, PChar->id, container, slotID); - if (rset && rset->rowsAffected()) - { - // Move successful, delete original item - OtherContainer->InsertItem(nullptr, slotID); - - // Send update packets - PChar->pushPacket(nullptr, static_cast(container), slotID); - PChar->pushPacket(PItem, LOC_RECYCLEBIN, NewSlotID); - PChar->pushPacket(nullptr, PItem->getID(), quantity, MsgStd::ThrowAway); - luautils::OnItemDrop(PChar, PItem, IsRecycleBin::Yes); - } - else + if (!rset || !rset->rowsAffected()) { - // Move not successful, put things back how they were - RecycleBin->InsertItem(nullptr, NewSlotID); - OtherContainer->InsertItem(PItem, slotID); + RecycleBin->MoveItemTo(NewSlotID, *OtherContainer, slotID); + return; } + + auto* PInserted = RecycleBin->GetItem(NewSlotID); + PChar->pushPacket(nullptr, static_cast(container), slotID); + PChar->pushPacket(PInserted, LOC_RECYCLEBIN, NewSlotID); + PChar->pushPacket(nullptr, itemID, quantity, MsgStd::ThrowAway); + luautils::OnItemDrop(PChar, PInserted, IsRecycleBin::Yes); } else // Bin is full { // Evict recycle bin slot 1 - CItem* PEvictedItem = RecycleBin->GetItem(1); - RecycleBin->InsertItem(nullptr, 1); + auto PEvictedItem = RecycleBin->RemoveItem(1); db::preparedStmt("DELETE FROM char_inventory WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", PChar->id, LOC_RECYCLEBIN, @@ -2940,30 +2949,29 @@ void AddItemToRecycleBin(CCharEntity* PChar, uint32 container, uint8 slotID, uin if (PEvictedItem) { - luautils::OnItemDrop(PChar, PEvictedItem); - destroy(PEvictedItem); + luautils::OnItemDrop(PChar, PEvictedItem.get()); } - // Move everything around to accomodate + // Slide slots 2..10 down to 1..9 for (int i = 2; i <= 10; ++i) { - // Update storage - CItem* PMovingItem = RecycleBin->GetItem(i); - RecycleBin->InsertItem(PMovingItem, i - 1); + if (RecycleBin->GetItem(i) == nullptr) + { + continue; + } + RecycleBin->MoveItemTo(i, *RecycleBin, i - 1); - // Update db const auto rset = db::preparedStmt("UPDATE char_inventory SET location = ?, slot = ? WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", LOC_RECYCLEBIN, i - 1, PChar->id, LOC_RECYCLEBIN, i); if (!rset || !rset->rowsAffected()) { - ShowError("Problem moving Recycle Bin items! (%s - %s)", PChar->getName(), PItem->getName()); + ShowError("Problem moving Recycle Bin items! (%s - %s)", PChar->getName(), itemName); } } - // Move item from original container to recycle bin - OtherContainer->InsertItem(nullptr, slotID); - RecycleBin->InsertItem(PItem, 10); + // Move new item from source container into freed slot 10 + OtherContainer->MoveItemTo(slotID, *RecycleBin, 10); + auto* PInserted = RecycleBin->GetItem(10); - // Update db const auto rset = db::preparedStmt("UPDATE char_inventory SET location = ?, slot = ? WHERE charid = ? AND location = ? AND slot = ? LIMIT 1", LOC_RECYCLEBIN, 10, @@ -2972,18 +2980,17 @@ void AddItemToRecycleBin(CCharEntity* PChar, uint32 container, uint8 slotID, uin slotID); if (!rset || !rset->rowsAffected()) { - ShowError("Problem moving Recycle Bin items! (%s - %s)", PChar->getName(), PItem->getName()); + ShowError("Problem moving Recycle Bin items! (%s - %s)", PChar->getName(), itemName); } - // Send update packets PChar->pushPacket(nullptr, static_cast(container), slotID); for (int i = 1; i <= 10; ++i) { CItem* PUpdatedItem = RecycleBin->GetItem(i); PChar->pushPacket(PUpdatedItem, LOC_RECYCLEBIN, i); } - PChar->pushPacket(nullptr, PItem->getID(), quantity, MsgStd::ThrowAway); - luautils::OnItemDrop(PChar, PItem, IsRecycleBin::Yes); + PChar->pushPacket(nullptr, itemID, quantity, MsgStd::ThrowAway); + luautils::OnItemDrop(PChar, PInserted, IsRecycleBin::Yes); } PChar->pushPacket(PChar); } @@ -3320,7 +3327,7 @@ void EquipItem(CCharEntity* PChar, uint8 slotID, uint8 equipSlotID, uint8 contai } if (!PChar->getEquip(SLOT_MAIN) || !PChar->getEquip(SLOT_MAIN)->isType(ITEM_EQUIPMENT) || - PChar->m_Weapons[SLOT_MAIN] == itemutils::GetUnarmedH2HItem()) + PChar->m_Weapons[SLOT_MAIN] == xi::items::unarmedH2H()) { CheckUnarmedWeapon(PChar); } @@ -3382,7 +3389,7 @@ void CheckValidEquipment(CCharEntity* PChar) UnequipItem(PChar, slotID); } // Unarmed H2H weapon check - if (!PChar->getEquip(SLOT_MAIN) || !PChar->getEquip(SLOT_MAIN)->isType(ITEM_EQUIPMENT) || PChar->m_Weapons[SLOT_MAIN] == itemutils::GetUnarmedH2HItem()) + if (!PChar->getEquip(SLOT_MAIN) || !PChar->getEquip(SLOT_MAIN)->isType(ITEM_EQUIPMENT) || PChar->m_Weapons[SLOT_MAIN] == xi::items::unarmedH2H()) { CheckUnarmedWeapon(PChar); } @@ -6783,12 +6790,12 @@ void CheckUnarmedWeapon(CCharEntity* PChar) if ((battleutils::GetSkillRank(SKILL_HAND_TO_HAND, PChar->GetMJob()) > 0 || battleutils::GetSkillRank(SKILL_HAND_TO_HAND, PChar->GetSJob()) > 0) && (!PSubslot || !PSubslot->isType(ITEM_EQUIPMENT))) { - PChar->m_Weapons[SLOT_MAIN] = itemutils::GetUnarmedH2HItem(); + PChar->m_Weapons[SLOT_MAIN] = xi::items::unarmedH2H(); PChar->look.main = 21; // The secret to H2H animations. setModelId for UnarmedH2H didn't work. } else { - PChar->m_Weapons[SLOT_MAIN] = itemutils::GetUnarmedItem(); + PChar->m_Weapons[SLOT_MAIN] = xi::items::unarmed(); PChar->look.main = 0; } BuildingCharWeaponSkills(PChar); diff --git a/src/map/utils/charutils.h b/src/map/utils/charutils.h index 5641bcdd35f..534e5604e0c 100644 --- a/src/map/utils/charutils.h +++ b/src/map/utils/charutils.h @@ -23,6 +23,8 @@ #include "common/cbasetypes.h" +#include + #include "entities/charentity.h" #include "items/item_equipment.h" #include "zone.h" @@ -123,7 +125,7 @@ bool CanTrade(CCharEntity* PChar, CCharEntity* PTarget); void CheckWeaponSkill(CCharEntity* PChar, uint8 skill); bool HasItem(CCharEntity* PChar, uint16 ItemID); uint32 getItemCount(CCharEntity* PChar, uint16 ItemID); -uint8 AddItem(CCharEntity* PChar, uint8 LocationID, CItem* PItem, bool silence = false); +auto AddItem(CCharEntity* PChar, uint8 LocationID, std::unique_ptr PItem, bool silence = false) -> uint8; uint8 AddItem(CCharEntity* PChar, uint8 LocationID, uint16 itemID, uint32 quantity = 1, bool silence = false); uint8 MoveItem(CCharEntity* PChar, uint8 LocationID, uint8 SlotID, uint8 NewSlotID); uint32 UpdateItem(CCharEntity* PChar, uint8 LocationID, uint8 slotID, int32 quantity, bool force = false); diff --git a/src/map/utils/dboxutils.cpp b/src/map/utils/dboxutils.cpp index cf0bb4fe8d6..7e2ae252dd5 100644 --- a/src/map/utils/dboxutils.cpp +++ b/src/map/utils/dboxutils.cpp @@ -71,7 +71,7 @@ void dboxutils::SendOldItems(CCharEntity* PChar, GP_CLI_COMMAND_PBX_BOXNO BoxNo) { while (rset->next()) { - CItem* PItem = itemutils::GetItem(rset->get("itemid")); + CItem* PItem = xi::items::spawn(rset->get("itemid")).release(); if (PItem != nullptr) { PItem->setSubID(rset->get("itemsubid")); @@ -158,7 +158,7 @@ void dboxutils::AddItemsToBeSent(CCharEntity* PChar, GP_CLI_COMMAND_PBX_BOXNO Bo } } - CItem* PUBoxItem = itemutils::GetItem(PItem->getID()); + CItem* PUBoxItem = xi::items::spawn(PItem->getID()).release(); if (PUBoxItem == nullptr) { ShowErrorFmt("DBOX: PUBoxItem was null (player: {}, item: {})", PChar->getName(), PItem->getID()); @@ -405,7 +405,7 @@ void dboxutils::SendNewItems(Scheduler& scheduler, CCharEntity* PChar, GP_CLI_CO PChar->id); FOR_DB_SINGLE_RESULT(rset) { - if (CItem* PItem = itemutils::GetItem(rset->get("itemid"))) + if (CItem* PItem = xi::items::spawn(rset->get("itemid")).release()) { PItem->setSubID(rset->get("itemsubid")); PItem->setQuantity(rset->get("quantity")); @@ -648,7 +648,7 @@ void dboxutils::TakeItemFromCell(CCharEntity* PChar, GP_CLI_COMMAND_PBX_BOXNO Bo PChar->id, PostWorkNo, BoxNo); if (rset && rset->rowsAffected()) { - if (charutils::AddItem(PChar, LOC_INVENTORY, itemutils::GetItem(PItem), true) != ERROR_SLOTID) + if (charutils::AddItem(PChar, LOC_INVENTORY, xi::items::clone(*PItem), true) != ERROR_SLOTID) { return; } @@ -660,7 +660,7 @@ void dboxutils::TakeItemFromCell(CCharEntity* PChar, GP_CLI_COMMAND_PBX_BOXNO Bo PChar->id, PostWorkNo, BoxNo); if (rset && rset->rowsAffected()) { - if (charutils::AddItem(PChar, LOC_INVENTORY, itemutils::GetItem(PItem), true) != ERROR_SLOTID) + if (charutils::AddItem(PChar, LOC_INVENTORY, xi::items::clone(*PItem), true) != ERROR_SLOTID) { return; } diff --git a/src/map/utils/fishingutils.cpp b/src/map/utils/fishingutils.cpp index b58ef1fefdd..58c8bd28779 100644 --- a/src/map/utils/fishingutils.cpp +++ b/src/map/utils/fishingutils.cpp @@ -1485,7 +1485,7 @@ int32 CatchFish(CCharEntity* PChar, uint16 FishID, BigFish bigFish, uint16 lengt if (PChar->getStorage(LOC_INVENTORY)->GetFreeSlotsCount() != 0) { - CItemFish* Fish = GetFish(FishID); + auto Fish = GetFish(FishID); if (Fish == nullptr) { @@ -1503,7 +1503,7 @@ int32 CatchFish(CCharEntity* PChar, uint16 FishID, BigFish bigFish, uint16 lengt } Fish->setQuantity(Count); - charutils::AddItem(PChar, LOC_INVENTORY, Fish); + charutils::AddItem(PChar, LOC_INVENTORY, std::move(Fish)); if (Count > 1) { @@ -1532,9 +1532,7 @@ int32 CatchItem(CCharEntity* PChar, uint16 ItemID, uint8 Count = 1) if (PChar->getStorage(LOC_INVENTORY)->GetFreeSlotsCount() != 0) { - CItem* Item = itemutils::GetItem(ItemID); - - if (Item == nullptr) + if (xi::items::lookup(ItemID) == nullptr) { ShowError("Invalid ItemID %i for fished item", ItemID); PChar->animation = ANIMATION_FISHING_STOP; @@ -2932,14 +2930,13 @@ void FishingAction(CCharEntity* PChar, const GP_CLI_COMMAND_FISHING_2_MODE mode, } } -CItemFish* GetFish(uint16 itemid) +auto GetFish(uint16 itemid) -> std::unique_ptr { - CItem* PItem = itemutils::GetItemPointer(itemid); + const CItem* PItem = xi::items::lookup(itemid); if (PItem && FishList[itemid]) { - // CItemFish constructor uses `const CItem&` input so this is ok - return new CItemFish(*PItem); + return std::make_unique(*PItem); } return nullptr; } diff --git a/src/map/utils/fishingutils.h b/src/map/utils/fishingutils.h index 191d4cbaa6f..01c7643f09a 100644 --- a/src/map/utils/fishingutils.h +++ b/src/map/utils/fishingutils.h @@ -990,7 +990,7 @@ uint8 UnhookMob(CCharEntity* PChar, Lost lost); fishresponse_t* FishingCheck(CCharEntity* PChar, uint8 fishingSkill, rod_t* rod, bait_t* bait, fishingarea_t* area); catchresponse_t* ReelCheck(CCharEntity* PChar, fishresponse_t* response, rod_t* rod); void FishingAction(CCharEntity* PChar, GP_CLI_COMMAND_FISHING_2_MODE mode, uint32 para, uint32 para2); -CItemFish* GetFish(uint16 itemid); // creates a `new` CItemFish if possible +auto GetFish(uint16 itemid) -> std::unique_ptr; // Initialization void LoadFishingMessages(); diff --git a/src/map/utils/guildutils.cpp b/src/map/utils/guildutils.cpp index 713ee096690..d028dc48fac 100644 --- a/src/map/utils/guildutils.cpp +++ b/src/map/utils/guildutils.cpp @@ -89,7 +89,7 @@ void Initialize() FOR_DB_MULTIPLE_RESULTS(rset) { - auto* PItem = new CItemShop(rset->get("itemid")); + auto PItem = std::make_unique(rset->get("itemid")); PItem->setMinPrice(rset->get("min_price")); PItem->setMaxPrice(rset->get("max_price")); @@ -99,9 +99,9 @@ void Initialize() PItem->setFlag(rset->get("flags")); PItem->setQuantity(PItem->IsDailyIncrease() ? PItem->getInitialQuantity() : 0); - PItem->setBasePrice(getItemDynamicBasePrice(PItem)); + PItem->setBasePrice(getItemDynamicBasePrice(PItem.get())); - PGuildShop->InsertItem(PItem); + PGuildShop->InsertItem(std::move(PItem)); } } diff --git a/src/map/utils/itemutils.cpp b/src/map/utils/itemutils.cpp index 2d2df438802..dfdb0d25d4e 100644 --- a/src/map/utils/itemutils.cpp +++ b/src/map/utils/itemutils.cpp @@ -42,16 +42,19 @@ #include "lua/luautils.h" #include "packets/c2s/0x02b_translate.h" -std::array g_pItemList; // global array of pointers to game items +namespace +{ +std::array, MAX_ITEMID> itemTemplates; +std::unique_ptr unarmedItem; +std::unique_ptr unarmedH2HItem; +} // namespace + std::array g_pDropList; // global array of monster droplist items std::array g_pLootList; // global array of BCNM lootlist items // Translation lookup: language -> (name -> {item id, translated name}) std::map>> g_TranslateMap; -CItemWeapon* PUnarmedItem; -CItemWeapon* PUnarmedH2HItem; - DropItem_t::DropItem_t(uint8 DropType, uint16 ItemID, uint16 DropRate) : DropType(DropType) , ItemID(ItemID) @@ -111,171 +114,94 @@ void LootContainer::ForEachItem(const std::function& fu } } -/************************************************************************ - * * - * Actually methods of working with a global collection of items * - * * - ************************************************************************/ - -namespace itemutils +namespace xi::items { -/************************************************************************ - * * - * Create an empty instance of the item by ID (private method) * - * * - ************************************************************************/ - -CItem* CreateItem(const uint16 itemId, const ItemType itemType) +auto lookup(const uint16 itemId) -> const CItem* { - switch (itemType) + if (itemId >= MAX_ITEMID) { - case ItemType::General: - return new CItemGeneral(itemId); - case ItemType::Linkshell: - return new CItemLinkshell(itemId); - case ItemType::Furnishing: - return new CItemFurnishing(itemId); - case ItemType::Puppet: - return new CItemPuppet(itemId); - case ItemType::Usable: - return new CItemUsable(itemId); - case ItemType::Equipment: - return new CItemEquipment(itemId); - case ItemType::Weapon: - return new CItemWeapon(itemId); - case ItemType::Currency: - return new CItemCurrency(itemId); - default: - { - ShowErrorFmt("CreateItem({}): Unknown item type {}", itemId, static_cast(itemType)); - return new CItemGeneral(itemId); - } + ShowWarning("xi::items::lookup: itemId %u too big", itemId); + return nullptr; } -} -/************************************************************************ - * * - * Create a new copy of the item ID * - * * - ************************************************************************/ + return itemTemplates[itemId].get(); +} -CItem* GetItem(const uint16 ItemID) +auto clone(const CItem& source) -> std::unique_ptr { - if (ItemID == 0xFFFF) + if (source.isType(ITEM_WEAPON)) { - return new CItemCurrency(ItemID); + return std::make_unique(static_cast(source)); } - if (ItemID < MAX_ITEMID && g_pItemList[ItemID] != nullptr) + if (source.isType(ITEM_EQUIPMENT)) { - return GetItem(g_pItemList[ItemID]); + return std::make_unique(static_cast(source)); } - return nullptr; -} - -/************************************************************************ - * * - * Create a copy of the item * - * * - ************************************************************************/ - -CItem* GetItem(CItem* PItem) -{ - if (PItem == nullptr) + if (source.isType(ITEM_USABLE)) { - ShowWarning("CItem::GetItem() - PItem is null."); - return nullptr; + return std::make_unique(static_cast(source)); } - if (PItem->isType(ITEM_WEAPON)) + if (source.isType(ITEM_LINKSHELL)) { - return new CItemWeapon(*static_cast(PItem)); + return std::make_unique(static_cast(source)); } - if (PItem->isType(ITEM_EQUIPMENT)) + if (source.isType(ITEM_FURNISHING)) { - return new CItemEquipment(*static_cast(PItem)); + return std::make_unique(static_cast(source)); } - if (PItem->isType(ITEM_USABLE)) + if (source.isType(ITEM_PUPPET)) { - return new CItemUsable(*static_cast(PItem)); + return std::make_unique(static_cast(source)); } - if (PItem->isType(ITEM_LINKSHELL)) + if (source.isType(ITEM_GENERAL)) { - return new CItemLinkshell(*static_cast(PItem)); + return std::make_unique(static_cast(source)); } - if (PItem->isType(ITEM_FURNISHING)) + if (source.isType(ITEM_CURRENCY)) { - return new CItemFurnishing(*static_cast(PItem)); + return std::make_unique(static_cast(source)); } - if (PItem->isType(ITEM_PUPPET)) - { - return new CItemPuppet(*static_cast(PItem)); - } + return nullptr; +} - if (PItem->isType(ITEM_GENERAL)) +auto spawn(uint16 itemId) -> std::unique_ptr +{ + if (itemId == 0xFFFF) { - return new CItemGeneral(*static_cast(PItem)); + return std::make_unique(itemId); } - if (PItem->isType(ITEM_CURRENCY)) + if (const CItem* tpl = lookup(itemId)) { - return new CItemCurrency(*static_cast(PItem)); + return clone(*tpl); } return nullptr; } -/************************************************************************ - * * - * Get a pointer to an item (read-only) * - * * - ************************************************************************/ - -CItem* GetItemPointer(uint16 ItemID) +auto unarmed() -> CItemWeapon* { - if (ItemID < MAX_ITEMID) - { - // False positive: this is CItem*, so it's OK - // cppcheck-suppress CastIntegerToAddressAtReturn - return g_pItemList[ItemID]; - } - ShowWarning("ItemID %u too big", ItemID); - return nullptr; + return unarmedItem.get(); } -/************************************************************************ - * * - * True if pointer points to a read-only g_pItemList array item * - * * - ************************************************************************/ - -bool IsItemPointer(CItem* item) +auto unarmedH2H() -> CItemWeapon* { - return g_pItemList[item->getID()] == item; + return unarmedH2HItem.get(); } -CItemWeapon* GetUnarmedItem() -{ - return PUnarmedItem; -} +} // namespace xi::items -CItemWeapon* GetUnarmedH2HItem() +namespace itemutils { - return PUnarmedH2HItem; -} - -/************************************************************************ - * * - * Get the monsters item drop list * - * * - ************************************************************************/ DropList_t* GetDropList(uint16 DropID) { @@ -297,6 +223,33 @@ DropList_t* GetDropList(uint16 DropID) void LoadItemList() { + // Bootstrap item templates from DB + auto buildFromType = [](uint16 itemId, ItemType itemType) -> std::unique_ptr + { + switch (itemType) + { + case ItemType::General: + return std::make_unique(itemId); + case ItemType::Linkshell: + return std::make_unique(itemId); + case ItemType::Furnishing: + return std::make_unique(itemId); + case ItemType::Puppet: + return std::make_unique(itemId); + case ItemType::Usable: + return std::make_unique(itemId); + case ItemType::Equipment: + return std::make_unique(itemId); + case ItemType::Weapon: + return std::make_unique(itemId); + case ItemType::Currency: + return std::make_unique(itemId); + default: + ShowErrorFmt("LoadItemList({}): Unknown item type {}", itemId, static_cast(itemType)); + return std::make_unique(itemId); + } + }; + auto rset = db::preparedStmt("SELECT " "b.itemId,b.name,b.sortname,b.name_jp,b.type,b.stackSize,b.flags," "b.aH,b.BaseSell,b.subid," @@ -320,7 +273,8 @@ void LoadItemList() MAX_ITEMID); FOR_DB_MULTIPLE_RESULTS(rset) { - CItem* PItem = CreateItem(rset->get("itemId"), rset->get("type")); + auto tplOwn = buildFromType(rset->get("itemId"), rset->get("type")); + CItem* PItem = tplOwn.get(); if (PItem != nullptr) { @@ -434,7 +388,7 @@ void LoadItemList() PFurnishing->setPlacement(rset->get("furn_placement")); } - g_pItemList[PItem->getID()] = PItem; + itemTemplates[PItem->getID()] = std::move(tplOwn); // Build translation maps. English uses sortname (the short display name) with spaces to match what the client sends. // Apply lowercase and replace underscores with spaces. @@ -469,9 +423,9 @@ void LoadItemList() const auto modID = rset->get("modId"); const auto value = rset->get("value"); - if ((g_pItemList[ItemID] != nullptr) && g_pItemList[ItemID]->isType(ITEM_EQUIPMENT)) + if (auto* tpl = itemTemplates[ItemID].get(); tpl != nullptr && tpl->isType(ITEM_EQUIPMENT)) { - static_cast(g_pItemList[ItemID])->addModifier(CModifier(modID, value)); + static_cast(tpl)->addModifier(CModifier(modID, value)); } } @@ -486,9 +440,9 @@ void LoadItemList() const auto value = rset->get("value"); const auto petType = rset->get("petType"); - if ((g_pItemList[ItemID]) && g_pItemList[ItemID]->isType(ITEM_EQUIPMENT)) + if (auto* tpl = itemTemplates[ItemID].get(); tpl != nullptr && tpl->isType(ITEM_EQUIPMENT)) { - static_cast(g_pItemList[ItemID])->addPetModifier(CPetModifier(modID, petType, value)); + static_cast(tpl)->addPetModifier(CPetModifier(modID, petType, value)); } } @@ -504,9 +458,9 @@ void LoadItemList() const auto latentId = rset->get("latentId"); const auto latentParam = rset->get("latentParam"); - if ((g_pItemList[ItemID] != nullptr) && g_pItemList[ItemID]->isType(ITEM_EQUIPMENT)) + if (auto* tpl = itemTemplates[ItemID].get(); tpl != nullptr && tpl->isType(ITEM_EQUIPMENT)) { - static_cast(g_pItemList[ItemID])->addLatent(latentId, latentParam, modID, value); + static_cast(tpl)->addLatent(latentId, latentParam, modID, value); } } } @@ -558,17 +512,6 @@ void LoadDropList() g_pDropList[0] = new DropList_t; } -/************************************************************************ - * * - * Handles loot from NPCs that drop things into * - * the loot pool instead of adding them directly to the inventory * - * * - ************************************************************************/ - -void LoadLootList() -{ -} - /************************************************************************ * * * Initialization of the game objects * @@ -580,19 +523,16 @@ void Initialize() TracyZoneScoped; LoadItemList(); LoadDropList(); - LoadLootList(); - - PUnarmedItem = new CItemWeapon(0); - - PUnarmedItem->setDmgType(DAMAGE_TYPE::NONE); - PUnarmedItem->setSkillType(SKILL_NONE); - PUnarmedItem->setDamage(3); - PUnarmedH2HItem = new CItemWeapon(0); + unarmedItem = std::make_unique(0); + unarmedItem->setDmgType(DAMAGE_TYPE::NONE); + unarmedItem->setSkillType(SKILL_NONE); + unarmedItem->setDamage(3); - PUnarmedH2HItem->setDmgType(DAMAGE_TYPE::HTH); - PUnarmedH2HItem->setSkillType(SKILL_HAND_TO_HAND); - PUnarmedH2HItem->setDamage(0); + unarmedH2HItem = std::make_unique(0); + unarmedH2HItem->setDmgType(DAMAGE_TYPE::HTH); + unarmedH2HItem->setSkillType(SKILL_HAND_TO_HAND); + unarmedH2HItem->setDamage(0); // load magian trial data AFTER items auto registerTrialListeners = lua["xi"]["magian"]["registerTrialListeners"]; @@ -613,11 +553,12 @@ void Initialize() void FreeItemList() { - for (int32 ItemID = 0; ItemID < MAX_ITEMID; ++ItemID) + for (auto& tpl : itemTemplates) { - destroy(g_pItemList[ItemID]); - g_pItemList[ItemID] = nullptr; + tpl.reset(); } + unarmedItem.reset(); + unarmedH2HItem.reset(); for (int32 DropID = 0; DropID < MAX_DROPID; ++DropID) { diff --git a/src/map/utils/itemutils.h b/src/map/utils/itemutils.h index 8bc5a228d9b..4e543335a89 100644 --- a/src/map/utils/itemutils.h +++ b/src/map/utils/itemutils.h @@ -21,7 +21,9 @@ #pragma once +#include #include +#include #include #include "items/item.h" @@ -29,6 +31,26 @@ #include "items/item_weapon.h" #include "packets/c2s/0x02b_translate.h" +namespace xi::items +{ + +auto lookup(uint16 itemId) -> const CItem*; + +template +auto lookup(const uint16 itemId) -> const T* +{ + static_assert(std::is_base_of_v, "T must derive from CItem"); + return dynamic_cast(lookup(itemId)); +} + +auto spawn(uint16 itemId) -> std::unique_ptr; +auto clone(const CItem& source) -> std::unique_ptr; + +auto unarmed() -> CItemWeapon*; +auto unarmedH2H() -> CItemWeapon*; + +} // namespace xi::items + #define MAX_ITEMID 32768 #define MAX_DROPID 5000 #define MAX_LOOTID 1300 @@ -101,14 +123,6 @@ namespace itemutils void Initialize(); void FreeItemList(); -CItem* GetItem(CItem* PItem); -CItem* GetItem(uint16 ItemID); -CItem* GetItemPointer(uint16 ItemID); -bool IsItemPointer(CItem* item); - -CItemWeapon* GetUnarmedItem(); -CItemWeapon* GetUnarmedH2HItem(); - DropList_t* GetDropList(uint16 DropID); auto TranslateItemName(GP_CLI_COMMAND_TRANSLATE_INDEX fromLang, GP_CLI_COMMAND_TRANSLATE_INDEX toLang, const std::string& name) -> std::optional>; diff --git a/src/map/utils/puppetutils.cpp b/src/map/utils/puppetutils.cpp index cff93172d5e..325e7e52f42 100644 --- a/src/map/utils/puppetutils.cpp +++ b/src/map/utils/puppetutils.cpp @@ -224,7 +224,7 @@ void SaveAutomaton(CCharEntity* PChar) } } -auto UnlockAttachment(CCharEntity* PChar, CItem* PItem) -> bool +auto UnlockAttachment(CCharEntity* PChar, const CItem* PItem) -> bool { const uint16 id = PItem->getID(); @@ -233,7 +233,7 @@ auto UnlockAttachment(CCharEntity* PChar, CItem* PItem) -> bool return false; } - const uint8 slot = static_cast(PItem)->getEquipSlot(); + const uint8 slot = static_cast(PItem)->getEquipSlot(); if (slot == ITEM_PUPPET_ATTACHMENT) { if (addBit(id & 0xFF, reinterpret_cast(PChar->m_unlockedAttachments.attachments), sizeof(PChar->m_unlockedAttachments.attachments))) @@ -267,7 +267,7 @@ auto UnlockAttachment(CCharEntity* PChar, CItem* PItem) -> bool return false; } -auto HasAttachment(const CCharEntity* PChar, CItem* PItem) -> bool +auto HasAttachment(const CCharEntity* PChar, const CItem* PItem) -> bool { const uint16 id = PItem->getID(); if (!PItem->isType(ITEM_PUPPET)) @@ -275,7 +275,7 @@ auto HasAttachment(const CCharEntity* PChar, CItem* PItem) -> bool return false; } - const uint8 slot = static_cast(PItem)->getEquipSlot(); + const uint8 slot = static_cast(PItem)->getEquipSlot(); // Note: getEquipSlot() returns ITEM_PUPPET_EQUIPSLOT values (1-based from DB), // not AutomatonSlot packet indices (0-based). @@ -294,7 +294,7 @@ auto HasAttachment(const CCharEntity* PChar, CItem* PItem) -> bool void setAttachment(CCharEntity* PChar, uint8 slotId, uint8 attachment) { - auto* PAttachment = static_cast(itemutils::GetItemPointer(0x2100 + attachment)); + auto* PAttachment = xi::items::lookup(0x2100 + attachment); if (attachment != 0) { if (PAttachment && !HasAttachment(PChar, PAttachment)) @@ -357,7 +357,7 @@ void setAttachment(CCharEntity* PChar, uint8 slotId, uint8 attachment) if (attachment != 0) { - PAttachment = static_cast(itemutils::GetItemPointer(0x2100 + attachment)); + PAttachment = xi::items::lookup(0x2100 + attachment); if (PAttachment && PAttachment->getEquipSlot() == ITEM_PUPPET_ATTACHMENT) { @@ -383,7 +383,7 @@ void setFrame(CCharEntity* PChar, AutomatonFrame frame) if (static_cast(PChar->getAutomatonFrame()) != 0) { - const auto* POldFrame = static_cast(itemutils::GetItemPointer(0x2000 + static_cast(PChar->getAutomatonFrame()))); + const auto* POldFrame = xi::items::lookup(0x2000 + static_cast(PChar->getAutomatonFrame())); if (POldFrame == nullptr || POldFrame->getEquipSlot() != ITEM_PUPPET_FRAME) { return; @@ -395,7 +395,7 @@ void setFrame(CCharEntity* PChar, AutomatonFrame frame) } // Check if they actually have the frame - auto* PFrame = static_cast(itemutils::GetItemPointer(0x2000 + static_cast(frame))); + auto* PFrame = xi::items::lookup(0x2000 + static_cast(frame)); if (PFrame == nullptr || PFrame->getEquipSlot() != ITEM_PUPPET_FRAME || (frame != AutomatonFrame::Harlequin && !HasAttachment(PChar, PFrame))) { return; @@ -426,7 +426,7 @@ void setHead(CCharEntity* PChar, AutomatonHead head) if (static_cast(PChar->getAutomatonHead()) != 0) { - const auto* POldHead = static_cast(itemutils::GetItemPointer(0x2000 + static_cast(PChar->getAutomatonHead()))); + const auto* POldHead = xi::items::lookup(0x2000 + static_cast(PChar->getAutomatonHead())); if (POldHead == nullptr || POldHead->getEquipSlot() != ITEM_PUPPET_HEAD) { return; @@ -438,7 +438,7 @@ void setHead(CCharEntity* PChar, AutomatonHead head) } // Check if they actually have the head - auto* PHead = static_cast(itemutils::GetItemPointer(0x2000 + static_cast(head))); + auto* PHead = xi::items::lookup(0x2000 + static_cast(head)); if (PHead == nullptr || PHead->getEquipSlot() != ITEM_PUPPET_HEAD || (head != AutomatonHead::Harlequin && !HasAttachment(PChar, PHead))) { return; @@ -664,7 +664,7 @@ void CheckAttachmentsForManeuver(const CCharEntity* PChar, const EFFECT maneuver { if (PAutomaton->getAttachment(i) != 0) { - auto* PAttachment = static_cast(itemutils::GetItemPointer(0x2100 + PAutomaton->getAttachment(i))); + auto* PAttachment = xi::items::lookup(0x2100 + PAutomaton->getAttachment(i)); if (PAttachment && (PAttachment->getElementSlots() >> (element * 4)) & 0xF) { @@ -690,7 +690,7 @@ void EquipAttachments(CAutomatonEntity* PAutomaton) { if (PAutomaton->getAttachment(i) != 0) { - auto* PAttachment = static_cast(itemutils::GetItemPointer(0x2100 + PAutomaton->getAttachment(i))); + auto* PAttachment = xi::items::lookup(0x2100 + PAutomaton->getAttachment(i)); if (PAttachment) { luautils::OnAttachmentEquip(PAutomaton, PAttachment); @@ -709,7 +709,7 @@ void UpdateAttachments(const CCharEntity* PChar) { if (PAutomaton->getAttachment(i) != 0) { - auto* PAttachment = static_cast(itemutils::GetItemPointer(0x2100 + PAutomaton->getAttachment(i))); + auto* PAttachment = xi::items::lookup(0x2100 + PAutomaton->getAttachment(i)); if (PAttachment) { @@ -740,7 +740,7 @@ void PreLevelRestriction(const CCharEntity* PChar) if (attachment != 0) { - CItemPuppet* PAttachment = dynamic_cast(itemutils::GetItemPointer(0x2100 + attachment)); + const auto* PAttachment = xi::items::lookup(0x2100 + attachment); if (PAttachment) { @@ -764,7 +764,7 @@ void PostLevelRestriction(const CCharEntity* PChar) const uint8 attachment = PAutomaton->getAttachment(i); if (attachment != 0) { - auto* PAttachment = dynamic_cast(itemutils::GetItemPointer(0x2100 + attachment)); + auto* PAttachment = xi::items::lookup(0x2100 + attachment); if (PAttachment) { // Attachment scripts may have custom equip logic that needs to be computed against the LvRestricted puppet stats diff --git a/src/map/utils/puppetutils.h b/src/map/utils/puppetutils.h index 4c5803f2a15..69f0a49ced4 100644 --- a/src/map/utils/puppetutils.h +++ b/src/map/utils/puppetutils.h @@ -30,8 +30,8 @@ namespace puppetutils void LoadAutomaton(CCharEntity* PChar); void SaveAttachments(CCharEntity* PChar); void SaveAutomaton(CCharEntity* PChar); -auto UnlockAttachment(CCharEntity* PChar, CItem* PItem) -> bool; -auto HasAttachment(const CCharEntity* PChar, CItem* PItem) -> bool; +auto UnlockAttachment(CCharEntity* PChar, const CItem* PItem) -> bool; +auto HasAttachment(const CCharEntity* PChar, const CItem* PItem) -> bool; void setAttachment(CCharEntity* PChar, uint8 slotId, uint8 attachment); void setFrame(CCharEntity* PChar, AutomatonFrame frame); void setHead(CCharEntity* PChar, AutomatonHead head); diff --git a/src/map/utils/synthutils.cpp b/src/map/utils/synthutils.cpp index 09814284a65..70612ae762a 100644 --- a/src/map/utils/synthutils.cpp +++ b/src/map/utils/synthutils.cpp @@ -330,7 +330,7 @@ auto isRightRecipe(CCharEntity* PChar) -> bool } // Check if recipe result is rare and player already owns a copy. - const CItem* PItem = itemutils::GetItemPointer(recipe.Result); + const CItem* PItem = xi::items::lookup(recipe.Result); if (PItem && PItem->hasFlag(ItemFlag::Rare) && charutils::HasItem(PChar, recipe.Result)) { PChar->pushPacket(PChar, SynthesisResult::CancelRareItem); From 24c73e75b966ec672206e8e403e880ec01644d26 Mon Sep 17 00:00:00 2001 From: sruon Date: Sun, 26 Apr 2026 23:27:53 -0600 Subject: [PATCH 2/2] Items, containers tests --- scripts/enum/item.lua | 1 + scripts/specs/core/CBaseEntity.lua | 1 - .../specs/test/CClientEntityPairActions.lua | 35 ++++++ .../tests/systems/items/container_move.lua | 65 ++++++++++ scripts/tests/systems/items/inventory.lua | 116 ++++++++++++++++++ scripts/tests/systems/items/lockstyle.lua | 59 +++++++++ .../systems/items/puppet_attachments.lua | 33 +++++ scripts/tests/systems/items/recycle_bin.lua | 66 ++++++++++ .../lua_client_entity_pair_actions.cpp | 71 +++++++++++ .../helpers/lua_client_entity_pair_actions.h | 5 + 10 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 scripts/tests/systems/items/container_move.lua create mode 100644 scripts/tests/systems/items/inventory.lua create mode 100644 scripts/tests/systems/items/lockstyle.lua create mode 100644 scripts/tests/systems/items/puppet_attachments.lua create mode 100644 scripts/tests/systems/items/recycle_bin.lua diff --git a/scripts/enum/item.lua b/scripts/enum/item.lua index 65a5d438ad7..4e2b58aa75a 100644 --- a/scripts/enum/item.lua +++ b/scripts/enum/item.lua @@ -3816,6 +3816,7 @@ xi.item = VALOREDGE_FRAME = 8225, SHARPSHOT_FRAME = 8226, STORMWAKER_FRAME = 8227, + STROBE_ATTACHMENT = 8449, RAAZ_TUSK = 8709, COPPER_AMAN_VOUCHER = 8711, RED_MOG_PELL = 8714, diff --git a/scripts/specs/core/CBaseEntity.lua b/scripts/specs/core/CBaseEntity.lua index 48038c3b288..ccbab9ac462 100644 --- a/scripts/specs/core/CBaseEntity.lua +++ b/scripts/specs/core/CBaseEntity.lua @@ -3562,7 +3562,6 @@ end function CBaseEntity:getAttachment(slotId) end ----@nodiscard ---@param itemId integer ---@param slotId integer ---@return nil diff --git a/scripts/specs/test/CClientEntityPairActions.lua b/scripts/specs/test/CClientEntityPairActions.lua index 8e83850dda7..478e99db229 100644 --- a/scripts/specs/test/CClientEntityPairActions.lua +++ b/scripts/specs/test/CClientEntityPairActions.lua @@ -107,3 +107,38 @@ end ---@return nil function CClientEntityPairActions:skillchain(target, ...) end + +---Move an item between containers or split a stack +---@param srcContainer xi.inventoryLocation Source container +---@param srcSlot integer Source slot index +---@param dstContainer xi.inventoryLocation Destination container +---@param quantity integer Quantity to move +---@param dstSlot? integer Destination slot (omit for first free) +---@return nil +function CClientEntityPairActions:moveItem(srcContainer, srcSlot, dstContainer, quantity, dstSlot) +end + +---Sort a container, merging partial stacks +---@param container xi.inventoryLocation Container to sort +---@return nil +function CClientEntityPairActions:sortContainer(container) +end + +---Drop an item (sends to recycle bin if enabled) +---@param container xi.inventoryLocation Source container +---@param slot integer Slot index +---@param quantity integer Quantity to drop +---@return nil +function CClientEntityPairActions:dropItem(container, slot, quantity) +end + +---@class LockstyleItem +---@field itemId integer Item ID +---@field slot xi.slot Equipment slot + +---Set lockstyle mode, optionally with item overrides +---@param mode integer Lockstyle mode (0=disable, 3=set, 4=enable) +---@param items? LockstyleItem[] Items to apply +---@return nil +function CClientEntityPairActions:setLockstyle(mode, items) +end diff --git a/scripts/tests/systems/items/container_move.lua b/scripts/tests/systems/items/container_move.lua new file mode 100644 index 00000000000..30d7c2be5d0 --- /dev/null +++ b/scripts/tests/systems/items/container_move.lua @@ -0,0 +1,65 @@ +local function findItemSlot(player, container, itemId) + for i = 1, 30 do + local item = player:getStorageItem(container, i, 255) + if item and item:getID() == itemId then + return i + end + end + + return nil +end + +describe('Moving items between containers', function() + ---@type CClientEntityPair + local player + + before_each(function() + player = xi.test.world:spawnPlayer( + { + zone = xi.zone.SOUTHERN_SAN_DORIA, + }) + end) + + it('a full stack can be moved to another container', function() + player:changeContainerSize(xi.inv.MOGSATCHEL, 30) + player:addItem(xi.item.FIRE_ARROW, 50) + + local srcSlot = findItemSlot(player, xi.inv.INVENTORY, xi.item.FIRE_ARROW) + assert(srcSlot, 'could not find fire arrows') + + player.actions:moveItem(xi.inv.INVENTORY, srcSlot, xi.inv.MOGSATCHEL, 50) + + assert(findItemSlot(player, xi.inv.INVENTORY, xi.item.FIRE_ARROW) == nil, + 'item should no longer be in inventory') + assert(findItemSlot(player, xi.inv.MOGSATCHEL, xi.item.FIRE_ARROW), + 'item should be in satchel') + end) + + it('non-equipment is rejected from wardrobe', function() + player:changeContainerSize(xi.inv.WARDROBE, 30) + player:addItem(xi.item.FIRE_CRYSTAL) + + local srcSlot = findItemSlot(player, xi.inv.INVENTORY, xi.item.FIRE_CRYSTAL) + assert(srcSlot) + + player.actions:moveItem(xi.inv.INVENTORY, srcSlot, xi.inv.WARDROBE, 1) + + assert(findItemSlot(player, xi.inv.INVENTORY, xi.item.FIRE_CRYSTAL), + 'item should still be in inventory') + assert(findItemSlot(player, xi.inv.WARDROBE, xi.item.FIRE_CRYSTAL) == nil, + 'item should not be in wardrobe') + end) + + it('sorting merges partial stacks into one', function() + player:addItem(xi.item.FIRE_ARROW, 30) + player:addItem(xi.item.FIRE_ARROW, 40) + + local slotsBefore = player:getFreeSlotsCount() + + player.actions:sortContainer(xi.inv.INVENTORY) + + assert(player:getFreeSlotsCount() == slotsBefore + 1, + string.format('sort should merge stacks (freed %d slots)', + player:getFreeSlotsCount() - slotsBefore)) + end) +end) diff --git a/scripts/tests/systems/items/inventory.lua b/scripts/tests/systems/items/inventory.lua new file mode 100644 index 00000000000..01b60eb5e71 --- /dev/null +++ b/scripts/tests/systems/items/inventory.lua @@ -0,0 +1,116 @@ +describe('Inventory', function() + ---@type CClientEntityPair + local player + + before_each(function() + player = xi.test.world:spawnPlayer( + { + zone = xi.zone.SOUTHERN_SAN_DORIA, + }) + end) + + it('can receive a single item', function() + player:addItem(xi.item.BRONZE_SWORD) + player.assert:hasItem(xi.item.BRONZE_SWORD) + end) + + it('overflowing a stack spills into a second slot', function() + local before = player:getFreeSlotsCount() + player:addItem(xi.item.FIRE_ARROW, 150) + local after = player:getFreeSlotsCount() + + player.assert:hasItem(xi.item.FIRE_ARROW) + assert(before - after == 2, + string.format('expected 2 slots consumed, saw %d', before - after)) + end) + + it('rejects items when full', function() + while player:getFreeSlotsCount() > 0 do + player:addItem(xi.item.BRONZE_SWORD) + end + + assert(player:getFreeSlotsCount() == 0, 'inventory should be full') + + player:addItem(xi.item.SHURIKEN) + player.assert.no:hasItem(xi.item.SHURIKEN) + end) + + it('cannot hold two copies of a Rare item', function() + player:addItem(xi.item.ADVENTURER_COUPON) + player.assert:hasItem(xi.item.ADVENTURER_COUPON) + local before = player:getFreeSlotsCount() + + player:addItem(xi.item.ADVENTURER_COUPON) + + assert(player:getFreeSlotsCount() == before, 'rare dup should not consume a slot') + end) + + it('removing an entire stack frees the slot', function() + player:addItem(xi.item.FIRE_ARROW, 5) + player.assert:hasItem(xi.item.FIRE_ARROW) + local withItem = player:getFreeSlotsCount() + + player:delItem(xi.item.FIRE_ARROW, 5) + + player.assert.no:hasItem(xi.item.FIRE_ARROW) + assert(player:getFreeSlotsCount() == withItem + 1) + end) + + it('partial removal keeps the slot occupied', function() + player:addItem(xi.item.FIRE_ARROW, 10) + local before = player:getFreeSlotsCount() + + player:delItem(xi.item.FIRE_ARROW, 3) + + player.assert:hasItem(xi.item.FIRE_ARROW) + assert(player:getFreeSlotsCount() == before) + end) + + it('gil goes straight to the currency slot', function() + local startGil = player:getGil() + local before = player:getFreeSlotsCount() + + player:addItem(xi.item.GIL, 1000) + + assert(player:getGil() == startGil + 1000, + string.format('expected %d gil, saw %d', startGil + 1000, player:getGil())) + assert(player:getFreeSlotsCount() == before) + end) + + it('table-form add returns a usable item handle', function() + local item = player:addItem({ id = xi.item.FIRE_ARROW, quantity = 5 }) + assert(item ~= nil) + assert(item:getID() == xi.item.FIRE_ARROW) + assert(item:getQuantity() == 5) + end) + + it('signatures survive the add path', function() + local item = player:addItem({ id = xi.item.BRONZE_SWORD, signature = 'TestSig' }) + assert(item ~= nil) + assert(item:getSignature() == 'TestSig', + string.format('expected TestSig, got %s', item:getSignature())) + end) + + it('cannot remove more than the owned quantity', function() + player:addItem(xi.item.FIRE_ARROW, 5) + local before = player:getFreeSlotsCount() + + player:delItem(xi.item.FIRE_ARROW, 100) + + player.assert:hasItem(xi.item.FIRE_ARROW) + assert(player:getFreeSlotsCount() == before) + end) + + it('charged equipment can be added with worn state', function() + local ok = player:addUsedItem(xi.item.WARP_RING) + assert(ok) + player.assert:hasItem(xi.item.WARP_RING) + end) + + it('linkpearl for a missing linkshell is cleaned up', function() + local before = player:getFreeSlotsCount() + + assert(player:addLinkpearl('NonexistentLinkshellName', false) == false) + assert(player:getFreeSlotsCount() == before) + end) +end) diff --git a/scripts/tests/systems/items/lockstyle.lua b/scripts/tests/systems/items/lockstyle.lua new file mode 100644 index 00000000000..cfc558b4f70 --- /dev/null +++ b/scripts/tests/systems/items/lockstyle.lua @@ -0,0 +1,59 @@ +describe('Lockstyle', function() + ---@type CClientEntityPair + local player + + before_each(function() + player = xi.test.world:spawnPlayer( + { + zone = xi.zone.SOUTHERN_SAN_DORIA, + }) + end) + + it('equipped item model appears in the appearance update', function() + player:addItem(xi.item.BRONZE_HARNESS) + player:equipItem(xi.item.BRONZE_HARNESS) + player.packets:clear() + + player.actions:setLockstyle(3, + { + { itemId = xi.item.BRONZE_HARNESS, slot = xi.slot.BODY }, + }) + + for _, pkt in pairs(player.packets:getIncoming()) do + if pkt.type == 0x051 then + local bodyModel = pkt.data[8] + pkt.data[9] * 256 + assert(bodyModel == 0x200F, + string.format('expected body model 0x200F, got 0x%04X', bodyModel)) + return + end + end + + assert(false, 'did not receive GRAP_LIST packet') + end) + + it('nonexistent item zeroes the body slot in the appearance update', function() + player.packets:clear() + + player.actions:setLockstyle(3, + { + { itemId = 99999, slot = xi.slot.BODY }, + }) + + for _, pkt in pairs(player.packets:getIncoming()) do + if pkt.type == 0x051 then + local bodyModel = pkt.data[8] + pkt.data[9] * 256 + assert(bodyModel == 0x2000, + string.format('expected zeroed body 0x2000, got 0x%04X', bodyModel)) + return + end + end + + assert(false, 'did not receive GRAP_LIST packet') + end) + + it('toggling on and off is safe', function() + player.actions:setLockstyle(3) + player.actions:setLockstyle(4) + player.actions:setLockstyle(0) + end) +end) diff --git a/scripts/tests/systems/items/puppet_attachments.lua b/scripts/tests/systems/items/puppet_attachments.lua new file mode 100644 index 00000000000..194e1349016 --- /dev/null +++ b/scripts/tests/systems/items/puppet_attachments.lua @@ -0,0 +1,33 @@ +describe('Puppet attachments', function() + ---@type CClientEntityPair + local player + + before_each(function() + player = xi.test.world:spawnPlayer( + { + job = xi.job.PUP, + level = 75, + zone = xi.zone.SOUTHERN_SAN_DORIA, + }) + + player:unlockAttachment(xi.item.HARLEQUIN_FRAME) + player:unlockAttachment(xi.item.HARLEQUIN_HEAD) + player:unlockAttachment(xi.item.STROBE_ATTACHMENT) + end) + + it('equipped attachment is visible on the spawned automaton', function() + player:setAttachment(xi.item.STROBE_ATTACHMENT, 0) + player:spawnPet(xi.petId.AUTOMATON) + + local pet = player:getPet() + assert(pet) + + local attachments = pet:getAttachments() + assert(attachments) + + local item = attachments[0] + assert(item) + assert(item:getName() == 'strobe', + string.format('expected strobe, got %s', item:getName())) + end) +end) diff --git a/scripts/tests/systems/items/recycle_bin.lua b/scripts/tests/systems/items/recycle_bin.lua new file mode 100644 index 00000000000..4c7517de85c --- /dev/null +++ b/scripts/tests/systems/items/recycle_bin.lua @@ -0,0 +1,66 @@ +local function findItemSlot(player, container, itemId) + for i = 1, 30 do + local item = player:getStorageItem(container, i, 255) + if item and item:getID() == itemId then + return i + end + end + + return nil +end + +describe('Recycle bin', function() + ---@type CClientEntityPair + local player + + before_each(function() + player = xi.test.world:spawnPlayer( + { + zone = xi.zone.SOUTHERN_SAN_DORIA, + }) + end) + + it('dropping removes item from inventory', function() + player:addItem(xi.item.BRONZE_SWORD) + local slot = findItemSlot(player, xi.inv.INVENTORY, xi.item.BRONZE_SWORD) + assert(slot) + + player.actions:dropItem(xi.inv.INVENTORY, slot, 1) + + assert(findItemSlot(player, xi.inv.INVENTORY, xi.item.BRONZE_SWORD) == nil, + 'item should no longer be in inventory') + end) + + it('dropped item ends up in the bin', function() + player:addItem(xi.item.BRONZE_SWORD) + local slot = findItemSlot(player, xi.inv.INVENTORY, xi.item.BRONZE_SWORD) + assert(slot) + + player.actions:dropItem(xi.inv.INVENTORY, slot, 1) + + local binItem = player:getStorageItem(xi.inv.RECYCLEBIN, 1, 255) + assert(binItem and binItem:getID() == xi.item.BRONZE_SWORD, + 'item should be in recycle bin slot 1') + end) + + it('11th drop evicts the oldest entry', function() + for i = 1, 10 do + player:addItem(xi.item.BRONZE_AXE) + local slot = findItemSlot(player, xi.inv.INVENTORY, xi.item.BRONZE_AXE) + assert(slot, 'failed to add filler ' .. i) + player.actions:dropItem(xi.inv.INVENTORY, slot, 1) + end + + local firstBefore = player:getStorageItem(xi.inv.RECYCLEBIN, 1, 255) + assert(firstBefore and firstBefore:getID() == xi.item.BRONZE_AXE) + + player:addItem(xi.item.BRONZE_SWORD) + local swordSlot = findItemSlot(player, xi.inv.INVENTORY, xi.item.BRONZE_SWORD) + assert(swordSlot) + player.actions:dropItem(xi.inv.INVENTORY, swordSlot, 1) + + local newest = player:getStorageItem(xi.inv.RECYCLEBIN, 10, 255) + assert(newest and newest:getID() == xi.item.BRONZE_SWORD, + 'newest drop should be in slot 10') + end) +end) diff --git a/src/test/lua/helpers/lua_client_entity_pair_actions.cpp b/src/test/lua/helpers/lua_client_entity_pair_actions.cpp index 69a44464639..48cee8796e4 100644 --- a/src/test/lua/helpers/lua_client_entity_pair_actions.cpp +++ b/src/test/lua/helpers/lua_client_entity_pair_actions.cpp @@ -36,8 +36,12 @@ #include "map/enums/party_kind.h" #include "map/lua/lua_baseentity.h" #include "map/packets/c2s/0x01a_action.h" +#include "map/packets/c2s/0x028_item_dump.h" +#include "map/packets/c2s/0x029_item_move.h" #include "map/packets/c2s/0x036_item_transfer.h" #include "map/packets/c2s/0x037_item_use.h" +#include "map/packets/c2s/0x03a_item_stack.h" +#include "map/packets/c2s/0x053_lockstyle.h" #include "map/packets/c2s/0x06e_group_solicit_req.h" #include "map/packets/c2s/0x074_group_solicit_res.h" #include "map/spell.h" @@ -505,6 +509,69 @@ void CLuaClientEntityPairActions::skillchain(CLuaBaseEntity* target, sol::variad } } +void CLuaClientEntityPairActions::moveItem(const uint8 srcContainer, const uint8 srcSlot, const uint8 dstContainer, const uint32 quantity, const sol::optional dstSlot) const +{ + const auto packet = parent_->packets().createPacket(); + auto* p = packet->as(); + p->ItemNum = quantity; + p->Category1 = srcContainer; + p->Category2 = dstContainer; + p->ItemIndex1 = srcSlot; + p->ItemIndex2 = dstSlot.value_or(0xFF); + + parent_->packets().sendBasicPacket(*packet); +} + +void CLuaClientEntityPairActions::sortContainer(const uint8 container) const +{ + const auto packet = parent_->packets().createPacket(); + auto* p = packet->as(); + p->Category = container; + + parent_->packets().sendBasicPacket(*packet); +} + +void CLuaClientEntityPairActions::dropItem(const uint8 container, const uint8 slot, const uint32 quantity) const +{ + const auto packet = parent_->packets().createPacket(); + auto* p = packet->as(); + p->ItemNum = quantity; + p->Category = container; + p->ItemIndex = slot; + + parent_->packets().sendBasicPacket(*packet); +} + +void CLuaClientEntityPairActions::setLockstyle(const uint8 mode, sol::optional items) const +{ + const auto packet = parent_->packets().createPacket(); + auto* p = packet->as(); + p->Mode = mode; + p->Count = 0; + + if (items.has_value()) + { + uint8 idx = 0; + for (const auto& [key, val] : items.value()) + { + if (!val.is() || idx >= 16) + { + break; + } + + auto entry = val.as(); + p->Items[idx].ItemNo = entry.get_or("itemId", 0); + p->Items[idx].EquipKind = entry.get_or("slot", 0); + p->Items[idx].ItemIndex = 0; + p->Items[idx].Category = 0; + ++idx; + } + p->Count = idx; + } + + parent_->packets().sendBasicPacket(*packet); +} + void CLuaClientEntityPairActions::Register() { SOL_USERTYPE("CClientEntityPairActions", CLuaClientEntityPairActions); @@ -523,4 +590,8 @@ void CLuaClientEntityPairActions::Register() SOL_REGISTER("acceptRaise", CLuaClientEntityPairActions::acceptRaise); SOL_REGISTER("engage", CLuaClientEntityPairActions::engage); SOL_REGISTER("skillchain", CLuaClientEntityPairActions::skillchain); + SOL_REGISTER("moveItem", CLuaClientEntityPairActions::moveItem); + SOL_REGISTER("sortContainer", CLuaClientEntityPairActions::sortContainer); + SOL_REGISTER("dropItem", CLuaClientEntityPairActions::dropItem); + SOL_REGISTER("setLockstyle", CLuaClientEntityPairActions::setLockstyle); } diff --git a/src/test/lua/helpers/lua_client_entity_pair_actions.h b/src/test/lua/helpers/lua_client_entity_pair_actions.h index b3870d736bd..5952180e3ae 100644 --- a/src/test/lua/helpers/lua_client_entity_pair_actions.h +++ b/src/test/lua/helpers/lua_client_entity_pair_actions.h @@ -51,6 +51,11 @@ class CLuaClientEntityPairActions void engage(CLuaBaseEntity* mob) const; void skillchain(CLuaBaseEntity* target, sol::variadic_args weaponskillIds) const; + void moveItem(uint8 srcContainer, uint8 srcSlot, uint8 dstContainer, uint32 quantity, sol::optional dstSlot) const; + void sortContainer(uint8 container) const; + void dropItem(uint8 container, uint8 slot, uint32 quantity) const; + void setLockstyle(uint8 mode, sol::optional items) const; + static void Register(); private: