• 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!
  • New resources must be posted under Resources tab. A discussion thread will be created automatically, you can't open threads manually anymore.
[OTCv8] In-Game Highscores Window [TFS 1.x]

[OTCv8] In-Game Highscores Window [TFS 1.x] 2026-05-23

No permission to download

USA Tibia

Member
Joined
Jun 4, 2020
Messages
3
Reaction score
12
USA Tibia submitted a new resource:

[OTCv8] In-Game Highscores Window [TFS 1.x] - A compact in-game highscores module for OTCv8

Features

  • In-game highscores popup using extended opcode 73
  • Level category first, with no rebirth/reset-specific category
  • Categories: Level, Sword, Axe, Club, Distance, Shielding, Magic Level, Fishing
  • Search box for filtering visible names on the current page
  • Page navigation
  • Highlighted own character row
  • Rank coloring: #1 gold, #2 white, #3 bronze, #4+ light gray
  • Includes a fixed 20x40 topbutton icon strip for normal/open toggle states...

Read more about this resource...
 
I'm working on my server based on Digimon at this worked like a charm with some modifications, thank you!
1780203413190.webp
 
I'm working on my server based on Digimon at this worked like a charm with some modifications, thank you!I
I’m glad to hear that the things I’m sharing are useful to someone else. I’ll keep uploading more then.

Feel free to send me a PM if you’d like a list of a few other things I’ve made that you could implement on your server.
 
Hello.
:)

In tfs 1.5 ive got the problem. I have got empty highscore window.
founds.webp
Someone can Help ?
of course best regards
 
Hello.
:)

In tfs 1.5 ive got the problem. I have got empty highscore window.

Someone can Help ?
of course best regards
Thanks for the screenshot.

Start by going through INSTALL_TFS1X.md again and double-checking that every step was followed exactly. A small missed step during installation is usually the first thing to rule out.

The important detail is that the window says Page 1 / 2, but the list says No records found. That usually means the module can see that there should be highscore pages, but it is not receiving or showing the actual player rows.

Can you send/check these things?

  1. What exact TFS 1.5 version or GitHub repo are you using?
  2. Are there any errors in the server console when you open the highscore window?
  3. Are there any errors in the client terminal/console?
  4. Did you install both parts of the module?
    • the client/module files
    • the server-side Lua/scripts
  5. Did you restart the server after installing it?
For #1, if you’d rather not share your server details publicly, feel free to send me a PM with the information and I’ll try to help from there.
 
UPDATE
who using tfs 1.5.

Highscore.lua (revscirpt) (Engine)

LUA:
local EXT_OP = 73

local skillColumns = {
    [1] = "skill_sword",
    [2] = "skill_axe",
    [3] = "skill_club",
    [4] = "skill_dist",
    [5] = "skill_shielding",
    [7] = "skill_fishing",
}

local function encodeJson(value)
    if json and json.encode then return json.encode(value) end
    if JSON and JSON.encode then return JSON.encode(value) end
    error("No JSON encoder found. Add a JSON library or expose json.encode.")
end

local function decodeJson(value)
    if json and json.decode then return json.decode(value) end
    if JSON and JSON.decode then return JSON.decode(value) end
    error("No JSON decoder found. Add a JSON library or expose json.decode.")
end

local function getResultNumber(resultId, column)
    return result.getNumber and result.getNumber(resultId, column) or result.getDataInt(resultId, column)
end

local function getResultString(resultId, column)
    return result.getString and result.getString(resultId, column) or result.getDataString(resultId, column)
end

local function freeResult(resultId)
    if result.free then result.free(resultId) else result.freeResult(resultId) end
end

local function nextResult(resultId)
    return result.next(resultId)
end

local function storeQuery(query)
    if db.storeQuery then return db.storeQuery(query) end
    return db.getResult(query)
end

local function sanitizeVocations(vocations)
    local values = {}
    if type(vocations) == "table" then
        for _, vocation in ipairs(vocations) do
            vocation = tonumber(vocation)
            if vocation and vocation >= 0 then
                values[#values + 1] = vocation
            end
        end
    end

    if #values == 0 then
        return nil
    end

    return table.concat(values, ",")
end

local function getScoreColumn(category)
    if category == 0 then return "`experience`" end
    if category == 6 then return "`maglevel`" end

    local skillColumn = skillColumns[category]
    if not skillColumn then return nil end

    return "`" .. skillColumn .. "`"
end

local ec = CreatureEvent("HighscoreExtendedOpcode")

function ec.onExtendedOpcode(player, opcode, buffer)
    if opcode ~= EXT_OP then
        return true
    end

    local status, msg = pcall(decodeJson, buffer)
    if not status or not msg or msg.action ~= "request" then
        return true
    end

    local category = tonumber(msg.category) or 0
    local page = math.max(1, tonumber(msg.page) or 1)
    local entriesPerPage = math.max(1, tonumber(msg.entriesPerPage) or 10)
    local vocationsFilter = sanitizeVocations(msg.vocations)

    local scoreColumn = getScoreColumn(category)
    if not scoreColumn then
        return true
    end

    -- Bierzemy absolutnie wszystkich z bazy (bez filtrów kont/grup), odrzucamy tylko Sample
    local baseWhere = "`p`.`name` NOT LIKE '%Sample%'"
    if vocationsFilter then
        baseWhere = baseWhere .. string.format(" AND `p`.`vocation` IN (%s)", vocationsFilter)
    end

    -- 1. Liczenie rekordów
    local countQuery = string.format("SELECT COUNT(*) FROM `players` AS `p` WHERE %s", baseWhere)
    local totalEntries = 0
    local countResult = storeQuery(countQuery)
    if countResult then
        totalEntries = getResultNumber(countResult, "COUNT(*)")
        freeResult(countResult)
    end

    local totalPages = math.max(1, math.ceil(totalEntries / entriesPerPage))
    if page > totalPages then
        page = totalPages
    end

    local offset = (page - 1) * entriesPerPage

    -- 2. Pobieranie danych (Pobieramy kolumnę skilla jako 'experience' dynamicznie, bo klient z tej zmiennej czyta punkty skilli!)
    local dataQuery = string.format(
        "SELECT `p`.`name`, `p`.`vocation`, `p`.`level`, `p`.`experience` AS `main_exp`, `p`.%s AS `calculated_score` " ..
        "FROM `players` AS `p` " ..
        "WHERE %s " ..
        "ORDER BY `p`.%s DESC, `p`.`name` ASC LIMIT %d, %d",
        scoreColumn, baseWhere, scoreColumn, offset, entriesPerPage
    )

    local dataResult = storeQuery(dataQuery)
    local highscoreData = {}
    local rank = offset + 1

    if dataResult then
        repeat
            -- Dla kategorii 0 (Level) wysyłamy poziom. Dla skilli wysyłamy wartość skilla w polu 'experience' (wymóg klienta)
            local scoreValue = getResultNumber(dataResult, "calculated_score")
            if category == 0 then
                scoreValue = getResultNumber(dataResult, "main_exp")
            end

            local entry = {
                rank = rank,
                name = getResultString(dataResult, "name"),
                vocation = getResultNumber(dataResult, "vocation"),
                level = getResultNumber(dataResult, "level"),
                experience = scoreValue -- To mapowanie naprawia wyświetlanie punktów w OTClient
            }
            table.insert(highscoreData, entry)
            rank = rank + 1
        until not nextResult(dataResult)
        freeResult(dataResult)
    end

    -- 3. Odpowiedź do klienta
    local response = {
        action = "highscore",
        category = category,
        page = page,
        pages = totalPages,
        data = highscoreData
    }

    player:sendExtendedOpcode(EXT_OP, encodeJson(response))
    return true
end
ec:register()

local loginEvent = CreatureEvent("HighscoreLogin")
function loginEvent.onLogin(player)
    player:registerEvent("HighscoreExtendedOpcode")
    return true
end
loginEvent:register()

and highscore.lua (client side)
part of whole script:
Code:
local filterOptions = {
  { label = "All Vocations", filter = {0,1,2,3,4,5,6,7,8,9,10,11,12} }
}

local serverToName = {
  [0] = "None",
  [1] = "Sorcerer", [5] = "Master Sorcerer", [9] = "Masterful Sorcerer",
  [2] = "Druid", [6] = "Elder Druid", [10] = "Ancient Druid",
  [3] = "Paladin", [7] = "Royal Paladin", [11] = "Glorious Paladin",
  [4] = "Knight", [8] = "Elite Knight", [12] = "Rageful Knight",
}
with custom profession

PS:

USA Tibia thanks for reply​

 
Back
Top