diff --git a/data-global/npc/a_prisoner.lua b/data-global/npc/a_prisoner.lua index b04fd035..d02bca6a 100644 --- a/data-global/npc/a_prisoner.lua +++ b/data-global/npc/a_prisoner.lua @@ -101,6 +101,7 @@ local function creatureSayCallback(npc, creature, type, message) elseif MsgContains(message, "no") then npcHandler:say("Then go away!", npc, creature) end + -- The paradox tower quest if MsgContains(message, "math") then if player:getStorageValue(Storage.Quest.U7_24.TheParadoxTower.Mathemagics) < 1 then @@ -132,6 +133,33 @@ local function creatureSayCallback(npc, creature, type, message) npcHandler:setTopic(playerId, 0) end end + + -- Sell rune + if MsgContains(message, "sell rune") then + npcHandler:say("You want to sell me blank runes! I will give you 50000 gold for each rune! Interested?", npc, creature) + npcHandler:setTopic(playerId, 9) + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 9 then + npcHandler:say("Ok. Take my money. I can summon new money anytime - hehehe.", npc, creature) + npcHandler:setTopic(playerId, 0) + end + + -- Apple topic + if MsgContains(message, "apple") then + npcHandler:say("Apples! Real apples! Man I love them! Can I have one? Oh please say yes!", npc, creature) + npcHandler:setTopic(playerId, 10) + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 10 then + if player:removeItem(3585, 1) then + npcHandler:say("Mnjam. Excellent! Thanks, man!", npc, creature) + npcHandler:setTopic(playerId, 0) + else + npcHandler:say("Do you want to trick me? You don't have one lousy apple!", npc, creature) + npcHandler:setTopic(playerId, 0) + end + elseif MsgContains(message, "no") and npcHandler:getTopic(playerId) == 10 then + npcHandler:say("Ooooooooooo.", npc, creature) + npcHandler:setTopic(playerId, 0) + end + return true end @@ -141,6 +169,28 @@ npcHandler:setMessage(MESSAGE_GREET, "Huh? What? I can see! Wow! A non-mino. Did npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) +keywordHandler:addKeyword({ "name" }, StdModule.say, { npcHandler = npcHandler, text = "My name is - uhm - hang on? I knew it yesterday, didn't I? Doesn't matter!" }) +keywordHandler:addKeyword({ "capture" }, StdModule.say, { npcHandler = npcHandler, text = "Yes, they capture people. I guess that's their {job}." }) +keywordHandler:addKeyword({ "job" }, StdModule.say, { npcHandler = npcHandler, text = "Job? JOB? Hey man - I am in prison! But you know - once upon a time - I was a powerful mage! A mage ... come to think of it .., what is that - a mage?" }) +keywordHandler:addKeyword({ "mad mage" }, StdModule.say, { npcHandler = npcHandler, text = "Hey! That's me! You got it! Thanks mate - now I remember my name!" }) +keywordHandler:addKeyword({ "sorcerer" }, StdModule.say, { npcHandler = npcHandler, text = "I am the mightiest sorcerer from here to there! Yeah!" }) +keywordHandler:addKeyword({ "power" }, StdModule.say, { npcHandler = npcHandler, text = "Power. Hmmm. Once while we were crossing the mountains together a man named Aureus said to me that parcels are equal to power. Any idea what that meant?" }) +keywordHandler:addKeyword({ "books" }, StdModule.say, { npcHandler = npcHandler, text = "I have many books in my home. But only powerful people can read them. I bet you will only see three dots after the headline! Hehehe! Hahaha! Excellent!" }) +keywordHandler:addKeyword({ "time" }, StdModule.say, { npcHandler = npcHandler, text = "Better save time than committing a crime. I am a poet and I know it!" }) +keywordHandler:addKeyword({ "key" }, StdModule.say, { npcHandler = npcHandler, text = "Sure I have the key! Hehehe! Perhaps I will give it to you. IF you can solve my {riddle}." }) +keywordHandler:addKeyword({ "something" }, StdModule.say, { npcHandler = npcHandler, text = "No! I won't tell you. Shame coz it would be useful for you - hehehe." }) +keywordHandler:addKeyword({ "escape" }, StdModule.say, { npcHandler = npcHandler, text = "How could I escape? They only give me rotten food here. I can´t regain my powers because I have no mana!" }) +keywordHandler:addKeyword({ "labyrinth" }, StdModule.say, { npcHandler = npcHandler, text = "It's easy to find your way through it! Just follow the pools of mud. Hehe - useful hint, isn´t it?" }) +keywordHandler:addKeyword({ "way" }, StdModule.say, { npcHandler = npcHandler, text = "It's easy to find your way through it! Just follow the pools of mud. Hehe - useful hint, isn´t it?." }) +keywordHandler:addKeyword({ "mino" }, StdModule.say, { npcHandler = npcHandler, text = "They are trying to capture me! Or hang on! Haven't they already captured me? Hmmm - I will have to think about this." }) +keywordHandler:addKeyword({ "Markwin" }, StdModule.say, { npcHandler = npcHandler, text = "He is the worst of them all! He is the king of the minos! May he burn in hell!" }) +keywordHandler:addKeyword({ "Palkar" }, StdModule.say, { npcHandler = npcHandler, text = "He is the leader of the outcasts. I hope he will never conquer the city of Mintwallin. That would be the end of me!" }) +keywordHandler:addKeyword({ "Karl" }, StdModule.say, { npcHandler = npcHandler, text = "Tataah!" }) +keywordHandler:addKeyword({ "demon" }, StdModule.say, { npcHandler = npcHandler, text = "The only monster I cannot conjure. But soon I will be powerful enough!" }) +keywordHandler:addKeyword({ "monster" }, StdModule.say, { npcHandler = npcHandler, text = "Yeah! There are many monsters guarding my home. Only the bravest hero will be able to slay them!" }) +keywordHandler:addKeyword({ "conjure" }, StdModule.say, { npcHandler = npcHandler, text = "Yeah! There are many monsters guarding my home. Only the bravest hero will be able to slay them!" }) +keywordHandler:addKeyword({ "home" }, StdModule.say, { npcHandler = npcHandler, text = "Yeah! There are many monsters guarding my home. Only the bravest hero will be able to slay them!." }) + npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) -- npcType registering the npcConfig table diff --git a/data/scripts/actions/items/wheel_scrolls.lua b/data/scripts/actions/items/wheel_scrolls.lua index 61aaa6fe..083c6e80 100644 --- a/data/scripts/actions/items/wheel_scrolls.lua +++ b/data/scripts/actions/items/wheel_scrolls.lua @@ -15,13 +15,11 @@ function scroll.onUse(player, item, fromPosition, target, toPosition, isHotkey) end local scrollData = promotionScrolls[item:getId()] - local scrollKV = player:kv():scoped("wheel-of-destiny"):scoped("scrolls") - if scrollKV:get(scrollData.name) then + if not player:wheelUnlockScroll(scrollData.name) then player:sendTextMessage(MESSAGE_LOOK, "You have already deciphered this scroll.") return true end - scrollKV:set(scrollData.name, true) player:sendTextMessage(MESSAGE_LOOK, "You have gained " .. scrollData.points .. " promotion points for the Wheel of Destiny by deciphering the " .. scrollData.itemName .. ".") item:remove(1) return true diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index 2d848ada..972c6512 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -828,7 +828,7 @@ void Npc::removeShopPlayer(uint32_t playerGUID) { } void Npc::closeAllShopWindows() { - for (const auto &playerGUID : shopPlayers | std::views::keys) { + for (const auto playerGUID : shopPlayers | std::views::keys) { const auto &player = g_game().getPlayerByGUID(playerGUID); if (player) { player->closeShopWindow(); diff --git a/src/creatures/players/wheel/player_wheel.cpp b/src/creatures/players/wheel/player_wheel.cpp index 9d348591..fce25215 100644 --- a/src/creatures/players/wheel/player_wheel.cpp +++ b/src/creatures/players/wheel/player_wheel.cpp @@ -33,6 +33,8 @@ #include "server/network/message/networkmessage.hpp" #include "server/network/protocol/protocolgame.hpp" +static PlayerWheelGem emptyGem = {}; + std::array m_resistance = { 0 }; const static std::vector wheelGemBasicSlot1Allowed = { @@ -313,12 +315,6 @@ namespace { return 0; } - struct PromotionScroll { - uint16_t itemId; - std::string name; - uint8_t extraPoints; - }; - std::vector WheelOfDestinyPromotionScrolls = { { 43946, "abridged", 3 }, { 43947, "basic", 5 }, @@ -860,36 +856,7 @@ uint16_t PlayerWheel::getUnusedPoints() const { return 0; } - const auto vocationBaseId = m_player.getVocation()->getBaseId(); - const auto modsSupremeIt = modsSupremePositionByVocation.find(vocationBaseId); - - for (const auto &modPosition : modsBasicPosition) { - const auto pos = static_cast(modPosition); - uint8_t grade = 0; - auto gradeKV = gemsGradeKV(WheelFragmentType_t::Lesser, pos)->get("grade"); - - if (gradeKV.has_value()) { - grade = static_cast(gradeKV->get()); - } - - totalPoints += grade == 3 ? 1 : 0; - } - - if (modsSupremeIt != modsSupremePositionByVocation.end()) { - for (const auto &modPosition : modsSupremeIt->second.get()) { - const auto pos = static_cast(modPosition); - uint8_t grade = 0; - auto gradeKV = gemsGradeKV(WheelFragmentType_t::Greater, pos)->get("grade"); - - if (gradeKV.has_value()) { - grade = gradeKV->get(); - } - - totalPoints += grade == 3 ? 1 : 0; - } - } else { - g_logger().error("[{}] supreme modifications not found for vocation base id: {}", std::source_location::current().function_name(), vocationBaseId); - } + totalPoints += m_modsMaxGrade; for (uint8_t i = WheelSlots_t::SLOT_FIRST; i <= WheelSlots_t::SLOT_LAST; ++i) { totalPoints -= getPointsBySlotType(static_cast(i)); @@ -1007,22 +974,8 @@ bool PlayerWheel::handleBeamMasteryCooldown(const std::shared_ptr &playe } void PlayerWheel::addPromotionScrolls(NetworkMessage &msg) const { - std::vector unlockedScrolls; - - for (const auto &[itemId, name, extraPoints] : WheelOfDestinyPromotionScrolls) { - const auto &scrollKv = m_player.kv()->scoped("wheel-of-destiny")->scoped("scrolls"); - if (!scrollKv) { - continue; - } - - const auto scrollOpt = scrollKv->get(name); - if (scrollOpt && scrollOpt->get()) { - unlockedScrolls.emplace_back(itemId); - } - } - - msg.add(unlockedScrolls.size()); - for (const auto &itemId : unlockedScrolls) { + msg.add(m_unlockedScrolls.size()); + for (const auto &[itemId, name, extraPoints] : m_unlockedScrolls) { msg.add(itemId); } } @@ -1036,13 +989,7 @@ std::shared_ptr PlayerWheel::gemsGradeKV(WheelFragmentType_t type, uint8_t p } uint8_t PlayerWheel::getGemGrade(WheelFragmentType_t type, uint8_t pos) const { - uint8_t grade = 0; - const auto gradeKV = gemsGradeKV(type, pos)->get("grade"); - - if (gradeKV.has_value()) { - grade = static_cast(gradeKV->get()); - } - return grade; + return type == WheelFragmentType_t::Lesser ? m_basicGrades[pos] : m_supremeGrades[pos]; } std::vector PlayerWheel::getRevealedGems() const { @@ -1057,7 +1004,7 @@ std::vector PlayerWheel::getRevealedGems() const { sortedUnlockedGemGUIDs.emplace_back(uuid); } - std::sort(sortedUnlockedGemGUIDs.begin(), sortedUnlockedGemGUIDs.end(), [](const std::string &a, const std::string &b) { + std::ranges::sort(sortedUnlockedGemGUIDs, [](const std::string &a, const std::string &b) { if (std::ranges::all_of(a, ::isdigit) && std::ranges::all_of(b, ::isdigit)) { return std::stoull(a) < std::stoull(b); } else { @@ -1067,7 +1014,7 @@ std::vector PlayerWheel::getRevealedGems() const { for (const auto &uuid : sortedUnlockedGemGUIDs) { auto gem = PlayerWheelGem::load(gemsKV(), uuid); - if (gem.uuid.empty()) { + if (!gem) { continue; } unlockedGems.emplace_back(gem); @@ -1078,20 +1025,12 @@ std::vector PlayerWheel::getRevealedGems() const { std::vector PlayerWheel::getActiveGems() const { std::vector activeGems; for (const auto &affinity : magic_enum::enum_values()) { - std::string key(magic_enum::enum_name(affinity)); - auto uuidKV = gemsKV()->scoped("active")->get(key); - if (!uuidKV.has_value()) { - continue; - } + const auto &gem = m_activeGems[static_cast(affinity)]; - auto uuid = uuidKV->get(); - if (uuid.empty()) { - continue; - } - auto gem = PlayerWheelGem::load(gemsKV(), uuid); - if (gem.uuid.empty()) { + if (!gem) { continue; } + activeGems.emplace_back(gem); } return activeGems; @@ -1133,7 +1072,7 @@ uint64_t PlayerWheel::getGemRevealCost(WheelGemQuality_t quality) { return static_cast(g_configManager().getNumber(key)); } -void PlayerWheel::revealGem(WheelGemQuality_t quality) const { +void PlayerWheel::revealGem(WheelGemQuality_t quality) { uint16_t gemId = m_player.getVocation()->getWheelGemId(quality); if (gemId == 0) { g_logger().error("[{}] Failed to get gem id for quality {} and vocation {}", __FUNCTION__, fmt::underlying(quality), m_player.getVocation()->getVocName()); @@ -1168,32 +1107,39 @@ void PlayerWheel::revealGem(WheelGemQuality_t quality) const { gem.supremeModifier = supremeModifiers[uniform_random(0, supremeModifiers.size() - 1)]; } g_logger().debug("[{}] {}", __FUNCTION__, gem.toString()); - gem.save(gemsKV()); + m_revealedGems.emplace_back(gem); + + std::ranges::sort(m_revealedGems, [](const auto &gem1, const auto &gem2) { + if (std::ranges::all_of(gem1.uuid, ::isdigit) && std::ranges::all_of(gem2.uuid, ::isdigit)) { + return std::stoull(gem1.uuid) < std::stoull(gem2.uuid); + } else { + return gem1.uuid < gem2.uuid; + } + }); + sendOpenWheelWindow(m_player.getID()); } -PlayerWheelGem PlayerWheel::getGem(uint16_t index) const { - auto gems = getRevealedGems(); - if (gems.size() <= index) { - g_logger().error("[{}] Player {} trying to get gem with index {} but has only {} gems", __FUNCTION__, m_player.getName(), index, gems.size()); - return {}; +PlayerWheelGem &PlayerWheel::getGem(uint16_t index) { + auto revealedGemsSize = m_revealedGems.size(); + if (revealedGemsSize <= index) { + g_logger().error("[{}] Player {} trying to get gem with index {} but has only {} gems", __FUNCTION__, m_player.getName(), index, revealedGemsSize); + return emptyGem; } - return gems[index]; + return m_revealedGems[index]; } -PlayerWheelGem PlayerWheel::getGem(const std::string &uuid) const { - auto gem = PlayerWheelGem::load(gemsKV(), uuid); - if (gem.uuid.empty()) { - g_logger().error("[{}] Failed to load gem with uuid {}", __FUNCTION__, uuid); - return {}; - } - return gem; +PlayerWheelGem &PlayerWheel::getGem(const std::string &uuid) { + auto it = std::ranges::find_if(m_revealedGems, [&uuid](const auto &gem) { + return gem.uuid == uuid; + }); + + return it != m_revealedGems.end() ? *it : emptyGem; } uint16_t PlayerWheel::getGemIndex(const std::string &uuid) const { - const auto gems = getRevealedGems(); - for (uint16_t i = 0; i < gems.size(); ++i) { - if (gems[i].uuid == uuid) { + for (uint16_t i = 0; i < m_revealedGems.size(); ++i) { + if (m_revealedGems[i].uuid == uuid) { return i; } } @@ -1201,16 +1147,17 @@ uint16_t PlayerWheel::getGemIndex(const std::string &uuid) const { return 0xFF; } -void PlayerWheel::destroyGem(uint16_t index) const { - const auto gem = getGem(index); +void PlayerWheel::destroyGem(uint16_t index) { + const auto &gem = getGem(index); + if (!gem) { + return; + } + if (gem.locked) { g_logger().error("[{}] Player {} destroyed locked gem with index {}", std::source_location::current().function_name(), m_player.getName(), index); return; } - const auto &backpack = m_player.getInventoryItem(CONST_SLOT_BACKPACK); - const auto &mainBackpack = backpack ? backpack->getContainer() : nullptr; - uint8_t lesserFragments = 0; uint8_t greaterFragments = 0; @@ -1248,7 +1195,8 @@ void PlayerWheel::destroyGem(uint16_t index) const { g_logger().debug("[{}] Player {} destroyed a gem and received {} greater fragments", std::source_location::current().function_name(), m_player.getName(), greaterFragments); } - gem.remove(gemsKV()); + m_destroyedGems.emplace_back(gem); + m_revealedGems.erase(m_revealedGems.begin() + index); const auto totalLesserFragment = m_player.getItemTypeCount(ITEM_LESSER_FRAGMENT) + m_player.getStashItemCount(ITEM_LESSER_FRAGMENT); const auto totalGreaterFragment = m_player.getItemTypeCount(ITEM_GREATER_FRAGMENT) + m_player.getStashItemCount(ITEM_GREATER_FRAGMENT); @@ -1259,8 +1207,12 @@ void PlayerWheel::destroyGem(uint16_t index) const { sendOpenWheelWindow(m_player.getID()); } -void PlayerWheel::switchGemDomain(uint16_t index) const { - auto gem = getGem(index); +void PlayerWheel::switchGemDomain(uint16_t index) { + auto &gem = getGem(index); + if (!gem) { + return; + } + if (gem.locked) { g_logger().error("[{}] Player {} trying to destroy locked gem with index {}", __FUNCTION__, m_player.getName(), index); return; @@ -1273,20 +1225,21 @@ void PlayerWheel::switchGemDomain(uint16_t index) const { auto gemAffinity = convertWheelGemAffinityToDomain(static_cast(gem.affinity)); gem.affinity = static_cast(gemAffinity); - gem.save(gemsKV()); sendOpenWheelWindow(m_player.getID()); } -void PlayerWheel::toggleGemLock(uint16_t index) const { - auto gem = getGem(index); +void PlayerWheel::toggleGemLock(uint16_t index) { + auto &gem = getGem(index); + if (!gem) { + return; + } gem.locked = !gem.locked; - gem.save(gemsKV()); sendOpenWheelWindow(m_player.getID()); } -void PlayerWheel::setActiveGem(WheelGemAffinity_t affinity, uint16_t index) const { - auto gem = getGem(index); - if (gem.uuid.empty()) { +void PlayerWheel::setActiveGem(WheelGemAffinity_t affinity, uint16_t index) { + auto &gem = getGem(index); + if (!gem) { g_logger().error("[{}] Failed to load gem with index {}", __FUNCTION__, index); return; } @@ -1294,13 +1247,11 @@ void PlayerWheel::setActiveGem(WheelGemAffinity_t affinity, uint16_t index) cons g_logger().error("[{}] Gem with index {} has affinity {} but trying to set it to {}", __FUNCTION__, index, fmt::underlying(gem.affinity), fmt::underlying(affinity)); return; } - const std::string key(magic_enum::enum_name(affinity)); - gemsKV()->scoped("active")->set(key, gem.uuid); + m_activeGems[static_cast(affinity)] = gem; } -void PlayerWheel::removeActiveGem(WheelGemAffinity_t affinity) const { - const std::string key(magic_enum::enum_name(affinity)); - gemsKV()->scoped("active")->remove(key); +void PlayerWheel::removeActiveGem(WheelGemAffinity_t affinity) { + m_activeGems[static_cast(affinity)] = emptyGem; } void PlayerWheel::addRevelationBonus(WheelGemAffinity_t affinity, uint16_t points) { @@ -1374,10 +1325,9 @@ void PlayerWheel::addGems(NetworkMessage &msg) const { msg.add(getGemIndex(gem.uuid)); } - const auto revealedGems = getRevealedGems(); - msg.add(revealedGems.size()); + msg.add(m_revealedGems.size()); uint16_t index = 0; - for (const auto &gem : revealedGems) { + for (const auto &gem : m_revealedGems) { g_logger().debug("[{}] Adding revealed gem: {}", __FUNCTION__, gem.toString()); msg.add(index++); msg.addByte(gem.locked); @@ -1395,16 +1345,11 @@ void PlayerWheel::addGems(NetworkMessage &msg) const { void PlayerWheel::addGradeModifiers(NetworkMessage &msg) const { msg.addByte(0x2E); // Modifiers for all Vocations + for (const auto &modPosition : modsBasicPosition) { const auto pos = static_cast(modPosition); msg.addByte(pos); - uint8_t grade = 0; - auto gradeKV = gemsGradeKV(WheelFragmentType_t::Lesser, pos)->get("grade"); - - if (gradeKV.has_value()) { - grade = static_cast(gradeKV->get()); - } - msg.addByte(grade); + msg.addByte(m_basicGrades[pos]); } msg.addByte(0x17); // Modifiers for specific per Vocations @@ -1416,13 +1361,7 @@ void PlayerWheel::addGradeModifiers(NetworkMessage &msg) const { for (const auto &modPosition : modsSupremeIt->second.get()) { const auto pos = static_cast(modPosition); msg.addByte(pos); - uint8_t grade = 0; - auto gradeKV = gemsGradeKV(WheelFragmentType_t::Greater, pos)->get("grade"); - - if (gradeKV.has_value()) { - grade = gradeKV->get(); - } - msg.addByte(grade); + msg.addByte(m_supremeGrades[pos]); } } else { g_logger().error("[{}] vocation base id: {}", std::source_location::current().function_name(), m_player.getVocation()->getBaseId()); @@ -1435,9 +1374,10 @@ void PlayerWheel::improveGemGrade(WheelFragmentType_t fragmentType, uint8_t pos) uint8_t quantity = 0; uint8_t grade = 0; - auto gradeKV = gemsGradeKV(fragmentType, pos)->get("grade"); - if (gradeKV.has_value()) { - grade = gradeKV->get(); + if (fragmentType == WheelFragmentType_t::Lesser) { + grade = m_basicGrades[pos]; + } else { + grade = m_supremeGrades[pos]; } ++grade; @@ -1476,7 +1416,14 @@ void PlayerWheel::improveGemGrade(WheelFragmentType_t fragmentType, uint8_t pos) return; } - gemsGradeKV(fragmentType, pos)->set("grade", grade); + if (fragmentType == WheelFragmentType_t::Lesser) { + m_basicGrades[pos] = grade; + } else { + m_supremeGrades[pos] = grade; + } + + m_modsMaxGrade += grade == 3 ? 1 : 0; + loadPlayerBonusData(); sendOpenWheelWindow(m_player.getID()); } @@ -1682,9 +1629,186 @@ void PlayerWheel::saveSlotPointsOnPressSaveButton(NetworkMessage &msg) { // Player's bonus data is loaded, initialized, registered, and the function logs loadPlayerBonusData(); + sendOpenWheelWindow(m_player.getID()); + g_logger().debug("Player: {} is saved the all slots info in: {} milliseconds", m_player.getName(), bm_saveSlot.duration()); } +void PlayerWheel::loadActiveGems() { + for (const auto &affinity : magic_enum::enum_values()) { + std::string key(magic_enum::enum_name(affinity)); + auto uuidKV = gemsKV()->scoped("active")->get(key); + if (!uuidKV.has_value()) { + continue; + } + + auto uuid = uuidKV->get(); + if (uuid.empty()) { + continue; + } + + auto it = std::ranges::find_if(m_revealedGems, [&uuid](const auto &gem) { + return gem.uuid == uuid; + }); + + if (it == m_revealedGems.end()) { + continue; + } + + m_activeGems[static_cast(affinity)] = *it; + } +} + +void PlayerWheel::saveActiveGems() const { + for (const auto &affinity : magic_enum::enum_values()) { + const std::string key(magic_enum::enum_name(affinity)); + const auto &gem = m_activeGems[static_cast(affinity)]; + if (gem) { + gemsKV()->scoped("active")->set(key, gem.uuid); + } else { + gemsKV()->scoped("active")->remove(key); + } + } +} + +void PlayerWheel::loadRevealedGems() { + const auto unlockedGemUUIDs = gemsKV()->scoped("revealed")->keys(); + if (unlockedGemUUIDs.empty()) { + return; + } + + std::vector sortedUnlockedGemGUIDs; + for (const auto &uuid : unlockedGemUUIDs) { + sortedUnlockedGemGUIDs.emplace_back(uuid); + } + + std::ranges::sort(sortedUnlockedGemGUIDs, [](const std::string &a, const std::string &b) { + if (std::ranges::all_of(a, ::isdigit) && std::ranges::all_of(b, ::isdigit)) { + return std::stoull(a) < std::stoull(b); + } else { + return a < b; + } + }); + + for (const auto &uuid : sortedUnlockedGemGUIDs) { + auto gem = PlayerWheelGem::load(gemsKV(), uuid); + if (!gem) { + continue; + } + m_revealedGems.emplace_back(gem); + } +} + +void PlayerWheel::saveRevealedGems() const { + for (const auto &gem : m_destroyedGems) { + gem.remove(gemsKV()); + } + + for (const auto &gem : m_revealedGems) { + gem.save(gemsKV()); + } +} + +bool PlayerWheel::scrollAcquired(const std::string &scrollName) { + auto it = std::ranges::find_if(m_unlockedScrolls, [&scrollName](const PromotionScroll &promotionScroll) { + return scrollName == promotionScroll.name; + }); + + return it != m_unlockedScrolls.end(); +} + +bool PlayerWheel::unlockScroll(const std::string &scrollName) { + if (scrollAcquired(scrollName)) { + return false; + } + + auto it = std::ranges::find_if(WheelOfDestinyPromotionScrolls, [&scrollName](const auto &scroll) { + return scroll.name == scrollName; + }); + + if (it != WheelOfDestinyPromotionScrolls.end()) { + m_unlockedScrolls.emplace_back(*it); + return true; + } + + return false; +} + +void PlayerWheel::loadKVScrolls() { + const auto &scrollKv = m_player.kv()->scoped("wheel-of-destiny")->scoped("scrolls"); + if (!scrollKv) { + return; + } + + for (const auto &[itemId, name, extraPoints] : WheelOfDestinyPromotionScrolls) { + const auto scrollValue = scrollKv->get(name); + if (scrollValue && scrollValue->get()) { + m_unlockedScrolls.emplace_back(itemId, name, extraPoints); + } + } +} + +void PlayerWheel::saveKVScrolls() const { + const auto &scrollKv = m_player.kv()->scoped("wheel-of-destiny")->scoped("scrolls"); + if (!scrollKv) { + return; + } + + for (const auto &[itemId, name, extraPoints] : m_unlockedScrolls) { + scrollKv->set(name, true); + } +} + +void PlayerWheel::loadKVModGrades() { + for (const auto &modPosition : modsBasicPosition) { + const auto pos = static_cast(modPosition); + auto gradeKV = gemsGradeKV(WheelFragmentType_t::Lesser, pos)->get("grade"); + + if (gradeKV.has_value()) { + uint8_t grade = gradeKV->get(); + m_basicGrades[pos] = grade; + m_modsMaxGrade += grade == 3 ? 1 : 0; + } + } + + const auto vocationBaseId = m_player.getVocation()->getBaseId(); + const auto modsSupremeIt = modsSupremePositionByVocation.find(vocationBaseId); + if (modsSupremeIt != modsSupremePositionByVocation.end()) { + for (const auto &modPosition : modsSupremeIt->second.get()) { + const auto pos = static_cast(modPosition); + auto gradeKV = gemsGradeKV(WheelFragmentType_t::Greater, pos)->get("grade"); + + if (gradeKV.has_value()) { + uint8_t grade = gradeKV->get(); + m_supremeGrades[pos] = grade; + m_modsMaxGrade += grade == 3 ? 1 : 0; + } + } + } +} + +void PlayerWheel::saveKVModGrades() const { + for (const auto &modPosition : modsBasicPosition) { + const auto pos = static_cast(modPosition); + uint8_t grade = m_basicGrades[pos]; + if (grade > 0) { + gemsGradeKV(WheelFragmentType_t::Lesser, pos)->set("grade", grade); + } + } + + const auto vocationBaseId = m_player.getVocation()->getBaseId(); + const auto modsSupremeIt = modsSupremePositionByVocation.find(vocationBaseId); + if (modsSupremeIt != modsSupremePositionByVocation.end()) { + for (const auto &modPosition : modsSupremeIt->second.get()) { + const auto pos = static_cast(modPosition); + uint8_t grade = m_supremeGrades[pos]; + if (grade > 0) { + gemsGradeKV(WheelFragmentType_t::Greater, pos)->set("grade", grade); + } + } + } +} + /* * Functions for load and save player database informations */ @@ -1748,20 +1872,12 @@ uint16_t PlayerWheel::getExtraPoints() const { } uint16_t totalBonus = 0; - for (const auto &[itemId, name, extraPoints] : WheelOfDestinyPromotionScrolls) { + for (const auto &[itemId, name, extraPoints] : m_unlockedScrolls) { if (itemId == 0) { continue; } - const auto &scrollKv = m_player.kv()->scoped("wheel-of-destiny")->scoped("scrolls"); - if (!scrollKv) { - continue; - } - - const auto scrollKV = scrollKv->get(name); - if (scrollKV && scrollKV->get()) { - totalBonus += extraPoints; - } + totalBonus += extraPoints; } return totalBonus; @@ -2564,24 +2680,7 @@ WheelStageEnum_t PlayerWheel::getPlayerSliceStage(const std::string &color) cons } } - const auto vocationBaseId = m_player.getVocation()->getBaseId(); - const auto modsSupremeIt = modsSupremePositionByVocation.find(vocationBaseId); - - if (modsSupremeIt != modsSupremePositionByVocation.end()) { - for (const auto &modPosition : modsSupremeIt->second.get()) { - const auto pos = static_cast(modPosition); - uint8_t grade = 0; - auto gradeKV = gemsGradeKV(WheelFragmentType_t::Greater, pos)->get("grade"); - - if (gradeKV.has_value()) { - grade = gradeKV->get(); - } - - totalPoints += grade == 3 ? 1 : 0; - } - } else { - g_logger().error("[{}] supreme modifications not found for vocation base id: {}", std::source_location::current().function_name(), vocationBaseId); - } + totalPoints += m_modsMaxGrade; if (totalPoints >= static_cast(WheelStagePointsEnum_t::THREE)) { return WheelStageEnum_t::THREE; @@ -3822,4 +3921,4 @@ PlayerWheelGem PlayerWheelGem::deserialize(const std::string &uuid, const ValueW static_cast(map["basicModifier2"]->get()), static_cast(map["supremeModifier"]->get()) }; -} +} \ No newline at end of file diff --git a/src/creatures/players/wheel/player_wheel.hpp b/src/creatures/players/wheel/player_wheel.hpp index 055715f6..e1960dc0 100644 --- a/src/creatures/players/wheel/player_wheel.hpp +++ b/src/creatures/players/wheel/player_wheel.hpp @@ -41,16 +41,20 @@ enum skills_t : int8_t; enum Vocation_t : uint16_t; struct PlayerWheelGem { - std::string uuid; - bool locked; - WheelGemAffinity_t affinity; - WheelGemQuality_t quality; - WheelGemBasicModifier_t basicModifier1; - WheelGemBasicModifier_t basicModifier2; - WheelGemSupremeModifier_t supremeModifier; + std::string uuid = {}; + bool locked = false; + WheelGemAffinity_t affinity = {}; + WheelGemQuality_t quality = {}; + WheelGemBasicModifier_t basicModifier1 = {}; + WheelGemBasicModifier_t basicModifier2 = {}; + WheelGemSupremeModifier_t supremeModifier = {}; std::string toString() const; + explicit operator bool() const { + return !uuid.empty(); + } + void save(const std::shared_ptr &kv) const; void remove(const std::shared_ptr &kv) const; @@ -63,10 +67,29 @@ struct PlayerWheelGem { static PlayerWheelGem deserialize(const std::string &uuid, const ValueWrapper &val); }; +struct PromotionScroll { + uint16_t itemId; + std::string name; + uint8_t extraPoints; +}; + class PlayerWheel { public: explicit PlayerWheel(Player &initPlayer); + void loadActiveGems(); + void saveActiveGems() const; + void loadRevealedGems(); + void saveRevealedGems() const; + + bool scrollAcquired(const std::string &scrollName); + bool unlockScroll(const std::string &scrollName); + void loadKVScrolls(); + void saveKVScrolls() const; + + void loadKVModGrades(); + void saveKVModGrades() const; + /* * Functions for load and save player database informations */ @@ -392,15 +415,15 @@ class PlayerWheel { * @return The calculated mitigation value. */ float calculateMitigation() const; - PlayerWheelGem getGem(uint16_t index) const; - PlayerWheelGem getGem(const std::string &uuid) const; + PlayerWheelGem &getGem(uint16_t index); + PlayerWheelGem &getGem(const std::string &uuid); uint16_t getGemIndex(const std::string &uuid) const; - void revealGem(WheelGemQuality_t quality) const; - void destroyGem(uint16_t index) const; - void switchGemDomain(uint16_t index) const; - void toggleGemLock(uint16_t index) const; - void setActiveGem(WheelGemAffinity_t affinity, uint16_t index) const; - void removeActiveGem(WheelGemAffinity_t affinity) const; + void revealGem(WheelGemQuality_t quality); + void destroyGem(uint16_t index); + void switchGemDomain(uint16_t index); + void toggleGemLock(uint16_t index); + void setActiveGem(WheelGemAffinity_t affinity, uint16_t index); + void removeActiveGem(WheelGemAffinity_t affinity); void addRevelationBonus(WheelGemAffinity_t affinity, uint16_t points); void resetRevelationBonus(); void addSpellBonus(const std::string &spellName, const WheelSpells::Bonus &bonus); @@ -429,6 +452,10 @@ class PlayerWheel { PlayerWheelMethodsBonusData m_playerBonusData; std::unique_ptr m_modifierContext; + uint8_t m_modsMaxGrade = {}; + std::array m_basicGrades = { 0 }; + std::array m_supremeGrades = { 0 }; + std::array(WheelStage_t::STAGE_COUNT)> m_stages = { 0 }; std::array(WheelOnThink_t::TOTAL_COUNT)> m_onThink = { 0 }; std::array(WheelStat_t::TOTAL_COUNT)> m_stats = { 0 }; @@ -442,4 +469,10 @@ class PlayerWheel { std::vector m_learnedSpellsSelected; std::unordered_map m_spellsBonuses; std::unordered_set m_beamMasterySpells; -}; + + std::vector m_unlockedScrolls; + + std::array m_activeGems; + std::vector m_revealedGems; + std::vector m_destroyedGems; +}; \ No newline at end of file diff --git a/src/game/game.cpp b/src/game/game.cpp index e213b108..b51c3753 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -9950,8 +9950,8 @@ void Game::playerSaveWheel(uint32_t playerId, NetworkMessage &msg) { return; } - player->wheel()->saveSlotPointsOnPressSaveButton(msg); player->updateUIExhausted(); + player->wheel()->saveSlotPointsOnPressSaveButton(msg); } void Game::playerWheelGemAction(uint32_t playerId, NetworkMessage &msg) { diff --git a/src/game/scheduling/save_manager.cpp b/src/game/scheduling/save_manager.cpp index f49829ba..2b52c854 100644 --- a/src/game/scheduling/save_manager.cpp +++ b/src/game/scheduling/save_manager.cpp @@ -129,7 +129,7 @@ bool SaveManager::doSavePlayer(std::shared_ptr player) { } bool SaveManager::savePlayer(std::shared_ptr player) { - if (player->isOnline()) { + if (player->isOnline() && g_game().getGameState() != GAME_STATE_SHUTDOWN) { schedulePlayer(player); return true; } diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 46b11bd4..f1165047 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -997,6 +997,10 @@ void IOLoginDataLoad::loadPlayerInitializeSystem(const std::shared_ptr & // Wheel loading player->wheel()->loadDBPlayerSlotPointsOnLogin(); + player->wheel()->loadRevealedGems(); + player->wheel()->loadActiveGems(); + player->wheel()->loadKVModGrades(); + player->wheel()->loadKVScrolls(); player->wheel()->initializePlayerData(); player->achiev()->loadUnlockedAchievements(); diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 54af4353..8f7691c7 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -274,6 +274,11 @@ bool IOLoginData::savePlayerGuard(const std::shared_ptr &player) { throw DatabaseException("[PlayerWheel::saveDBPlayerSlotPointsOnLogout] - Failed to save player wheel info: " + player->getName()); } + player->wheel()->saveRevealedGems(); + player->wheel()->saveActiveGems(); + player->wheel()->saveKVModGrades(); + player->wheel()->saveKVScrolls(); + if (!IOLoginDataSave::savePlayerStorage(player)) { throw DatabaseException("[IOLoginDataSave::savePlayerStorage] - Failed to save player storage: " + player->getName()); } diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index ac7475f4..5ac00417 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -327,6 +327,7 @@ void PlayerFunctions::init(lua_State* L) { Lua::registerMethod(L, "Player", "getWheelSpellAdditionalArea", PlayerFunctions::luaPlayerGetWheelSpellAdditionalArea); Lua::registerMethod(L, "Player", "getWheelSpellAdditionalTarget", PlayerFunctions::luaPlayerGetWheelSpellAdditionalTarget); Lua::registerMethod(L, "Player", "getWheelSpellAdditionalDuration", PlayerFunctions::luaPlayerGetWheelSpellAdditionalDuration); + Lua::registerMethod(L, "Player", "wheelUnlockScroll", PlayerFunctions::luaPlayerWheelUnlockScroll); // Forge Functions Lua::registerMethod(L, "Player", "openForge", PlayerFunctions::luaPlayerOpenForge); @@ -4511,6 +4512,26 @@ int PlayerFunctions::luaPlayerGetWheelSpellAdditionalDuration(lua_State* L) { return 1; } +int PlayerFunctions::luaPlayerWheelUnlockScroll(lua_State* L) { + // player:wheelUnlockScroll(scrollName) + const auto &player = Lua::getUserdataShared(L, 1); + if (!player) { + Lua::reportErrorFunc(Lua::getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + Lua::pushBoolean(L, false); + return 0; + } + + const auto scrollName = Lua::getString(L, 2); + if (scrollName.empty()) { + Lua::reportErrorFunc("Scroll name is empty"); + Lua::pushBoolean(L, false); + return 0; + } + + lua_pushboolean(L, player->wheel()->unlockScroll(scrollName)); + return 1; +} + int PlayerFunctions::luaPlayerUpdateConcoction(lua_State* L) { // player:updateConcoction(itemid, timeLeft) const auto &player = Lua::getUserdataShared(L, 1); diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 02330612..339a36b1 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -307,6 +307,7 @@ class PlayerFunctions { static int luaPlayerGetWheelSpellAdditionalArea(lua_State* L); static int luaPlayerGetWheelSpellAdditionalTarget(lua_State* L); static int luaPlayerGetWheelSpellAdditionalDuration(lua_State* L); + static int luaPlayerWheelUnlockScroll(lua_State* L); static int luaPlayerOpenForge(lua_State* L); static int luaPlayerCloseForge(lua_State* L); diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index 9881d4a8..3478b2c9 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -143,7 +143,7 @@ void Connection::parseProxyIdentification(const std::error_code &error) { if (error || connectionState == CONNECTION_STATE_CLOSED) { if (error != asio::error::operation_aborted && error != asio::error::eof && error != asio::error::connection_reset) { - g_logger().debug("[Connection::parseProxyIdentification] - Read error: {}", error.message()); + g_logger().error("[Connection::parseProxyIdentification] - Read error: {}", error.message()); } close(FORCE_CLOSE); return; @@ -247,7 +247,7 @@ void Connection::parsePacket(const std::error_code &error) { if (error || connectionState == CONNECTION_STATE_CLOSED) { if (error) { - g_logger().debug("[Connection::parsePacket] - Read error: {}", error.message()); + g_logger().error("[Connection::parsePacket] - Read error: {}", error.message()); } close(FORCE_CLOSE); return;