Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable stack size per ItemType #4854

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/lib/core/container.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function Container.createLootItem(self, item)
end

while itemCount > 0 do
local count = math.min(ITEM_STACK_SIZE, itemCount)
local count = math.min(itemType:getStackSize(), itemCount)

local subType = count
if itemType:isFluidContainer() then
Expand Down
47 changes: 42 additions & 5 deletions data/npc/lib/npc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,56 @@ function msgcontains(message, keyword)
return message:find(keyword) and not message:find('(%w+)' .. keyword)
end


function doNpcSellItem(cid, itemid, amount, subType, ignoreCap, inBackpacks, backpack)
local amount = amount or 1
local subType = subType or 0
local startingAmount = amount
local result = RETURNVALUE_NOERROR
local bpSize = 20
local itemType = ItemType(itemid)

if ItemType(itemid):isStackable() then
if itemType:isStackable() then
local stuff
local bpsToAdd = math.ceil(amount / itemType:getStackSize() / bpSize)
if inBackpacks then
stuff = Game.createItem(backpack, 1)
stuff:addItem(itemid, math.min(ITEM_STACK_SIZE, amount))
local bps = {}
for i = 1, bpsToAdd, 1 do
stuff = Game.createItem(backpack, 1)
local itemAdded = true
while startingAmount > 0 and itemAdded do
local item = stuff:addItem(itemid, math.min(itemType:getStackSize(), startingAmount))
if item then
startingAmount = startingAmount - math.min(itemType:getStackSize(), startingAmount)
itemAdded = true
else
itemAdded = false
end
end
bps[i] = stuff
end
for i = 1, bpsToAdd, 1 do
result = result and Player(cid):addItemEx(bps[i], ignoreCap)
end

if result ~= RETURNVALUE_NOERROR then
for i = 1, bpsToAdd, 1 do
if bps[i] then
bps[i]:remove()
end
end
end
else
stuff = Game.createItem(itemid, math.min(ITEM_STACK_SIZE, amount))
while startingAmount > 0 and result == RETURNVALUE_NOERROR do
stuff = Game.createItem(itemid, math.min(itemType:getStackSize(), startingAmount))
if result == RETURNVALUE_NOERROR then
result = Player(cid):addItemEx(stuff, ignoreCap)
startingAmount = startingAmount - math.min(itemType:getStackSize(), startingAmount)
end
end
end
return Player(cid):addItemEx(stuff, ignoreCap) ~= RETURNVALUE_NOERROR and 0 or amount, 0

return result ~= RETURNVALUE_NOERROR and 0 or amount - startingAmount, 0
end

local a = 0
Expand Down
14 changes: 7 additions & 7 deletions src/container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,22 +347,22 @@ ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t
uint32_t slotIndex = 0;
for (Item* containerItem : itemlist) {
if (containerItem != item && containerItem->equals(item) &&
containerItem->getItemCount() < ITEM_STACK_SIZE) {
containerItem->getItemCount() < containerItem->getStackSize()) {
if (queryAdd(slotIndex++, *item, count, flags) == RETURNVALUE_NOERROR) {
n += ITEM_STACK_SIZE - containerItem->getItemCount();
n += containerItem->getStackSize() - containerItem->getItemCount();
}
}
}
} else {
const Item* destItem = getItemByIndex(index);
if (item->equals(destItem) && destItem->getItemCount() < ITEM_STACK_SIZE) {
if (item->equals(destItem) && destItem->getItemCount() < destItem->getStackSize()) {
if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) {
n = ITEM_STACK_SIZE - destItem->getItemCount();
n = destItem->getStackSize() - destItem->getItemCount();
}
}
}

maxQueryCount = freeSlots * ITEM_STACK_SIZE + n;
maxQueryCount = freeSlots * item->getStackSize() + n;
if (maxQueryCount < count) {
return RETURNVALUE_CONTAINERNOTENOUGHROOM;
}
Expand Down Expand Up @@ -462,14 +462,14 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item**

bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags);
if (autoStack && item->isStackable() && item->getParent() != this) {
if (*destItem && (*destItem)->equals(item) && (*destItem)->getItemCount() < ITEM_STACK_SIZE) {
if (*destItem && (*destItem)->equals(item) && (*destItem)->getItemCount() < (*destItem)->getStackSize()) {
return this;
}

// try find a suitable item to stack with
uint32_t n = 0;
for (Item* listItem : itemlist) {
if (listItem != item && listItem->equals(item) && listItem->getItemCount() < ITEM_STACK_SIZE) {
if (listItem != item && listItem->equals(item) && listItem->getItemCount() < item->getStackSize()) {
*destItem = listItem;
index = n;
return this;
Expand Down
43 changes: 22 additions & 21 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder,
uint32_t n;

if (item->equals(toItem)) {
n = std::min<uint32_t>(ITEM_STACK_SIZE - toItem->getItemCount(), m);
n = std::min<uint32_t>(item->getStackSize() - toItem->getItemCount(), m);
toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n);
updateItem = toItem;
} else {
Expand Down Expand Up @@ -1329,7 +1329,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde

if (item->isStackable() && item->equals(toItem)) {
uint32_t m = std::min<uint32_t>(item->getItemCount(), maxQueryCount);
uint32_t n = std::min<uint32_t>(ITEM_STACK_SIZE - toItem->getItemCount(), m);
uint32_t n = std::min<uint32_t>(item->getStackSize() - toItem->getItemCount(), m);

toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n);

Expand Down Expand Up @@ -1593,7 +1593,7 @@ void Game::addMoney(Cylinder* cylinder, uint64_t money, uint32_t flags /*= 0*/)

money -= currencyCoins * worth;
while (currencyCoins > 0) {
const uint16_t count = std::min<uint32_t>(ITEM_STACK_SIZE, currencyCoins);
const uint16_t count = std::min<uint32_t>(Item::items[it.second].stackSize, currencyCoins);

Item* remaindItem = Item::CreateItem(it.second, count);

Expand Down Expand Up @@ -1850,7 +1850,7 @@ void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId)
}

if (slotItem && slotItem->getID() == it.id &&
(!it.stackable || slotItem->getItemCount() == ITEM_STACK_SIZE || !equipItem)) {
(!it.stackable || slotItem->getItemCount() == slotItem->getStackSize() || !equipItem)) {
internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(),
nullptr, 0, player, nullptr, &fromPos, &toPos);
} else if (equipItem) {
Expand Down Expand Up @@ -2734,8 +2734,9 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st
}

Container* tradeContainer = tradeItem->getContainer();
if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > ITEM_STACK_SIZE) {
player->sendCancelMessage("You can only trade up to " + std::to_string(ITEM_STACK_SIZE) + " objects at once.");
if (tradeContainer && tradeContainer->getItemHoldingCount() + 1 > tradeContainer->getStackSize()) {
player->sendCancelMessage("You can only trade up to " + std::to_string(tradeContainer->getStackSize()) +
" objects at once.");
return;
}

Expand Down Expand Up @@ -3035,7 +3036,12 @@ void Game::internalCloseTrade(Player* player, bool sendCancel /* = true*/)
void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint16_t amount,
bool ignoreCap /* = false*/, bool inBackpacks /* = false*/)
{
if (amount == 0 || amount > ITEM_STACK_SIZE) {
const ItemType& it = Item::items.getItemIdByClientId(spriteId);
if (it.id == 0) {
return;
}

if (amount == 0) {
return;
}

Expand All @@ -3051,11 +3057,6 @@ void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t coun
return;
}

const ItemType& it = Item::items.getItemIdByClientId(spriteId);
if (it.id == 0) {
return;
}

uint8_t subType;
if (it.isSplash() || it.isFluidContainer()) {
subType = clientFluidToServer(count);
Expand All @@ -3072,7 +3073,12 @@ void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t coun

void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint16_t amount, bool ignoreEquipped)
{
if (amount == 0 || amount > ITEM_STACK_SIZE) {
const ItemType& it = Item::items.getItemIdByClientId(spriteId);
if (it.id == 0) {
return;
}

if (amount == 0) {
return;
}

Expand All @@ -3088,11 +3094,6 @@ void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, u
return;
}

const ItemType& it = Item::items.getItemIdByClientId(spriteId);
if (it.id == 0) {
return;
}

uint8_t subType;
if (it.isSplash() || it.isFluidContainer()) {
subType = clientFluidToServer(count);
Expand Down Expand Up @@ -5316,7 +5317,7 @@ void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16
if (it.stackable) {
uint16_t tmpAmount = offer.amount;
while (tmpAmount > 0) {
int32_t stackCount = std::min<int32_t>(ITEM_STACK_SIZE, tmpAmount);
int32_t stackCount = std::min<int32_t>(it.stackSize, tmpAmount);
Item* item = Item::CreateItem(it.id, stackCount);
if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
delete item;
Expand Down Expand Up @@ -5424,7 +5425,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16
if (it.stackable) {
uint16_t tmpAmount = amount;
while (tmpAmount > 0) {
uint16_t stackCount = std::min<uint16_t>(ITEM_STACK_SIZE, tmpAmount);
uint16_t stackCount = std::min<uint16_t>(it.stackSize, tmpAmount);
Item* item = Item::CreateItem(it.id, stackCount);
if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) !=
RETURNVALUE_NOERROR) {
Expand Down Expand Up @@ -5471,7 +5472,7 @@ void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16
if (it.stackable) {
uint16_t tmpAmount = amount;
while (tmpAmount > 0) {
uint16_t stackCount = std::min<uint16_t>(ITEM_STACK_SIZE, tmpAmount);
uint16_t stackCount = std::min<uint16_t>(it.stackSize, tmpAmount);
Item* item = Item::CreateItem(it.id, stackCount);
if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) {
delete item;
Expand Down
2 changes: 0 additions & 2 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ static constexpr int32_t RANGE_REQUEST_TRADE_INTERVAL = 400;

static constexpr int32_t MAX_STACKPOS = 10;

static constexpr uint8_t ITEM_STACK_SIZE = 100;

/**
* Main Game class.
* This class is responsible to control everything that happens
Expand Down
2 changes: 1 addition & 1 deletion src/iomarket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void IOMarket::processExpiredOffers(DBResult_ptr result, bool)
if (itemType.stackable) {
uint16_t tmpAmount = amount;
while (tmpAmount > 0) {
uint16_t stackCount = std::min<uint16_t>(ITEM_STACK_SIZE, tmpAmount);
uint16_t stackCount = std::min<uint16_t>(itemType.stackSize, tmpAmount);
Item* item = Item::CreateItem(itemType.id, stackCount);
if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) !=
RETURNVALUE_NOERROR) {
Expand Down
2 changes: 2 additions & 0 deletions src/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,8 @@ class Item : virtual public Thing

bool hasMarketAttributes() const;

uint8_t getStackSize() const { return items[id].stackSize; }

std::unique_ptr<ItemAttributes>& getAttributes()
{
if (!attributes) {
Expand Down
6 changes: 6 additions & 0 deletions src/items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ const std::unordered_map<std::string, ItemParseAttributes_t> ItemParseAttributes
{"storeitem", ITEM_PARSE_STOREITEM},
{"worth", ITEM_PARSE_WORTH},
{"supply", ITEM_PARSE_SUPPLY},
{"stacksize", ITEM_PARSE_STACKSIZE},
};

const std::unordered_map<std::string, ItemTypes_t> ItemTypesMap = {{"key", ITEM_TYPE_KEY},
Expand Down Expand Up @@ -1865,6 +1866,11 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id)
break;
}

case ITEM_PARSE_STACKSIZE: {
it.stackSize = pugi::cast<uint8_t>(valueAttribute.value());
break;
}

default: {
// It should not ever get to here, only if you add a new key to the map and don't configure a case
// for it.
Expand Down
4 changes: 4 additions & 0 deletions src/items.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "itemloader.h"
#include "position.h"

static constexpr uint8_t ITEM_STACK_SIZE = 100;

class ConditionDamage;

enum SlotPositionBits : uint32_t
Expand Down Expand Up @@ -172,6 +174,7 @@ enum ItemParseAttributes_t
ITEM_PARSE_ALLOWDISTREAD,
ITEM_PARSE_STOREITEM,
ITEM_PARSE_WORTH,
ITEM_PARSE_STACKSIZE,
ITEM_PARSE_REFLECTPERCENTALL,
ITEM_PARSE_REFLECTPERCENTELEMENTS,
ITEM_PARSE_REFLECTPERCENTMAGIC,
Expand Down Expand Up @@ -353,6 +356,7 @@ class ItemType
int32_t runeMagLevel = 0;
int32_t runeLevel = 0;
uint64_t worth = 0;
uint8_t stackSize = ITEM_STACK_SIZE;

CombatType_t combatType = COMBAT_NONE;

Expand Down
Loading
Loading