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(L, 2); - if (player) { - lua_pushnumber(L, player->getPreyTick(slot)); - } - else { - lua_pushnil(L); - } - return 1; -} - -// SET -int PlayerFunctions::luaPlayerSetPreyState(lua_State* L) { - // player:setPreyState(slot, state) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - uint16_t state = getNumber(L, 3); - if (player) { - player->preySlotState[slot] = state; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyUnlocked(lua_State* L) { - // player:setPreyUnlocked(slot, mode) - Player* player = getUserdata(L, 1); - uint32_t slot = getNumber(L, 2); - uint16_t mode = getNumber(L, 3); - if (player) { - player->preySlotUnlocked[slot] = mode; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyCurrentMonster(lua_State* L) { - // player:SetPreyCurrentMonster(slot, monster) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - std::string monster = getString(L, 3); - player->preySlotCurrentMonster[slot] = monster; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyMonsterList(lua_State* L) { - // player:setPreyMonsterList(slot, list) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - std::string list = getString(L, 3); - player->preySlotMonsterList[slot] = list; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyFreeRerollIn(lua_State* L) { - // player:setPreyFreeRerollIn(slot, time) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - uint16_t time = getNumber(L, 3); - player->preySlotFreeRerollIn[slot] = time; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyTimeLeft(lua_State* L) { - // player:setPreyTimeLeft(slot, time) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - uint16_t time = getNumber(L, 3); - player->preySlotTimeLeft[slot] = time; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyNextUse(lua_State* L) { - // player:setPreyNextUse(slot, time) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - uint32_t time = getNumber(L, 3); - player->preySlotNextUse[slot] = time; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyBonusType(lua_State* L) { - // player:setPreyBonusType(slot, type) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - uint16_t type = getNumber(L, 3); - player->preySlotBonusType[slot] = type; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyBonusValue(lua_State* L) { - // player:setPreyBonusValue(slot, value) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - uint16_t value = getNumber(L, 3); - player->preySlotBonusValue[slot] = value; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyBonusGrade(lua_State* L) { - // player:getPreyBonusValue(slot, grade) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - if (player) { - uint16_t grade = getNumber(L, 3); - player->preySlotBonusGrade[slot] = grade; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyBonusRerolls(lua_State* L) { - // player:setPreyBonusRerolls(slot, grade) - Player* player = getUserdata(L, 1); - if (player) { - uint16_t value = getNumber(L, 2); - player->preyBonusRerolls = value; - } - else { - lua_pushnil(L); - } - return 1; -} - -int PlayerFunctions::luaPlayerSetPreyTick(lua_State* L) { - // player:setPreyTick(slot, tick) - Player* player = getUserdata(L, 1); - uint16_t slot = getNumber(L, 2); - uint16_t tick = getNumber(L, 3); - if (player) { - player->preySlotTick[slot] = tick; - } - else { - lua_pushnil(L); - } - return 1; -} - -/**/ - int PlayerFunctions::luaPlayerIsPzLocked(lua_State* L) { // player:isPzLocked() Player* player = getUserdata(L, 1); diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index cd0bbac9160..d65145b247b 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -53,6 +53,15 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "charmExpansion", PlayerFunctions::luaPlayercharmExpansion); registerMethod(L, "Player", "getCharmMonsterType", PlayerFunctions::luaPlayergetCharmMonsterType); + registerMethod(L, "Player", "getPreyCards", PlayerFunctions::luaPlayerGetPreyCards); + registerMethod(L, "Player", "getPreyLootPercentage", PlayerFunctions::luaPlayerGetPreyLootPercentage); + registerMethod(L, "Player", "getPreyExperiencePercentage", PlayerFunctions::luaPlayerGetPreyExperiencePercentage); + registerMethod(L, "Player", "preyThirdSlot", PlayerFunctions::luaPlayerPreyThirdSlot); + registerMethod(L, "Player", "taskHuntingThirdSlot", PlayerFunctions::luaPlayerTaskThirdSlot); + registerMethod(L, "Player", "removePreyStamina", PlayerFunctions::luaPlayerRemovePreyStamina); + registerMethod(L, "Player", "addPreyCards", PlayerFunctions::luaPlayerAddPreyCards); + registerMethod(L, "Player", "removeTaskHuntingPoints", PlayerFunctions::luaPlayerRemoveTaskHuntingPoints); + registerMethod(L, "Player", "getCapacity", PlayerFunctions::luaPlayerGetCapacity); registerMethod(L, "Player", "setCapacity", PlayerFunctions::luaPlayerSetCapacity); @@ -150,15 +159,6 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "getStamina", PlayerFunctions::luaPlayerGetStamina); registerMethod(L, "Player", "setStamina", PlayerFunctions::luaPlayerSetStamina); - registerMethod(L, "Player", "getPreyStamina", PlayerFunctions::luaPlayerGetPreyStamina); - registerMethod(L, "Player", "getPreyType", PlayerFunctions::luaPlayerGetPreyType); - registerMethod(L, "Player", "getPreyValue", PlayerFunctions::luaPlayerGetPreyValue); - registerMethod(L, "Player", "getPreyName", PlayerFunctions::luaPlayerGetPreyName); - registerMethod(L, "Player", "setPreyStamina", PlayerFunctions::luaPlayerSetPreyStamina); - registerMethod(L, "Player", "setPreyType", PlayerFunctions::luaPlayerSetPreyType); - registerMethod(L, "Player", "setPreyValue", PlayerFunctions::luaPlayerSetPreyValue); - registerMethod(L, "Player", "setPreyName", PlayerFunctions::luaPlayerSetPreyName); - registerMethod(L, "Player", "getSoul", PlayerFunctions::luaPlayerGetSoul); registerMethod(L, "Player", "addSoul", PlayerFunctions::luaPlayerAddSoul); registerMethod(L, "Player", "getMaxSoul", PlayerFunctions::luaPlayerGetMaxSoul); @@ -240,34 +240,6 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "getClient", PlayerFunctions::luaPlayerGetClient); - // New prey - // GET - registerMethod(L, "Player", "getPreyState", PlayerFunctions::luaPlayerGetPreyState); - registerMethod(L, "Player", "getPreyUnlocked", PlayerFunctions::luaPlayerGetPreyUnlocked); - registerMethod(L, "Player", "getPreyCurrentMonster", PlayerFunctions::luaPlayerGetPreyCurrentMonster); - registerMethod(L, "Player", "getPreyMonsterList", PlayerFunctions::luaPlayerGetPreyMonsterList); - registerMethod(L, "Player", "getPreyFreeRerollIn", PlayerFunctions::luaPlayerGetPreyFreeRerollIn); - registerMethod(L, "Player", "getPreyTimeLeft", PlayerFunctions::luaPlayerGetPreyTimeLeft); - registerMethod(L, "Player", "getPreyNextUse", PlayerFunctions::luaPlayerGetPreyNextUse); - registerMethod(L, "Player", "getPreyBonusType", PlayerFunctions::luaPlayerGetPreyBonusType); - registerMethod(L, "Player", "getPreyBonusValue", PlayerFunctions::luaPlayerGetPreyBonusValue); - registerMethod(L, "Player", "getPreyBonusGrade", PlayerFunctions::luaPlayerGetPreyBonusGrade); - registerMethod(L, "Player", "getPreyBonusRerolls", PlayerFunctions::luaPlayerGetPreyBonusRerolls); - registerMethod(L, "Player", "getPreyTick", PlayerFunctions::luaPlayerGetPreyTick); - // SET - registerMethod(L, "Player", "setPreyState", PlayerFunctions::luaPlayerSetPreyState); - registerMethod(L, "Player", "setPreyUnlocked", PlayerFunctions::luaPlayerSetPreyUnlocked); - registerMethod(L, "Player", "setPreyCurrentMonster", PlayerFunctions::luaPlayerSetPreyCurrentMonster); - registerMethod(L, "Player", "setPreyMonsterList", PlayerFunctions::luaPlayerSetPreyMonsterList); - registerMethod(L, "Player", "setPreyFreeRerollIn", PlayerFunctions::luaPlayerSetPreyFreeRerollIn); - registerMethod(L, "Player", "setPreyTimeLeft", PlayerFunctions::luaPlayerSetPreyTimeLeft); - registerMethod(L, "Player", "setPreyNextUse", PlayerFunctions::luaPlayerSetPreyNextUse); - registerMethod(L, "Player", "setPreyBonusType", PlayerFunctions::luaPlayerSetPreyBonusType); - registerMethod(L, "Player", "setPreyBonusValue", PlayerFunctions::luaPlayerSetPreyBonusValue); - registerMethod(L, "Player", "setPreyBonusGrade", PlayerFunctions::luaPlayerSetPreyBonusGrade); - registerMethod(L, "Player", "setPreyBonusRerolls", PlayerFunctions::luaPlayerSetPreyBonusRerolls); - registerMethod(L, "Player", "setPreyTick", PlayerFunctions::luaPlayerSetPreyTick); - registerMethod(L, "Player", "getHouse", PlayerFunctions::luaPlayerGetHouse); registerMethod(L, "Player", "sendHouseWindow", PlayerFunctions::luaPlayerSendHouseWindow); registerMethod(L, "Player", "setEditHouse", PlayerFunctions::luaPlayerSetEditHouse); @@ -332,6 +304,15 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayercharmExpansion(lua_State* L); static int luaPlayergetCharmMonsterType(lua_State* L); + static int luaPlayerGetPreyCards(lua_State* L); + static int luaPlayerGetPreyLootPercentage(lua_State* L); + static int luaPlayerPreyThirdSlot(lua_State* L); + static int luaPlayerTaskThirdSlot(lua_State* L); + static int luaPlayerRemovePreyStamina(lua_State* L); + static int luaPlayerAddPreyCards(lua_State* L); + static int luaPlayerGetPreyExperiencePercentage(lua_State* L); + static int luaPlayerRemoveTaskHuntingPoints(lua_State* L); + static int luaPlayerGetCapacity(lua_State* L); static int luaPlayerSetCapacity(lua_State* L); @@ -430,15 +411,6 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerGetStamina(lua_State* L); static int luaPlayerSetStamina(lua_State* L); - static int luaPlayerGetPreyStamina(lua_State* L); - static int luaPlayerGetPreyType(lua_State* L); - static int luaPlayerGetPreyValue(lua_State* L); - static int luaPlayerGetPreyName(lua_State* L); - static int luaPlayerSetPreyStamina(lua_State* L); - static int luaPlayerSetPreyType(lua_State* L); - static int luaPlayerSetPreyValue(lua_State* L); - static int luaPlayerSetPreyName(lua_State* L); - static int luaPlayerGetSoul(lua_State* L); static int luaPlayerAddSoul(lua_State* L); static int luaPlayerGetMaxSoul(lua_State* L); @@ -525,32 +497,6 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerSetLootContainer(lua_State* L); static int luaPlayerGetLootContainer(lua_State* L); - static int luaPlayerGetPreyState(lua_State * L); - static int luaPlayerGetPreyUnlocked(lua_State * L); - static int luaPlayerGetPreyCurrentMonster(lua_State * L); - static int luaPlayerGetPreyMonsterList(lua_State * L); - static int luaPlayerGetPreyFreeRerollIn(lua_State * L); - static int luaPlayerGetPreyTimeLeft(lua_State * L); - static int luaPlayerGetPreyNextUse(lua_State * L); - static int luaPlayerGetPreyBonusType(lua_State * L); - static int luaPlayerGetPreyBonusValue(lua_State * L); - static int luaPlayerGetPreyBonusGrade(lua_State * L); - static int luaPlayerGetPreyBonusRerolls(lua_State * L); - static int luaPlayerGetPreyTick(lua_State * L); - - static int luaPlayerSetPreyState(lua_State * L); - static int luaPlayerSetPreyUnlocked(lua_State * L); - static int luaPlayerSetPreyCurrentMonster(lua_State * L); - static int luaPlayerSetPreyMonsterList(lua_State * L); - static int luaPlayerSetPreyFreeRerollIn(lua_State * L); - static int luaPlayerSetPreyTimeLeft(lua_State * L); - static int luaPlayerSetPreyNextUse(lua_State * L); - static int luaPlayerSetPreyBonusType(lua_State * L); - static int luaPlayerSetPreyBonusValue(lua_State * L); - static int luaPlayerSetPreyBonusGrade(lua_State * L); - static int luaPlayerSetPreyBonusRerolls(lua_State * L); - static int luaPlayerSetPreyTick(lua_State * L); - static int luaPlayerGetClient(lua_State* L); static int luaPlayerGetHouse(lua_State* L); diff --git a/src/otserv.cpp b/src/otserv.cpp index ae45fed37ef..f2e5d3c76f3 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -42,6 +42,8 @@ #include "server/network/protocol/protocolstatus.h" #include "server/network/webhook/webhook.h" #include "server/server.h" +#include "io/ioprey.h" +#include "io/iobestiary.h" #if __has_include("gitmetadata.h") #include "gitmetadata.h" @@ -169,6 +171,7 @@ void loadModules() { "data/npclua"); g_game().loadBoostedCreature(); + g_ioprey().InitializeTaskHuntOptions(); } #ifndef UNIT_TESTING diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 7e08feb2aaf..98e2786c490 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -39,7 +39,6 @@ #include "creatures/players/management/waitlist.h" #include "items/weapons/weapons.h" - template void ProtocolGame::addGameTask(Callable function, Args &&... args) { @@ -314,15 +313,6 @@ void ProtocolGame::login(const std::string &name, uint32_t accountId, OperatingS return; } - // New Prey - if (!IOLoginData::loadPlayerPreyData(player)) - { - SPDLOG_WARN("[ProtocolGame::login] - " - "Prey data could not be loaded from player: {}", - player->getName()); - return; - }; - player->setOperatingSystem(operatingSystem); if (!g_game().placeCreature(player, player->getLoginPosition()) && !g_game().placeCreature(player, player->getTemplePosition(), false, true)) @@ -727,6 +717,7 @@ void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyt case 0xAB: parseChannelInvite(msg); break; case 0xAC: parseChannelExclude(msg); break; case 0xB1: parseHighscores(msg); break; + case 0xBA: parseTaskHuntingAction(msg); break; case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; case 0xC7: parseTournamentLeaderboard(msg); break; case 0xC9: /* update tile */ break; @@ -752,6 +743,7 @@ void ProtocolGame::parsePacketFromDispatcher(NetworkMessage msg, uint8_t recvbyt case 0xE6: parseBugReport(msg); break; case 0xE7: /* thank you */ break; case 0xE8: parseDebugAssert(msg); break; + case 0xEB: parsePreyAction(msg); break; case 0xEE: parseGreet(msg); break; case 0xEF: if (!g_configManager().getBoolean(STOREMODULES)) { parseCoinTransfer(msg); } break; /* premium coins transfer */ case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break; @@ -1602,6 +1594,20 @@ void ProtocolGame::parseHighscores(NetworkMessage &msg) g_game().playerHighscores(player, type, category, vocation, worldName, page, entriesPerPage); } +void ProtocolGame::parseTaskHuntingAction(NetworkMessage &msg) +{ + uint8_t slot = msg.getByte(); + uint8_t action = msg.getByte(); + bool upgrade = msg.getByte() != 0; + uint16_t raceId = msg.get(); + + if (!g_configManager().getBoolean(TASK_HUNTING_ENABLED)) { + return; + } + + addGameTask(&Game::playerTaskHuntingAction, player->getID(), slot, action, upgrade, raceId); +} + void ProtocolGame::sendHighscoresNoData() { NetworkMessage msg; @@ -1752,7 +1758,6 @@ void ProtocolGame::parseRuleViolationReport(NetworkMessage &msg) void ProtocolGame::parseBestiarysendRaces() { - IOBestiary g_bestiary; NetworkMessage msg; msg.addByte(0xd5); msg.add(BESTY_RACE_LAST); @@ -1776,7 +1781,7 @@ void ProtocolGame::parseBestiarysendRaces() } msg.addString(BestClass); msg.add(count); - uint16_t unlockedCount = g_bestiary.getBestiaryRaceUnlocked(player, static_cast(i)); + uint16_t unlockedCount = g_iobestiary().getBestiaryRaceUnlocked(player, static_cast(i)); msg.add(unlockedCount); } writeToOutputBuffer(msg); @@ -1794,7 +1799,6 @@ void ProtocolGame::sendBestiaryEntryChanged(uint16_t raceid) void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) { - IOBestiary g_bestiary; uint16_t raceId = msg.get(); std::string Class = ""; MonsterType *mtype = nullptr; @@ -1819,7 +1823,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) } uint32_t killCounter = player->getBestiaryKillCount(raceId); - uint8_t currentLevel = g_bestiary.getKillStatus(mtype, killCounter); + uint8_t currentLevel = g_iobestiary().getKillStatus(mtype, killCounter); NetworkMessage newmsg; newmsg.addByte(0xd7); @@ -1840,7 +1844,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) newmsg.addByte(lootList.size()); for (LootBlock loot : lootList) { - int8_t difficult = g_bestiary.calculateDifficult(loot.chance); + int8_t difficult = g_iobestiary().calculateDifficult(loot.chance); bool shouldAddItem = false; switch (currentLevel) @@ -1898,7 +1902,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) if (currentLevel > 2) { - std::map elements = g_bestiary.getMonsterElements(mtype); + std::map elements = g_iobestiary().getMonsterElements(mtype); newmsg.addByte(elements.size()); for (auto it = std::begin(elements), end = std::end(elements); it != end; it++) @@ -1913,7 +1917,7 @@ void ProtocolGame::parseBestiarysendMonsterData(NetworkMessage &msg) if (currentLevel > 3) { - charmRune_t mType_c = g_bestiary.getCharmFromTarget(player, mtype); + charmRune_t mType_c = g_iobestiary().getCharmFromTarget(player, mtype); if (mType_c != CHARM_NONE) { newmsg.addByte(1); @@ -2303,17 +2307,15 @@ void ProtocolGame::parseMemberFinderWindow(NetworkMessage &msg) void ProtocolGame::parseSendBuyCharmRune(NetworkMessage &msg) { - IOBestiary g_bestiary; charmRune_t runeID = static_cast(msg.getByte()); uint8_t action = msg.getByte(); uint16_t raceid = msg.get(); - g_bestiary.sendBuyCharmRune(player, runeID, action, raceid); + g_iobestiary().sendBuyCharmRune(player, runeID, action, raceid); } void ProtocolGame::refreshBestiaryTracker(std::list trackerList) { NetworkMessage msg; - IOBestiary g_bestiary; msg.addByte(0xB9); msg.addByte(trackerList.size()); for (MonsterType *mtype : trackerList) @@ -2325,7 +2327,7 @@ void ProtocolGame::refreshBestiaryTracker(std::list trackerList) msg.add(mtype->info.bestiarySecondUnlock); msg.add(mtype->info.bestiaryToUnlock); - if (g_bestiary.getKillStatus(mtype, killAmount) == 4) + if (g_iobestiary().getKillStatus(mtype, killAmount) == 4) { msg.addByte(4); } @@ -2339,7 +2341,6 @@ void ProtocolGame::refreshBestiaryTracker(std::list trackerList) void ProtocolGame::BestiarysendCharms() { - IOBestiary g_bestiary; int32_t removeRuneCost = player->getLevel() * 100; if (player->hasCharmExpansion()) { @@ -2358,7 +2359,7 @@ void ProtocolGame::BestiarysendCharms() msg.addString(c_type->description); msg.addByte(0); // Unknown msg.add(c_type->points); - if (g_bestiary.hasCharmUnlockedRuneBit(c_type, player->getUnlockedRunesBit())) + if (g_iobestiary().hasCharmUnlockedRuneBit(c_type, player->getUnlockedRunesBit())) { msg.addByte(1); uint16_t raceid = player->parseRacebyCharm(c_type->id, false, 0); @@ -2381,12 +2382,12 @@ void ProtocolGame::BestiarysendCharms() } msg.addByte(4); // Unknown - std::list finishedMonsters = g_bestiary.getBestiaryFinished(player); - std::list usedRunes = g_bestiary.getCharmUsedRuneBitAll(player); + std::list finishedMonsters = g_iobestiary().getBestiaryFinished(player); + std::list usedRunes = g_iobestiary().getCharmUsedRuneBitAll(player); for (charmRune_t charmRune : usedRunes) { - Charm *tmpCharm = g_bestiary.getBestiaryCharm(charmRune); + Charm *tmpCharm = g_iobestiary().getBestiaryCharm(charmRune); uint16_t tmp_raceid = player->parseRacebyCharm(tmpCharm->id, false, 0); finishedMonsters.remove(tmp_raceid); } @@ -2402,7 +2403,6 @@ void ProtocolGame::BestiarysendCharms() void ProtocolGame::parseBestiarysendCreatures(NetworkMessage &msg) { - IOBestiary g_bestiary; std::ostringstream ss; std::map race = {}; std::string text = ""; @@ -2422,7 +2422,7 @@ void ProtocolGame::parseBestiarysendCreatures(NetworkMessage &msg) } } else { std::string raceName = msg.getString(); - race = g_bestiary.findRaceByName(raceName); + race = g_iobestiary().findRaceByName(raceName); if (race.size() == 0) { @@ -2436,7 +2436,7 @@ void ProtocolGame::parseBestiarysendCreatures(NetworkMessage &msg) newmsg.addByte(0xd6); newmsg.addString(text); newmsg.add(race.size()); - std::map creaturesKilled = g_bestiary.getBestiaryKillCountByMonsterIDs(player, race); + std::map creaturesKilled = g_iobestiary().getBestiaryKillCountByMonsterIDs(player, race); for (auto it_ : race) { @@ -2453,7 +2453,7 @@ void ProtocolGame::parseBestiarysendCreatures(NetworkMessage &msg) { return; } - progress = g_bestiary.getKillStatus(tmpType, _it.second); + progress = g_iobestiary().getKillStatus(tmpType, _it.second); } } @@ -2505,6 +2505,28 @@ void ProtocolGame::parseDebugAssert(NetworkMessage &msg) addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment); } +void ProtocolGame::parsePreyAction(NetworkMessage &msg) +{ + int8_t index = -1; + uint8_t slot = msg.getByte(); + uint8_t action = msg.getByte(); + uint8_t option = 0; + uint16_t raceId = 0; + if (action == static_cast(PreyAction_MonsterSelection)) { + index = msg.getByte(); + } else if (action == static_cast(PreyAction_Option)) { + option = msg.getByte(); + } else if (action == static_cast(PreyAction_ListAll_Selection)) { + raceId = msg.get(); + } + + if (!g_configManager().getBoolean(PREY_ENABLED)) { + return; + } + + addGameTask(&Game::playerPreyAction, player->getID(), slot, action, option, index, raceId); +} + void ProtocolGame::parseInviteToParty(NetworkMessage &msg) { uint32_t targetId = msg.get(); @@ -3576,49 +3598,6 @@ void ProtocolGame::sendPremiumTrigger() } } -// Send preyInfo -void ProtocolGame::initPreyData() -{ - for (uint8_t i = 0; i <= PREY_SLOTNUM_THIRD; i++) - { - sendPreyData(static_cast(i), PREY_STATE_LOCKED); - } - - sendResourcesBalance(); - sendPreyRerollPrice(); -} - -void ProtocolGame::sendPreyRerollPrice(uint32_t price /*= 0*/, uint8_t wildcard /*= 0*/, uint8_t directly /*= 0*/) -{ - NetworkMessage msg; - msg.addByte(0xE9); // reroll prices - msg.add(price); // price - msg.addByte(wildcard); // wildcard - msg.addByte(directly); // selectCreatureDirectly price (5 in tibia) - - // Prey Task - msg.add(0); - msg.add(0); - msg.addByte(0); - msg.addByte(0); - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendPreyData(PreySlotNum_t slot, PreyState_t slotState) -{ - NetworkMessage msg; - msg.addByte(0xE8); - msg.addByte(slot); - - msg.addByte(slotState); - msg.addByte(0x00); // empty byte - msg.add(0); // next free roll - msg.addByte(0x00); // wildCards - - writeToOutputBuffer(msg); -} - void ProtocolGame::sendTextMessage(const TextMessage &message) { NetworkMessage msg; @@ -3922,11 +3901,12 @@ void ProtocolGame::sendGameNews() writeToOutputBuffer(msg); } -void ProtocolGame::sendResourcesBalance(uint64_t money /*= 0*/, uint64_t bank /*= 0*/, uint64_t prey /*= 0*/) +void ProtocolGame::sendResourcesBalance(uint64_t money /*= 0*/, uint64_t bank /*= 0*/, uint64_t preyCards /*= 0*/, uint64_t taskHunting /*= 0*/) { sendResourceBalance(RESOURCE_BANK, bank); sendResourceBalance(RESOURCE_INVENTORY, money); - sendResourceBalance(RESOURCE_PREY, prey); + sendResourceBalance(RESOURCE_PREY_CARDS, preyCards); + sendResourceBalance(RESOURCE_TASK_HUNTING, taskHunting); } void ProtocolGame::sendResourceBalance(Resource_t resourceType, uint64_t value) @@ -4115,7 +4095,7 @@ void ProtocolGame::sendMarketEnter(uint32_t depotId) writeToOutputBuffer(msg); updateCoinBalance(); - sendResourcesBalance(player->getMoney(), player->getBankBalance()); + sendResourcesBalance(player->getMoney(), player->getBankBalance(), player->getPreyCards(), player->getTaskHuntingPoints()); } void ProtocolGame::sendCoinBalance() @@ -5408,6 +5388,9 @@ void ProtocolGame::sendAddCreature(const Creature *creature, const Position &pos sendItemsPrice(); + sendPreyPrices(); + player->sendPreyData(); + player->sendTaskHuntingData(); sendForgingData(); //gameworld light-settings @@ -5472,7 +5455,6 @@ void ProtocolGame::sendAddCreature(const Creature *creature, const Position &pos sendLootContainers(); sendBasicData(); - initPreyData(); player->sendClientCheck(); player->sendGameNews(); @@ -6193,6 +6175,138 @@ void ProtocolGame::sendStoreRequestAdditionalInfo(uint32_t offerId, ClientOffer_ writeToOutputBuffer(msg); } +void ProtocolGame::sendPreyTimeLeft(const PreySlot* slot) +{ + if (!player || !slot) { + return; + } + + NetworkMessage msg; + + msg.addByte(0xE7); + msg.addByte(static_cast(slot->id)); + msg.add(slot->bonusTimeLeft); + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPreyData(const PreySlot* slot) +{ + NetworkMessage msg; + msg.addByte(0xE8); + msg.addByte(static_cast(slot->id)); + msg.addByte(static_cast(slot->state)); + + if (slot->state == PreyDataState_Locked) { + msg.addByte(player->isPremium() ? 0x01 : 0x00); + } else if (slot->state == PreyDataState_Inactive) { + // Empty + } else if (slot->state == PreyDataState_Active) { + if (const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(slot->selectedRaceId)) { + msg.addString(mtype->name); + const Outfit_t outfit = mtype->info.outfit; + msg.add(outfit.lookType); + if (outfit.lookType == 0) { + msg.add(outfit.lookTypeEx); + } else { + msg.addByte(outfit.lookHead); + msg.addByte(outfit.lookBody); + msg.addByte(outfit.lookLegs); + msg.addByte(outfit.lookFeet); + msg.addByte(outfit.lookAddons); + } + + msg.addByte(static_cast(slot->bonus)); + msg.add(slot->bonusPercentage); + msg.addByte(slot->bonusRarity); + msg.add(slot->bonusTimeLeft); + } + } else if (slot->state == PreyDataState_Selection) { + msg.addByte(static_cast(slot->raceIdList.size())); + std::for_each(slot->raceIdList.begin(), slot->raceIdList.end(), [&msg](uint16_t raceId) + { + if (const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(raceId)) { + msg.addString(mtype->name); + const Outfit_t outfit = mtype->info.outfit; + msg.add(outfit.lookType); + if (outfit.lookType == 0) { + msg.add(outfit.lookTypeEx); + } else { + msg.addByte(outfit.lookHead); + msg.addByte(outfit.lookBody); + msg.addByte(outfit.lookLegs); + msg.addByte(outfit.lookFeet); + msg.addByte(outfit.lookAddons); + } + } else { + SPDLOG_WARN("[ProtocolGame::sendPreyData] - Unknown monster type raceid: {}", raceId); + return; + } + }); + } else if (slot->state == PreyDataState_SelectionChangeMonster) { + msg.addByte(static_cast(slot->bonus)); + msg.add(slot->bonusPercentage); + msg.addByte(slot->bonusRarity); + msg.add(static_cast(slot->raceIdList.size())); + std::for_each(slot->raceIdList.begin(), slot->raceIdList.end(), [&msg](uint16_t raceId) + { + if (const MonsterType* mtype = g_monsters().getMonsterTypeByRaceId(raceId)) { + msg.addString(mtype->name); + const Outfit_t outfit = mtype->info.outfit; + msg.add(outfit.lookType); + if (outfit.lookType == 0) { + msg.add(outfit.lookTypeEx); + } else { + msg.addByte(outfit.lookHead); + msg.addByte(outfit.lookBody); + msg.addByte(outfit.lookLegs); + msg.addByte(outfit.lookFeet); + msg.addByte(outfit.lookAddons); + } + } else { + SPDLOG_WARN("[ProtocolGame::sendPreyData] - Unknown monster type raceid: {}", raceId); + return; + } + }); + } else if (slot->state == PreyDataState_ListSelection) { + const std::map bestiaryList = g_game().getBestiaryList(); + msg.add(static_cast(bestiaryList.size())); + std::for_each(bestiaryList.begin(), bestiaryList.end(), [&msg](auto& mType) + { + msg.add(mType.first); + }); + } else { + SPDLOG_WARN("[ProtocolGame::sendPreyData] - Unknown prey state: {}", slot->state); + return; + } + + msg.add(std::max(static_cast(((slot->freeRerollTimeStamp - OTSYS_TIME()) / 1000)), 0)); + msg.addByte(static_cast(slot->option)); + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendPreyPrices() +{ + if (!player) { + return; + } + + NetworkMessage msg; + + msg.addByte(0xE9); + + msg.add(player->getPreyRerollPrice()); + msg.addByte(static_cast(g_configManager().getNumber(PREY_BONUS_REROLL_PRICE))); + msg.addByte(static_cast(g_configManager().getNumber(PREY_SELECTION_LIST_PRICE))); + msg.add(player->getTaskHuntingRerollPrice()); + msg.add(player->getTaskHuntingRerollPrice()); + msg.addByte(static_cast(g_configManager().getNumber(TASK_HUNTING_SELECTION_LIST_PRICE))); + msg.addByte(static_cast(g_configManager().getNumber(TASK_HUNTING_BONUS_REROLL_PRICE))); + + writeToOutputBuffer(msg); +} + void ProtocolGame::sendStoreTrasactionHistory(HistoryStoreOfferList &list, uint32_t page, uint8_t entriesPerPage) { NetworkMessage msg; @@ -6594,11 +6708,20 @@ void ProtocolGame::openImbuementWindow(Item *item) msg.add(itm.second); } - sendResourcesBalance(player->getMoney(), player->getBankBalance()); + sendResourcesBalance(player->getMoney(), player->getBankBalance(), player->getPreyCards(), player->getTaskHuntingPoints()); writeToOutputBuffer(msg); } +void ProtocolGame::sendMessageDialog(const std::string &message) +{ + NetworkMessage msg; + msg.addByte(0xED); + msg.addByte(0x14); // Unknown type + msg.addString(message); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendImbuementResult(const std::string message) { NetworkMessage msg; @@ -6765,6 +6888,75 @@ void ProtocolGame::sendUpdateInputAnalyzer(CombatType_t type, int32_t amount, st writeToOutputBuffer(msg); } +void ProtocolGame::sendTaskHuntingData(const TaskHuntingSlot* slot) +{ + NetworkMessage msg; + msg.addByte(0xBB); + msg.addByte(static_cast(slot->id)); + msg.addByte(static_cast(slot->state)); + if (slot->state == PreyTaskDataState_Locked) { + msg.addByte(player->isPremium() ? 0x01 : 0x00); + } else if (slot->state == PreyTaskDataState_Inactive) { + // Empty + } else if (slot->state == PreyTaskDataState_Selection) { + const Player* user = player; + msg.add(static_cast(slot->raceIdList.size())); + std::for_each(slot->raceIdList.begin(), slot->raceIdList.end(), [&msg, user](uint16_t raceid) + { + msg.add(raceid); + msg.addByte(user->isCreatureUnlockedOnTaskHunting(g_monsters().getMonsterTypeByRaceId(raceid)) ? 0x01 : 0x00); + }); + } else if (slot->state == PreyTaskDataState_ListSelection) { + const Player* user = player; + const std::map bestiaryList = g_game().getBestiaryList(); + msg.add(static_cast(bestiaryList.size())); + std::for_each(bestiaryList.begin(), bestiaryList.end(), [&msg, user](auto& mType) + { + msg.add(mType.first); + msg.addByte(user->isCreatureUnlockedOnTaskHunting(g_monsters().getMonsterType(mType.second)) ? 0x01 : 0x00); + }); + } else if (slot->state == PreyTaskDataState_Active) { + if (const TaskHuntingOption* option = g_ioprey().GetTaskRewardOption(slot)) { + msg.add(slot->selectedRaceId); + if (slot->upgrade) { + msg.addByte(0x01); + msg.add(option->secondKills); + } else { + msg.addByte(0x00); + msg.add(option->firstKills); + } + msg.add(slot->currentKills); + msg.addByte(slot->rarity); + } else { + SPDLOG_WARN("[ProtocolGame::sendTaskHuntingData] - Unknown slot option {} on player {}", slot->id, player->getName()); + return; + } + } else if (slot->state == PreyTaskDataState_Completed) { + if (const TaskHuntingOption* option = g_ioprey().GetTaskRewardOption(slot)) { + msg.add(slot->selectedRaceId); + if (slot->upgrade) { + msg.addByte(0x01); + msg.add(option->secondKills); + msg.add(std::min(slot->currentKills, option->secondKills)); + } else { + msg.addByte(0x00); + msg.add(option->firstKills); + msg.add(std::min(slot->currentKills, option->firstKills)); + } + msg.addByte(slot->rarity); + } else { + SPDLOG_WARN("[ProtocolGame::sendTaskHuntingData] - Unknown slot option {} on player {}", slot->id, player->getName()); + return; + } + } else { + SPDLOG_WARN("[ProtocolGame::sendTaskHuntingData] - Unknown task hunting state: {}", slot->state); + return; + } + + msg.add(std::max(static_cast(((slot->freeRerollTimeStamp - OTSYS_TIME()) / 1000)), 0)); + writeToOutputBuffer(msg); +} + void ProtocolGame::MoveUpCreature(NetworkMessage &msg, const Creature *creature, const Position &newPos, const Position &oldPos) { if (creature != player) diff --git a/src/server/network/protocol/protocolgame.h b/src/server/network/protocol/protocolgame.h index b2f117ec61c..360c96f658f 100644 --- a/src/server/network/protocol/protocolgame.h +++ b/src/server/network/protocol/protocolgame.h @@ -28,6 +28,7 @@ #include "creatures/creature.h" #include "game/scheduling/tasks.h" #include "game/gamestore.h" +#include "io/ioprey.h" class NetworkMessage; class Player; @@ -38,6 +39,9 @@ class Tile; class Connection; class Quest; class ProtocolGame; +class PreySlot; +class TaskHuntingSlot; +class TaskHuntingOption; using ProtocolGame_ptr = std::shared_ptr; @@ -138,6 +142,7 @@ class ProtocolGame final : public Protocol void parseCyclopediaCharacterInfo(NetworkMessage &msg); void parseHighscores(NetworkMessage &msg); + void parseTaskHuntingAction(NetworkMessage &msg); void sendHighscoresNoData(); void sendHighscores(const std::vector &characters, uint8_t categoryId, uint32_t vocationId, uint16_t page, uint16_t pages); @@ -146,6 +151,7 @@ class ProtocolGame final : public Protocol void parseGreet(NetworkMessage &msg); void parseBugReport(NetworkMessage &msg); void parseDebugAssert(NetworkMessage &msg); + void parsePreyAction(NetworkMessage &msg); void parseRuleViolationReport(NetworkMessage &msg); void parseBestiarysendRaces(); @@ -276,12 +282,7 @@ class ProtocolGame final : public Protocol // Unjust Panel void sendUnjustifiedPoints(const uint8_t &dayProgress, const uint8_t &dayLeft, const uint8_t &weekProgress, const uint8_t &weekLeft, const uint8_t &monthProgress, const uint8_t &monthLeft, const uint8_t &skullDuration); - - // Send preyInfo - void initPreyData(); - void sendPreyRerollPrice(uint32_t price = 0, uint8_t wildcard = 0, uint8_t directly = 0); - void sendPreyData(PreySlotNum_t slot, PreyState_t slotState); - + void sendCancelWalk(); void sendChangeSpeed(const Creature *creature, uint32_t speed); void sendCancelTarget(); @@ -320,7 +321,7 @@ class ProtocolGame final : public Protocol void sendCloseShop(); void sendClientCheck(); void sendGameNews(); - void sendResourcesBalance(uint64_t money = 0, uint64_t bank = 0, uint64_t prey = 0); + void sendResourcesBalance(uint64_t money = 0, uint64_t bank = 0, uint64_t preyCards = 0, uint64_t taskHunting = 0); void sendResourceBalance(Resource_t resourceType, uint64_t value); void sendSaleItemList(const std::vector &shopVector, const std::map &inventoryMap); void sendMarketEnter(uint32_t depotId); @@ -367,6 +368,11 @@ class ProtocolGame final : public Protocol void sendStoreError(GameStoreError_t error, const std::string &message); void sendStorePurchaseSuccessful(const std::string &message, const uint32_t coinBalance); void sendStoreRequestAdditionalInfo(uint32_t offerId, ClientOffer_t clientOfferType); + + void sendPreyTimeLeft(const PreySlot* slot); + void sendPreyData(const PreySlot* slot); + void sendPreyPrices(); + void sendStoreTrasactionHistory(HistoryStoreOfferList &list, uint32_t page, uint8_t entriesPerPage); void parseStoreOpenTransactionHistory(NetworkMessage &msg); void parseStoreRequestTransactionHistory(NetworkMessage &msg); @@ -430,12 +436,15 @@ class ProtocolGame final : public Protocol void AddPlayerSkills(NetworkMessage &msg); void sendBlessStatus(); void sendPremiumTrigger(); + void sendMessageDialog(const std::string &message); void AddWorldLight(NetworkMessage &msg, LightInfo lightInfo); void AddCreatureLight(NetworkMessage &msg, const Creature *creature); //tiles static void RemoveTileThing(NetworkMessage &msg, const Position &pos, uint32_t stackpos); + void sendTaskHuntingData(const TaskHuntingSlot* slot); + void MoveUpCreature(NetworkMessage &msg, const Creature *creature, const Position &newPos, const Position &oldPos); void MoveDownCreature(NetworkMessage &msg, const Creature *creature, const Position &newPos, const Position &oldPos); diff --git a/src/server/server_definitions.hpp b/src/server/server_definitions.hpp index 2a7498f3743..722f28f4e34 100644 --- a/src/server/server_definitions.hpp +++ b/src/server/server_definitions.hpp @@ -59,25 +59,11 @@ enum SessionEndInformations : uint8_t { SESSION_END_UNK3, }; -enum PreySlotNum_t : uint8_t{ - PREY_SLOTNUM_FIRST = 0, - PREY_SLOTNUM_SECOND = 1, - PREY_SLOTNUM_THIRD = 2, -}; - -enum PreyState_t : uint8_t -{ - PREY_STATE_LOCKED = 0, - PREY_STATE_INACTIVE = 1, - PREY_STATE_ACTIVE = 2, - PREY_STATE_SELECTION = 3, - PREY_STATE_SELECTION_CHANGE_MONSTER = 4, -}; - enum Resource_t : uint8_t{ RESOURCE_BANK = 0x00, RESOURCE_INVENTORY = 0x01, - RESOURCE_PREY = 0x0A, + RESOURCE_PREY_CARDS = 0x0A, + RESOURCE_TASK_HUNTING = 0x32 }; enum InspectObjectTypes : uint8_t {