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..6d33de95390 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..49e87c0a999 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..ad01787583a 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..dd6a69215ae --- /dev/null +++ b/src/creatures/players/cyclopedia/player_cyclopedia.cpp @@ -0,0 +1,223 @@ +/** + * 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..9062eb57d7c --- /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..345f43e99a5 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..03a0100e13a 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..c465169bd2d 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..a32d5aa4779 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..c400d3fc7aa 100644 --- a/src/lua/functions/core/game/game_functions.hpp +++ b/src/lua/functions/core/game/game_functions.hpp @@ -88,6 +88,9 @@ 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: @@ -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..11eae040cef 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..0a5563542ba 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -364,6 +364,30 @@ 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 +742,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..39103dd19fe 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,10 +3612,17 @@ void ProtocolGame::sendCyclopediaCharacterRecentDeaths(uint16_t page, uint16_t p msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_RECENTDEATHS); msg.addByte(0x00); - msg.add(page); + + 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); - msg.add(entries.size()); - for (const RecentDeathEntry &entry : entries) { + for (uint16_t i = firstObject; i < finalObject; i++) { + RecentDeathEntry entry = entries[i]; msg.add(entry.timestamp); msg.addString(entry.cause, "ProtocolGame::sendCyclopediaCharacterRecentDeaths - entry.cause"); } @@ -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); + + 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); - msg.add(entries.size()); - for (const RecentPvPKillEntry &entry : entries) { + 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..bb9510dd107 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..151f0e2e23b 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 +