• 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!
  • 2026 staff recruitment is open! Check it out and consider applying!

Leaving house doesnt remove nonpickupable items

Wusse

Member
Joined
Oct 3, 2023
Messages
95
Solutions
2
Reaction score
23
Hey!

Tfs 1.4.2

When selling a house, the decoration items a player would usually have from construction kits like tables and chairs stay in the house even after leaving. the rest of the items that can be picked-up gets sent to the inbox.

The problem with this is that the chairs and tables will stay in that house forever. Map cleaning doesnt remove them nor does server save.

So im wondering how you guys handle this issue?

I know that some servers turn the items into their construction kit part and then sends it to the players inbox.

any ideas on how to achieve this?

and what is the goal if the item doesnt have a construction kit part, lets say a random non-pickupable item that a player dragged into their house, the smart thing would be to delete those items but how do you seperate what items have been dragged in and what have been there from the map editor?

Reference:
 
Solution
If its tfs 1.4.2 then you need to change the sources.
Look in house.cpp, it needs some editing
LUA:
bool House::transferToDepot(Player* player) const
After alot of debugging and trial and errors i actually made it to work.

the only downpart about the solution is that i kinda have to hardcode the items that have a construction kit declared to it.

and yes it doesnt remove all items that arent pickupable but it covers the items that players actually go to buy from the NPC so thats a win.

thank you!
Post automatically merged:

This is the solution i came up with (if anybody has a way to make it better id love to see the input!)

C++:
bool House::transferToDepot(Player* player) const
{
    if (townId == 0 || owner == 0) {
        return...
items.xml
You need to set flag forcesave/forceserialize == true
That didnt really work

I tried adding both attributes to the items:
LUA:
    <item id="3807" article="a" name="tusk table">
        <attribute key="rotateTo" value="3808" />
        <attribute key="destroyTo" value="2253" />
        <attribute key="forcesave" value="true" />
    </item>
    <item id="3808" article="a" name="tusk table">
        <attribute key="rotateTo" value="3807" />
        <attribute key="destroyTo" value="2253" />
        <attribute key="forceserialize" value="true" />
    </item>

Neither of them works :/

same thing happens, but the attribute is getting recognized by the server so i tried to clean map and close and open the server. the items are still in the house.
 
If its tfs 1.4.2 then you need to change the sources.
Look in house.cpp, it needs some editing
LUA:
bool House::transferToDepot(Player* player) const
 
If its tfs 1.4.2 then you need to change the sources.
Look in house.cpp, it needs some editing
LUA:
bool House::transferToDepot(Player* player) const
After alot of debugging and trial and errors i actually made it to work.

the only downpart about the solution is that i kinda have to hardcode the items that have a construction kit declared to it.

and yes it doesnt remove all items that arent pickupable but it covers the items that players actually go to buy from the NPC so thats a win.

thank you!
Post automatically merged:

This is the solution i came up with (if anybody has a way to make it better id love to see the input!)

C++:
bool House::transferToDepot(Player* player) const
{
    if (townId == 0 || owner == 0) {
        return false;
    }

    std::vector<std::pair<Item*, int32_t>> itemsToMove;

    for (HouseTile* tile : houseTiles) {
        if (const TileItemVector* items = tile->getItemList()) {
            for (Item* item : *items) {
                // Check if its a furniture that should become a kit
                int32_t kitId = 0;
                switch (item->getID()) {
                case 1666: case 1667: case 1668: case 1669: kitId = 3901; break;
                case 1670: case 1671: case 1672: case 1673: kitId = 3902; break;
                case 1650: case 1651: case 1652: case 1653: kitId = 3903; break;
                case 1674: case 1675: case 1676: case 1677: kitId = 3904; break;
                case 1658: case 1659: case 1660: case 1661: kitId = 3905; break;
                case 3813: kitId = 3906; break;
                case 3817: kitId = 3907; break;
                case 1619: kitId = 3908; break;
                case 2105: kitId = 3909; break;
                case 12799: kitId = 3910; break;
                case 1614: kitId = 3911; break;
                case 3805: case 3806: kitId = 3912; break;
                case 3807: case 3808: kitId = 3913; break;
                case 3809: case 3810: kitId = 3914; break;
                case 1714: case 1715: case 1716: case 1717: kitId = 3915; break;
                case 1724: case 1725: case 1726: case 1727: kitId = 3916; break;
                case 1732: case 1733: case 1734: case 1735: kitId = 3917; break;
                case 1775: kitId = 3918; break;
                case 1774: kitId = 3919; break;
                case 1750: case 1751: case 1752: case 1753: kitId = 3920; break;
                case 3832: kitId = 3921; break;
                case 2095: kitId = 3922; break;
                case 2098: kitId = 3923; break;
                case 2064: kitId = 3924; break;
                case 2582: kitId = 3925; break;
                case 2117: kitId = 3926; break;
                case 1728: kitId = 3927; break;
                case 1442: kitId = 3928; break;
                case 1446: kitId = 3929; break;
                case 1447: kitId = 3930; break;
                case 2034: kitId = 3931; break;
                case 2604: kitId = 3932; break;
                case 2080: kitId = 3933; break;
                case 2084: kitId = 3934; break;
                case 3821: kitId = 3935; break;
                case 3811: kitId = 3936; break;
                case 2101: kitId = 3937; break;
                case 3812: kitId = 3938; break;
                case 5046: kitId = 5086; break;
                case 5055: kitId = 5087; break;
                case 5056: kitId = 5088; break;
                case 6109: case 6110: kitId = 6115; break;
                case 6111: case 6112: kitId = 6114; break;
                case 6356: kitId = 6372; break;
                case 6368: case 6369: case 6370: case 6371: kitId = 6373; break;
                case 8688: kitId = 8692; break;
                case 9975: kitId = 9974; break;
                case 11127: kitId = 11126; break;
                case 11129: kitId = 11133; break;
                case 11125: kitId = 11124; break;
                case 11203: kitId = 11205; break;
                case 1616: kitId = 14328; break;
                case 1615: kitId = 14329; break;
                case 16020: kitId = 16075; break;
                case 16098: kitId = 16099; break;
                case 20295: case 20296: kitId = 20254; break;
                case 20297: case 20298: kitId = 20255; break;
                case 20299: case 20300: kitId = 20257; break;
                default: break;
                }

                // furnitures with containers gets emptied here first
                if (kitId != 0 && item->getContainer()) {
                    Container* container = item->getContainer();
                    for (Item* containerItem : container->getItemList()) {
                        g_game.internalMoveItem(containerItem->getParent(), player->getInbox(),
                            INDEX_WHEREEVER, containerItem, containerItem->getItemCount(),
                            nullptr, FLAG_NOLIMIT);
                    }
                }

                if (kitId != 0) {
                    itemsToMove.push_back({ item, kitId });
                }
                else if (item->isPickupable()) {
                    itemsToMove.push_back({ item, 0 });
                }
            }
        }
    }

    for (auto& entry : itemsToMove) {
        Item* item = entry.first;
        int32_t kitId = entry.second;

        if (kitId != 0) {
            Item* kit = Item::CreateItem(kitId, item->getItemCount());
            if (kit) {
                if (g_game.internalAddItem(player->getInbox(), kit, INDEX_WHEREEVER, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
                    g_game.internalRemoveItem(item, item->getItemCount());
                }
                else {
                    delete kit;
                }
            }
        }
        else {
            g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT);
        }
    }

    return true;
}


The normal functionality is intact the difference is that we have hardcoded the items that are declared in data/actions/scripts/other/construction_kits.lua (these are the decoration items normally sold by NPCs)

So what the code actually does is this:
1. it handles pickupable items normally.
2. Constructed items have been declared by me with their rotated counterparts. (for an example some tables have 2 directions but are essentially the same item but with different itemIDs)
3. The code will empty out the items in the containers of the constructed items before "transforming" them into kits. (meaning if you have a armor rack with items inside of it the items gets sent to your inbox before making the item into a kit that also later on gets sent to your inbox.
 
Last edited:
Solution
After alot of debugging and trial and errors i actually made it to work.

the only downpart about the solution is that i kinda have to hardcode the items that have a construction kit declared to it.

and yes it doesnt remove all items that arent pickupable but it covers the items that players actually go to buy from the NPC so thats a win.

thank you!
Post automatically merged:

This is the solution i came up with (if anybody has a way to make it better id love to see the input!)

C++:
bool House::transferToDepot(Player* player) const
{
    if (townId == 0 || owner == 0) {
        return false;
    }

    std::vector<std::pair<Item*, int32_t>> itemsToMove;

    for (HouseTile* tile : houseTiles) {
        if (const TileItemVector* items = tile->getItemList()) {
            for (Item* item : *items) {
                // Check if its a furniture that should become a kit
                int32_t kitId = 0;
                switch (item->getID()) {
                case 1666: case 1667: case 1668: case 1669: kitId = 3901; break;
                case 1670: case 1671: case 1672: case 1673: kitId = 3902; break;
                case 1650: case 1651: case 1652: case 1653: kitId = 3903; break;
                case 1674: case 1675: case 1676: case 1677: kitId = 3904; break;
                case 1658: case 1659: case 1660: case 1661: kitId = 3905; break;
                case 3813: kitId = 3906; break;
                case 3817: kitId = 3907; break;
                case 1619: kitId = 3908; break;
                case 2105: kitId = 3909; break;
                case 12799: kitId = 3910; break;
                case 1614: kitId = 3911; break;
                case 3805: case 3806: kitId = 3912; break;
                case 3807: case 3808: kitId = 3913; break;
                case 3809: case 3810: kitId = 3914; break;
                case 1714: case 1715: case 1716: case 1717: kitId = 3915; break;
                case 1724: case 1725: case 1726: case 1727: kitId = 3916; break;
                case 1732: case 1733: case 1734: case 1735: kitId = 3917; break;
                case 1775: kitId = 3918; break;
                case 1774: kitId = 3919; break;
                case 1750: case 1751: case 1752: case 1753: kitId = 3920; break;
                case 3832: kitId = 3921; break;
                case 2095: kitId = 3922; break;
                case 2098: kitId = 3923; break;
                case 2064: kitId = 3924; break;
                case 2582: kitId = 3925; break;
                case 2117: kitId = 3926; break;
                case 1728: kitId = 3927; break;
                case 1442: kitId = 3928; break;
                case 1446: kitId = 3929; break;
                case 1447: kitId = 3930; break;
                case 2034: kitId = 3931; break;
                case 2604: kitId = 3932; break;
                case 2080: kitId = 3933; break;
                case 2084: kitId = 3934; break;
                case 3821: kitId = 3935; break;
                case 3811: kitId = 3936; break;
                case 2101: kitId = 3937; break;
                case 3812: kitId = 3938; break;
                case 5046: kitId = 5086; break;
                case 5055: kitId = 5087; break;
                case 5056: kitId = 5088; break;
                case 6109: case 6110: kitId = 6115; break;
                case 6111: case 6112: kitId = 6114; break;
                case 6356: kitId = 6372; break;
                case 6368: case 6369: case 6370: case 6371: kitId = 6373; break;
                case 8688: kitId = 8692; break;
                case 9975: kitId = 9974; break;
                case 11127: kitId = 11126; break;
                case 11129: kitId = 11133; break;
                case 11125: kitId = 11124; break;
                case 11203: kitId = 11205; break;
                case 1616: kitId = 14328; break;
                case 1615: kitId = 14329; break;
                case 16020: kitId = 16075; break;
                case 16098: kitId = 16099; break;
                case 20295: case 20296: kitId = 20254; break;
                case 20297: case 20298: kitId = 20255; break;
                case 20299: case 20300: kitId = 20257; break;
                default: break;
                }

                // furnitures with containers gets emptied here first
                if (kitId != 0 && item->getContainer()) {
                    Container* container = item->getContainer();
                    for (Item* containerItem : container->getItemList()) {
                        g_game.internalMoveItem(containerItem->getParent(), player->getInbox(),
                            INDEX_WHEREEVER, containerItem, containerItem->getItemCount(),
                            nullptr, FLAG_NOLIMIT);
                    }
                }

                if (kitId != 0) {
                    itemsToMove.push_back({ item, kitId });
                }
                else if (item->isPickupable()) {
                    itemsToMove.push_back({ item, 0 });
                }
            }
        }
    }

    for (auto& entry : itemsToMove) {
        Item* item = entry.first;
        int32_t kitId = entry.second;

        if (kitId != 0) {
            Item* kit = Item::CreateItem(kitId, item->getItemCount());
            if (kit) {
                if (g_game.internalAddItem(player->getInbox(), kit, INDEX_WHEREEVER, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) {
                    g_game.internalRemoveItem(item, item->getItemCount());
                }
                else {
                    delete kit;
                }
            }
        }
        else {
            g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT);
        }
    }

    return true;
}


The normal functionality is intact the difference is that we have hardcoded the items that are declared in data/actions/scripts/other/construction_kits.lua (these are the decoration items normally sold by NPCs)

So what the code actually does is this:
1. it handles pickupable items normally.
2. Constructed items have been declared by me with their rotated counterparts. (for an example some tables have 2 directions but are essentially the same item but with different itemIDs)
3. The code will empty out the items in the containers of the constructed items before "transforming" them into kits. (meaning if you have a armor rack with items inside of it the items gets sent to your inbox before making the item into a kit that also later on gets sent to your inbox.
Perhaps using less "hardcoding" by defining all items manually would be better. On my server, for example, I've set dummies to pickable, but that looks bad visually. Would it be feasible to create a "construction kit" flag and set it to true in items.xml, so that the code reads it and I don't have to define each item individually in C++?
 
Perhaps using less "hardcoding" by defining all items manually would be better. On my server, for example, I've set dummies to pickable, but that looks bad visually. Would it be feasible to create a "construction kit" flag and set it to true in items.xml, so that the code reads it and I don't have to define each item individually in C++?
I like this idea a lot and had a similar thought about it.

The biggest issue would be:
How to actually create these items since the contruction kits are already items with different itemIDs right.

so for an example a construction kit version for a Round Table is a item with a itemDescription saying something along the line of "construction kit for a round table" these arent items that get generated but items that are declared already in items.xml and in the dat/spr files.

As much as i love the idea i think it requires a lot of work.
 
I like this idea a lot and had a similar thought about it.

The biggest issue would be:
How to actually create these items since the contruction kits are already items with different itemIDs right.

so for an example a construction kit version for a Round Table is a item with a itemDescription saying something along the line of "construction kit for a round table" these arent items that get generated but items that are declared already in items.xml and in the dat/spr files.

As much as i love the idea i think it requires a lot of work.
I was thinking of something more or less like this (I don't know if it works, probably not, we'd need to test it):

item.h
C++:
class ItemType {
public:
    uint16_t constructionKitId = 0;

    bool hasConstructionKit() const {
        return constructionKitId != 0;
    }
};

inside Items::parseItemNode:
C++:
if (key == "constructionKitId") {
    it.constructionKitId = pugi::cast<uint16_t>(value);
}

house logic:
C++:
for (HouseTile* tile : houseTiles) {
    if (const TileItemVector* items = tile->getItemList()) {
        for (Item* item : *items) {

            const ItemType& it = Item::items[item->getID()];
            uint16_t kitId = it.constructionKitId;

            if (kitId != 0) {
                if (Container* container = item->getContainer()) {
                    for (Item* containerItem : container->getItemList()) {
                        g_game.internalMoveItem(
                            containerItem->getParent(),
                            player->getInbox(),
                            INDEX_WHEREEVER,
                            containerItem,
                            containerItem->getItemCount(),
                            nullptr,
                            FLAG_NOLIMIT
                        );
                    }
                }

                itemsToMove.emplace_back(item, kitId);
            }
            else if (item->isPickupable()) {
                itemsToMove.emplace_back(item, 0);
            }
        }
    }
}

And finally, in items who must turn a construction kit we need to point with this flag (so we will have the same kitId for IDs of round table):
XML:
<attribute key="constructionKitId" value="3913"/>
 
I was thinking of something more or less like this (I don't know if it works, probably not, we'd need to test it):

item.h
C++:
class ItemType {
public:
    uint16_t constructionKitId = 0;

    bool hasConstructionKit() const {
        return constructionKitId != 0;
    }
};

inside Items::parseItemNode:
C++:
if (key == "constructionKitId") {
    it.constructionKitId = pugi::cast<uint16_t>(value);
}

house logic:
C++:
for (HouseTile* tile : houseTiles) {
    if (const TileItemVector* items = tile->getItemList()) {
        for (Item* item : *items) {

            const ItemType& it = Item::items[item->getID()];
            uint16_t kitId = it.constructionKitId;

            if (kitId != 0) {
                if (Container* container = item->getContainer()) {
                    for (Item* containerItem : container->getItemList()) {
                        g_game.internalMoveItem(
                            containerItem->getParent(),
                            player->getInbox(),
                            INDEX_WHEREEVER,
                            containerItem,
                            containerItem->getItemCount(),
                            nullptr,
                            FLAG_NOLIMIT
                        );
                    }
                }

                itemsToMove.emplace_back(item, kitId);
            }
            else if (item->isPickupable()) {
                itemsToMove.emplace_back(item, 0);
            }
        }
    }
}

And finally, in items who must turn a construction kit we need to point with this flag (so we will have the same kitId for IDs of round table):
XML:
<attribute key="constructionKitId" value="3913"/>
OH i see what you mean,

Its not a bad approach at all and would 100% remove the hardcoding itemids from the SRC.

And when a server would want new items to become "decoration items" they could simply make a new construction kit item as a counterpart for that specific "decoration item" and simply declare The flag in items.xml
(that also includes registrering it in construction_kits.lua so they can unpack the item).

Its actually smart.

Im currently working on something entierly different so i wont have time to test this out unfortunatly but i love the idea!
 
Back
Top