• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

TFS 1.X+ tfs 1.2 runes on container

Manigold

Active Member
Joined
Nov 2, 2017
Messages
198
Solutions
8
Reaction score
48
hello I'm using tfs 1.2 downgraded to 8.0 by ninja , and i realize when i have for example a sd rune(just happen with runes)with 99 charges inside a full container, if i try to put on this container another sd rune with 100 charges ,a sd with 1 charge is created on container(container full of itens) and the sd that had 100 charges stay with 99 ,probably trying to stack with the 99 charges rune (i can do infinite times on the same full container), and runes should not be stackable on 8.0 version .
I don't know if i have to edit something on the sources or itens.otb , can someone help-me to find out?
maybe a pic explain better :
runesbugss.png

**EDIT = i'm having problems removing stackable from runes on items .otb with item editor ,after i edit items.otb the client start to crash =(

**EDIT 2 = maybe someone know how to prevent itens(or just runes) stack on full containers? i think that would solve the problem
 
Last edited:
hello I'm using tfs 1.2 downgraded to 8.0 by ninja , and i realize when i have for example a sd rune(just happen with runes)with 99 charges inside a full container, if i try to put on this container another sd rune with 100 charges ,a sd with 1 charge is created on container(container full of itens) and the sd that had 100 charges stay with 99 ,probably trying to stack with the 99 charges rune (i can do infinite times on the same full container), and runes should not be stackable on 8.0 version .
I don't know if i have to edit something on the sources or itens.otb , can someone help-me to find out?
maybe a pic explain better :
View attachment 33546

**EDIT = i'm having problems removing stackable from runes on items .otb with item editor ,after i edit items.otb the client start to crash =(
Allways edit both, spr/dat by ObjectBuilder and otb by ItemEditor.
Anyway in item editor always use option Tools=>Reload Item Attributes
 
I need to edit dat ,and spr?what exactly i need to edit ?
1. Take rune ID thats you want to remove stackable from items.xml
2. Open ItemEditor and using search "binoculars icon" =>search by server id and paste this ID. Double click it, and you see now item info, Server and Client ID.
3. Go to ObjectBuilder find rune by Client ID from step 2 and by double click edit it. In Tab "Properties" uncheck "Stackable" and save item, then save button to compile.
4. Close ObjectBuilder and ItemEditor, open again ItemEditor for reload spr file and in Tools tab click Reload Items Attrribute. Then Save.
Thats all
 
well if i remove the atribute stackable from runes , i'm not able to buy or conjure runes with charges =( , maybe someone more skilled than me can give me a hint ,please :p?


there's some way to do it in sources ?maybe prevent stack items on full container?or just runes, because they not try to stack on containers with empty slots .
 
Last edited:
i'm not sure but do you have infinity runes in config.lua ?
i had this kind of problem long time ago don't remember exactly how i fixed it but give it a try and look at your config.lua
 
U ask for - how to do "not stacking" item, after answer u write "maybe someone more skilled can give me a hint ,please"
JUST FK!
well i mean more skilled than me ,sorry for misunderstood i'm not here to offend .
i'm not sure but do you have infinity runes in config.lua ?
i had this kind of problem long time ago don't remember exactly how i fixed it but give it a try and look at your config.lua
no the runes are not infinite , the only problem happens with the full container ,if i have a slot inside the full container with some rune with less than 100 charges, i can put more and more runes of the same type inside this container.
 
Last edited:
well i mean more skilled than me ,sorry for misunderstood i'm not here to offend .

no the runes are not infinite , the only problem happens with the full container ,if i have a slot inside the full container with some rune with less tha 100 charges, i can put more and more runes of the same type inside this container.
No problem, we just did not understand each other.
I have 1.2 but for 10.98 and i dont have problem with stacking runes. Then maybe i can help you a little with code.
You have downgraded dev, soo they can hold corrputed lines or something.
First
Can you paste there you container.cpp file via code? I have to take a look, how this look in your file.
 
No problem, we just did not understand each other.
I have 1.2 but for 10.98 and i dont have problem with stacking runes. Then maybe i can help you a little with code.
You have downgraded dev, soo they can hold corrputed lines or something.
First
Can you paste there you container.cpp file via code? I have to take a look, how this look in your file.
ok =) , by the way this is the tfs i'm using ninjalulz/forgottenserver
and here's container.cpp
C++:
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2016  Mark Samman <[email protected]>
*
* 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "otpch.h"

#include "container.h"
#include "iomap.h"
#include "game.h"

extern Game g_game;

Container::Container(uint16_t _type) : Item(_type)
{
    maxSize = items[_type].maxItems;
    totalWeight = 0;
    serializationCount = 0;
    unlocked = true;
}

Container::Container(uint16_t _type, uint16_t _size) : Item(_type)
{
    maxSize = _size;
    totalWeight = 0;
    serializationCount = 0;
    unlocked = true;
}

Container::~Container()
{
    for (Item* item : itemlist) {
        item->setParent(nullptr);
        item->decrementReferenceCounter();
    }
}

Item* Container::clone() const
{
    Container* clone = static_cast<Container*>(Item::clone());
    for (Item* item : itemlist) {
        clone->addItem(item->clone());
    }
    clone->totalWeight = totalWeight;
    return clone;
}

Container* Container::getParentContainer()
{
    Thing* thing = getParent();
    if (!thing) {
        return nullptr;
    }
    return thing->getContainer();
}

bool Container::hasParent() const
{
    return dynamic_cast<const Container*>(getParent()) != nullptr;
}

void Container::addItem(Item* item)
{
    itemlist.push_back(item);
    item->setParent(this);
}

Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream)
{
    if (attr == ATTR_CONTAINER_ITEMS) {
        if (!propStream.read<uint32_t>(serializationCount)) {
            return ATTR_READ_ERROR;
        }
        return ATTR_READ_END;
    }
    return Item::readAttr(attr, propStream);
}

bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream)
{
    bool ret = Item::unserializeItemNode(f, node, propStream);
    if (!ret) {
        return false;
    }

    uint32_t type;
    NODE nodeItem = f.getChildNode(node, type);
    while (nodeItem) {
        //load container items
        if (type != OTBM_ITEM) {
            // unknown type
            return false;
        }

        PropStream itemPropStream;
        if (!f.getProps(nodeItem, itemPropStream)) {
            return false;
        }

        Item* item = Item::CreateItem(itemPropStream);
        if (!item) {
            return false;
        }

        if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) {
            return false;
        }

        addItem(item);
        updateItemWeight(item->getWeight());

        nodeItem = f.getNextNode(nodeItem, type);
    }
    return true;
}

void Container::updateItemWeight(int32_t diff)
{
    totalWeight += diff;
    if (Container* parentContainer = getParentContainer()) {
        parentContainer->updateItemWeight(diff);
    }
}

uint32_t Container::getWeight() const
{
    return Item::getWeight() + totalWeight;
}

std::string Container::getContentDescription() const
{
    std::ostringstream os;
    return getContentDescription(os).str();
}

std::ostringstream& Container::getContentDescription(std::ostringstream& os) const
{
    bool firstitem = true;
    for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
        Item* item = *it;

        Container* container = item->getContainer();
        if (container && !container->empty()) {
            continue;
        }

        if (firstitem) {
            firstitem = false;
        } else {
            os << ", ";
        }

        os << item->getNameDescription();
    }

    if (firstitem) {
        os << "nothing";
    }
    return os;
}

Item* Container::getItemByIndex(size_t index) const
{
    if (index >= size()) {
        return nullptr;
    }
    return itemlist[index];
}

uint32_t Container::getItemHoldingCount() const
{
    uint32_t counter = 0;
    for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
        ++counter;
    }
    return counter;
}

bool Container::isHoldingItem(const Item* item) const
{
    for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
        if (*it == item) {
            return true;
        }
    }
    return false;
}

void Container::onAddContainerItem(Item* item)
{
    SpectatorVec list;
    g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2);

    //send to client
    for (Creature* spectator : list) {
        spectator->getPlayer()->sendAddContainerItem(this, item);
    }

    //event methods
    for (Creature* spectator : list) {
        spectator->getPlayer()->onAddContainerItem(item);
    }
}

void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem)
{
    SpectatorVec list;
    g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2);

    //send to client
    for (Creature* spectator : list) {
        spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem);
    }

    //event methods
    for (Creature* spectator : list) {
        spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem);
    }
}

void Container::onRemoveContainerItem(uint32_t index, Item* item)
{
    SpectatorVec list;
    g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2);

    //send change to client
    for (Creature* spectator : list) {
        spectator->getPlayer()->sendRemoveContainerItem(this, index);
    }

    //event methods
    for (Creature* spectator : list) {
        spectator->getPlayer()->onRemoveContainerItem(this, item);
    }
}

ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count,
        uint32_t flags, Creature* actor/* = nullptr*/) const
{
    bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags);
    if (childIsOwner) {
        //a child container is querying, since we are the top container (not carried by a player)
        //just return with no error.
        return RETURNVALUE_NOERROR;
    }

    if (!unlocked) {
        return RETURNVALUE_NOTPOSSIBLE;
    }

    const Item* item = thing.getItem();
    if (item == nullptr) {
        return RETURNVALUE_NOTPOSSIBLE;
    }

    if (!item->isPickupable()) {
        return RETURNVALUE_CANNOTPICKUP;
    }

    if (item == this) {
        return RETURNVALUE_THISISIMPOSSIBLE;
    }

    const Cylinder* cylinder = getParent();
    if (!hasBitSet(FLAG_NOLIMIT, flags)) {
        while (cylinder) {
            if (cylinder == &thing) {
                return RETURNVALUE_THISISIMPOSSIBLE;
            }

            cylinder = cylinder->getParent();
        }

        if (index == INDEX_WHEREEVER && size() >= capacity()) {
            return RETURNVALUE_CONTAINERNOTENOUGHROOM;
        }
    } else {
        while (cylinder) {
            if (cylinder == &thing) {
                return RETURNVALUE_THISISIMPOSSIBLE;
            }

            cylinder = cylinder->getParent();
        }
    }

    const Cylinder* topParent = getTopParent();
    if (topParent != this) {
        return topParent->queryAdd(INDEX_WHEREEVER, *item, count, flags | FLAG_CHILDISOWNER, actor);
    } else {
        return RETURNVALUE_NOERROR;
    }
}

ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count,
        uint32_t& maxQueryCount, uint32_t flags) const
{
    const Item* item = thing.getItem();
    if (item == nullptr) {
        maxQueryCount = 0;
        return RETURNVALUE_NOTPOSSIBLE;
    }

    if (hasBitSet(FLAG_NOLIMIT, flags)) {
        maxQueryCount = std::max<uint32_t>(1, count);
        return RETURNVALUE_NOERROR;
    }

    int32_t freeSlots = std::max<int32_t>(capacity() - size(), 0);

    if (item->isStackable()) {
        uint32_t n = 0;

        if (index == INDEX_WHEREEVER) {
            //Iterate through every item and check how much free stackable slots there is.
            uint32_t slotIndex = 0;
            for (Item* containerItem : itemlist) {
                if (containerItem != item && containerItem->equals(item) && containerItem->getItemCount() < 100) {
                    uint32_t remainder = (100 - containerItem->getItemCount());
                    if (queryAdd(slotIndex++, *item, remainder, flags) == RETURNVALUE_NOERROR) {
                        n += remainder;
                    }
                }
            }
        } else {
            const Item* destItem = getItemByIndex(index);
            if (item->equals(destItem) && destItem->getItemCount() < 100) {
                uint32_t remainder = 100 - destItem->getItemCount();
                if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) {
                    n = remainder;
                }
            }
        }

        maxQueryCount = freeSlots * 100 + n;
        if (maxQueryCount < count) {
            return RETURNVALUE_CONTAINERNOTENOUGHROOM;
        }
    } else {
        maxQueryCount = freeSlots;
        if (maxQueryCount == 0) {
            return RETURNVALUE_CONTAINERNOTENOUGHROOM;
        }
    }
    return RETURNVALUE_NOERROR;
}

ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const
{
    int32_t index = getThingIndex(&thing);
    if (index == -1) {
        return RETURNVALUE_NOTPOSSIBLE;
    }

    const Item* item = thing.getItem();
    if (item == nullptr) {
        return RETURNVALUE_NOTPOSSIBLE;
    }

    if (count == 0 || (item->isStackable() && count > item->getItemCount())) {
        return RETURNVALUE_NOTPOSSIBLE;
    }

    if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) {
        return RETURNVALUE_NOTMOVEABLE;
    }
    return RETURNVALUE_NOERROR;
}

Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** destItem,
        uint32_t& flags)
{
    if (!unlocked) {
        *destItem = nullptr;
        return this;
    }

    if (index == 254 /*move up*/) {
        index = INDEX_WHEREEVER;
        *destItem = nullptr;

        Container* parentContainer = dynamic_cast<Container*>(getParent());
        if (parentContainer) {
            return parentContainer;
        }
        return this;
    }

    if (index == 255 /*add wherever*/) {
        index = INDEX_WHEREEVER;
        *destItem = nullptr;
    } else if (index >= static_cast<int32_t>(capacity())) {
        /*
        if you have a container, maximize it to show all 20 slots
        then you open a bag that is inside the container you will have a bag with 8 slots
        and a "grey" area where the other 12 slots where from the container
        if you drop the item on that grey area
        the client calculates the slot position as if the bag has 20 slots
        */
        index = INDEX_WHEREEVER;
        *destItem = nullptr;
    }

    const Item* item = thing.getItem();
    if (!item) {
        return this;
    }

    bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags);
    if (autoStack && item->isStackable() && item->getParent() != 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() < 100) {
                *destItem = listItem;
                index = n;
                return this;
            }
            ++n;
        }
    }

    if (index != INDEX_WHEREEVER) {
        Item* itemFromIndex = getItemByIndex(index);
        if (itemFromIndex) {
            *destItem = itemFromIndex;
        }

        Cylinder* subCylinder = dynamic_cast<Cylinder*>(*destItem);
        if (subCylinder) {
            index = INDEX_WHEREEVER;
            *destItem = nullptr;
            return subCylinder;
        }
    }
    return this;
}

void Container::addThing(Thing* thing)
{
    return addThing(0, thing);
}

void Container::addThing(int32_t index, Thing* thing)
{
    if (index >= static_cast<int32_t>(capacity())) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    Item* item = thing->getItem();
    if (item == nullptr) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    item->setParent(this);
    itemlist.push_front(item);
    updateItemWeight(item->getWeight());

    //send change to client
    if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) {
        onAddContainerItem(item);
    }
}

void Container::addItemBack(Item* item)
{
    addItem(item);
    updateItemWeight(item->getWeight());

    //send change to client
    if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) {
        onAddContainerItem(item);
    }
}

void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count)
{
    int32_t index = getThingIndex(thing);
    if (index == -1) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    Item* item = thing->getItem();
    if (item == nullptr) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    const int32_t oldWeight = item->getWeight();
    item->setID(itemId);
    item->setSubType(count);
    updateItemWeight(-oldWeight + item->getWeight());

    //send change to client
    if (getParent()) {
        onUpdateContainerItem(index, item, item);
    }
}

void Container::replaceThing(uint32_t index, Thing* thing)
{
    Item* item = thing->getItem();
    if (!item) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    Item* replacedItem = getItemByIndex(index);
    if (!replacedItem) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    itemlist[index] = item;
    item->setParent(this);
    updateItemWeight(-static_cast<int32_t>(replacedItem->getWeight()) + item->getWeight());

    //send change to client
    if (getParent()) {
        onUpdateContainerItem(index, replacedItem, item);
    }

    replacedItem->setParent(nullptr);
}

void Container::removeThing(Thing* thing, uint32_t count)
{
    Item* item = thing->getItem();
    if (item == nullptr) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    int32_t index = getThingIndex(thing);
    if (index == -1) {
        return /*RETURNVALUE_NOTPOSSIBLE*/;
    }

    if (item->isStackable() && count != item->getItemCount()) {
        uint8_t newCount = static_cast<uint8_t>(std::max<int32_t>(0, item->getItemCount() - count));
        const int32_t oldWeight = item->getWeight();
        item->setItemCount(newCount);
        updateItemWeight(-oldWeight + item->getWeight());

        //send change to client
        if (getParent()) {
            onUpdateContainerItem(index, item, item);
        }
    } else {
        updateItemWeight(-static_cast<int32_t>(item->getWeight()));

        //send change to client
        if (getParent()) {
            onRemoveContainerItem(index, item);
        }

        item->setParent(nullptr);
        itemlist.erase(itemlist.begin() + index);
    }
}

int32_t Container::getThingIndex(const Thing* thing) const
{
    int32_t index = 0;
    for (Item* item : itemlist) {
        if (item == thing) {
            return index;
        }
        ++index;
    }
    return -1;
}

size_t Container::getFirstIndex() const
{
    return 0;
}

size_t Container::getLastIndex() const
{
    return size();
}

uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType/* = -1*/) const
{
    uint32_t count = 0;
    for (Item* item : itemlist) {
        if (item->getID() == itemId) {
            count += countByType(item, subType);
        }
    }
    return count;
}

std::map<uint32_t, uint32_t>& Container::getAllItemTypeCount(std::map<uint32_t, uint32_t> &countMap) const
{
    for (Item* item : itemlist) {
        countMap[item->getID()] += item->getItemCount();
    }
    return countMap;
}

Thing* Container::getThing(size_t index) const
{
    return getItemByIndex(index);
}

void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
{
    Cylinder* topParent = getTopParent();
    if (topParent->getCreature()) {
        topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT);
    } else if (topParent == this) {
        //let the tile class notify surrounding players
        if (topParent->getParent()) {
            topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR);
        }
    } else {
        topParent->postAddNotification(thing, oldParent, index, LINK_PARENT);
    }
}

void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
{
    Cylinder* topParent = getTopParent();
    if (topParent->getCreature()) {
        topParent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT);
    } else if (topParent == this) {
        //let the tile class notify surrounding players
        if (topParent->getParent()) {
            topParent->getParent()->postRemoveNotification(thing, newParent, index, LINK_NEAR);
        }
    } else {
        topParent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
    }
}

void Container::internalAddThing(Thing* thing)
{
    internalAddThing(0, thing);
}

void Container::internalAddThing(uint32_t, Thing* thing)
{
    Item* item = thing->getItem();
    if (item == nullptr) {
        return;
    }

    item->setParent(this);
    itemlist.push_front(item);
    updateItemWeight(item->getWeight());
}

void Container::startDecaying()
{
    for (Item* item : itemlist) {
        item->startDecaying();
    }
}

ContainerIterator Container::iterator() const
{
    ContainerIterator cit;
    if (!itemlist.empty()) {
        cit.over.push_back(this);
        cit.cur = itemlist.begin();
    }
    return cit;
}

Item* ContainerIterator::operator*()
{
    return *cur;
}

void ContainerIterator::advance()
{
    if (Item* i = *cur) {
        if (Container* c = i->getContainer()) {
            if (!c->empty()) {
                over.push_back(c);
            }
        }
    }

    ++cur;

    if (cur == over.front()->itemlist.end()) {
        over.pop_front();
        if (!over.empty()) {
            cur = over.front()->itemlist.begin();
        }
    }
}
 
Well gentlemen, i made some progress finally , i find out that only happens if the item has <attribute key="type" value="rune" /> on items.xml .If i remove this line, i have no problem with bags full ,but ..... removing this atrybute from runes i can't move them all together just one by one,and the description of rune is not shown .
hope anyone can help-me =)
That problem should be solved removing stackable from runes on items.otb, but if i remove, everytime i see a rune the client crash.
Editing the tibia.dat i'm able to move more runes at the same time ,just like other stackable items , but i don't want to make changes on the client,.Any chance of solving this by editing the sources?
 
Last edited:
Well gentlemen, i made some progress finally , i find out that only happens if the item has <attribute key="type" value="rune" /> on items.xml .If i remove this line, i have no problem with bags full ,but ..... removing this atrybute from runes i can't move them all together just one by one,and the description of rune is not shown .
hope anyone can help-me =)
That problem should be solved removing stackable from runes on items.otb, but if i remove, everytime i see a rune the client crash.
Editing the tibia.dat i'm able to move more runes at the same time ,just like other stackable items , but i don't want to make changes on the client,.Any chance of solving this by editing the sources?
Tibia.dat and items.otb are a combo, settimgs should match.
 
Tibia.dat and items.otb are a combo, settimgs should match.
yes its true ,but if i remove stackable from items.otb i get debug everytime i see a rune , propably is some issue of this distro that send some fucked packet , maybe its because this server is downgraded from 8.6 and 8.6 has stackable runes, and to save some time ninja didn't change this properly in sources.


tibiadebug.png
 
Last edited:
Back
Top