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

Save changes made ingame TFS 1.2

Mkalo

ボーカロイド
Senator
Joined
Jun 1, 2011
Messages
1,118
Solutions
55
Reaction score
946
Location
Japan
Alright, I'll explain how does it work:

It will save the tile you choose to the database and load it whenever you call the load function. It supports attributes, but it is optional, if you don't want to modify your source you don't need to and it will still work but it wont save any attributes to the db (actionids, names, text etc...)

Lets go to the code:

Table schema:
Code:
CREATE TABLE `tile_save` (
  `itemid` int(11) NOT NULL,
  `count` int(11) NOT NULL,
  `posx` int(11) NOT NULL,
  `posy` int(11) NOT NULL,
  `posz` int(11) NOT NULL,
  `stackpos` int(11) NOT NULL,
  `attributes` blob NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Lib:
Code:
if not Item.serializeAttributes or not Item.unserializeAttributes then
    function Item.serializeAttributes(self)
        return ""
    end

    function Item.unserializeAttributes(self)
        return
    end
end

function loadSavedTiles(pos, secondpos)
    local storeQ
    if not pos then
        storeQ = "SELECT * FROM `tile_save` ORDER BY `stackpos` ASC;"
    elseif not secondpos then
        storeQ = "SELECT * FROM `tile_save` WHERE `posx` = " .. pos.x .. " AND `posy` = " .. pos.y .. " AND `posz` = " .. pos.z .. " ORDER BY `stackpos`ASC;"
    else
        storeQ = "SELECT * FROM `tile_save` WHERE `posx` >= " .. pos.x .. " AND `posy` >= " .. pos.y .. " AND `posx` <= " .. secondpos.x .. " AND `posy` <= " .. secondpos.y .. " AND `posz` >= " .. pos.z .. " AND `posz` <= " .. secondpos.z .. " ORDER BY `stackpos` ASC;"
    end

    db.asyncStoreQuery(storeQ,
    function(query)
        if query then
            local ret = {}
            repeat
                local itemid, count, posx, posy, posz, stackpos = result.getDataInt(query, "itemid"), result.getDataInt(query, "count"), result.getDataInt(query, "posx"), result.getDataInt(query, "posy"), result.getDataInt(query, "posz"), result.getDataInt(query, "stackpos")
                local attributes = result.getStream(query, "attributes") or ""
                if itemid and posx and posy and posz then
                    local tile = Tile(posx, posy, posz)
                    if tile then
                        if stackpos == 0 then
                            tile:removeFromMap()
                        end
                        local item = Game.createItem(itemid, count, {x=posx, y=posy, z=posz, stackpos=stackpos})
                        if item then
                            item:unserializeAttributes(attributes)
                        end
                    end
                end
            until not result.next(query)
        end
    end)
end

function Tile:removeFromMap()
    local items = self:getItems()
    table.insert(items, self:getGround())
    for i = 1, #items do
        items[i]:remove()
    end
end

function Tile:save()
    if self:getGround() then
        local pos = self:getPosition()
        db.query("DELETE FROM `tile_save` WHERE `posx` = " .. pos.x .. " AND `posy` = " .. pos.y .. " AND `posz` = " .. pos.z .. ";")
        local buffer = {"INSERT INTO `tile_save` (`itemid`, `count`, `posx`, `posy`, `posz`, `stackpos`, `attributes`) VALUES "}
        local items = {self:getGround()}
        local itab = self:getItems()
        for i = #itab, 1, -1 do
            table.insert(items, itab[i])
        end
        for i = 1, #items do
            local item = items[i]
            local attributes = item:serializeAttributes()
            if i ~= 1 then
                table.insert(buffer, ",")
            end
            table.insert(buffer, string.format("(%d, %d, %d, %d, %d, %d, %s)", item:getId(), item:getCount(), pos.x, pos.y, pos.z, i-1, db.escapeBlob(attributes, #attributes)))

        end
        table.insert(buffer, ";")
        db.query(table.concat(buffer))
    end
end

function Tile:removeSave()
   local pos = self:getPosition()
   db.query("DELETE FROM `tile_save` WHERE `posx` = " .. pos.x .. " AND `posy` = " .. pos.y .. " AND `posz` = " .. pos.z .. ";")
end

function Tile:load()
    loadSavedTiles(self:getPosition())
    return true
end

Add this to tile.lua in "\data\lib\core". You can stop here and jump to the talkaction if you don't wanna change your source to save attributes.

C++ :
luascript.h:

Under:
Code:
        static int luaItemRemoveAttribute(lua_State* L);

Add:
Code:
        static int luaItemSerializeAttributes(lua_State* L);
        static int luaItemUnserializeAttributes(lua_State* L);

luascript.cpp:

Under:
Code:
    registerMethod("Item", "removeAttribute", LuaScriptInterface::luaItemRemoveAttribute);

Add:
Code:
    registerMethod("Item", "serializeAttributes", LuaScriptInterface::luaItemSerializeAttributes);

    registerMethod("Item", "unserializeAttributes", LuaScriptInterface::luaItemUnserializeAttributes);


Above:
Code:
int LuaScriptInterface::luaItemMoveTo(lua_State* L)

Add:
Code:
int LuaScriptInterface::luaItemSerializeAttributes(lua_State* L)
{
    // item:serializeAttributes()
    Item* item = getUserdata<Item>(L, 1);
    if (!item) {
        lua_pushnil(L);
        return 1;
    }
    PropWriteStream propWriteStream;
    item->serializeAttr(propWriteStream);
    size_t attributesSize;
    const char* attributes = propWriteStream.getStream(attributesSize);
    lua_pushlstring(L, attributes, attributesSize);
    return 1;
}

int LuaScriptInterface::luaItemUnserializeAttributes(lua_State* L)
{
    // item:unserializeAttributes(propStream)
    Item* item = getUserdata<Item>(L, 1);
    if (!item) {
        lua_pushnil(L);
        return 1;
    }
    size_t attrSize;
    const char* attr = lua_tolstring(L, 2, &attrSize);
    PropStream propStream;
    propStream.init(attr, attrSize);

    item->unserializeAttr(propStream);
    return 1;
}

Now it's up to you, whatever you're going to build it just be careful on how you save the tiles cause it can and will duplicate items if done wrong. (Saving tiles and then having a rollback wont rollback the saved tiles.)

Example Talkaction:
Code:
function onSay(player, words, param)
    local tile = Tile(player:getPosition())
    if param == "save" then
        tile:save()
    elseif param == "load" then
        loadSavedTiles()
    elseif param == "unsave" then
        tile:removeSave()
    else
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You can either save the tile or load all tiles")
    end
    return false
end

You can also add loadSavedTiles() to startup.lua in "globalevents\scripts" to load all tiles when opening the server.

  • !command save: Save the current tile from the player position
  • !command load: Loads ALL tiles saved in the database.
  • !command unsave: Removes the current tile information from the database.
How loadSavedTiles function works:
You can use it either without any parameter and it will reload all the tiles saved.
If you use only 1 parameter it will reload only that position.
If you use 2 parameters it will reload the tiles inside that area. The first position is the NW corner pos and the second is the SE corner pos. It supports multi level you just have to remember that the NW must be the lower corner and the SE the higher one.

All the others are simple and self explanatory.
 
Last edited:
This i so good, was about to write something similiar when I saw this.
Thanks once again :)
 
I don't like it: luaItemSerializeAttributes
Why not just write the required properties into database directly there is only few of them what actually needed.

What is that for? db.asyncStoreQuery(storeQ, function)
 
I don't like it: luaItemSerializeAttributes
Why not just write the required properties into database directly there is only few of them what actually needed.

What is that for? db.asyncStoreQuery(storeQ, function)
For the callback I guess.
AsyncBlocks.png

(Source: http://codeblog.jonskeet.uk/2011/05/08/eduasync-part-1-introduction/)
 
I don't like it: luaItemSerializeAttributes
Why not just write the required properties into database directly there is only few of them what actually needed.

What is that for? db.asyncStoreQuery(storeQ, function)
There is not only just a few:

https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L668-L774
https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L388-L661

The db.asyncStoreQuery is used to load the tiles without freezing the code. For example:

If you use:
loadSavedTiles()
print("Hello")

It wont wait for the loadSavedTiles to end loading to print Hello.
 
There is not only just a few:

https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L668-L774
https://github.com/otland/forgottenserver/blob/master/src/item.cpp#L388-L661

The db.asyncStoreQuery is used to load the tiles without freezing the code. For example:

If you use:
loadSavedTiles()
print("Hello")

It wont wait for the loadSavedTiles to end loading to print Hello.
woah :eek:? rly?
Didn't know that can happen. though it will wait for return value.


But about items. Lets be honest here, You don't need to know is item moveable or not.
Because its defined already before on TFS
when you create the item, it adds these attributes on creating.
 
woah :eek:? rly?
Didn't know that can happen. though it will wait for return value.


But about items. Lets be honest here, You don't need to know is item moveable or not.
Because its defined already before on TFS
when you create the item, it adds these attributes on creating.
Those attributes wont be saved, only if they are modified attributes.
 
Those attributes wont be saved, only if they are modified attributes.
well perhaps someone still needs that serializer. But I don't see the use for it.
If there is anything custom with the item, most likely I have unique or action ID on it. And that is something I would store to database directly.
 
Hello! i know its been a while since you post this. But how i can change the talkaction to save a group of tiles or all the tiles in game ?
i was thinking like a "from position to position" and take a part of the map. But im not really a good scripter.
 
Back
Top