From 5bb5d384209e1c808f63e969bb161638e0326bea Mon Sep 17 00:00:00 2001 From: Elson Costa Date: Mon, 15 Apr 2024 17:57:05 -0300 Subject: [PATCH] badge & title system: improvements and created talkactions to manager title e badges. summary: wip. friend system: wip. --- data/scripts/talkactions/god/manage_badge.lua | 33 ++++ data/scripts/talkactions/god/manage_title.lua | 68 +++++++ .../players/cyclopedia/player_badge.cpp | 19 +- .../players/cyclopedia/player_badge.hpp | 4 +- .../players/cyclopedia/player_cyclopedia.cpp | 183 +++++++----------- .../players/cyclopedia/player_cyclopedia.hpp | 76 ++++++-- .../players/cyclopedia/player_title.cpp | 41 ++-- .../players/cyclopedia/player_title.hpp | 11 +- src/creatures/players/player.cpp | 27 +-- src/creatures/players/player.hpp | 3 + src/game/game.cpp | 148 +++++++++++++- src/game/game.hpp | 124 +----------- .../creatures/player/player_functions.cpp | 33 +++- .../creatures/player/player_functions.hpp | 2 + src/server/network/protocol/protocolgame.cpp | 69 ++++--- src/server/network/protocol/protocolgame.hpp | 2 + 16 files changed, 505 insertions(+), 338 deletions(-) create mode 100644 data/scripts/talkactions/god/manage_badge.lua create mode 100644 data/scripts/talkactions/god/manage_title.lua diff --git a/data/scripts/talkactions/god/manage_badge.lua b/data/scripts/talkactions/god/manage_badge.lua new file mode 100644 index 00000000000..f2899a0d592 --- /dev/null +++ b/data/scripts/talkactions/god/manage_badge.lua @@ -0,0 +1,33 @@ +local addBadge = TalkAction("/addbadge") + +function addBadge.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + local split = param:split(",") + if not split[2] then + player:sendCancelMessage("Insufficient parameters. Usage: /addbadge playerName, badgeID") + return true + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return true + end + + -- Trim left + split[2] = split[2]:gsub("^%s*(.-)$", "%1") + local id = tonumber(split[2]) + target:addBadge(id) + return true +end + +addBadge:separator(" ") +addBadge:groupType("god") +addBadge:register() diff --git a/data/scripts/talkactions/god/manage_title.lua b/data/scripts/talkactions/god/manage_title.lua new file mode 100644 index 00000000000..cb4fb9acb29 --- /dev/null +++ b/data/scripts/talkactions/god/manage_title.lua @@ -0,0 +1,68 @@ +local addTitle = TalkAction("/addtitle") + +function addTitle.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + local split = param:split(",") + if not split[2] then + player:sendCancelMessage("Insufficient parameters. Usage: /addtitle playerName, badgeID") + return true + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return true + end + + -- Trim left + split[2] = split[2]:gsub("^%s*(.-)$", "%1") + local id = tonumber(split[2]) + target:addTitle(id) + return true +end + +addTitle:separator(" ") +addTitle:groupType("god") +addTitle:register() + +----------------------------------------- +local setTitle = TalkAction("/settitle") + +function setTitle.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + local split = param:split(",") + if not split[2] then + player:sendCancelMessage("Insufficient parameters. Usage: /settitle playerName, badgeID") + return true + end + + local target = Player(split[1]) + if not target then + player:sendCancelMessage("A player with that name is not online.") + return true + end + + -- Trim left + split[2] = split[2]:gsub("^%s*(.-)$", "%1") + local id = tonumber(split[2]) + target:setCurrentTitle(id) + return true +end + +setTitle:separator(" ") +setTitle:groupType("god") +setTitle:register() diff --git a/src/creatures/players/cyclopedia/player_badge.cpp b/src/creatures/players/cyclopedia/player_badge.cpp index 082efa45e94..c13832bdf56 100644 --- a/src/creatures/players/cyclopedia/player_badge.cpp +++ b/src/creatures/players/cyclopedia/player_badge.cpp @@ -24,7 +24,7 @@ bool PlayerBadge::hasBadge(uint8_t id) const { } if (auto it = std::find_if(m_badgesUnlocked.begin(), m_badgesUnlocked.end(), [id](auto badge_it) { - return badge_it.first == id; + return badge_it.first.m_id == id; }); it != m_badgesUnlocked.end()) { return true; @@ -38,36 +38,35 @@ bool PlayerBadge::add(uint8_t id, uint32_t timestamp /* = 0*/) { return false; } - const Badge &badge = g_game().getBadgeById(id); + const Badge &badge = g_game().getBadgeByIdOrName(id); if (badge.m_id == 0) { return false; } int toSaveTimeStamp = timestamp != 0 ? timestamp : (OTSYS_TIME() / 1000); - getUnlockedKV()->set(std::to_string(badge.m_id), toSaveTimeStamp); - m_badgesUnlocked.push_back({ badge.m_id, toSaveTimeStamp }); + getUnlockedKV()->set(badge.m_name, toSaveTimeStamp); + m_badgesUnlocked.push_back({ badge, toSaveTimeStamp }); m_badgesUnlocked.shrink_to_fit(); return true; } -std::vector> PlayerBadge::getUnlockedBadges() const { +std::vector> PlayerBadge::getUnlockedBadges() const { return m_badgesUnlocked; } void PlayerBadge::loadUnlockedBadges() { const auto &unlockedBadges = getUnlockedKV()->keys(); g_logger().debug("[{}] - Loading unlocked badges: {}", __FUNCTION__, unlockedBadges.size()); - for (const auto &strBadgeId : unlockedBadges) { - auto id = static_cast(std::stoul(strBadgeId)); - const Badge &badge = g_game().getBadgeById(static_cast(id)); + for (const auto &badgeName : unlockedBadges) { + const Badge &badge = g_game().getBadgeByIdOrName(0, badgeName); if (badge.m_id == 0) { - g_logger().error("[{}] - Badge {} not found.", __FUNCTION__, strBadgeId); + g_logger().error("[{}] - Badge {} not found.", __FUNCTION__, badgeName); continue; } g_logger().debug("[{}] - Badge {} found for player {}.", __FUNCTION__, badge.m_name, m_player.getName()); - m_badgesUnlocked.push_back({ badge.m_id, getUnlockedKV()->get(strBadgeId)->getNumber() }); + m_badgesUnlocked.push_back({ badge, getUnlockedKV()->get(badgeName)->getNumber() }); } } diff --git a/src/creatures/players/cyclopedia/player_badge.hpp b/src/creatures/players/cyclopedia/player_badge.hpp index 2bffab0764d..791fa2cecb5 100644 --- a/src/creatures/players/cyclopedia/player_badge.hpp +++ b/src/creatures/players/cyclopedia/player_badge.hpp @@ -43,13 +43,13 @@ class PlayerBadge { bool hasBadge(uint8_t id) const; bool add(uint8_t id, uint32_t timestamp = 0); - std::vector> getUnlockedBadges() const; + std::vector> getUnlockedBadges() const; void loadUnlockedBadges(); const std::shared_ptr &getUnlockedKV(); private: // {badge ID, time when it was unlocked} std::shared_ptr m_badgeUnlockedKV; - std::vector> m_badgesUnlocked; + std::vector> m_badgesUnlocked; Player &m_player; }; diff --git a/src/creatures/players/cyclopedia/player_cyclopedia.cpp b/src/creatures/players/cyclopedia/player_cyclopedia.cpp index 70e23fda12f..04ac4f1ec37 100644 --- a/src/creatures/players/cyclopedia/player_cyclopedia.cpp +++ b/src/creatures/players/cyclopedia/player_cyclopedia.cpp @@ -18,7 +18,66 @@ PlayerCyclopedia::PlayerCyclopedia(Player &player) : m_player(player) { } -// Death History +void PlayerCyclopedia::addXpBoostsObtained(uint16_t amount) { + m_storeXpBoosts += amount; +} + +void PlayerCyclopedia::addRewardCollectionObtained(uint16_t amount) { + m_dailyRewardCollections += amount; +} + +void PlayerCyclopedia::addHirelingsObtained(uint16_t amount) { + m_hirelings += amount; +} + +void PlayerCyclopedia::addPreyCardsObtained(uint16_t amount) { + m_preyCards += amount; +} + +void PlayerCyclopedia::addCharmsPointsObtained(uint16_t amount) { + m_charms += amount; +} + +void PlayerCyclopedia::addGoshnarTaintsObtained(uint16_t amount) { + m_goshnar += amount; +} + +void PlayerCyclopedia::addDromePointsObtained(uint16_t amount) { + m_drome += amount; +} + +void PlayerCyclopedia::addLoginStreak(uint16_t amount) { + m_loginStreak += amount; +} + +void PlayerCyclopedia::addTaskHuntingPointsObtained(uint16_t amount) { + m_taskHuntingPoints += amount; +} + +void PlayerCyclopedia::addMapAreaDiscoveredPercentage(uint16_t amount) { + m_mapAreaDiscoveredPercentage += amount; +} + +void PlayerCyclopedia::addHirelingOutfitObtained(uint16_t lookType) { + m_hirelingOutfits.push_back(lookType); +} + +void PlayerCyclopedia::addHirelingJobsObtained(uint8_t jobId) { + m_hirelingJobs.push_back(jobId); +} + +void PlayerCyclopedia::addBlessingsObtained(Blessings_t id, uint16_t amount) { + m_blessings[id] += amount; +} + +// void PlayerCyclopedia::addHouseItemsObtained(uint16_t itemId, uint32_t amount) { +// m_houseItems[itemId] += amount; +// } + +// std::map> PlayerCyclopedia::getAccountLevelVocation() const { +// return accountLevelSummary; +// } + std::vector PlayerCyclopedia::getDeathHistory() const { return m_deathHistory; } @@ -35,123 +94,15 @@ void PlayerCyclopedia::insertPvpKillOnHistory(std::string cause, uint32_t timest 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); +// const std::shared_ptr &PlayerTitle::getSummary(std::string &key) { +// return m_player.kv()->scoped("titles")->scoped("summary")->get(key); // } // -// void addHirelingJobsObtained(uint8_t jobId) { -// hirelingJobsObtained.push_back(jobId); +// uint16_t PlayerAchievement::getPoints() const { +// return m_player.kv()->scoped("achievements")->get("points")->getNumber(); // } // -// 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; +// void PlayerAchievement::addPoints(uint16_t toAddPoints) { +// auto oldPoints = getPoints(); +// m_player.kv()->scoped("achievements")->set("points", oldPoints + toAddPoints); // } diff --git a/src/creatures/players/cyclopedia/player_cyclopedia.hpp b/src/creatures/players/cyclopedia/player_cyclopedia.hpp index 5b190fb12c7..48f2e856011 100644 --- a/src/creatures/players/cyclopedia/player_cyclopedia.hpp +++ b/src/creatures/players/cyclopedia/player_cyclopedia.hpp @@ -12,34 +12,76 @@ class Player; class KV; +struct Summary { + uint16_t m_storeXpBoosts = 0; + uint16_t m_dailyRewardCollections = 0; + uint16_t m_hirelings = 0; + uint16_t m_preyCards = 0; + uint16_t m_charms = 0; + uint16_t m_goshnar = 0; + uint16_t m_drome = 0; + uint16_t m_loginStreak = 0; + uint16_t m_taskHuntingPoints = 0; + uint16_t m_mapAreaDiscoveredPercentage = 0; + + std::vector m_hirelingOutfits; + std::vector m_hirelingJobs; + std::map m_blessings; + + Summary(uint16_t mStoreXpBoosts, uint16_t mDailyRewardCollections, uint16_t mHirelings, uint16_t mPreyCards, uint16_t mCharms, uint16_t mGoshnar, uint16_t mDrome, uint16_t mLoginStreak, uint16_t mTaskHuntingPoints, uint16_t mMapAreaDiscoveredPercentage, const std::vector &mHirelingOutfits, const std::vector &mHirelingJobs, const std::map &mBlessings) : + m_storeXpBoosts(mStoreXpBoosts), m_dailyRewardCollections(mDailyRewardCollections), m_hirelings(mHirelings), m_preyCards(mPreyCards), m_charms(mCharms), m_goshnar(mGoshnar), m_drome(mDrome), m_loginStreak(mLoginStreak), m_taskHuntingPoints(mTaskHuntingPoints), m_mapAreaDiscoveredPercentage(mMapAreaDiscoveredPercentage), m_hirelingOutfits(mHirelingOutfits), m_hirelingJobs(mHirelingJobs), m_blessings(mBlessings) { } +}; + class PlayerCyclopedia { public: explicit PlayerCyclopedia(Player &player); + Summary loadSummary() { + return Summary(m_storeXpBoosts, m_dailyRewardCollections, m_hirelings, m_preyCards, m_charms, m_goshnar, m_drome, m_loginStreak, m_taskHuntingPoints, m_mapAreaDiscoveredPercentage, m_hirelingOutfits, m_hirelingJobs, m_blessings); + } + + void addXpBoostsObtained(uint16_t amount); + void addRewardCollectionObtained(uint16_t amount); + void addHirelingsObtained(uint16_t amount); + void addPreyCardsObtained(uint16_t amount); + void addCharmsPointsObtained(uint16_t amount); + void addGoshnarTaintsObtained(uint16_t amount); + void addDromePointsObtained(uint16_t amount); + void addLoginStreak(uint16_t amount); + void addTaskHuntingPointsObtained(uint16_t amount); + void addMapAreaDiscoveredPercentage(uint16_t amount); + + void addHirelingOutfitObtained(uint16_t lookType); + void addHirelingJobsObtained(uint8_t jobId); + void addBlessingsObtained(Blessings_t id, uint16_t amount); + // void addHouseItemsObtained(uint16_t itemId, uint32_t amount); + std::vector getDeathHistory() const; - std::vector getPvpKillsHistory() const; void insertDeathOnHistory(std::string cause, uint32_t timestamp); + std::vector getPvpKillsHistory() const; void insertPvpKillOnHistory(std::string cause, uint32_t timestamp, uint8_t status); private: - Player &m_player; + uint16_t m_storeXpBoosts = 0; + uint16_t m_dailyRewardCollections = 0; + uint16_t m_hirelings = 0; + uint16_t m_preyCards = 0; + uint16_t m_charms = 0; + uint16_t m_goshnar = 0; + uint16_t m_drome = 0; + uint16_t m_loginStreak = 0; + uint16_t m_taskHuntingPoints = 0; + uint16_t m_mapAreaDiscoveredPercentage = 0; - // 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_hirelingOutfits; + std::vector m_hirelingJobs; + std::map m_blessings; + + // StashItemList houseItems; + // std::map> m_accountLevelSummary; std::vector m_deathHistory; std::vector m_pvpKillsHistory; - // std::map> m_accountLevelSummary; + + Player &m_player; }; diff --git a/src/creatures/players/cyclopedia/player_title.cpp b/src/creatures/players/cyclopedia/player_title.cpp index 727bccc1f6e..69e0d5b9bf9 100644 --- a/src/creatures/players/cyclopedia/player_title.cpp +++ b/src/creatures/players/cyclopedia/player_title.cpp @@ -24,7 +24,7 @@ bool PlayerTitle::isTitleUnlocked(uint8_t id) const { } if (auto it = std::find_if(m_titlesUnlocked.begin(), m_titlesUnlocked.end(), [id](auto title_it) { - return title_it.first == id; + return title_it.first.m_id == id; }); it != m_titlesUnlocked.end()) { return true; @@ -38,24 +38,28 @@ bool PlayerTitle::add(uint8_t id, uint32_t timestamp /* = 0*/) { return false; } - const Title &title = g_game().getTitleById(id); + const Title &title = g_game().getTitleByIdOrName(id); if (title.m_id == 0) { return false; } int toSaveTimeStamp = timestamp != 0 ? timestamp : (OTSYS_TIME() / 1000); - getUnlockedKV()->set(std::to_string(title.m_id), toSaveTimeStamp); - m_titlesUnlocked.push_back({ title.m_id, toSaveTimeStamp }); + getUnlockedKV()->set(title.m_maleName, toSaveTimeStamp); + m_titlesUnlocked.push_back({ title, toSaveTimeStamp }); m_titlesUnlocked.shrink_to_fit(); return true; } -std::vector> PlayerTitle::getUnlockedTitles() const { +std::vector> PlayerTitle::getUnlockedTitles() const { return m_titlesUnlocked; } uint8_t PlayerTitle::getCurrentTitle() const { - return m_currentTitle; + return static_cast(m_player.kv()->scoped("titles")->get("current-title")->getNumber()); +} + +void PlayerTitle::setCurrentTitle(uint8_t id) { + m_player.kv()->scoped("titles")->set("current-title", id != 0 && isTitleUnlocked(id) ? id : 0); } std::string PlayerTitle::getCurrentTitleName() const { @@ -64,32 +68,27 @@ std::string PlayerTitle::getCurrentTitleName() const { return ""; } - auto title = g_game().getTitleById(currentTitle); - return (m_player.getSex() == PLAYERSEX_FEMALE && !title.m_femaleName.empty()) ? title.m_femaleName : title.m_maleName; -} - -void PlayerTitle::setCurrentTitle(uint8_t id) { - if (id != 0 && !isTitleUnlocked(id)) { - return; + auto title = g_game().getTitleByIdOrName(currentTitle); + if (title.m_id == 0) { + return ""; } - - m_currentTitle = id; + return (m_player.getSex() == PLAYERSEX_FEMALE && !title.m_femaleName.empty()) ? title.m_femaleName : title.m_maleName; } void PlayerTitle::loadUnlockedTitles() { const auto &unlockedTitles = getUnlockedKV()->keys(); g_logger().debug("[{}] - Loading unlocked titles: {}", __FUNCTION__, unlockedTitles.size()); - for (const auto &strTitleId : unlockedTitles) { - auto id = static_cast(std::stoul(strTitleId)); - const Title &title = g_game().getTitleById(id); + for (const auto &titleName : unlockedTitles) { + const Title &title = g_game().getTitleByIdOrName(0, titleName); if (title.m_id == 0) { - g_logger().error("[{}] - Title {} not found.", __FUNCTION__, strTitleId); + g_logger().error("[{}] - Title {} not found.", __FUNCTION__, titleName); continue; } - g_logger().debug("[{}] - Title {} found for player {}.", __FUNCTION__, title.m_maleName, m_player.getName()); + auto femaleLog = !title.m_femaleName.empty() ? ("/" + title.m_femaleName) : ""; + g_logger().debug("[{}] - Title {}{} found for player {}.", __FUNCTION__, title.m_maleName, femaleLog, m_player.getName()); - m_titlesUnlocked.push_back({ title.m_id, getUnlockedKV()->get(strTitleId)->getNumber() }); + m_titlesUnlocked.push_back({ title, getUnlockedKV()->get(titleName)->getNumber() }); } } diff --git a/src/creatures/players/cyclopedia/player_title.hpp b/src/creatures/players/cyclopedia/player_title.hpp index 17a6af6f9c0..bef11072a34 100644 --- a/src/creatures/players/cyclopedia/player_title.hpp +++ b/src/creatures/players/cyclopedia/player_title.hpp @@ -76,19 +76,18 @@ namespace std { class PlayerTitle { public: explicit PlayerTitle(Player &player); - bool isTitleUnlocked(uint8_t id) const; + [[nodiscard]] bool isTitleUnlocked(uint8_t id) const; bool add(uint8_t id, uint32_t timestamp = 0); - std::vector> getUnlockedTitles() const; - uint8_t getCurrentTitle() const; - std::string getCurrentTitleName() const; + [[nodiscard]] std::vector> getUnlockedTitles() const; + [[nodiscard]] uint8_t getCurrentTitle() const; void setCurrentTitle(uint8_t id); + [[nodiscard]] std::string getCurrentTitleName() const; void loadUnlockedTitles(); const std::shared_ptr &getUnlockedKV(); private: // {title ID, time when it was unlocked} std::shared_ptr m_titleUnlockedKV; - std::vector> m_titlesUnlocked; - uint8_t m_currentTitle; + std::vector> m_titlesUnlocked; Player &m_player; }; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index b67614976d5..d76312d0603 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -635,6 +635,19 @@ phmap::flat_hash_map> Player::getAllSlotItems() c return itemMap; } +phmap::flat_hash_map Player::getBlessingNames() const { + return { + { TWIST_OF_FATE, "Twist of Fate" }, + { WISDOM_OF_SOLITUDE, "The Wisdom of Solitude" }, + { SPARK_OF_THE_PHOENIX, "The Spark of the Phoenix" }, + { FIRE_OF_THE_SUNS, "The Fire of the Suns" }, + { SPIRITUAL_SHIELDING, "The Spiritual Shielding" }, + { EMBRACE_OF_TIBIA, "The Embrace of Tibia" }, + { BLOOD_OF_THE_MOUNTAIN, "Blood of the Mountain" }, + { HEARTH_OF_THE_MOUNTAIN, "Heart of the Mountain" }, + }; +} + void Player::setTraining(bool value) { for (const auto &[key, player] : g_game().getPlayers()) { if (!this->isInGhostMode() || player->isAccessPlayer()) { @@ -6167,6 +6180,8 @@ void Player::sendCyclopediaCharacterAchievements(uint16_t secretsUnlocked, std:: } } +// todo send titles function + uint64_t Player::getMoney() const { std::vector> containers; uint64_t moneyCount = 0; @@ -6609,17 +6624,6 @@ void Player::initializeTaskHunting() { } std::string Player::getBlessingsName() const { - static const phmap::flat_hash_map BlessingNames = { - { TWIST_OF_FATE, "Twist of Fate" }, - { WISDOM_OF_SOLITUDE, "The Wisdom of Solitude" }, - { SPARK_OF_THE_PHOENIX, "The Spark of the Phoenix" }, - { FIRE_OF_THE_SUNS, "The Fire of the Suns" }, - { SPIRITUAL_SHIELDING, "The Spiritual Shielding" }, - { EMBRACE_OF_TIBIA, "The Embrace of Tibia" }, - { BLOOD_OF_THE_MOUNTAIN, "Blood of the Mountain" }, - { HEARTH_OF_THE_MOUNTAIN, "Heart of the Mountain" }, - }; - uint8_t count = 0; std::for_each(blessings.begin(), blessings.end(), [&count](uint8_t amount) { if (amount != 0) { @@ -6627,6 +6631,7 @@ std::string Player::getBlessingsName() const { } }); + auto BlessingNames = getBlessingNames(); std::ostringstream os; for (uint8_t i = 1; i <= 8; i++) { if (hasBlessing(i)) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 81ae6c84c67..b0a69529164 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -2587,6 +2587,9 @@ class Player final : public Creature, public Cylinder, public Bankable { // This get all players slot items phmap::flat_hash_map> getAllSlotItems() const; + // This get all blessings + phmap::flat_hash_map getBlessingNames() const; + /** * @brief Get the equipped items of the player-> * @details This function returns a vector containing the items currently equipped by the player diff --git a/src/game/game.cpp b/src/game/game.cpp index bfc8f9cd4f9..f40352ff846 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -195,6 +195,118 @@ Game::Game() { wildcardTree = std::make_shared(false); + m_badges = { + Badge(1, CyclopediaBadgeType_t::ACCOUNT_AGE, "Fledegeling Hero", 1), + Badge(2, CyclopediaBadgeType_t::ACCOUNT_AGE, "Veteran Hero", 5), + Badge(3, CyclopediaBadgeType_t::ACCOUNT_AGE, "Senior Hero", 10), + Badge(4, CyclopediaBadgeType_t::ACCOUNT_AGE, "Ancient Hero", 15), + Badge(5, CyclopediaBadgeType_t::ACCOUNT_AGE, "Exalted Hero", 20), + + Badge(6, CyclopediaBadgeType_t::LOYALTY, "Tibia Loyalist (Grade 1)", 100), + Badge(7, CyclopediaBadgeType_t::LOYALTY, "Tibia Loyalist (Grade 2)", 1000), + Badge(8, CyclopediaBadgeType_t::LOYALTY, "Tibia Loyalist (Grade 3)", 5000), + + Badge(9, CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL, "Global Player (Grade 1)", 500), + Badge(10, CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL, "Global Player (Grade 2)", 1000), + Badge(11, CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL, "Global Player (Grade 3)", 2000), + + Badge(12, CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS, "Master Class (Grade 1)", 100), + Badge(13, CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS, "Master Class (Grade 2)", 250), + Badge(14, CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS, "Master Class (Grade 3)", 500), + + Badge(15, CyclopediaBadgeType_t::TOURNAMENT_PARTICIPATION, "Freshman of the Tournament", 1), + Badge(16, CyclopediaBadgeType_t::TOURNAMENT_PARTICIPATION, "Regular of the Tournament", 5), + Badge(17, CyclopediaBadgeType_t::TOURNAMENT_PARTICIPATION, "Hero of the Tournament", 10), + + Badge(18, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Competitor", 1000), + Badge(19, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Challenger", 2500), + Badge(20, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Master", 5000), + Badge(21, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Champion", 10000), + }; + + m_titles = { + Title(1, CyclopediaTitleType_t::GOLD, "Gold Hoarder", "Earned at least 1,000,000 gold.", 1000000, false), + Title(2, CyclopediaTitleType_t::GOLD, "Platinum Hoarder", "Earned at least 10,000,000 gold.", 10000000, false), + Title(3, CyclopediaTitleType_t::GOLD, "Crystal Hoarder", "Earned at least 100,000,000 gold.", 100000000, false), + + Title(4, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 1)", "Unlocked 10 or more Mounts.", 10, true), + Title(5, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 2)", "Unlocked 20 or more Mounts.", 20, true), + Title(6, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 3)", "Unlocked 30 or more Mounts.", 30, true), + Title(7, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 4)", "Unlocked 40 or more Mounts.", 40, true), + Title(8, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 5)", "Unlocked 50 or more Mounts.", 50, true), + + Title(9, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 1)", "Unlocked 10 or more Outfits.", 10, true), + Title(10, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 2)", "Unlocked 20 or more Outfits.", 20, true), + Title(11, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 3)", "Unlocked 30 or more Outfits.", 30, true), + Title(12, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 4)", "Unlocked 40 or more Outfits.", 40, true), + Title(13, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 5)", "Unlocked 50 or more Outfits.", 50, true), + + Title(14, CyclopediaTitleType_t::LEVEL, "Trolltrasher", "Reached level 50.", 50, false), + Title(15, CyclopediaTitleType_t::LEVEL, "Cyclopscamper", "Reached level 100.", 100, false), + Title(16, CyclopediaTitleType_t::LEVEL, "Dragondouser", "Reached level 200.", 200, false), + Title(17, CyclopediaTitleType_t::LEVEL, "Demondoom", "Reached level 300.", 300, false), + Title(18, CyclopediaTitleType_t::LEVEL, "Drakenbane", "Reached level 400.", 400, false), + Title(19, CyclopediaTitleType_t::LEVEL, "Silencer", "Reached level 500.", 500, false), + Title(20, CyclopediaTitleType_t::LEVEL, "Exalted", "Reached level 1000.", 1000, false), + + Title(21, CyclopediaTitleType_t::HIGHSCORES, "Legend of Fishing", "", "Highest fishing level on character's world.", static_cast(HighscoreCategories_t::FISHING)), + Title(22, CyclopediaTitleType_t::HIGHSCORES, "Legend of Magic", "", "Highest magic level on character's world.", static_cast(HighscoreCategories_t::MAGIC_LEVEL)), + Title(23, CyclopediaTitleType_t::HIGHSCORES, "Legend of Marksmanship", "", "Highest distance level on character's world.", static_cast(HighscoreCategories_t::DISTANCE_FIGHTING)), + Title(24, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Axe", "", "Highest axe level on character's world.", static_cast(HighscoreCategories_t::AXE_FIGHTING)), + Title(25, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Club", "", "Highest club level on character's world.", static_cast(HighscoreCategories_t::CLUB_FIGHTING)), + Title(26, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Fist", "", "Highest fist level on character's world.", static_cast(HighscoreCategories_t::FIST_FIGHTING)), + Title(27, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Shield", "", "Highest shielding level on character's world.", static_cast(HighscoreCategories_t::SHIELDING)), + Title(28, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Sword", "", "Highest sword level on character's world.", static_cast(HighscoreCategories_t::SWORD_FIGHTING)), + Title(29, CyclopediaTitleType_t::HIGHSCORES, "Apex Predator", "", "Highest Level on character's world.", static_cast(HighscoreCategories_t::EXPERIENCE)), + Title(30, CyclopediaTitleType_t::HIGHSCORES, "Prince Charming", "Princess Charming", "Highest score of accumulated charm points on character's world.", static_cast(HighscoreCategories_t::CHARMS)), + + Title(31, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_HUMANOID), "Bipedantic", "", "Unlocked All Humanoid Bestiary entries."), + Title(32, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_LYCANTHROPE), "Blood Moon Hunter", "Blood Moon Huntress", "Unlocked All Lycanthrope Bestiary entries."), + Title(33, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_AMPHIBIC), "Coldblooded", "", "Unlocked All Amphibic Bestiary entries."), + Title(34, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_BIRD), "Death from Below", "", "Unlocked all Bird Bestiary entries."), + Title(35, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_DEMON), "Demonator", "", "Unlocked all Demon Bestiary entries."), + Title(36, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_DRAGON), "Dragonslayer", "", "Unlocked all Dragon Bestiary entries."), + Title(37, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_ELEMENTAL), "Elementalist", "", "Unlocked all Elemental Bestiary entries."), + Title(38, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_VERMIN), "Exterminator", "", "Unlocked all Vermin Bestiary entries."), + Title(39, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_FEY), "Fey Swatter", "", "Unlocked all Fey Bestiary entries."), + Title(40, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_UNDEAD), "Ghosthunter", "Ghosthuntress", "Unlocked all Undead Bestiary entries."), + Title(41, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_CONSTRUCT), "Handyman", "Handywoman", "Unlocked all Construct Bestiary entries."), + Title(42, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_MAMMAL), "Huntsman", "Huntress", "Unlocked all Mammal Bestiary entries."), + Title(43, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_EXTRA_DIMENSIONAL), "Interdimensional Destroyer", "", "Unlocked all Extra Dimensional Bestiary entries."), + Title(44, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_HUMAN), "Manhunter", "Manhuntress", "Unlocked all Human Bestiary entries."), + Title(45, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_MAGICAL), "Master of Illusion", "Mistress of Illusion", "Unlocked all Magical Bestiary entries."), + Title(46, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_SLIME), "Ooze Blues", "", "Unlocked all Slime Bestiary entries."), + Title(47, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_AQUATIC), "Sea Bane", "", "Unlocked all Aquatic Bestiary entries."), + Title(48, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_REPTILE), "Snake Charmer", "", "Unlocked all Reptile Bestiary entries."), + Title(49, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_GIANT), "Tumbler", "", "Unlocked all Giant Bestiary entries."), + Title(50, CyclopediaTitleType_t::BESTIARY, static_cast(BestiaryType_t::BESTY_RACE_PLANT), "Weedkiller", "", "Unlocked all Plant Bestiary entries."), + Title(51, CyclopediaTitleType_t::BESTIARY, 0, "Executioner", "", "Unlocked all Bestiary entries."), + + Title(52, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 1)", "Reward Streak of at least 7 days of consecutive logins.", 7, true), + Title(53, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 2)", "Reward Streak of at least 30 days of consecutive logins.", 30, true), + Title(54, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 3)", "Reward Streak of at least 90 days of consecutive logins.", 90, true), + Title(55, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 4)", "Reward Streak of at least 180 days of consecutive logins.", 180, true), + Title(56, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 5)", "Reward Streak of at least 365 days of consecutive logins.", 365, true), + + Title(57, CyclopediaTitleType_t::TASK, "Aspiring Huntsman", "Invested 160,000 tasks points.", 160000, true, "Aspiring Huntswoman"), + Title(58, CyclopediaTitleType_t::TASK, "Competent Beastslayer", "Invested 320,000 tasks points.", 320000, true), + Title(59, CyclopediaTitleType_t::TASK, "Feared Bountyhunter", "Invested 430,000 tasks points.", 430000, true), + + Title(60, CyclopediaTitleType_t::MAP, "Dedicated Entrepreneur", "Explored 50% of all the map areas.", 50, false), + Title(61, CyclopediaTitleType_t::MAP, "Globetrotter", "Explored all map areas.", 100, false), + + Title(62, CyclopediaTitleType_t::QUEST, "Planegazer", "Followed the trail of the Planestrider to the end.", TitleStorage(1000, 1, false), true), + Title(63, CyclopediaTitleType_t::QUEST, "Hero of Bounac", "You prevailed during the battle of Bounac and broke the siege that held Bounac's people in its firm grasp.", TitleStorage(1000, 1, false), true), + Title(64, CyclopediaTitleType_t::QUEST, "Royal Bounacean Advisor", "Called to the court of Bounac by Kesar the Younger himself.", TitleStorage(1000, 1, false), true), + Title(65, CyclopediaTitleType_t::QUEST, "Time Traveller", "Anywhere in time or space.", TitleStorage(1000, 1, false), true), + + Title(66, CyclopediaTitleType_t::OTHERS, "Admirer of the Crown", "Adjust your crown and handle it.", true), + Title(67, CyclopediaTitleType_t::OTHERS, "Big Spender", "Unlocked the full Golden Outfit.", true), + Title(68, CyclopediaTitleType_t::OTHERS, "Guild Leader", "Leading a Guild.", true), + Title(69, CyclopediaTitleType_t::OTHERS, "Jack of all Taints", "Highest score for killing Goshnar and his aspects on character's world.", true), + Title(70, CyclopediaTitleType_t::OTHERS, "Reigning Drome Champion", "Finished most recent Tibiadrome rota ranked in the top 5.", true), + }; + m_highscoreCategoriesNames = { { static_cast(HighscoreCategories_t::ACHIEVEMENTS), "Achievement Points" }, { static_cast(HighscoreCategories_t::AXE_FIGHTING), "Axe Fighting" }, @@ -8107,6 +8219,19 @@ void Game::kickPlayer(uint32_t playerId, bool displayEffect) { player->removePlayer(displayEffect); } +void Game::playerFriendSystemAction(std::shared_ptr player, uint8_t type, uint8_t titleId) { + uint32_t playerGUID = player->getGUID(); + g_logger().info("ProtocolGame::parseFriendSystemAction 3"); + if (type == 0x0E) { + g_logger().info("ProtocolGame::parseFriendSystemAction 4"); + player->title()->setCurrentTitle(titleId); + player->sendCyclopediaCharacterBaseInformation(); + player->sendCyclopediaCharacterTitles(); + // player->sendCyclopediaCharacterTitles(player->title()->getUnlockedTitles()); + return; + } +} + void Game::playerCyclopediaCharacterInfo(std::shared_ptr player, uint32_t characterID, CyclopediaCharacterInfoType_t characterInfoType, uint16_t entriesPerPage, uint16_t page) { uint32_t playerGUID = player->getGUID(); if (characterID != playerGUID) { @@ -10503,28 +10628,35 @@ std::map Game::getAchievements() { // // } -Badge Game::getBadgeById(uint8_t id) { - auto it = std::find_if(m_badges.begin(), m_badges.end(), [id](const Badge &b) { - return b.m_id == id; +Badge Game::getBadgeByIdOrName(uint8_t id, const std::string &name /*= ""*/) { + if (id == 0 && name.empty()) { + return {}; + } + auto it = std::find_if(m_badges.begin(), m_badges.end(), [id, name](const Badge &b) { + return id != 0 ? b.m_id == id : b.m_name == name; }); if (it != m_badges.end()) { return *it; } - return Badge(); + return {}; } std::unordered_set Game::getBadges() { return m_badges; } -Title Game::getTitleById(uint8_t id) { - auto it = std::find_if(m_titles.begin(), m_titles.end(), [id](const Title &t) { - return t.m_id == id; +Title Game::getTitleByIdOrName(uint8_t id, const std::string &name /*= ""*/) { + if (id == 0 && name.empty()) { + return {}; + } + g_logger().info("title searched: id {}, name {}", id, name); + auto it = std::find_if(m_titles.begin(), m_titles.end(), [id, name](const Title &t) { + return id != 0 ? t.m_id == id : t.m_maleName == name; }); if (it != m_titles.end()) { return *it; } - return Title(); + return {}; } std::unordered_set Game::getTitles() { diff --git a/src/game/game.hpp b/src/game/game.hpp index 8da122fae78..0d4266e04f6 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -309,6 +309,8 @@ class Game { void playerReportRuleViolationReport(uint32_t playerId, const std::string &targetName, uint8_t reportType, uint8_t reportReason, const std::string &comment, const std::string &translation); + void playerFriendSystemAction(std::shared_ptr<Player> player, uint8_t type, uint8_t titleId); + void playerCyclopediaCharacterInfo(std::shared_ptr<Player> player, uint32_t characterID, CyclopediaCharacterInfoType_t characterInfoType, uint16_t entriesPerPage, uint16_t page); void playerHighscores(std::shared_ptr<Player> player, HighscoreType_t type, uint8_t category, uint32_t vocation, const std::string &worldName, uint16_t page, uint8_t entriesPerPage); @@ -721,132 +723,18 @@ class Game { std::vector<Achievement> getPublicAchievements(); std::map<uint16_t, Achievement> getAchievements(); - // void registerBadge(uint8_t id, std::string name, uint16_t amount); - Badge getBadgeById(uint8_t id); std::unordered_set<Badge> getBadges(); + Badge getBadgeByIdOrName(uint8_t id, const std::string &name = ""); - // void registerTitle(uint8_t id, std::string maleName, std::string femaleName, std::string type, std::string description, bool permanent); - Title getTitleById(uint8_t id); std::unordered_set<Title> getTitles(); + Title getTitleByIdOrName(uint8_t id, const std::string &name = ""); private: std::map<uint16_t, Achievement> m_achievements; std::map<std::string, uint16_t> m_achievementsNameToId; - std::unordered_set<Badge> m_badges = { - Badge(1, CyclopediaBadgeType_t::ACCOUNT_AGE, "Fledegeling Hero", 1), - Badge(2, CyclopediaBadgeType_t::ACCOUNT_AGE, "Veteran Hero", 5), - Badge(3, CyclopediaBadgeType_t::ACCOUNT_AGE, "Senior Hero", 10), - Badge(4, CyclopediaBadgeType_t::ACCOUNT_AGE, "Ancient Hero", 15), - Badge(5, CyclopediaBadgeType_t::ACCOUNT_AGE, "Exalted Hero", 20), - - Badge(6, CyclopediaBadgeType_t::LOYALTY, "Tibia Loyalist (Grade 1)", 100), - Badge(7, CyclopediaBadgeType_t::LOYALTY, "Tibia Loyalist (Grade 2)", 1000), - Badge(8, CyclopediaBadgeType_t::LOYALTY, "Tibia Loyalist (Grade 3)", 5000), - - Badge(9, CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL, "Global Player (Grade 1)", 500), - Badge(10, CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL, "Global Player (Grade 2)", 1000), - Badge(11, CyclopediaBadgeType_t::ACCOUNT_ALL_LEVEL, "Global Player (Grade 3)", 2000), - - Badge(12, CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS, "Master Class (Grade 1)", 100), - Badge(13, CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS, "Master Class (Grade 2)", 250), - Badge(14, CyclopediaBadgeType_t::ACCOUNT_ALL_VOCATIONS, "Master Class (Grade 3)", 500), - - Badge(15, CyclopediaBadgeType_t::TOURNAMENT_PARTICIPATION, "Freshman of the Tournament", 1), - Badge(16, CyclopediaBadgeType_t::TOURNAMENT_PARTICIPATION, "Regular of the Tournament", 5), - Badge(17, CyclopediaBadgeType_t::TOURNAMENT_PARTICIPATION, "Hero of the Tournament", 10), - - Badge(18, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Competitor", 1000), - Badge(19, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Challenger", 2500), - Badge(20, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Master", 5000), - Badge(21, CyclopediaBadgeType_t::TOURNAMENT_POINTS, "Tournament Champion", 10000), - }; - std::map<uint8_t, std::string> m_badgesNames; - - std::unordered_set<Title> m_titles = { - Title(1, CyclopediaTitleType_t::GOLD, "Gold Hoarder", "Earned at least 1,000,000 gold.", 1000000, false), - Title(2, CyclopediaTitleType_t::GOLD, "Platinum Hoarder", "Earned at least 10,000,000 gold.", 10000000, false), - Title(3, CyclopediaTitleType_t::GOLD, "Crystal Hoarder", "Earned at least 100,000,000 gold.", 100000000, false), - - Title(4, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 1)", "Unlocked 10 or more Mounts.", 10, true), - Title(5, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 2)", "Unlocked 20 or more Mounts.", 20, true), - Title(6, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 3)", "Unlocked 30 or more Mounts.", 30, true), - Title(7, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 4)", "Unlocked 40 or more Mounts.", 40, true), - Title(8, CyclopediaTitleType_t::MOUNTS, "Beaststrider (Grade 5)", "Unlocked 50 or more Mounts.", 50, true), - - Title(9, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 1)", "Unlocked 10 or more Outfits.", 10, true), - Title(10, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 2)", "Unlocked 20 or more Outfits.", 20, true), - Title(11, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 3)", "Unlocked 30 or more Outfits.", 30, true), - Title(12, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 4)", "Unlocked 40 or more Outfits.", 40, true), - Title(13, CyclopediaTitleType_t::OUTFITS, "Tibia's Topmodel (Grade 5)", "Unlocked 50 or more Outfits.", 50, true), - - Title(14, CyclopediaTitleType_t::LEVEL, "Trolltrasher", "Reached level 50.", 50, false), - Title(15, CyclopediaTitleType_t::LEVEL, "Cyclopscamper", "Reached level 100.", 100, false), - Title(16, CyclopediaTitleType_t::LEVEL, "Dragondouser", "Reached level 200.", 200, false), - Title(17, CyclopediaTitleType_t::LEVEL, "Demondoom", "Reached level 300.", 300, false), - Title(18, CyclopediaTitleType_t::LEVEL, "Drakenbane", "Reached level 400.", 400, false), - Title(19, CyclopediaTitleType_t::LEVEL, "Silencer", "Reached level 500.", 500, false), - Title(20, CyclopediaTitleType_t::LEVEL, "Exalted", "Reached level 1000.", 1000, false), - - Title(21, CyclopediaTitleType_t::HIGHSCORES, "Legend of Fishing", "", "Highest fishing level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::FISHING)), - Title(22, CyclopediaTitleType_t::HIGHSCORES, "Legend of Magic", "", "Highest magic level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::MAGIC_LEVEL)), - Title(23, CyclopediaTitleType_t::HIGHSCORES, "Legend of Marksmanship", "", "Highest distance level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::DISTANCE_FIGHTING)), - Title(24, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Axe", "", "Highest axe level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::AXE_FIGHTING)), - Title(25, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Club", "", "Highest club level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::CLUB_FIGHTING)), - Title(26, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Fist", "", "Highest fist level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::FIST_FIGHTING)), - Title(27, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Shield", "", "Highest shielding level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::SHIELDING)), - Title(28, CyclopediaTitleType_t::HIGHSCORES, "Legend of the Sword", "", "Highest sword level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::SWORD_FIGHTING)), - Title(29, CyclopediaTitleType_t::HIGHSCORES, "Apex Predator", "", "Highest Level on character's world.", static_cast<uint8_t>(HighscoreCategories_t::EXPERIENCE)), - Title(30, CyclopediaTitleType_t::HIGHSCORES, "Prince Charming", "Princess Charming", "Highest score of accumulated charm points on character's world.", static_cast<uint8_t>(HighscoreCategories_t::CHARMS)), - - Title(31, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_HUMANOID), "Bipedantic", "", "Unlocked All Humanoid Bestiary entries."), - Title(32, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_LYCANTHROPE), "Blood Moon Hunter", "Blood Moon Huntress", "Unlocked All Lycanthrope Bestiary entries."), - Title(33, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_AMPHIBIC), "Coldblooded", "", "Unlocked All Amphibic Bestiary entries."), - Title(34, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_BIRD), "Death from Below", "", "Unlocked all Bird Bestiary entries."), - Title(35, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_DEMON), "Demonator", "", "Unlocked all Demon Bestiary entries."), - Title(36, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_DRAGON), "Dragonslayer", "", "Unlocked all Dragon Bestiary entries."), - Title(37, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_ELEMENTAL), "Elementalist", "", "Unlocked all Elemental Bestiary entries."), - Title(38, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_VERMIN), "Exterminator", "", "Unlocked all Vermin Bestiary entries."), - Title(39, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_FEY), "Fey Swatter", "", "Unlocked all Fey Bestiary entries."), - Title(40, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_UNDEAD), "Ghosthunter", "Ghosthuntress", "Unlocked all Undead Bestiary entries."), - Title(41, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_CONSTRUCT), "Handyman", "Handywoman", "Unlocked all Construct Bestiary entries."), - Title(42, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_MAMMAL), "Huntsman", "Huntress", "Unlocked all Mammal Bestiary entries."), - Title(43, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_EXTRA_DIMENSIONAL), "Interdimensional Destroyer", "", "Unlocked all Extra Dimensional Bestiary entries."), - Title(44, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_HUMAN), "Manhunter", "Manhuntress", "Unlocked all Human Bestiary entries."), - Title(45, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_MAGICAL), "Master of Illusion", "Mistress of Illusion", "Unlocked all Magical Bestiary entries."), - Title(46, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_SLIME), "Ooze Blues", "", "Unlocked all Slime Bestiary entries."), - Title(47, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_AQUATIC), "Sea Bane", "", "Unlocked all Aquatic Bestiary entries."), - Title(48, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_REPTILE), "Snake Charmer", "", "Unlocked all Reptile Bestiary entries."), - Title(49, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_GIANT), "Tumbler", "", "Unlocked all Giant Bestiary entries."), - Title(50, CyclopediaTitleType_t::BESTIARY, static_cast<uint16_t>(BestiaryType_t::BESTY_RACE_PLANT), "Weedkiller", "", "Unlocked all Plant Bestiary entries."), - Title(51, CyclopediaTitleType_t::BESTIARY, 0, "Executioner", "", "Unlocked all Bestiary entries."), - - Title(52, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 1)", "Reward Streak of at least 7 days of consecutive logins.", 7, true), - Title(53, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 2)", "Reward Streak of at least 30 days of consecutive logins.", 30, true), - Title(54, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 3)", "Reward Streak of at least 90 days of consecutive logins.", 90, true), - Title(55, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 4)", "Reward Streak of at least 180 days of consecutive logins.", 180, true), - Title(56, CyclopediaTitleType_t::LOGIN, "Creature of Habit (Grade 5)", "Reward Streak of at least 365 days of consecutive logins.", 365, true), - - Title(57, CyclopediaTitleType_t::TASK, "Aspiring Huntsman", "Invested 160,000 tasks points.", 160000, true, "Aspiring Huntswoman"), - Title(58, CyclopediaTitleType_t::TASK, "Competent Beastslayer", "Invested 320,000 tasks points.", 320000, true), - Title(59, CyclopediaTitleType_t::TASK, "Feared Bountyhunter", "Invested 430,000 tasks points.", 430000, true), - - Title(60, CyclopediaTitleType_t::MAP, "Dedicated Entrepreneur", "Explored 50% of all the map areas.", 50, false), - Title(61, CyclopediaTitleType_t::MAP, "Globetrotter", "Explored all map areas.", 100, false), - - Title(62, CyclopediaTitleType_t::QUEST, "Planegazer", "Followed the trail of the Planestrider to the end.", TitleStorage(1000, 1, false), true), - Title(63, CyclopediaTitleType_t::QUEST, "Hero of Bounac", "You prevailed during the battle of Bounac and broke the siege that held Bounac's people in its firm grasp.", TitleStorage(1000, 1, false), true), - Title(64, CyclopediaTitleType_t::QUEST, "Royal Bounacean Advisor", "Called to the court of Bounac by Kesar the Younger himself.", TitleStorage(1000, 1, false), true), - Title(65, CyclopediaTitleType_t::QUEST, "Time Traveller", "Anywhere in time or space.", TitleStorage(1000, 1, false), true), - - Title(66, CyclopediaTitleType_t::OTHERS, "Admirer of the Crown", "Adjust your crown and handle it.", true), - Title(67, CyclopediaTitleType_t::OTHERS, "Big Spender", "Unlocked the full Golden Outfit.", true), - Title(68, CyclopediaTitleType_t::OTHERS, "Guild Leader", "Leading a Guild.", true), - Title(69, CyclopediaTitleType_t::OTHERS, "Jack of all Taints", "Highest score for killing Goshnar and his aspects on character's world.", true), - Title(70, CyclopediaTitleType_t::OTHERS, "Reigning Drome Champion", "Finished most recent Tibiadrome rota ranked in the top 5.", true), - - }; - std::map<uint8_t, std::string> m_titlesNames; + std::unordered_set<Badge> m_badges; + std::unordered_set<Title> m_titles; std::vector<HighscoreCategory> m_highscoreCategories; std::unordered_map<uint8_t, std::string> m_highscoreCategoriesNames; diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 16b071c6458..7ec9fa517aa 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -4317,12 +4317,33 @@ int PlayerFunctions::luaPlayerGetTitles(lua_State* L) { return 1; } - // if (auto item = player->title()->getUnlockedTitles()) { - // pushUserdata<Item>(L, item); - // setItemMetatable(L, -1, item); - // } else { - // pushBoolean(L, false); - // } + auto playerTitles = player->title()->getUnlockedTitles(); + lua_createtable(L, static_cast<int>(playerTitles.size()), 0); + + int index = 0; + for (auto title : playerTitles) { + lua_pushnumber(L, title.first.m_id); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int PlayerFunctions::luaPlayerSetCurrentTitle(lua_State* L) { + // player:setCurrentTitle(id) + const auto &player = getUserdataShared<Player>(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + const auto &title = g_game().getTitleByIdOrName(getNumber<uint8_t>(L, 2, 0)); + if (title.m_id == 0) { + reportErrorFunc(getErrorDesc(LUA_ERROR_VARIANT_NOT_FOUND)); + return 1; + } + + player->title()->setCurrentTitle(title.m_id); + pushBoolean(L, true); return 1; } diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 1ac135e5cbc..e638bed91a2 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -369,6 +369,7 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "addBadge", PlayerFunctions::luaPlayerAddBadge); registerMethod(L, "Player", "addTitle", PlayerFunctions::luaPlayerAddTitle); registerMethod(L, "Player", "getTitles", PlayerFunctions::luaPlayerGetTitles); + registerMethod(L, "Player", "setCurrentTitle", PlayerFunctions::luaPlayerSetCurrentTitle); // registerMethod(L, "Player", "addHirelingOutfitObtained", PlayerFunctions::luaPlayerAddHirelingOutfitObtained); // registerMethod(L, "Player", "addHirelingJobsObtained", PlayerFunctions::luaPlayerAddHirelingJobsObtained); @@ -746,6 +747,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerAddBadge(lua_State* L); static int luaPlayerAddTitle(lua_State* L); static int luaPlayerGetTitles(lua_State* L); + static int luaPlayerSetCurrentTitle(lua_State* L); // static int luaPlayerAddHirelingOutfitObtained(lua_State* L); // static int luaPlayerAddHirelingJobsObtained(lua_State* L); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index d45247f9a35..c4d4ec55ba5 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -1093,6 +1093,9 @@ void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyt case 0x80: g_game().playerCloseTrade(player->getID()); break; + case 0x81: + parseFriendSystemAction(msg); + break; case 0x82: parseUseItem(msg); break; @@ -2054,6 +2057,16 @@ void ProtocolGame::sendItemInspection(uint16_t itemId, uint8_t itemCount, std::s writeToOutputBuffer(msg); } +void ProtocolGame::parseFriendSystemAction(NetworkMessage &msg) { + g_logger().info("ProtocolGame::parseFriendSystemAction 1"); + uint8_t state = msg.getByte(); + if (state == 0x0E) { + g_logger().info("ProtocolGame::parseFriendSystemAction 2"); + uint8_t titleId = msg.getByte(); + g_game().playerFriendSystemAction(player, state, titleId); + } +} + void ProtocolGame::parseCyclopediaCharacterInfo(NetworkMessage &msg) { if (oldProtocol) { return; @@ -3387,7 +3400,8 @@ void ProtocolGame::sendCyclopediaCharacterGeneralStats() { // 1: No data available at the moment. // 2: You are not allowed to see this character's data. // 3: You are not allowed to inspect this character. - msg.addByte(0x00); + msg.addByte(0x00); // 0x00 Here means 'no error' + msg.add<uint64_t>(player->getExperience()); msg.add<uint16_t>(player->getLevel()); msg.addByte(player->getLevelPercent()); @@ -3397,6 +3411,7 @@ void ProtocolGame::sendCyclopediaCharacterGeneralStats() { msg.add<uint16_t>(player->getStaminaXpBoost()); // StaminaMultiplier(100=x1.0) msg.add<uint16_t>(player->getExpBoostStamina()); // xpBoostRemainingTime msg.addByte(player->getExpBoostStamina() > 0 ? 0x00 : 0x01); // canBuyStoreXpBoost + msg.add<uint32_t>(std::min<int32_t>(player->getHealth(), std::numeric_limits<uint16_t>::max())); msg.add<uint32_t>(std::min<int32_t>(player->getMaxHealth(), std::numeric_limits<uint16_t>::max())); msg.add<uint32_t>(std::min<int32_t>(player->getMana(), std::numeric_limits<uint16_t>::max())); @@ -3695,13 +3710,13 @@ void ProtocolGame::sendCyclopediaCharacterItemSummary() { NetworkMessage msg; msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_ITEMSUMMARY); - msg.addByte(0x00); + msg.addByte(0x00); // 0x00 Here means 'no error' - msg.add<uint16_t>(0); - msg.add<uint16_t>(0); - msg.add<uint16_t>(0); - msg.add<uint16_t>(0); - msg.add<uint16_t>(0); + msg.add<uint16_t>(0); // inventoryItems.size() + msg.add<uint16_t>(0); // storeInboxItems.size() + msg.add<uint16_t>(0); // supplyStashItems.size() + msg.add<uint16_t>(0); // depotBoxItems.size() + msg.add<uint16_t>(0); // inboxItems.size() writeToOutputBuffer(msg); } @@ -3817,20 +3832,27 @@ void ProtocolGame::sendCyclopediaCharacterStoreSummary() { NetworkMessage msg; msg.addByte(0xDA); msg.addByte(CYCLOPEDIA_CHARACTERINFO_STORESUMMARY); - msg.addByte(0x00); - // Remaining Store Xp Boost Time - msg.add<uint32_t>(player->getExpBoostStamina()); - // RemainingDailyRewardXpBoostTime - msg.add<uint32_t>(0); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); - msg.addByte(0x00); - msg.add<uint16_t>(0); + msg.addByte(0x00); // 0x00 Here means 'no error' + msg.add<uint32_t>(player->getExpBoostStamina()); // Remaining Store Xp Boost Time + msg.add<uint32_t>(0); // RemainingDailyRewardXpBoostTime + + auto cyclopediaSummary = player->cyclopedia()->loadSummary(); + + auto blessings = player->getBlessingNames(); + msg.addByte(static_cast<uint8_t>(blessings.size())); // getBlessingsObtained + for (const auto &it : blessings) { + msg.addString(it.second, "ProtocolGame::sendCyclopediaCharacterStoreSummary - blessing.name"); + msg.addByte(static_cast<uint8_t>((cyclopediaSummary.m_blessings)[it.first])); + } + + msg.addByte(0x00); // getTaskHuntingSlotById + msg.addByte(0x00); // getPreyCardsObtained + msg.addByte(0x00); // getRewardCollectionObtained + msg.addByte(0x00); // player->hasCharmExpansion() ? 0x01 : 0x00 + msg.addByte(0x00); // getHirelingsObtained + msg.addByte(0x00); // getHirelinsJobsObtained + msg.addByte(0x00); // getHirelinsOutfitsObtained + msg.add<uint16_t>(0); // getHouseItemsObtained writeToOutputBuffer(msg); } @@ -3907,7 +3929,7 @@ void ProtocolGame::sendCyclopediaCharacterInspection() { } // Loyalty title - if (player->getLoyaltyTitle().length() != 0) { + if (!player->getLoyaltyTitle().empty()) { playerDescriptionSize++; msg.addString("Loyalty Title", "ProtocolGame::sendCyclopediaCharacterInspection - Loyalty Title"); msg.addString(player->getLoyaltyTitle(), "ProtocolGame::sendCyclopediaCharacterInspection - player->getLoyaltyTitle()"); @@ -4005,7 +4027,7 @@ void ProtocolGame::sendCyclopediaCharacterTitles() { if (!player || oldProtocol) { return; } - + g_logger().info("ProtocolGame::addBytes - sendCyclopediaCharacterTitles"); auto titles = g_game().getTitles(); g_logger().info("ProtocolGame::sendCyclopediaCharacterTitles - titles size: {}", titles.size()); @@ -4024,6 +4046,7 @@ void ProtocolGame::sendCyclopediaCharacterTitles() { msg.addString(title.m_description, messageTitleDesc); msg.addByte(title.m_permanent ? 0x01 : 0x00); msg.addByte(player->title()->isTitleUnlocked(title.m_id) ? 0x01 : 0x00); + g_logger().debug("ProtocolGame::addBytes - titles: {}, unlocked: {}", title.m_id, player->title()->isTitleUnlocked(title.m_id)); } writeToOutputBuffer(msg); } diff --git a/src/server/network/protocol/protocolgame.hpp b/src/server/network/protocol/protocolgame.hpp index 889bfaab595..411b29e95fd 100644 --- a/src/server/network/protocol/protocolgame.hpp +++ b/src/server/network/protocol/protocolgame.hpp @@ -131,6 +131,8 @@ class ProtocolGame final : public Protocol { void sendItemInspection(uint16_t itemId, uint8_t itemCount, std::shared_ptr<Item> item, bool cyclopedia); void parseInspectionObject(NetworkMessage &msg); + void parseFriendSystemAction(NetworkMessage &msg); + void parseCyclopediaCharacterInfo(NetworkMessage &msg); void parseHighscores(NetworkMessage &msg);