From 4097edf05536095d39666b5a839c962486d84a9e Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Wed, 10 Apr 2024 13:20:48 -0300 Subject: [PATCH] badge & title system: wip. --- config.lua.dist | 4 + src/canary_server.cpp | 1 + src/config/config_enums.hpp | 2 + src/config/configmanager.cpp | 2 + src/creatures/CMakeLists.txt | 1 + .../players/cyclopedia/player_cyclopedia.cpp | 224 +++++++++++++++ .../players/cyclopedia/player_cyclopedia.hpp | 67 +++++ src/creatures/players/player.cpp | 46 +++ src/creatures/players/player.hpp | 25 +- src/game/game.cpp | 7 +- src/game/game.hpp | 25 ++ .../functions/core/game/game_functions.cpp | 30 ++ .../functions/core/game/game_functions.hpp | 8 +- .../creatures/player/player_functions.cpp | 263 ++++++++++++++++++ .../creatures/player/player_functions.hpp | 49 ++++ src/server/network/protocol/protocolgame.cpp | 138 +++++++-- src/server/network/protocol/protocolgame.hpp | 5 +- src/server/server_definitions.hpp | 6 +- src/utils/const.hpp | 3 + vcproj/canary.vcxproj | 4 +- 20 files changed, 875 insertions(+), 35 deletions(-) create mode 100644 src/creatures/players/cyclopedia/player_cyclopedia.cpp create mode 100644 src/creatures/players/cyclopedia/player_cyclopedia.hpp diff --git a/config.lua.dist b/config.lua.dist index a5bb3d73ca5..78d97be5516 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -184,6 +184,10 @@ loyaltyPointsPerPremiumDaySpent = 0 loyaltyPointsPerPremiumDayPurchased = 0 loyaltyBonusPercentageMultiplier = 1.0 +-- Cyclopedia info +badgeSystemEnabled = true +titleSystemEnabled = true + -- Custom PvP system -- NOTE: Rate is additive percent for each level and applied multiplicative to totalDamage -- NOTE: Damage multipliers for each vocation are adjusted in data/XML/vocations.xml diff --git a/src/canary_server.cpp b/src/canary_server.cpp index 0961bc93fde..f49455bad55 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -374,6 +374,7 @@ void CanaryServer::loadModules() { modulesLoadHelper((g_npcs().load(false, true)), "npc"); g_game().loadBoostedCreature(); + // TODO g_game().initializeGameWorldHighscores(); g_ioBosstiary().loadBoostedBoss(); g_ioprey().initializeTaskHuntOptions(); } diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index b9b857f435c..73f073a0c81 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -20,6 +20,7 @@ enum ConfigKey_t : uint16_t { AUTH_TYPE, AUTOBANK, AUTOLOOT, + BADGE_SYSTEM_ENABLED, BESTIARY_KILL_MULTIPLIER, BESTIARY_RATE_CHARM_SHOP_PRICE, BIND_ONLY_GLOBAL_ADDRESS, @@ -277,6 +278,7 @@ enum ConfigKey_t : uint16_t { TIBIADROME_CONCOCTION_COOLDOWN, TIBIADROME_CONCOCTION_DURATION, TIBIADROME_CONCOCTION_TICK_TYPE, + TITLE_SYSTEM_ENABLED, TOGGLE_ATTACK_SPEED_ONFIST, TOGGLE_CHAIN_SYSTEM, TOGGLE_DOWNLOAD_MAP, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index c1702672e71..aeb23835db2 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -89,6 +89,7 @@ bool ConfigManager::load() { loadBoolConfig(L, ALLOW_RELOAD, "allowReload", false); loadBoolConfig(L, AUTOBANK, "autoBank", false); loadBoolConfig(L, AUTOLOOT, "autoLoot", false); + loadBoolConfig(L, BADGE_SYSTEM_ENABLED, "badgeSystemEnabled", true); loadBoolConfig(L, BOOSTED_BOSS_SLOT, "boostedBossSlot", true); loadBoolConfig(L, CLASSIC_ATTACK_SPEED, "classicAttackSpeed", false); loadBoolConfig(L, CLEAN_PROTECTION_ZONES, "cleanProtectionZones", false); @@ -139,6 +140,7 @@ bool ConfigManager::load() { loadBoolConfig(L, TASK_HUNTING_FREE_THIRD_SLOT, "taskHuntingFreeThirdSlot", false); loadBoolConfig(L, TELEPORT_PLAYER_TO_VOCATION_ROOM, "teleportPlayerToVocationRoom", true); loadBoolConfig(L, TELEPORT_SUMMONS, "teleportSummons", false); + loadBoolConfig(L, TITLE_SYSTEM_ENABLED, "titleSystemEnabled", true); loadBoolConfig(L, TOGGLE_ATTACK_SPEED_ONFIST, "toggleAttackSpeedOnFist", false); loadBoolConfig(L, TOGGLE_CHAIN_SYSTEM, "toggleChainSystem", true); loadBoolConfig(L, TOGGLE_DOWNLOAD_MAP, "toggleDownloadMap", false); diff --git a/src/creatures/CMakeLists.txt b/src/creatures/CMakeLists.txt index f3dedf3a8f3..2259182e17d 100644 --- a/src/creatures/CMakeLists.txt +++ b/src/creatures/CMakeLists.txt @@ -22,6 +22,7 @@ target_sources(${PROJECT_NAME}_lib PRIVATE players/storages/storages.cpp players/player.cpp players/achievement/player_achievement.cpp + players/cyclopedia/player_cyclopedia.cpp players/wheel/player_wheel.cpp players/wheel/wheel_gems.cpp players/vocations/vocation.cpp diff --git a/src/creatures/players/cyclopedia/player_cyclopedia.cpp b/src/creatures/players/cyclopedia/player_cyclopedia.cpp new file mode 100644 index 00000000000..0d2646b0afb --- /dev/null +++ b/src/creatures/players/cyclopedia/player_cyclopedia.cpp @@ -0,0 +1,224 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2024 OpenTibiaBR + * 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 "player_cyclopedia.hpp" + +#include "creatures/players/player.hpp" +#include "game/game.hpp" +#include "kv/kv.hpp" + +PlayerCyclopedia::PlayerCyclopedia(Player &player) : + m_player(player) {} + +// Badge +bool PlayerCyclopedia::hasBadge(uint8_t id) { + if (auto it = std::find_if(badges.begin(), badges.end(), [id](uint8_t it) { + return it == id; + }); it != badges.end()) { + return true; + } + + return false; +} + +void PlayerCyclopedia::addBadge(uint8_t id) { + if (hasBadge(id)) { + return; + } + + badges.emplace_back(id); +} + +// Title +std::vector PlayerCyclopedia::getTitles() const { + return titles; +} + +uint8_t PlayerCyclopedia::getCurrentTitle() const { + return m_currentTitle; +} + +std::string PlayerCyclopedia::getCurrentTitleName() const { + auto currentTitle = getCurrentTitle(); + if (currentTitle == 0) { + return ""; + } + + auto title = g_game().getTitle(currentTitle); + return (m_player.getSex() == PLAYERSEX_MALE) ? title.maleName : title.femaleName; +} + +void PlayerCyclopedia::setCurrentTitle(uint8_t id) { + if (id != 0 && !isTitleUnlocked(id)) { + return; + } + + m_currentTitle = id; +} + +void PlayerCyclopedia::addTitle(uint8_t id) { + if (isTitleUnlocked(id)) { + return; + } + + titles.emplace_back(id); +} + + + +bool PlayerCyclopedia::isTitleUnlocked(uint8_t id) const { + if (auto it = std::find_if(titles.begin(), titles.end(), [id](uint8_t title_it) { + return title_it == id; + }); it != titles.end()) { + return true; + } + + return false; +} + +// Death History +std::vector PlayerCyclopedia::getDeathHistory() const { + return m_deathHistory; +} + +void PlayerCyclopedia::insertDeathOnHistory(std::string cause, uint32_t timestamp) { + m_deathHistory.emplace_back(std::move(cause), timestamp); +} + +std::vector PlayerCyclopedia::getPvpKillsHistory() const { + return m_pvpKillsHistory; +} + +void PlayerCyclopedia::insertPvpKillOnHistory(std::string cause, uint32_t timestamp, uint8_t status) { + m_pvpKillsHistory.emplace_back(std::move(cause), timestamp, status); +} + +// Player summary region +// Get: +//std::vector getHirelinsOutfitsObtained() const { +// return m_player.hirelingOutfitsObtained; +//} +// +//std::vector getHirelinsJobsObtained() const { +// return hirelingJobsObtained; +//} +// +//std::map getBlessingsObtained() const { +// return blessingsObtained; +//} +// +//StashItemList getHouseItemsObtained() const { +// return houseItemsObtained; +//} +// +//uint16_t getXpBoostsObtained() const { +// return storeXpBoostsObtained; +//} +// +//uint16_t getRewardCollectionObtained() const { +// return dailyRewardCollectionsObtained; +//} +// +//uint16_t getHirelingsObtained() const { +// return hirelingsObtained; +//} +// +//uint16_t getPreyCardsObtained() const { +// return preyCardsObtained; +//} +// +//uint16_t getCharmsPointsObtained() const { +// return charmsObtained; +//} +// +//uint16_t getGoshnarTaintsObtained() const { +// return goshnarObtained; +//} +// +//uint16_t getDromePointsObtained() const { +// return dromeObtained; +//} +// +//uint16_t getLoginStreak() const { +// return loginStreak; +//} +// +//uint16_t getTaskHuntingPointsObtained() const { +// return taskHuntingPointsObtained; +//} +// +//uint16_t getMapAreaDiscoveredPercentage() const { +// return mapAreaDiscoveredPercentage; +//} +// +//// Player summary region +//// Set: +//void addHirelingOutfitObtained(uint16_t lookType) { +// hirelingOutfitsObtained.push_back(lookType); +//} +// +//void addHirelingJobsObtained(uint8_t jobId) { +// hirelingJobsObtained.push_back(jobId); +//} +// +//void addBlessingsObtained(Blessings_t id, uint16_t amount) { +// blessingsObtained[id] += amount; +//} +// +//void addHouseItemsObtained(uint16_t itemId, uint32_t amount) { +// houseItemsObtained[itemId] += amount; +//} +// +//void addXpBoostsObtained(uint16_t amount) { +// storeXpBoostsObtained += amount; +//} +// +//void addRewardCollectionObtained(uint16_t amount) { +// dailyRewardCollectionsObtained += amount; +//} +// +//void addHirelingsObtained(uint16_t amount) { +// hirelingsObtained += amount; +//} +// +//void addPreyCardsObtained(uint16_t amount) { +// preyCardsObtained += amount; +//} +// +//void addCharmsPointsObtained(uint16_t amount) { +// charmsObtained += amount; +//} +// +//void addGoshnarTaintsObtained(uint16_t amount) { +// goshnarObtained += amount; +//} +// +//void addDromePointsObtained(uint16_t amount) { +// dromeObtained += amount; +//} +// +//void addLoginStreak(uint16_t amount) { +// loginStreak += amount; +//} +// +//void addTaskHuntingPointsObtained(uint16_t amount) { +// taskHuntingPointsObtained += amount; +//} +// +//void addMapAreaDiscoveredPercentage(uint16_t amount) { +// mapAreaDiscoveredPercentage += amount; +//} +// + +//std::map> getAccountLevelVocation() const { +// return accountLevelSummary; +//} + diff --git a/src/creatures/players/cyclopedia/player_cyclopedia.hpp b/src/creatures/players/cyclopedia/player_cyclopedia.hpp new file mode 100644 index 00000000000..3c20a4e8821 --- /dev/null +++ b/src/creatures/players/cyclopedia/player_cyclopedia.hpp @@ -0,0 +1,67 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2024 OpenTibiaBR + * 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/ + */ + +#pragma once + +class Player; + +struct PlayerTitle { + PlayerTitle() {} + + uint16_t id = 0; + std::string maleName; + std::string femaleName; + std::string description; + bool permanent = false; +}; + +class PlayerCyclopedia { +public: + explicit PlayerCyclopedia(Player &player); + bool hasBadge(uint8_t id); + void addBadge(uint8_t id); + + std::vector getTitles() const; + uint8_t getCurrentTitle() const; + std::string getCurrentTitleName() const; + void setCurrentTitle(uint8_t id); + void addTitle(uint8_t id); + bool isTitleUnlocked(uint8_t id) const; + + std::vector getDeathHistory() const; + std::vector getPvpKillsHistory() const; + void insertDeathOnHistory(std::string cause, uint32_t timestamp); + void insertPvpKillOnHistory(std::string cause, uint32_t timestamp, uint8_t status); + +private: + Player &m_player; + + std::vector badges; + std::vector titles; + uint8_t m_currentTitle; + +// std::vector hirelingOutfitsObtained; +// std::vector hirelingJobsObtained; +// std::map blessingsObtained; +// StashItemList houseItemsObtained; +// uint16_t storeXpBoostsObtained = 0; +// uint16_t dailyRewardCollectionsObtained = 0; +// uint16_t hirelingsObtained = 0; +// uint16_t preyCardsObtained = 0; +// uint16_t charmsObtained = 0; +// uint16_t goshnarObtained = 0; +// uint16_t dromeObtained = 0; +// uint16_t loginStreak = 0; +// uint16_t taskHuntingPointsObtained = 0; +// uint16_t mapAreaDiscoveredPercentage = 0; + + std::vector m_deathHistory; + std::vector m_pvpKillsHistory; + std::map> m_accountLevelSummary; +}; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 75e5b5ef2af..54accf16918 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -16,6 +16,7 @@ #include "creatures/players/player.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "creatures/players/achievement/player_achievement.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "creatures/players/storages/storages.hpp" #include "game/game.hpp" #include "game/modal_window/modal_window.hpp" @@ -133,6 +134,10 @@ std::string Player::getDescription(int32_t lookDistance) { s << " You are a " << loyaltyTitle << "."; } + if (summary()->getCurrentTitle() != 0) { + s << " You are a " << summary()->getCurrentTitleName() << "."; + } + if (isVip()) { s << " You are VIP."; } @@ -161,6 +166,10 @@ std::string Player::getDescription(int32_t lookDistance) { s << " " << subjectPronoun << " " << getSubjectVerb() << " " << article << " " << loyaltyTitle << "."; } + if (summary()->getCurrentTitle() != 0) { + s << " " << subjectPronoun << " " << getSubjectVerb() << " a " << summary()->getCurrentTitleName() << "."; + } + if (isVip()) { s << " " << subjectPronoun << " " << getSubjectVerb() << " VIP."; } @@ -2844,6 +2853,34 @@ void Player::death(std::shared_ptr lastHitCreature) { } sendTextMessage(MESSAGE_EVENT_ADVANCE, blessOutput.str()); + // Pvp and pve death registration + std::ostringstream description; + if (pvpDeath) { + description << "Killed " << getName() << '.'; + CyclopediaCharacterInfo_RecentKillStatus_t status = unfairFightReduction != 100 ? CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_UNJUSTIFIED : CYCLOPEDIA_CHARACTERINFO_RECENTKILLSTATUS_JUSTIFIED; + if (lastHitCreature && lastHitCreature->getPlayer()) { + lastHitCreature->getPlayer()->summary()->insertPvpKillOnHistory(std::move(description.str()), OTSYS_TIME() / 1000, status); + } else if (lastHitCreature && lastHitCreature->getMaster() && lastHitCreature->getMaster()->getPlayer()) { + lastHitCreature->getMaster()->getPlayer()->summary()->insertPvpKillOnHistory(std::move(description.str()), OTSYS_TIME() / 1000, status); + } + } else { + description << "Died at Level " << getLevel() << " by"; + if (lastHitCreature) { + const char& character = lastHitCreature->getName().front(); + if (character == 'a' || character == 'e' || character == 'i' || character == 'o' || character == 'u') { + description << " an "; + } else { + description << " a "; + } + description << lastHitCreature->getName(); + } else { + description << " a field item"; + } + description << '.'; + + getPlayer()->summary()->insertDeathOnHistory(std::move(description.str()), OTSYS_TIME() / 1000); + } + sendStats(); sendSkills(); sendReLoginWindow(unfairFightReduction); @@ -7967,6 +8004,15 @@ const std::unique_ptr &Player::achiev() const { return m_playerAchievement; } +// Summary interface +std::unique_ptr &Player::summary() { + return m_playerCyclopedia; +} + +const std::unique_ptr &Player::summary() const { + return m_playerCyclopedia; +} + void Player::sendLootMessage(const std::string &message) const { auto party = getParty(); if (!party) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 592db179071..6087ba42691 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -34,6 +34,7 @@ #include "creatures/npcs/npc.hpp" #include "game/bank/bank.hpp" #include "enums/object_category.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" class House; class NetworkMessage; @@ -49,11 +50,13 @@ class TaskHuntingSlot; class Spell; class PlayerWheel; class PlayerAchievement; +class PlayerCyclopedia; class Spectators; class Account; struct ModalWindow; struct Achievement; +struct PlayerTitle; struct ForgeHistory { ForgeAction_t actionType = ForgeAction_t::FUSION; @@ -730,6 +733,13 @@ class Player final : public Creature, public Cylinder, public Bankable { uint32_t getCapacity() const; + uint32_t getBonusCapacity() const { + if (hasFlag(PlayerFlags_t::CannotPickupItem) || hasFlag(PlayerFlags_t::HasInfiniteCapacity)) { + return std::numeric_limits::max(); + } + return bonusCapacity; + } + uint32_t getFreeCapacity() const { if (hasFlag(PlayerFlags_t::CannotPickupItem)) { return 0; @@ -1646,14 +1656,14 @@ class Player final : public Creature, public Cylinder, public Bankable { client->sendCyclopediaCharacterInspection(); } } - void sendCyclopediaCharacterBadges() { + void sendCyclopediaCharacterBadges(std::map badges) { if (client) { - client->sendCyclopediaCharacterBadges(); + client->sendCyclopediaCharacterBadges(badges); } } - void sendCyclopediaCharacterTitles() { + void sendCyclopediaCharacterTitles(std::map titles) { if (client) { - client->sendCyclopediaCharacterTitles(); + client->sendCyclopediaCharacterTitles(titles); } } void sendHighscoresNoData() { @@ -2587,6 +2597,10 @@ class Player final : public Creature, public Cylinder, public Bankable { std::unique_ptr &achiev(); const std::unique_ptr &achiev() const; + // Player summary interface + std::unique_ptr &summary(); + const std::unique_ptr &summary() const; + void sendLootMessage(const std::string &message) const; std::shared_ptr getLootPouch(); @@ -2654,6 +2668,7 @@ class Player final : public Creature, public Cylinder, public Bankable { uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override; void stashContainer(StashContainerList itemDict); ItemsTierCountList getInventoryItemsId() const; +// todo ItemsTierCountList getStoreInboxItemsId() const; // This function is a override function of base class std::map &getAllItemTypeCount(std::map &countMap) const override; @@ -2979,9 +2994,11 @@ class Player final : public Creature, public Cylinder, public Bankable { friend class IOLoginDataLoad; friend class IOLoginDataSave; friend class PlayerAchievement; + friend class PlayerCyclopedia; std::unique_ptr m_wheelPlayer; std::unique_ptr m_playerAchievement; + std::unique_ptr m_playerCyclopedia; std::mutex quickLootMutex; diff --git a/src/game/game.cpp b/src/game/game.cpp index cac0b596ebf..2e380dd590a 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -37,6 +37,7 @@ #include "creatures/players/imbuements/imbuements.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "creatures/players/achievement/player_achievement.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "creatures/npcs/npc.hpp" #include "server/network/webhook/webhook.hpp" #include "server/network/protocol/protocollogin.hpp" @@ -8270,10 +8271,10 @@ void Game::playerCyclopediaCharacterInfo(std::shared_ptr player, uint32_ player->sendCyclopediaCharacterInspection(); break; case CYCLOPEDIA_CHARACTERINFO_BADGES: - player->sendCyclopediaCharacterBadges(); + player->sendCyclopediaCharacterBadges(getPlayerBadges()); break; case CYCLOPEDIA_CHARACTERINFO_TITLES: - player->sendCyclopediaCharacterTitles(); + player->sendCyclopediaCharacterTitles(getPlayerTitles()); break; default: player->sendCyclopediaCharacterNoData(characterInfoType, 1); @@ -8379,7 +8380,7 @@ void Game::processHighscoreResults(DBResult_ptr result, uint32_t playerID, uint8 } else { characterVocation = 0; } - characters.emplace_back(std::move(result->getString("name")), result->getNumber("points"), result->getNumber("id"), result->getNumber("rank"), result->getNumber("level"), characterVocation); + characters.emplace_back(std::move(result->getString("name")), result->getNumber("points"), result->getNumber("id"), result->getNumber("rank"), result->getNumber("level"), characterVocation, 0); } while (result->next()); } diff --git a/src/game/game.hpp b/src/game/game.hpp index 708aa4b1782..6ef72b69c4a 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -717,10 +717,35 @@ class Game { std::vector getPublicAchievements(); std::map getAchievements(); + void registerPlayerBadges(uint8_t id, std::string name) { + playerBadges[id] = name; + } + std::map getPlayerBadges() const { + return playerBadges; + } + + void registerPlayerTitle(uint8_t id, std::string maleName, std::string femaleName, std::string description, bool permanent) { + playerTitles[id] = PlayerTitle(); + playerTitles[id].id = id; + playerTitles[id].maleName = maleName; + playerTitles[id].femaleName = femaleName; + playerTitles[id].description = description; + playerTitles[id].permanent = permanent; + } + std::map getPlayerTitles() const { + return playerTitles; + } + PlayerTitle getTitle(uint8_t id) { + return playerTitles[id]; + } + private: std::map m_achievements; std::map m_achievementsNameToId; + std::map playerBadges; + std::map playerTitles; + std::vector m_highscoreCategories; std::unordered_map m_highscoreCategoriesNames; diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index ea418dede66..6294c4ae016 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -27,6 +27,7 @@ #include "lua/callbacks/event_callback.hpp" #include "lua/callbacks/events_callbacks.hpp" #include "creatures/players/achievement/player_achievement.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "map/spectators.hpp" // Game @@ -904,3 +905,32 @@ int GameFunctions::luaGameGetAchievements(lua_State* L) { } return 1; } + +int GameFunctions::luaGameRegisterPlayerBadges(lua_State* L) { + // Game.registerPlayerBadges(id, name) + if (lua_gettop(L) < 2) { + reportErrorFunc("Achievement can only be registered with all params."); + lua_pushnil(L); + } else { + g_game().registerPlayerBadges(getNumber(L, 1), getString(L, 2)); + pushBoolean(L, true); + } + return 1; +} + +int GameFunctions::luaGameRegisterPlayerTitle(lua_State* L) { + // Game.registerPlayerTitle(id, maleName, femaleName, description, permanent) + if (lua_gettop(L) < 5) { + reportErrorFunc("Achievement can only be registered with all params."); + lua_pushnil(L); + } else { + uint16_t id = getNumber(L, 1); + std::string maleName = getString(L, 2); + std::string femaleName = getString(L, 3); + std::string description = getString(L, 4); + bool permanent = getBoolean(L, 5); + g_game().registerPlayerTitle(id, maleName, femaleName, description, permanent); + pushBoolean(L, true); + } + return 1; +} diff --git a/src/lua/functions/core/game/game_functions.hpp b/src/lua/functions/core/game/game_functions.hpp index 7f3b642e97d..dfa970bda99 100644 --- a/src/lua/functions/core/game/game_functions.hpp +++ b/src/lua/functions/core/game/game_functions.hpp @@ -88,7 +88,10 @@ class GameFunctions final : LuaScriptInterface { registerMethod(L, "Game", "getSecretAchievements", GameFunctions::luaGameGetSecretAchievements); registerMethod(L, "Game", "getPublicAchievements", GameFunctions::luaGameGetPublicAchievements); registerMethod(L, "Game", "getAchievements", GameFunctions::luaGameGetAchievements); - } + + registerMethod(L, "Game", "registerPlayerBadges", GameFunctions::luaGameRegisterPlayerBadges); + registerMethod(L, "Game", "registerPlayerTitle", GameFunctions::luaGameRegisterPlayerTitle); + } private: static int luaGameCreateMonsterType(lua_State* L); @@ -164,4 +167,7 @@ class GameFunctions final : LuaScriptInterface { static int luaGameGetSecretAchievements(lua_State* L); static int luaGameGetPublicAchievements(lua_State* L); static int luaGameGetAchievements(lua_State* L); + + static int luaGameRegisterPlayerBadges(lua_State* L); + static int luaGameRegisterPlayerTitle(lua_State* L); }; diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 3ba2c43841d..210f69d85df 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -15,6 +15,7 @@ #include "creatures/players/player.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "creatures/players/achievement/player_achievement.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "game/game.hpp" #include "io/iologindata.hpp" #include "io/ioprey.hpp" @@ -4279,3 +4280,265 @@ int PlayerFunctions::luaPlayerRemoveAchievementPoints(lua_State* L) { pushBoolean(L, true); return 1; } + +int PlayerFunctions::luaPlayerAddBadge(lua_State *L) { + // player:addBadge(id) + const auto &player = getUserdataShared(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + player->summary()->addBadge(getNumber(L, 2, 0)); + pushBoolean(L, true); + return 1; +} + +int PlayerFunctions::luaPlayerAddTitle(lua_State* L) { + // player:addTitle(id) + const auto &player = getUserdataShared(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + player->summary()->addTitle(getNumber(L, 2, 0)); + pushBoolean(L, true); + return 1; +} + +int PlayerFunctions::luaPlayerGetTitles(lua_State* L) { + // player:getTitles() + const auto &player = getUserdataShared(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + int index = 0; + lua_createtable(L, player->summary()->getTitles().size(), 0); + for (const auto id : player->summary()->getTitles()) { + lua_pushnumber(L, id); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +//int PlayerFunctions::luaPlayerAddHirelingOutfitObtained(lua_State* L) { +// // player:addHirelingOutfitObtained(lookType) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addHirelingOutfitObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddHirelingJobsObtained(lua_State* L) { +// // player:addHirelingJobsObtained(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addHirelingJobsObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddBlessingsObtained(lua_State* L) { +// // player:addBlessingsObtained(id, amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addBlessingsObtained(static_cast(getNumber(L, 2)), getNumber(L, 3)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddHouseItemsObtained(lua_State* L) { +// // player:addHouseItemsObtained(id, amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addHouseItemsObtained(getNumber(L, 2), getNumber(L, 3)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddXpBoostsObtained(lua_State* L) { +// // player:addXpBoostsObtained(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addXpBoostsObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddRewardCollectionObtained(lua_State* L) { +// // player:addRewardCollectionObtained(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addRewardCollectionObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddHirelingsObtained(lua_State* L) { +// // player:addHirelingsObtained(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addHirelingsObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddPreyCardsObtained(lua_State* L) { +// // player:addPreyCardsObtained(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addPreyCardsObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddLoginStreak(lua_State* L) { +// // player:addLoginStreak(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addLoginStreak(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddTaskHuntingPointsObtained(lua_State* L) { +// // player:addTaskHuntingPointsObtained(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addTaskHuntingPointsObtained(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerAddMapAreaDiscoveredPercentage(lua_State* L) { +// // player:addMapAreaDiscoveredPercentage(amount) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// player->addMapAreaDiscoveredPercentage(getNumber(L, 2)); +// pushBoolean(L, true); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerGetBestiaryRaceEntries(lua_State* L) { +// // player:getBestiaryRaceEntries(race) +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// lua_pushnumber(L, g_iobestiary().getBestiaryRaceUnlocked(player, static_cast(getNumber(L, 2, 0)))); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerGetLoginStreak(lua_State* L) { +// // player:getLoginStreak() +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// lua_pushnumber(L, player->getLoginStreak()); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerGetTaskHuntingPointsObtained(lua_State* L) { +// // player:getTaskHuntingPointsObtained() +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// lua_pushnumber(L, player->getTaskHuntingPointsObtained()); +// return 1; +//} +// +//int PlayerFunctions::luaPlayerGetMapAreaDiscoveredPercentage(lua_State* L) { +// // player:getMapAreaDiscoveredPercentage() +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// lua_pushnumber(L, player->getMapAreaDiscoveredPercentage()); +// return 1; +//} +// + +//int PlayerFunctions::luaPlayerGetAccountLevelVocation(lua_State* L) { +// // player:getAccountLevelVocation() +// Player* player = getUserdata(L, 1); +// if (!player) { +// lua_pushnil(L); +// return 1; +// } +// +// int index = 0; +// int size = 0; +// std::map> accountData = player->getAccountLevelVocation(); +// for (const auto data : accountData) { +// size += data.second.size(); +// } +// +// lua_createtable(L, size, 0); +// for (const auto data : accountData) { +// for (const auto level : data.second) { +// lua_createtable(L, 0, 2); +// setField(L, "vocation", data.first); +// setField(L, "level", level); +// lua_rawseti(L, -2, ++index); +// } +// } +// return 1; +//} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index fafc99ff955..3beda790136 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -364,6 +364,31 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "addAchievementPoints", PlayerFunctions::luaPlayerAddAchievementPoints); registerMethod(L, "Player", "removeAchievementPoints", PlayerFunctions::luaPlayerRemoveAchievementPoints); + // Cyclopedia Functions + registerMethod(L, "Player", "addBadge", PlayerFunctions::luaPlayerAddBadge); + registerMethod(L, "Player", "addTitle", PlayerFunctions::luaPlayerAddTitle); + registerMethod(L, "Player", "getTitles", PlayerFunctions::luaPlayerGetTitles); + +// registerMethod(L, "Player", "addHirelingOutfitObtained", PlayerFunctions::luaPlayerAddHirelingOutfitObtained); +// registerMethod(L, "Player", "addHirelingJobsObtained", PlayerFunctions::luaPlayerAddHirelingJobsObtained); +// registerMethod(L, "Player", "addBlessingsObtained", PlayerFunctions::luaPlayerAddBlessingsObtained); +// registerMethod(L, "Player", "addHouseItemsObtained", PlayerFunctions::luaPlayerAddHouseItemsObtained); +// registerMethod(L, "Player", "addXpBoostsObtained", PlayerFunctions::luaPlayerAddXpBoostsObtained); +// registerMethod(L, "Player", "addRewardCollectionObtained", PlayerFunctions::luaPlayerAddRewardCollectionObtained); +// registerMethod(L, "Player", "addHirelingsObtained", PlayerFunctions::luaPlayerAddHirelingsObtained); +// registerMethod(L, "Player", "addPreyCardsObtained", PlayerFunctions::luaPlayerAddPreyCardsObtained); +// registerMethod(L, "Player", "addLoginStreak", PlayerFunctions::luaPlayerAddLoginStreak); +// registerMethod(L, "Player", "addTaskHuntingPointsObtained", PlayerFunctions::luaPlayerAddTaskHuntingPointsObtained); +// registerMethod(L, "Player", "addMapAreaDiscoveredPercentage", PlayerFunctions::luaPlayerAddMapAreaDiscoveredPercentage); +// +// registerMethod(L, "Player", "getBestiaryRaceEntries", PlayerFunctions::luaPlayerGetBestiaryRaceEntries); +// registerMethod(L, "Player", "getLoginStreak", PlayerFunctions::luaPlayerGetLoginStreak); +// registerMethod(L, "Player", "getTaskHuntingPointsObtained", PlayerFunctions::luaPlayerGetTaskHuntingPointsObtained); +// registerMethod(L, "Player", "getMapAreaDiscoveredPercentage", PlayerFunctions::luaPlayerGetMapAreaDiscoveredPercentage); +// +// registerMethod(L, "Player", "getAccountLevelVocation", PlayerFunctions::luaPlayerGetAccountLevelVocation); + + GroupFunctions::init(L); GuildFunctions::init(L); MountFunctions::init(L); @@ -718,5 +743,29 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerAddAchievementPoints(lua_State* L); static int luaPlayerRemoveAchievementPoints(lua_State* L); + static int luaPlayerAddBadge(lua_State* L); + static int luaPlayerAddTitle(lua_State* L); + static int luaPlayerGetTitles(lua_State* L); + +// static int luaPlayerAddHirelingOutfitObtained(lua_State* L); +// static int luaPlayerAddHirelingJobsObtained(lua_State* L); +// static int luaPlayerAddBlessingsObtained(lua_State* L); +// static int luaPlayerAddHouseItemsObtained(lua_State* L); +// static int luaPlayerAddXpBoostsObtained(lua_State* L); +// static int luaPlayerAddRewardCollectionObtained(lua_State* L); +// static int luaPlayerAddHirelingsObtained(lua_State* L); +// static int luaPlayerAddPreyCardsObtained(lua_State* L); +// static int luaPlayerAddLoginStreak(lua_State* L); +// static int luaPlayerAddTaskHuntingPointsObtained(lua_State* L); +// static int luaPlayerAddMapAreaDiscoveredPercentage(lua_State* L); +// +// static int luaPlayerGetLoginStreak(lua_State* L); +// static int luaPlayerGetTaskHuntingPointsObtained(lua_State* L); +// static int luaPlayerGetMapAreaDiscoveredPercentage(lua_State* L); +// +// static int luaPlayerGetBestiaryRaceEntries(lua_State* L); +// +// static int luaPlayerGetAccountLevelVocation(lua_State* L); + friend class CreatureFunctions; }; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 0d0d8e79f17..cf374e78314 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -27,6 +27,7 @@ #include "creatures/players/player.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "creatures/players/achievement/player_achievement.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" #include "creatures/players/grouping/familiars.hpp" #include "server/network/protocol/protocolgame.hpp" #include "game/scheduling/dispatcher.hpp" @@ -3406,8 +3407,8 @@ void ProtocolGame::sendCyclopediaCharacterGeneralStats() { msg.add(player->getOfflineTrainingTime() / 60 / 1000); msg.add(player->getSpeed()); msg.add(player->getBaseSpeed()); - msg.add(player->getCapacity()); - msg.add(player->getCapacity()); + msg.add(player->getBonusCapacity()); + msg.add(player->getBaseCapacity()); msg.add(player->hasFlag(PlayerFlags_t::HasInfiniteCapacity) ? 1000000 : player->getFreeCapacity()); msg.addByte(8); msg.addByte(1); @@ -3611,13 +3612,20 @@ void ProtocolGame::sendCyclopediaCharacterRecentDeaths(uint16_t page, uint16_t p msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_RECENTDEATHS); msg.addByte(0x00); - msg.add(page); - msg.add(pages); - msg.add(entries.size()); - for (const RecentDeathEntry &entry : entries) { - msg.add(entry.timestamp); - msg.addString(entry.cause, "ProtocolGame::sendCyclopediaCharacterRecentDeaths - entry.cause"); - } + + uint16_t totalPages = static_cast(std::ceil(static_cast(entries.size()) / pages)); + uint16_t currentPage = std::min(page, totalPages); + uint16_t firstObject = (currentPage - 1) * pages; + uint16_t finalObject = firstObject + pages; + + msg.add(currentPage); + msg.add(totalPages); + msg.add(pages); + for (uint16_t i = firstObject; i < finalObject; i++) { + RecentDeathEntry entry = entries[i]; + msg.add(entry.timestamp); + msg.addString(entry.cause, "ProtocolGame::sendCyclopediaCharacterRecentDeaths - entry.cause"); + } writeToOutputBuffer(msg); } @@ -3630,10 +3638,17 @@ void ProtocolGame::sendCyclopediaCharacterRecentPvPKills(uint16_t page, uint16_t msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_RECENTPVPKILLS); msg.addByte(0x00); - msg.add(page); - msg.add(pages); - msg.add(entries.size()); - for (const RecentPvPKillEntry &entry : entries) { + + uint16_t totalPages = static_cast(std::ceil(static_cast(entries.size()) / pages)); + uint16_t currentPage = std::min(page, totalPages); + uint16_t firstObject = (currentPage - 1) * pages; + uint16_t finalObject = firstObject + pages; + + msg.add(currentPage); + msg.add(totalPages); + msg.add(pages); + for (uint16_t i = firstObject; i < finalObject; i++) { + RecentPvPKillEntry entry = entries[i]; msg.add(entry.timestamp); msg.addString(entry.description, "ProtocolGame::sendCyclopediaCharacterRecentPvPKills - entry.description"); msg.addByte(entry.status); @@ -3837,7 +3852,24 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { msg.addByte(slot); msg.addString(inventoryItem->getName(), "ProtocolGame::sendCyclopediaCharacterInspection - inventoryItem->getName()"); AddItem(msg, inventoryItem); - msg.addByte(0); + + uint8_t itemImbuements = 0; + auto startImbuements = msg.getBufferPosition(); + msg.skipBytes(1); + for (uint8_t slotid = 0; slotid < inventoryItem->getImbuementSlot(); slotid++) { + ImbuementInfo imbuementInfo; + if (!inventoryItem->getImbuementInfo(slotid, &imbuementInfo)) { + continue; + } + + msg.add(imbuementInfo.imbuement->getID()); + itemImbuements++; + } + + auto endImbuements = msg.getBufferPosition(); + msg.setBufferPosition(startImbuements); + msg.addByte(itemImbuements); + msg.setBufferPosition(endImbuements); auto descriptions = Item::getDescriptions(Item::items[inventoryItem->getID()], inventoryItem); msg.addByte(descriptions.size()); @@ -3865,6 +3897,13 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { msg.addString("Vocation", "ProtocolGame::sendCyclopediaCharacterInspection - Vocation"); msg.addString(player->getVocation()->getVocName(), "ProtocolGame::sendCyclopediaCharacterInspection - player->getVocation()->getVocName()"); + // Player title + if (player->summary()->getCurrentTitle() != 0) { + playerDescriptionSize++; + msg.addString("Title", "ProtocolGame::sendCyclopediaCharacterInspection - Title"); + msg.addString(player->summary()->getCurrentTitleName(), "ProtocolGame::sendCyclopediaCharacterInspection - player->summary()->getCurrentTitleName()"); + } + // Loyalty title if (player->getLoyaltyTitle().length() != 0) { playerDescriptionSize++; @@ -3872,6 +3911,40 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { msg.addString(player->getLoyaltyTitle(), "ProtocolGame::sendCyclopediaCharacterInspection - player->getLoyaltyTitle()"); } + // todo: Prey description +// for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) { +// PreySlot* slot = player->getPreySlotById(static_cast(slotId)); +// if (slot && slot->isOccupied()) { +// playerDescriptionSize++; +// std::ostringstream ss; +// ss << "Active Prey " << (slotId + 1); +// msg.addString(ss.str(), "ProtocolGame::sendCyclopediaCharacterInspection - active prey"); +// ss.str(""); +// ss.clear(); +// +// if (auto mtype = g_monsters().getMonsterTypeByRaceId(slot->selectedRaceId)) { +// ss << mtype->name; +// } else { +// ss << "Unknown creature"; +// } +// +// if (slot->bonus == PreyBonus_Damage) { +// ss << " (Improved Damage +"; +// } else if (slot->bonus == PreyBonus_Defense) { +// ss << " (Improved Defense +"; +// } else if (slot->bonus == PreyBonus_Experience) { +// ss << " (Improved Experience +"; +// } else if (slot->bonus == PreyBonus_Loot) { +// ss << " (Improved Loot +"; +// } +// ss << slot->bonusPercentage << "%, remaining "; +// uint8_t hours = slot->bonusTimeLeft / 3600; +// uint8_t minutes = (slot->bonusTimeLeft - (hours * 3600)) / 60; +// ss << std::to_string(hours) << ":" << (minutes < 10 ? "0" : "") << std::to_string(minutes) << "h)"; +// msg.addString(ss.str(), "ProtocolGame::sendCyclopediaCharacterInspection - prey description"); +// } +// } + // Outfit description playerDescriptionSize++; msg.addString("Outfit", "ProtocolGame::sendCyclopediaCharacterInspection - Outfit"); @@ -3890,7 +3963,7 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { writeToOutputBuffer(msg); } -void ProtocolGame::sendCyclopediaCharacterBadges() { +void ProtocolGame::sendCyclopediaCharacterBadges(std::map badges) { if (!player || oldProtocol) { return; } @@ -3909,13 +3982,24 @@ void ProtocolGame::sendCyclopediaCharacterBadges() { msg.addByte(player->isPremium() ? 0x01 : 0x00); // Character loyalty title msg.addString(player->getLoyaltyTitle(), "ProtocolGame::sendCyclopediaCharacterBadges - player->getLoyaltyTitle()"); - // Enable badges - msg.addByte(0x00); - // Todo badges loop + + uint8_t badgesSize = 0; + auto badgesSizePosition = msg.getBufferPosition(); + msg.skipBytes(1); + for (const auto it : badges) { + if (player->summary()->hasBadge(it.first)) { + msg.add(it.first); + msg.addString(it.second, "ProtocolGame::sendCyclopediaCharacterBadges - name"); + badgesSize++; + } + } + msg.setBufferPosition(badgesSizePosition); + msg.addByte(badgesSize); + writeToOutputBuffer(msg); } -void ProtocolGame::sendCyclopediaCharacterTitles() { +void ProtocolGame::sendCyclopediaCharacterTitles(std::map titles) { if (!player || oldProtocol) { return; } @@ -3923,9 +4007,19 @@ void ProtocolGame::sendCyclopediaCharacterTitles() { NetworkMessage msg; msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_TITLES); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); + msg.addByte(0x00); // 0x00 Here means 'no error' + msg.addByte(player->summary()->getCurrentTitle()); + msg.addByte(static_cast(titles.size())); + std::string messageTitleName = "ProtocolGame::sendCyclopediaCharacterTitles - title.name"; + std::string messageTitleDesc = "ProtocolGame::sendCyclopediaCharacterTitles - title.description"; + for (const auto title : titles) { + msg.addByte(title.first); + msg.addString(player->getSex() == PLAYERSEX_MALE ? title.second.maleName : title.second.femaleName, messageTitleName); + msg.addString(title.second.description, messageTitleDesc); + msg.addByte(title.second.permanent ? 0x01 : 0x00); + msg.addByte(player->achiev()->isUnlocked(title.first) ? 0x01 : 0x00); + } + writeToOutputBuffer(msg); } diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index 306bb873f25..661431a1b2c 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -13,6 +13,7 @@ #include "creatures/interactions/chat.hpp" #include "creatures/creature.hpp" #include "enums/forge_conversion.hpp" +#include "creatures/players/cyclopedia/player_cyclopedia.hpp" class NetworkMessage; class Player; @@ -317,8 +318,8 @@ class ProtocolGame final : public Protocol { void sendCyclopediaCharacterOutfitsMounts(); void sendCyclopediaCharacterStoreSummary(); void sendCyclopediaCharacterInspection(); - void sendCyclopediaCharacterBadges(); - void sendCyclopediaCharacterTitles(); + void sendCyclopediaCharacterBadges(std::map badges); + void sendCyclopediaCharacterTitles(std::map titles); void sendCreatureWalkthrough(std::shared_ptr creature, bool walkthrough); void sendCreatureShield(std::shared_ptr creature); diff --git a/src/server/server_definitions.hpp b/src/server/server_definitions.hpp index ad816b76b02..0321c92c674 100644 --- a/src/server/server_definitions.hpp +++ b/src/server/server_definitions.hpp @@ -113,13 +113,14 @@ enum Supply_Stash_Actions_t : uint8_t { }; struct HighscoreCharacter { - HighscoreCharacter(std::string name, uint64_t points, uint32_t id, uint32_t rank, uint16_t level, uint8_t vocation) : + HighscoreCharacter(std::string name, uint64_t points, uint32_t id, uint32_t rank, uint16_t level, uint8_t vocation, uint8_t title) : name(std::move(name)), points(points), id(id), rank(rank), level(level), - vocation(vocation) { } + vocation(vocation), + title(title) { } std::string name; uint64_t points; @@ -127,4 +128,5 @@ struct HighscoreCharacter { uint32_t rank; uint16_t level; uint8_t vocation; + uint8_t title; }; diff --git a/src/utils/const.hpp b/src/utils/const.hpp index c3f82df7ff7..581fb36ff5f 100644 --- a/src/utils/const.hpp +++ b/src/utils/const.hpp @@ -21,6 +21,9 @@ static constexpr int32_t CHANNEL_GUILD = 0x00; static constexpr int32_t CHANNEL_PARTY = 0x01; static constexpr int32_t CHANNEL_PRIVATE = 0xFFFF; +// Highscore interval time to reload from database in seconds +static constexpr int64_t EVENT_HIGHSCORE_INTERVAL = 3600; + // This is in miliseconds static constexpr int32_t EVENT_IMBUEMENT_INTERVAL = 1000; static constexpr uint8_t IMBUEMENT_MAX_TIER = 3; diff --git a/vcproj/canary.vcxproj b/vcproj/canary.vcxproj index e31fdbad08a..2dc1627198b 100644 --- a/vcproj/canary.vcxproj +++ b/vcproj/canary.vcxproj @@ -45,6 +45,7 @@ + @@ -257,6 +258,7 @@ + @@ -595,4 +597,4 @@ - \ No newline at end of file +