From 42af88d201cd3d4afed91445b1ac13ebabf76986 Mon Sep 17 00:00:00 2001
From: Marcos <66353315+marcosvf132@users.noreply.github.com>
Date: Sun, 24 Apr 2022 13:36:35 -0300
Subject: [PATCH] [Feature/Enhancement] - Prey system rework (CPP) + Hunting
task system (#172)
Complete rewrite of old prey system from LUA to CPP and implementation of Hunting Tasks system.
Do not test with GOD char or with low amount of creatures registered on bestiary. The PR already include the necessary amount of then.
Addition of hunting task system and entire rework of prey system. All working on CPP with few LUA integration.
---
config.lua.dist | 28 +
data/events/scripts/monster.lua | 18 +-
data/events/scripts/player.lua | 10 +
data/global.lua | 13 -
data/lib/core/functions/container.lua | 14 +-
data/migrations/0.lua | 54 +-
data/migrations/1.lua | 5 +
data/modules/modules.xml | 4 -
.../scripts/daily_reward/daily_reward.lua | 2 +-
data/modules/scripts/gamestore/init.lua | 41 +-
data/modules/scripts/prey_system/assets.lua | 36 -
data/modules/scripts/prey_system/prey.lua | 715 ------------------
data/startup/others/functions.lua | 40 +-
schema.sql | 68 +-
src/CMakeLists.txt | 1 +
src/config/config_definitions.hpp | 16 +
src/config/configmanager.cpp | 18 +
src/creatures/combat/combat.cpp | 7 +-
src/creatures/combat/condition.cpp | 1 -
src/creatures/combat/spells.cpp | 1 +
src/creatures/creature.cpp | 1 -
src/creatures/interactions/chat.cpp | 1 -
src/creatures/monsters/monster.cpp | 1 -
src/creatures/monsters/monster.h | 3 +
src/creatures/monsters/monsters.cpp | 1 -
.../monsters/spawns/spawn_monster.cpp | 1 -
src/creatures/players/player.cpp | 78 +-
src/creatures/players/player.h | 298 ++++++--
src/game/game.cpp | 144 ++--
src/game/game.h | 6 +
src/io/iobestiary.h | 15 +
src/io/iologindata.cpp | 284 +++----
src/io/iologindata.h | 3 -
src/io/ioprey.cpp | 640 ++++++++++++++++
src/io/ioprey.h | 263 +++++++
.../functions/core/game/config_functions.hpp | 15 +
.../functions/core/game/game_functions.cpp | 9 +-
.../creatures/creature_functions.cpp | 3 +-
.../creatures/player/player_functions.cpp | 595 ++++-----------
.../creatures/player/player_functions.hpp | 90 +--
src/otserv.cpp | 3 +
src/server/network/protocol/protocolgame.cpp | 348 +++++++--
src/server/network/protocol/protocolgame.h | 23 +-
src/server/server_definitions.hpp | 18 +-
44 files changed, 2119 insertions(+), 1816 deletions(-)
create mode 100644 data/migrations/1.lua
delete mode 100644 data/modules/scripts/prey_system/assets.lua
delete mode 100644 data/modules/scripts/prey_system/prey.lua
create mode 100644 src/io/ioprey.cpp
create mode 100644 src/io/ioprey.h
diff --git a/config.lua.dist b/config.lua.dist
index cc84364dd7e..5d8f4aebc87 100644
--- a/config.lua.dist
+++ b/config.lua.dist
@@ -54,6 +54,34 @@ depotBoxes = 18
-- GameStore
gamestoreByModules = true
+-- Prey system
+-- NOTE: preyRerollPricePerLevel: Price multiplier in gold coin for rerolling prey list.
+-- NOTE: preySelectListPrice: Price to manually select creature on list and to lock prey slot.
+-- NOTE: preyBonusRerollPrice: Price to manually reroll bonus type and to enable automatic reroll.
+-- NOTE: preyBonusTime: Time in seconds that players will have of prey bonus.
+-- NOTE: preyFreeRerollTime: Time in seconds that players will have to wait to get a new free prey list.
+preySystemEnabled = true
+preyFreeThirdSlot = false
+preyRerollPricePerLevel = 200
+preySelectListPrice = 1
+preyBonusRerollPrice = 2
+preyBonusPercentMin = 5
+preyBonusPercentMax = 40
+preyBonusTime = 2 * 60 * 60
+preyFreeRerollTime = 20 * 60 * 60
+
+-- Task hunting system
+-- NOTE: taskHuntingLimitedTasksExhaust: Time to wait to select a new creature on the task hunting slot after claiming the reward.
+-- NOTE: taskHuntingRerollPricePerLevel: Price multiplier in gold coin for rerolling task hunting list.
+-- NOTE: taskHuntingFreeRerollTime: Time in seconds that players will have to wait to get a new free task hunting list.
+taskHuntingSystemEnabled = true
+taskHuntingFreeThirdSlot = false
+taskHuntingLimitedTasksExhaust = 20 * 60 * 60
+taskHuntingRerollPricePerLevel = 200
+taskHuntingSelectListPrice = 1
+taskHuntingBonusRerollPrice = 2
+taskHuntingFreeRerollTime = 20 * 60 * 60
+
-- NOTE: Access only for Premium Account
onlyPremiumAccount = false
diff --git a/data/events/scripts/monster.lua b/data/events/scripts/monster.lua
index 6c996f136a8..227aa4918ed 100644
--- a/data/events/scripts/monster.lua
+++ b/data/events/scripts/monster.lua
@@ -10,8 +10,18 @@ function Monster:onDropLoot(corpse)
local mType = self:getType()
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
+ end
+ end
+
for i = 1, #monsterLoot do
- local item = corpse:createLootItem(monsterLoot[i])
+ local item = corpse:createLootItem(monsterLoot[i], charmBonus, preyChanceBoost)
if not item then
Spdlog.info("Could not add loot item to corpse of monster '".. mType:getName() .."'")
end
@@ -19,6 +29,12 @@ function Monster:onDropLoot(corpse)
if player then
local text = ("Loot of %s: %s"):format(mType:getNameDescription(), corpse:getContentDescription())
+ if preyChanceBoost ~= 100 then
+ text = text .. " (active prey bonus)"
+ end
+ if charmBonus then
+ text = text .. " (active charm bonus)"
+ end
local party = player:getParty()
if party then
party:broadcastPartyLoot(text)
diff --git a/data/events/scripts/player.lua b/data/events/scripts/player.lua
index f853e50f216..70a603e6695 100644
--- a/data/events/scripts/player.lua
+++ b/data/events/scripts/player.lua
@@ -263,9 +263,11 @@ local function useStamina(player)
staminaMinutes = 0
end
nextUseStaminaTime[playerId] = currentTime + 120
+ player:removePreyStamina(120)
else
staminaMinutes = staminaMinutes - 1
nextUseStaminaTime[playerId] = currentTime + 60
+ player:removePreyStamina(60)
end
player:setStamina(staminaMinutes)
end
@@ -299,6 +301,14 @@ function Player:onGainExperience(source, exp, rawExp)
end
end
+ -- Prey system
+ if configManager.getBoolean(configKeys.PREY_ENABLED) then
+ local monsterType = source:getType()
+ if monsterType and monsterType:raceId() > 0 then
+ exp = math.ceil((exp * self:getPreyExperiencePercentage(monsterType:raceId())) / 100)
+ end
+ end
+
return exp
end
diff --git a/data/global.lua b/data/global.lua
index fd11180a842..626234e1742 100644
--- a/data/global.lua
+++ b/data/global.lua
@@ -65,11 +65,6 @@ if damageImpact == nil then
damageImpact = {}
end
--- New prey => preyTimeLeft
-if nextPreyTime == nil then
- nextPreyTime = {}
-end
-
do -- Event Schedule rates
local lootRate = Game.getEventSLoot()
if lootRate ~= 100 then
@@ -126,10 +121,6 @@ if nextUseStaminaTime == nil then
nextUseStaminaTime = {}
end
-if nextUseStaminaPrey == nil then
- nextUseStaminaPrey = {}
-end
-
if nextUseXpStamina == nil then
nextUseXpStamina = {}
end
@@ -138,10 +129,6 @@ if lastItemImbuing == nil then
lastItemImbuing = {}
end
-if nextDelayPreyReroll == nil then
- nextDelayPreyReroll = {}
-end
-
-- Delay potion
if not playerDelayPotion then
playerDelayPotion = {}
diff --git a/data/lib/core/functions/container.lua b/data/lib/core/functions/container.lua
index 04c90103b2d..351f4481253 100644
--- a/data/lib/core/functions/container.lua
+++ b/data/lib/core/functions/container.lua
@@ -2,7 +2,7 @@ function Container.isContainer(self)
return true
end
-function Container.createLootItem(self, item, boolCharm)
+function Container.createLootItem(self, item, charm, prey)
if self:getEmptySlots() == 0 then
return true
end
@@ -16,8 +16,14 @@ function Container.createLootItem(self, item, boolCharm)
return
end
- if boolCharm and lootBlockType:getType() == ITEM_TYPE_CREATUREPRODUCT then
- chanceTo = (chanceTo * (GLOBAL_CHARM_GUT + 100))/100
+ -- Bestiary charm bonus
+ if charm and lootBlockType:getType() == ITEM_TYPE_CREATUREPRODUCT then
+ 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
@@ -40,7 +46,7 @@ function Container.createLootItem(self, item, boolCharm)
if tmpItem:isContainer() then
for i = 1, #item.childLoot do
- if not tmpItem:createLootItem(item.childLoot[i]) then
+ if not tmpItem:createLootItem(item.childLoot[i], charm, prey) then
tmpItem:remove()
return false
end
diff --git a/data/migrations/0.lua b/data/migrations/0.lua
index c931d4cffc9..7bd6766c866 100644
--- a/data/migrations/0.lua
+++ b/data/migrations/0.lua
@@ -1,5 +1,53 @@
--- return true = There are others migrations file
--- return false = This is the last migration file
function onUpdateDatabase()
- return false
+ Spdlog.info("Updating database to version 19 (Prey system rework + Task hunting system)")
+ db.query([[
+ ALTER TABLE `players`
+ DROP `prey_stamina_1`,
+ DROP `prey_stamina_2`,
+ DROP `prey_stamina_3`,
+ DROP `prey_column`,
+ ADD `prey_wildcard` bigint(21) NOT NULL DEFAULT 0,
+ ADD `task_points` bigint(21) NOT NULL DEFAULT 0;
+ ]])
+
+ db.query([[
+ DROP TABLE `player_prey`;
+ ]])
+
+ db.query([[
+ DROP TABLE `prey_slots`;
+ ]])
+
+ db.query([[
+ CREATE TABLE IF NOT EXISTS `player_taskhunt` (
+ `player_id` int(11) NOT NULL,
+ `slot` tinyint(1) NOT NULL,
+ `state` tinyint(1) NOT NULL,
+ `raceid` varchar(250) NOT NULL,
+ `upgrade` tinyint(1) NOT NULL,
+ `rarity` tinyint(1) NOT NULL,
+ `kills` varchar(250) NOT NULL,
+ `disabled_time` bigint(20) NOT NULL,
+ `free_reroll` bigint(20) NOT NULL,
+ `monster_list` BLOB NULL
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ ]])
+
+ db.query([[
+ CREATE TABLE IF NOT EXISTS `player_prey` (
+ `player_id` int(11) NOT NULL,
+ `slot` tinyint(1) NOT NULL,
+ `state` tinyint(1) NOT NULL,
+ `raceid` varchar(250) NOT NULL,
+ `option` tinyint(1) NOT NULL,
+ `bonus_type` tinyint(1) NOT NULL,
+ `bonus_rarity` tinyint(1) NOT NULL,
+ `bonus_percentage` varchar(250) NOT NULL,
+ `bonus_time` varchar(250) NOT NULL,
+ `free_reroll` bigint(20) NOT NULL,
+ `monster_list` BLOB NULL
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ ]])
+
+ return true
end
diff --git a/data/migrations/1.lua b/data/migrations/1.lua
new file mode 100644
index 00000000000..c931d4cffc9
--- /dev/null
+++ b/data/migrations/1.lua
@@ -0,0 +1,5 @@
+-- return true = There are others migrations file
+-- return false = This is the last migration file
+function onUpdateDatabase()
+ return false
+end
diff --git a/data/modules/modules.xml b/data/modules/modules.xml
index ca01b803338..c63a998ed4a 100644
--- a/data/modules/modules.xml
+++ b/data/modules/modules.xml
@@ -10,10 +10,6 @@
-
-
-
-
diff --git a/data/modules/scripts/daily_reward/daily_reward.lua b/data/modules/scripts/daily_reward/daily_reward.lua
index 5673d2650ab..c392b82b5cd 100644
--- a/data/modules/scripts/daily_reward/daily_reward.lua
+++ b/data/modules/scripts/daily_reward/daily_reward.lua
@@ -513,7 +513,7 @@ function Player.selectDailyReward(self, msg)
end
if (dailyTable.type == DAILY_REWARD_TYPE_PREY_REROLL) then
- self:setPreyBonusRerolls(self:getPreyBonusRerolls() + reward)
+ self:addPreyCards(reward)
DailyReward.insertHistory(self:getGuid(), self:getDayStreak(), "Claimed reward no. \z
" .. self:getDayStreak() + 1 .. ". Picked reward: " .. reward .. "x Prey bonus reroll(s)")
DailyReward.processReward(playerId, source)
diff --git a/data/modules/scripts/gamestore/init.lua b/data/modules/scripts/gamestore/init.lua
index 818603889de..0382ca6cea0 100644
--- a/data/modules/scripts/gamestore/init.lua
+++ b/data/modules/scripts/gamestore/init.lua
@@ -390,8 +390,8 @@ function parseBuyStoreOffer(playerId, msg)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_NAMECHANGE then local newName = msg:getString(); GameStore.processNameChangePurchase(player, offer.id, productType, newName, offer.name, offerPrice)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_SEXCHANGE then GameStore.processSexChangePurchase(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_EXPBOOST then GameStore.processExpBoostPuchase(player)
- elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYSLOT then GameStore.processPreySlotPurchase(player)
- elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT then GameStore.processPreyHuntingSlotPurchase(player)
+ elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYSLOT then GameStore.processPreyThirdSlot(player)
+ elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT then GameStore.processTaskHuntingThirdSlot(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then GameStore.processPreyBonusReroll(player, offer.count)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_TEMPLE then GameStore.processTempleTeleportPurchase(player)
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_CHARGES then GameStore.processChargesPurchase(player, offer.itemtype, offer.name, offer.charges)
@@ -587,7 +587,7 @@ function Player.canBuyOffer(self, offer)
disabledReason = "You already have maximum of reward tokens."
end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYBONUS then
- if self:getPreyBonusRerolls() >= 50 then
+ if self:getPreyCards()>= 50 then
disabled = 1
disabledReason = "You already have maximum of prey wildcards."
end
@@ -596,8 +596,13 @@ function Player.canBuyOffer(self, offer)
disabled = 1
disabledReason = "You already have charm expansion."
end
+ elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_HUNTINGSLOT then
+ if self:taskHuntingThirdSlot() then
+ disabled = 1
+ disabledReason = "You already have 3 slots released."
+ end
elseif offer.type == GameStore.OfferTypes.OFFER_TYPE_PREYSLOT then
- if self:getStorageValue(Prey.Config.StoreSlotStorage) == 1 then
+ if self:preyThirdSlot() then
disabled = 1
disabledReason = "You already have 3 slots released."
end
@@ -1458,33 +1463,25 @@ function GameStore.processExpBoostPuchase(player)
player:setStorageValue(GameStore.Storages.expBoostCount, expBoostCount + 1)
end
-function GameStore.processPreySlotPurchase(player)
- if player:getStorageValue(Prey.Config.StoreSlotStorage) < 1 then
- player:setStorageValue(Prey.Config.StoreSlotStorage, 1)
- player:setPreyUnlocked(CONST_PREY_SLOT_THIRD, 2)
- player:setPreyState(CONST_PREY_SLOT_THIRD, 1)
-
- -- Update Prey Data
- for slot = CONST_PREY_SLOT_FIRST, CONST_PREY_SLOT_THIRD do
- player:sendPreyData(slot)
- end
+function GameStore.processPreyThirdSlot(player)
+ if player:preyThirdSlot() then
+ return error({code = 1, message = "You already have unlocked all prey slots."})
end
+ player:preyThirdSlot(true)
end
-function GameStore.processPreyHuntingSlotPurchase(player)
- if player:getStorageValue(CONST_HUNTING_STORAGE) < 1 then
- player:setStorageValue(CONST_HUNTING_STORAGE, 1)
-
- -- Update Prey Data
- player:sendPreyHuntingData(CONST_PREY_SLOT_THIRD)
+function GameStore.processTaskHuntingThirdSlot(player)
+ if player:taskHuntingThirdSlot() then
+ return error({code = 1, message = "You already have unlocked all task hunting slots."})
end
+ player:taskHuntingThirdSlot(true)
end
function GameStore.processPreyBonusReroll(player, offerCount)
- if player:getPreyBonusRerolls() + offerCount >= 51 then
+ if player:getPreyCards() + offerCount >= 51 then
return error({code = 1, message = "You cannot own more than 50 prey wildcards."})
end
- player:setPreyBonusRerolls(player:getPreyBonusRerolls() + offerCount)
+ player:addPreyCards(offerCount)
end
function GameStore.processTempleTeleportPurchase(player)
diff --git a/data/modules/scripts/prey_system/assets.lua b/data/modules/scripts/prey_system/assets.lua
deleted file mode 100644
index 1715cb74d53..00000000000
--- a/data/modules/scripts/prey_system/assets.lua
+++ /dev/null
@@ -1,36 +0,0 @@
-preyRaceIds = {
- "1196", "262", "267", "268", "269", "270", "271", "314", "563", "738", "913", "112", "237", "238", "239", "240", "241", "242",
- "243", "244", "245", "246", "261", "437", "697", "734", "735", "769", "770", "772", "779", "780", "781", "782", "783",
- "784", "795", "859", "860", "861", "862", "1097", "1098", "1099", "1100", "1101", "1105", "111", "212", "218", "318",
- "561", "562", "914", "915", "1307", "67", "326", "503", "524", "533", "680", "700", "701", "702", "706", "873", "879",
- "882", "884", "885", "897", "1038", "1039", "1041", "1043", "1325", "1326", "1327", "1751", "35", "285", "288", "291",
- "295", "296", "519", "523", "581", "586", "725", "727", "728", "729", "1019", "1134", "1135", "1197", "1198", "1619",
- "1620", "1622", "34", "121", "317", "385", "386", "461", "617", "618", "643", "672", "673", "963", "1376", "1380",
- "49", "236", "313", "456", "457", "458", "889", "890", "1000", "1116", "1121", "1563", "1569", "1670", "717", "1224",
- "1235", "1260", "1264", "1265", "1266", "383", "1434", "1436", "1437", "1438", "1439", "1485", "1496", "1626", "1740",
- "1741", "22", "55", "324", "334", "389", "391", "1161", "1162", "1163", "1314", "1320", "1820", "1821", "1822", "1841",
- "1857", "9", "10", "11", "12", "47", "54", "57", "58", "72", "73", "77", "222", "223", "224", "225", "247", "248", "249",
- "250", "253", "254", "255", "310", "322", "323", "331", "332", "333", "371", "372", "376", "521", "525", "526", "527",
- "529", "578", "579", "583", "585", "587", "719", "776", "777", "867", "868", "922", "960", "961", "974", "1119", "1120",
- "1145", "1146", "1147", "1413","1481","1482","1512", "1513", "1514", "1775", "1776", "1824", "1800", "1799", "1798", "2",
- "4", "5", "6", "7", "8", "15", "23", "24", "25", "29", "50", "53", "59", "61", "62", "63", "64", "66", "69", "70", "71",
- "76", "214", "215", "216", "277", "319", "328", "329", "377", "379", "392", "393", "463", "464", "540", "541", "614",
- "737", "741", "745", "886", "888", "916", "917", "918", "920", "924", "925", "926", "1044", "1045", "1046", "1051",
- "1052", "1053", "1109", "1321", "1322", "1394", "1412", "1486", "1488", "1503", "1504", "1505", "1506", "1507", "1508",
- "1509", "1510", "1529", "1730", "1731", "1732", "1733", "1734", "1735", "510", "1142", "1143", "1144", "1549", "17", "51",
- "80", "95", "103", "104", "108", "109", "292", "299", "330", "460", "518", "520", "570", "698", "739", "740", "869", "880",
- "894", "978", "980", "1004", "1012", "1013", "1014", "1015", "1016", "1018", "1021", "1022", "1137", "1138", "1157", "1442",
- "1443", "1653", "1654", "1655", "1659", "1663", "1664", "1665", "1666", "1668", "1669", "1671", "1721", "1722", "1723", "1728",
- "1729", "1808", "1807", "1806", "1816", "1819", "3", "13", "14", "16", "21", "27", "31", "32", "41", "42", "52", "56", "60",
- "74", "94", "105", "106", "110", "116", "117", "118", "122", "123", "125", "211", "260", "325", "327", "384", "387", "502",
- "509", "516", "555", "556", "557", "559", "560", "630", "693", "720", "723", "730", "733", "750", "751", "752", "870", "872",
- "877", "898", "981", "1118", "1139", "1174", "1395", "1548", "1570", "1742", "1855", "1856", "120", "221", "511", "679", "881",
- "891", "919", "979", "982", "1042", "1141", "28", "81", "113", "114", "115", "119", "220", "258", "259", "290", "438", "439",
- "616", "620", "623", "624", "625", "627", "655", "656", "694", "695", "1096", "1525", "1817", "1818", "19", "20", "265", "289",
- "513", "514", "515", "584", "1054", "1056", "1658", "18", "33", "37", "48", "65", "68", "78", "99", "100", "101", "256", "257",
- "281", "282", "283", "284", "286", "298", "321", "388", "446", "465", "483", "508", "512", "558", "580", "594", "595", "675",
- "696", "704", "707", "708", "710", "711", "712", "958", "959", "962", "975", "976", "1040", "1055", "1148", "1415", "1646",
- "1647", "1674", "1675", "1724", "1725", "1726", "1805", "26", "30", "36", "38", "39", "43", "44", "45", "79", "82", "83", "124",
- "213", "219", "227", "228", "251", "621", "631", "632", "633", "641", "674", "691", "709", "731", "732", "778", "786", "787",
- "788", "790", "791", "792", "796", "797", "801", "878", "899", "912", "1531", "1532", "1544", "1545", "1546", "1736", "1737"
-}
diff --git a/data/modules/scripts/prey_system/prey.lua b/data/modules/scripts/prey_system/prey.lua
deleted file mode 100644
index c14dbc393c2..00000000000
--- a/data/modules/scripts/prey_system/prey.lua
+++ /dev/null
@@ -1,715 +0,0 @@
-dofile('data/modules/scripts/prey_system/assets.lua')
-
-Prey = {
- Credits = "System remake: Westwol ~ Packet logic: Cjaker ~ Formulas: slavidodo ~ Revision: Rick, Sameshima, RodrigoSilva93",
- Version = "6.4",
- LastUpdate = "12/02/2021",
-}
-
-Prey.Difficulty = {
- HARMLESS = 0,
- TRIVIAL = 1,
- EASY = 2,
- MEDIUM = 3,
- HARD = 4,
- CHALLEGING = 5
-}
-
-CONST_PREY_SLOT_FIRST = 0
-CONST_PREY_SLOT_SECOND = 1
-CONST_PREY_SLOT_THIRD = 2
-
-CONST_BONUS_DAMAGE_BOOST = 0
-CONST_BONUS_DAMAGE_REDUCTION = 1
-CONST_BONUS_XP_BONUS = 2
-CONST_BONUS_IMPROVED_LOOT = 3
-
-Prey.Config = {
- PreyTime = 7200, -- Seconds
- StoreSlotStorage = 63253,
- ListRerollPrice = 150,
- BonusRerollPrice = 1,
- SelectWithWildCardPrice = 5,
-}
-
-Prey.S_Packets = {
- ShowDialog = 0xED,
- PreyRerollPrice = 0xE9,
- PreyData = 0xE8,
- PreyTimeLeft = 0xE7
-}
-
-Prey.StateTypes = {
- LOCKED = 0,
- INACTIVE = 1,
- ACTIVE = 2,
- SELECTION = 3,
- SELECTION_CHANGE_MONSTER = 4,
- SELECTION_WITH_WILDCARD = 6
-}
-
-Prey.UnlockTypes = {
- PREMIUM_OR_STORE = 0,
- STORE = 1,
- NONE = 2
-}
-
-Prey.Actions = {
- NEW_LIST = 0,
- NEW_BONUS = 1,
- SELECT = 2,
- LIST_ALL_MONSTERS = 3,
- SELECT_ALL_MONSTERS = 4,
- TICK_LOCK = 5
-}
-
-Prey.C_Packets = {
- RequestData = 0xED,
- PreyAction = 0xEB
-}
-
-Prey.Bonuses = {
- [CONST_BONUS_DAMAGE_BOOST] = {7, 9, 11, 13, 15, 17, 19, 21, 23, 25},
- [CONST_BONUS_DAMAGE_REDUCTION] = {12, 14, 16, 18, 20, 22, 24, 26, 28, 30},
- [CONST_BONUS_XP_BONUS] = {13, 16, 19, 22, 25, 28, 31, 34, 37, 40},
- [CONST_BONUS_IMPROVED_LOOT] = {13, 16, 19, 22, 25, 28, 31, 34, 37, 40}
-}
-
-Prey.MonsterList = {
- "Azure Frog", "Coral Frog", "Crimson Frog", "Filth Toad", "Infernal Frog", "Orchid Frog", "Salamander", "Toad",
- "Abyssal Calamary", "Blood Crab", "Crab", "Deathling Scout", "Deathling Spellsinger", "Deepling Brawler",
- "Deepling Elite", "Deepling Guard", "Deepling Master Librarian", "Deepling Scout", "Deepling Spellsinger",
- "Deepling Tyrant", "Deepling Warrior", "Deepling Worker", "Deepsea Blood Crab", "Manta Ray", "Quara Constrictor",
- "Quara Constrictor Scout", "Quara Hydromancer", "Quara Hydromancer Scout", "Quara Mantassin", "Quara Mantassin Scout",
- "Quara Pincher", "Quara Pincher Scout", "Quara Predator", "Quara Predator Scout", "Shark", "Berserker Chicken",
- "Demon Parrot", "Marsh Stalker", "Penguin", "Terror Bird", "Biting Book", "Clay Guardian", "Damaged Worker Golem",
- "Diamond Servant", "Enraged Crystal Golem", "Eternal Guardian", "Glooth Golem", "Golden Servant", "Ice Golem",
- "Infected Weeper", "Iron Servant", "Lava Golem","Metal Gargoyle", "Orewalker", "Rotten Golem", "Rustheap Golem",
- "Sandstone Scorpion", "Stone Devourer", "Stone Golem", "Walker", "War Golem", "Weeper", "Worker Golem", "Askarak Demon",
- "Askarak Lord", "Askarak Prince", "Brachiodemon", "Dark Torturer", "Dawnfire Asura", "Demon", "Demon Outcast",
- "Destroyer", "Diabolic Imp", "Fire Devil", "Floating Savant", "Frost Flower Asura", "Fury", "Gozzler", "Grimeleech",
- "Hellfire Fighter", "Hellflayer", "Hellhound", "Hellspawn", "Infernal Demon", "Juggernaut", "Many Faces",
- "Midnight Asura", "Nightfiend", "Plaguesmith", "Shaburak Demon", "Shaburak Lord", "Shaburak Prince",
- "True Dawnfire Asura", "True Frost Flower Asura", "True Midnight Asura", "Vexclaw", "Dragon", "Dragon Hatchling",
- "Dragon Lord", "Dragon Lord Hatchling", "Draken Abomination", "Draken Elite", "Draken Spellweaver", "Draken Warmaster",
- "Elder Wyrm", "Frost Dragon", "Frost Dragon Hatchling", "Ghastly Dragon", "Hydra", "Wyrm", "Cliff Strider",
- "Earth Elemental", "Energy Elemental", "Fire Elemental", "High Voltage Elemental", "Ironblight",
- "Knowledge Elemental", "Lava Lurker", "Massive Earth Elemental", "Massive Fire Elemental", "Massive Water Elemental",
- "Ravenous Lava Lurker", "Turbulent Elemental", "Water Elemental", "Breach Brood", "Courage Leech", "Dread Intruder",
- "Instable Breach Brood", "Instable Sparkion", "Reality Reaver", "Sparkion", "Stabilizing Dread Intruder",
- "Stabilizing Reality Reaver", "Yielothax", "Arctic Faun", "Boogy", "Dark Faun", "Dryad", "Faun", "Nymph", "Percht",
- "Pixie", "Pooka", "Schiach", "Swan Maiden", "Twisted Pooka", "Behemoth", "Cyclops", "Cyclops Drone",
- "Cyclops Smith", "Frost Giant", "Frost Giantess", "Ogre Brute", "Ogre Rowdy", "Ogre Ruffian", "Ogre Sage",
- "Ogre Savage", "Ogre Shaman", "Orclops Doomhauler", "Orclops Ravager", "Acolyte of the Cult", "Adept of the Cult",
- "Adventurer", "Amazon", "Assassin", "Bandit", "Barbarian Bloodwalker", "Barbarian Brutetamer", "Barbarian Headsplitter",
- "Barbarian Skullhunter", "Black Sphinx Acolyte", "Blood Hand", "Blood Priest", "Burning Gladiator", "Cobra Assassin",
- "Cobra Scout", "Cobra Vizier", "Crazed Beggar", "Crypt Defiler", "Cult Believer", "Cult Enforcer", "Cult Scholar",
- "Dark Apprentice", "Dark Magician", "Dark Monk", "Enlightened of the Cult", "Feverish Citizen", "Gang Member",
- "Gladiator", "Glooth Bandit", "Glooth Brigand", "Goldhanded Cultist", "Grave Robber",
- "Hero", "Hunter", "Ice Witch", "Infernalist", "Mad Scientist", "Misguided Thief", "Monk",
- "Mutated Human", "Necromancer", "Nomad", "Nomad Female", "Novice of the Cult", "Pirate Buccaneer", "Pirate Corsair",
- "Pirate Cutthroat", "Pirate Marauder", "Poacher", "Priestess", "Priestess of the Wild Sun", "Renegade Knight",
- "Shadow Pupil", "Smuggler", "Stalker", "Valkyrie", "Vicious Squire", "Vile Grandmaster", "Warlock", "Wild Warrior",
- "Witch", "Barkless Devotee", "Barkless Fanatic", "Broken Shaper", "Chakoya Toolshaper", "Chakoya Tribewarden",
- "Chakoya Windcaller", "Corym Charlatan", "Corym Skirmisher", "Corym Vanguard", "Crazed Summer Rearguard",
- "Crazed Summer Vanguard", "Crazed Winter Rearguard", "Crazed Winter Vanguard", "Dwarf", "Dwarf Geomancer",
- "Dwarf Guard", "Dwarf Soldier", "Dworc Fleshhunter", "Dworc Venomsniper", "Dworc Voodoomaster", "Elf",
- "Elf Arcanist", "Elf Scout", "Enslaved Dwarf", "Execowtioner", "Firestarter", "Frost Troll", "Furious Troll",
- "Goblin", "Goblin Assassin", "Goblin Leader", "Goblin Scavenger", "Insane Siren", "Island Troll",
- "Little Corym Charlatan", "Lost Basher", "Lost Berserker", "Lost Exile", "Lost Husher", "Lost Thrower", "Minotaur",
- "Minotaur Amazon", "Minotaur Archer", "Minotaur Cult Follower", "Minotaur Cult Prophet", "Minotaur Cult Zealot",
- "Minotaur Guard", "Minotaur Hunter", "Minotaur Invader", "Minotaur Mage", "Mooh'Tah Warrior", "Moohtant", "Orc",
- "Orc Berserker", "Orc Cult Fanatic", "Orc Cult Inquisitor", "Orc Cult Minion", "Orc Cult Priest", "Orc Cultist",
- "Orc Leader", "Orc Marauder", "Orc Rider", "Orc Shaman", "Orc Spearman", "Orc Warlord", "Orc Warrior",
- "Pirat Cutthroat", "Pirat Scoundrel", "Shaper Matriarch", "Soul-Broken Harbinger", "Swamp Troll", "Troll",
- "Troll Champion", "Troll Guard", "Troll", "Troll Legionnaire", "Twisted Shaper", "Worm Priestess", "Werebadger",
- "Werebear", "Wereboar", "Werefox", "Werehyaena", "Werehyaena Shaman", "Werelion", "Werelioness", "Werewolf",
- "Animated Feather", "Arachnophobica", "Armadile", "Blue Djinn", "Bog Raider", "Bonelord", "Brain Squid",
- "Choking Fear", "Crypt Warden", "Crystal Spider", "Crystal Wolf", "Crystalcrusher", "Cursed Book", "Efreet",
- "Elder Bonelord", "Energetic Book", "Energuardian of Tales", "Enfeebled Silencer",
- "Feral Sphinx", "Feversleep", "Flying Book", "Forest Fury", "Frazzlemaw", "Gargoyle", "Gazer", "Gryphon",
- "Guardian of Tales", "Guzzlemaw", "Icecold Book", "Lamassu", "Lumbering Carnivor", "Manticore", "Marid", "Medusa",
- "Menacing Carnivor", "Midnight Panther", "Nightmare", "Nightmare Scion", "Nightstalker", "Phantasm", "Rage Squid",
- "Retching Horror", "Rorc", "Shock Head", "Sight of Surrender", "Silencer", "Sphinx", "Spiky Carnivor",
- "Squid Warden", "Terrorsleep", "Thanatursus", "Thornfire Wolf", "Weakened Frazzlemaw", "Badger", "Bat", "Bear",
- "Boar", "Cave Rat", "Clomp", "Doom Deer", "Elephant", "Evil Sheep", "Evil Sheep Lord", "Exotic Bat", "Fox",
- "Gloom Wolf", "Gnarlhound", "Hot Dog", "Killer Rabbit", "Kongra", "Lion", "Mammoth", "Merlkin", "Mole",
- "Mutated Bat", "Mutated Rat", "Mutated Tiger", "Noble Lion", "Panda", "Polar Bear", "Rat", "Roaring Lion",
- "Sibang", "Skunk", "Starving Wolf", "Stone Rhino", "Tiger", "Vulcongra", "War Wolf", "Water Buffalo",
- "White Lion", "Winter Wolf", "Wolf", "Branchy Crawler", "Carniphila", "Cloak Of Terror", "Glooth Anemone",
- "Haunted Treeling", "Hideous Fungus", "Humongous Fungus", "Leaf Golem", "Omnivora", "Spit Nettle", "Swampling",
- "Wilting Leaf Golem", "Adult Goanna", "Cobra", "Crocodile", "Killer Caiman", "Lizard Chosen",
- "Lizard Dragon Priest", "Lizard High Guard", "Lizard Legionnaire", "Lizard Magistratus", "Lizard Noble",
- "Lizard Sentinel", "Lizard Snakecharmer", "Lizard Templar", "Lizard Zaogun", "Sea Serpent", "Seacrest Serpent",
- "Serpent Spawn", "Snake", "Stampor", "Stonerefiner", "Thornback Tortoise", "Tortoise", "Wyvern", "Young Goanna",
- "Young Sea Serpent", "Acid Blob", "Death Blob", "Defiler", "Devourer", "Glooth Blob", "Ink Blob", "Mercury Blob",
- "Midnight Spawn", "Slime", "Son of Verminor", "Squidgy Slime", "Banshee", "Betrayed Wraith", "Blightwalker",
- "Blood Beast", "Bonebeast", "Bony Sea Devil", "Braindeath", "Burster Spectre", "Capricious Phantom",
- "Crypt Shambler", "Cursed Prospector", "Death Priest", "Demon Skeleton", "Distorted Phantom",
- "Druid's Apparition", "Elder Mummy", "Evil Prospector", "Falcon Knight", "Falcon Paladin", "Flimsy Lost Soul",
- "Freakish Lost Soul", "Gazer Spectre", "Ghost", "Ghost Wolf", "Ghoul", "Ghoulish Hyaena", "Grave Guard",
- "Gravedigger", "Grim Reaper", "Hand of Cursed Fate", "Honour Guard", "Infernal Phantom", "Knight's Apparition",
- "Lich", "Lost Soul", "Mean Lost Soul", "Mould Phantom", "Mummy", "Paladin's Apparition", "Pirate Ghost",
- "Pirate Skeleton", "Putrid Mummy", "Ripper Spectre", "Rot Elemental", "Skeleton", "Skeleton Elite Warrior",
- "Skeleton Warrior", "Sorcerer's Apparition", "Souleater", "Spectre", "Tarnished Spirit", "Tomb Servant",
- "Undead Dragon", "Undead Elite Gladiator", "Undead Gladiator", "Vampire", "Vampire Bride", "Vampire Pig",
- "Vampire Viscount", "Vibrant Phantom", "Vicious Manbat", "White Shade", "Zombie", "Ancient Scarab",
- "Brimstone Bug", "Bug", "Carrion Worm", "Cave Devourer", "Centipede", "Crawler", "Deepworm", "Diremaw",
- "Drillworm", "Emerald Damselfly", "Exotic Cave Spider", "Giant Spider", "Hibernal Moth", "Hive Overseer",
- "Insect Swarm", "Insectoid Scout", "Insectoid Worker", "Kollos", "Lacewing Moth", "Ladybug", "Lancer Beetle",
- "Larva", "Poison Spider", "Rotworm", "Sacred Spider", "Sandcrawler", "Scarab", "Scorpion", "Spider", "Spidris",
- "Spidris Elite", "Spitter", "Swarmer", "Tarantula", "Terramite", "Tunnel Tyrant", "Wailing Widow", "Wasp",
- "Waspoid", "Wiggler", "Terrified Elephant"
-}
-
--- Communication functions
-function Player.sendResource(self, resourceType, value)
- local typeByte = 0
- if resourceType == "bank" then
- typeByte = 0x00
- elseif resourceType == "inventory" then
- typeByte = 0x01
- elseif resourceType == "prey" then
- typeByte = 0x0A
- end
- local msg = NetworkMessage()
- msg:addByte(0xEE)
- msg:addByte(typeByte)
- msg:addU64(value)
- msg:sendToPlayer(self)
-end
-
-function Player.sendErrorDialog(self, error)
- local msg = NetworkMessage()
- msg:addByte(Prey.S_Packets.ShowDialog)
- msg:addByte(0x15)
- msg:addString(error)
- msg:sendToPlayer(self)
-end
-
--- Core functions
-function Player.setRandomBonusValue(self, slot, bonus, typeChange)
- local type = self:getPreyBonusType(slot)
- local bonusValue = math.random(1, 10)
- local starUP = math.random(1, 3)
- local value = Prey.Bonuses[type][bonusValue]
- local bonusGrade = self:getPreyBonusGrade(slot)
-
- if bonus then
- if typeChange then
- self:setPreyBonusGrade(slot, bonusValue)
- self:setPreyBonusValue(slot, value)
- else
- local upgradeStar = bonusGrade + starUP
- if upgradeStar >= 10 then
- upgradeStar = 10
- end
- local newBonus = Prey.Bonuses[type][upgradeStar]
- self:setPreyBonusGrade(slot, upgradeStar)
- self:setPreyBonusValue(slot, newBonus)
- end
- end
-end
-
-function Player.createMonsterList(self)
- -- Do not allow repeated monsters
- local repeatedList = {}
- for slot = CONST_PREY_SLOT_FIRST, CONST_PREY_SLOT_THIRD do
- if (self:getPreyCurrentMonster(slot) ~= '') then
- repeatedList[#repeatedList + 1] = self:getPreyCurrentMonster(slot)
- end
- if (self:getPreyMonsterList(slot) ~= '') then
- local currentList = self:getPreyMonsterList(slot):split(";")
- for i = 1, #currentList do
- repeatedList[#repeatedList + 1] = currentList[i]
- end
- end
- end
- -- Generating monsterList
- local monsters = {}
- local counters = {
- Trivial = 0,
- Easy = 0,
- Medium = 0,
- Hard = 0,
- Challeging = 0
- }
- while (#monsters ~= 9) do
- local randomMonster = Prey.MonsterList[math.random(#Prey.MonsterList)]
- local difficulty = getMonsterDifficulty(randomMonster)
- -- Verify that monster actually exists
- if MonsterType(randomMonster) and not table.contains(monsters, randomMonster)
- and not table.contains(repeatedList, randomMonster) then
- if difficulty == Prey.Difficulty.TRIVIAL and counters.Trivial < getMaxMonster(self, Prey.Difficulty.TRIVIAL) then
- monsters[#monsters + 1] = randomMonster
- counters.Trivial = counters.Trivial + 1
- elseif difficulty == Prey.Difficulty.EASY and counters.Easy < getMaxMonster(self, Prey.Difficulty.EASY) then
- monsters[#monsters + 1] = randomMonster
- counters.Easy = counters.Easy + 1
- elseif difficulty == Prey.Difficulty.MEDIUM and counters.Medium < getMaxMonster(self, Prey.Difficulty.MEDIUM) then
- monsters[#monsters + 1] = randomMonster
- counters.Medium = counters.Medium + 1
- elseif difficulty == Prey.Difficulty.HARD and counters.Hard < getMaxMonster(self, Prey.Difficulty.HARD) then
- monsters[#monsters + 1] = randomMonster
- counters.Hard = counters.Hard + 1
- elseif difficulty == Prey.Difficulty.CHALLEGING and counters.Challeging < getMaxMonster(self, Prey.Difficulty.CHALLEGING) then
- monsters[#monsters + 1] = randomMonster
- counters.Challeging = counters.Challeging + 1
- end
- end
- end
- return table.concat(monsters, ";")
-end
-
-function Player.resetPreySlot(self, slot, from)
- self:setPreyMonsterList(slot, self:createMonsterList())
- self:setPreyCurrentMonster(slot, "")
- self:setPreyState(slot, from)
- return self:sendPreyData(slot)
-end
-
-function Player.getMinutesUntilFreeReroll(self, slot)
- local currentTime = os.time()
- if (self:getPreyNextUse(slot) <= currentTime) then
- return 0
- end
-
- return math.floor((self:getPreyNextUse(slot) - currentTime))
-end
-
-function Player.getRerollPrice(self)
- return (self:getLevel() * Prey.Config.ListRerollPrice)
-end
-
-function getNameByRace(race)
- local mtype = MonsterType(race)
- if mtype then
- return mtype:getName()
- end
-end
-
-function Player.getMonsterList(self)
- local repeatedList = {}
- local sortList = {}
- local monsterList = {}
-
- for slot = CONST_PREY_SLOT_FIRST, CONST_PREY_SLOT_THIRD do
- if (self:getPreyCurrentMonster(slot) ~= '') then
- repeatedList[#repeatedList + 1] = self:getPreyCurrentMonster(slot)
- end
- if (self:getPreyMonsterList(slot) ~= '') then
- local currentList = self:getPreyMonsterList(slot):split(";")
- for i = 1, #currentList do
- repeatedList[#repeatedList + 1] = currentList[i]
- end
- end
- end
-
- -- Insert the monstersId
- for i = 1, #preyRaceIds do
- table.insert(sortList, preyRaceIds[i])
- end
-
- -- Do not allow repeated monsters
- for k, v in pairs(sortList) do
- if not table.contains(repeatedList, getNameByRace(tonumber(v))) then
- table.insert(monsterList, v)
- end
- end
-
- return monsterList
-end
-
-function Player.setAutomaticBonus(self, slot)
- local monster = self:getPreyCurrentMonster(slot)
-
- -- Automatic Bonus Reroll
- if self:getPreyTick(slot) == 1 and self:getPreyBonusRerolls() >= 1 then
- self:setPreyBonusType(slot, self:getDiffBonus(slot))
- self:setRandomBonusValue(slot, true, true)
- self:setPreyBonusRerolls(self:getPreyBonusRerolls() - 1)
- self:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Your %s's prey bonus was automatically rolled.", monster:lower()))
- self:setPreyTimeLeft(slot, Prey.Config.PreyTime)
-
- -- Lock Prey
- elseif self:getPreyTick(slot) == 2 and self:getPreyBonusRerolls() >= 5 then
- self:setPreyBonusRerolls(self:getPreyBonusRerolls() - 5)
- self:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Your %s's prey time was automatically renewed.", monster:lower()))
- self:setPreyTimeLeft(slot, Prey.Config.PreyTime)
- else
- self:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Your %s's prey has expired because you don't have enough prey wildcards.", monster:lower()))
- self:setPreyCurrentMonster(slot, "")
- self:setPreyTick(slot, 0)
- end
-end
-
-function onRecvbyte(player, msg, byte)
- if (byte == Prey.C_Packets.RequestData) then
- player:sendPreyData(CONST_PREY_SLOT_FIRST)
- player:sendPreyData(CONST_PREY_SLOT_SECOND)
- player:sendPreyData(CONST_PREY_SLOT_THIRD)
- elseif (byte == Prey.C_Packets.PreyAction) then
- player:preyAction(msg)
- end
-end
-
-function Player.preyAction(self, msg)
-
- local slot = msg:getByte()
- local action = msg:getByte()
-
- if not slot then
- return self:sendErrorDialog("Sorry, there was an issue, please relog-in.")
- end
-
- -- Verify whether the slot is unlocked
- if (self:getPreyUnlocked(slot) ~= 2) then
- return self:sendErrorDialog("Sorry, you don't have this slot unlocked yet.")
- end
-
- -- Listreroll
- if (action == Prey.Actions.NEW_LIST) then
-
- -- Verifying state
- if (self:getPreyState(slot) ~= Prey.StateTypes.ACTIVE and self:getPreyState(slot) ~= Prey.StateTypes.SELECTION
- and self:getPreyState(slot) ~= Prey.StateTypes.SELECTION_CHANGE_MONSTER)
- and self:getPreyState(slot) ~= Prey.StateTypes.INACTIVE then
- return self:sendErrorDialog("This is slot is not even active.")
- end
-
- -- If free reroll is available
- if (self:getMinutesUntilFreeReroll(slot) == 0) then
- self:setPreyNextUse(slot, os.time() + 20 * 60 * 60)
- elseif (not self:removeMoneyBank(self:getRerollPrice())) then
- return self:sendErrorDialog("You do not have enough money to perform this action.")
- end
-
- self:setPreyCurrentMonster(slot, "")
- self:setPreyMonsterList(slot, self:createMonsterList())
- self:setPreyState(slot, Prey.StateTypes.SELECTION_CHANGE_MONSTER)
-
- -- Listreroll with wildcards
- elseif (action == Prey.Actions.LIST_ALL_MONSTERS) then
-
- -- Removing bonus rerolls
- self:setPreyBonusRerolls(self:getPreyBonusRerolls() - 5)
-
- self:setPreyCurrentMonster(slot, "")
- self:setPreyMonsterList(slot, "")
- self:setPreyState(slot, Prey.StateTypes.SELECTION_WITH_WILDCARD)
-
- -- Select monster from the list
- elseif (action == Prey.Actions.SELECT_ALL_MONSTERS) then
- local race = msg:getU16()
- local race = getNameByRace(race)
-
- -- Converts RaceID to String
- self:setPreyCurrentMonster(slot, race)
-
- self:setPreyState(slot, Prey.StateTypes.ACTIVE)
- self:setPreyMonsterList(slot, "")
- self:setPreyTimeLeft(slot, Prey.Config.PreyTime)
-
- -- Bonus reroll
- elseif (action == Prey.Actions.NEW_BONUS) then
-
- -- Verifying state
- if (self:getPreyState(slot) ~= Prey.StateTypes.ACTIVE) then
- return self:sendErrorDialog("This is slot is not even active.")
- end
-
- if (self:getPreyBonusRerolls() < 1) then
- return self:sendErrorDialog("You don't have any bonus rerolls.")
- end
-
- -- Removing bonus rerolls
- self:setPreyBonusRerolls(self:getPreyBonusRerolls() - 1)
-
- -- Calculating new bonus
- local oldType = self:getPreyBonusType(slot)
- self:setPreyBonusType(slot, math.random(CONST_BONUS_DAMAGE_BOOST, CONST_BONUS_IMPROVED_LOOT))
- self:setRandomBonusValue(slot, true, false)
- self:setPreyTimeLeft(slot, Prey.Config.PreyTime)
-
- -- Select monster from list
- elseif (action == Prey.Actions.SELECT) then
-
- local selectedMonster = msg:getByte()
- local monsterList = self:getPreyMonsterList(slot):split(";")
-
- -- Verify if the monster exists.
- local monster = MonsterType(monsterList[selectedMonster + 1])
- if not monster then
- return self:sendPreyData(slot)
- end
-
- -- Verifying slot state
- if (self:getPreyState(slot) ~= Prey.StateTypes.SELECTION
- and self:getPreyState(slot) ~= Prey.StateTypes.SELECTION_CHANGE_MONSTER) then
- return self:sendErrorDialog("This slot can't select monsters.")
- end
-
- -- Proceeding to prey monster selection
- self:selectPreyMonster(slot, monsterList[selectedMonster + 1])
-
- -- Automatic Reroll/Lock
- elseif (action == Prey.Actions.TICK_LOCK) then
-
- local button = msg:getByte()
- if button == 1 then
- self:setPreyTick(slot, 1)
- elseif button == 2 then
- self:setPreyTick(slot, 2)
- else
- self:setPreyTick(slot, 0)
- end
- end
-
- -- Perfom slot update
- return self:sendPreyData(slot)
-end
-
-function Player.selectPreyMonster(self, slot, monster)
-
- -- Verify if the monster exists.
- local monster = MonsterType(monster)
- if not monster then
- return self:sendPreyData(slot)
- end
-
- local msg = NetworkMessage()
-
- -- Only first/expired selection list gets new prey bonus
- if (self:getPreyState(slot) == Prey.StateTypes.SELECTION) then
- -- Generating random prey type
- self:setPreyBonusType(slot, math.random(CONST_BONUS_DAMAGE_BOOST, CONST_BONUS_IMPROVED_LOOT))
- -- Generating random bonus stats
- self:setRandomBonusValue(slot, true, true)
- elseif (self:getPreyBonusGrade(slot) == 0) then
- -- Generating random prey type
- self:setPreyBonusType(slot, math.random(CONST_BONUS_DAMAGE_BOOST, CONST_BONUS_IMPROVED_LOOT))
- -- Generating random bonus stats
- self:setRandomBonusValue(slot, true, true)
- end
-
- -- Setting current monster
- self:setPreyCurrentMonster(slot, monster:getName())
- -- Setting preySlot state
- self:setPreyState(slot, Prey.StateTypes.ACTIVE)
- -- Cleaning up monsterList
- self:setPreyMonsterList(slot, "")
- -- Time left
- self:setPreyTimeLeft(slot, Prey.Config.PreyTime)
-end
-
-function Player.sendPreyData(self, slot)
- -- Unlock First Slot
- if self:getPreyState(CONST_PREY_SLOT_FIRST) == 0 then
- self:setPreyUnlocked(CONST_PREY_SLOT_FIRST, 2)
- self:setPreyState(CONST_PREY_SLOT_FIRST, 1)
- end
-
- -- Unlock/lock second slot (premium status)
- if self:isPremium() then
- if self:getPreyState(CONST_PREY_SLOT_SECOND) == 0 then
- self:setPreyUnlocked(CONST_PREY_SLOT_SECOND, 2)
- self:setPreyState(CONST_PREY_SLOT_SECOND, 1)
- end
- else
- self:setPreyUnlocked(CONST_PREY_SLOT_SECOND, 0)
- self:setPreyState(CONST_PREY_SLOT_SECOND, 0)
- end
-
- -- Unlock store slot
- if self:getPreyState(CONST_PREY_SLOT_THIRD) == 0 then
- if self:getStorageValue(Prey.Config.StoreSlotStorage) == 1 then
- self:setPreyUnlocked(CONST_PREY_SLOT_THIRD, 2)
- self:setPreyState(CONST_PREY_SLOT_THIRD, 1)
- else
- self:setPreyUnlocked(CONST_PREY_SLOT_THIRD, 1)
- self:setPreyState(CONST_PREY_SLOT_THIRD, 0)
- end
- end
-
- local slotState = self:getPreyState(slot)
- local tickState = self:getPreyTick(slot)
-
- local msg = NetworkMessage()
- msg:addByte(Prey.S_Packets.PreyData) -- packet header
-
- if slotState == Prey.StateTypes.SELECTION_CHANGE_MONSTER then
- msg:addByte(slot) -- slot number
- msg:addByte(slotState)
- msg:addByte(self:getPreyBonusType(slot))
- msg:addU16(self:getPreyBonusValue(slot))
- msg:addByte(self:getPreyBonusGrade(slot))
-
- local monsterList = self:getPreyMonsterList(slot):split(";")
- msg:addByte(#monsterList)
- for i = 1, #monsterList do
- local monster = MonsterType(monsterList[i])
- if monster then
- msg:addString(monster:getName())
- msg:addU16(monster:getOutfit().lookType or 21)
- msg:addByte(monster:getOutfit().lookHead or 0x00)
- msg:addByte(monster:getOutfit().lookBody or 0x00)
- msg:addByte(monster:getOutfit().lookLegs or 0x00)
- msg:addByte(monster:getOutfit().lookFeet or 0x00)
- msg:addByte(monster:getOutfit().lookAddons or 0x00)
- else
- return self:resetPreySlot(slot, Prey.StateTypes.SELECTION_CHANGE_MONSTER)
- end
- end
-
- elseif slotState == Prey.StateTypes.SELECTION then
- msg:addByte(slot)
- msg:addByte(slotState)
-
- local preyMonsterList = self:getPreyMonsterList(slot)
- if preyMonsterList == '' then
- self:setPreyMonsterList(slot, self:createMonsterList())
- return self:sendPreyData(slot)
- end
-
- local monsterList = preyMonsterList:split(";")
- msg:addByte(#monsterList)
- for i = 1, #monsterList do
- local monster = MonsterType(monsterList[i])
- if monster then
- msg:addString(monster:getName())
- msg:addU16(monster:getOutfit().lookType or 21)
- msg:addByte(monster:getOutfit().lookHead or 0x00)
- msg:addByte(monster:getOutfit().lookBody or 0x00)
- msg:addByte(monster:getOutfit().lookLegs or 0x00)
- msg:addByte(monster:getOutfit().lookFeet or 0x00)
- msg:addByte(monster:getOutfit().lookAddons or 0x00)
- else
- return self:resetPreySlot(slot, Prey.StateTypes.SELECTION)
- end
- end
-
- elseif slotState == Prey.StateTypes.ACTIVE then
- msg:addByte(slot)
- msg:addByte(slotState)
- local monster = MonsterType(self:getPreyCurrentMonster(slot))
- if monster and self:getPreyTimeLeft(slot) > 0 then
- msg:addString(monster:getName())
- msg:addU16(monster:getOutfit().lookType or 21)
- msg:addByte(monster:getOutfit().lookHead or 0x00)
- msg:addByte(monster:getOutfit().lookBody or 0x00)
- msg:addByte(monster:getOutfit().lookLegs or 0x00)
- msg:addByte(monster:getOutfit().lookFeet or 0x00)
- msg:addByte(monster:getOutfit().lookAddons or 0x00)
- msg:addByte(self:getPreyBonusType(slot))
- msg:addU16(self:getPreyBonusValue(slot))
- msg:addByte(self:getPreyBonusGrade(slot))
- msg:addU16(self:getPreyTimeLeft(slot))
- else
- return self:resetPreySlot(slot, Prey.StateTypes.SELECTION)
- end
-
- elseif slotState == Prey.StateTypes.INACTIVE then
- msg:addByte(slot) -- slot number
- msg:addByte(slotState) -- slot state
- self:setRandomBonusValue(slot, true, true)
-
- elseif slotState == Prey.StateTypes.LOCKED then
- msg:addByte(slot)
- msg:addByte(slotState)
- msg:addByte(self:getPreyUnlocked(slot))
-
- elseif slotState == Prey.StateTypes.SELECTION_WITH_WILDCARD then
- local raceList = self:getMonsterList()
-
- msg:addByte(slot) -- slot number
- msg:addByte(slotState) -- slot state
-
- -- Check if has any bonus
- if self:getPreyTimeLeft(slot) <= 0 then
- self:setRandomBonusValue(slot, true, true)
- end
-
- msg:addByte(self:getPreyBonusType(slot)) -- bonus type
- msg:addU16(self:getPreyBonusValue(slot)) -- bonus value
- msg:addByte(self:getPreyBonusGrade(slot)) -- bonus grade
- msg:addU16(#raceList) -- monsters count
-
- for i = 1, #raceList do
- msg:addU16(raceList[i]) -- raceID
- end
- end
-
- -- Next free reroll
- msg:addU32(self:getMinutesUntilFreeReroll(slot))
-
- -- Automatic Reroll/Lock Prey
- msg:addByte(tickState)
-
- -- send prey message
- msg:sendToPlayer(self)
-
- -- close emb window
- self:closeImbuementWindow()
-
- -- Send resources
- self:sendResource("prey", self:getPreyBonusRerolls())
- self:sendResource("bank", self:getBankBalance())
- self:sendResource("inventory", self:getMoney())
-
- -- Send reroll price
- self:sendPreyRerollPrice()
-
-end
-
-function Player:sendPreyRerollPrice()
- local msg = NetworkMessage()
-
- msg:addByte(Prey.S_Packets.PreyRerollPrice)
- msg:addU32(self:getRerollPrice())
- msg:addByte(Prey.Config.BonusRerollPrice) -- wildcards
- msg:addByte(Prey.Config.SelectWithWildCardPrice) -- select directly
-
- -- Feature unavailable
- msg:addU32(0)
- msg:addU32(0)
- msg:addByte(0)
- msg:addByte(0)
-
- msg:sendToPlayer(self)
-end
-
-function getMonsterDifficulty(monster)
- local stars
- if MonsterType(monster) == nil or MonsterType(monster):experience() == 0 then
- return 0
- else
- stars = MonsterType(monster):BestiaryStars()
- end
- return stars
-end
-
-function getMaxMonster(self, tier)
- local level = self:getLevel()
-
- if(tier == Prey.Difficulty.HARMLESS) then return 0 end
-
- if level >=8 and level <= 100 then
- if tier == Prey.Difficulty.TRIVIAL then return 1
- elseif tier == Prey.Difficulty.EASY then return 4
- elseif tier == Prey.Difficulty.MEDIUM then return 4
- elseif tier == Prey.Difficulty.HARD then return 4
- elseif tier == Prey.Difficulty.CHALLEGING then return 1 end
- elseif level >= 101 and level <= 250 then
- if tier == Prey.Difficulty.TRIVIAL then return 1
- elseif tier == Prey.Difficulty.EASY then return 3
- elseif tier == Prey.Difficulty.MEDIUM then return 5
- elseif tier == Prey.Difficulty.HARD then return 4
- elseif tier == Prey.Difficulty.CHALLEGING then return 1 end
- else
- if tier == Prey.Difficulty.TRIVIAL then return 1
- elseif tier == Prey.Difficulty.EASY then return 3
- elseif tier == Prey.Difficulty.MEDIUM then return 4
- elseif tier == Prey.Difficulty.HARD then return 5
- elseif tier == Prey.Difficulty.CHALLEGING then return 1 end
- end
-end
diff --git a/data/startup/others/functions.lua b/data/startup/others/functions.lua
index 3e5ad8ff82d..a1391b833e8 100644
--- a/data/startup/others/functions.lua
+++ b/data/startup/others/functions.lua
@@ -134,42 +134,4 @@ function loadLuaMapBookDocument(tablename)
else
Spdlog.info("Loaded ".. totals[2] .." of ".. totals[1] .." books and documents in the map")
end
-end
-
--- Functions that cannot be used in reload command, so they have been moved here
--- Prey slots consumption
-function preyTimeLeft(player, slot)
- local timeLeft = player:getPreyTimeLeft(slot) / 60
- local monster = player:getPreyCurrentMonster(slot)
- if (timeLeft >= 1) then
- local playerId = player:getId()
- local currentTime = os.time()
- local timePassed = currentTime - nextPreyTime[playerId][slot]
-
- -- Setting new timeleft
- if timePassed >= 59 then
- timeLeft = timeLeft - 1
- nextPreyTime[playerId][slot] = currentTime + 60
- end
-
- -- Sending new timeLeft
- player:setPreyTimeLeft(slot, timeLeft * 60)
- else
- -- Performing automatic Bonus/LockPrey actions
- if player:getPreyTick(slot) == 1 then
- player:setAutomaticBonus(slot)
- player:sendPreyData(slot)
- return true
- elseif player:getPreyTick(slot) == 2 then
- player:setAutomaticBonus(slot)
- player:sendPreyData(slot)
- return true
- end
-
- -- Expiring prey as there's no timeLeft
- player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format("Your %s's prey has expired.", monster:lower()))
- player:setPreyCurrentMonster(slot, "")
- end
-
- return player:sendPreyData(slot)
-end
+end
\ No newline at end of file
diff --git a/schema.sql b/schema.sql
index f7bcb875b58..7318d0aa51a 100644
--- a/schema.sql
+++ b/schema.sql
@@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS `server_config` (
CONSTRAINT `server_config_pk` PRIMARY KEY (`config`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '0'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
+INSERT INTO `server_config` (`config`, `value`) VALUES ('db_version', '1'), ('motd_hash', ''), ('motd_num', '0'), ('players_record', '0');
-- Table structure `accounts`
CREATE TABLE IF NOT EXISTS `accounts` (
@@ -124,15 +124,13 @@ CREATE TABLE IF NOT EXISTS `players` (
`skill_manaleech_amount` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
`manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
`max_manashield` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
- `prey_stamina_1` int(11) DEFAULT NULL,
- `prey_stamina_2` int(11) DEFAULT NULL,
- `prey_stamina_3` int(11) DEFAULT NULL,
- `prey_column` smallint(6) NOT NULL DEFAULT '1',
`xpboost_stamina` smallint(5) DEFAULT NULL,
`xpboost_value` tinyint(4) DEFAULT NULL,
`marriage_status` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
`marriage_spouse` int(11) NOT NULL DEFAULT '-1',
`bonus_rerolls` bigint(21) NOT NULL DEFAULT '0',
+ `prey_wildcard` bigint(21) NOT NULL DEFAULT '0',
+ `task_points` bigint(21) NOT NULL DEFAULT '0',
`quickloot_fallback` tinyint(1) DEFAULT '0',
`lookmountbody` tinyint(3) unsigned NOT NULL DEFAULT '0',
`lookmountfeet` tinyint(3) unsigned NOT NULL DEFAULT '0',
@@ -607,23 +605,30 @@ CREATE TABLE IF NOT EXISTS `player_namelocks` (
-- Table structure `player_prey`
CREATE TABLE IF NOT EXISTS `player_prey` (
`player_id` int(11) NOT NULL,
- `name` varchar(50) NOT NULL,
- `mindex` smallint(6) NOT NULL,
- `mcolumn` int(11) NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
--- Table structure `player_preytimes`
-CREATE TABLE IF NOT EXISTS `player_preytimes` (
+ `slot` tinyint(1) NOT NULL,
+ `state` tinyint(1) NOT NULL,
+ `raceid` varchar(250) NOT NULL,
+ `option` tinyint(1) NOT NULL,
+ `bonus_type` tinyint(1) NOT NULL,
+ `bonus_rarity` tinyint(1) NOT NULL,
+ `bonus_percentage` varchar(250) NOT NULL,
+ `bonus_time` varchar(250) NOT NULL,
+ `free_reroll` bigint(20) NOT NULL,
+ `monster_list` BLOB NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- Table structure `player_taskhunt`
+CREATE TABLE IF NOT EXISTS `player_taskhunt` (
`player_id` int(11) NOT NULL,
- `bonus_type1` int(11) NOT NULL,
- `bonus_value1` int(11) NOT NULL,
- `bonus_name1` varchar(50) NOT NULL,
- `bonus_type2` int(11) NOT NULL,
- `bonus_value2` int(11) NOT NULL,
- `bonus_name2` varchar(50) NOT NULL,
- `bonus_type3` int(11) NOT NULL,
- `bonus_value3` int(11) NOT NULL,
- `bonus_name3` varchar(50) NOT NULL
+ `slot` tinyint(1) NOT NULL,
+ `state` tinyint(1) NOT NULL,
+ `raceid` varchar(250) NOT NULL,
+ `upgrade` tinyint(1) NOT NULL,
+ `rarity` tinyint(1) NOT NULL,
+ `kills` varchar(250) NOT NULL,
+ `disabled_time` bigint(20) NOT NULL,
+ `free_reroll` bigint(20) NOT NULL,
+ `monster_list` BLOB NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Table structure `player_rewards`
@@ -708,27 +713,6 @@ CREATE TABLE IF NOT EXISTS `towns` (
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;
--- Table structure `prey_slots`
-CREATE TABLE IF NOT EXISTS `prey_slots` (
- `player_id` int(11) NOT NULL,
- `num` smallint(2) NOT NULL,
- `state` smallint(2) NOT NULL DEFAULT '1',
- `unlocked` smallint(2) NOT NULL DEFAULT '0',
- `current` varchar(40) NOT NULL DEFAULT '',
- `monster_list` varchar(360) NOT NULL,
- `free_reroll_in` int(11) NOT NULL DEFAULT '0',
- `time_left` smallint(5) NOT NULL DEFAULT '0',
- `next_use` int(11) NOT NULL DEFAULT '0',
- `bonus_type` smallint(3) NOT NULL,
- `bonus_value` smallint(3) NOT NULL DEFAULT '0',
- `bonus_grade` smallint(3) NOT NULL DEFAULT '0',
- `tick` smallint(3) NOT NULL DEFAULT '0',
- INDEX `player_id` (`player_id`),
- CONSTRAINT `prey_slots_players_fk`
- FOREIGN KEY (`player_id`) REFERENCES `players` (`id`)
- ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
-- Create Account god/god
INSERT INTO `accounts`
(`id`, `name`, `email`, `password`, `type`) VALUES
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 247f835f267..2088d68114d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -181,6 +181,7 @@ target_sources(${PROJECT_NAME}
io/iomap.cpp
io/iomapserialize.cpp
io/iomarket.cpp
+ io/ioprey.cpp
items/bed.cpp
items/containers/container.cpp
items/containers/depot/depotchest.cpp
diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp
index 3c0c69f4f96..7f0609d244a 100644
--- a/src/config/config_definitions.hpp
+++ b/src/config/config_definitions.hpp
@@ -65,6 +65,10 @@ enum booleanConfig_t {
SORT_LOOT_BY_CHANCE,
TOGLE_SAVE_INTERVAL,
TOGLE_SAVE_INTERVAL_CLEAN_MAP,
+ PREY_ENABLED,
+ PREY_FREE_THIRD_SLOT,
+ TASK_HUNTING_ENABLED,
+ TASK_HUNTING_FREE_THIRD_SLOT,
STASH_MOVING,
TOGLE_IMBUEMENT_SHRINE_STORAGE,
AUTOLOOT,
@@ -156,6 +160,18 @@ enum integerConfig_t {
STAMINA_PZ_GAIN,
STAMINA_TRAINER_GAIN,
SAVE_INTERVAL_TIME,
+ PREY_REROLL_PRICE_LEVEL,
+ PREY_SELECTION_LIST_PRICE,
+ PREY_BONUS_PERCENT_MIN,
+ PREY_BONUS_PERCENT_MAX,
+ PREY_BONUS_TIME,
+ PREY_BONUS_REROLL_PRICE,
+ PREY_FREE_REROLL_TIME,
+ TASK_HUNTING_LIMIT_EXHAUST,
+ TASK_HUNTING_REROLL_PRICE_LEVEL,
+ TASK_HUNTING_SELECTION_LIST_PRICE,
+ TASK_HUNTING_BONUS_REROLL_PRICE,
+ TASK_HUNTING_FREE_REROLL_TIME,
MAX_ALLOWED_ON_A_DUMMY,
FREE_QUEST_STAGE,
DEPOTCHEST,
diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp
index ebfee7d1076..cddb58c66c3 100644
--- a/src/config/configmanager.cpp
+++ b/src/config/configmanager.cpp
@@ -269,6 +269,24 @@ bool ConfigManager::load()
floating[RATE_NPC_ATTACK] = getGlobalFloat(L, "rateNpcAttack", 1.0);
floating[RATE_NPC_DEFENSE] = getGlobalFloat(L, "rateNpcDefense", 1.0);
+ boolean[PREY_ENABLED] = getGlobalBoolean(L, "preySystemEnabled", true);
+ boolean[PREY_FREE_THIRD_SLOT] = getGlobalBoolean(L, "preyFreeThirdSlot", false);
+ integer[PREY_REROLL_PRICE_LEVEL] = getGlobalNumber(L, "preyRerollPricePerLevel", 200);
+ integer[PREY_SELECTION_LIST_PRICE] = getGlobalNumber(L, "preySelectListPrice", 1);
+ integer[PREY_BONUS_PERCENT_MIN] = getGlobalNumber(L, "preyBonusPercentMin", 5);
+ integer[PREY_BONUS_PERCENT_MAX] = getGlobalNumber(L, "preyBonusPercentMax", 40);
+ integer[PREY_BONUS_TIME] = getGlobalNumber(L, "preyBonusTime", 7200);
+ integer[PREY_BONUS_REROLL_PRICE] = getGlobalNumber(L, "preyBonusRerollPrice", 2);
+ integer[PREY_FREE_REROLL_TIME] = getGlobalNumber(L, "preyFreeRerollTime", 72000);
+
+ boolean[TASK_HUNTING_ENABLED] = getGlobalBoolean(L, "taskHuntingSystemEnabled", true);
+ boolean[TASK_HUNTING_FREE_THIRD_SLOT] = getGlobalBoolean(L, "taskHuntingFreeThirdSlot", false);
+ integer[TASK_HUNTING_LIMIT_EXHAUST] = getGlobalNumber(L, "taskHuntingLimitedTasksExhaust", 72000);
+ integer[TASK_HUNTING_REROLL_PRICE_LEVEL] = getGlobalNumber(L, "taskHuntingRerollPricePerLevel", 200);
+ integer[TASK_HUNTING_SELECTION_LIST_PRICE] = getGlobalNumber(L, "taskHuntingSelectListPrice", 1);
+ integer[TASK_HUNTING_BONUS_REROLL_PRICE] = getGlobalNumber(L, "taskHuntingBonusRerollPrice", 2);
+ integer[TASK_HUNTING_FREE_REROLL_TIME] = getGlobalNumber(L, "taskHuntingFreeRerollTime", 72000);
+
loaded = true;
lua_close(L);
return true;
diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp
index f2ac309c059..491554c2d40 100644
--- a/src/creatures/combat/combat.cpp
+++ b/src/creatures/combat/combat.cpp
@@ -28,7 +28,6 @@
#include "creatures/monsters/monsters.h"
#include "items/weapons/weapons.h"
-
CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const
{
CombatDamage damage;
@@ -577,8 +576,7 @@ void Combat::CombatConditionFunc(Creature* caster, Creature* target, const Comba
if (playerCharmRaceid != 0) {
const MonsterType* mType = g_monsters().getMonsterType(caster->getName());
if (mType && playerCharmRaceid == mType->info.raceid) {
- IOBestiary g_bestiary;
- Charm* charm = g_bestiary.getBestiaryCharm(CHARM_CLEANSE);
+ Charm* charm = g_iobestiary().getBestiaryCharm(CHARM_CLEANSE);
if (charm && (charm->chance > normal_random(0, 100))) {
if (player->hasCondition(condition->getType())) {
player->removeCondition(condition->getType());
@@ -902,8 +900,7 @@ void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da
if (playerCharmRaceid != 0) {
const MonsterType* mType = g_monsters().getMonsterType(target->getName());
if (mType && playerCharmRaceid == mType->info.raceid) {
- IOBestiary g_bestiary;
- Charm* charm = g_bestiary.getBestiaryCharm(CHARM_LOW);
+ Charm* charm = g_iobestiary().getBestiaryCharm(CHARM_LOW);
if (charm) {
chance += charm->percent;
}
diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp
index 7daff812b55..766d139a5c4 100644
--- a/src/creatures/combat/condition.cpp
+++ b/src/creatures/combat/condition.cpp
@@ -22,7 +22,6 @@
#include "creatures/combat/condition.h"
#include "game/game.h"
-
bool Condition::setParam(ConditionParam_t param, int32_t value)
{
switch (param) {
diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp
index b76f6289e9e..5de6d269c46 100644
--- a/src/creatures/combat/spells.cpp
+++ b/src/creatures/combat/spells.cpp
@@ -25,6 +25,7 @@
#include "game/game.h"
#include "lua/scripts/lua_environment.hpp"
#include "utils/pugicast.h"
+
Spells::Spells()
{
scriptInterface.initState();
diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp
index 4dc3b9111e5..21c5d371fae 100644
--- a/src/creatures/creature.cpp
+++ b/src/creatures/creature.cpp
@@ -29,7 +29,6 @@ double Creature::speedA = 857.36;
double Creature::speedB = 261.29;
double Creature::speedC = -4795.01;
-
Creature::Creature()
{
onIdleStatus();
diff --git a/src/creatures/interactions/chat.cpp b/src/creatures/interactions/chat.cpp
index 2af47734162..7eb5c390b2a 100644
--- a/src/creatures/interactions/chat.cpp
+++ b/src/creatures/interactions/chat.cpp
@@ -24,7 +24,6 @@
#include "utils/pugicast.h"
#include "game/scheduling/scheduler.h"
-
bool PrivateChatChannel::isInvited(uint32_t guid) const
{
if (guid == getOwner()) {
diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp
index 62541f27b4a..b5cc76834b4 100644
--- a/src/creatures/monsters/monster.cpp
+++ b/src/creatures/monsters/monster.cpp
@@ -26,7 +26,6 @@
#include "creatures/combat/spells.h"
#include "lua/creature/events.h"
-
int32_t Monster::despawnRange;
int32_t Monster::despawnRadius;
diff --git a/src/creatures/monsters/monster.h b/src/creatures/monsters/monster.h
index 0f69551cd18..39e954dac66 100644
--- a/src/creatures/monsters/monster.h
+++ b/src/creatures/monsters/monster.h
@@ -208,6 +208,9 @@ class Monster final : public Creature
bool getIgnoreFieldDamage() const {
return ignoreFieldDamage;
}
+ uint16_t getRaceId() const {
+ return mType->info.raceid;
+ }
BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
bool checkDefense = false, bool checkArmor = false, bool field = false) override;
diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp
index aee54a42392..3a3b954b064 100644
--- a/src/creatures/monsters/monsters.cpp
+++ b/src/creatures/monsters/monsters.cpp
@@ -28,7 +28,6 @@
#include "utils/pugicast.h"
-
spellBlock_t::~spellBlock_t()
{
if (combatSpell) {
diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp
index abad561e4ae..2e2724b5a0f 100644
--- a/src/creatures/monsters/spawns/spawn_monster.cpp
+++ b/src/creatures/monsters/spawns/spawn_monster.cpp
@@ -27,7 +27,6 @@
#include "utils/pugicast.h"
#include "lua/creature/events.h"
-
static constexpr int32_t MONSTER_MINSPAWN_INTERVAL = 1000; // 1 second
static constexpr int32_t MONSTER_MAXSPAWN_INTERVAL = 86400000; // 1 day
diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp
index f0a2a1f0c32..257ea98911b 100644
--- a/src/creatures/players/player.cpp
+++ b/src/creatures/players/player.cpp
@@ -36,7 +36,6 @@
#include "items/weapons/weapons.h"
#include "io/iobestiary.h"
-
MuteCountMap Player::muteCountMap;
uint32_t Player::playerAutoID = 0x10010000;
@@ -74,6 +73,18 @@ Player::~Player()
it.second->decrementReferenceCounter();
}
+ for (PreySlot* slot : preys) {
+ if (slot) {
+ delete slot;
+ }
+ }
+
+ for (TaskHuntingSlot* slot : taskHunting) {
+ if (slot) {
+ delete slot;
+ }
+ }
+
inbox->stopDecaying();
inbox->decrementReferenceCounter();
@@ -4403,6 +4414,17 @@ 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!");
+ }
+ reloadTaskSlot(taskSlot->id);
+ }
}
return unjustified;
@@ -5808,6 +5830,52 @@ void Player::openPlayerContainers()
}
}
+void Player::initializePrey()
+{
+ if (preys.empty()) {
+ for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) {
+ auto slot = new PreySlot(static_cast(slotId));
+ if (!g_configManager().getBoolean(PREY_ENABLED)) {
+ slot->state = PreyDataState_Inactive;
+ } else if (slot->id == PreySlot_Three && !g_configManager().getBoolean(PREY_FREE_THIRD_SLOT)) {
+ slot->state = PreyDataState_Locked;
+ } else {
+ slot->state = PreyDataState_Selection;
+ slot->reloadMonsterGrid(getPreyBlackList(), getLevel());
+ }
+
+ if (!setPreySlotClass(slot)) {
+ delete slot;
+ }
+ }
+ }
+}
+
+void Player::initializeTaskHunting()
+{
+ if (taskHunting.empty()) {
+ for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) {
+ auto slot = new TaskHuntingSlot(static_cast(slotId));
+ if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ slot->state = PreyTaskDataState_Inactive;
+ } else if (slot->id == PreySlot_Three && !g_configManager().getBoolean(TASK_HUNTING_FREE_THIRD_SLOT)) {
+ slot->state = PreyTaskDataState_Locked;
+ } else {
+ slot->state = PreyTaskDataState_Selection;
+ slot->reloadMonsterGrid(getTaskHuntingBlackList(), getLevel());
+ }
+
+ if (!setTaskHuntingSlotClass(slot)) {
+ delete slot;
+ }
+ }
+ }
+
+ if (client && g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ client->writeToOutputBuffer(g_ioprey().GetTaskHuntingBaseDate());
+ }
+}
+
std::string Player::getBlessingsName() const
{
uint8_t count = 0;
@@ -5841,6 +5909,14 @@ std::string Player::getBlessingsName() const
return os.str();
}
+bool Player::isCreatureUnlockedOnTaskHunting(const MonsterType* mtype) const {
+ if (!mtype) {
+ return false;
+ }
+
+ return getBestiaryKillCount(mtype->info.raceid) >= mtype->info.bestiaryToUnlock;
+}
+
/*******************************************************************************
* Interfaces
******************************************************************************/
diff --git a/src/creatures/players/player.h b/src/creatures/players/player.h
index 87e2bd242cd..0109ea3676a 100644
--- a/src/creatures/players/player.h
+++ b/src/creatures/players/player.h
@@ -34,6 +34,7 @@
#include "imbuements/imbuements.h"
#include "items/containers/inbox/inbox.h"
#include "io/ioguild.h"
+#include "io/ioprey.h"
#include "creatures/appearance/mounts/mounts.h"
#include "creatures/appearance/outfit/outfit.h"
#include "grouping/party.h"
@@ -53,6 +54,8 @@ class SchedulerTask;
class Bed;
class Guild;
class Imbuement;
+class PreySlot;
+class TaskHuntingSlot;
struct OpenContainer {
Container* container;
@@ -175,69 +178,6 @@ class Player final : public Creature, public Cylinder
}
}
- // New Prey
- uint16_t getPreyState(uint16_t slot) const {
- return preySlotState[slot];
- }
-
- uint16_t getPreyUnlocked(uint16_t slot) const {
- return preySlotUnlocked[slot];
- }
-
- std::string getPreyCurrentMonster(uint16_t slot) const {
- return preySlotCurrentMonster[slot];
- }
-
- std::string getPreyMonsterList(uint16_t slot) const {
- return preySlotMonsterList[slot];
- }
-
- uint16_t getPreyFreeRerollIn(uint16_t slot) const {
- return preySlotFreeRerollIn[slot];
- }
-
- uint16_t getPreyTimeLeft(uint16_t slot) const {
- return preySlotTimeLeft[slot];
- }
-
- uint32_t getPreyNextUse(uint16_t slot) const {
- return preySlotNextUse[slot];
- }
-
- uint16_t getPreyBonusType(uint16_t slot) const {
- return preySlotBonusType[slot];
- }
-
- uint16_t getPreyBonusValue(uint16_t slot) const {
- return preySlotBonusValue[slot];
- }
-
- uint16_t getPreyBonusGrade(uint16_t slot) const {
- return preySlotBonusGrade[slot];
- }
-
- uint16_t getPreyBonusRerolls() const {
- return preyBonusRerolls;
- }
-
- uint16_t getPreyTick(uint16_t slot) const {
- return preySlotTick[slot];
- }
- //
-
- uint16_t getPreyStamina(uint16_t index) const {
- return preyStaminaMinutes[index];
- }
- uint16_t getPreyType(uint16_t index) const {
- return preyBonusType[index];
- }
- uint16_t getPreyValue(uint16_t index) const {
- return preyBonusValue[index];
- }
- std::string getPreyName(uint16_t index) const {
- return preyBonusName[index];
- }
-
bool addOfflineTrainingTries(skills_t skill, uint64_t tries);
void addOfflineTrainingTime(int32_t addTime) {
@@ -1879,6 +1819,215 @@ class Player final : public Creature, public Cylinder
error_t SetAccountInterface(account::Account *account);
error_t GetAccountInterface(account::Account *account);
+ void sendMessageDialog(const std::string& message) const
+ {
+ if (client) {
+ client->sendMessageDialog(message);
+ }
+ }
+
+ // Prey system
+ void initializePrey();
+
+ void sendPreyData() const {
+ if (client) {
+ for (const PreySlot* slot : preys) {
+ client->sendPreyData(slot);
+ }
+
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards());
+ }
+ }
+
+ void sendPreyTimeLeft(const PreySlot* slot) const {
+ if (g_configManager().getBoolean(PREY_ENABLED) && client) {
+ client->sendPreyTimeLeft(slot);
+ }
+ }
+
+ void reloadPreySlot(PreySlot_t slotid) {
+ if (g_configManager().getBoolean(PREY_ENABLED) && client) {
+ client->sendPreyData(getPreySlotById(slotid));
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ }
+ }
+
+ PreySlot* getPreySlotById(PreySlot_t slotid) {
+ if (auto it = std::find_if(preys.begin(), preys.end(), [slotid](const PreySlot* preyIt) {
+ return preyIt->id == slotid;
+ }); it != preys.end()) {
+ return *it;
+ }
+
+ return nullptr;
+ }
+
+ bool setPreySlotClass(PreySlot* slot) {
+ if (getPreySlotById(slot->id)) {
+ return false;
+ }
+
+ preys.push_back(slot);
+ return true;
+ }
+
+ bool usePreyCards(uint16_t amount) {
+ if (preyCards < amount) {
+ return false;
+ }
+
+ preyCards -= amount;
+ if (client) {
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ }
+ return true;
+ }
+
+ void addPreyCards(uint64_t amount) {
+ preyCards += amount;
+ if (client) {
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ }
+ }
+
+ uint64_t getPreyCards() const {
+ return preyCards;
+ }
+
+ uint32_t getPreyRerollPrice() const {
+ return getLevel() * g_configManager().getNumber(PREY_REROLL_PRICE_LEVEL);
+ }
+
+ std::vector getPreyBlackList() const {
+ std::vector rt;
+ for (const PreySlot* slot : preys) {
+ if (slot) {
+ if (slot->isOccupied()) {
+ rt.push_back(slot->selectedRaceId);
+ }
+ for (uint16_t raceId : slot->raceIdList) {
+ rt.push_back(raceId);
+ }
+ }
+ }
+
+ return rt;
+ }
+
+ PreySlot* getPreyWithMonster(uint16_t raceId) const {
+ if (!g_configManager().getBoolean(PREY_ENABLED)) {
+ return nullptr;
+ }
+
+ if (auto it = std::find_if(preys.begin(), preys.end(), [raceId](const PreySlot* it) {
+ return it->selectedRaceId == raceId;
+ }); it != preys.end()) {
+ return *it;
+ }
+
+ return nullptr;
+ }
+
+ // Task hunting system
+ void initializeTaskHunting();
+ bool isCreatureUnlockedOnTaskHunting(const MonsterType* mtype) const;
+
+ bool setTaskHuntingSlotClass(TaskHuntingSlot* slot) {
+ if (getTaskHuntingSlotById(slot->id)) {
+ return false;
+ }
+
+ taskHunting.push_back(slot);
+ return true;
+ }
+
+ void reloadTaskSlot(PreySlot_t slotid) {
+ if (g_configManager().getBoolean(TASK_HUNTING_ENABLED) && client) {
+ client->sendTaskHuntingData(getTaskHuntingSlotById(slotid));
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ }
+ }
+
+ TaskHuntingSlot* getTaskHuntingSlotById(PreySlot_t slotid) {
+ if (auto it = std::find_if(taskHunting.begin(), taskHunting.end(), [slotid](const TaskHuntingSlot* itTask) {
+ return itTask->id == slotid;
+ }); it != taskHunting.end()) {
+ return *it;
+ }
+
+ return nullptr;
+ }
+
+ std::vector getTaskHuntingBlackList() const {
+ std::vector rt;
+
+ std::for_each(taskHunting.begin(), taskHunting.end(), [&rt](const TaskHuntingSlot* slot)
+ {
+ if (slot->isOccupied()) {
+ rt.push_back(slot->selectedRaceId);
+ } else {
+ std::for_each(slot->raceIdList.begin(), slot->raceIdList.end(), [&rt](uint16_t raceId)
+ {
+ rt.push_back(raceId);
+ });
+ }
+ });
+
+ return rt;
+ }
+
+ void sendTaskHuntingData() const {
+ if (client) {
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ for (const TaskHuntingSlot* slot : taskHunting) {
+ if (slot) {
+ client->sendTaskHuntingData(slot);
+ }
+ }
+ }
+ }
+
+ void addTaskHuntingPoints(uint16_t amount) {
+ taskHuntingPoints += amount;
+ if (client) {
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ }
+ }
+
+ bool useTaskHuntingPoints(uint64_t amount) {
+ if (taskHuntingPoints < amount) {
+ return false;
+ }
+
+ taskHuntingPoints -= amount;
+ if (client) {
+ client->sendResourcesBalance(getMoney(), getBankBalance(), getPreyCards(), getTaskHuntingPoints());
+ }
+ return true;
+ }
+
+ uint64_t getTaskHuntingPoints() const {
+ return taskHuntingPoints;
+ }
+
+ uint32_t getTaskHuntingRerollPrice() const {
+ return getLevel() * g_configManager().getNumber(TASK_HUNTING_REROLL_PRICE_LEVEL);
+ }
+
+ TaskHuntingSlot* getTaskHuntingWithCreature(uint16_t raceId) const {
+ if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ return nullptr;
+ }
+
+ if (auto it = std::find_if(taskHunting.begin(), taskHunting.end(), [raceId](const TaskHuntingSlot* itTask) {
+ return itTask->selectedRaceId == raceId;
+ }); it != taskHunting.end()) {
+ return *it;
+ }
+
+ return nullptr;
+ }
+
private:
std::forward_list getMuteConditions() const;
@@ -1971,6 +2120,9 @@ class Player final : public Creature, public Cylinder
std::vector outfits;
std::vector familiars;
+ std::vector preys;
+ std::vector taskHunting;
+
GuildWarVector guildWarVector;
std::forward_list invitePartyList;
@@ -1997,6 +2149,8 @@ class Player final : public Creature, public Cylinder
uint64_t lastAttack = 0;
uint64_t bankBalance = 0;
uint64_t lastQuestlogUpdate = 0;
+ uint64_t preyCards = 0;
+ uint64_t taskHuntingPoints = 0;
int64_t lastFailedFollow = 0;
int64_t skullTicks = 0;
int64_t lastWalkthroughAttempt = 0;
@@ -2068,10 +2222,6 @@ class Player final : public Creature, public Cylinder
uint16_t lastStatsTrainingTime = 0;
uint16_t staminaMinutes = 2520;
- std::vector preyStaminaMinutes = {7200, 7200, 7200};
- std::vector preyBonusType = {0, 0, 0};
- std::vector preyBonusValue = {0, 0, 0};
- std::vector preyBonusName = {"", "", ""};
std::vector blessings = { 0, 0, 0, 0, 0, 0, 0, 0 };
uint16_t maxWriteLen = 0;
uint16_t baseXpGain = 100;
@@ -2109,20 +2259,6 @@ class Player final : public Creature, public Cylinder
int32_t UnlockedRunesBit = 0;
std::pair cleanseCondition = {CONDITION_NONE, 0};
- // New Prey
- uint16_t preyBonusRerolls = 0;
- std::vector preySlotState = {0, 0, 0};
- std::vector preySlotUnlocked = {0, 0, 0};
- std::vector preySlotCurrentMonster = { "", "", "" };
- std::vector preySlotMonsterList = { "", "", "" };
- std::vector preySlotFreeRerollIn = { 0, 0, 0 };
- std::vector preySlotTimeLeft = {7200, 7200, 7200};
- std::vector preySlotNextUse = { 0, 0, 0 };
- std::vector preySlotBonusType = {0, 0, 0};
- std::vector preySlotBonusValue = {0, 0, 0};
- std::vector preySlotBonusGrade = { 0, 0, 0 };
- std::vector preySlotTick = { 0, 0, 0 };
-
uint8_t soul = 0;
uint8_t levelPercent = 0;
double_t magLevelPercent = 0;
diff --git a/src/game/game.cpp b/src/game/game.cpp
index f756acff612..61a06dad0ae 100644
--- a/src/game/game.cpp
+++ b/src/game/game.cpp
@@ -47,6 +47,7 @@
#include "creatures/npcs/npc.h"
#include "creatures/npcs/npcs.h"
#include "server/network/webhook/webhook.h"
+
Game::Game()
{
offlineTrainingWindow.choices.emplace_back("Sword Fighting and Shielding", SKILL_SWORD);
@@ -5844,6 +5845,34 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
damage.primary.value = std::abs(damage.primary.value);
damage.secondary.value = std::abs(damage.secondary.value);
+ Monster* targetMonster;
+ if (target && target->getMonster()) {
+ targetMonster = target->getMonster();
+ } else {
+ targetMonster = nullptr;
+ }
+
+ const Monster* attackerMonster;
+ if (attacker && attacker->getMonster()) {
+ attackerMonster = attacker->getMonster();
+ } else {
+ attackerMonster = nullptr;
+ }
+
+ if (attackerPlayer && targetMonster) {
+ const PreySlot* slot = attackerPlayer->getPreyWithMonster(targetMonster->getRaceId());
+ if (slot && slot->isOccupied() && slot->bonus == PreyBonus_Damage && slot->bonusTimeLeft > 0) {
+ damage.primary.value += static_cast(std::ceil((damage.primary.value * slot->bonusPercentage) / 100));
+ damage.secondary.value += static_cast(std::ceil((damage.secondary.value * slot->bonusPercentage) / 100));
+ }
+ } else if (attackerMonster && targetPlayer) {
+ const PreySlot* slot = targetPlayer->getPreyWithMonster(attackerMonster->getRaceId());
+ if (slot && slot->isOccupied() && slot->bonus == PreyBonus_Defense && slot->bonusTimeLeft > 0) {
+ damage.primary.value -= static_cast(std::ceil((damage.primary.value * slot->bonusPercentage) / 100));
+ damage.secondary.value -= static_cast(std::ceil((damage.secondary.value * slot->bonusPercentage) / 100));
+ }
+ }
+
TextMessage message;
message.position = targetPos;
@@ -5870,25 +5899,20 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
addMagicEffect(spectators, targetPos, CONST_ME_CRITICAL_DAMAGE);
}
- if (!damage.extension && attacker && attacker->getMonster() && targetPlayer) {
+ if (!damage.extension && attackerMonster && targetPlayer) {
// Charm rune (target as player)
- MonsterType* mType = g_monsters().getMonsterType(attacker->getName());
- if (mType) {
- IOBestiary g_bestiary;
- charmRune_t activeCharm = g_bestiary.getCharmFromTarget(targetPlayer, mType);
- if (activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE) {
- Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
- if (charm && charm->type == CHARM_DEFENSIVE &&(charm->chance > normal_random(0, 100))) {
- if (g_bestiary.parseCharmCombat(charm, targetPlayer, attacker, (damage.primary.value + damage.secondary.value))) {
- return false; // Dodge charm
- }
- }
+ if (charmRune_t activeCharm = g_iobestiary().getCharmFromTarget(targetPlayer, g_monsters().getMonsterTypeByRaceId(attackerMonster->getRaceId()));
+ activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE) {
+ if (Charm* charm = g_iobestiary().getBestiaryCharm(activeCharm);
+ charm->type == CHARM_DEFENSIVE && charm->chance > normal_random(0, 100) &&
+ g_iobestiary().parseCharmCombat(charm, targetPlayer, attacker, (damage.primary.value + damage.secondary.value))) {
+ return false; // Dodge charm
}
}
}
if (target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) {
- int32_t manaDamage = std::min(target->getMana(), healthChange);
+ int32_t manaDamage = std::min(target->getMana(), healthChange);
uint16_t manaShield = target->getManaShield();
if (manaShield > 0) {
if (manaShield > manaDamage) {
@@ -6028,16 +6052,14 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
}
target->drainHealth(attacker, realDamage);
- if (realDamage > 0) {
- if (Monster* targetMonster = target->getMonster()) {
- if (attackerPlayer && attackerPlayer->getPlayer()) {
- attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value);
- }
+ if (realDamage > 0 && targetMonster) {
+ if (attackerPlayer && attackerPlayer->getPlayer()) {
+ attackerPlayer->updateImpactTracker(damage.secondary.type, damage.secondary.value);
+ }
- if (targetMonster->israndomStepping()) {
- targetMonster->setIgnoreFieldDamage(true);
- targetMonster->updateMapCache();
- }
+ if (targetMonster->israndomStepping()) {
+ targetMonster->setIgnoreFieldDamage(true);
+ targetMonster->updateMapCache();
}
}
@@ -6045,19 +6067,14 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
if (attackerPlayer) {
//life leech
uint16_t lifeChance = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_CHANCE);
- uint16_t lifeSkill = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_AMOUNT);
+ uint16_t lifeSkill = attackerPlayer->getSkillLevel(SKILL_LIFE_LEECH_AMOUNT);
if (normal_random(0, 100) < lifeChance) {
// Vampiric charm rune
- if (target && target->getMonster()) {
- uint16_t playerCharmRaceidVamp = attackerPlayer->parseRacebyCharm(CHARM_VAMP, false, 0);
- if (playerCharmRaceidVamp != 0) {
- const MonsterType* mType = g_monsters().getMonsterType(target->getName());
- if (mType && playerCharmRaceidVamp == mType->info.raceid) {
- IOBestiary g_bestiary;
- Charm* lifec = g_bestiary.getBestiaryCharm(CHARM_VAMP);
- if (lifec) {
- lifeSkill += lifec->percent;
- }
+ if (targetMonster) {
+ if (uint16_t playerCharmRaceidVamp = attackerPlayer->parseRacebyCharm(CHARM_VAMP, false, 0);
+ playerCharmRaceidVamp != 0 && playerCharmRaceidVamp == targetMonster->getRaceId()) {
+ if (const Charm* lifec = g_iobestiary().getBestiaryCharm(CHARM_VAMP)) {
+ lifeSkill += lifec->percent;
}
}
}
@@ -6077,16 +6094,11 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
uint16_t manaSkill = attackerPlayer->getSkillLevel(SKILL_MANA_LEECH_AMOUNT);
if (normal_random(0, 100) < manaChance) {
// Void charm rune
- if (target && target->getMonster()) {
- uint16_t playerCharmRaceidVoid = attackerPlayer->parseRacebyCharm(CHARM_VOID, false, 0);
- if (playerCharmRaceidVoid != 0) {
- const MonsterType* mType = g_monsters().getMonsterType(target->getName());
- if (mType && playerCharmRaceidVoid == mType->info.raceid) {
- IOBestiary g_bestiary;
- Charm* voidc = g_bestiary.getBestiaryCharm(CHARM_VOID);
- if (voidc) {
- manaSkill += voidc->percent;
- }
+ if (targetMonster) {
+ if (uint16_t playerCharmRaceidVoid = attackerPlayer->parseRacebyCharm(CHARM_VOID, false, 0);
+ playerCharmRaceidVoid != 0 && playerCharmRaceidVoid == targetMonster->getRace()) {
+ if (const Charm* voidc = g_iobestiary().getBestiaryCharm(CHARM_VOID)) {
+ manaSkill += voidc->percent;
}
}
}
@@ -6101,17 +6113,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage
Combat::doCombatMana(nullptr, attackerPlayer, tmpDamage, tmpParams);
}
- //Charm rune (attacker as player)
- if (!damage.extension && target && target->getMonster()) {
- MonsterType* mType = g_monsters().getMonsterType(target->getName());
- if (mType) {
- IOBestiary g_bestiary;
- charmRune_t activeCharm = g_bestiary.getCharmFromTarget(attackerPlayer, mType);
- if (activeCharm != CHARM_NONE) {
- Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
- if (charm && charm->type == CHARM_OFFENSIVE && (charm->chance >= normal_random(0, 100))) {
- g_bestiary.parseCharmCombat(charm, attackerPlayer, target, realDamage);
- }
+ // Charm rune (attacker as player)
+ if (!damage.extension && targetMonster) {
+ if (charmRune_t activeCharm = g_iobestiary().getCharmFromTarget(attackerPlayer, g_monsters().getMonsterTypeByRaceId(targetMonster->getRaceId()));
+ activeCharm != CHARM_NONE) {
+ if (Charm* charm = g_iobestiary().getBestiaryCharm(activeCharm);
+ charm->type == CHARM_OFFENSIVE && (charm->chance >= normal_random(0, 100))) {
+ g_iobestiary().parseCharmCombat(charm, attackerPlayer, target, realDamage);
}
}
}
@@ -6374,12 +6382,11 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage&
//Charm rune (target as player)
MonsterType* mType = g_monsters().getMonsterType(attacker->getName());
if (mType) {
- IOBestiary g_bestiary;
- charmRune_t activeCharm = g_bestiary.getCharmFromTarget(targetPlayer, mType);
+ charmRune_t activeCharm = g_iobestiary().getCharmFromTarget(targetPlayer, mType);
if (activeCharm != CHARM_NONE && activeCharm != CHARM_CLEANSE) {
- Charm* charm = g_bestiary.getBestiaryCharm(activeCharm);
+ Charm* charm = g_iobestiary().getBestiaryCharm(activeCharm);
if (charm && charm->type == CHARM_DEFENSIVE && (charm->chance > normal_random(0, 100))) {
- if (g_bestiary.parseCharmCombat(charm, targetPlayer, attacker, manaChange)) {
+ if (g_iobestiary().parseCharmCombat(charm, targetPlayer, attacker, manaChange)) {
return false; // Dodge charm
}
}
@@ -6700,6 +6707,7 @@ void Game::addBestiaryList(uint16_t raceid, std::string name)
if (it != BestiaryList.end()) {
return;
}
+
BestiaryList.insert(std::pair(raceid, name));
}
@@ -7336,6 +7344,26 @@ void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, c
}
}
+void Game::playerPreyAction(uint32_t playerId, uint8_t slot, uint8_t action, uint8_t option, int8_t index, uint16_t raceId)
+{
+ Player* player = getPlayerByID(playerId);
+ if (!player) {
+ return;
+ }
+
+ g_ioprey().ParsePreyAction(player, static_cast(slot), static_cast(action), static_cast(option), index, raceId);
+}
+
+void Game::playerTaskHuntingAction(uint32_t playerId, uint8_t slot, uint8_t action, bool upgrade, uint16_t raceId)
+{
+ Player* player = getPlayerByID(playerId);
+ if (!player) {
+ return;
+ }
+
+ g_ioprey().ParseTaskHuntingAction(player, static_cast(slot), static_cast(action), upgrade, raceId);
+}
+
void Game::playerNpcGreet(uint32_t playerId, uint32_t npcId)
{
Player* player = getPlayerByID(playerId);
diff --git a/src/game/game.h b/src/game/game.h
index b49a20f7df6..056950fbe44 100644
--- a/src/game/game.h
+++ b/src/game/game.h
@@ -36,6 +36,7 @@
#include "lua/creature/raids.h"
#include "creatures/players/grouping/team_finder.hpp"
#include "utils/wildcardtree.h"
+#include "io/ioprey.h"
#include "items/items_classification.hpp"
class ServiceManager;
@@ -44,9 +45,12 @@ class Monster;
class Npc;
class CombatInfo;
class Charm;
+class IOPrey;
class ItemClassification;
static constexpr int32_t EVENT_LIGHTINTERVAL_MS = 10000;
+static constexpr int32_t EVENT_DECAYINTERVAL = 250;
+static constexpr int32_t EVENT_DECAY_BUCKETS = 4;
class Game
{
@@ -247,6 +251,8 @@ class Game
void playerDebugAssert(uint32_t playerId, const std::string& assertLine,
const std::string& date, const std::string& description,
const std::string& comment);
+ void playerPreyAction(uint32_t playerId, uint8_t slot, uint8_t action, uint8_t option, int8_t index, uint16_t raceId);
+ void playerTaskHuntingAction(uint32_t playerId, uint8_t slot, uint8_t action, bool upgrade, uint16_t raceId);
void playerNpcGreet(uint32_t playerId, uint32_t npcId);
void playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId,
uint8_t button, uint8_t choice);
diff --git a/src/io/iobestiary.h b/src/io/iobestiary.h
index aefd55f6584..fa04cd9f657 100644
--- a/src/io/iobestiary.h
+++ b/src/io/iobestiary.h
@@ -60,6 +60,19 @@ class Charm
class IOBestiary
{
public:
+ IOBestiary() = default;
+
+ // non-copyable
+ IOBestiary(IOBestiary const&) = delete;
+ void operator=(IOBestiary const&) = delete;
+
+ static IOBestiary& getInstance() {
+ // Guaranteed to be destroyed
+ static IOBestiary instance;
+ // Instantiated on first use
+ return instance;
+ }
+
Charm* getBestiaryCharm(charmRune_t activeCharm, bool force = false);
void addBestiaryKill(Player* player, MonsterType* mtype, uint32_t amount = 1);
bool parseCharmCombat(Charm* charm, Player* player, Creature* target, int32_t realDamage);
@@ -88,4 +101,6 @@ class IOBestiary
};
+constexpr auto g_iobestiary = &IOBestiary::getInstance;
+
#endif // SRC_IO_IOBESTIARY_H_
diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp
index 12302efe0cd..c69ea5ab801 100644
--- a/src/io/iologindata.cpp
+++ b/src/io/iologindata.cpp
@@ -24,10 +24,10 @@
#include "game/game.h"
#include "game/scheduling/scheduler.h"
#include "creatures/monsters/monster.h"
+#include "io/ioprey.h"
#include
-
bool IOLoginData::authenticateAccountPassword(const std::string& email, const std::string& password, account::Account *account) {
if (account::ERROR_NO != account->LoadAccountDB(email)) {
SPDLOG_ERROR("Email {} doesn't match any account.", email);
@@ -140,111 +140,6 @@ bool IOLoginData::loadPlayerById(Player* player, uint32_t id)
return loadPlayer(player, db.storeQuery(query.str()));
}
-// New Prey
-bool IOLoginData::loadPlayerPreyData(Player* player)
-{
- Database& db = Database::getInstance();
- DBResult_ptr result;
- std::ostringstream query;
- query << "SELECT `num`, `state`, `unlocked`, `current`, `monster_list`, `free_reroll_in`, `time_left`, `next_use`, `bonus_type`, `bonus_value`, `bonus_grade`, `tick` FROM `prey_slots` WHERE `player_id` = " << player->getGUID();
- if ((result = db.storeQuery(query.str()))) {
- do {
- uint16_t slotNum = result->getNumber("num");
- player->preySlotState[slotNum] = result->getNumber("state");
- player->preySlotUnlocked[slotNum] = result->getNumber("unlocked");
- player->preySlotCurrentMonster[slotNum] = result->getString("current");
- player->preySlotMonsterList[slotNum] = result->getString("monster_list");
- player->preySlotFreeRerollIn[slotNum] = result->getNumber("free_reroll_in");
- player->preySlotTimeLeft[slotNum] = result->getNumber("time_left");
- player->preySlotNextUse[slotNum] = result->getNumber("next_use");
- player->preySlotBonusType[slotNum] = result->getNumber("bonus_type");
- player->preySlotBonusValue[slotNum] = result->getNumber("bonus_value");
- player->preySlotBonusGrade[slotNum] = result->getNumber("bonus_grade");
- player->preySlotTick[slotNum] = result->getNumber("tick");
- } while (result->next());
- }
- else {
- query.str(std::string());
- DBInsert preyDataQuery("INSERT INTO `prey_slots` (`player_id`, `num`, `state`, `unlocked`, `current`, `monster_list`, `free_reroll_in`, `time_left`, `next_use`, `bonus_type`, `bonus_value`, `bonus_grade`, `tick`) VALUES ");
- for (size_t num = 0; num < PREY_SLOTNUM_THIRD + 1; num++) {
- query << player->getGUID() << ',' << num << ',' << 0 << ',' << 0 << ',' << db.escapeString("") << ',' << db.escapeString("") << ',' << 0 << ',' << 0 << ',' << 0 << ',' << 0 << ',' << 0 << ',' << 0 << ',' << 0;
- if (!preyDataQuery.addRow(query)) {
- return false;
- }
- }
- if (!preyDataQuery.execute()) {
- return false;
- }
- // Reload player data
- return loadPlayerPreyData(player);
- }
-
- return true;
-}
-
-bool IOLoginData::loadPlayerPreyById(Player* player, uint32_t id)
-{
- Database& db = Database::getInstance();
- std::ostringstream query;
- query << "SELECT `player_id`, `bonus_type1`, `bonus_value1`, `bonus_name1`, `bonus_type2`, `bonus_value2`, `bonus_name2`, `bonus_type3`, `bonus_value3`, `bonus_name3` FROM `player_preytimes` WHERE `player_id` = " << id;
- DBResult_ptr result = db.storeQuery(query.str());
-
- if (!result) {
- return false;
- }
-
- player->preyBonusType[0] = result->getNumber("bonus_type1");
- player->preyBonusType[1] = result->getNumber("bonus_type2");
- player->preyBonusType[2] = result->getNumber("bonus_type3");
-
- player->preyBonusValue[0] = result->getNumber("bonus_value1");
- player->preyBonusValue[1] = result->getNumber("bonus_value2");
- player->preyBonusValue[2] = result->getNumber("bonus_value3");
-
- player->preyBonusName[0] = result->getString("bonus_name1");
- player->preyBonusName[1] = result->getString("bonus_name2");
- player->preyBonusName[2] = result->getString("bonus_name3");
-
- return true;
-}
-
-bool IOLoginData::savePlayerPreyById(Player* player, uint32_t id)
-{
- Database& db = Database::getInstance();
- std::ostringstream querycheck;
- std::ostringstream query;
- querycheck << "SELECT `bonus_type1` FROM `player_preytimes` WHERE `player_id` = " << id;
- DBResult_ptr returnQuery = db.storeQuery(querycheck.str());
-
- if (!returnQuery) {
- query << "INSERT INTO `player_preytimes` (`player_id`, `bonus_type1`, `bonus_value1`, `bonus_name1`, `bonus_type2`, `bonus_value2`, `bonus_name2`, `bonus_type3`, `bonus_value3`, `bonus_name3`) VALUES (";
- query << id << ", ";
- query << player->getPreyType(0) << ", ";
- query << player->getPreyValue(0) << ", ";
- query << db.escapeString(player->getPreyName(0)) << ", ";
- query << player->getPreyType(1) << ", ";
- query << player->getPreyValue(1) << ", ";
- query << db.escapeString(player->getPreyName(1)) << ", ";
- query << player->getPreyType(2) << ", ";
- query << player->getPreyValue(2) << ", ";
- query << db.escapeString(player->getPreyName(2)) << ")";
- } else {
- query << "UPDATE `player_preytimes` SET ";
- query << "`bonus_type1` = " << player->getPreyType(0) << ',';
- query << "`bonus_value1` = " << player->getPreyValue(0) << ',';
- query << "`bonus_name1` = " << db.escapeString(player->getPreyName(0)) << ',';
- query << "`bonus_type2` = " << player->getPreyType(1) << ',';
- query << "`bonus_value2` = " << player->getPreyValue(1) << ',';
- query << "`bonus_name2` = " << db.escapeString(player->getPreyName(1)) << ',';
- query << "`bonus_type3` = " << player->getPreyType(2) << ',';
- query << "`bonus_value3` = " << player->getPreyValue(2) << ',';
- query << "`bonus_name3` = " << db.escapeString(player->getPreyName(2));
- query << " WHERE `player_id` = " << id;
- }
-
- return db.executeQuery(query.str());
-}
-
bool IOLoginData::loadPlayerByName(Player* player, const std::string& name)
{
Database& db = Database::getInstance();
@@ -279,8 +174,6 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
acc.GetCoins(&(player->coinBalance));
- player->preyBonusRerolls = result->getNumber("bonus_rerolls");
-
Group* group = g_game().groups.getGroup(result->getNumber("group_id"));
if (!group) {
SPDLOG_ERROR("Player {} has group id {} whitch doesn't exist", player->name, result->getNumber("group_id"));
@@ -389,6 +282,9 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
player->loginPosition.y = result->getNumber("posy");
player->loginPosition.z = result->getNumber("posz");
+ player->addPreyCards(result->getNumber("prey_wildcard"));
+ player->addTaskHuntingPoints(result->getNumber("task_points"));
+
player->lastLoginSaved = result->getNumber("lastlogin");
player->lastLogout = result->getNumber("lastlogout");
@@ -410,10 +306,6 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
}
player->staminaMinutes = result->getNumber("stamina");
- player->preyStaminaMinutes[0] = result->getNumber("prey_stamina_1");
- player->preyStaminaMinutes[1] = result->getNumber("prey_stamina_2");
- player->preyStaminaMinutes[2] = result->getNumber("prey_stamina_3");
-
player->setStoreXpBoost(result->getNumber("xpboost_value"));
player->setExpBoostStamina(result->getNumber("xpboost_stamina"));
@@ -754,7 +646,73 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
} while (result->next());
}
- loadPlayerPreyData(player);
+ // Load prey class
+ if (g_configManager().getBoolean(PREY_ENABLED)) {
+ query.str(std::string());
+ query << "SELECT * FROM `player_prey` WHERE `player_id` = " << player->getGUID();
+ if (result = db.storeQuery(query.str())) {
+ do {
+ auto slot = new PreySlot(static_cast(result->getNumber("slot")));
+ slot->state = static_cast(result->getNumber("state"));
+ slot->selectedRaceId = result->getNumber("raceid");
+ slot->option = static_cast(result->getNumber("option"));
+ slot->bonus = static_cast(result->getNumber("bonus_type"));
+ slot->bonusRarity = static_cast(result->getNumber("bonus_rarity"));
+ slot->bonusPercentage = result->getNumber("bonus_percentage");
+ slot->bonusTimeLeft = result->getNumber("bonus_time");
+ slot->freeRerollTimeStamp = result->getNumber("free_reroll");
+
+ unsigned long preySize;
+ const char* preyStream = result->getStream("monster_list", preySize);
+ PropStream propPreyStream;
+ propPreyStream.init(preyStream, preySize);
+
+ uint16_t raceId;
+ while (propPreyStream.read(raceId)) {
+ slot->raceIdList.push_back(raceId);
+ }
+
+ player->setPreySlotClass(slot);
+ } while (result->next());
+ }
+ }
+
+ // Load task hunting class
+ if (g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ query.str(std::string());
+ query << "SELECT * FROM `player_taskhunt` WHERE `player_id` = " << player->getGUID();
+ if (result = db.storeQuery(query.str())) {
+ do {
+ auto slot = new TaskHuntingSlot(static_cast(result->getNumber("slot")));
+ slot->state = static_cast(result->getNumber("state"));
+ slot->selectedRaceId = result->getNumber("raceid");
+ slot->upgrade = result->getNumber("upgrade");
+ slot->rarity = static_cast(result->getNumber("rarity"));
+ slot->currentKills = result->getNumber("kills");
+ slot->disabledUntilTimeStamp = result->getNumber("disabled_time");
+ slot->freeRerollTimeStamp = result->getNumber("free_reroll");
+
+ unsigned long taskHuntSize;
+ const char* taskHuntStream = result->getStream("monster_list", taskHuntSize);
+ PropStream propTaskHuntStream;
+ propTaskHuntStream.init(taskHuntStream, taskHuntSize);
+
+ uint16_t raceId;
+ while (propTaskHuntStream.read(raceId)) {
+ slot->raceIdList.push_back(raceId);
+ }
+
+ if (slot->state == PreyTaskDataState_Inactive && slot->disabledUntilTimeStamp < OTSYS_TIME()) {
+ slot->state = PreyTaskDataState_Selection;
+ }
+
+ player->setTaskHuntingSlotClass(slot);
+ } while (result->next());
+ }
+ }
+
+ player->initializePrey();
+ player->initializeTaskHunting();
player->updateBaseSpeed();
player->updateInventoryWeight();
player->updateInventoryImbuement(true);
@@ -858,7 +816,6 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList,
bool IOLoginData::savePlayer(Player* player)
{
- savePlayerPreyById(player, player->getGUID());
if (player->getHealth() <= 0) {
player->changeHealth(1);
}
@@ -911,6 +868,9 @@ bool IOLoginData::savePlayer(Player* player)
query << "`posy` = " << loginPosition.getY() << ',';
query << "`posz` = " << loginPosition.getZ() << ',';
+ query << "`prey_wildcard` = " << player->getPreyCards() << ',';
+ query << "`task_points` = " << player->getTaskHuntingPoints() << ',';
+
query << "`cap` = " << (player->capacity / 100) << ',';
query << "`sex` = " << static_cast(player->sex) << ',';
@@ -959,10 +919,6 @@ bool IOLoginData::savePlayer(Player* player)
query << "`offlinetraining_time` = " << player->getOfflineTrainingTime() / 1000 << ',';
query << "`offlinetraining_skill` = " << player->getOfflineTrainingSkill() << ',';
query << "`stamina` = " << player->getStaminaMinutes() << ',';
- query << "`prey_stamina_1` = " << player->getPreyStamina(0) << ",";
- query << "`prey_stamina_2` = " << player->getPreyStamina(1) << ",";
- query << "`prey_stamina_3` = " << player->getPreyStamina(2) << ",";
-
query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ',';
query << "`skill_fist_tries` = " << player->skills[SKILL_FIST].tries << ',';
query << "`skill_club` = " << player->skills[SKILL_CLUB].level << ',';
@@ -993,7 +949,6 @@ bool IOLoginData::savePlayer(Player* player)
query << "`max_manashield` = " << player->getMaxManaShield() << ',';
query << "`xpboost_value` = " << player->getStoreXpBoost() << ',';
query << "`xpboost_stamina` = " << player->getExpBoostStamina() << ',';
- query << "`bonus_rerolls` = " << player->getPreyBonusRerolls() << ',';
query << "`quickloot_fallback` = " << (player->quickLootFallbackToMainContainer ? 1 : 0) << ',';
if (!player->isOffline()) {
@@ -1203,26 +1158,87 @@ bool IOLoginData::savePlayer(Player* player)
return false;
}
- // New Prey
- query.str(std::string());
- query << "DELETE FROM `prey_slots` WHERE `player_id` = " << player->getGUID();
- if (!db.executeQuery(query.str())) {
- SPDLOG_WARN("[IOLoginData::savePlayer] - Failed to delete table 'prey_slosts' from player: {}", player->getName());
- return false;
+ // Save prey class
+ if (g_configManager().getBoolean(PREY_ENABLED)) {
+ query.str(std::string());
+ query << "DELETE FROM `player_prey` WHERE `player_id` = " << player->getGUID();
+ if (!db.executeQuery(query.str())) {
+ return false;
+ }
+
+ for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) {
+ PreySlot* slot = player->getPreySlotById(static_cast(slotId));
+ if (slot) {
+ query.str(std::string());
+ query << "INSERT INTO `player_prey` (`player_id`, `slot`, `state`, `raceid`, `option`, `bonus_type`, `bonus_rarity`, `bonus_percentage`, `bonus_time`, `free_reroll`, `monster_list`) VALUES (";
+ query << player->getGUID() << ", ";
+ query << static_cast(slot->id) << ", ";
+ query << static_cast(slot->state) << ", ";
+ query << slot->selectedRaceId << ", ";
+ query << static_cast(slot->option) << ", ";
+ query << static_cast(slot->bonus) << ", ";
+ query << static_cast(slot->bonusRarity) << ", ";
+ query << slot->bonusPercentage << ", ";
+ query << slot->bonusTimeLeft << ", ";
+ query << slot->freeRerollTimeStamp << ", ";
+
+ PropWriteStream propPreyStream;
+ std::for_each(slot->raceIdList.begin(), slot->raceIdList.end(), [&propPreyStream](uint16_t raceId)
+ {
+ propPreyStream.write(raceId);
+ });
+
+ size_t preySize;
+ const char* preyList = propPreyStream.getStream(preySize);
+ query << db.escapeBlob(preyList, static_cast(preySize)) << ")";
+
+ if (!db.executeQuery(query.str())) {
+ SPDLOG_WARN("[IOLoginData::savePlayer] - Error saving prey slot data from player: {}", player->getName());
+ return false;
+ }
+ }
+ }
}
- query.str(std::string());
- DBInsert preyDataQuery("INSERT INTO `prey_slots` (`player_id`, `num`, `state`, `unlocked`, `current`, `monster_list`, `free_reroll_in`, `time_left`, `next_use`, `bonus_type`, `bonus_value`, `bonus_grade`, `tick`) VALUES ");
- for (size_t num = 0; num < PREY_SLOTNUM_THIRD + 1; num++) {
- query << player->getGUID() << ',' << num << ',' << player->preySlotState[num] << ',' << player->preySlotUnlocked[num] << ',' << db.escapeString(player->preySlotCurrentMonster[num]) << ',' << db.escapeString(player->preySlotMonsterList[num]) << ',' << player->preySlotFreeRerollIn[num] << ',' << player->preySlotTimeLeft[num] << ',' << player->preySlotNextUse[num] << ',' << player->preySlotBonusType[num] << ',' << player->preySlotBonusValue[num] << ',' << player->preySlotBonusGrade[num] << ',' << player->preySlotTick[num];
- if (!preyDataQuery.addRow(query)) {
+ // Save task hunting class
+ if (g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ query.str(std::string());
+ query << "DELETE FROM `player_taskhunt` WHERE `player_id` = " << player->getGUID();
+ if (!db.executeQuery(query.str())) {
return false;
}
- }
- if (!preyDataQuery.execute()) {
- SPDLOG_WARN("[IOLoginData::savePlayer] - Failed for save prey from playerr: {}", player->getName());
- return false;
+ for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) {
+ TaskHuntingSlot* slot = player->getTaskHuntingSlotById(static_cast(slotId));
+ if (slot) {
+ query.str(std::string());
+ query << "INSERT INTO `player_taskhunt` (`player_id`, `slot`, `state`, `raceid`, `upgrade`, `rarity`, `kills`, `disabled_time`, `free_reroll`, `monster_list`) VALUES (";
+ query << player->getGUID() << ", ";
+ query << static_cast(slot->id) << ", ";
+ query << static_cast(slot->state) << ", ";
+ query << slot->selectedRaceId << ", ";
+ query << (slot->upgrade ? 1 : 0) << ", ";
+ query << static_cast(slot->rarity) << ", ";
+ query << slot->currentKills << ", ";
+ query << slot->disabledUntilTimeStamp << ", ";
+ query << slot->freeRerollTimeStamp << ", ";
+
+ PropWriteStream propTaskHuntingStream;
+ std::for_each(slot->raceIdList.begin(), slot->raceIdList.end(), [&propTaskHuntingStream](uint16_t raceId)
+ {
+ propTaskHuntingStream.write(raceId);
+ });
+
+ size_t taskHuntingSize;
+ const char* taskHuntingList = propTaskHuntingStream.getStream(taskHuntingSize);
+ query << db.escapeBlob(taskHuntingList, static_cast(taskHuntingSize)) << ")";
+
+ if (!db.executeQuery(query.str())) {
+ SPDLOG_WARN("[IOLoginData::savePlayer] - Error saving task hunting slot data from player: {}", player->getName());
+ return false;
+ }
+ }
+ }
}
query.str(std::string());
diff --git a/src/io/iologindata.h b/src/io/iologindata.h
index 00ac24d032b..ce8bcf49231 100644
--- a/src/io/iologindata.h
+++ b/src/io/iologindata.h
@@ -42,12 +42,9 @@ class IOLoginData
static bool preloadPlayer(Player* player, const std::string& name);
static bool loadPlayerById(Player* player, uint32_t id);
- static bool loadPlayerPreyData(Player * player);
- static bool loadPlayerPreyById(Player* player, uint32_t id);
static bool loadPlayerByName(Player* player, const std::string& name);
static bool loadPlayer(Player* player, DBResult_ptr result);
static bool savePlayer(Player* player);
- static bool savePlayerPreyById(Player* player, uint32_t id);
static uint32_t getGuidByName(const std::string& name);
static bool getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name);
static std::string getNameByGuid(uint32_t guid);
diff --git a/src/io/ioprey.cpp b/src/io/ioprey.cpp
new file mode 100644
index 00000000000..b4df052dcc8
--- /dev/null
+++ b/src/io/ioprey.cpp
@@ -0,0 +1,640 @@
+/**
+ * Canary - A free and open-source MMORPG server emulator
+ * Copyright (C) 2021 OpenTibiaBR
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "otpch.h"
+
+#include "declarations.hpp"
+#include "creatures/monsters/monster.h"
+#include "creatures/players/player.h"
+#include "config/configmanager.h"
+#include "game/game.h"
+#include "io/ioprey.h"
+
+// Prey class
+PreySlot::PreySlot(PreySlot_t id) :
+ id(id) {
+ eraseBonus();
+ reloadBonusValue();
+ reloadBonusType();
+}
+
+void PreySlot::reloadBonusType()
+{
+ if (bonusRarity == 10) {
+ PreyBonus_t bonus_tmp = bonus;
+ while (bonus_tmp == bonus) {
+ bonus = static_cast(normal_random(PreyBonus_First, PreyBonus_Last));
+ }
+ return;
+ }
+
+ bonus = static_cast(normal_random(PreyBonus_First, PreyBonus_Last));
+}
+
+void PreySlot::reloadBonusValue()
+{
+ auto minBonusPercent = static_cast(g_configManager().getNumber(PREY_BONUS_PERCENT_MIN));
+ auto maxBonusPercent = static_cast(g_configManager().getNumber(PREY_BONUS_PERCENT_MAX));
+ auto stagePercent = static_cast(std::floor((maxBonusPercent - minBonusPercent) / 8));
+ if (bonusRarity >= 9) {
+ bonusRarity = 10;
+ } else {
+ bonusRarity = static_cast(normal_random(bonusRarity + 1, 10));
+ }
+
+ bonusPercentage = stagePercent * bonusRarity;
+ if (bonusPercentage > maxBonusPercent) {
+ bonusPercentage = maxBonusPercent;
+ } else if (bonusPercentage < minBonusPercent) {
+ bonusPercentage = minBonusPercent;
+ }
+}
+
+void PreySlot::reloadMonsterGrid(std::vector blackList, uint32_t level)
+{
+ raceIdList.clear();
+
+ if (!g_configManager().getBoolean(PREY_ENABLED)) {
+ return;
+ }
+
+ // Disabling prey system if the server have less then 36 registered monsters on bestiary because:
+ // - Impossible to generate random lists without duplications on slots.
+ // - Stress the server with unnecessary loops.
+ std::map bestiary = g_game().getBestiaryList();
+ if (bestiary.size() < 36) {
+ return;
+ }
+
+ uint8_t stageOne;
+ uint8_t stageTwo;
+ uint8_t stageThree;
+ uint8_t stageFour;
+ if (auto levelStage = static_cast(std::floor(level / 100));
+ levelStage == 0) { // From level 0 to 99
+ stageOne = 3;
+ stageTwo = 3;
+ stageThree = 2;
+ stageFour = 1;
+ } else if (levelStage <= 2) { // From level 100 to 299
+ stageOne = 1;
+ stageTwo = 3;
+ stageThree = 3;
+ stageFour = 2;
+ } else if (levelStage <= 4) { // From level 300 to 499
+ stageOne = 1;
+ stageTwo = 2;
+ stageThree = 3;
+ stageFour = 3;
+ } else { // From level 500 to ...
+ stageOne = 1;
+ stageTwo = 1;
+ stageThree = 3;
+ stageFour = 4;
+ }
+
+ uint8_t tries = 0;
+ auto maxIndex = static_cast(bestiary.size() - 1);
+ while (raceIdList.size() < 9) {
+ uint16_t raceId = (*(std::next(bestiary.begin(), normal_random(0, maxIndex)))).first;
+ tries++;
+
+ if (std::count(blackList.begin(), blackList.end(), raceId) != 0) {
+ continue;
+ }
+
+ blackList.push_back(raceId);
+ const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(raceId);
+ if (!mtype || mtype->info.experience == 0) {
+ continue;
+ } else if (stageOne != 0 && mtype->info.bestiaryStars <= 1) {
+ raceIdList.push_back(raceId);
+ --stageOne;
+ } else if (stageTwo != 0 && mtype->info.bestiaryStars == 2) {
+ raceIdList.push_back(raceId);
+ --stageTwo;
+ } else if (stageThree != 0 && mtype->info.bestiaryStars == 3) {
+ raceIdList.push_back(raceId);
+ --stageThree;
+ } else if (stageFour != 0 && mtype->info.bestiaryStars >= 4) {
+ raceIdList.push_back(raceId);
+ --stageFour;
+ } else if (tries >= 10) {
+ raceIdList.push_back(raceId);
+ tries = 0;
+ }
+ }
+}
+
+// Task hunting class
+void TaskHuntingSlot::reloadMonsterGrid(std::vector blackList, uint32_t level)
+{
+ raceIdList.clear();
+
+ if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ return;
+ }
+
+ // Disabling task hunting system if the server have less then 36 registered monsters on bestiary because:
+ // - Impossible to generate random lists without duplications on slots.
+ // - Stress the server with unnecessary loops.
+ std::map bestiary = g_game().getBestiaryList();
+ if (bestiary.size() < 36) {
+ return;
+ }
+
+ uint8_t stageOne;
+ uint8_t stageTwo;
+ uint8_t stageThree;
+ uint8_t stageFour;
+ if (auto levelStage = static_cast(std::floor(level / 100));
+ levelStage == 0) { // From level 0 to 99
+ stageOne = 3;
+ stageTwo = 3;
+ stageThree = 2;
+ stageFour = 1;
+ } else if (levelStage <= 2) { // From level 100 to 299
+ stageOne = 1;
+ stageTwo = 3;
+ stageThree = 3;
+ stageFour = 2;
+ } else if (levelStage <= 4) { // From level 300 to 499
+ stageOne = 1;
+ stageTwo = 2;
+ stageThree = 3;
+ stageFour = 3;
+ } else { // From level 500 to ...
+ stageOne = 1;
+ stageTwo = 1;
+ stageThree = 3;
+ stageFour = 4;
+ }
+
+ uint8_t tries = 0;
+ auto maxIndex = static_cast(bestiary.size() - 1);
+ while (raceIdList.size() < 9) {
+ uint16_t raceId = (*(std::next(bestiary.begin(), normal_random(0, maxIndex)))).first;
+ tries++;
+
+ if (std::count(blackList.begin(), blackList.end(), raceId) != 0) {
+ continue;
+ }
+
+ blackList.push_back(raceId);
+ const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(raceId);
+ if (!mtype || mtype->info.experience == 0) {
+ continue;
+ } else if (stageOne != 0 && mtype->info.bestiaryStars <= 1) {
+ raceIdList.push_back(raceId);
+ --stageOne;
+ } else if (stageTwo != 0 && mtype->info.bestiaryStars == 2) {
+ raceIdList.push_back(raceId);
+ --stageTwo;
+ } else if (stageThree != 0 && mtype->info.bestiaryStars == 3) {
+ raceIdList.push_back(raceId);
+ --stageThree;
+ } else if (stageFour != 0 && mtype->info.bestiaryStars >= 4) {
+ raceIdList.push_back(raceId);
+ --stageFour;
+ } else if (tries >= 10) {
+ raceIdList.push_back(raceId);
+ tries = 0;
+ }
+ }
+}
+
+void TaskHuntingSlot::reloadReward()
+{
+ if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ return;
+ }
+
+ if (rarity >= 4) {
+ rarity = 5;
+ return;
+ }
+
+ int32_t chance;
+ if (rarity == 0) {
+ chance = normal_random(0, 100);
+ } else if (rarity == 1) {
+ chance = normal_random(0, 70);
+ } else if (rarity == 2) {
+ chance = normal_random(0, 45);
+ } else if (rarity == 3) {
+ chance = normal_random(0, 20);
+ } else {
+ return;
+ }
+
+ if (chance <= 5) {
+ rarity = 5;
+ } else if (chance <= 20) {
+ rarity = 4;
+ } else if (chance <= 45) {
+ rarity = 3;
+ } else if (chance <= 70) {
+ rarity = 2;
+ } else {
+ rarity = 1;
+ }
+}
+
+// Prey/Task hunting global class
+void IOPrey::CheckPlayerPreys(Player* player, uint8_t amount) const
+{
+ if (!player) {
+ return;
+ }
+
+ for (uint8_t slotId = PreySlot_First; slotId <= PreySlot_Last; slotId++) {
+ if (PreySlot* slot = player->getPreySlotById(static_cast(slotId));
+ slot && slot->isOccupied()) {
+ if (slot->bonusTimeLeft <= amount) {
+ if (slot->option == PreyOption_AutomaticReroll) {
+ if (player->usePreyCards(static_cast(g_configManager().getNumber(PREY_BONUS_REROLL_PRICE)))) {
+ slot->reloadBonusValue();
+ slot->reloadBonusType();
+ slot->bonusTimeLeft = static_cast(g_configManager().getNumber(PREY_BONUS_TIME));
+ player->sendTextMessage(MESSAGE_STATUS, "Your prey bonus type and time has been succesfully reseted.");
+ player->reloadPreySlot(static_cast(slotId));
+ continue;
+ }
+
+ player->sendTextMessage(MESSAGE_STATUS, "You don't have enought prey cards to enable automatic reroll when your slot expire.");
+ } else if (slot->option == PreyOption_Locked) {
+ if (player->usePreyCards(static_cast(g_configManager().getNumber(PREY_SELECTION_LIST_PRICE)))) {
+ slot->bonusTimeLeft = static_cast(g_configManager().getNumber(PREY_BONUS_TIME));
+ player->sendTextMessage(MESSAGE_STATUS, "Your prey bonus time has been succesfully reseted.");
+ player->reloadPreySlot(static_cast(slotId));
+ continue;
+ }
+
+ player->sendTextMessage(MESSAGE_STATUS, "You don't have enought prey cards to lock monster and bonus when the slot expire.");
+ } else {
+ player->sendTextMessage(MESSAGE_STATUS, "Your prey bonus has expired.");
+ }
+
+ slot->eraseBonus();
+ slot->state = PreyDataState_Inactive;
+ player->reloadPreySlot(static_cast(slotId));
+ } else {
+ slot->bonusTimeLeft -= amount;
+ player->sendPreyTimeLeft(slot);
+ }
+ }
+ }
+}
+
+void IOPrey::ParsePreyAction(Player* player,
+ PreySlot_t slotId,
+ PreyAction_t action,
+ PreyOption_t option,
+ int8_t index,
+ uint16_t raceId) const
+{
+ PreySlot* slot = player->getPreySlotById(slotId);
+ if (!slot || slot->state == PreyDataState_Locked) {
+ player->sendMessageDialog("To unlock this prey slot first you must buy it on store.");
+ return;
+ }
+
+ if (action == PreyAction_ListReroll) {
+ if (slot->freeRerollTimeStamp > OTSYS_TIME() && !g_game().removeMoney(player, player->getPreyRerollPrice(), 0, true)) {
+ player->sendMessageDialog("You don't have enought money to reroll the prey slot.");
+ return;
+ } else if (slot->freeRerollTimeStamp <= OTSYS_TIME()) {
+ slot->freeRerollTimeStamp = OTSYS_TIME() + g_configManager().getNumber(PREY_FREE_REROLL_TIME) * 1000;
+ }
+
+ slot->eraseBonus(true);
+ slot->state = PreyDataState_Selection;
+ slot->reloadMonsterGrid(player->getPreyBlackList(), player->getLevel());
+ } else if (action == PreyAction_ListAll_Cards) {
+ if (!player->usePreyCards(static_cast(g_configManager().getNumber(PREY_SELECTION_LIST_PRICE)))) {
+ player->sendMessageDialog("You don't have enought prey cards to choose a monsters on the list.");
+ return;
+ }
+
+ slot->bonusTimeLeft = 0;
+ slot->selectedRaceId = 0;
+ slot->state = PreyDataState_ListSelection;
+ } else if (action == PreyAction_ListAll_Selection) {
+ if (slot->isOccupied()) {
+ player->sendMessageDialog("You already have an active monster on this prey slot.");
+ return;
+ } else if (!slot->canSelect() || slot->state != PreyDataState_ListSelection) {
+ player->sendMessageDialog("There was an error while processing your action. Please try reopening the prey window.");
+ return;
+ } else if (player->getPreyWithMonster(raceId)) {
+ player->sendMessageDialog("This creature is already selected on another slot.");
+ return;
+ }
+
+ if (slot->bonus == PreyBonus_None) {
+ slot->reloadBonusValue();
+ slot->reloadBonusType();
+ }
+
+ slot->state = PreyDataState_Active;
+ slot->selectedRaceId = raceId;
+ slot->removeMonsterType(raceId);
+ slot->bonusTimeLeft = static_cast(g_configManager().getNumber(PREY_BONUS_TIME));
+ } else if (action == PreyAction_BonusReroll) {
+ if (!slot->isOccupied()) {
+ player->sendMessageDialog("You don't have any active monster on this prey slot.");
+ return;
+ } else if (!player->usePreyCards(static_cast(g_configManager().getNumber(PREY_BONUS_REROLL_PRICE)))) {
+ player->sendMessageDialog("You don't have enought prey cards to reroll this prey slot bonus type.");
+ return;
+ }
+
+ slot->reloadBonusValue();
+ slot->reloadBonusType();
+ slot->bonusTimeLeft = static_cast(g_configManager().getNumber(PREY_BONUS_TIME));
+ } else if (action == PreyAction_MonsterSelection) {
+ if (slot->isOccupied()) {
+ player->sendMessageDialog("You already have an active monster on this prey slot.");
+ return;
+ } else if (!slot->canSelect() || index == -1 || (index + 1) > slot->raceIdList.size()) {
+ player->sendMessageDialog("There was an error while processing your action. Please try reopening the prey window.");
+ return;
+ } else if (player->getPreyWithMonster(slot->raceIdList[index])) {
+ player->sendMessageDialog("This creature is already selected on another slot.");
+ return;
+ }
+
+ if (slot->bonus == PreyBonus_None) {
+ slot->reloadBonusValue();
+ slot->reloadBonusType();
+ }
+ slot->state = PreyDataState_Active;
+ slot->selectedRaceId = slot->raceIdList[index];
+ slot->removeMonsterType(slot->selectedRaceId);
+ slot->bonusTimeLeft = static_cast(g_configManager().getNumber(PREY_BONUS_TIME));
+ } else if (action == PreyAction_Option) {
+ if (option == PreyOption_AutomaticReroll && player->getPreyCards() < static_cast(g_configManager().getNumber(PREY_BONUS_REROLL_PRICE))) {
+ player->sendMessageDialog("You don't have enought prey cards to enable automatic reroll when your slot expire.");
+ return;
+ } else if (option == PreyOption_Locked && player->getPreyCards() < static_cast(g_configManager().getNumber(PREY_SELECTION_LIST_PRICE))) {
+ player->sendMessageDialog("You don't have enought prey cards to lock monster and bonus when the slot expire.");
+ return;
+ }
+
+ slot->option = option;
+ } else {
+ SPDLOG_WARN("[IOPrey::ParsePreyAction] - Unknown prey action: {}", action);
+ return;
+ }
+
+ player->reloadPreySlot(slotId);
+}
+
+void IOPrey::ParseTaskHuntingAction(Player* player,
+ PreySlot_t slotId,
+ PreyTaskAction_t action,
+ bool upgrade,
+ uint16_t raceId) const
+{
+ TaskHuntingSlot* slot = player->getTaskHuntingSlotById(slotId);
+ if (!slot || slot->state == PreyTaskDataState_Locked) {
+ player->sendMessageDialog("To unlock this task hunting slot first you must buy it on store.");
+ return;
+ }
+
+ if (action == PreyTaskAction_ListReroll) {
+ if (slot->disabledUntilTimeStamp >= OTSYS_TIME()) {
+ std::ostringstream ss;
+ ss << "You need to wait " << ((slot->disabledUntilTimeStamp - OTSYS_TIME()) / 60000) << " minutes to select a new creature on task.";
+ player->sendMessageDialog(ss.str());
+ return;
+ } else if (slot->freeRerollTimeStamp > OTSYS_TIME() && !g_game().removeMoney(player, player->getTaskHuntingRerollPrice(), 0, true)) {
+ player->sendMessageDialog("You don't have enought money to reroll the task hunting slot.");
+ return;
+ } else if (slot->freeRerollTimeStamp <= OTSYS_TIME()) {
+ slot->freeRerollTimeStamp = OTSYS_TIME() + g_configManager().getNumber(TASK_HUNTING_FREE_REROLL_TIME) * 1000;
+ }
+
+ slot->eraseTask();
+ slot->reloadReward();
+ slot->state = PreyTaskDataState_Selection;
+ slot->reloadMonsterGrid(player->getTaskHuntingBlackList(), player->getLevel());
+ } else if (action == PreyTaskAction_RewardsReroll) {
+ if (!player->usePreyCards(static_cast(g_configManager().getNumber(TASK_HUNTING_BONUS_REROLL_PRICE)))) {
+ player->sendMessageDialog("You don't have enought prey cards to reroll you task reward rarity.");
+ return;
+ }
+
+ slot->reloadReward();
+ } else if (action == PreyTaskAction_ListAll_Cards) {
+ if (slot->disabledUntilTimeStamp >= OTSYS_TIME()) {
+ std::ostringstream ss;
+ ss << "You need to wait " << ((slot->disabledUntilTimeStamp - OTSYS_TIME()) / 60000) << " minutes to select a new creature on task.";
+ player->sendMessageDialog(ss.str());
+ return;
+ } else if (!player->usePreyCards(static_cast(g_configManager().getNumber(TASK_HUNTING_SELECTION_LIST_PRICE)))) {
+ player->sendMessageDialog("You don't have enought prey cards to choose a creature on list for you task hunting slot.");
+ return;
+ }
+
+ slot->selectedRaceId = 0;
+ slot->state = PreyTaskDataState_ListSelection;
+ } else if (action == PreyTaskAction_MonsterSelection) {
+ if (slot->disabledUntilTimeStamp >= OTSYS_TIME()) {
+ std::ostringstream ss;
+ ss << "You need to wait " << ((slot->disabledUntilTimeStamp - OTSYS_TIME()) / 60000) << " minutes to select a new creature on task.";
+ player->sendMessageDialog(ss.str());
+ return;
+ } else if (!slot->canSelect()) {
+ player->sendMessageDialog("There was an error while processing your action. Please try reopening the task window.");
+ return;
+ } else if (slot->isOccupied()) {
+ player->sendMessageDialog("You already have an active monster on this task hunting slot.");
+ return;
+ } else if (slot->state == PreyTaskDataState_Selection && !slot->isCreatureOnList(raceId)) {
+ player->sendMessageDialog("There was an error while processing your action. Please try reopening the task window.");
+ return;
+ } else if (player->getTaskHuntingWithCreature(raceId)) {
+ player->sendMessageDialog("This creature is already selected on another slot.");
+ return;
+ }
+
+ if (const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(raceId)) {
+ slot->currentKills = 0;
+ slot->selectedRaceId = raceId;
+ slot->removeMonsterType(raceId);
+ slot->state = PreyTaskDataState_Active;
+ slot->upgrade = upgrade && player->isCreatureUnlockedOnTaskHunting(mtype);
+ }
+ } else if (action == PreyTaskAction_Cancel) {
+ if (!g_game().removeMoney(player, player->getTaskHuntingRerollPrice(), 0, true)) {
+ player->sendMessageDialog("You don't have enought money to cancel your current task hunting.");
+ return;
+ }
+
+ slot->eraseTask();
+ slot->reloadReward();
+ slot->state = PreyTaskDataState_Selection;
+ slot->reloadMonsterGrid(player->getTaskHuntingBlackList(), player->getLevel());
+ } else if (action == PreyTaskAction_Claim) {
+ if (!slot->isOccupied()) {
+ player->sendMessageDialog("You cannot claim your task reward with an empty task hunting slot.");
+ return;
+ }
+
+ if (const TaskHuntingOption* option = GetTaskRewardOption(slot)) {
+ uint16_t reward;
+ int32_t boostChange = normal_random(0, 100);
+ if (slot->rarity >= 4 && boostChange <= 5) {
+ boostChange = 20;
+ } else if (slot->rarity >= 4 && boostChange <= 10) {
+ boostChange = 15;
+ } else {
+ boostChange = 10;
+ }
+
+ if (slot->upgrade && slot->currentKills >= option->secondKills) {
+ reward = option->secondReward;
+ } else if (!slot->upgrade && slot->currentKills >= option->firstKills) {
+ reward = option->firstReward;
+ } else {
+ player->sendMessageDialog("There was an error while processing you task hunting reward. Please try reopening the window.");
+ return;
+ }
+
+ std::ostringstream ss;
+ reward = static_cast(std::ceil((reward * boostChange) / 10));
+ ss << "Congratulations! You have earned " << reward;
+ if (boostChange == 20) {
+ ss << " Hunting Task points including a 100% bonus.";
+ } else if (boostChange == 15) {
+ ss << " Hunting Task points including a 50% bonus.";
+ } else {
+ ss << " Hunting Task points.";
+ }
+
+ slot->eraseTask();
+ slot->reloadReward();
+ slot->state = PreyTaskDataState_Inactive;
+ player->addTaskHuntingPoints(reward);
+ player->sendMessageDialog(ss.str());
+ slot->reloadMonsterGrid(player->getTaskHuntingBlackList(), player->getLevel());
+ slot->disabledUntilTimeStamp = OTSYS_TIME() + g_configManager().getNumber(TASK_HUNTING_LIMIT_EXHAUST) * 1000;
+ }
+ } else {
+ SPDLOG_WARN("[IOPrey::ParseTaskHuntingAction] - Unknown task action: {}", action);
+ return;
+ }
+ player->reloadTaskSlot(slotId);
+}
+
+void IOPrey::InitializeTaskHuntOptions()
+{
+ if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) {
+ return;
+ }
+
+ // Move it to config.lua
+ uint8_t killStage = 25; // Kill stage is the multiplier for kills and rewards on task hunting.
+
+ uint8_t limitOfStars = 5; // This is hardcoded on client but i'm saving it in case that they change it in the future.
+ uint16_t kills = killStage;
+ NetworkMessage msg;
+ for (uint8_t difficulty = PreyTaskDifficult_First; difficulty <= PreyTaskDifficult_Last; ++difficulty) { // Difficulties of creatures on bestiary.
+ auto reward = static_cast(std::round((10 * kills) / killStage));
+ for (uint8_t star = 1; star <= limitOfStars; ++star) { // Amount of task stars on task hunting.
+ auto option = new TaskHuntingOption();
+ option->difficult = static_cast(difficulty);
+ option->rarity = star;
+
+ option->firstKills = kills;
+ option->firstReward = reward;
+
+ option->secondKills = kills * 2;
+ option->secondReward = reward * 2;
+
+ taskOption.push_back(option);
+
+ reward = static_cast(std::round((reward * (115 + (difficulty * limitOfStars))) / 100));
+ }
+
+ kills *= 4;
+ }
+
+ msg.addByte(0xBA);
+ std::map bestiaryList = g_game().getBestiaryList();
+ msg.add(static_cast(bestiaryList.size()));
+ std::for_each(bestiaryList.begin(), bestiaryList.end(), [&msg](auto& mType)
+ {
+ const MonsterType* mtype = g_monsters().getMonsterType(mType.second);
+ if (!mtype) {
+ return;
+ }
+
+ msg.add(mtype->info.raceid);
+ if (mtype->info.bestiaryStars <= 1) {
+ msg.addByte(0x01);
+ } else if (mtype->info.bestiaryStars <= 3) {
+ msg.addByte(0x02);
+ } else {
+ msg.addByte(0x03);
+ }
+ });
+
+ msg.addByte(static_cast(taskOption.size()));
+ std::for_each(taskOption.begin(), taskOption.end(), [&msg](const TaskHuntingOption* option)
+ {
+ msg.addByte(static_cast(option->difficult));
+ msg.addByte(option->rarity);
+ msg.add(option->firstKills);
+ msg.add(option->firstReward);
+ msg.add(option->secondKills);
+ msg.add(option->secondReward);
+ });
+ baseDataMessage = msg;
+}
+
+TaskHuntingOption* IOPrey::GetTaskRewardOption(const TaskHuntingSlot* slot) const
+{
+ if (!slot) {
+ return nullptr;
+ }
+
+ const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(slot->selectedRaceId);
+ if (!mtype) {
+ return nullptr;
+ }
+
+ PreyTaskDifficult_t difficult;
+ if (mtype->info.bestiaryStars <= 1) {
+ difficult = PreyTaskDifficult_Easy;
+ } else if (mtype->info.bestiaryStars <= 3) {
+ difficult = PreyTaskDifficult_Medium;
+ } else {
+ difficult = PreyTaskDifficult_Hard;
+ }
+
+ if (auto it = std::find_if(taskOption.begin(), taskOption.end(), [difficult, slot](const TaskHuntingOption* optionIt) {
+ return optionIt->difficult == difficult && optionIt->rarity == slot->rarity;
+ }); it != taskOption.end()) {
+ return *it;
+ }
+
+ return nullptr;
+}
diff --git a/src/io/ioprey.h b/src/io/ioprey.h
new file mode 100644
index 00000000000..5d5d7f4a335
--- /dev/null
+++ b/src/io/ioprey.h
@@ -0,0 +1,263 @@
+/**
+ * Canary - A free and open-source MMORPG server emulator
+ * Copyright (C) 2021 OpenTibiaBR
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef SRC_IO_IOPREY_H_
+#define SRC_IO_IOPREY_H_
+
+#include
+#include
+#include
+#include
+#include "server/network/protocol/protocolgame.h"
+
+enum PreySlot_t : uint8_t {
+ PreySlot_One = 0,
+ PreySlot_Two = 1,
+ PreySlot_Three = 2,
+
+ PreySlot_First = PreySlot_One,
+ PreySlot_Last = PreySlot_Three
+};
+
+enum PreyDataState_t : uint8_t {
+ PreyDataState_Locked = 0,
+ PreyDataState_Inactive = 1,
+ PreyDataState_Active = 2,
+ PreyDataState_Selection = 3,
+ PreyDataState_SelectionChangeMonster = 4,
+ PreyDataState_ListSelection = 5,
+ PreyDataState_WildcardSelection = 6
+};
+
+enum PreyBonus_t : uint8_t {
+ PreyBonus_Damage = 0,
+ PreyBonus_Defense = 1,
+ PreyBonus_Experience = 2,
+ PreyBonus_Loot = 3,
+ PreyBonus_None = 4, // Do not send this to client
+
+ PreyBonus_First = PreyBonus_Damage,
+ PreyBonus_Last = PreyBonus_Loot
+};
+
+enum PreyOption_t : uint8_t {
+ PreyOption_None = 0,
+ PreyOption_AutomaticReroll = 1,
+ PreyOption_Locked = 2
+};
+
+enum PreyAction_t : uint8_t {
+ PreyAction_ListReroll = 0,
+ PreyAction_BonusReroll = 1,
+ PreyAction_MonsterSelection = 2,
+ PreyAction_ListAll_Cards = 3,
+ PreyAction_ListAll_Selection = 4,
+ PreyAction_Option = 5
+};
+
+enum PreyTaskDataState_t : uint8_t {
+ PreyTaskDataState_Locked = 0,
+ PreyTaskDataState_Inactive = 1,
+ PreyTaskDataState_Selection = 2,
+ PreyTaskDataState_ListSelection = 3,
+ PreyTaskDataState_Active = 4,
+ PreyTaskDataState_Completed = 5
+};
+
+enum PreyTaskAction_t : uint8_t {
+ PreyTaskAction_ListReroll = 0,
+ PreyTaskAction_RewardsReroll = 1,
+ PreyTaskAction_ListAll_Cards = 2,
+ PreyTaskAction_MonsterSelection = 3,
+ PreyTaskAction_Cancel = 4,
+ PreyTaskAction_Claim = 5
+};
+
+enum PreyTaskDifficult_t : uint8_t {
+ PreyTaskDifficult_None = 0,
+ PreyTaskDifficult_Easy = 1,
+ PreyTaskDifficult_Medium = 2,
+ PreyTaskDifficult_Hard = 3,
+
+ PreyTaskDifficult_First = PreyTaskDifficult_Easy,
+ PreyTaskDifficult_Last = PreyTaskDifficult_Hard
+};
+
+class NetworkMessage;
+
+class PreySlot
+{
+ public:
+ PreySlot() = default;
+ explicit PreySlot(PreySlot_t id);
+ virtual ~PreySlot() = default;
+
+ bool isOccupied() const {
+ return selectedRaceId != 0 && bonusTimeLeft > 0;
+ }
+
+ bool canSelect() const {
+ return (state == PreyDataState_Selection || state == PreyDataState_ListSelection || state == PreyDataState_Inactive);
+ }
+
+ void eraseBonus(bool maintainBonus = false) {
+ if (!maintainBonus) {
+ bonus = PreyBonus_None;
+ bonusPercentage = 5;
+ bonusRarity = 1;
+ }
+ state = PreyDataState_Selection;
+ option = PreyOption_None;
+ selectedRaceId = 0;
+ bonusTimeLeft = 0;
+ }
+
+ void removeMonsterType(uint16_t raceId) {
+ raceIdList.erase(std::remove(raceIdList.begin(), raceIdList.end(), raceId), raceIdList.end());
+ }
+
+ void reloadBonusType();
+ void reloadBonusValue();
+ void reloadMonsterGrid(std::vector blackList, uint32_t level);
+
+ PreySlot_t id = PreySlot_First;
+ PreyBonus_t bonus = PreyBonus_None;
+ PreyDataState_t state = PreyDataState_Locked;
+ PreyOption_t option = PreyOption_None;
+
+ std::vector raceIdList;
+
+ uint8_t bonusRarity = 1;
+
+ uint16_t selectedRaceId = 0;
+ uint16_t bonusPercentage = 0;
+ uint16_t bonusTimeLeft = 0;
+
+ int64_t freeRerollTimeStamp = 0;
+};
+
+class TaskHuntingSlot
+{
+ public:
+ TaskHuntingSlot() = default;
+ explicit TaskHuntingSlot(PreySlot_t id) : id(id) { }
+ virtual ~TaskHuntingSlot() = default;
+
+ bool isOccupied() const {
+ return selectedRaceId != 0;
+ }
+
+ bool canSelect() const {
+ return (state == PreyTaskDataState_Selection || state == PreyTaskDataState_ListSelection);
+ }
+
+ void eraseTask() {
+ upgrade = false;
+ state = PreyTaskDataState_Selection;
+ selectedRaceId = 0;
+ currentKills = 0;
+ rarity = 1;
+ }
+
+ void removeMonsterType(uint16_t raceId) {
+ raceIdList.erase(std::remove(raceIdList.begin(), raceIdList.end(), raceId), raceIdList.end());
+ }
+
+ bool isCreatureOnList(uint16_t raceId) const {
+ auto it = std::find_if(raceIdList.begin(), raceIdList.end(), [raceId](uint16_t it) {
+ return it == raceId;
+ });
+
+ return it != raceIdList.end();
+ }
+
+ void reloadReward();
+ void reloadMonsterGrid(std::vector blackList, uint32_t level);
+
+ PreySlot_t id = PreySlot_First;
+ PreyTaskDataState_t state = PreyTaskDataState_Inactive;
+
+ bool upgrade = false;
+
+ uint8_t rarity = 1;
+
+ uint16_t selectedRaceId = 0;
+ uint16_t currentKills = 0;
+
+ int64_t disabledUntilTimeStamp = 0;
+ int64_t freeRerollTimeStamp = 0;
+
+ std::vector raceIdList;
+};
+
+class TaskHuntingOption
+{
+ public:
+ TaskHuntingOption() = default;
+ virtual ~TaskHuntingOption() = default;
+
+ PreyTaskDifficult_t difficult = PreyTaskDifficult_None;
+ uint8_t rarity = 1;
+
+ uint16_t firstKills = 0;
+ uint16_t secondKills = 0;
+
+ uint16_t firstReward = 0;
+ uint16_t secondReward = 0;
+};
+
+class IOPrey
+{
+public:
+ IOPrey() = default;
+
+ // non-copyable
+ IOPrey(IOPrey const&) = delete;
+ void operator=(IOPrey const&) = delete;
+
+ static IOPrey& getInstance() {
+ // Guaranteed to be destroyed
+ static IOPrey instance;
+ // Instantiated on first use
+ return instance;
+ }
+
+ void CheckPlayerPreys(Player* player, uint8_t amount) const;
+ void ParsePreyAction(Player* player, PreySlot_t slotId, PreyAction_t action, PreyOption_t option, int8_t index, uint16_t raceId) const;
+
+ void ParseTaskHuntingAction(Player* player, PreySlot_t slotId, PreyTaskAction_t action, bool upgrade, uint16_t raceId) const;
+
+ void InitializeTaskHuntOptions();
+ TaskHuntingOption* GetTaskRewardOption(const TaskHuntingSlot* slot) const;
+
+ std::vector GetTaskOptions() const {
+ return taskOption;
+ }
+
+ NetworkMessage GetTaskHuntingBaseDate() const {
+ return baseDataMessage;
+ }
+
+ NetworkMessage baseDataMessage;
+ std::vector taskOption;
+};
+
+constexpr auto g_ioprey = &IOPrey::getInstance;
+
+#endif // SRC_IO_IOPREY_H_
diff --git a/src/lua/functions/core/game/config_functions.hpp b/src/lua/functions/core/game/config_functions.hpp
index 5dfc2b66b66..63b4bd48595 100644
--- a/src/lua/functions/core/game/config_functions.hpp
+++ b/src/lua/functions/core/game/config_functions.hpp
@@ -96,6 +96,21 @@ class ConfigFunctions final : LuaScriptInterface {
registerEnumIn(L, "configKeys", MAX_PLAYERS)
registerEnumIn(L, "configKeys", PZ_LOCKED)
registerEnumIn(L, "configKeys", DEFAULT_DESPAWNRANGE)
+ registerEnumIn(L, "configKeys", PREY_ENABLED)
+ registerEnumIn(L, "configKeys", PREY_FREE_THIRD_SLOT)
+ registerEnumIn(L, "configKeys", PREY_REROLL_PRICE_LEVEL)
+ registerEnumIn(L, "configKeys", PREY_BONUS_PERCENT_MIN)
+ registerEnumIn(L, "configKeys", PREY_BONUS_PERCENT_MAX)
+ registerEnumIn(L, "configKeys", PREY_BONUS_TIME)
+ registerEnumIn(L, "configKeys", PREY_BONUS_REROLL_PRICE)
+ registerEnumIn(L, "configKeys", PREY_FREE_REROLL_TIME)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_ENABLED)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_FREE_THIRD_SLOT)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_LIMIT_EXHAUST)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_REROLL_PRICE_LEVEL)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_SELECTION_LIST_PRICE)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_BONUS_REROLL_PRICE)
+ registerEnumIn(L, "configKeys", TASK_HUNTING_FREE_REROLL_TIME)
registerEnumIn(L, "configKeys", DEFAULT_DESPAWNRADIUS)
registerEnumIn(L, "configKeys", RATE_EXPERIENCE)
registerEnumIn(L, "configKeys", RATE_SKILL)
diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp
index e4dc459a8e3..c5ec469dabf 100644
--- a/src/lua/functions/core/game/game_functions.cpp
+++ b/src/lua/functions/core/game/game_functions.cpp
@@ -30,7 +30,6 @@
#include "lua/functions/creatures/npc/npc_type_functions.hpp"
#include "lua/scripts/scripts.h"
-
// Game
int GameFunctions::luaGameCreateMonsterType(lua_State* L) {
// Game.createMonsterType(name)
@@ -125,7 +124,6 @@ int GameFunctions::luaGameGetBoostedCreature(lua_State* L) {
int GameFunctions::luaGameGetBestiaryList(lua_State* L) {
// Game.getBestiaryList([bool[string or BestiaryType_t]])
- IOBestiary g_bestiary;
lua_newtable(L);
int index = 0;
bool name = getBoolean(L, 2, false);
@@ -144,7 +142,7 @@ int GameFunctions::luaGameGetBestiaryList(lua_State* L) {
}
else {
if (isNumber(L, 2)) {
- std::map tmplist = g_bestiary.findRaceByName("CANARY", false, getNumber(L, 2));
+ std::map tmplist = g_iobestiary().findRaceByName("CANARY", false, getNumber(L, 2));
for (auto itb : tmplist) {
if (name) {
pushString(L, itb.second);
@@ -156,7 +154,7 @@ int GameFunctions::luaGameGetBestiaryList(lua_State* L) {
}
}
else {
- std::map tmplist = g_bestiary.findRaceByName(getString(L, 2));
+ std::map tmplist = g_iobestiary().findRaceByName(getString(L, 2));
for (auto itc : tmplist) {
if (name) {
pushString(L, itc.second);
@@ -541,9 +539,8 @@ int GameFunctions::luaGameCreateBestiaryCharm(lua_State* L) {
lua_pushnil(L);
return 1;
}
- IOBestiary g_bestiary;
- if (Charm* charm = g_bestiary.getBestiaryCharm(static_cast(getNumber(L, 1, 0)), true)) {
+ if (Charm* charm = g_iobestiary().getBestiaryCharm(static_cast(getNumber(L, 1, 0)), true)) {
pushUserdata(L, charm);
setMetatable(L, -1, "Charm");
} else {
diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp
index d260606d305..26b75414a8a 100644
--- a/src/lua/functions/creatures/creature_functions.cpp
+++ b/src/lua/functions/creatures/creature_functions.cpp
@@ -701,9 +701,8 @@ int CreatureFunctions::luaCreatureRemove(lua_State* L) {
return 1;
}
- Player* player = creature->getPlayer();
bool forced = getBoolean(L, 2, true);
- if (player) {
+ if (Player* player = creature->getPlayer()) {
if (forced) {
player->removePlayer(true);
} else {
diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp
index b5482c13b17..4bd468e31c3 100644
--- a/src/lua/functions/creatures/player/player_functions.cpp
+++ b/src/lua/functions/creatures/player/player_functions.cpp
@@ -27,10 +27,10 @@
#include "creatures/players/player.h"
#include "game/game.h"
#include "io/iologindata.h"
+#include "io/ioprey.h"
#include "items/item.h"
#include "lua/functions/creatures/player/player_functions.hpp"
-
int PlayerFunctions::luaPlayerSendInventory(lua_State* L) {
// player:sendInventory()
Player* player = getUserdata(L, 1);
@@ -176,11 +176,10 @@ int PlayerFunctions::luaPlayerUnlockAllCharmRunes(lua_State* L) {
// player:unlockAllCharmRunes()
Player* player = getUserdata(L, 1);
if (player) {
- IOBestiary g_bestiary;
for (int8_t i = CHARM_WOUND; i <= CHARM_LAST; i++) {
- Charm* charm = g_bestiary.getBestiaryCharm(static_cast(i));
+ Charm* charm = g_iobestiary().getBestiaryCharm(static_cast(i));
if (charm) {
- int32_t value = g_bestiary.bitToggle(player->getUnlockedRunesBit(), charm, true);
+ int32_t value = g_iobestiary().bitToggle(player->getUnlockedRunesBit(), charm, true);
player->setUnlockedRunesBit(value);
}
}
@@ -195,13 +194,12 @@ int PlayerFunctions::luaPlayeraddCharmPoints(lua_State* L) {
// player:addCharmPoints()
Player* player = getUserdata(L, 1);
if (player) {
- IOBestiary g_bestiary;
int16_t charms = getNumber(L, 2);
if (charms >= 0) {
- g_bestiary.addCharmPoints(player, static_cast(charms));
+ g_iobestiary().addCharmPoints(player, static_cast(charms));
} else {
charms = -charms;
- g_bestiary.addCharmPoints(player, static_cast(charms), true);
+ g_iobestiary().addCharmPoints(player, static_cast(charms), true);
}
pushBoolean(L, true);
} else {
@@ -301,8 +299,7 @@ int PlayerFunctions::luaPlayeraddBestiaryKill(lua_State* L) {
if (player) {
MonsterType* mtype = g_monsters().getMonsterType(getString(L, 2));
if (mtype) {
- IOBestiary g_bestiary;
- g_bestiary.addBestiaryKill(player, mtype, getNumber(L, 3, 1));
+ g_iobestiary().addBestiaryKill(player, mtype, getNumber(L, 3, 1));
pushBoolean(L, true);
} else {
lua_pushnil(L);
@@ -336,6 +333,128 @@ int PlayerFunctions::luaPlayergetCharmMonsterType(lua_State* L) {
return 1;
}
+int PlayerFunctions::luaPlayerRemovePreyStamina(lua_State* L) {
+ // player:removePreyStamina(amount)
+ Player* player = getUserdata(L, 1);
+ if (player) {
+ g_ioprey().CheckPlayerPreys(player, getNumber(L, 2, 1));
+ pushBoolean(L, true);
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerAddPreyCards(lua_State* L) {
+ // player:addPreyCards(amount)
+ if (Player* player = getUserdata(L, 1)) {
+ player->addPreyCards(getNumber(L, 2, 0));
+ pushBoolean(L, true);
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerGetPreyCards(lua_State* L) {
+ // player:getPreyCards()
+ if (const Player* player = getUserdata(L, 1)) {
+ lua_pushnumber(L, static_cast(player->getPreyCards()));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerGetPreyExperiencePercentage(lua_State* L) {
+ // player:getPreyExperiencePercentage(raceId)
+ if (const Player* player = getUserdata(L, 1)) {
+ if (const PreySlot* slot = player->getPreyWithMonster(getNumber(L, 2, 0));
+ slot && slot->isOccupied() && slot->bonus == PreyBonus_Experience && slot->bonusTimeLeft > 0) {
+ lua_pushnumber(L, static_cast(100 + slot->bonusPercentage));
+ } else {
+ lua_pushnumber(L, 100);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerRemoveTaskHuntingPoints(lua_State* L) {
+ // player:removeTaskHuntingPoints(amount)
+ if (Player* player = getUserdata(L, 1)) {
+ pushBoolean(L, player->useTaskHuntingPoints(getNumber(L, 2, 0)));
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerGetPreyLootPercentage(lua_State* L) {
+ // player:getPreyLootPercentage(raceid)
+ if (const Player* player = getUserdata(L, 1)) {
+ if (const PreySlot* slot = player->getPreyWithMonster(getNumber(L, 2, 0));
+ slot && slot->isOccupied() && slot->bonus == PreyBonus_Loot) {
+ lua_pushnumber(L, 100 + slot->bonusPercentage);
+ } else {
+ lua_pushnumber(L, 100);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerPreyThirdSlot(lua_State* L) {
+ // get: player:preyThirdSlot() set: player:preyThirdSlot(bool)
+ if (Player* player = getUserdata(L, 1);
+ PreySlot* slot = player->getPreySlotById(PreySlot_Three)) {
+ if (lua_gettop(L) == 1) {
+ pushBoolean(L, slot->state != PreyDataState_Locked);
+ } else {
+ if (getBoolean(L, 2, false)) {
+ slot->eraseBonus();
+ slot->state = PreyDataState_Selection;
+ slot->reloadMonsterGrid(player->getPreyBlackList(), player->getLevel());
+ player->reloadPreySlot(PreySlot_Three);
+ } else {
+ slot->state = PreyDataState_Locked;
+ }
+
+ pushBoolean(L, true);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
+int PlayerFunctions::luaPlayerTaskThirdSlot(lua_State* L) {
+ // get: player:taskHuntingThirdSlot() set: player:taskHuntingThirdSlot(bool)
+ if (Player* player = getUserdata(L, 1);
+ TaskHuntingSlot* slot = player->getTaskHuntingSlotById(PreySlot_Three)) {
+ if (lua_gettop(L) == 1) {
+ pushBoolean(L, slot->state != PreyTaskDataState_Locked);
+ } else {
+ if (getBoolean(L, 2, false)) {
+ slot->eraseTask();
+ slot->reloadReward();
+ slot->state = PreyTaskDataState_Selection;
+ slot->reloadMonsterGrid(player->getTaskHuntingBlackList(), player->getLevel());
+ player->reloadTaskSlot(PreySlot_Three);
+ } else {
+ slot->state = PreyTaskDataState_Locked;
+ }
+
+ pushBoolean(L, true);
+ }
+ } else {
+ lua_pushnil(L);
+ }
+ return 1;
+}
+
int PlayerFunctions::luaPlayercharmExpansion(lua_State* L) {
// get: player:charmExpansion() set: player:charmExpansion(bool)
Player* player = getUserdata(L, 1);
@@ -1324,137 +1443,6 @@ int PlayerFunctions::luaPlayerSetSpecialContainersAvailable(lua_State* L) {
return 1;
}
-int PlayerFunctions::luaPlayerGetPreyStamina(lua_State* L) {
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- Player* player = getUserdata(L, 1);
- if (player) {
- lua_pushnumber(L, player->getPreyStamina(column));
- } else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyType(lua_State* L) {
- Player* player = getUserdata(L, 1);
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- if (player) {
- lua_pushnumber(L, player->getPreyType(column));
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyValue(lua_State* L) {
- Player* player = getUserdata(L, 1);
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- if (player) {
- lua_pushnumber(L, player->getPreyValue(column));
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyName(lua_State* L) {
- Player* player = getUserdata(L, 1);
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- if (player) {
- pushString(L, player->getPreyName(column));
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
-int PlayerFunctions::luaPlayerSetPreyStamina(lua_State* L) {
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- uint16_t stamina = getNumber(L, 3);
- Player* player = getUserdata(L, 1);
- if (player) {
- player->preyStaminaMinutes[column] = std::min(7200, stamina);
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
-int PlayerFunctions::luaPlayerSetPreyType(lua_State* L) {
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- uint16_t type = getNumber(L, 3);
- Player* player = getUserdata(L, 1);
- if (player) {
- player->preyBonusType[column] = type;
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
-int PlayerFunctions::luaPlayerSetPreyValue(lua_State* L) {
- uint16_t value = getNumber(L, 3);
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- Player* player = getUserdata(L, 1);
- if (player) {
- player->preyBonusValue[column] = value;
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
-int PlayerFunctions::luaPlayerSetPreyName(lua_State* L) {
- uint16_t column = getNumber(L, 2);
- if (column > 2) {
- column = 2;
- }
-
- std::string name = getString(L, 3);
- Player* player = getUserdata(L, 1);
- if (player) {
- player->preyBonusName[column] = name;
- } else {
- lua_pushnil(L);
- }
-
- return 1;
-}
-
int PlayerFunctions::luaPlayerGetStamina(lua_State* L) {
// player:getStamina()
Player* player = getUserdata(L, 1);
@@ -2585,333 +2573,6 @@ int PlayerFunctions::luaPlayerPopupFYI(lua_State* L) {
return 1;
}
-// New Prey
-// GET
-int PlayerFunctions::luaPlayerGetPreyState(lua_State* L) {
- // player:getPreyState(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyState(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyUnlocked(lua_State* L) {
- // player:getPreyUnlocked(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyUnlocked(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyCurrentMonster(lua_State* L) {
- // player:getPreyCurrentMonster(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- pushString(L, player->getPreyCurrentMonster(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyMonsterList(lua_State* L) {
- // player:getPreyMonsterList(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- pushString(L, player->getPreyMonsterList(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyFreeRerollIn(lua_State* L) {
- // player:getPreyFreeRerollIn(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyFreeRerollIn(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyTimeLeft(lua_State* L) {
- // player:getPreyTimeLeft(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyTimeLeft(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyNextUse(lua_State* L) {
- // player:getPreyNextUse(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyNextUse(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyBonusType(lua_State* L) {
- // player:getPreyBonusType(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyBonusType(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyBonusValue(lua_State* L) {
- // player:getPreyBonusValue(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyBonusValue(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyBonusGrade(lua_State* L) {
- // player:getPreyBonusGrade(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber(L, 2);
- if (player) {
- lua_pushnumber(L, player->getPreyBonusGrade(slot));
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyBonusRerolls(lua_State* L) {
- // player:getPreyBonusRerolls(slot)
- Player* player = getUserdata(L, 1);
- if (player) {
- lua_pushnumber(L, player->getPreyBonusRerolls());
- }
- else {
- lua_pushnil(L);
- }
- return 1;
-}
-
-int PlayerFunctions::luaPlayerGetPreyTick(lua_State* L) {
- // player:getPreyTick(slot)
- Player* player = getUserdata(L, 1);
- uint16_t slot = getNumber