• 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.4+] Hero Statues

Trollheim

Well-Known Member
Joined
May 27, 2022
Messages
65
Reaction score
88
Hello.
I've made script to show Highscore players in game, not only on the site. It looks like this:
1669809323682.png

And I decided to share it here, because I want to give back something to the community I've been in silently for many years.
Script can show highscore of level, magic level, skills and vocations. You have to put some statue item on the map, that can display text.
1669809638263.png

Then in config area you need to paste position of the statue and what highscore should be presented on it.



Lua:
---------- CONFIG ------------
local howManyPlayers = 10
local statues = {
    { position = Position(100, 100, 7), skill = "level" },
    { position = Position(100, 101, 7), skill = "magic" },
    { position = Position(100, 102, 7), skill = "axe" },
    { position = Position(100, 103, 7), skill = "sword" },
    { position = Position(100, 104, 7), skill = "club" },
    { position = Position(100, 105, 7), skill = "shielding" },
    { position = Position(100, 106, 7), skill = "dist" },
    { position = Position(100, 107, 7), skill = "fist" },
    { position = Position(100, 108, 7), skill = "sorcerer" },
    { position = Position(100, 109, 7), skill = "druid" },
    { position = Position(100, 110, 7), skill = "knight" },
    { position = Position(100, 111, 7), skill = "paladin" },
    { position = Position(100, 112, 7), skill = "rook" }
}
------------------------------

local numberOfPlayersInDB = db.storeQuery("SELECT COUNT(*) AS `count` FROM `players` LIMIT " .. howManyPlayers)
local tmpHowManyPlayers = result.getDataString(numberOfPlayersInDB, "count")
result.free(numberOfPlayersInDB)
howManyPlayers = math.min(howManyPlayers,tmpHowManyPlayers)

local function getPlayersByLevelText()
    local topPlayerQuery = db.storeQuery("SELECT * FROM `players` ORDER BY `experience` DESC LIMIT " .. howManyPlayers)
    local text = "Top " .. howManyPlayers .. " players by Level:"
    for i = 1, howManyPlayers do
        local topPlayerName = result.getDataString(topPlayerQuery, "name")
        local topPlayerLevel = result.getDataString(topPlayerQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]"
        result.next(topPlayerQuery)
    end   
    result.free(topPlayerQuery)
    return text
end
local function getPlayersByMagicText()
    local topPlayerQuery = db.storeQuery("SELECT * FROM `players` ORDER BY `manaspent` DESC LIMIT " .. howManyPlayers)
    local text = "Top " .. howManyPlayers .. " players by Magic Level:"
    for i = 1, howManyPlayers do
        local topPlayerName = result.getDataString(topPlayerQuery, "name")
        local topPlayerMagic = result.getDataString(topPlayerQuery, "maglevel")
        local topPlayerLevel = result.getDataString(topPlayerQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]: " .. topPlayerMagic
        result.next(topPlayerQuery)
    end   
    result.free(topPlayerQuery)
    return text
end
local function getPlayersBySkillText(skillName)
    local topPlayerQuery = db.storeQuery("SELECT * FROM `players` ORDER BY `skill_" .. skillName .."` DESC, `skill_" .. skillName .. "_tries` DESC LIMIT " .. howManyPlayers)
    local uppercaseSkillName = skillName:gsub("^%l", string.upper)
    if uppercaseSkillName == "Dist" then
        uppercaseSkillName = "Distance"
    end
    if uppercaseSkillName ~= "Shielding" then
        uppercaseSkillName = uppercaseSkillName .. " fighting"
    end
    local text = "Top " .. howManyPlayers .. " players by " .. uppercaseSkillName .. ":"
    for i = 1, howManyPlayers do
        local topPlayerName = result.getDataString(topPlayerQuery, "name")
        local topPlayerSkill = result.getDataString(topPlayerQuery, "skill_" .. skillName )
        local topPlayerLevel = result.getDataString(topPlayerQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]: " .. topPlayerSkill
        result.next(topPlayerQuery)
    end   
    result.free(topPlayerQuery)
    return text
end
local function getPlayersByVocationText(name)
    local vocationIds = {
        ["rook"] = { 0, 9, "Rook Slayers" },
        ["sorcerer"] = { 1, 5, "Sorcerers" },
        ["druid"] = { 2, 6, "Druids" },
        ["paladin"] = { 3, 7, "Paladins" },
        ["knight"] = { 4, 8, "Knights" }
    }
    local numberOfPlayersInDB = db.storeQuery("SELECT COUNT(*) AS `count` FROM `players` WHERE `vocation` = " .. vocationIds[name][1] .. " or `vocation` = " .. vocationIds[name][2] .. " ORDER BY `experience` DESC LIMIT " .. howManyPlayers)
    local vocationPlayers = result.getDataString(numberOfPlayersInDB, "count")
    result.free(numberOfPlayersInDB)
    vocationPlayers = math.min(vocationPlayers, howManyPlayers)
    local topPlayerQuery = db.storeQuery("SELECT * FROM `players` WHERE `vocation` = " .. vocationIds[name][1] .. " or `vocation` = " .. vocationIds[name][2] .. " ORDER BY `experience` DESC LIMIT " .. vocationPlayers)
    local text = "Top " .. vocationPlayers .. " " .. vocationIds[name][3] ..  " by Level:"
    for i = 1, vocationPlayers do
        local topPlayerName = result.getDataString(topPlayerQuery, "name")
        local topPlayerLevel = result.getDataString(topPlayerQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]"
        result.next(topPlayerQuery)
    end   
    result.free(topPlayerQuery)
    return text
end

local globalevent = GlobalEvent("HeroStatues")
function globalevent.onStartup()
    local texts = {
        ["sorcerer"] = getPlayersByVocationText("sorcerer"),
        ["druid"] = getPlayersByVocationText("druid"),
        ["paladin"] = getPlayersByVocationText("paladin"),
        ["knight"] = getPlayersByVocationText("knight"),
        ["rook"] = getPlayersByVocationText("rook"),
        ["level"] = getPlayersByLevelText(),
        ["magic"] = getPlayersByMagicText(),
        ["fist"] = getPlayersBySkillText("fist"),
        ["sword"] = getPlayersBySkillText("sword"),
        ["axe"] = getPlayersBySkillText("axe"),
        ["club"] = getPlayersBySkillText("club"),
        ["dist"] = getPlayersBySkillText("dist"),
        ["shielding"] = getPlayersBySkillText("shielding"),
    }
    
    for k,v in pairs(statues) do
        local statue = Tile(v.position):getTopTopItem()
        statue:setAttribute(ITEM_ATTRIBUTE_TEXT, texts[v.skill])
    end
    return true
end
globalevent:register()
It could be made, to read from map editor which highscore to present, but I wanted to have control of how many statues of different highscore I have on the map.

Let me tell you what you think, and give me any hints what could be done better :)
 
This system looks really good! But it doesn't update the ranks unless you start the server again (i mean, the statues are updated only onStartUp). How could I update the ranks more often, and how does that affect in server performance? (considering is a globalevent)
 
This system looks really good! But it doesn't update the ranks unless you start the server again (i mean, the statues are updated only onStartUp). How could I update the ranks more often, and how does that affect in server performance? (considering is a globalevent)
The purpous of this script was to give players statue for the whole day. If you want to check current highscore you can visit website :)
But if you like you can add this to the end of the script:

Lua:
local updateStatue = GlobalEvent("UpdateHeroStatues")
function updateStatue.onThink(interval)
    local texts = {
        ["sorcerer"] = getPlayersByVocationText("sorcerer"),
        ["druid"] = getPlayersByVocationText("druid"),
        ["paladin"] = getPlayersByVocationText("paladin"),
        ["knight"] = getPlayersByVocationText("knight"),
        ["rook"] = getPlayersByVocationText("rook"),
        ["level"] = getPlayersByLevelText(),
        ["magic"] = getPlayersByMagicText(),
        ["fist"] = getPlayersBySkillText("fist"),
        ["sword"] = getPlayersBySkillText("sword"),
        ["axe"] = getPlayersBySkillText("axe"),
        ["club"] = getPlayersBySkillText("club"),
        ["dist"] = getPlayersBySkillText("dist"),
        ["shielding"] = getPlayersBySkillText("shielding"),
    }
    
    for k,v in pairs(statues) do
        local statue = Tile(v.position):getTopTopItem()
        statue:setAttribute(ITEM_ATTRIBUTE_TEXT, texts[v.skill])
    end
    return true
end
updateStatue:interval(10 * 60000)
updateStatue:register()

It should update statuesevery 10 minutes. It was not tested.
You could also change onLook event to always get actual highscores but it might be abused by players to lag server.
I would not recommend to update statues faster then 1 minute.
 
The purpous of this script was to give players statue for the whole day. If you want to check current highscore you can visit website :)
But if you like you can add this to the end of the script:

Lua:
local updateStatue = GlobalEvent("UpdateHeroStatues")
function updateStatue.onThink(interval)
    local texts = {
        ["sorcerer"] = getPlayersByVocationText("sorcerer"),
        ["druid"] = getPlayersByVocationText("druid"),
        ["paladin"] = getPlayersByVocationText("paladin"),
        ["knight"] = getPlayersByVocationText("knight"),
        ["rook"] = getPlayersByVocationText("rook"),
        ["level"] = getPlayersByLevelText(),
        ["magic"] = getPlayersByMagicText(),
        ["fist"] = getPlayersBySkillText("fist"),
        ["sword"] = getPlayersBySkillText("sword"),
        ["axe"] = getPlayersBySkillText("axe"),
        ["club"] = getPlayersBySkillText("club"),
        ["dist"] = getPlayersBySkillText("dist"),
        ["shielding"] = getPlayersBySkillText("shielding"),
    }
   
    for k,v in pairs(statues) do
        local statue = Tile(v.position):getTopTopItem()
        statue:setAttribute(ITEM_ATTRIBUTE_TEXT, texts[v.skill])
    end
    return true
end
updateStatue:interval(10 * 60000)
updateStatue:register()

It should update statuesevery 10 minutes. It was not tested.
You could also change onLook event to always get actual highscores but it might be abused by players to lag server.
I would not recommend to update statues faster then 1 minute.
Tested this and it works like a charm, the only struggle I have is finding statues that the script work with, caus for some reason it doesn't want to work with just any statue.
But the script works like a charm, a very nice addition to any server!
 
Tested this and it works like a charm, the only struggle I have is finding statues that the script work with, caus for some reason it doesn't want to work with just any statue.
But the script works like a charm, a very nice addition to any server!
Just in items.xml edit statue to read text.


<attribute key="allowDistRead" value="true" />
 
Just in items.xml edit statue to read text.


<attribute key="allowDistRead" value="true" />
Oooh you're right, that works!

Was a bit confusing because the hero statue didn't have that attribute in items.xml either when I checked, but it does however already have a "writable option" in rme so that must be the reason why.

Thanks for clarifying for me, and it's good to know I was on the right path just stupid enough not to realize the simple fix haha.

Thanks for hooking me up with variation so I don't just have to use one for them all <3
 
Oooh you're right, that works!

Was a bit confusing because the hero statue didn't have that attribute in items.xml either when I checked, but it does however already have a "writable option" in rme so that must be the reason why.

Thanks for clarifying for me, and it's good to know I was on the right path just stupid enough not to realize the simple fix haha.

Thanks for hooking me up with variation so I don't just have to use one for them all <3
No problem, i was on the same error too
 
Lua:
---------- CONFIG ------------
local howManyPlayers = 10
local statues = {
    { position = Position(32383, 32212, 7), skill = "level" }, -- {x = 32383, y = 32212, z = 7} THAIS
    { position = Position(100, 101, 7), skill = "magic" },
    { position = Position(100, 102, 7), skill = "axe" },
    { position = Position(100, 103, 7), skill = "sword" },
    { position = Position(100, 104, 7), skill = "club" },
    { position = Position(100, 105, 7), skill = "shielding" },
    { position = Position(100, 106, 7), skill = "dist" },
    { position = Position(100, 107, 7), skill = "fist" },
    { position = Position(100, 108, 7), skill = "sorcerer" },
    { position = Position(100, 109, 7), skill = "druid" },
    { position = Position(100, 110, 7), skill = "knight" },
    { position = Position(100, 111, 7), skill = "paladin" },
    { position = Position(32094, 32201, 7), skill = "rook" } -- {x = 32094, y = 32201, z = 7}
}
------------------------------

local function getPlayersByLevelText()
    local filteredQuery = db.storeQuery("SELECT * FROM `players` WHERE `level` < 1000 AND `group_id` NOT IN (3, 4) ORDER BY `experience` DESC LIMIT " .. howManyPlayers)
    local text = "Top " .. howManyPlayers .. " players by Level:"
    for i = 1, howManyPlayers do
        local topPlayerName = result.getDataString(filteredQuery, "name")
        local topPlayerLevel = result.getDataString(filteredQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]"
        result.next(filteredQuery)
    end   
    result.free(filteredQuery)
    return text
end

local function getPlayersByVocationText(name)
    local vocationIds = {
        ["rook"] = { 0, 9, "Rook Slayers" },
        ["sorcerer"] = { 1, 5, "Sorcerers" },
        ["druid"] = { 2, 6, "Druids" },
        ["paladin"] = { 3, 7, "Paladins" },
        ["knight"] = { 4, 8, "Knights" }
    }
    local filteredQuery = db.storeQuery("SELECT COUNT(*) AS `count` FROM `players` WHERE (`vocation` = " .. vocationIds[name][1] .. " or `vocation` = " .. vocationIds[name][2] .. ") AND `level` < 1000 AND `group_id` NOT IN (3, 4) ORDER BY `experience` DESC LIMIT 1")
    local vocationPlayers = result.getDataInt(filteredQuery, "count")
    result.free(filteredQuery)
    vocationPlayers = math.min(vocationPlayers, howManyPlayers)
    local text = "Top " .. vocationPlayers .. " " .. vocationIds[name][3] ..  " by Level:"
    local topPlayerQuery = db.storeQuery("SELECT * FROM `players` WHERE (`vocation` = " .. vocationIds[name][1] .. " or `vocation` = " .. vocationIds[name][2] .. ") AND `level` < 1000 AND `group_id` NOT IN (3, 4) ORDER BY `experience` DESC LIMIT " .. vocationPlayers)
    for i = 1, vocationPlayers do
        local topPlayerName = result.getDataString(topPlayerQuery, "name")
        local topPlayerLevel = result.getDataString(topPlayerQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]"
        result.next(topPlayerQuery)
    end   
    result.free(topPlayerQuery)
    return text
end

local globalevent = GlobalEvent("HeroStatues")
function globalevent.onStartup()
    local texts = {
        ["sorcerer"] = getPlayersByVocationText("sorcerer"),
        ["druid"] = getPlayersByVocationText("druid"),
        ["paladin"] = getPlayersByVocationText("paladin"),
        ["knight"] = getPlayersByVocationText("knight"),
        ["rook"] = getPlayersByVocationText("rook"),
        ["level"] = getPlayersByLevelText(),
        -- Adicione outras categorias conforme necessário
    }
    
    for k,v in pairs(statues) do
        local tile = Tile(v.position)
        if tile then
            local statue = tile:getTopTopItem()
            if statue then
                statue:setAttribute(ITEM_ATTRIBUTE_TEXT, texts[v.skill])
            else
                print("Erro: Estátua não encontrada na posição: " .. v.position.x .. ", " .. v.position.y .. ", " .. v.position.z)
            end
        else
            print("Erro: Tile não encontrado na posição: " .. v.position.x .. ", " .. v.position.y .. ", " .. v.position.z)
        end
    end
end

globalevent:register()


Introduced a condition in the queries to filter out players with a group_id of 3 or 4, assuming these represent GMs and Gods. This ensures they are excluded from the ranking.
 
Lua:
---------- CONFIG ------------
local howManyPlayers = 10
local statues = {
    { position = Position(32383, 32212, 7), skill = "level" }, -- {x = 32383, y = 32212, z = 7} THAIS
    { position = Position(100, 101, 7), skill = "magic" },
    { position = Position(100, 102, 7), skill = "axe" },
    { position = Position(100, 103, 7), skill = "sword" },
    { position = Position(100, 104, 7), skill = "club" },
    { position = Position(100, 105, 7), skill = "shielding" },
    { position = Position(100, 106, 7), skill = "dist" },
    { position = Position(100, 107, 7), skill = "fist" },
    { position = Position(100, 108, 7), skill = "sorcerer" },
    { position = Position(100, 109, 7), skill = "druid" },
    { position = Position(100, 110, 7), skill = "knight" },
    { position = Position(100, 111, 7), skill = "paladin" },
    { position = Position(32094, 32201, 7), skill = "rook" } -- {x = 32094, y = 32201, z = 7}
}
------------------------------

local function getPlayersByLevelText()
    local filteredQuery = db.storeQuery("SELECT * FROM `players` WHERE `level` < 1000 AND `group_id` NOT IN (3, 4) ORDER BY `experience` DESC LIMIT " .. howManyPlayers)
    local text = "Top " .. howManyPlayers .. " players by Level:"
    for i = 1, howManyPlayers do
        local topPlayerName = result.getDataString(filteredQuery, "name")
        local topPlayerLevel = result.getDataString(filteredQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]"
        result.next(filteredQuery)
    end  
    result.free(filteredQuery)
    return text
end

local function getPlayersByVocationText(name)
    local vocationIds = {
        ["rook"] = { 0, 9, "Rook Slayers" },
        ["sorcerer"] = { 1, 5, "Sorcerers" },
        ["druid"] = { 2, 6, "Druids" },
        ["paladin"] = { 3, 7, "Paladins" },
        ["knight"] = { 4, 8, "Knights" }
    }
    local filteredQuery = db.storeQuery("SELECT COUNT(*) AS `count` FROM `players` WHERE (`vocation` = " .. vocationIds[name][1] .. " or `vocation` = " .. vocationIds[name][2] .. ") AND `level` < 1000 AND `group_id` NOT IN (3, 4) ORDER BY `experience` DESC LIMIT 1")
    local vocationPlayers = result.getDataInt(filteredQuery, "count")
    result.free(filteredQuery)
    vocationPlayers = math.min(vocationPlayers, howManyPlayers)
    local text = "Top " .. vocationPlayers .. " " .. vocationIds[name][3] ..  " by Level:"
    local topPlayerQuery = db.storeQuery("SELECT * FROM `players` WHERE (`vocation` = " .. vocationIds[name][1] .. " or `vocation` = " .. vocationIds[name][2] .. ") AND `level` < 1000 AND `group_id` NOT IN (3, 4) ORDER BY `experience` DESC LIMIT " .. vocationPlayers)
    for i = 1, vocationPlayers do
        local topPlayerName = result.getDataString(topPlayerQuery, "name")
        local topPlayerLevel = result.getDataString(topPlayerQuery, "level")
        text = text .. "\n" .. i .. ". " .. topPlayerName .. " [" .. topPlayerLevel .. "]"
        result.next(topPlayerQuery)
    end  
    result.free(topPlayerQuery)
    return text
end

local globalevent = GlobalEvent("HeroStatues")
function globalevent.onStartup()
    local texts = {
        ["sorcerer"] = getPlayersByVocationText("sorcerer"),
        ["druid"] = getPlayersByVocationText("druid"),
        ["paladin"] = getPlayersByVocationText("paladin"),
        ["knight"] = getPlayersByVocationText("knight"),
        ["rook"] = getPlayersByVocationText("rook"),
        ["level"] = getPlayersByLevelText(),
        -- Adicione outras categorias conforme necessário
    }
   
    for k,v in pairs(statues) do
        local tile = Tile(v.position)
        if tile then
            local statue = tile:getTopTopItem()
            if statue then
                statue:setAttribute(ITEM_ATTRIBUTE_TEXT, texts[v.skill])
            else
                print("Erro: Estátua não encontrada na posição: " .. v.position.x .. ", " .. v.position.y .. ", " .. v.position.z)
            end
        else
            print("Erro: Tile não encontrado na posição: " .. v.position.x .. ", " .. v.position.y .. ", " .. v.position.z)
        end
    end
end

globalevent:register()


Introduced a condition in the queries to filter out players with a group_id of 3 or 4, assuming these represent GMs and Gods. This ensures they are excluded from the ranking.
Most servers use group ID 1 for player and 2 for tutor, so it makes more sense to just check if group ID < 3, instead of using the IN keyword.
 
Back
Top