diff --git a/data/events/scripts/monster.lua b/data/events/scripts/monster.lua index 2d2f787eab3..e14985a3722 100644 --- a/data/events/scripts/monster.lua +++ b/data/events/scripts/monster.lua @@ -12,10 +12,8 @@ function Monster:onDropLoot(corpse) local player = Player(corpse:getCorpseOwner()) if not player or player:getStamina() > 840 then local monsterLoot = mType:getLoot() - local preyChanceBoost = 100 local charmBonus = false if player and mType and mType:raceId() > 0 then - preyChanceBoost = player:getPreyLootPercentage(mType:raceId()) local charm = player:getCharmMonsterType(CHARM_GUT) if charm and charm:raceId() == mType:raceId() then charmBonus = true @@ -23,15 +21,33 @@ function Monster:onDropLoot(corpse) end for i = 1, #monsterLoot do - local item = corpse:createLootItem(monsterLoot[i], charmBonus, preyChanceBoost) + local item = corpse:createLootItem(monsterLoot[i], charmBonus) if self:getName():lower() == Game.getBoostedCreature():lower() then - local itemBoosted = corpse:createLootItem(monsterLoot[i], charmBonus, preyChanceBoost) + local itemBoosted = corpse:createLootItem(monsterLoot[i], charmBonus) if not itemBoosted then - Spdlog.warn(string.format("[Monster:onDropLoot] - Could not add loot item to boosted monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) + Spdlog.warn(string.format("[1][Monster:onDropLoot] - Could not add loot item to boosted monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) end end if not item then - Spdlog.warn(string.format("[Monster:onDropLoot] - Could not add loot item to monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) + Spdlog.warn(string.format("[2][Monster:onDropLoot] - Could not add loot item to monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) + end + end + local preyLootActive = false + -- Runs the loot again if the player gets a chance to loot in the prey + if player then + local preyLootPercent = player:getPreyLootPercentage(mType:raceId()) + if preyLootPercent > 0 then + local probability = math.random(0, 100) + if probability < preyLootPercent then + for i, loot in pairs(monsterLoot) do + local item = corpse:createLootItem(monsterLoot[i], charmBonus) + if not item then + Spdlog.warn(string.format("[3][Monster:onDropLoot] - Could not add loot item to monster: %s, from corpse id: %d.", self:getName(), corpse:getId())) + else + preyLootActive = true + end + end + end end end @@ -42,7 +58,7 @@ function Monster:onDropLoot(corpse) else text = ("Loot of %s: %s"):format(mType:getNameDescription(), corpse:getContentDescription()) end - if preyChanceBoost ~= 100 then + if preyLootActive then text = text .. " (active prey bonus)" end if charmBonus then diff --git a/data/libs/functions/container.lua b/data/libs/functions/container.lua index 351f4481253..674202871b9 100644 --- a/data/libs/functions/container.lua +++ b/data/libs/functions/container.lua @@ -2,7 +2,7 @@ function Container.isContainer(self) return true end -function Container.createLootItem(self, item, charm, prey) +function Container.createLootItem(self, item, charm) if self:getEmptySlots() == 0 then return true end @@ -21,11 +21,6 @@ function Container.createLootItem(self, item, charm, prey) chanceTo = math.ceil((chanceTo * GLOBAL_CHARM_GUT) / 100) end - -- Active prey loot bonus - if prey ~= 100 then - chanceTo = math.ceil((chanceTo * prey) / 100) - end - if randvalue < chanceTo then if lootBlockType:isStackable() then local maxc, minc = item.maxCount or 1, item.minCount or 1 @@ -46,7 +41,7 @@ function Container.createLootItem(self, item, charm, prey) if tmpItem:isContainer() then for i = 1, #item.childLoot do - if not tmpItem:createLootItem(item.childLoot[i], charm, prey) then + if not tmpItem:createLootItem(item.childLoot[i], charm) then tmpItem:remove() return false end diff --git a/data/modules/scripts/gamestore/gamestore.lua b/data/modules/scripts/gamestore/gamestore.lua index 875f1215471..e295fe8109c 100644 --- a/data/modules/scripts/gamestore/gamestore.lua +++ b/data/modules/scripts/gamestore/gamestore.lua @@ -4160,6 +4160,48 @@ GameStore.Categories = { rookgaard = true, state = GameStore.States.STATE_NONE, offers = { + { + icons = {"Prey_Bonus_Reroll.png"}, + name = "Prey Wildcard", + price = 50, + id = 1, + count = 5, + description = "Use Prey Wildcards to reroll the bonus of an active prey, to lock your active prey or to select a prey of your choice.\n\n{character}\n{info} added directly to Prey dialog\n{info} maximum amount that can be owned by character: 50", + type = GameStore.OfferTypes.OFFER_TYPE_PREYBONUS + }, + { + icons = {"Instant_Reward_Access.png"}, + name = "Instant Reward Access", + price = 100, + id = 2, + count = 1, + description = "No matter where you are in Tibia, claim your daily reward on the spot!\n\n{character}\n{info} added to your reward wall\n{info} maximum amount that can be owned by character: 90", + type = GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS + }, + { + icons = {"Charm_Expansion_Offer.png"}, + name = "Charm Expansion", + price = 450, + id = 3, + description = "Assign as many of your unlocked Charms as you like and get a 25% discount whenever you are removing a Charm from a creature!\n\n{character}\n{once}", + type = GameStore.OfferTypes.OFFER_TYPE_CHARMS + }, + { + icons = {"Permanent_Prey_Slot.png"}, + name = "Permanent Prey Slot", + price = 900, + id = 4, + description = "Get an additional prey slot to activate additional prey!\n\n{character}\n{info} maximum amount that can be owned by character: 3\n{info} added directly to Prey dialog", + type = GameStore.OfferTypes.OFFER_TYPE_PREYSLOT + }, + { + icons = {"Permanent_Hunting_Task_Slot.png"}, + name = "Permanent Hunting Task Slot", + price = 900, + id = 5, + description = "Get an additional hunting tasks slot to activate additional hunting task!\n\n{character}\n{info} maximum amount that can be owned by character: 3\n{info} added directly to Hunting Task dialog", + type = GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT + }, { icons = { "Gold_Converter.png" }, name = "Gold Converter", @@ -4178,23 +4220,6 @@ GameStore.Categories = { description = "Carries as many gold, platinum or crystal coins as your capacity allows, however, no other items.\n\n{character}\n{storeinbox}\n{once}\n{useicon} use it to open it\n{info} always placed on the first position of your Store inbox", type = GameStore.OfferTypes.OFFER_TYPE_POUNCH, }, - { - icons = { "Instant_Reward_Access.png" }, - name = "Instant Reward Access", - price = 100, - id = 2, - count = 1, - description = "No matter where you are in Tibia, claim your daily reward on the spot!\n\n{character}\n{info} added to your reward wall\n{info} maximum amount that can be owned by character: 90", - type = GameStore.OfferTypes.OFFER_TYPE_INSTANT_REWARD_ACCESS, - }, - { - icons = { "Charm_Expansion_Offer.png" }, - name = "Charm Expansion", - price = 450, - id = 65005, - description = "Assign as many of your unlocked Charms as you like and get a 25% discount whenever you are removing a Charm from a creature!\n\n{character}\n{once}", - type = GameStore.OfferTypes.OFFER_TYPE_CHARMS, - }, { icons = { "Magic_Gold_Converter.png" }, name = "Magic Gold Converter", @@ -4204,27 +4229,10 @@ GameStore.Categories = { description = "Changes automatically either a stack of 100 gold pieces into 1 platinum coin, or a stack of 100 platinum coins into 1 crystal coin!\n\n{character}\n{storeinbox}\n{useicon} use it to activate or deactivate the automatic conversion\n{info} converts all stacks of 100 gold or platinum in the inventory whenever it is activated\n{info} deactivated upon purchase\n{info} usable for 500 conversions a piece", type = GameStore.OfferTypes.OFFER_TYPE_CHARGES, }, - { - icons = { "Permanent_Prey_Slot.png" }, - name = "Permanent Prey Slot", - price = 900, - id = 65008, - description = "Get an additional prey slot to activate additional prey!\n\n{character}\n{info} maximum amount that can be owned by character: 3\n{info} added directly to Prey dialog", - type = GameStore.OfferTypes.OFFER_TYPE_PREYSLOT, - }, { icons = { "Prey_Bonus_Reroll.png" }, name = "Prey Wildcard", price = 50, - id = 1, - count = 5, - description = "Use Prey Wildcards to reroll the bonus of an active prey, to lock your active prey or to select a prey of your choice.\n\n{character}\n{info} added directly to Prey dialog\n{info} maximum amount that can be owned by character: 50", - type = GameStore.OfferTypes.OFFER_TYPE_PREYBONUS, - }, - { - icons = { "Prey_Bonus_Reroll.png" }, - name = "Prey Wildcard", - price = 200, count = 20, description = "Use Prey Wildcards to reroll the bonus of an active prey, to lock your active prey or to select a prey of your choice.\n\n{character}\n{info} added directly to Prey dialog\n{info} maximum amount that can be owned by character: 50", type = GameStore.OfferTypes.OFFER_TYPE_PREYBONUS, diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua index 923ea573899..c17a733091e 100644 --- a/data/modules/scripts/gamestore/init.lua +++ b/data/modules/scripts/gamestore/init.lua @@ -31,7 +31,13 @@ GameStore.OfferTypes = { OFFER_TYPE_HIRELING_NAMECHANGE = 21, OFFER_TYPE_HIRELING_SEXCHANGE = 22, OFFER_TYPE_HIRELING_SKILL = 23, - OFFER_TYPE_HIRELING_OUTFIT = 24 + OFFER_TYPE_HIRELING_OUTFIT = 24, + OFFER_TYPE_HUNTINGSLOT = 25 +} + +GameStore.SubActions = { + PREY_THIRDSLOT = 0, + TASKHUNTING_THIRDSLOT = 14 } GameStore.ActionType = { @@ -309,22 +315,24 @@ function parseRequestStoreOffers(playerId, msg) end elseif actionType == GameStore.ActionType.OPEN_USEFUL_THINGS then local subAction = msg:getByte() - local redirectId = subAction - local category = nil - if subAction >= 4 then - category = GameStore.getCategoryByName("Blessings") - else - category = GameStore.getCategoryByName("Useful Things") - end + local offerId = subAction + local category = GameStore.getCategoryByName("Useful Things") -- Third prey slot offerId -- We can't use offerId 0 - if subAction == 0 then - redirectId = 65008 + -- Subaction 0 is offer from "unlock permanently" of prey window button, offerId 4 is the id of the gamestore.lua third slot + if subAction == GameStore.SubActions.PREY_THIRDSLOT then + offerId = 4 + -- Subaction 14 is offer from "unlock permanently" of task hunting window button, offerId 5 is the id of the gamestore.lua third slot + elseif subAction == GameStore.SubActions.TASKHUNTING_THIRDSLOT then + offerId = 5 end + -- Enable this for look sub action offer button + -- Example of subaction is the button for redirect to the store on clicking "unlock permanently" on prey window + --Spdlog.info("SubAction ".. subAction) if category then - addPlayerEvent(sendShowStoreOffers, 50, playerId, category, redirectId) + addPlayerEvent(sendShowStoreOffers, 50, playerId, category, offerId) end elseif actionType == GameStore.ActionType.OPEN_OFFER then diff --git a/src/creatures/creature.h b/src/creatures/creature.h index affac8113d8..74483753049 100644 --- a/src/creatures/creature.h +++ b/src/creatures/creature.h @@ -511,17 +511,20 @@ class Creature : virtual public Thing delete this; } } + struct CountBlock_t { + int32_t total; + int64_t ticks; + }; + using CountMap = std::map; + CountMap getDamageMap() const { + return damageMap; + } protected: virtual bool useCacheMap() const { return false; } - struct CountBlock_t { - int32_t total; - int64_t ticks; - }; - static constexpr int32_t mapWalkWidth = Map::maxViewportX * 2 + 1; static constexpr int32_t mapWalkHeight = Map::maxViewportY * 2 + 1; static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; @@ -529,7 +532,6 @@ class Creature : virtual public Thing Position position; - using CountMap = std::map; CountMap damageMap; std::list summons; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 040d61892f1..3306e2b946c 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -4387,16 +4387,38 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) } } } - } else if (const Monster* monster = target->getMonster(); - TaskHuntingSlot* taskSlot = getTaskHuntingWithCreature(monster->getRaceId())) { - if (const TaskHuntingOption* option = g_ioprey().GetTaskRewardOption(taskSlot)) { - taskSlot->currentKills += 1; - if ((taskSlot->upgrade && taskSlot->currentKills >= option->secondKills) || - (!taskSlot->upgrade && taskSlot->currentKills >= option->firstKills)) { - taskSlot->state = PreyTaskDataState_Completed; - sendTextMessage(MESSAGE_STATUS, "You succesfully finished your hunting task. Your reward is ready to be claimed!"); + } else if (const Monster* monster = target->getMonster()) { + // Access to the monster's map damage to check if the player attacked it + for (auto [playerId, damage] : monster->getDamageMap()) { + auto damagePlayer = g_game().getPlayerByID(playerId); + if (!damagePlayer) { + continue; + } + + // If the player is not in a party and sharing exp active and enabled + // And it's not the player killing the creature, then we ignore everything else + auto damageParty = damagePlayer->getParty(); + if (this->getID() != damagePlayer->getID() && + (!damageParty || !damageParty->isSharedExperienceActive() || !damageParty->isSharedExperienceEnabled())) + { + continue; + } + + TaskHuntingSlot* taskSlot = damagePlayer->getTaskHuntingWithCreature(monster->getRaceId()); + if (!taskSlot || monster->isSummon()) { + continue; + } + + if (const TaskHuntingOption* option = g_ioprey().GetTaskRewardOption(taskSlot)) { + taskSlot->currentKills += 1; + if ((taskSlot->upgrade && taskSlot->currentKills >= option->secondKills) || + (!taskSlot->upgrade && taskSlot->currentKills >= option->firstKills)) { + taskSlot->state = PreyTaskDataState_Completed; + std::string message = "You succesfully finished your hunting task. Your reward is ready to be claimed!"; + damagePlayer->sendTextMessage(MESSAGE_STATUS, message); + } + damagePlayer->reloadTaskSlot(taskSlot->id); } - reloadTaskSlot(taskSlot->id); } } @@ -5848,6 +5870,8 @@ void Player::initializePrey() slot->state = PreyDataState_Inactive; } else if (slot->id == PreySlot_Three && !g_configManager().getBoolean(PREY_FREE_THIRD_SLOT)) { slot->state = PreyDataState_Locked; + } else if (slot->id == PreySlot_Two && !isPremium()) { + slot->state = PreyDataState_Locked; } else { slot->state = PreyDataState_Selection; slot->reloadMonsterGrid(getPreyBlackList(), getLevel()); @@ -5869,6 +5893,8 @@ void Player::initializeTaskHunting() slot->state = PreyTaskDataState_Inactive; } else if (slot->id == PreySlot_Three && !g_configManager().getBoolean(TASK_HUNTING_FREE_THIRD_SLOT)) { slot->state = PreyTaskDataState_Locked; + } else if (slot->id == PreySlot_Two && !isPremium()) { + slot->state = PreyTaskDataState_Locked; } else { slot->state = PreyTaskDataState_Selection; slot->reloadMonsterGrid(getTaskHuntingBlackList(), getLevel()); diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 845a24175bc..237c5539f7c 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -658,7 +658,16 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) if (result = db.storeQuery(query.str())) { do { auto slot = new PreySlot(static_cast(result->getNumber("slot"))); - slot->state = static_cast(result->getNumber("state")); + PreyDataState_t state = static_cast(result->getNumber("state")); + if (slot->id == PreySlot_Two && state == PreyDataState_Locked) { + if (!player->isPremium()) { + slot->state = PreyDataState_Locked; + } else { + slot->state = PreyDataState_Selection; + } + } else { + slot->state = state; + } slot->selectedRaceId = result->getNumber("raceid"); slot->option = static_cast(result->getNumber("option")); slot->bonus = static_cast(result->getNumber("bonus_type")); @@ -689,7 +698,16 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) if (result = db.storeQuery(query.str())) { do { auto slot = new TaskHuntingSlot(static_cast(result->getNumber("slot"))); - slot->state = static_cast(result->getNumber("state")); + PreyTaskDataState_t state = static_cast(result->getNumber("state")); + if (slot->id == PreySlot_Two && state == PreyTaskDataState_Locked) { + if (!player->isPremium()) { + slot->state = PreyTaskDataState_Locked; + } else { + slot->state = PreyTaskDataState_Selection; + } + } else { + slot->state = state; + } slot->selectedRaceId = result->getNumber("raceid"); slot->upgrade = result->getNumber("upgrade"); slot->rarity = static_cast(result->getNumber("rarity"));