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()