Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Highscores #4152

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/events/events.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<event class="Player" method="onGainSkillTries" enabled="1" />
<event class="Player" method="onWrapItem" enabled="1" />
<event class="Player" method="onInventoryUpdate" enabled="1" />
<event class="Player" method="onRequestHighscores" enabled="1" />

<!-- Monster methods -->
<event class="Monster" method="onDropLoot" enabled="1" />
Expand Down
12 changes: 12 additions & 0 deletions data/events/scripts/player.lua
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,15 @@ function Player:onInventoryUpdate(item, slot, equip)
EventCallback.onInventoryUpdate(self, item, slot, equip)
end
end

function Player:onRequestHighscores(params)
local cacheKey = getHighscoresCacheKey(params)
if highscoresCache[cacheKey] ~= nil and ((os.time() - highscoresTimestamps[cacheKey]) < highscoresCacheTime) then
return highscoresCache[cacheKey], highscoresTimestamps[cacheKey]
end

highscoresCache[cacheKey] = getHighscores(params)
highscoresTimestamps[cacheKey] = os.time()

return highscoresCache[cacheKey], highscoresTimestamps[cacheKey]
end
4 changes: 4 additions & 0 deletions data/lib/core/achievements.lua
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,8 @@ function Player.addAchievement(self, ach, hideMsg)

if not self:hasAchievement(achievement.id) then
self:setStorageValue(PlayerStorageKeys.achievementsBase + achievement.id, 1)
local value = self:getStorageValue(PlayerStorageKeys.achievementPoints)
self:setStorageValue(PlayerStorageKeys.achievementPoints, value + 1)
if not hideMsg then
self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You earned the achievement \"" .. achievement.name .. "\".")
end
Expand All @@ -723,6 +725,8 @@ function Player.removeAchievement(self, ach)

if self:hasAchievement(achievement.id) then
self:setStorageValue(PlayerStorageKeys.achievementsBase + achievement.id, -1)
local value = self:getStorageValue(PlayerStorageKeys.achievementPoints)
self:setStorageValue(PlayerStorageKeys.achievementPoints, value - 1)
end
return true
end
Expand Down
1 change: 1 addition & 0 deletions data/lib/core/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dofile('data/lib/core/constants.lua')
dofile('data/lib/core/container.lua')
dofile('data/lib/core/creature.lua')
dofile('data/lib/core/game.lua')
dofile('data/lib/core/highscores.lua')
dofile('data/lib/core/item.lua')
dofile('data/lib/core/itemtype.lua')
dofile('data/lib/core/party.lua')
Expand Down
138 changes: 138 additions & 0 deletions data/lib/core/highscores.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
highscoresCache = {}
highscoresTimestamps = {}
highscoresMaxResults = 100
highscoresCacheTime = 30 * 60 -- 30 minutes
highscoresExcludedGroups = {4, 5, 6}

-- VocationId : VocationClientId
local vocationIdsToClientIds = {
[0] = 0,
[1] = 3,
[2] = 4,
[3] = 2,
[4] = 1,
[5] = 13,
[6] = 14,
[7] = 12,
[8] = 11
}

highscoresQueries = {
[HIGHSCORES_CATEGORY_LEVEL] = [[
SELECT `id`, `name`, `vocation`, `level`, `experience` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_MAGLEVEL] = [[
SELECT `id`, `name`, `vocation`, `level`, `maglevel` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `manaspent` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_FIST_FIGHTING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_fist` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_fist_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_AXE_FIGHTING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_axe` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_axe_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_CLUB_FIGHTING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_club` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_club_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_SWORD_FIGHTING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_sword` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_sword_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_DISTANCE_FIGHTING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_dist` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_dist_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_SHIELDING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_shielding` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_shielding_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_FISHING] = [[
SELECT `id`, `name`, `vocation`, `level`, `skill_fishing` AS `points` FROM `players`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `skill_fishing_tries` DESC LIMIT %d, %d
]],
[HIGHSCORES_CATEGORY_ACHIEVEMENTS] = [[
SELECT `id`, `name`, `vocation`, `level`, `value` AS `points` FROM `player_storage`
LEFT JOIN `players` ON `players`.`id` = `player_storage`.`player_id`
WHERE `deletion` = 0 %s
AND `player_storage`.`key` = ]] .. PlayerStorageKeys.achievementPoints .. [[
AND `group_id` NOT IN (]] .. table.concat(highscoresExcludedGroups, ", ") .. [[)
ORDER BY `points` DESC, `name` ASC LIMIT %d, %d
]]
-- CUSTOM HIGHSCORE
--[[,
[HIGHSCORES_CATEGORY_DEATHS] = [[
SELECT DISTINCT(`id`) `id`, `name`, `vocation`, `players`.`level`,
(SELECT COUNT(*) FROM `player_deaths` WHERE `player_deaths`.`player_id` = `players`.`id`) AS `points`
FROM `players`
LEFT JOIN `player_deaths` ON `player_deaths`.`player_id` = `players`.`id`
WHERE `deletion` = 0 %s
AND `group_id` NOT IN (4, 5, 6)
ORDER BY `points` DESC, `name` ASC LIMIT %d, %d
]]
--]]
}

function getHighscores(params)
if not highscoresQueries[params.category] then
return {}, os.time()
end

local from = (params.page - 1) * 20
local to = params.page * 20

local vocWhere = ""
if params.vocation ~= 0xFFFFFFFF then
local ids = Vocation(params.vocation):getPromotions() or {VOCATION_NONE}
vocWhere = "AND `vocation` IN (" .. table.concat(ids, ", ") .. ")"
end

local entries = {}
local rank = from + 1
local resultId = db.storeQuery(string.format(highscoresQueries[params.category], vocWhere, from, to))
if resultId then
repeat
table.insert(entries, {
id = result.getNumber(resultId, "id"),
rank = rank,
name = result.getString(resultId, "name"),
title = "",
vocation = vocationIdsToClientIds[result.getNumber(resultId, "vocation")],
world = configManager.getString(configKeys.SERVER_NAME),
level = result.getNumber(resultId, "level"),
points = result.getNumber(resultId, "points")
})
rank = rank + 1
until not result.next(resultId)
result.free(resultId)
end
return entries
end

function getHighscoresCacheKey(params)
if (params.world == "OWN" or params.world == "") then
params.world = configManager.getString(configKeys.SERVER_NAME)
end
return string.format("%d%s%d%d", params.category, params.world, params.vocation, params.page)
end
1 change: 1 addition & 0 deletions data/lib/core/storages.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ PlayerStorageKeys = {

achievementsBase = 300000,
achievementsCounter = 20000,
achievementPoints = 19999
}

GlobalStorageKeys = {
Expand Down
12 changes: 12 additions & 0 deletions data/lib/core/vocation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ function Vocation.getBase(self)
end
return base
end

function Vocation.getPromotions(self)
local ids = {}
local base = self:getBase()

table.insert(ids, base:getId())
while base:getPromotion() do
base = base:getPromotion()
table.insert(ids, base:getId())
end
return ids
end
4 changes: 2 additions & 2 deletions src/definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ static constexpr auto STATUS_SERVER_VERSION = "1.5";
static constexpr auto STATUS_SERVER_DEVELOPERS = "The Forgotten Server Team";

static constexpr auto CLIENT_VERSION_MIN = 1280;
static constexpr auto CLIENT_VERSION_MAX = 1286;
static constexpr auto CLIENT_VERSION_STR = "12.86";
static constexpr auto CLIENT_VERSION_MAX = 1287;
static constexpr auto CLIENT_VERSION_STR = "12.87";

static constexpr auto AUTHENTICATOR_DIGITS = 6U;
static constexpr auto AUTHENTICATOR_PERIOD = 30U;
Expand Down
58 changes: 58 additions & 0 deletions src/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ bool Events::load()
info.playerOnWrapItem = event;
} else if (methodName == "onInventoryUpdate") {
info.playerOnInventoryUpdate = event;
} else if (methodName == "onRequestHighscores") {
info.playerOnRequestHighscores = event;
} else {
std::cout << "[Warning - Events::load] Unknown player method: " << methodName << std::endl;
}
Expand Down Expand Up @@ -1117,6 +1119,62 @@ void Events::eventPlayerOnInventoryUpdate(Player* player, Item* item, slots_t sl
scriptInterface.callVoidFunction(4);
}

void Events::eventPlayerOnRequestHighscores(Player* player, std::vector<HighscoresEntry>& entries,
HighscoresParams& params)
{
// Player:onRequestHighscores(params)
if (info.playerOnRequestHighscores == -1) {
return;
}

if (!scriptInterface.reserveScriptEnv()) {
std::cout << "[Error - Events::eventPlayerOnRequestHighscores] Call stack overflow" << std::endl;
return;
}

ScriptEnvironment* env = scriptInterface.getScriptEnv();
env->setScriptId(info.playerOnRequestHighscores, &scriptInterface);

lua_State* L = scriptInterface.getLuaState();
scriptInterface.pushFunction(info.playerOnRequestHighscores);

LuaScriptInterface::pushUserdata<Player>(L, player);
LuaScriptInterface::setMetatable(L, -1, "Player");

lua_createtable(L, 0, 4);
LuaScriptInterface::setField(L, "world", params.world);
LuaScriptInterface::setField(L, "vocation", params.vocation);
LuaScriptInterface::setField(L, "category", params.category);
LuaScriptInterface::setField(L, "page", params.page);

if (scriptInterface.protectedCall(L, 2, 2) != 0) {
LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L));
} else {

lua_pushnil(L);
while (lua_next(L, -3) != 0) {
const auto tableIndex = lua_gettop(L);
uint32_t id = LuaScriptInterface::getField<uint32_t>(L, tableIndex, "id");
uint32_t rank = LuaScriptInterface::getField<uint32_t>(L, tableIndex, "rank");
std::string name = LuaScriptInterface::getFieldString(L, tableIndex, "name");
std::string title = LuaScriptInterface::getFieldString(L, tableIndex, "title");
uint8_t vocation = LuaScriptInterface::getField<uint8_t>(L, tableIndex, "vocation");
std::string world = LuaScriptInterface::getFieldString(L, tableIndex, "world");
uint16_t level = LuaScriptInterface::getField<uint16_t>(L, tableIndex, "level");
uint64_t points = LuaScriptInterface::getField<uint64_t>(L, tableIndex, "points");

entries.emplace_back(id, rank, name, title, vocation, world, level, points);
lua_pop(L, 9);
}

params.totalPages = entries.size() < 20 ? params.page : params.totalPages;
params.timestamp = LuaScriptInterface::getNumber<uint64_t>(L, -1);
lua_pop(L, 2);
}

scriptInterface.resetScriptEnv();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you can do the network messaging in Lua and avoid messing with the table here. Something in #4043 might be helpful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean sending the HighscoresWindow part or handling the packets received from the client?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean Player:onRequestHighscores should return nothing, and should create a NetworkMessage and fill it in Lua instead of returning a table and filling the network message in C++


void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse)
{
// Monster:onDropLoot(corpse)
Expand Down
4 changes: 4 additions & 0 deletions src/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "const.h"
#include "creature.h"
#include "highscores.h"
#include "luascript.h"

class ItemType;
Expand Down Expand Up @@ -56,6 +57,7 @@ class Events
int32_t playerOnGainSkillTries = -1;
int32_t playerOnWrapItem = -1;
int32_t playerOnInventoryUpdate = -1;
int32_t playerOnRequestHighscores = -1;

// Monster
int32_t monsterOnDropLoot = -1;
Expand Down Expand Up @@ -109,6 +111,8 @@ class Events
void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries);
void eventPlayerOnWrapItem(Player* player, Item* item);
void eventPlayerOnInventoryUpdate(Player* player, Item* item, slots_t slot, bool equip);
void eventPlayerOnRequestHighscores(Player* player, std::vector<HighscoresEntry>& entries,
HighscoresParams& params);

// Monster
void eventMonsterOnDropLoot(Monster* monster, Container* corpse);
Expand Down
30 changes: 30 additions & 0 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3516,6 +3516,36 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit)
}
}

void Game::playerShowHighscores(uint32_t playerId, uint8_t category, uint32_t vocation, const std::string& world,
uint16_t page)
{
Player* player = getPlayerByID(playerId);
if (!player) {
return;
}

std::vector<HighscoresEntry> entries;
HighscoresParams params;
params.category = category;
params.vocation = vocation;
params.world = world;
params.type = 0x00;
params.battlEye = 0xFF;

uint32_t totalPlayers = IOLoginData::getTotalExistingPlayers();
uint16_t totalPages = totalPlayers / 20;
if ((totalPlayers % 20) > 0) {
totalPages++;
}

params.page = page > totalPages ? 1 : page;
params.totalPages = totalPages;

g_events->eventPlayerOnRequestHighscores(player, entries, params);

player->sendHighscores(entries, params);
}

void Game::playerShowQuestLog(uint32_t playerId)
{
Player* player = getPlayerByID(playerId);
Expand Down
2 changes: 2 additions & 0 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ class Game
const uint16_t spriteId);
void playerEditPodium(uint32_t playerId, Outfit_t outfit, const Position& position, uint8_t stackPos,
const uint16_t spriteId, bool podiumVisible, Direction direction);
void playerShowHighscores(uint32_t playerId, uint8_t category, uint32_t vocation, const std::string& world,
uint16_t page);
void playerShowQuestLog(uint32_t playerId);
void playerShowQuestLine(uint32_t playerId, uint16_t questId);
void playerResetQuestTracker(uint32_t playerId, const std::vector<uint16_t>& missionIds);
Expand Down
Loading