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"));