• 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!

Lua TFS 1.x how to save items to database with Lua

whitevo

Feeling good, thats what I do.
Joined
Jan 2, 2015
Messages
3,452
Solutions
1
Reaction score
625
Location
Estonia
I am creating completely new housing feature for my server and was not using any of the functions given by the source.

I was thinking how should I save and load player houses with Lua.
Got few ideas, but first wanted to try what does these "houseTiles" do (the ones what you can set in RME)

And I was a amazed. I could simply create one house what covers all the houses and it would save the items what are on those tiles.

But there was one problem. It doesn't save all the objects only movable items I think?
How can I make it so that house tiles save: walls, floors, stones, ladders, etc? (in other words everything)

I think I finally found where object checking takes place which to save and which not.
But i suck at c++
So what must be changed here so it will save ALL items to database?

Right now my guess is that if I remove this line, it will work?
Or I need to do something more?

https://github.com/otland/forgotten...f69ac47d6354/src/iomapserialize.cpp#L250-L252

But what must be added to change floor ID's? Or I should save them to database separately and simply redraw them on server startup?
 
Last edited:
removing the above line (the one i linked to) still was not enough.
The items are not still drawn on load.
What am I missing?

EDIT:
The wall is registered to database, but in some reason it was not loaded

Is it because of this?
Code:
uint32_t item_count;
        if (!propStream.read<uint32_t>(item_count)) {
            continue;
        }

        while (item_count--) {
            loadItem(propStream, tile);
        }

I'm not sure what does it mean, but if its like: if item doesn't have count then don't create the item.
And I'm not sure can "wall" be counted?
So perhaps that is why its not loaded?
If that is the case, how I change the item_count to 1 if its missing?
 
Last edited:
you can just save the buildable house spots / items to a file (if items dont have attributes its super easy) and create them on restart
 
you can just save the buildable house spots / items to a file (if items dont have attributes its super easy) and create them on restart
don't know how to do it in Lua. And I don't see the point in it if source already has the function to save and load these items... I just cant figure out what is stopping the item loading for unmoveable objects.
 
can you at least read first post?


So the else part does not create walls?

Will this help then?
Code:
if (const TileItemVector* items = tile->getItemList()) {
            for (Item* findItem : *items) {
                    item = findItem;
                    break;
            }
}
Meh your fault for using bold text for stuff you already found the answer.

Well you can just push the ground to the vector:
Above:
Code:
    for (Item* item : *tileItems) {

Place:
Code:
    Item* ground = tile->getGround();
    if (ground) {
        items.push_front(ground);
    }

EDIT:
To load imoveable items you have to remove that check if (iType.moveable || !tile) but it may break beds and stuff
 
Last edited:
Alright, I will try tomorrow (maybe today), and let you know how it went.
Although the creating grounds with source is better. I don't really get the logic how that line works.
And already created Lua version to save and load ground tiles. (while I was waiting help on this matter)

EDIT:

Apparently It didn't need to compile all files Just the 2 I edited.

Result was the same. It did not load the wall. There must be something else

This is how final function looks like
Code:
bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
{
    uint16_t id;
    if (!propStream.read<uint16_t>(id)) {
        return false;
    }

    Tile* tile = nullptr;
    if (parent->getParent() == nullptr) {
        tile = parent->getTile();
    }

    const ItemType& iType = Item::items[id];
    if (iType.moveable || !tile) {
        //create a new item
        Item* item = Item::CreateItem(id);
        if (item) {
            if (item->unserializeAttr(propStream)) {
                Container* container = item->getContainer();
                if (container && !loadContainer(propStream, container)) {
                    delete item;
                    return false;
                }

                parent->internalAddThing(item);
                item->startDecaying();
            } else {
                std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
                delete item;
                return false;
            }
        }
    } else {
        // Stationary items like doors/beds/blackboards/bookcases
        Item* item = nullptr;
        if (const TileItemVector* items = tile->getItemList()) {
            for (Item* findItem : *items) {
                item = findItem;
                break;
            }
        }

        if (item) {
            if (item->unserializeAttr(propStream)) {
                Container* container = item->getContainer();
                if (container && !loadContainer(propStream, container)) {
                    return false;
                }

                g_game.transformItem(item, id);
            } else {
                std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
            }
        } else {
            //The map changed since the last save, just read the attributes
            std::unique_ptr<Item> dummy(Item::CreateItem(id));
            if (dummy) {
                dummy->unserializeAttr(propStream);
                Container* container = dummy->getContainer();
                if (container) {
                    if (!loadContainer(propStream, container)) {
                        return false;
                    }
                } else if (BedItem* bedItem = dynamic_cast<BedItem*>(dummy.get())) {
                    uint32_t sleeperGUID = bedItem->getSleeper();
                    if (sleeperGUID != 0) {
                        g_game.removeBedSleeper(sleeperGUID);
                    }
                }
            }
        }
    }
    return true;
}
 
Last edited:
don't know how to do it in Lua. And I don't see the point in it if source already has the function to save and load these items... I just cant figure out what is stopping the item loading for unmoveable objects.
tile:getItems() -> save in .lua file in table format [pos] = {ids in order} -> dofile on startup -> draw
 
To load imoveable items you have to remove that check if (iType.moveable || !tile) but it may break beds and stuff
Uff so how does the final function look like?
(there might be some {} symbols missing or too much, but overall does it look right?)

Code:
bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
{
    uint16_t id;
    if (!propStream.read<uint16_t>(id)) {
        return false;
    }

    Tile* tile = nullptr;
    if (parent->getParent() == nullptr) {
        tile = parent->getTile();
    }

    const ItemType& iType = Item::items[id];
        //create a new item
        Item* item = Item::CreateItem(id);
        if (item) {
            if (item->unserializeAttr(propStream)) {
                Container* container = item->getContainer();
                if (container && !loadContainer(propStream, container)) {
                    delete item;
                    return false;
                }

                parent->internalAddThing(item);
                item->startDecaying();
            } else {
                std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
                delete item;
                return false;
            }
        // Stationary items like doors/beds/blackboards/bookcases
        Item* item = nullptr;
        if (const TileItemVector* items = tile->getItemList()) {
            for (Item* findItem : *items) {
                item = findItem;
                break;
            }
        }

        if (item) {
            if (item->unserializeAttr(propStream)) {
                Container* container = item->getContainer();
                if (container && !loadContainer(propStream, container)) {
                    return false;
                }

                g_game.transformItem(item, id);
            } else {
                std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
            }
        } else {
            //The map changed since the last save, just read the attributes
            std::unique_ptr<Item> dummy(Item::CreateItem(id));
            if (dummy) {
                dummy->unserializeAttr(propStream);
                Container* container = dummy->getContainer();
                if (container) {
                    if (!loadContainer(propStream, container)) {
                        return false;
                    }
                } else if (BedItem* bedItem = dynamic_cast<BedItem*>(dummy.get())) {
                    uint32_t sleeperGUID = bedItem->getSleeper();
                    if (sleeperGUID != 0) {
                        g_game.removeBedSleeper(sleeperGUID);
                    }
                }
            }
    }
    return true;
}
 
Uff so how does the final function look like?
(there might be some {} symbols missing or too much, but overall does it look right?)

Code:
bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
{
    uint16_t id;
    if (!propStream.read<uint16_t>(id)) {
        return false;
    }

    Tile* tile = nullptr;
    if (parent->getParent() == nullptr) {
        tile = parent->getTile();
    }

    const ItemType& iType = Item::items[id];
        //create a new item
        Item* item = Item::CreateItem(id);
        if (item) {
            if (item->unserializeAttr(propStream)) {
                Container* container = item->getContainer();
                if (container && !loadContainer(propStream, container)) {
                    delete item;
                    return false;
                }

                parent->internalAddThing(item);
                item->startDecaying();
            } else {
                std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
                delete item;
                return false;
            }
        // Stationary items like doors/beds/blackboards/bookcases
        Item* item = nullptr;
        if (const TileItemVector* items = tile->getItemList()) {
            for (Item* findItem : *items) {
                item = findItem;
                break;
            }
        }

        if (item) {
            if (item->unserializeAttr(propStream)) {
                Container* container = item->getContainer();
                if (container && !loadContainer(propStream, container)) {
                    return false;
                }

                g_game.transformItem(item, id);
            } else {
                std::cout << "WARNING: Unserialization error in IOMapSerialize::loadItem()" << id << std::endl;
            }
        } else {
            //The map changed since the last save, just read the attributes
            std::unique_ptr<Item> dummy(Item::CreateItem(id));
            if (dummy) {
                dummy->unserializeAttr(propStream);
                Container* container = dummy->getContainer();
                if (container) {
                    if (!loadContainer(propStream, container)) {
                        return false;
                    }
                } else if (BedItem* bedItem = dynamic_cast<BedItem*>(dummy.get())) {
                    uint32_t sleeperGUID = bedItem->getSleeper();
                    if (sleeperGUID != 0) {
                        g_game.removeBedSleeper(sleeperGUID);
                    }
                }
            }
    }
    return true;
}
https://github.com/otland/forgottenserver/pull/1943

You are probably going to enjoy this as you're more of a Lua guy.
I implemented IOMapSerialize::saveItem as Game::serializeItem.

You can also do it for IOMapSerialize::saveTile, after getting some feedback on the PR I could do it myself.

Tbh there isn't much more to saveTile han the saveItem, it just adds the position and how many items in the tile to the stream, you could implement it in lua using the functions that I already provided for propstream/propwritestream.
 
https://github.com/otland/forgottenserver/pull/1943

You are probably going to enjoy this as you're more of a Lua guy.
I implemented IOMapSerialize::saveItem as Game::serializeItem.

You can also do it for IOMapSerialize::saveTile, after getting some feedback on the PR I could do it myself.

Tbh there isn't much more to saveTile han the saveItem, it just adds the position and how many items in the tile to the stream, you could implement it in lua using the functions that I already provided for propstream/propwritestream.
What am I doing wrong?
the stream only gives me one symbol "}"
10rlmw0.jpg

Even though I created all the functions to make house functions work with mkalo solution.
If there is anyone who knows how do edit the
bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
function to load all items, I would prefer that.

From the very begging I knew I can simply save all the items to database with Lua, but I prefer to do that with source, because I have no clue how much lag can it cause when you constantly rewrite house tiles with Lua..
And the propStream solution doesn't really look any better than just writing your own stream solution.. But realized that after I compiled so I just gave it a try xD
 
Last edited:
What am I doing wrong?
the stream only gives me one symbol "}"


Even though I created all the functions to make house functions work with mkalo solution.
If there is anyone who knows how do edit the
bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent)
function to load all items, I would prefer that.

From the very begging I knew I can simply save all the items to database with Lua, but I prefer to do that with source, because I have no clue how much lag can it cause when you constantly rewrite house tiles with Lua..
And the propStream solution doesn't really look any better than just writing your own stream solution.. But realized that after I compiled so I just gave it a try xD
Well you could in theory write your own stream solution, that wouldn't be any closer to being better than using this. You would need to implement these functions:
https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L366-L625
https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L646-L752
https://github.com/otland/forgottenserver/blob/master/src/container.cpp#L99-L146
etc etc etc...

You're not doing anything wrong. You're not suposed to print the values of a propStream, they have null bytes on it and it will not print them.
Ex: print("Mkalo\0is Beautiful") will print only "Mkalo".

You have to use the PropStream if you wanna read the information:
Code:
function onSay(player, words, param)
    local propWriteStream = PropWriteStream()
    Game.serializeItem(propWriteStream, Game.createItem(2160, 100))
    local propStream = PropStream(propWriteStream:getStream())
    print("ItemID:" .. propStream:read(uint16_t))
    print("ATTR_COUNT:" .. propStream:read(uint8_t)) -- The attribute ATTR_COUNT id is 15 so we know the next value will be a count.
    print("Count:" .. propStream:read(uint8_t))
    return false
end

When you use Game.serializeItem it will first add an uint16_t with id and then the attributes, if i's a container it will force an attribute for containers and add all items inside it in the stream.
 
Well you could in theory write your own stream solution, that wouldn't be any closer to being better than using this. You would need to implement these functions:
https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L366-L625
https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L646-L752
https://github.com/otland/forgottenserver/blob/master/src/container.cpp#L99-L146
etc etc etc...

You're not doing anything wrong. You're not suposed to print the values of a propStream, they have null bytes on it and it will not print them.
Ex: print("Mkalo\0is Beautiful") will print only "Mkalo".

You have to use the PropStream if you wanna read the information:
Code:
function onSay(player, words, param)
    local propWriteStream = PropWriteStream()
    Game.serializeItem(propWriteStream, Game.createItem(2160, 100))
    local propStream = PropStream(propWriteStream:getStream())
    print("ItemID:" .. propStream:read(uint16_t))
    print("ATTR_COUNT:" .. propStream:read(uint8_t)) -- The attribute ATTR_COUNT id is 15 so we know the next value will be a count.
    print("Count:" .. propStream:read(uint8_t))
    return false
end

When you use Game.serializeItem it will first add an uint16_t with id and then the attributes, if i's a container it will force an attribute for containers and add all items inside it in the stream.
how do I write stream to dadabase then? I though I can use TEXT field.

Code:
if not db.query("SELECT * FROM `houseFloors` WHERE 1") then
        db.query("CREATE TABLE `houseFloors` ( `id` INT NOT NULL AUTO_INCREMENT, `itemID` INT(5) NOT NULL, `itemAID` INT(5), `posx` INT(5) NOT NULL, `posy` INT(5) NOT NULL, `posz` INT(2) NOT NULL, PRIMARY KEY (`id`)) ENGINE = InnoDB;")
    else
the select query will give error to console, but it also RETURNS false (so i can use it xD)
 
how do I write stream to dadabase then? I though I can use TEXT field.

Code:
if not db.query("SELECT * FROM `houseFloors` WHERE 1") then
        db.query("CREATE TABLE `houseFloors` ( `id` INT NOT NULL AUTO_INCREMENT, `itemID` INT(5) NOT NULL, `itemAID` INT(5), `posx` INT(5) NOT NULL, `posy` INT(5) NOT NULL, `posz` INT(2) NOT NULL, PRIMARY KEY (`id`)) ENGINE = InnoDB;")
    else
the select query will give error to console, but it also RETURNS false (so i can use it xD)
Alright here is a fully working example:
http://pastebin.com/Yk9CJYqh

Hope this will clear all your doubts, tried to do it really organized.
 
Alright, so what I did was revert back my source and went with my original plan. Simply write functions what store item location and attributes to database.
I am not using serializing. Even though this seems more advanced way to go about this. It does look pretty clunky.
Not only you have to compile source to do it, but if person with no code knowledge reads it, he has no clue what the fuck is going on.

So I went went with more easy to read code and version where script user does not need to compile source.

I'm not sure how will it affect the server speed when someone constantly moves things around housetiles (that is why I preferred source edit at first).
I did add a delay so its not going to update houses every time object is moved in house. Instead it starts a timer. (also tiles are saved every 15 minutes)
Code:
local waitingForUpdateHouseTiles = {}
function building_registerHouseTileSave(position)
    if not comparePositionT(waitingForUpdateHouseTiles, position) then table.insert(waitingForUpdateHouseTiles, position) end
    stopAddEvent("houseTiles", "save")
    registerAddEvent("houseTiles", "save", 60*1000, {building_saveHouseTiles})
end

my startUp function, this is all the information I need from item and it loads houses the way I wanted.
Code:
function building_loadHouseTiles()
local houseItemData = db.storeQuery("SELECT * FROM `houseItems` WHERE 1")
    
    repeat
        local itemID = DBNumberResultReader(houseItemData, "itemID")
        if itemID and itemID ~= 0 then
            local posx = DBNumberResultReader(houseItemData, "posx")
            local posy = DBNumberResultReader(houseItemData, "posy")
            local posz = DBNumberResultReader(houseItemData, "posz")
            local itemAID = DBNumberResultReader(houseItemData, "itemAID")
            local fluidType = DBNumberResultReader(houseItemData, "fluidType")
            local count = DBNumberResultReader(houseItemData, "count")
            local itemText = result.getString(houseItemData, "itemText")
            
            createItem(itemID, {x=posx, y=posy, z=posz}, count, itemAID, fluidType, itemText)
        end
    until not result.next(houseItemData)
    result.free(houseItemData)
end

I guess this it for now.
I didn't find the help I was looking for, but hopefully this thread helps someone else or sparked some ideas for their own game.
 
Back
Top