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

feat: cyclopedia improvements #2629

Merged
merged 29 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
684203d
cyclopedia: wip.
elsongabriel Apr 9, 2024
a511099
moved Recent Kills and Death History functions from 'protocolgame.cpp…
elsongabriel May 13, 2024
35e1a6b
cyclopedia: wip.
elsongabriel May 13, 2024
01330b0
on_look.lua: add player id and fixes.
elsongabriel May 22, 2024
1fa9170
cyclopedia: improvements.
elsongabriel May 22, 2024
062d4d7
wip bless counting and improvements.
elsongabriel May 24, 2024
f79056b
cyclopedia: finish store summary.
elsongabriel May 24, 2024
c0c8514
player_achievement & player_title: improvements on checks.
elsongabriel May 25, 2024
216f870
fixed move Recent Kills and Death History functions from 'protocolgam…
elsongabriel May 27, 2024
628eca8
fix Recent Kills to 70 kills history.
elsongabriel May 27, 2024
be5dfc8
Added marriage description into char inspection.
elsongabriel May 27, 2024
b76dee0
fix on_look.
elsongabriel May 28, 2024
004dd92
Merge branch 'main' into feature/cyclopedia
elsongabriel May 30, 2024
90e38e2
Merge branch 'main' into feature/cyclopedia
elsongabriel Jun 12, 2024
0492d52
Code format - (Clang-format)
github-actions[bot] Jun 12, 2024
6bb3d8b
Merge branch 'main' into feature/cyclopedia
elsongabriel Jun 21, 2024
e3b98f7
Merge branch 'main' into feature/cyclopedia
elsongabriel Jun 23, 2024
7aace50
Update src/game/game.cpp
elsongabriel Jun 23, 2024
ae2f0dd
Merge branch 'main' into feature/cyclopedia
elsongabriel Jun 25, 2024
319d62d
Code format - (Clang-format)
github-actions[bot] Jun 25, 2024
0c93647
fix.
elsongabriel Jun 25, 2024
14f6c21
Merge branch 'feature/cyclopedia' of https://github.com/elsongabriel/…
elsongabriel Jun 25, 2024
6375531
improvements on getArticle function.
elsongabriel Jun 26, 2024
9fd0408
Code format - (Clang-format)
github-actions[bot] Jun 26, 2024
b5acd26
improvements on prey description.
elsongabriel Jun 26, 2024
e48c7a2
including missing files on canary.vcxproj.
elsongabriel Jun 26, 2024
ad02bc2
improved function name.
elsongabriel Jun 26, 2024
066edde
Merge branch 'main' into feature/cyclopedia
elsongabriel Jul 2, 2024
35970cd
resolved conversations.
elsongabriel Jul 5, 2024
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
4 changes: 2 additions & 2 deletions data/modules/scripts/blessings/blessings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Blessings.Credits = {

Blessings.Config = {
AdventurerBlessingLevel = configManager.getNumber(configKeys.ADVENTURERSBLESSING_LEVEL), -- Free full bless until level
HasToF = false, -- Enables/disables twist of fate
HasToF = not configManager.getBoolean(configKeys.TOGGLE_SERVER_IS_RETRO), -- Enables/disables twist of fate
InquisitonBlessPriceMultiplier = 1.1, -- Bless price multiplied by henricus
SkulledDeathLoseStoreItem = configManager.getBoolean(configKeys.SKULLED_DEATH_LOSE_STORE_ITEM), -- Destroy all items on store when dying with red/blackskull
InventoryGlowOnFiveBless = configManager.getBoolean(configKeys.INVENTORY_GLOW), -- Glow in yellow inventory items when the player has 5 or more bless,
Expand Down Expand Up @@ -142,7 +142,7 @@ Blessings.sendBlessDialog = function(player)
msg:addU16(Blessings.BitWiseTable[v.id])
msg:addByte(player:getBlessingCount(v.id))
if player:getClient().version > 1200 then
msg:addByte(0) -- Store Blessings Count
msg:addByte(player:getBlessingCount(v.id, true)) -- Store Blessings Count
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions data/modules/scripts/gamestore/gamestore.lua
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ GameStore.Categories = {
icons = { "Blood_of_the_Mountain.png" },
name = "Blood of the Mountain",
price = 25,
blessid = 8,
blessid = 7,
count = 1,
id = GameStore.SubActions.BLESSING_BLOOD,
description = "<i>Reduces your character's chance to lose any items as well as the amount of your character's experience and skill loss upon death:</i>\n\n&#8226; 1 blessing = 8.00% less Skill / XP loss, 30% equipment protection\n&#8226; 2 blessing = 16.00% less Skill / XP loss, 55% equipment protection\n&#8226; 3 blessing = 24.00% less Skill / XP loss, 75% equipment protection\n&#8226; 4 blessing = 32.00% less Skill / XP loss, 90% equipment protection\n&#8226; 5 blessing = 40.00% less Skill / XP loss, 100% equipment protection\n&#8226; 6 blessing = 48.00% less Skill / XP loss, 100% equipment protection\n&#8226; 7 blessing = 56.00% less Skill / XP loss, 100% equipment protection\n\n{character} \n{limit|5} \n{info} added directly to the Record of Blessings \n{info} characters with a red or black skull will always lose all equipment upon death",
Expand All @@ -154,7 +154,7 @@ GameStore.Categories = {
icons = { "Heart_of_the_Mountain.png" },
name = "Heart of the Mountain",
price = 25,
blessid = 7,
blessid = 8,
count = 1,
id = GameStore.SubActions.BLESSING_HEART,
description = "<i>Reduces your character's chance to lose any items as well as the amount of your character's experience and skill loss upon death:</i>\n\n&#8226; 1 blessing = 8.00% less Skill / XP loss, 30% equipment protection\n&#8226; 2 blessing = 16.00% less Skill / XP loss, 55% equipment protection\n&#8226; 3 blessing = 24.00% less Skill / XP loss, 75% equipment protection\n&#8226; 4 blessing = 32.00% less Skill / XP loss, 90% equipment protection\n&#8226; 5 blessing = 40.00% less Skill / XP loss, 100% equipment protection\n&#8226; 6 blessing = 48.00% less Skill / XP loss, 100% equipment protection\n&#8226; 7 blessing = 56.00% less Skill / XP loss, 100% equipment protection\n\n{character} \n{limit|5} \n{info} added directly to the Record of Blessings \n{info} characters with a red or black skull will always lose all equipment upon death",
Expand Down
31 changes: 20 additions & 11 deletions data/modules/scripts/gamestore/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ GameStore.SubActions = {
BLESSING_SUNS = 6,
BLESSING_SPIRITUAL = 7,
BLESSING_EMBRACE = 8,
BLESSING_HEART = 9,
BLESSING_BLOOD = 10,
BLESSING_BLOOD = 9,
BLESSING_HEART = 10,
BLESSING_ALL_PVE = 11,
BLESSING_ALL_PVP = 12,
CHARM_EXPANSION = 13,
Expand Down Expand Up @@ -399,6 +399,17 @@ function parseRequestStoreOffers(playerId, msg)
player:updateUIExhausted()
end

-- Used on cyclopedia store summary
local function insertPlayerTransactionSummary(player, offer)
local id = offer.id
if offer.type == GameStore.OfferTypes.OFFER_TYPE_HOUSE then
id = offer.itemtype
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_BLESSINGS then
id = offer.blessid
end
player:createTransactionSummary(offer.type, math.max(1, offer.count or 1), id)
end

function parseBuyStoreOffer(playerId, msg)
local player = Player(playerId)
local id = msg:getU32()
Expand Down Expand Up @@ -450,9 +461,7 @@ function parseBuyStoreOffer(playerId, msg)
-- Handled errors are thrown to indicate that the purchase has failed;
-- Handled errors have a code index and unhandled errors do not
local pcallOk, pcallError = pcall(function()
if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM then
GameStore.processItemPurchase(player, offer.itemtype, offer.count or 1, offer.movable, offer.setOwner)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then
if offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM or offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_UNIQUE then
GameStore.processItemPurchase(player, offer.itemtype, offer.count or 1, offer.movable, offer.setOwner)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS then
GameStore.processInstantRewardAccess(player, offer.count)
Expand All @@ -466,11 +475,9 @@ function parseBuyStoreOffer(playerId, msg)
GameStore.processPremiumPurchase(player, offer.id)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_STACKABLE then
GameStore.processStackablePurchase(player, offer.itemtype, offer.count, offer.name, offer.movable, offer.setOwner)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HOUSE then
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HOUSE or offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_BED then
GameStore.processHouseRelatedPurchase(player, offer)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT then
GameStore.processOutfitPurchase(player, offer.sexId, offer.addon)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT or offer.type == GameStore.OfferTypes.OFFER_TYPE_OUTFIT_ADDON then
GameStore.processOutfitPurchase(player, offer.sexId, offer.addon)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_MOUNT then
GameStore.processMountPurchase(player, offer.id)
Expand Down Expand Up @@ -504,8 +511,6 @@ function parseBuyStoreOffer(playerId, msg)
GameStore.processHirelingSkillPurchase(player, offer)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HIRELING_OUTFIT then
GameStore.processHirelingOutfitPurchase(player, offer)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_ITEM_BED then
GameStore.processHouseRelatedPurchase(player, offer)
else
-- This should never happen by our convention, but just in case the guarding condition is messed up...
error({ code = 0, message = "This offer is unavailable [2]" })
Expand All @@ -523,6 +528,9 @@ function parseBuyStoreOffer(playerId, msg)
return queueSendStoreAlertToUser(alertMessage, 500, playerId)
end

if table.contains({ GameStore.OfferTypes.OFFER_TYPE_HOUSE, GameStore.OfferTypes.OFFER_TYPE_EXPBOOST, GameStore.OfferTypes.OFFER_TYPE_PREYBONUS, GameStore.OfferTypes.OFFER_TYPE_BLESSINGS, GameStore.OfferTypes.OFFER_TYPE_ALLBLESSINGS, GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS }, offer.type) then
insertPlayerTransactionSummary(player, offer)
end
local configure = useOfferConfigure(offer.type)
if configure ~= GameStore.ConfigureOffers.SHOW_CONFIGURE then
if not player:makeCoinTransaction(offer) then
Expand Down Expand Up @@ -1817,6 +1825,7 @@ function GameStore.processHirelingPurchase(player, offer, productType, hirelingN

player:makeCoinTransaction(offer, hirelingName)
local message = "You have successfully bought " .. hirelingName
player:createTransactionSummary(offer.type, 1)
return addPlayerEvent(sendStorePurchaseSuccessful, 650, player:getId(), message)
-- If not, we ask him to do!
else
Expand Down
7 changes: 4 additions & 3 deletions data/scripts/eventcallbacks/player/on_look.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ function callback.playerOnLook(player, thing, position, distance)
description = string.format("%s\nDecays to: %d", description, decayId)
end
elseif thing:isCreature() then
local str = "%s\nHealth: %d / %d"
local str, pId = "%s\n%s\nHealth: %d / %d"
if thing:isPlayer() and thing:getMaxMana() > 0 then
pId = string.format("Player ID: %i", thing:getGuid())
str = string.format("%s, Mana: %d / %d", str, thing:getMana(), thing:getMaxMana())
end
description = string.format(str, description, thing:getHealth(), thing:getMaxHealth()) .. "."
description = string.format(str, description, pId, thing:getHealth(), thing:getMaxHealth())
elsongabriel marked this conversation as resolved.
Show resolved Hide resolved
end

description = string.format("%s\nPosition: (%d, %d, %d)", description, position.x, position.y, position.z)
Expand All @@ -76,7 +77,7 @@ function callback.playerOnLook(player, thing, position, distance)
description = string.format("%s\nSpeed: %d", description, speed)

if thing:isPlayer() then
description = string.format("%s\nIP: %s.", description, Game.convertIpToString(thing:getIp()))
description = string.format("%s\nIP: %s", description, Game.convertIpToString(thing:getIp()))
end
end
end
Expand Down
1 change: 1 addition & 0 deletions src/creatures/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ target_sources(${PROJECT_NAME}_lib PRIVATE
players/player.cpp
players/achievement/player_achievement.cpp
players/cyclopedia/player_badge.cpp
players/cyclopedia/player_cyclopedia.cpp
players/cyclopedia/player_title.cpp
players/wheel/player_wheel.cpp
players/wheel/wheel_gems.cpp
Expand Down
13 changes: 7 additions & 6 deletions src/creatures/players/achievement/player_achievement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ bool PlayerAchievement::add(uint16_t id, bool message /* = true*/, uint32_t time
addPoints(achievement.points);
int toSaveTimeStamp = timestamp != 0 ? timestamp : (OTSYS_TIME() / 1000);
getUnlockedKV()->set(achievement.name, toSaveTimeStamp);
m_achievementsUnlocked.push_back({ achievement.id, toSaveTimeStamp });
m_achievementsUnlocked.emplace_back(achievement.id, toSaveTimeStamp);
m_achievementsUnlocked.shrink_to_fit();
return true;
}
Expand Down Expand Up @@ -80,7 +80,8 @@ bool PlayerAchievement::isUnlocked(uint16_t id) const {
}

uint16_t PlayerAchievement::getPoints() const {
return m_player.kv()->scoped("achievements")->get("points")->getNumber();
auto kvScoped = m_player.kv()->scoped("achievements")->get("points");
return kvScoped ? static_cast<uint16_t>(kvScoped->getNumber()) : 0;
}

void PlayerAchievement::addPoints(uint16_t toAddPoints) {
Expand Down Expand Up @@ -109,12 +110,12 @@ void PlayerAchievement::loadUnlockedAchievements() {

g_logger().debug("[{}] - Achievement {} found for player {}.", __FUNCTION__, achievementName, m_player.getName());

m_achievementsUnlocked.push_back({ achievement.id, getUnlockedKV()->get(achievementName)->getNumber() });
m_achievementsUnlocked.emplace_back(achievement.id, getUnlockedKV()->get(achievementName)->getNumber());
}
}

void PlayerAchievement::sendUnlockedSecretAchievements() {
std::vector<std::pair<Achievement, uint32_t>> m_achievementsUnlocked;
std::vector<std::pair<Achievement, uint32_t>> achievementsUnlocked;
uint16_t unlockedSecret = 0;
for (const auto &[achievId, achievCreatedTime] : getUnlockedAchievements()) {
Achievement achievement = g_game().getAchievementById(achievId);
Expand All @@ -126,10 +127,10 @@ void PlayerAchievement::sendUnlockedSecretAchievements() {
unlockedSecret++;
}

m_achievementsUnlocked.push_back({ achievement, achievCreatedTime });
achievementsUnlocked.emplace_back(achievement, achievCreatedTime);
}

m_player.sendCyclopediaCharacterAchievements(unlockedSecret, m_achievementsUnlocked);
m_player.sendCyclopediaCharacterAchievements(unlockedSecret, achievementsUnlocked);
}

const std::shared_ptr<KV> &PlayerAchievement::getUnlockedKV() {
Expand Down
6 changes: 3 additions & 3 deletions src/creatures/players/achievement/player_achievement.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class PlayerAchievement {
explicit PlayerAchievement(Player &player);
bool add(uint16_t id, bool message = true, uint32_t timestamp = 0);
bool remove(uint16_t id);
bool isUnlocked(uint16_t id) const;
uint16_t getPoints() const;
[[nodiscard]] bool isUnlocked(uint16_t id) const;
[[nodiscard]] uint16_t getPoints() const;
void addPoints(uint16_t toAddPoints);
void removePoints(uint16_t toRemovePoints);
std::vector<std::pair<uint16_t, uint32_t>> getUnlockedAchievements() const;
[[nodiscard]] std::vector<std::pair<uint16_t, uint32_t>> getUnlockedAchievements() const;
void loadUnlockedAchievements();
void sendUnlockedSecretAchievements();
const std::shared_ptr<KV> &getUnlockedKV();
Expand Down
183 changes: 183 additions & 0 deletions src/creatures/players/cyclopedia/player_cyclopedia.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* Canary - A free and open-source MMORPG server emulator
* Copyright (©) 2019-2024 OpenTibiaBR <[email protected]>
* Repository: https://github.com/opentibiabr/canary
* License: https://github.com/opentibiabr/canary/blob/main/LICENSE
* Contributors: https://github.com/opentibiabr/canary/graphs/contributors
* Website: https://docs.opentibiabr.com/
*/

#include "pch.hpp"

#include "database/databasetasks.hpp"
#include "creatures/players/player.hpp"
#include "player_cyclopedia.hpp"
#include "game/game.hpp"
#include "kv/kv.hpp"

PlayerCyclopedia::PlayerCyclopedia(Player &player) :
m_player(player) { }

void PlayerCyclopedia::loadSummaryData() {
DBResult_ptr result = g_database().storeQuery(fmt::format("SELECT COUNT(*) as `count` FROM `player_hirelings` WHERE `player_id` = {}", m_player.getGUID()));
auto kvScoped = m_player.kv()->scoped("summary")->scoped(g_game().getSummaryKeyByType(static_cast<uint8_t>(Summary_t::HIRELINGS)));
if (result && !kvScoped->get("amount").has_value()) {
kvScoped->set("amount", result->getNumber<int16_t>("count"));
}
}

void PlayerCyclopedia::loadDeathHistory(uint16_t page, uint16_t entriesPerPage) {
Benchmark bm_check;
uint32_t offset = static_cast<uint32_t>(page - 1) * entriesPerPage;
auto query = fmt::format("SELECT `time`, `level`, `killed_by`, `mostdamage_by`, (select count(*) FROM `player_deaths` WHERE `player_id` = {}) as `entries` FROM `player_deaths` WHERE `player_id` = {} AND `time` >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) ORDER BY `time` DESC LIMIT {}, {}", m_player.getGUID(), m_player.getGUID(), offset, entriesPerPage);

uint32_t playerID = m_player.getID();
std::function<void(DBResult_ptr, bool)> callback = [playerID, page, entriesPerPage](const DBResult_ptr &result, bool) {
std::shared_ptr<Player> player = g_game().getPlayerByID(playerID);
if (!player) {
return;
}

player->resetAsyncOngoingTask(PlayerAsyncTask_RecentDeaths);
if (!result) {
player->sendCyclopediaCharacterRecentDeaths(0, 0, {});
return;
}

auto pages = result->getNumber<uint32_t>("entries");
pages += entriesPerPage - 1;
pages /= entriesPerPage;

std::vector<RecentDeathEntry> entries;
entries.reserve(result->countResults());
do {
std::string cause1 = result->getString("killed_by");
std::string cause2 = result->getString("mostdamage_by");

std::ostringstream cause;
cause << "Died at Level " << result->getNumber<uint32_t>("level") << " by";
if (!cause1.empty()) {
cause << getArticle(cause1) << cause1;
}

if (!cause2.empty()) {
if (!cause1.empty()) {
cause << " and";
}
cause << getArticle(cause2) << cause2;
}
cause << '.';
elsongabriel marked this conversation as resolved.
Show resolved Hide resolved

entries.emplace_back(std::move(cause.str()), result->getNumber<uint32_t>("time"));
} while (result->next());
player->sendCyclopediaCharacterRecentDeaths(page, static_cast<uint16_t>(pages), entries);
};
g_databaseTasks().store(query, callback);
m_player.addAsyncOngoingTask(PlayerAsyncTask_RecentDeaths);

g_logger().debug("Loading death history from the player {} took {} milliseconds.", m_player.getName(), bm_check.duration());
}

void PlayerCyclopedia::loadRecentKills(uint16_t page, uint16_t entriesPerPage) {
Benchmark bm_check;

const std::string &escapedName = g_database().escapeString(m_player.getName());
uint32_t offset = static_cast<uint32_t>(page - 1) * entriesPerPage;
auto query = fmt::format("SELECT `d`.`time`, `d`.`killed_by`, `d`.`mostdamage_by`, `d`.`unjustified`, `d`.`mostdamage_unjustified`, `p`.`name`, (select count(*) FROM `player_deaths` WHERE ((`killed_by` = {} AND `is_player` = 1) OR (`mostdamage_by` = {} AND `mostdamage_is_player` = 1))) as `entries` FROM `player_deaths` AS `d` INNER JOIN `players` AS `p` ON `d`.`player_id` = `p`.`id` WHERE ((`d`.`killed_by` = {} AND `d`.`is_player` = 1) OR (`d`.`mostdamage_by` = {} AND `d`.`mostdamage_is_player` = 1)) AND `time` >= UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 70 DAY)) ORDER BY `time` DESC LIMIT {}, {}", escapedName, escapedName, escapedName, escapedName, offset, entriesPerPage);

uint32_t playerID = m_player.getID();
std::function<void(DBResult_ptr, bool)> callback = [playerID, page, entriesPerPage](const DBResult_ptr &result, bool) {
std::shared_ptr<Player> player = g_game().getPlayerByID(playerID);
if (!player) {
return;
}

player->resetAsyncOngoingTask(PlayerAsyncTask_RecentPvPKills);
if (!result) {
player->sendCyclopediaCharacterRecentPvPKills(0, 0, {});
return;
}

auto pages = result->getNumber<uint32_t>("entries");
pages += entriesPerPage - 1;
pages /= entriesPerPage;

std::vector<RecentPvPKillEntry> entries;
entries.reserve(result->countResults());
do {
std::string cause1 = result->getString("killed_by");
std::string cause2 = result->getString("mostdamage_by");
std::string name = result->getString("name");

uint8_t status = CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_JUSTIFIED;
if (player->getName() == cause1) {
if (result->getNumber<uint32_t>("unjustified") == 1) {
status = CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_UNJUSTIFIED;
}
} else if (player->getName() == cause2) {
if (result->getNumber<uint32_t>("mostdamage_unjustified") == 1) {
status = CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_UNJUSTIFIED;
}
}

entries.emplace_back(fmt::format("Killed {}.", name), result->getNumber<uint32_t>("time"), status);
} while (result->next());
player->sendCyclopediaCharacterRecentPvPKills(page, static_cast<uint16_t>(pages), entries);
};
g_databaseTasks().store(query, callback);
m_player.addAsyncOngoingTask(PlayerAsyncTask_RecentPvPKills);

g_logger().debug("Loading recent kills from the player {} took {} milliseconds.", m_player.getName(), bm_check.duration());
}

void PlayerCyclopedia::updateStoreSummary(uint8_t type, uint16_t amount, const std::string &id) {
switch (type) {
case Summary_t::HOUSE_ITEMS:
case Summary_t::BLESSINGS:
insertValue(type, amount, id);
break;
case Summary_t::ALL_BLESSINGS:
for (int i = 1; i < 8; ++i) {
insertValue(static_cast<uint8_t>(Summary_t::BLESSINGS), amount, fmt::format("{}", i));
}
break;
default:
updateAmount(type, amount);
break;
}
}

uint16_t PlayerCyclopedia::getAmount(uint8_t type) {
auto kvScope = m_player.kv()->scoped("summary")->scoped(g_game().getSummaryKeyByType(type))->get("amount");
return static_cast<uint16_t>(kvScope ? kvScope->getNumber() : 0);
}

void PlayerCyclopedia::updateAmount(uint8_t type, uint16_t amount) {
auto oldAmount = getAmount(type);
m_player.kv()->scoped("summary")->scoped(g_game().getSummaryKeyByType(type))->set("amount", oldAmount + amount);
}

std::map<uint16_t, uint16_t> PlayerCyclopedia::getResult(uint8_t type) const {
auto kvScope = m_player.kv()->scoped("summary")->scoped(g_game().getSummaryKeyByType(type));
std::map<uint16_t, uint16_t> result; // ID, amount
for (const auto &scope : kvScope->keys()) {
size_t pos = scope.find('.');
if (pos == std::string::npos) {
g_logger().error("[{}] Invalid key format: {}", __FUNCTION__, scope);
continue;
}
std::string id = scope.substr(0, pos);
auto amount = kvScope->scoped(id)->get("amount");
result.emplace(std::stoll(id), static_cast<uint16_t>(amount ? amount->getNumber() : 0));
}
return result;
}

void PlayerCyclopedia::insertValue(uint8_t type, uint16_t amount, const std::string &id) {
auto result = getResult(type);
auto it = result.find(std::stoll(id));
auto oldAmount = (it != result.end() ? it->second : 0);
auto newAmount = oldAmount + amount;
m_player.kv()->scoped("summary")->scoped(g_game().getSummaryKeyByType(type))->scoped(id)->set("amount", newAmount);
g_logger().debug("[{}] type: {}, id: {}, old amount: {}, added amount: {}, new amount: {}", __FUNCTION__, type, id, oldAmount, amount, newAmount);
}
Loading
Loading