From 65c58b09642db0eb9ca2aae5b2a9c27a02994506 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 22 Aug 2024 10:49:05 -0300 Subject: [PATCH 01/27] fix: register actions quests compatibility (#2799) Libs must be independent, given that errors are returned due to storage checks and other forms. --- data-canary/scripts/lib/register_actions.lua | 271 ++++++++++++++++++ .../scripts/lib/register_actions.lua | 0 2 files changed, 271 insertions(+) create mode 100644 data-canary/scripts/lib/register_actions.lua rename {data => data-otservbr-global}/scripts/lib/register_actions.lua (100%) diff --git a/data-canary/scripts/lib/register_actions.lua b/data-canary/scripts/lib/register_actions.lua new file mode 100644 index 00000000000..7c0aa49102e --- /dev/null +++ b/data-canary/scripts/lib/register_actions.lua @@ -0,0 +1,271 @@ +local holeId = { 294, 369, 370, 385, 394, 411, 412, 413, 432, 433, 435, 8709, 594, 595, 615, 609, 610, 615, 1156, 482, 483, 868, 874, 4824, 7768, 433, 432, 413, 7767, 411, 370, 369, 7737, 7755, 7768, 7767, 7515, 7516, 7517, 7518, 7519, 7520, 7521, 7522, 7762, 8144, 8690, 8709, 12203, 12961, 17239, 19220, 23364 } -- usable rope holes, for rope spots see global.lua +local wildGrowth = { 2130, 2130, 2982, 2524, 2030, 2029, 10182 } -- wild growth destroyable by machete +local jungleGrass = { [3696] = 3695, [3702] = 3701, [17153] = 17151 } -- grass destroyable by machete +local groundIds = { 354, 355 } -- pick usable ground +local sandIds = { 231 } -- desert sand +local fruits = { 2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2684, 2685, 5097, 8839, 8840, 8841 } -- fruits to make decorated cake with knife +local holes = { 593, 606, 608, 867, 21341 } -- holes opened by shovel + +function destroyItem(player, target, toPosition) + if type(target) ~= "userdata" or not target:isItem() then + return false + end + + if target:hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) or target:hasAttribute(ITEM_ATTRIBUTE_ACTIONID) then + return false + end + + if toPosition.x == CONTAINER_POSITION then + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) + return true + end + + local destroyId = ItemType(target.itemid):getDestroyId() + if destroyId == 0 then + return false + end + + if math.random(7) == 1 then + local item = Game.createItem(destroyId, 1, toPosition) + if item then + item:decay() + end + + -- Move items outside the container + if target:isContainer() then + for i = target:getSize() - 1, 0, -1 do + local containerItem = target:getItem(i) + if containerItem then + containerItem:moveTo(toPosition) + end + end + end + + target:remove(1) + end + + toPosition:sendMagicEffect(CONST_ME_POFF) + return true +end + +function onUseMachete(player, item, fromPosition, target, toPosition, isHotkey) + local targetId = target.itemid + if not targetId then + return true + end + + if table.contains(wildGrowth, targetId) then + toPosition:sendMagicEffect(CONST_ME_POFF) + target:remove() + return true + end + + local grass = jungleGrass[targetId] + if grass then + target:transform(grass) + target:decay() + player:addAchievementProgress("Nothing Can Stop Me", 100) + return true + end + return destroyItem(player, target, toPosition) +end + +function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) + if target.itemid == 10310 then -- shiny stone refining + local chance = math.random(1, 100) + if chance == 1 then + player:addItem(ITEM_CRYSTAL_COIN) -- 1% chance of getting crystal coin + elseif chance <= 6 then + player:addItem(ITEM_GOLD_COIN) -- 5% chance of getting gold coin + elseif chance <= 51 then + player:addItem(ITEM_PLATINUM_COIN) -- 45% chance of getting platinum coin + else + player:addItem(3028) -- 49% chance of getting small diamond + end + + player:addAchievementProgress("Petrologist", 100) + target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) + target:remove(1) + return true + end + + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + if table.contains(groundIds, ground.itemid) and ground.actionid == actionIds.pickHole then + ground:transform(394) + ground:decay() + toPosition:sendMagicEffect(CONST_ME_POFF) + + toPosition.z = toPosition.z + 1 + tile:relocateTo(toPosition) + return true + end + + -- Ice fishing hole + if ground.itemid == 7200 then + ground:transform(7236) + ground:decay() + toPosition:sendMagicEffect(CONST_ME_HITAREA) + return true + end + return false +end + +function onUseRope(player, item, fromPosition, target, toPosition, isHotkey) + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if ground and table.contains(ropeSpots, ground:getId()) or tile:getItemById(12935) then + tile = Tile(toPosition:moveUpstairs()) + if not tile then + return false + end + + if tile:hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then + player:sendCancelMessage(RETURNVALUE_PLAYERISPZLOCKED) + return true + end + + player:teleportTo(toPosition, false) + return true + end + + if table.contains(holeId, target.itemid) then + toPosition.z = toPosition.z + 1 + tile = Tile(toPosition) + if not tile then + return false + end + + local thing = tile:getTopVisibleThing() + if not thing then + return true + end + + if thing:isPlayer() then + if Tile(toPosition:moveUpstairs()):queryAdd(thing) ~= RETURNVALUE_NOERROR then + return false + end + + return thing:teleportTo(toPosition, false) + elseif thing:isItem() and thing:getType():isMovable() then + return thing:moveTo(toPosition:moveUpstairs()) + end + return true + end + return false +end + +function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) + local tile = Tile(toPosition) + if not tile then + return false + end + + local ground = tile:getGround() + if not ground then + return false + end + + local groundId = ground:getId() + if table.contains(holes, groundId) then + ground:transform(groundId + 1) + ground:decay() + + toPosition.z = toPosition.z + 1 + tile:relocateTo(toPosition) + player:addAchievementProgress("The Undertaker", 500) + elseif target.itemid == 867 then -- large hole + target:transform(868) + target:decay() + player:addAchievementProgress("The Undertaker", 500) + elseif target.itemid == 17950 then -- swamp digging + if not player:hasExhaustion("swamp-digging") then + local chance = math.random(100) + if chance >= 1 and chance <= 42 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a dead snake.") + player:addItem(4259) + elseif chance >= 43 and chance <= 79 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a small diamond.") + player:addItem(3028) + elseif chance >= 80 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a leech.") + player:addItem(17858) + end + + player:setExhaustion("swamp-digging", 7 * 24 * 60 * 60) + player:getPosition():sendMagicEffect(CONST_ME_GREEN_RINGS) + end + elseif table.contains(sandIds, groundId) then + local randomValue = math.random(1, 100) + if target.actionid == actionIds.sandHole and randomValue <= 20 then + ground:transform(615) + ground:decay() + elseif randomValue == 1 then + Game.createItem(3042, 1, toPosition) + player:addAchievementProgress("Gold Digger", 100) + elseif randomValue > 95 then + Game.createMonster("Scarab", toPosition) + end + + toPosition:sendMagicEffect(CONST_ME_POFF) + else + return false + end + return true +end + +function onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({ 3453, 9596 }, item.itemid) then + return false + end + + if target.itemid == 3653 then -- wheat + target:transform(3651) + target:decay() + Game.createItem(3605, 1, toPosition) -- bunch of wheat + player:addAchievementProgress("Happy Farmer", 200) + return true + end + + if target.itemid == 5464 then -- burning sugar cane + target:transform(5463) + target:decay() + Game.createItem(5466, 1, toPosition) -- bunch of sugar cane + player:addAchievementProgress("Natural Sweetener", 50) + return true + end + return destroyItem(player, target, toPosition) +end + +function onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({ 3304, 9598 }, item.itemid) then + return false + end + return destroyItem(player, target, toPosition) +end + +function onUseKitchenKnife(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({ 3469, 9594, 9598 }, item.itemid) then + return false + end + + if table.contains(fruits, target.itemid) and player:removeItem(6277, 1) then + target:remove(1) + player:addItem(6278, 1) + player:getPosition():sendMagicEffect(CONST_ME_MAGIC_GREEN) + return true + end + return false +end diff --git a/data/scripts/lib/register_actions.lua b/data-otservbr-global/scripts/lib/register_actions.lua similarity index 100% rename from data/scripts/lib/register_actions.lua rename to data-otservbr-global/scripts/lib/register_actions.lua From 5db19a9622dedd7466c0c9d9a01cc50f6c2e430c Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Mon, 26 Aug 2024 18:02:09 -0300 Subject: [PATCH 02/27] fix: wrong params in creatureSetIcon function (#2835) --- src/lua/functions/creatures/creature_functions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lua/functions/creatures/creature_functions.cpp b/src/lua/functions/creatures/creature_functions.cpp index 6b09945971a..81a4a0b5304 100644 --- a/src/lua/functions/creatures/creature_functions.cpp +++ b/src/lua/functions/creatures/creature_functions.cpp @@ -1020,11 +1020,11 @@ int CreatureFunctions::luaCreatureSetIcon(lua_State* L) { return 1; } const auto key = getString(L, 2); - auto category = getNumber(L, 3); - auto count = getNumber(L, 5, 0); + const auto category = getNumber(L, 3); + const auto count = getNumber(L, 5, 0); CreatureIcon creatureIcon; if (category == CreatureIconCategory_t::Modifications) { - auto icon = getNumber(L, 5); + auto icon = getNumber(L, 4); creatureIcon = CreatureIcon(icon, count); } else { auto icon = getNumber(L, 4); From 89ce6c42847168984f3bcc2b188d07fe60385448 Mon Sep 17 00:00:00 2001 From: kokekanon <114332266+kokekanon@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:40:54 -0400 Subject: [PATCH 03/27] fix: wrong packet in protocol 11.00 (sendSaleItemList) (#2837) --- src/server/network/protocol/protocolgame.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index ca6f1f77961..032b9b512d0 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -4931,8 +4931,9 @@ void ProtocolGame::sendSaleItemList(const std::vector &shopVector, co } uint16_t itemsToSend = 0; + const uint16_t ItemsToSendLimit = oldProtocol ? 0xFF : 0xFFFF; auto msgPosition = msg.getBufferPosition(); - msg.skipBytes(2); + msg.skipBytes(oldProtocol ? 1 : 2); for (const ShopBlock &shopBlock : shopVector) { if (shopBlock.itemSellPrice == 0) { @@ -4947,14 +4948,18 @@ void ProtocolGame::sendSaleItemList(const std::vector &shopVector, co } else { msg.add(std::min(it->second, std::numeric_limits::max())); } - if (++itemsToSend >= 0xFFFF) { + if (++itemsToSend >= ItemsToSendLimit) { break; } } } msg.setBufferPosition(msgPosition); - msg.add(itemsToSend); + if (oldProtocol) { + msg.addByte(static_cast(itemsToSend)); + } else { + msg.add(itemsToSend); + } writeToOutputBuffer(msg); } From 43c085cb51681ea096e53c259e0b3f7e8c66910a Mon Sep 17 00:00:00 2001 From: HT Cesta <58153179+htc16@users.noreply.github.com> Date: Mon, 26 Aug 2024 20:00:29 -0300 Subject: [PATCH 04/27] fix: quests from version 9 revised (#2827) --- data-otservbr-global/lib/core/quests.lua | 88 +++--- data-otservbr-global/lib/core/storages.lua | 236 +++++++-------- data-otservbr-global/npc/appaloosa.lua | 4 +- .../npc/broken_servant_sentry.lua | 40 ++- data-otservbr-global/npc/commander_stone.lua | 98 +++--- data-otservbr-global/npc/doctor_gnomedix.lua | 4 +- data-otservbr-global/npc/gnomally.lua | 8 +- data-otservbr-global/npc/gnomaticus.lua | 18 +- data-otservbr-global/npc/gnomelvis.lua | 24 +- data-otservbr-global/npc/gnomeral.lua | 102 +++---- data-otservbr-global/npc/gnomerik.lua | 66 ++-- data-otservbr-global/npc/gnomespector.lua | 4 +- data-otservbr-global/npc/gnomewart.lua | 14 +- data-otservbr-global/npc/gnominus.lua | 4 +- data-otservbr-global/npc/gnomission.lua | 30 +- data-otservbr-global/npc/hyacinth.lua | 22 +- data-otservbr-global/npc/lily.lua | 14 +- data-otservbr-global/npc/palomino.lua | 4 +- data-otservbr-global/npc/paulie.lua | 6 +- data-otservbr-global/npc/scrutinon.lua | 45 ++- data-otservbr-global/npc/servant_sentry.lua | 8 +- data-otservbr-global/npc/spectulus.lua | 16 +- data-otservbr-global/npc/tom.lua | 18 +- data-otservbr-global/npc/vascalir.lua | 284 +++++++++--------- data-otservbr-global/npc/xelvar.lua | 8 +- .../creaturescripts/customs/freequests.lua | 30 +- .../globalevents/others/check_mount.lua | 4 +- .../scripts/lib/register_actions.lua | 2 +- .../quests/bigfoot_burden/actions_beer.lua | 4 +- .../quests/bigfoot_burden/actions_crystal.lua | 6 +- .../bigfoot_burden/actions_extractor.lua | 6 +- .../bigfoot_burden/actions_matchmaker.lua | 6 +- .../bigfoot_burden/actions_mouthpiece.lua | 4 +- .../quests/bigfoot_burden/actions_music.lua | 24 +- .../quests/bigfoot_burden/actions_pig.lua | 6 +- .../quests/bigfoot_burden/actions_repair.lua | 6 +- .../quests/bigfoot_burden/actions_rewards.lua | 10 +- .../bigfoot_burden/actions_shooting.lua | 10 +- .../quests/bigfoot_burden/actions_spores.lua | 8 +- .../quests/bigfoot_burden/actions_stone.lua | 4 +- .../creaturescripts_bosses_warzone.lua | 14 +- .../creaturescripts_versperoth_kill.lua | 4 +- .../creaturescripts_wiggler_kill.lua | 6 +- .../movements_gnomebase_teleport.lua | 12 +- .../bigfoot_burden/movements_task_ear.lua | 12 +- .../movements_task_endurance.lua | 4 +- .../movements_task_shooting.lua | 2 +- .../bigfoot_burden/movements_task_x_ray.lua | 4 +- .../movements_versperoth_spawn.lua | 12 +- .../movements_warzone_teleport.lua | 10 +- .../liquid_black/actions_chairteleport.lua | 4 +- .../liquid_black/actions_notescoordinates.lua | 4 +- .../quests/liquid_black/movements_ladder.lua | 2 +- .../liquid_black/movements_quick_access.lua | 2 +- .../liquid_black/movements_shortcut.lua | 4 +- .../the_rookie_guard/mission02_defence.lua | 20 +- .../mission03_rational_request.lua | 6 +- .../mission04_home_brewed.lua | 2 +- .../the_rookie_guard/mission05_web_terror.lua | 14 +- .../mission06_run_like_wolf.lua | 35 +-- .../the_rookie_guard/mission07_attack.lua | 14 +- .../the_rookie_guard/mission09_rock_troll.lua | 20 +- .../mission10_tomb_raiding.lua | 16 +- .../mission11_sweet_poison.lua | 8 +- .../mission12_into_fortress.lua | 72 ++--- .../quests/the_rookie_guard/missions.lua | 66 ++-- .../spells/monster/spider_queen_wrap.lua | 2 +- data-otservbr-global/startup/tables/chest.lua | 9 + .../startup/tables/door_quest.lua | 8 +- data/libs/functions/player.lua | 12 +- 70 files changed, 864 insertions(+), 831 deletions(-) diff --git a/data-otservbr-global/lib/core/quests.lua b/data-otservbr-global/lib/core/quests.lua index 433803b9b21..8623ba7072a 100644 --- a/data-otservbr-global/lib/core/quests.lua +++ b/data-otservbr-global/lib/core/quests.lua @@ -262,12 +262,12 @@ if not Quests then }, [5] = { name = "Bigfoot's Burden", - startStorageId = Storage.BigfootBurden.QuestLine, + startStorageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, startStorageValue = 1, missions = { [1] = { name = "Looking for Gnomerik", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1033, startValue = 1, endValue = 2, @@ -277,7 +277,7 @@ if not Quests then }, [2] = { name = "A New Recruit", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1034, startValue = 3, endValue = 4, @@ -285,7 +285,7 @@ if not Quests then }, [3] = { name = "Recruitment: A Test in Gnomology", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1035, startValue = 5, endValue = 7, @@ -298,7 +298,7 @@ if not Quests then }, [4] = { name = "Recruitment: Medical Examination", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1036, startValue = 8, endValue = 9, @@ -306,7 +306,7 @@ if not Quests then }, [5] = { name = "Recruitment: Ear Examination", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1037, startValue = 10, endValue = 12, @@ -319,7 +319,7 @@ if not Quests then }, [6] = { name = "Recruitment: Gnomish Warfare", - storageId = Storage.BigfootBurden.Shooting, + storageId = Storage.Quest.U9_60.BigfootsBurden.Shooting, missionId = 1038, startValue = 0, endValue = 5, @@ -327,13 +327,13 @@ if not Quests then return string.format( "Hit five targets in a row. \z Don't hit an innocent target as it will reset your hit counter. %d / 5", - (math.max(player:getStorageValue(Storage.BigfootBurden.Shooting), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting), 0)) ) end, }, [7] = { name = "Recruitment: Gnomish Warfare", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1039, startValue = 15, endValue = 16, @@ -341,7 +341,7 @@ if not Quests then }, [8] = { name = "Recruitment: Endurance Test", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1040, startValue = 17, endValue = 20, @@ -353,7 +353,7 @@ if not Quests then }, [9] = { name = "Recruitment: Soul Melody", - storageId = Storage.BigfootBurden.QuestLine, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLine, missionId = 1041, startValue = 21, endValue = 23, @@ -365,7 +365,7 @@ if not Quests then }, [10] = { name = "Recruitment", - storageId = Storage.BigfootBurden.QuestLineComplete, + storageId = Storage.Quest.U9_60.BigfootsBurden.QuestLineComplete, missionId = 1042, startValue = 1, endValue = 2, @@ -373,7 +373,7 @@ if not Quests then }, [11] = { name = "Gnome Reputation", - storageId = Storage.BigfootBurden.Rank, + storageId = Storage.Quest.U9_60.BigfootsBurden.Rank, missionId = 1043, startValue = 0, endValue = 999999, @@ -381,13 +381,13 @@ if not Quests then return string.format( "Your reputation in the eyes of gnomekind is %d.\nYour standing rises at: \z \nReputation 30 - I \nReputation 120 - II \nReputation 480 - III \nReputation 1440 - IV \n", - (math.max(player:getStorageValue(Storage.BigfootBurden.Rank), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank), 0)) ) end, }, [12] = { name = "Daily Minor: Crystal Keeper", - storageId = Storage.BigfootBurden.RepairedCrystalCount, + storageId = Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount, missionId = 1044, startValue = 0, endValue = 5, @@ -395,13 +395,13 @@ if not Quests then return string.format( "Use the repair crystal to repair five damaged blue crystals in the crystal caves. \z Damaged crystals will not glow.\n%d / 5", - (math.max(player:getStorageValue(Storage.BigfootBurden.RepairedCrystalCount), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount), 0)) ) end, }, [13] = { name = "Daily Minor: Raiders of the Lost Spark", - storageId = Storage.BigfootBurden.ExtractedCount, + storageId = Storage.Quest.U9_60.BigfootsBurden.ExtractedCount, missionId = 1045, startValue = 0, endValue = 7, @@ -409,23 +409,23 @@ if not Quests then return string.format( "Kill crystal crushers and use the discharger item on the corpse to collect their charges. \z Gather 7 charges and report back. %d / 7", - (math.max(player:getStorageValue(Storage.BigfootBurden.ExtractedCount), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExtractedCount), 0)) ) end, }, [14] = { name = "Daily Minor Plus: Exterminators", - storageId = Storage.BigfootBurden.ExterminatedCount, + storageId = Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount, missionId = 1046, startValue = 0, endValue = 10, description = function(player) - return string.format("Kill 10 of the wigglers for the gnomes. Then report back. %d / 10", (math.max(player:getStorageValue(Storage.BigfootBurden.ExterminatedCount), 0))) + return string.format("Kill 10 of the wigglers for the gnomes. Then report back. %d / 10", (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount), 0))) end, }, [15] = { name = "Daily Minor Plus: Mushroom Digger", - storageId = Storage.BigfootBurden.MushroomCount, + storageId = Storage.Quest.U9_60.BigfootsBurden.MushroomCount, missionId = 1047, startValue = 0, endValue = 3, @@ -434,13 +434,13 @@ if not Quests then "Find a truffle sniffing pig and lure it around. \z Occasionally it will unearth some truffles. Use the baby pig on the truffles to feed it 3 times. \z Then report back to the gnomes. %d / 3", - (math.max(player:getStorageValue(Storage.BigfootBurden.MushroomCount), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomCount), 0)) ) end, }, [16] = { name = "Daily Major: Matchmaker", - storageId = Storage.BigfootBurden.MatchmakerStatus, + storageId = Storage.Quest.U9_60.BigfootsBurden.MatchmakerStatus, missionId = 1048, startValue = 0, endValue = 1, @@ -452,7 +452,7 @@ if not Quests then }, [17] = { name = "Daily Major: The Tinker's Bell", - storageId = Storage.BigfootBurden.GolemCount, + storageId = Storage.Quest.U9_60.BigfootsBurden.GolemCount, missionId = 1049, startValue = 0, endValue = 4, @@ -460,13 +460,13 @@ if not Quests then return string.format( "Use the harmonic bell on the mad golems in the golem workshop so that they will \z automatically be teleported to the gnomish workshops. Then report back to the gnomes. %d / 4", - (math.max(player:getStorageValue(Storage.BigfootBurden.GolemCount), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount), 0)) ) end, }, [18] = { name = "Daily Major Advanced: Spores", - storageId = Storage.BigfootBurden.SporeCount, + storageId = Storage.Quest.U9_60.BigfootsBurden.SporeCount, missionId = 1050, startValue = 0, endValue = 4, @@ -475,7 +475,7 @@ if not Quests then }, [19] = { name = "Daily Major Advanced: Yet Another Grinding", - storageId = Storage.BigfootBurden.GrindstoneStatus, + storageId = Storage.Quest.U9_60.BigfootsBurden.GrindstoneStatus, missionId = 1051, startValue = 0, endValue = 2, @@ -483,7 +483,7 @@ if not Quests then }, [20] = { name = "Gnomish War Hero (Warzone 1)", - storageId = Storage.BigfootBurden.Warzone1Access, + storageId = Storage.Quest.U9_60.BigfootsBurden.Warzone1Access, missionId = 1052, startValue = 1, endValue = 2, @@ -494,7 +494,7 @@ if not Quests then }, [21] = { name = "Gnomish War Hero (Warzone 2)", - storageId = Storage.BigfootBurden.Warzone2Access, + storageId = Storage.Quest.U9_60.BigfootsBurden.Warzone2Access, missionId = 1053, startValue = 1, endValue = 2, @@ -505,7 +505,7 @@ if not Quests then }, [22] = { name = "Gnomish War Hero (Warzone 3)", - storageId = Storage.BigfootBurden.Warzone3Access, + storageId = Storage.Quest.U9_60.BigfootsBurden.Warzone3Access, missionId = 1054, startValue = 1, endValue = 2, @@ -5742,12 +5742,12 @@ if not Quests then }, [43] = { name = "The Rookie Guard", - startStorageId = Storage.TheRookieGuard.Questline, + startStorageId = Storage.Quest.U9_1.TheRookieGuard.Questline, startStorageValue = 1, missions = { [1] = { name = "Mission 01: A Taste of Things to Come", - storageId = Storage.TheRookieGuard.Mission01, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission01, missionId = 10397, startValue = 1, endValue = 1, @@ -5757,7 +5757,7 @@ if not Quests then }, [2] = { name = "Mission 02: Defence!", - storageId = Storage.TheRookieGuard.Mission02, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission02, missionId = 10398, startValue = 1, endValue = 5, @@ -5771,7 +5771,7 @@ if not Quests then }, [3] = { name = "Mission 03: A Rational Request", - storageId = Storage.TheRookieGuard.RatKills, + storageId = Storage.Quest.U9_1.TheRookieGuard.RatKills, missionId = 10399, startValue = 0, endValue = 5, @@ -5779,13 +5779,13 @@ if not Quests then return string.format( "Vascalir sent you to the Rookgaard sewers to kill 5 rats. You have already killed %d/5. \z Return to Vascalir once you're done.", - (math.max(player:getStorageValue(Storage.TheRookieGuard.RatKills), 0)) + (math.max(player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills), 0)) ) end, }, [4] = { name = "Mission 04: Home-Brewed", - storageId = Storage.TheRookieGuard.Mission04, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission04, missionId = 10400, startValue = 1, endValue = 6, @@ -5800,7 +5800,7 @@ if not Quests then }, [5] = { name = "Mission 05: Web of Terror", - storageId = Storage.TheRookieGuard.Mission05, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission05, missionId = 10401, startValue = 1, endValue = 6, @@ -5815,7 +5815,7 @@ if not Quests then }, [6] = { name = "Mission 06: Run Like a Wolf", - storageId = Storage.TheRookieGuard.Mission06, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission06, missionId = 10402, startValue = 1, endValue = 7, @@ -5831,7 +5831,7 @@ if not Quests then }, [7] = { name = "Mission 07: Attack!", - storageId = Storage.TheRookieGuard.Mission07, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission07, missionId = 10403, startValue = 1, endValue = 2, @@ -5842,7 +5842,7 @@ if not Quests then }, [8] = { name = "Mission 08: Less Risk - More Fun", - storageId = Storage.TheRookieGuard.Mission08, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission08, missionId = 10404, startValue = 1, endValue = 2, @@ -5853,7 +5853,7 @@ if not Quests then }, [9] = { name = "Mission 09: Rock 'n Troll", - storageId = Storage.TheRookieGuard.Mission09, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission09, missionId = 10405, startValue = 1, endValue = 10, @@ -5872,7 +5872,7 @@ if not Quests then }, [10] = { name = "Mission 10: Tomb Raiding", - storageId = Storage.TheRookieGuard.Mission10, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission10, missionId = 10406, startValue = 1, endValue = 3, @@ -5884,7 +5884,7 @@ if not Quests then }, [11] = { name = "Mission 11: Sweet Poison", - storageId = Storage.TheRookieGuard.Mission11, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission11, missionId = 10407, startValue = 1, endValue = 5, @@ -5898,7 +5898,7 @@ if not Quests then }, [12] = { name = "Mission 12: Into The Fortress", - storageId = Storage.TheRookieGuard.Mission12, + storageId = Storage.Quest.U9_1.TheRookieGuard.Mission12, missionId = 10408, startValue = 1, endValue = 15, diff --git a/data-otservbr-global/lib/core/storages.lua b/data-otservbr-global/lib/core/storages.lua index 72a532ef3da..cfbda04a03b 100644 --- a/data-otservbr-global/lib/core/storages.lua +++ b/data-otservbr-global/lib/core/storages.lua @@ -86,7 +86,6 @@ Storage = { WagonTicket = 30009, FirstMageWeapon = 30011, KawillBlessing = 30014, - RentedHorseTimer = 30015, FountainOfLife = 30016, -- Promotion Storage cannot be changed, it is set in source code Promotion = 30018, @@ -100,10 +99,6 @@ Storage = { ChayenneReward = 30033, SwampDiggingTimeout = 30034, Atrad = 30036, - ElementalistQuest1 = 30037, - ElementalistQuest2 = 30038, - ElementalistQuest3 = 30039, - ElementalistOutfitStart = 30040, WayfarerOutfit = 30041, DreamOutfit = 30042, Percht1 = 30043, @@ -132,11 +127,6 @@ Storage = { ThirdStage = 50003, Crystal = 50004, }, - LiquidBlackQuest = { - -- Reserved storage from 50010 - 50014 - Questline = 50010, - Visitor = 50011, - }, Kilmaresh = { -- Reserved storage from 50015 - 50049 Questline = 50015, @@ -515,62 +505,6 @@ Storage = { AmuletTimer = 50520, AmuletStatus = 50521, }, - BigfootBurden = { - -- Reserved storage from 50660 - 50719 - QuestLine = 50660, - Test = 50661, - Shooting = 50662, - QuestLineComplete = 50663, - MelodyTone1 = 50664, - MelodyTone2 = 50665, - MelodyTone3 = 50666, - MelodyTone4 = 50667, - MelodyTone5 = 50668, - MelodyTone6 = 50669, - MelodyTone7 = 50670, - MelodyStatus = 50671, - Rank = 50672, - MissionCrystalKeeper = 50673, - CrystalKeeperTimout = 50674, - RepairedCrystalCount = 50675, - MissionRaidersOfTheLostSpark = 50676, - ExtractedCount = 50677, - RaidersOfTheLostSparkTimeout = 50678, - MissionExterminators = 50679, - ExterminatedCount = 50680, - ExterminatorsTimeout = 50681, - MissionMushroomDigger = 50682, - MushroomCount = 50683, - MushroomDiggerTimeout = 50684, - MissionMatchmaker = 50685, - MatchmakerStatus = 50686, - MatchmakerIdNeeded = 50687, - MatchmakerTimeout = 50688, - MissionTinkersBell = 50689, - GolemCount = 50690, - TinkerBellTimeout = 50691, - MissionSporeGathering = 50692, - SporeCount = 50693, - SporeGatheringTimeout = 50694, - MissionGrindstoneHunt = 50695, - GrindstoneStatus = 50696, - GrindstoneTimeout = 50697, - WarzoneStatus = 50698, - Warzone1Access = 50699, - Warzone2Access = 50700, - Warzone3Access = 50701, - Warzone1Reward = 50702, - Warzone2Reward = 50703, - Warzone3Reward = 50704, - BossKills = 50705, - DoorGoldenFruits = 50706, - GnomedixMsg = 50710, - }, - TheirMastersVoice = { - -- Reserved storage from 50720 - 50739 - SlimeGobblerTimeout = 50720, - SlimeGobblerReceived = 50721, - }, GravediggerOfDrefia = { -- Reserved storage from 50760 - 50849 QuestStart = 50760, @@ -815,41 +749,6 @@ Storage = { }, -- TowerDefenceQuest: when there is space, bring this quest that is in update 8.1 to Tibia Tales }, - TheRookieGuard = { - --Reserved storage 52360 - 52395 - Questline = 52360, - Mission01 = 52361, - Mission02 = 52362, - Mission03 = 52363, - Mission04 = 52364, - Mission05 = 52365, - Mission06 = 52366, - Mission07 = 52367, - Mission08 = 52368, - Mission09 = 52369, - Mission10 = 52370, - Mission11 = 52371, - Mission12 = 52372, - StonePileTimer = 52373, - Catapults = 52374, - RatKills = 52375, - PoacherCorpse = 52376, - LibraryChest = 52377, - TrollChests = 52378, - TunnelPillars = 52379, - Sarcophagus = 52380, - AcademyChest = 52381, - KraknaknorkChests = 52382, - TutorialDelay = 52383, - LibraryDoor = 52384, - UnholyCryptDoor = 52385, - AcademyDoor = 52386, - AcademyChestTimer = 52387, - WarWolfDenChest = 52388, - UnholyCryptChests = 52389, - OrcFortressChests = 52390, - Level8Warning = 52391, - }, BanutaSecretTunnel = { -- Reserved storage from 51680 - 51689 DeeperBanutaShortcut = 51680, @@ -2338,17 +2237,61 @@ Storage = { U9_1 = { --update 9.1 - Reserved Storages 43351 - 43550 AwashWorldChange = {}, DemonWarsWorldChange = {}, - ElementalistOutfits = {}, - HorseStationWorldChange = {}, - InsectoidInvasionWorldChange = {}, + ElementalistOutfits = { + Questline = 43351, + Outfit = 43352, + Addon1 = 43353, + Addon2 = 43354, + }, + HorseStationWorldChange = { + Timer = 43355, + }, LooseEnds = {}, OverhuntingWorldChange = {}, SteamshipWorldChange = {}, SwampFeverWorldChange = {}, TheMagesTowerWorldChange = {}, TheMummysCurseWorldChange = {}, - TheRookieGuard = {}, - TheirMastersVoiceWorldChange = {}, + TheRookieGuard = { + Questline = 43356, + Mission01 = 43357, + Mission02 = 43358, + Mission03 = 43359, + Mission04 = 43360, + Mission05 = 43361, + Mission06 = 43362, + Mission07 = 43363, + Mission08 = 43364, + Mission09 = 43365, + Mission10 = 43366, + Mission11 = 43367, + Mission12 = 43368, + StonePileTimer = 43369, + Catapults = 43370, + RatKills = 43371, + PoacherCorpse = 43372, + LibraryChest = 43373, + TrollChests = 43374, + TunnelPillars = 43375, + Sarcophagus = 43376, + AcademyChest = 43377, + KraknaknorkChests = 43378, + TutorialDelay = 43379, + LibraryDoor = 43380, + UnholyCryptDoor = 43381, + AcademyDoor = 43382, + AcademyChestTimer = 43383, + WarWolfDenChest = 43384, + UnholyCryptChests = 43385, + OrcFortressChests = 43386, + Level8Warning = 43387, + }, + TheirMastersVoiceWorldChange = { + CurrentServantWave = 43388, + ServantsKilled = 43389, + SlimeGobblerTimeout = 43390, + SlimeGobblerReceived = 43391, + }, ThornfireWorldChange = {}, TwistedWatersWorldChange = {}, }, @@ -2361,7 +2304,10 @@ Storage = { HiveBornWorldChange = {}, InsectoidOutfits = {}, KingsdayMiniWorldChange = {}, - LiquidBlack = {}, + LiquidBlackQuest = { + Questline = 50010, + Visitor = 50011, + }, LumberjackMiniWorldChange = {}, NomadsMiniWorldChange = {}, NoodlesIsGoneMiniWorldChange = {}, @@ -2375,16 +2321,69 @@ Storage = { SpringIntoLife = {}, }, U9_60 = { -- update 9.60 - Reserved Storages 43851 - 44000 - BigfootsBurden = {}, - CrystalWarlordOutfits = {}, + BigfootsBurden = { + QuestLine = 43851, + Test = 43852, + Shooting = 43853, + QuestLineComplete = 43854, + MelodyTone1 = 43855, + MelodyTone2 = 43856, + MelodyTone3 = 43857, + MelodyTone4 = 43858, + MelodyTone5 = 43859, + MelodyTone6 = 43860, + MelodyTone7 = 43861, + MelodyStatus = 43862, + Rank = 43863, + MissionCrystalKeeper = 43864, + CrystalKeeperTimout = 43865, + RepairedCrystalCount = 43866, + MissionRaidersOfTheLostSpark = 43867, + ExtractedCount = 43868, + RaidersOfTheLostSparkTimeout = 43869, + MissionExterminators = 43870, + ExterminatedCount = 43871, + ExterminatorsTimeout = 43872, + MissionMushroomDigger = 43873, + MushroomCount = 43874, + MushroomDiggerTimeout = 43875, + MissionMatchmaker = 43876, + MatchmakerStatus = 43877, + MatchmakerIdNeeded = 43878, + MatchmakerTimeout = 43879, + MissionTinkersBell = 43880, + GolemCount = 43881, + TinkerBellTimeout = 43882, + MissionSporeGathering = 43883, + SporeCount = 43884, + SporeGatheringTimeout = 43885, + MissionGrindstoneHunt = 43886, + GrindstoneStatus = 43887, + GrindstoneTimeout = 43888, + WarzoneStatus = 43889, + Warzone1Access = 43890, + Warzone2Access = 43891, + Warzone3Access = 43892, + Warzone1Reward = 43893, + Warzone2Reward = 43894, + Warzone3Reward = 43895, + BossKills = 43896, + DoorGoldenFruits = 43897, + GnomedixMsg = 43898, + Warzones = 43899, + Weeper = 43900, + Versperoth = { + Battle = 43901, + Health = 43902, + }, + Mouthpiece = 43903, + }, DevovorgasEssenceMiniWorldChange = {}, - SoilGuardianOutfits = {}, SpiderNestMiniWorldChange = {}, WarpathMiniWorldChange = {}, }, U9_80 = { -- update 9.80 - Reserved Storages 44001 - 44100 ChildOfDestiny = {}, - DemonOutfits = {}, GoblinMerchant = {}, VenoreDailyTasks = {}, }, @@ -2765,11 +2764,6 @@ GlobalStorage = { WarzoneIV = 60003, }, }, - TheirMastersVoice = { - -- Reserved storage from 60010 - 60019 - CurrentServantWave = 60010, - ServantsKilled = 60011, - }, Feroxa = { -- Reserved storage from 60020 - 60029 Chance = 60020, @@ -2839,16 +2833,6 @@ GlobalStorage = { AstralPowerCounter = 60095, AstralGlyph = 60096, }, - BigfootBurden = { - -- Reserved storage from 60110 - 60119 - Warzones = 60110, - Weeper = 60111, - Versperoth = { - Battle = 60112, - Health = 60113, - }, - Mouthpiece = 60114, - }, TheOrderOfTheLion = { -- Reserved storage from 60170 - 60171 Drume = { diff --git a/data-otservbr-global/npc/appaloosa.lua b/data-otservbr-global/npc/appaloosa.lua index 7031d711207..6d48969d8c4 100644 --- a/data-otservbr-global/npc/appaloosa.lua +++ b/data-otservbr-global/npc/appaloosa.lua @@ -82,7 +82,7 @@ local function creatureSayCallback(npc, creature, type, message) destination:sendMagicEffect(CONST_ME_TELEPORT) npcHandler:say("Have a nice trip!", npc, creature) elseif npcHandler:getTopic(playerId) == 2 then - if player:getStorageValue(Storage.RentedHorseTimer) >= os.time() then + if player:getStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer) >= os.time() then npcHandler:say("You already have a horse.", npc, creature) return true end @@ -94,7 +94,7 @@ local function creatureSayCallback(npc, creature, type, message) local mountId = { 22, 25, 26 } player:addMount(mountId[math.random(#mountId)]) - player:setStorageValue(Storage.RentedHorseTimer, os.time() + 86400) + player:setStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer, os.time() + 86400) player:addAchievement("Natural Born Cowboy") npcHandler:say("I'll give you one of our experienced ones. Take care! Look out for low hanging branches.", npc, creature) end diff --git a/data-otservbr-global/npc/broken_servant_sentry.lua b/data-otservbr-global/npc/broken_servant_sentry.lua index d0d426d951e..317d423e32a 100644 --- a/data-otservbr-global/npc/broken_servant_sentry.lua +++ b/data-otservbr-global/npc/broken_servant_sentry.lua @@ -55,35 +55,40 @@ local function creatureSayCallback(npc, creature, type, message) local playerId = player:getId() if MsgContains(message, "slime") or MsgContains(message, "mould") or MsgContains(message, "fungus") or MsgContains(message, "sample") then - if getPlayerStorageValue(creature, Storage.ElementalistQuest1) < 1 then + if getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Outfit) < 1 then npcHandler:say("If. You. Bring. Slime. Fungus. Samples. Fro-Fro-Fro-Frrrr*chhhhchrk*From. Other. Tower. You. Must. Be. The. Master. Are. You. There. Master?", npc, creature) npcHandler:setTopic(playerId, 1) - elseif getPlayerStorageValue(creature, Storage.ElementalistQuest1) == 1 then + elseif getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Outfit) == 1 then npcHandler:say("If. You. Bring. Slime. Fungus. Samples. Fro-Fro-Fro-Frrrr*chhhhchrk*From. Other. Tower. You. Must. Be. The. Master. Are. You. There. Master?", npc, creature) npcHandler:setTopic(playerId, 3) end elseif MsgContains(message, "cap") or MsgContains(message, "mage") then - if (getPlayerItemCount(creature, 12599) >= 1 and getPlayerStorageValue(creature, Storage.ElementalistQuest1) == 2) and getPlayerStorageValue(creature, Storage.ElementalistQuest2) < 1 then - npcHandler:say("Yo-Yo-Your*chhhrk*. Cap. Is. Slimed. I. Can. Clean. It. *chhhhrrrkchrk* ...", npc, creature) - npcHandler:say("Here. You. Are. *chhhrrrrkchrk*", npc, creature) + if (getPlayerItemCount(creature, 12599) >= 1 and getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Outfit) == 2) and getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Addon1) < 1 then + npcHandler:say({ + "Yo-Yo-Your*chhhrk*. Cap. Is. Slimed. I. Can. Clean. It. *chhhhrrrkchrk* ...", + "Here. You. Are. chhhrrrrkchrk", + }, npc, creature) doPlayerRemoveItem(creature, 12599, 1) - setPlayerStorageValue(creature, Storage.ElementalistQuest2, 1) + setPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Addon1, 1) doPlayerAddOutfit(creature, 432, 1) doPlayerAddOutfit(creature, 433, 1) npcHandler:setTopic(playerId, 0) - elseif getPlayerStorageValue(creature, Storage.ElementalistQuest2) == 1 then + elseif getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Addon1) == 1 then npcHandler:say("You already have this outfit!", npc, creature) npcHandler:setTopic(playerId, 0) end elseif MsgContains(message, "staff") or MsgContains(message, "spike") then - if (getPlayerItemCount(creature, 12803) >= 1 and getPlayerStorageValue(creature, Storage.ElementalistQuest1) == 2) and getPlayerStorageValue(creature, Storage.ElementalistQuest3) < 1 then - npcHandler:say({ "Yo-Yo-Your*chhhrk*. Cap. Is. Slimed. I. Can. Clean. It. *chhhhrrrkchrk* ...", "Here. You. Are. *chhhrrrrkchrk*" }, npc, creature, 4000) + if (getPlayerItemCount(creature, 12803) >= 1 and getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Outfit) == 2) and getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Addon2) < 1 then + npcHandler:say({ + "Yo-Yo-Your*chhhrk*. Cap. Is. Slimed. I. Can. Clean. It. *chhhhrrrkchrk* ...", + "Here. You. Are. *chhhrrrrkchrk*", + }, npc, creature) doPlayerRemoveItem(creature, 12803, 1) - setPlayerStorageValue(creature, Storage.ElementalistQuest3, 1) + setPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Addon2, 1) doPlayerAddOutfit(creature, 432, 2) doPlayerAddOutfit(creature, 433, 2) npcHandler:setTopic(playerId, 0) - elseif getPlayerStorageValue(creature, Storage.ElementalistQuest3) == 1 then + elseif getPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Addon2) == 1 then npcHandler:say("You already have this outfit!", npc, creature) npcHandler:setTopic(playerId, 0) end @@ -93,16 +98,19 @@ local function creatureSayCallback(npc, creature, type, message) npcHandler:setTopic(playerId, 2) elseif npcHandler:getTopic(playerId) == 2 then npcHandler:say("Thank. I. Will. Start. Analysing. No-No-No-No*chhrrrk*Now.", npc, creature) - setPlayerStorageValue(creature, Storage.ElementalistQuest1, 1) - setPlayerStorageValue(creature, Storage.ElementalistOutfitStart, 1) --this for default start of Outfit and Addon Quests + setPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Outfit, 1) + setPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Questline, 1) --this for default start of Outfit and Addon Quests npcHandler:setTopic(playerId, 0) elseif npcHandler:getTopic(playerId) == 3 then npcHandler:say("I. Greet. You. Ma-Ma-Ma-ster! Did. You. Bring. Mo-Mo-Mo-M*chhhhrrrk*ore. Samples. For. Me. To-To-To. Analyse-lyse-lyse?", npc, creature) npcHandler:setTopic(playerId, 4) elseif (npcHandler:getTopic(playerId) == 4) and getPlayerItemCount(creature, 12601) >= 20 then - npcHandler:say({ "Please. Wait. I. Can. Not. Han-Han-Han*chhhhhrrrchrk*Handle. *chhhhrchrk* This. Is. Enough. Material. *chrrrchhrk* ...", "I. Have-ve-ve-veee*chrrrck*. Also. Cleaned. Your. Clothes. Master. It. Is. No-No-No*chhrrrrk*Now. Free. Of. Sample. Stains." }, npc, creature, 4000) + npcHandler:say({ + "Please. Wait. I. Can. Not. Han-Han-Han*chhhhhrrrchrk*Handle. *chhhhrchrk* This. Is. Enough. Material. *chrrrchhrk* ...", + "I. Have-ve-ve-veee*chrrrck*. Also. Cleaned. Your. Clothes. Master. It. Is. No-No-No*chhrrrrk*Now. Free. Of. Sample. Stains.", + }, npc, creature) doPlayerRemoveItem(creature, 12601, 20) - setPlayerStorageValue(creature, Storage.ElementalistQuest1, 2) + setPlayerStorageValue(creature, Storage.Quest.U9_1.ElementalistOutfits.Outfit, 2) doPlayerAddOutfit(creature, 432, 0) doPlayerAddOutfit(creature, 433, 0) npcHandler:setTopic(playerId, 0) @@ -114,6 +122,8 @@ local function creatureSayCallback(npc, creature, type, message) return true end +npcHandler:setMessage(MESSAGE_GREET, "The Master is-is-is-is de-ad. Plea*chrrrrchk*se. Be. In. Mourning.") + npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) diff --git a/data-otservbr-global/npc/commander_stone.lua b/data-otservbr-global/npc/commander_stone.lua index 07240ff0fab..f4f03336b8f 100644 --- a/data-otservbr-global/npc/commander_stone.lua +++ b/data-otservbr-global/npc/commander_stone.lua @@ -58,34 +58,34 @@ local function creatureSayCallback(npc, creature, type, message) return false end - if MsgContains(message, "mission") and player:getStorageValue(Storage.BigfootBurden.QuestLineComplete) >= 2 then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 30 then + if MsgContains(message, "mission") and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLineComplete) >= 2 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 30 then npcHandler:say({ "Two missions are available for your {rank}: crystal {keeper} and {spark} hunting. You can undertake each mission but you can turn in a specific mission only once each 20 hours. ...", "If you lose a mission item you can probably buy it from Gnomally." }, npc, creature) npcHandler:setTopic(playerId, 0) - elseif player:getStorageValue(Storage.BigfootBurden.Rank) >= 30 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 30 then npcHandler:say({ "For your {rank} there are four missions avaliable: crystal {keeper}, {spark} hunting, monster {extermination} and mushroom {digging}. By the way, you {rank} now allows you to take aditional missions from {Gnomeral} in {Gnomebase Alpha}. ... ", "If you lose a mission item you can probably buy it from Gnomally." }, npc, creature) npcHandler:setTopic(playerId, 0) end -- Crystal Kepper elseif MsgContains(message, "keeper") then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 30 then - if player:getStorageValue(Storage.BigfootBurden.MissionCrystalKeeper) < 1 and player:getStorageValue(Storage.BigfootBurden.CrystalKeeperTimout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 30 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionCrystalKeeper) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.CrystalKeeperTimout) < os.time() then npcHandler:say("You will have to repair some damaged crystals. Go into the Crystal grounds and repair them, using this harmonic crystal. Repair five of them and return to me. ", npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionCrystalKeeper, 1) - player:setStorageValue(Storage.BigfootBurden.RepairedCrystalCount, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionCrystalKeeper, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount, 0) player:addItem(15703, 1) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.CrystalKeeperTimout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.CrystalKeeperTimout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionCrystalKeeper) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.RepairedCrystalCount) >= 5 then -- can report missions + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionCrystalKeeper) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount) >= 5 then -- can report missions player:removeItem(15703, 1) - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 5) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 5) player:addItem(16128, 1) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionCrystalKeeper, 0) - player:setStorageValue(Storage.BigfootBurden.CrystalKeeperTimout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) - player:setStorageValue(Storage.BigfootBurden.RepairedCrystalCount, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionCrystalKeeper, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.CrystalKeeperTimout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount, -1) player:addAchievement("Crystal Keeper") player:checkGnomeRank() npcHandler:say("You did well. That will help us a lot. Take your {token} and this gnomish supply package as a reward. ", npc, creature) @@ -106,23 +106,23 @@ local function creatureSayCallback(npc, creature, type, message) -- Raiders of the Lost Spark elseif MsgContains(message, "spark") then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 30 then - if player:getStorageValue(Storage.BigfootBurden.MissionRaidersOfTheLostSpark) < 1 and player:getStorageValue(Storage.BigfootBurden.RaidersOfTheLostSparkTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 30 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionRaidersOfTheLostSpark) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.RaidersOfTheLostSparkTimeout) < os.time() then npcHandler:say({ "Take this extractor and drive it into a body of a slain crystal crusher. This will charge your own body with energy sparks. Charge it with seven sparks and return to me. ...", "Don't worry. The gnomes assured me you'd be save. That is if nothing strange or unusual occurs! " }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionRaidersOfTheLostSpark, 1) - player:setStorageValue(Storage.BigfootBurden.ExtractedCount, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionRaidersOfTheLostSpark, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExtractedCount, 0) player:addItem(15696, 1) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.RaidersOfTheLostSparkTimeout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.RaidersOfTheLostSparkTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionRaidersOfTheLostSpark) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.ExtractedCount) >= 7 then -- can report missions - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 5) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionRaidersOfTheLostSpark) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExtractedCount) >= 7 then -- can report missions + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 5) player:removeItem(15696, 1) player:addItem(16128, 1) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionRaidersOfTheLostSpark, 0) - player:setStorageValue(Storage.BigfootBurden.ExtractedCount, -1) - player:setStorageValue(Storage.BigfootBurden.RaidersOfTheLostSparkTimeout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionRaidersOfTheLostSpark, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExtractedCount, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.RaidersOfTheLostSparkTimeout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) player:addAchievement("Call Me Sparky") player:checkGnomeRank() npcHandler:say("You did well. That will help us a lot. Take your {token} and this gnomish supply package as a reward. ", npc, creature) @@ -143,21 +143,21 @@ local function creatureSayCallback(npc, creature, type, message) -- Exterminators elseif MsgContains(message, "extermination") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 30 then - if player:getStorageValue(Storage.BigfootBurden.MissionExterminators) < 1 and player:getStorageValue(Storage.BigfootBurden.ExterminatorsTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 30 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionExterminators) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatorsTimeout) < os.time() then npcHandler:say("The wigglers have become a pest that threaten our resources and supplies. Kill 10 wigglers in the caves like the mushroon gardens or the truffles ground. {Report} back to me when you are done. ", npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionExterminators, 1) - player:setStorageValue(Storage.BigfootBurden.ExterminatedCount, 0) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.ExterminatorsTimeout) > os.time() then -- trying to take mission while in cooldown + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionExterminators, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount, 0) --- taking missions + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatorsTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionExterminators) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.ExterminatedCount) >= 10 then -- can report missions - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 5) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionExterminators) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount) >= 10 then -- can report missions + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 5) player:addItem(16128, 1) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionExterminators, 0) - player:setStorageValue(Storage.BigfootBurden.ExterminatedCount, -1) - player:setStorageValue(Storage.BigfootBurden.ExterminatorsTimeout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionExterminators, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatorsTimeout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) player:addAchievement("One Foot Vs. Many") player:checkGnomeRank() npcHandler:say("You did well. That will help us a lot. Take your {token} and this gnomish supply package as a reward. ", npc, creature) @@ -178,28 +178,28 @@ local function creatureSayCallback(npc, creature, type, message) -- Mushroom Digger elseif MsgContains(message, "digging") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 30 then - if player:getStorageValue(Storage.BigfootBurden.MissionMushroomDigger) < 1 and player:getStorageValue(Storage.BigfootBurden.MushroomDiggerTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 30 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMushroomDigger) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomDiggerTimeout) < os.time() then npcHandler:say({ "Take this little piggy here. It will one day become a great mushroom hunter for sure. For now it is depended on you and other pigs. ...", "Well, other pigs like it is one, I mean. I was of course not comparing you with a pig! Go to the truffles area and follow the truffle pigs there. If they dig up some truffles, let the little pig eat the mushrooms. ...", "You'll have to feed it three times. Then return it to me. ...", "Keep in mind that the pig has to be returned to his mother after a while. If you don't do this, the gnomes will call it back via teleport crystals.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionMushroomDigger, 1) - player:setStorageValue(Storage.BigfootBurden.MushroomCount, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMushroomDigger, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomCount, 0) player:addItem(15828, 1) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.MushroomDiggerTimeout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomDiggerTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionMushroomDigger) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.MushroomCount) >= 3 then -- can report missions + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMushroomDigger) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomCount) >= 3 then -- can report missions player:removeItem(15828, 1) - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 5) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 5) player:addItem(16128, 1) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionMushroomDigger, 0) - player:setStorageValue(Storage.BigfootBurden.MushroomCount, -1) - player:setStorageValue(Storage.BigfootBurden.MushroomDiggerTimeout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMushroomDigger, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomCount, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomDiggerTimeout, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) player:addAchievement("The Picky Pig") player:checkGnomeRank() npcHandler:say("You did well. That will help us a lot. Take your {token} and this gnomish supply package as a reward. ", npc, creature) @@ -218,10 +218,10 @@ local function creatureSayCallback(npc, creature, type, message) end -- Mushroom Digger elseif MsgContains(message, "report") then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 30 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 30 then npcHandler:say("Which mission do you want to report: crystal {keeper}, {spark} hunting?", npc, creature) npcHandler:setTopic(playerId, 1) - elseif player:getStorageValue(Storage.BigfootBurden.Rank) >= 30 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 30 then npcHandler:say("Which mission do you want to report: crystal {keeper}, {spark} hunting, monster {extermination} or mushroom {digging}?", npc, creature) npcHandler:setTopic(playerId, 2) end diff --git a/data-otservbr-global/npc/doctor_gnomedix.lua b/data-otservbr-global/npc/doctor_gnomedix.lua index aaef536ddcd..aafe3d8b5d1 100644 --- a/data-otservbr-global/npc/doctor_gnomedix.lua +++ b/data-otservbr-global/npc/doctor_gnomedix.lua @@ -38,9 +38,9 @@ npcType.onSay = function(npc, creature, type, message) local player = Player(creature) local playerId = player:getId() - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then npcHandler:setMessage(MESSAGE_GREET, "Stand still on the examination platform |PLAYERNAME|.") - player:setStorageValue(Storage.BigfootBurden.QuestLine, 6) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 6) end npcHandler:onSay(npc, creature, type, message) end diff --git a/data-otservbr-global/npc/gnomally.lua b/data-otservbr-global/npc/gnomally.lua index 47b965fe0b0..6fca7dc4ff4 100644 --- a/data-otservbr-global/npc/gnomally.lua +++ b/data-otservbr-global/npc/gnomally.lua @@ -125,9 +125,9 @@ local function creatureSayCallback(npc, creature, type, message) topic[playerId] = message elseif MsgContains(message, "relations") then local player = Player(creature) - if player:getStorageValue(Storage.BigfootBurden.QuestLine) >= 25 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) >= 25 then npcHandler:say("Our relations improve with every mission you undertake on our behalf. Another way to improve your relations with us gnomes is to trade in minor crystal tokens. ...", npc, creature) - npcHandler:say("Your renown amongst us gnomes is currently {" .. math.max(0, player:getStorageValue(Storage.BigfootBurden.Rank)) .. "}. Do you want to improve your standing by sacrificing tokens? One token will raise your renown by 5 points. ", npc, creature) + npcHandler:say("Your renown amongst us gnomes is currently {" .. math.max(0, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank)) .. "}. Do you want to improve your standing by sacrificing tokens? One token will raise your renown by 5 points. ", npc, creature) npcHandler:setTopic(playerId, 2) else npcHandler:say("You are not even a recruit of the Bigfoots. Sorry I can't help you.", npc, creature) @@ -174,9 +174,9 @@ local function creatureSayCallback(npc, creature, type, message) elseif npcHandler:getTopic(playerId) == 4 then local player = Player(creature) if player:removeItem(16128, renown[playerId]) then - player:setStorageValue(Storage.BigfootBurden.Rank, math.max(0, player:getStorageValue(Storage.BigfootBurden.Rank)) + renown[playerId] * 5) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, math.max(0, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank)) + renown[playerId] * 5) player:checkGnomeRank() - npcHandler:say("As you wish! Your new renown is {" .. player:getStorageValue(Storage.BigfootBurden.Rank) .. "}.", npc, creature) + npcHandler:say("As you wish! Your new renown is {" .. player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) .. "}.", npc, creature) else npcHandler:say("You don't have these many tokens.", npc, creature) end diff --git a/data-otservbr-global/npc/gnomaticus.lua b/data-otservbr-global/npc/gnomaticus.lua index 501dc49e5a2..cc360074b0d 100644 --- a/data-otservbr-global/npc/gnomaticus.lua +++ b/data-otservbr-global/npc/gnomaticus.lua @@ -59,24 +59,24 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "shooting") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 11 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 11 then npcHandler:say({ "To the left you see our shooting range. Grab a cannon and shoot at the targets. You need five hits to succeed. ...", "Shoot at the villain targets that will pop up. DON'T shoot innocent civilians since this will reset your score and you have to start all over. Report to me afterwards.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 13) - player:setStorageValue(Storage.BigfootBurden.Shooting, 0) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 13 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 13) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting, 0) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 13 then npcHandler:say("Shoot at the villain targets that will pop up. DON'T shoot innocent civilians since this will reset your score and you have to start all over. {Report} to me afterwards.", npc, creature) end elseif MsgContains(message, "report") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 14 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 14 then npcHandler:say("You are showing some promise! Now continue with the recruitment and talk to Gnomewart to the south for your endurance test!", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Shooting, player:getStorageValue(Storage.BigfootBurden.Shooting) + 1) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 15) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 13 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting) + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 15) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 13 then npcHandler:say("Sorry you are not done yet.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) <= 12 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) <= 12 then npcHandler:say("You have nothing to report at all.", npc, creature) end end diff --git a/data-otservbr-global/npc/gnomelvis.lua b/data-otservbr-global/npc/gnomelvis.lua index 7385fdb5a1b..6b8525dddfe 100644 --- a/data-otservbr-global/npc/gnomelvis.lua +++ b/data-otservbr-global/npc/gnomelvis.lua @@ -59,36 +59,36 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "looking") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) >= 19 or player:getStorageValue(Storage.BigfootBurden.QuestLine) <= 22 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) >= 19 or player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) <= 22 then npcHandler:say("I'm the gnomish {musical} supervisor!", npc, creature) end elseif MsgContains(message, "musical") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 19 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 19 then npcHandler:say({ "Ah well. Everyone has a very personal melody in his soul. Only if you know your soul melody then you know yourself. And only if you know yourself will you be admitted to the Bigfoot company. ...", "So what you have to do is to find your soul melody. Do you see the huge crystals in this room? Those are harmonic crystals. Use them to deduce your soul melody. Simply use them to create a sound sequence. ...", "Every soul melody consists of seven sound sequences. You will have to figure out your correct soul melody by trial and error. If you hit a wrong note, you will have to start over.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 21) - player:setStorageValue(Storage.BigfootBurden.MelodyStatus, 1) - if player:getStorageValue(Storage.BigfootBurden.MelodyTone1) < 1 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 21) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MelodyStatus, 1) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MelodyTone1) < 1 then for i = 0, 6 do - player:setStorageValue(Storage.BigfootBurden.MelodyTone1 + i, math.random(1, 4)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MelodyTone1 + i, math.random(1, 4)) end end - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 21 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 21 then npcHandler:say("What you have to do is to find your soul melody. Use the harmonic crystals to deduce your soul melody. Every soul melody consists of seven sound sequences. ...", npc, creature) npcHandler:say("You will have to figure out your correct soul melody by trial and error.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 22 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 22 then npcHandler:say({ "Congratulations on finding your soul melody. And a pretty one as far as I can tell. Now you are a true recruit of the Bigfoot company! Commander Stone might have some tasks for you to do! ...", "Look for him in the central chamber. I marked your map where you will find him.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 25) - player:setStorageValue(Storage.BigfootBurden.QuestLineComplete, 2) - player:setStorageValue(Storage.BigfootBurden.Rank) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 25) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLineComplete, 2) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) player:addAchievement("Becoming a Bigfoot") - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 25 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 25 then npcHandler:say("Congratulations on finding your soul melody.", npc, creature) end end diff --git a/data-otservbr-global/npc/gnomeral.lua b/data-otservbr-global/npc/gnomeral.lua index 250681ffa97..c36d1d51cdf 100644 --- a/data-otservbr-global/npc/gnomeral.lua +++ b/data-otservbr-global/npc/gnomeral.lua @@ -62,39 +62,39 @@ local function creatureSayCallback(npc, creature, type, message) return false end - if MsgContains(message, "mission") and player:getStorageValue(Storage.BigfootBurden.QuestLineComplete) >= 2 then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 120 and player:getStorageValue(Storage.BigfootBurden.Rank) < 480 then + if MsgContains(message, "mission") and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLineComplete) >= 2 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 120 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 480 then npcHandler:say("For your rank there are two missions available: {matchmaker} and golem {repair}. You can undertake each mission, but you can turn in a specific mission only once every 20 hours. ", npc, creature) npcHandler:setTopic(playerId, 0) - elseif player:getStorageValue(Storage.BigfootBurden.Rank) >= 480 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 480 then npcHandler:say("For your rank there are four missions available: {matchmaker}, golem {repair}, {spore} gathering and {grindstone} hunt. You can undertake each mission, but you can turn in a specific mission only once every 20 hours.", npc, creature) npcHandler:setTopic(playerId, 0) end -- Matchmaker elseif MsgContains(message, "matchmaker") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 120 then - if player:getStorageValue(Storage.BigfootBurden.MissionMatchmaker) < 1 and player:getStorageValue(Storage.BigfootBurden.MatchmakerTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 120 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMatchmaker) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerTimeout) < os.time() then npcHandler:say({ "You will have to find a lonely crystal a perfect match. I don't understand the specifics but the gnomes told me that even crystals need a mate to produce offspring. ...", "Be that as it may, in this package you'll find a crystal. Take it out of the package and go to the crystal caves to find it a mate. Just look out for huge red crystals and try your luck. ...", "They should look like one of those seen in your soul melody test. You will find them in the crystal grounds. {Report} back to me when you are done.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionMatchmaker, 1) - player:setStorageValue(Storage.BigfootBurden.MatchmakerStatus, 0) - player:setStorageValue(Storage.BigfootBurden.MatchmakerIdNeeded, math.random(15809, 15815)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMatchmaker, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerStatus, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerIdNeeded, math.random(15809, 15815)) player:addItem(15802, 1) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.MatchmakerTimeout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionMatchmaker) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.MatchmakerStatus) == 1 then -- can report missions - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 10) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMatchmaker) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerStatus) == 1 then -- can report missions + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 10) player:addItem(16128, 2) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionMatchmaker, 0) - player:setStorageValue(Storage.BigfootBurden.MatchmakerStatus, -1) - player:setStorageValue(Storage.BigfootBurden.MatchmakerIdNeeded, -1) - player:setStorageValue(Storage.BigfootBurden.MatchmakerTimeout, os.time() + 72000) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMatchmaker, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerStatus, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerIdNeeded, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerTimeout, os.time() + 72000) player:addAchievement("Crystals in Love") player:checkGnomeRank() npcHandler:say("Gnomo arigato |PLAYERNAME|! You did well. That will help us a lot. Take your tokens and this gnomish supply package as a reward. ", npc, creature) @@ -115,28 +115,28 @@ local function creatureSayCallback(npc, creature, type, message) -- Golem Repair elseif MsgContains(message, "repair") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 120 then - if player:getStorageValue(Storage.BigfootBurden.MissionTinkersBell) < 1 and player:getStorageValue(Storage.BigfootBurden.TinkerBellTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 120 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionTinkersBell) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.TinkerBellTimeout) < os.time() then npcHandler:say("Our gnomish crystal golems sometimes go nuts. A recent earthquake has disrupted the entire production of a golem factory. ... ", npc, creature) npcHandler:say({ "I'm no expert on how those golems work, but it seems that when the crystals of the golems get out of harmony, they do as they please and even sometimes become violent. The violent ones are lost. ...", "Don't bother with them, though you may decide to kill some to get rid of them. The others can be repaired, but to recall them to the workshops, the golems have to be put into a specific resonance. ...", "Use the bell I gave you on the golems, so the gnomes can recall them to their workshops. Getting four of them should be enough for now. Report back when you are ready.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionTinkersBell, 1) - player:setStorageValue(Storage.BigfootBurden.GolemCount, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionTinkersBell, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount, 0) player:addItem(15832, 1) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.TinkerBellTimeout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.TinkerBellTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionTinkersBell) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.GolemCount) >= 4 then -- can report missions + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionTinkersBell) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount) >= 4 then -- can report missions player:removeItem(15832, 1) - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 10) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 5) player:addItem(16128, 2) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionTinkersBell, 0) - player:setStorageValue(Storage.BigfootBurden.GolemCount, -1) - player:setStorageValue(Storage.BigfootBurden.TinkerBellTimeout, os.time() + 72000) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionTinkersBell, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.TinkerBellTimeout, os.time() + 72000) player:addAchievement("Substitute Tinker") player:checkGnomeRank() npcHandler:say("Gnomo arigato |PLAYERNAME|! You did well. That will help us a lot. Take your tokens and this gnomish supply package as a reward. ", npc, creature) @@ -157,28 +157,28 @@ local function creatureSayCallback(npc, creature, type, message) -- Spore Gathering elseif MsgContains(message, "spore") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 480 then - if player:getStorageValue(Storage.BigfootBurden.MissionSporeGathering) < 1 and player:getStorageValue(Storage.BigfootBurden.SporeGatheringTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 480 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionSporeGathering) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeGatheringTimeout) < os.time() then npcHandler:say({ "We gnomes want you to gather a special collection of spores. All you have to do is use a puffball mushroom and use the spore gathering kit I gave you to gather the spores. ...", "There is a catch though. You need to collect different spores in a specific sequence to fill your gathering kit. If you mix the spores in the wrong way, you ruin your collection and have to start over. ...", "You have to gather them in this sequence: red, green, blue and yellow. You can see on your kit what is required next.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionSporeGathering, 1) - player:setStorageValue(Storage.BigfootBurden.SporeCount, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionSporeGathering, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeCount, 0) player:addItem(15817, 1) npcHandler:setTopic(playerId, 0) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.SporeGatheringTimeout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeGatheringTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionSporeGathering) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.SporeCount) == 4 then -- can report missions + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionSporeGathering) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeCount) == 4 then -- can report missions player:removeItem(15821, 1) - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 10) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 10) player:addItem(16128, 2) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionSporeGathering, 0) - player:setStorageValue(Storage.BigfootBurden.SporeCount, -1) - player:setStorageValue(Storage.BigfootBurden.SporeGatheringTimeout, os.time() + 72000) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionSporeGathering, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeCount, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeGatheringTimeout, os.time() + 72000) player:addAchievement("Spore Hunter") player:checkGnomeRank() npcHandler:say("Gnomo arigato |PLAYERNAME|! You did well. That will help us a lot. Take your tokens and this gnomish supply package as a reward. ", npc, creature) @@ -199,26 +199,26 @@ local function creatureSayCallback(npc, creature, type, message) -- Grindstone Hunt elseif MsgContains(message, "grindstone") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 480 then - if player:getStorageValue(Storage.BigfootBurden.MissionGrindstoneHunt) < 1 and player:getStorageValue(Storage.BigfootBurden.GrindstoneTimeout) < os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 480 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionGrindstoneHunt) < 1 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneTimeout) < os.time() then npcHandler:say({ "We gnomes need some special grindstones to cut and polish specific crystals. The thing is, they can only be found in a quite dangerous lava cave full of vile monsters. You'll reach it via the hot spot teleporter. ...", "It will be your task to get one such grindstone and bring it back to me.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.MissionGrindstoneHunt, 1) - player:setStorageValue(Storage.BigfootBurden.GrindstoneStatus, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionGrindstoneHunt, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneStatus, 0) npcHandler:setTopic(playerId, 0) --- taking missions - elseif player:getStorageValue(Storage.BigfootBurden.GrindstoneTimeout) > os.time() then -- trying to take mission while in cooldown + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneTimeout) > os.time() then -- trying to take mission while in cooldown npcHandler:say("Sorry, you will have to wait before you can undertake this mission again.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.MissionGrindstoneHunt) > 0 then -- reporting mission - if player:getStorageValue(Storage.BigfootBurden.GrindstoneStatus) == 1 then -- can report missions + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionGrindstoneHunt) > 0 then -- reporting mission + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneStatus) == 1 then -- can report missions player:removeItem(15826, 1) - player:setStorageValue(Storage.BigfootBurden.Rank, player:getStorageValue(Storage.BigfootBurden.Rank) + 10) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + 10) player:addItem(16128, 2) player:addItem(15698, 1) - player:setStorageValue(Storage.BigfootBurden.MissionGrindstoneHunt, 0) - player:setStorageValue(Storage.BigfootBurden.GrindstoneStatus, -1) - player:setStorageValue(Storage.BigfootBurden.GrindstoneTimeout, os.time() + 72000) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionGrindstoneHunt, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneStatus, -1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneTimeout, os.time() + 72000) player:addAchievement("Grinding Again") player:checkGnomeRank() npcHandler:say("Gnomo arigato |PLAYERNAME|! You did well. That will help us a lot. Take your tokens and this gnomish supply package as a reward. ", npc, creature) @@ -237,10 +237,10 @@ local function creatureSayCallback(npc, creature, type, message) end -- Grindstone Hunt elseif MsgContains(message, "report") then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 120 and player:getStorageValue(Storage.BigfootBurden.Rank) < 480 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 120 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 480 then npcHandler:say("Which mission do you want to report: {matchmaker}, golem {repair}?", npc, creature) npcHandler:setTopic(playerId, 1) - elseif player:getStorageValue(Storage.BigfootBurden.Rank) >= 480 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 480 then npcHandler:say("Which mission do you want to report: {matchmaker}, golem {repair}, {spore} gathering or {grindstone} hunt?", npc, creature) npcHandler:setTopic(playerId, 2) end diff --git a/data-otservbr-global/npc/gnomerik.lua b/data-otservbr-global/npc/gnomerik.lua index f4994d2e585..25a83175c15 100644 --- a/data-otservbr-global/npc/gnomerik.lua +++ b/data-otservbr-global/npc/gnomerik.lua @@ -51,8 +51,8 @@ npcType.onCloseChannel = function(npc, creature) end keywordHandler:addGreetKeyword({ "hi" }, { npcHandler = npcHandler, text = "Hello and welcome in the gnomish {recruitment} office." }, function(player) - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 1 then - player:setStorageValue(Storage.BigfootBurden.QuestLine, 3) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 1 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 3) end end) keywordHandler:addAliasKeyword({ "hello" }) @@ -65,7 +65,7 @@ local function creatureSayCallback(npc, creature, type, message) return false end - if player:getStorageValue(Storage.BigfootBurden.NeedsBeer) == 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.NeedsBeer) == 1 then if MsgContains(message, "recruit") or MsgContains(message, "test") or MsgContains(message, "result") then npcHandler:say({ "I suggest you relax a bit with a fresh mushroom beer and we can talk after that. ...", "Gnominus... He is the one you need right now, find him." }, npc, creature) end @@ -73,18 +73,18 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "recruit") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then npcHandler:say("Yes... Yes... . We already talked about that. I can't remember if you have already tried the {test}, so lets get going.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 3 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 3 then npcHandler:say("We are hiring people to fight in our so called Bigfoot company against the foes of gnomekind. Are you interested in joining?", npc, creature) npcHandler:setTopic(playerId, 1) end -- TEST elseif MsgContains(message, "test") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then if npcHandler:getTopic(playerId) < 1 then - player:setStorageValue(Storage.BigfootBurden.Test, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, 0) npcHandler:say({ "Imagine, during your travels you come upon a rare and unknown mushroom. Would you {A}) note down its specifics and location and look for a gnome to take care of it. ...", "Or would you {B}) smash it to an unrecognisable pulp. Or would you {C}) pluck it to take it with you for further examination. Or would you {D}) try to become friends with the mushroom by singing questionable bar-room songs?", @@ -163,27 +163,27 @@ local function creatureSayCallback(npc, creature, type, message) end -- ANSWERS elseif message:lower() == "a" then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then if (npcHandler:getTopic(playerId) % 2) == 0 then if npcHandler:getTopic(playerId) == 2 then npcHandler:say("Indeed an excellent and smart decision for an ungnomish lifeform. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 18 then npcHandler:say("A well thought out answer I have to admit. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 20 then npcHandler:say("Ah, we have a true warrior here I guess. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 28 then npcHandler:say("Fear not. We don't expect too much of you anyway. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 30 then npcHandler:say("Ha! A Krazzelzak would for sure fit someone like you! But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) else if npcHandler:getTopic(playerId) < 33 then @@ -197,15 +197,15 @@ local function creatureSayCallback(npc, creature, type, message) end end elseif message:lower() == "b" then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then if (npcHandler:getTopic(playerId) % 2) == 0 then if npcHandler:getTopic(playerId) == 6 then npcHandler:say("Although chances are the gnome will end up rescuing you instead, it is the attempt that counts. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 14 then npcHandler:say("I knew this question was too easy. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) else if npcHandler:getTopic(playerId) < 33 then @@ -219,19 +219,19 @@ local function creatureSayCallback(npc, creature, type, message) end end elseif message:lower() == "c" then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then if (npcHandler:getTopic(playerId) % 2) == 0 then if npcHandler:getTopic(playerId) == 4 then npcHandler:say("That's the spirit! Initiative is always a good thing. Well most of the time. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 22 then npcHandler:say("You have no idea how many answer this question wrong. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 24 then npcHandler:say("That's the spirit! But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) else if npcHandler:getTopic(playerId) < 33 then @@ -245,31 +245,31 @@ local function creatureSayCallback(npc, creature, type, message) end end elseif message:lower() == "d" then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 5 then if (npcHandler:getTopic(playerId) % 2) == 0 then if npcHandler:getTopic(playerId) == 8 then npcHandler:say("Of COURSE you wouldn't! NO ONE would! But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 10 then npcHandler:say("I can only hope that is your honest opinion. But let us continue with the test.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 12 then npcHandler:say("Oh, you silver tongued devil almost made me blush. But of course you're right. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 16 then npcHandler:say("How true. How true. *sigh* But fear not! We gnomes are here to help! But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 26 then npcHandler:say("That's just what I'd do - if I weren't a gnome already, that is. But let us continue with the {test}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) elseif npcHandler:getTopic(playerId) == 32 then npcHandler:say("Excellent! Well this concludes the test. Now let us see your {results}.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.Test, player:getStorageValue(Storage.BigfootBurden.Test) + 7) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) + 7) npcHandler:setTopic(playerId, npcHandler:getTopic(playerId) + 1) else if npcHandler:getTopic(playerId) < 33 then @@ -285,21 +285,21 @@ local function creatureSayCallback(npc, creature, type, message) -- TEST elseif MsgContains(message, "result") then if npcHandler:getTopic(playerId) == 33 then - if player:getStorageValue(Storage.BigfootBurden.Test) < 100 then - player:setStorageValue(Storage.BigfootBurden.NeedsBeer, 1) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) < 100 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.NeedsBeer, 1) npcHandler:say({ - "You have failed the test with " .. player:getStorageValue(Storage.BigfootBurden.Test) .. " of 112 possible points. You probably were just too nervous. ...", + "You have failed the test with " .. player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) .. " of 112 possible points. You probably were just too nervous. ...", "I suggest you relax a bit with a fresh mushroom beer and we'll start over after that. Gnominus sells some beer. You should find him somewhere in the central chamber.", }, npc, creature) else - npcHandler:say("You have passed the test with " .. player:getStorageValue(Storage.BigfootBurden.Test) .. " of 112 possible points. Congratulations. You are ready to proceed with the more physical parts of your examination! Go and talk to Gnomespector about it.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 6) + npcHandler:say("You have passed the test with " .. player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Test) .. " of 112 possible points. Congratulations. You are ready to proceed with the more physical parts of your examination! Go and talk to Gnomespector about it.", npc, creature) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 6) end end elseif MsgContains(message, "yes") then if npcHandler:getTopic(playerId) == 1 then npcHandler:say("Excellent! Now let us begin with the gnomish aptitude test. Just tell me when you feel ready for the {test}!", npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 5) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 5) npcHandler:setTopic(playerId, 0) end end diff --git a/data-otservbr-global/npc/gnomespector.lua b/data-otservbr-global/npc/gnomespector.lua index 7d5fb54cea9..475552e76cf 100644 --- a/data-otservbr-global/npc/gnomespector.lua +++ b/data-otservbr-global/npc/gnomespector.lua @@ -63,12 +63,12 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "recruit") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 6 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 6 then npcHandler:say({ "Your examination is quite easy. Just step through the green crystal {apparatus} in the south! We will examine you with what we call g-rays. Where g stands for gnome of course ...", "Afterwards walk up to Gnomedix for your ear examination.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 8) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 8) npcHandler:setTopic(playerId, 1) end elseif MsgContains(message, "apparatus") and npcHandler:getTopic(playerId) == 1 then diff --git a/data-otservbr-global/npc/gnomewart.lua b/data-otservbr-global/npc/gnomewart.lua index 1e50d429fcf..c9b7acdadb1 100644 --- a/data-otservbr-global/npc/gnomewart.lua +++ b/data-otservbr-global/npc/gnomewart.lua @@ -59,21 +59,21 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "endurance") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 15 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 15 then npcHandler:say({ "Ah, the test is a piece of mushroomcake! Just take the teleporter over there in the south and follow the hallway. ...", "You'll need to run quite a bit. It is important that you don't give up! Just keep running and running and running and ... I guess you got the idea. ...", "At the end of the hallway you'll find a teleporter. Step on it and you are done! I'm sure you'll do a true gnomerun! Afterwards talk to me.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 17) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 17 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 17) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 17 then npcHandler:say("Just take the teleporter over there to the south and follow the hallway. At the end of the hallway you'll find a teleporter. Step on it and you are done!", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) == 18 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 18 then npcHandler:say("You have passed the test and are ready to create your soul melody. Talk to Gnomelvis in the east about it.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 19) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) < 15 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 19) + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) < 15 then npcHandler:say("Your endurance will be tested here when the time comes. For the moment please continue with the other phases of your recruitment.", npc, creature) - elseif player:getStorageValue(Storage.BigfootBurden.QuestLine) >= 19 then + elseif player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) >= 19 then npcHandler:say("You have passed the test. If you consider what huge feet you have to move it's quite impressive.", npc, creature) end end diff --git a/data-otservbr-global/npc/gnominus.lua b/data-otservbr-global/npc/gnominus.lua index 8e69e64fb53..7d03924d554 100644 --- a/data-otservbr-global/npc/gnominus.lua +++ b/data-otservbr-global/npc/gnominus.lua @@ -60,7 +60,7 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "recruitment") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 3 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 3 then npcHandler:say("Your examination is quite easy. Just step through the green crystal apparatus in the south! We will examine you with what we call g-rays. Where g stands for gnome of course ...", npc, creature) npcHandler:say("Afterwards walk up to Gnomedix for your ear examination.", npc, creature) npcHandler:setTopic(playerId, 1) @@ -73,7 +73,7 @@ local function creatureSayCallback(npc, creature, type, message) elseif npcHandler:getTopic(playerId) == 1 then if MsgContains(message, "apparatus") then npcHandler:say("Don't be afraid. It won't hurt! Just step in!", npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 4) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 4) npcHandler:setTopic(playerId, 0) end elseif npcHandler:getTopic(playerId) == 2 then diff --git a/data-otservbr-global/npc/gnomission.lua b/data-otservbr-global/npc/gnomission.lua index 3a35ecc117d..cff1d1b4893 100644 --- a/data-otservbr-global/npc/gnomission.lua +++ b/data-otservbr-global/npc/gnomission.lua @@ -79,12 +79,12 @@ local function creatureSayCallback(npc, creature, type, message) end elseif MsgContains(message, "snippet") then if npcHandler:getTopic(playerId) == 3 then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 1440 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 1440 then npcHandler:say("It seems you did not even set one big foot into the warzone, I am sorry.") else - if player:getStorageValue(Storage.BigfootBurden.Warzone1Access) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Warzone1Access) < 1 then if player:removeItem(16136, 1) then - player:setStorageValue(Storage.BigfootBurden.Warzone1Access, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Warzone1Access, 1) npcHandler:say("As a war hero you are allowed to use the warzone teleporter one for free!", npc, creature) npcHandler:setTopic(playerId, 0) else @@ -97,13 +97,13 @@ local function creatureSayCallback(npc, creature, type, message) end elseif MsgContains(message, "lash") then if npcHandler:getTopic(playerId) == 3 then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 1440 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 1440 then npcHandler:say("It seems you did not even set one big foot into the warzone, I am sorry.") else - if player:getStorageValue(Storage.BigfootBurden.Warzone3Access) < 1 then - if player:getStorageValue(Storage.BigfootBurden.WarzoneStatus) >= 3 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Warzone3Access) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus) >= 3 then if player:removeItem(16206, 1) then - player:setStorageValue(Storage.BigfootBurden.Warzone3Access, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Warzone3Access, 1) npcHandler:say("As a war hero you are allowed to use the warzone teleporter three for free!", npc, creature) npcHandler:setTopic(playerId, 0) else @@ -119,13 +119,13 @@ local function creatureSayCallback(npc, creature, type, message) end elseif MsgContains(message, "hat") then if npcHandler:getTopic(playerId) == 3 then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 1440 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 1440 then npcHandler:say("It seems you did not even set one big foot into the warzone, I am sorry.") else - if player:getStorageValue(Storage.BigfootBurden.Warzone2Access) < 1 then - if player:getStorageValue(Storage.BigfootBurden.WarzoneStatus) >= 2 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Warzone2Access) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus) >= 2 then if player:removeItem(16205, 1) then - player:setStorageValue(Storage.BigfootBurden.Warzone2Access, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Warzone2Access, 1) npcHandler:say("As a war hero you are allowed to use the warzone teleporter second for free!", npc, creature) npcHandler:setTopic(playerId, 0) else @@ -141,10 +141,10 @@ local function creatureSayCallback(npc, creature, type, message) end elseif MsgContains(message, "mission") then if npcHandler:getTopic(playerId) == 1 then - if player:getStorageValue(Storage.BigfootBurden.Rank) >= 1440 then - if player:getStorageValue(Storage.BigfootBurden.WarzoneStatus) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) >= 1440 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus) < 1 then npcHandler:say("Fine, I grant you the permission to enter the warzones. Be warned though, this will be not a picnic. Better bring some friends with you. Bringing a lot of them sounds like a good idea.", npc, creature) - player:setStorageValue(Storage.BigfootBurden.WarzoneStatus, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus, 1) else npcHandler:say("You have already accepted this mission.", npc, creature) end @@ -161,7 +161,7 @@ end npcHandler:setMessage(MESSAGE_GREET, "Hello |PLAYERNAME|. You are probably eager to enter the {warzones}.") local function onTradeRequest(npc, creature) - if Player(creature):getStorageValue(Storage.BigfootBurden.BossKills) < 20 then + if Player(creature):getStorageValue(Storage.Quest.U9_60.BigfootsBurden.BossKills) < 20 then npcHandler:say("Only if you have killed 20 of our major enemies in the warzones I am allowed to trade with you.", npc, creature) return false end diff --git a/data-otservbr-global/npc/hyacinth.lua b/data-otservbr-global/npc/hyacinth.lua index cbf37f8e9c6..542865e809e 100644 --- a/data-otservbr-global/npc/hyacinth.lua +++ b/data-otservbr-global/npc/hyacinth.lua @@ -54,9 +54,9 @@ local function greetCallback(npc, creature) local player = Player(creature) local playerId = player:getId() - if player:getStorageValue(Storage.TheRookieGuard.Mission04) == 2 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 2 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, traveller |PLAYERNAME|. You must be the one sent by Lily. Do you have a sack of {herbs} for me?") - elseif player:getStorageValue(Storage.TheRookieGuard.Mission04) == 3 or player:getStorageValue(Storage.TheRookieGuard.Mission04) == 4 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 3 or player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 4 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, traveller |PLAYERNAME|. I still have a present for you! Would you like to have it now?") else npcHandler:setMessage(MESSAGE_GREET, "Greetings, traveller |PLAYERNAME|. As you have found the way to my hut, how can I {help} you?") @@ -71,9 +71,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { npcHandler = npcHandler, text = "Thank you so much! I'm just too old to walk into the village each day, and the herbs must be fresh. Say, would you like to have a sample of my potions as reward?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 2 and player:getItemCount(12671) >= 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 2 and player:getItemCount(12671) >= 1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission04, 3) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, 3) player:removeItem(12671, 1) end) keywordHandler:addAliasKeyword({ "herbs" }) @@ -83,7 +83,7 @@ local mission4LostHerbs = keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Well, then I must have mistaken you with someone else. Or did you lose it on the way?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 2 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 2 end) -- Mission 4: Confirm (Lost herbs) @@ -110,9 +110,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Oh, and I also have another present for you! Do you still have some space in your inventory?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 3 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 3 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission04, 4) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, 4) player:addItemEx(Game.createItem(7876, 2), true, CONST_SLOT_BACKPACK) end) @@ -127,9 +127,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Anyway, this old man has taken enough of your time. Why don't you go back to the village and talk to Vascalir? If you stay on the path, you should be safe. Don't forget your potions!", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 4 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 4 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission04, 5) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, 5) player:addItemEx(Game.createItem(12669, 1), true, CONST_SLOT_BACKPACK) end) @@ -138,7 +138,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Oh, but I insist! After all you made the long way. Please, take my reward!", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 3 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 3 end) -- Mission 4: Decline (Second reward) @@ -146,7 +146,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Well, make some space and then talk to me again. I give you something really useful.", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 4 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 4 end) -- Basic Keywords diff --git a/data-otservbr-global/npc/lily.lua b/data-otservbr-global/npc/lily.lua index fad82e1db11..a22ae6b53d9 100644 --- a/data-otservbr-global/npc/lily.lua +++ b/data-otservbr-global/npc/lily.lua @@ -65,10 +65,10 @@ local function greetCallback(npc, creature) local playerId = creature:getId() local player = Player(creature) -- Continue mission 4 - if player:getStorageValue(Storage.TheRookieGuard.Mission04) == 1 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 1 then npcHandler:setMessage(MESSAGE_GREET, "Oh hey, |PLAYERNAME|! Vascalir must have sent you to help me with a little {mission}, right?") -- Not finished mission 4 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission04) == 2 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 2 then npcHandler:setMessage(MESSAGE_GREET, "Hello, |PLAYERNAME|! Back so soon? Have you delivered the herbs to Hyacinth?") else npcHandler:setMessage(MESSAGE_GREET, "Welcome, |PLAYERNAME|! You look a little stressed today. If you like to view my offers of potions, just ask me for a {trade}. In case you're looking for the marketplace and dungeons, just follow the path to the east!") @@ -86,7 +86,7 @@ local mission4 = keywordHandler:addKeyword({ "yes" }, StdModule.say, { "He's old and can't make his way into the village anymore, but needs some of the herbs that grow only around here. Could you please deliver a bag of herbs to Hyacinth?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 1 end) keywordHandler:addAliasKeyword({ "mission" }) @@ -96,7 +96,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Oh. In that case, maybe you're interested in a {trade} - I sell potions and buy a few other things.", reset = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 1 end) -- Mission 4: Accept @@ -112,7 +112,7 @@ mission4:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission04, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, 2) player:addItemEx(Game.createItem(12671, 1), true, CONST_SLOT_WHEREEVER) player:addMapMark({ x = 32091, y = 32178, z = 7 }, MAPMARK_GREENNORTH, "North Exit") player:addMapMark({ x = 32139, y = 32176, z = 7 }, MAPMARK_GREENNORTH, "To Hyacinth") @@ -131,7 +131,7 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { npcHandler = npcHandler, text = "No, you haven't. If you're looking for the way to Hyacinth, just leave the village to the north and then go east. I've marked it on your map!", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 2 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 2 end) -- Mission 4: Confirm Not Delivered (Without) @@ -139,7 +139,7 @@ local mission4LostHerbs = keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Is something... wrong? You didn't lose the herbs, did you?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 2 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 2 end) -- Mission 4: Confirm Lost Herbs diff --git a/data-otservbr-global/npc/palomino.lua b/data-otservbr-global/npc/palomino.lua index 6be58366c8b..66ab3857716 100644 --- a/data-otservbr-global/npc/palomino.lua +++ b/data-otservbr-global/npc/palomino.lua @@ -83,7 +83,7 @@ local function creatureSayCallback(npc, creature, type, message) destination:sendMagicEffect(CONST_ME_TELEPORT) npcHandler:say("Have a nice trip!", npc, creature) elseif npcHandler:getTopic(playerId) == 2 then - if player:getStorageValue(Storage.RentedHorseTimer) >= os.time() then + if player:getStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer) >= os.time() then npcHandler:say("You already have a horse.", npc, creature) return true end @@ -95,7 +95,7 @@ local function creatureSayCallback(npc, creature, type, message) local mountId = { 22, 25, 26 } player:addMount(mountId[math.random(#mountId)]) - player:setStorageValue(Storage.RentedHorseTimer, os.time() + 86400) + player:setStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer, os.time() + 86400) player:addAchievement("Natural Born Cowboy") npcHandler:say("I'll give you one of our experienced ones. Take care! Look out for low hanging branches.", npc, creature) end diff --git a/data-otservbr-global/npc/paulie.lua b/data-otservbr-global/npc/paulie.lua index 3b099caaa88..7884b91affc 100644 --- a/data-otservbr-global/npc/paulie.lua +++ b/data-otservbr-global/npc/paulie.lua @@ -62,7 +62,7 @@ local function greetCallback(npc, creature) local playerId = creature:getId() local player = Player(creature) -- Mission 8: The Rookie Guard Quest - if player:getStorageValue(Storage.TheRookieGuard.Mission08) == 1 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == 1 then npcHandler:setMessage(MESSAGE_GREET, "Welcome |PLAYERNAME|! Special newcomer offer, today only! Deposit some money - or {deposit ALL} of your money! - and get 50 gold for free!") else npcHandler:setMessage(MESSAGE_GREET, "Yes? What may I do for you, |PLAYERNAME|? Bank business, perhaps?") @@ -158,12 +158,12 @@ local function creatureSayCallback(npc, creature, type, message) end elseif npcHandler:getTopic(playerId) == 2 then if MsgContains(message, "yes") then - if player:getStorageValue(Storage.TheRookieGuard.Mission08) == 1 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == 1 then player:depositMoney(count[playerId]) Bank.credit(player, 50) npcHandler:say("Alright, we have added the amount of " .. count[playerId] .. " +50 gold to your {balance} - that is the money you deposited plus a bonus of 50 gold. \z Thank you! You can withdraw your money anytime.", npc, creature) - player:setStorageValue(Storage.TheRookieGuard.Mission08, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08, 2) npcHandler:setTopic(playerId, 0) return false end diff --git a/data-otservbr-global/npc/scrutinon.lua b/data-otservbr-global/npc/scrutinon.lua index 86639f88bf9..b163f8ed624 100644 --- a/data-otservbr-global/npc/scrutinon.lua +++ b/data-otservbr-global/npc/scrutinon.lua @@ -44,6 +44,38 @@ end npcType.onCloseChannel = function(npc, creature) npcHandler:onCloseChannel(npc, creature) end + +local function creatureSayCallback(npc, creature, type, message) + local player = Player(creature) + local playerId = player:getId() + + if not npcHandler:checkInteraction(npc, creature) then + return false + end + + if MsgContains(message, "quirefang") then + npcHandler:say({ + "This island is cleft. Go there only prepared or you will meet your end. The surface of this forgotten rock is a barren wasteland full of hostile creatures. ...", + "Its visage is covered with holes and tunnels in which its leggy inhabitants are hiding. Its bowels filled with the strangest creatures, waiting to feast on whatever dares to disturb their hive. ...", + "And you will find no shelter in Quirefang's black depths, where the creatures of the deep are fulfilling a dark prophecy. ...", + "It is impossible to reach it by ship or boat. However, there was one before you. A {visitor} who found a way to enter the island.", + }, npc, creature) + npcHandler:setTopic(playerId, 1) + elseif npcHandler:getTopic(playerId) == 1 and MsgContains(message, "visitor") then + npcHandler:say({ + "He travelled on the very ground to reach the hostile shores of Quirefang. He used something that turned the soil and carved a way to the island. ...", + "You will need to follow his trail if you want to reach it.", + }, npc, creature) + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Questline) < 1 then + player:setStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Questline, 1) + player:setStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor, 1) + end + npcHandler:setTopic(playerId, 0) + end + + return true +end + -- Travel local function addTravelKeyword(keyword, destination) local travelKeyword = keywordHandler:addKeyword({ keyword }, StdModule.say, { npcHandler = npcHandler, text = "Do you want to sail " .. keyword:titleCase() .. "?" }) @@ -65,15 +97,10 @@ keywordHandler:addKeyword({ "name" }, StdModule.say, { "There are drifts and storms surrounding that place that are far too dangerous to navigate through even for the most versed captains. They often sail not closer than to this island here and drop off whoever dares to explore near this dreaded coast.", }, }) -keywordHandler:addKeyword({ "quirefang" }, StdModule.say, { - npcHandler = npcHandler, - text = { - "This island is cleft. Go there only prepared or you will meet your end. The surface of this forgotten rock is a barren wasteland full of hostile creatures. ...", - "Its visage is covered with holes and tunnels in which its leggy inhabitants are hiding. Its bowels filled with the strangest creatures, waiting to feast on whatever dares to disturb their hive. ...", - "And you will find no shelter in Quirefang's black depths, where the creatures of the deep are fulfilling a dark prophecy. ...", - "It is impossible to reach it by ship or boat. However, there was one before you. A {visitor} who found a way to enter the island.", - }, -}) + +npcHandler:setMessage(MESSAGE_GREET, "|PLAYERNAME|. I have been watching your fate for quite some time. It's about time you came here. Do you seek to enter the riven island of Quirefang or travel back from where you came?") + +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) diff --git a/data-otservbr-global/npc/servant_sentry.lua b/data-otservbr-global/npc/servant_sentry.lua index d1ff8453170..6d9805e24a7 100644 --- a/data-otservbr-global/npc/servant_sentry.lua +++ b/data-otservbr-global/npc/servant_sentry.lua @@ -56,14 +56,14 @@ end keywordHandler:addKeyword({ "master" }, StdModule.say, { npcHandler = npcHandler, text = "Our. Master. Is. Gone. You. Can. Not. Visit. Him! We. Stand. {Sentry}!" }) keywordHandler:addKeyword({ "sentry" }, StdModule.say, { npcHandler = npcHandler, text = "{Master}. Conducted. Experiments. Great. Problems. You. Must. Go!" }) keywordHandler:addKeyword({ "slime" }, StdModule.say, { npcHandler = npcHandler, text = "{Slime}. Dangerous. We. Have. It. Under. Control. ... We. Will. Stand. {Sentry}." }, function(player) - return player:getStorageValue(Storage.TheirMastersVoice.SlimeGobblerReceived) == 1 + return player:getStorageValue(Storage.Quest.U9_1.TheirMastersVoiceWorldChange.SlimeGobblerReceived) == 1 end) local function greetCallback(npc, creature) local player = Player(creature) local playerId = player:getId() - if player:getStorageValue(Storage.TheirMastersVoice.SlimeGobblerReceived) < 1 then + if player:getStorageValue(Storage.Quest.U9_1.TheirMastersVoiceWorldChange.SlimeGobblerReceived) < 1 then npcHandler:say("The. {Slime}. Has. Entered. Our. {Master}. Has. Left! We. Must. {Help}.", npc, creature) end return true @@ -77,12 +77,12 @@ local function creatureSayCallback(npc, creature, type, message) return false end - if MsgContains(message, "help") then + if MsgContains(message, "slime") then npcHandler:say("Defeat. {Slime}. We. Will. Why. Did. You. Kill. Us? Do. You. Want. To. Rectify. And. Help?", npc, creature) npcHandler:setTopic(playerId, 1) elseif MsgContains(message, "yes") then if npcHandler:getTopic(playerId) == 1 then - player:setStorageValue(Storage.TheirMastersVoice.SlimeGobblerReceived, 1) + player:setStorageValue(Storage.Quest.U9_1.TheirMastersVoiceWorldChange.SlimeGobblerReceived, 1) player:addItem(12077, 1) npcHandler:say("Then. Take. This. Gobbler. Always. Hungry. Eats. Slime. Fungus. Go.", npc, creature) npcHandler:setTopic(playerId, 0) diff --git a/data-otservbr-global/npc/spectulus.lua b/data-otservbr-global/npc/spectulus.lua index 442b03498cb..20257671367 100644 --- a/data-otservbr-global/npc/spectulus.lua +++ b/data-otservbr-global/npc/spectulus.lua @@ -376,7 +376,7 @@ local function creatureSayCallback(npc, creature, type, message) npcHandler:setTopic(playerId, 0) end - if MsgContains(message, "machine") and player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then + if MsgContains(message, "machine") and player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then npcHandler:say({ "Ah, the machine you found at that island. Well, I built this thing to venture far beneath the very soil we walk on. I suspected something there. Something deep down below. Something evil. Even more so than the dreaded bugs which are crawling my study. ...", "Drilling hole after hole only to get stuck in another hard, unbreakable sediment again and again, I was about to quit this pointless enterprise. ...", @@ -384,8 +384,8 @@ local function creatureSayCallback(npc, creature, type, message) "I am well aware that this may sound laughable now - at this part all of my colleagues burst into laughter anyway - but suddenly there were stairs. Incredibly large stairs that led to the underworld. A world deep under the sea - can you believe this?", }, npc, creature) npcHandler:setTopic(playerId, 21) - elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 21 and player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 21 and player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then npcHandler:say({ "You do? Well, the end of this story was that I had to leave the place. ...", "I couldnt explore what lies below the stairs as there was an unpredictable stream. Diving into these waters would have been an uncontrollable risk, even with the means to survive without any air. ...", @@ -396,8 +396,8 @@ local function creatureSayCallback(npc, creature, type, message) }, npc, creature) npcHandler:setTopic(playerId, 22) end - elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 22 and player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 22 and player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then npcHandler:say({ "Well, if you really want to delve into this - I could use some help. So you have found my {machine} on that island? And you found the notes with the coordinates? Then you can find the entrance! ...", "Just look for a large staircase with sprawling steps. There is an unpassable stream there that will prevent you from venturing further on. But fear not, you can indeed travel down there - with these small {enhancements} I created. ...", @@ -406,10 +406,10 @@ local function creatureSayCallback(npc, creature, type, message) }, npc, creature) npcHandler:setTopic(playerId, 23) end - elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 23 and player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 3 then + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 23 and player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 3 then npcHandler:say("Then off you go! Im sorry that I cannot offer you any further help but Im sure you will find support along your way. And - be careful. The sea can appear pitch black down there.", npc, creature) - player:setStorageValue(Storage.LiquidBlackQuest.Visitor, 4) + player:setStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor, 4) npcHandler:setTopic(playerId, 24) end elseif MsgContains(message, "task") then diff --git a/data-otservbr-global/npc/tom.lua b/data-otservbr-global/npc/tom.lua index c66ade91e30..74f441adb29 100644 --- a/data-otservbr-global/npc/tom.lua +++ b/data-otservbr-global/npc/tom.lua @@ -62,13 +62,13 @@ local function greetCallback(npc, creature) local playerId = creature:getId() local player = Player(creature) -- Starting mission 6 - if player:getStorageValue(Storage.TheRookieGuard.Mission06) == 1 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 1 then npcHandler:setMessage(MESSAGE_GREET, "Hey there, |PLAYERNAME|. Did Vascalir send you to me for a {mission}?") -- Not finished mission 6 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission06) > 1 and player:getStorageValue(Storage.TheRookieGuard.Mission06) < 6 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) > 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) < 6 then npcHandler:setMessage(MESSAGE_GREET, "Now, now - we can't work with that. Go back to that wolf den and fulfil your mission! Unless there is anything else I can help you with.") -- Finishing mission 6 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission06) == 6 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 6 then npcHandler:setMessage(MESSAGE_GREET, "Hey there, |PLAYERNAME|. You look... exhausted. Did you run a lot? And more importantly, were you able to find some war wolf leather?") else npcHandler:setMessage(MESSAGE_GREET, "Hey there, |PLAYERNAME|. I'm Tom the tanner. If you have fresh {corpses}, leather, paws or other animal body parts, {trade} with me.") @@ -101,7 +101,7 @@ local mission6 = keywordHandler:addKeyword({ "yes" }, StdModule.say, { "That's why I wouldn't call it 'stealing', what an ugly word... anyway, if you bring the skin back to me, I'll make some great war wolf boots from them. What do you say?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission06) == 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 1 end) keywordHandler:addAliasKeyword({ "mission" }) @@ -110,7 +110,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Alright. Can I help you with something else then?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission06) == 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 1 end) -- Mission 6: Accept @@ -124,7 +124,7 @@ mission6:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission06, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 2) player:addMapMark({ x = 32138, y = 32132, z = 7 }, MAPMARK_GREENSOUTH, "War Wolf Den") end ) @@ -147,9 +147,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "You can also tame creatures to ride on that will also increase your speed. So don't worry if you're out of breath now - you won't always be that slow. Now off with you and back to Vascalir, I have work to do.", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission06) == 6 and player:getItemCount(12740) >= 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 6 and player:getItemCount(12740) >= 1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission06, 7) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 7) player:removeItem(12740, 1) player:addItemEx(Game.createItem(3552, 1), true, CONST_SLOT_WHEREEVER) end) @@ -159,7 +159,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Are you sure? I think I see some war wolf leather on you. You should reply with {yes}.", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission06) == 6 and player:getItemCount(12740) >= 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 6 and player:getItemCount(12740) >= 1 end) -- Basic keywords diff --git a/data-otservbr-global/npc/vascalir.lua b/data-otservbr-global/npc/vascalir.lua index ef799e65709..0edd2e00c3f 100644 --- a/data-otservbr-global/npc/vascalir.lua +++ b/data-otservbr-global/npc/vascalir.lua @@ -61,11 +61,11 @@ local function greetCallback(npc, creature) local playerId = creature:getId() local player = Player(creature) -- Reject to start missions - if player:getStorageValue(Storage.TheRookieGuard.Questline) == -1 and player:getLevel() > 5 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline) == -1 and player:getLevel() > 5 then npcHandler:say("Welcome, adventurer |PLAYERNAME|. Thank you for offering your help - but you are already too experienced to start this quest. Just go on hunting monsters, you'll be better off that way.", npc, creature) return false -- Warn if started missions and reached level 8 - elseif player:getStorageValue(Storage.TheRookieGuard.Questline) == 1 and player:getLevel() == 8 and player:getStorageValue(Storage.TheRookieGuard.Level8Warning) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline) == 1 and player:getLevel() == 8 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Level8Warning) == -1 then npcHandler:setMessage(MESSAGE_GREET, { "|PLAYERNAME| - a small word of advice before we continue this mission. You are level 8 now, while it is possible to reach higher levels while still on Rookgaard, you should consider leaving Rookgaard at about level 9. ...", "You can still go on with this mission, but you won't be able to finish the quest once you've reached level 9. So only kill the monsters you absolutely have to kill - if you want to finish this quest! ...", @@ -73,114 +73,114 @@ local function greetCallback(npc, creature) "What would you like to do? {Continue} the mission or {delete} the unfinished questline from your questlog?", }) -- Completed all missions - elseif player:getStorageValue(Storage.TheRookieGuard.Questline) == 2 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline) == 2 then npcHandler:say("|PLAYERNAME|, the only thing left for you to do here is to talk to the oracle above the academy and leave for the Isle of Destiny. Thanks again for your great work and good luck on your journeys!", npc, creature) return false -- Not started mission 2 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission02) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Welcome, adventurer |PLAYERNAME|. These are dire times for Rookgaard... have you come to help in our {mission}?") -- Not finished mission 2 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission02) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission02) <= 3 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) <= 3 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|. Your task is still not done - do you remember everything you need to do?") -- Finishing mission 2 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission02) == 4 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == 4 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|. I've heard a loud rumbling from the roof - I hope the stones didn't fall on your toes. Have you loaded at least two catapults?") -- Finished mission 2 but not started mission 3 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission02) == 5 and player:getStorageValue(Storage.TheRookieGuard.Mission03) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == 5 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|. Actually I have some more equipment I could give to you, but first I want to see how you fight. You have fought before, haven't you?") -- Not finished or finishing mission 3 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission03) == 1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == 1 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|. Are you done with the 5 rats I asked you to kill?") -- Started but not finished mission 4 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission04) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission04) <= 4 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) <= 4 then npcHandler:say("Greetings, |PLAYERNAME|. Right now I don't need your help. I heard that Lily south-west of here requires assistance though.", npc, creature) return false -- Finishing mission 4 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission04) == 5 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 5 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|. Glad to see you made it back in one piece. I hope you're not too exhausted, because I could use your {help} again.") -- Finished mission 4 but not started mission 5 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission04) == 6 and player:getStorageValue(Storage.TheRookieGuard.Mission05) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 6 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Oh, hello |PLAYERNAME|! Have you made up your mind about sneaking into the tarantula's lair and retrieving a sample of her web? Are you up for it?") -- Started but not finished mission 5 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission05) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission05) <= 2 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) <= 2 then npcHandler:setMessage(MESSAGE_GREET, "Do you need the instruction for the tarantula's lair again?") -- Finishing mission 5 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission05) == 3 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == 3 then npcHandler:setMessage(MESSAGE_GREET, "Oh, well done! Let me take that spider web sample from you - careful, careful... it's sturdy, yet fragile. Thank you! I should be able to make a great paralyse trap with this one. Here, I have something sturdy for you as well - want it?") - player:setStorageValue(Storage.TheRookieGuard.Mission05, 5) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, 5) player:addExperience(50, true) -- Finishing mission 5 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission05) == 5 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == 5 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|! How about that studded armor - would you like to have it now?") -- Started but not finished mission 6 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission06) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission06) <= 6 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) <= 6 then npcHandler:say("Greetings, |PLAYERNAME|. Right now I don't need your help. You should pay a visit to Tom the Tanner. His hut is south-west of the academy!", npc, creature) return false -- Finished mission 6 but not started mission 7 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission06) == 7 and player:getStorageValue(Storage.TheRookieGuard.Mission07) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 7 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == -1 then npcHandler:setMessage(MESSAGE_GREET, "|PLAYERNAME|! Thank the gods you are back! While you were gone, something horrible happened. Do you smell the fire?") -- Started but not finished mission 7 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.TheRookieGuard.LibraryChest) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) == -1 then npcHandler:say("You can find the vault if you go down the stairs in the northern part of the academy. The book should be in a large blue chest somewhere down there - I hope it's not burnt yet.", npc, creature) return false -- Finishing mission 7 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.TheRookieGuard.LibraryChest) == 1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) == 1 then npcHandler:setMessage(MESSAGE_GREET, "Oh my, what happened to your hair? Your face is all black, too - it must have been a hell of flames down there. That's so brave of you. Did you get the book?") -- Finished mission 7 but not started mission 8 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission07) == 2 and player:getStorageValue(Storage.TheRookieGuard.Mission08) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 2 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Are you prepared for your next mission, |PLAYERNAME|?") -- Started but not finished mission 8 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission08) == 1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == 1 then npcHandler:say("I think it's a good idea to go see Paulie before you leave the village again. Just go downstairs and to the right to find the bank.", npc, creature) return false -- Finished mission 8 but not started mission 9 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission08) == 2 and player:getStorageValue(Storage.TheRookieGuard.Mission09) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == 2 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Now that you know how to store your money, it's time to go after the trolls. I'm even going to give you some more equipment as reward. Do you feel ready for that mission?") -- Started but not finished mission 9 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission09) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission09) <= 7 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) <= 7 then npcHandler:say("|PLAYERNAME|, you need to discover the troll tunnel and find a way to make it collapse. Maybe you're able to use some of the trolls' tools. Make sure they can't enter the village via that tunnel anymore!", npc, creature) return false -- Finishing mission 9 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission09) == 8 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 8 then npcHandler:setMessage(MESSAGE_GREET, "|PLAYERNAME|, welcome back! That was great work you did there. Let me give you something for your efforts - you deserve it. Here, want a helmet?") - player:setStorageValue(Storage.TheRookieGuard.Mission09, 9) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09, 9) player:addExperience(50, true) -- Finish mission 9 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission09) == 9 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 9 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|. Do you have enough space for the brass helmet now?") -- Finished mission 9 but not started mission 10 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission09) == 10 and player:getStorageValue(Storage.TheRookieGuard.Mission10) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 10 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|. Are you ready for your next mission?") -- Started but not finished mission 10 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|. I see you haven't explored the whole crypt yet - do you need explanations again?") -- Finishing mission 10 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) == 1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) == 1 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|! Did you find a nice, fleshy bone in the crypt?") -- Finish mission 10 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission10) == 2 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 2 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|! Do you have enough space for that sword now?") -- Finished mission 10 but not started mission 11 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission10) == 3 and player:getStorageValue(Storage.TheRookieGuard.Mission11) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 3 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == -1 then npcHandler:setMessage(MESSAGE_GREET, "Greetings, |PLAYERNAME|! I'm in a really good mood, I must say. We're almost able to infiltrate Kraknaknork's hideout. I have one last little favour to ask and then my plan is complete. Are you ready?") -- Started but not finished mission 11 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.TheRookieGuard.Mission11) == 2 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 2 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|! But - back so soon? Please find the wasps' lair in the north-western region of Rookgaard and use the flask I gave you on its dead body. Or did you lose the flask?") -- Finishing mission 11 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission11) == 3 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 3 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|! Were you able to bring back some wasp poison?") -- Finish mission 11 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission11) == 4 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 4 then npcHandler:setMessage(MESSAGE_GREET, "Welcome back, |PLAYERNAME|! Do you have enough space for that brass shield now?") -- Finished mission 11 but not started mission 12 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission11) == 5 and player:getStorageValue(Storage.TheRookieGuard.Mission12) == -1 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 5 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) == -1 then npcHandler:setMessage(MESSAGE_GREET, "|PLAYERNAME|, the time of our triumph has come. Are you ready to vanquish Kraknaknork once and for all?") -- Started but not finished mission 12 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission12) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission12) <= 13 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) <= 13 then npcHandler:say("|PLAYERNAME|, the air smells like victory. Head into the orc fortress and vanquish Kraknaknork once and for all. Don't forget to take the items from below the academy!", npc, creature) return false -- Finish mission 12 - elseif player:getStorageValue(Storage.TheRookieGuard.Mission12) == 14 then + elseif player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) == 14 then npcHandler:setMessage(MESSAGE_GREET, "|PLAYERNAME|! You're back! And you're covered in orc blood... that can only mean... were you able to kill Kraknaknork?") end return true @@ -194,7 +194,7 @@ local mission2 = keywordHandler:addKeyword({ "yes" }, StdModule.say, { "What would you say about you defeat Kraknaknork, save Rookgaard and earn some experience and better equipment on the way? Sounds good?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission02) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == -1 end) keywordHandler:addAliasKeyword({ "mission" }) @@ -204,7 +204,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Well, if you change your mind you know where to find me. Remember that if you help Rookgaard, Rookgaard might be able to help you.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission02) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == -1 end) local mission02Reject = KeywordNode:new({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "OK, dude!" }) @@ -225,9 +225,9 @@ local mission2Accept = mission2:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Questline, 1) - player:setStorageValue(Storage.TheRookieGuard.Mission02, 1) - player:setStorageValue(Storage.TheRookieGuard.Catapults, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Catapults, 0) player:addMapMark({ x = 32082, y = 32182, z = 7 }, MAPMARK_FLAG, "Barn") player:addMapMark({ x = 32097, y = 32181, z = 7 }, MAPMARK_BAG, "Norma's Bar") player:addMapMark({ x = 32105, y = 32203, z = 7 }, MAPMARK_BAG, "Obi's Shop") @@ -260,9 +260,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Actually I have some more equipment I could give to you, but first I want to see how you fight. You have fought before, haven't you?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission02) == 4 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == 4 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission02, 5) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02, 5) player:addItemEx(Game.createItem(3426, 1), true, CONST_SLOT_WHEREEVER) end) @@ -271,7 +271,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Oh, but you have... you should say {yes}!", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission02) == 4 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == 4 end) -- Mission 3: Start @@ -285,7 +285,7 @@ local mission3 = keywordHandler:addKeyword({ "yes" }, StdModule.say, { "If you run low on health, go on full defence - click the little shield icon - and leave the dungeon. Nothing corwardish about running, because dying hurts. Are you ready to go?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission02) == 5 and player:getStorageValue(Storage.TheRookieGuard.Mission03) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == 5 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == -1 end) -- Mission 3: Decline @@ -293,7 +293,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "No worries, let's refresh your memory. To fight a monster, click on it in the battle list and you'll automatically attack it. It's as easy as that! If you want to practice, just hunt a few harmless rabbits south of here. Remember it now?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission02) == 5 and player:getStorageValue(Storage.TheRookieGuard.Mission03) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) == 5 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == -1 end) -- Mission 3: Accept @@ -311,8 +311,8 @@ mission3:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission03, 1) - player:setStorageValue(Storage.TheRookieGuard.RatKills, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills, 0) player:addMapMark({ x = 32097, y = 32205, z = 7 }, MAPMARK_GREENSOUTH, "Rat Dungeon") player:addMapMark({ x = 32041, y = 32228, z = 7 }, MAPMARK_GREENSOUTH, "Rat Dungeon") end @@ -332,9 +332,9 @@ mission3:addChildKeyword({ "no" }, StdModule.say, { -- Mission 3: Complain not finished keywordHandler:addKeyword({ "yes" }, nil, {}, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission03) == 1 and player:getStorageValue(Storage.TheRookieGuard.RatKills) < 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills) < 5 end, function(player) - local ratKills = player:getStorageValue(Storage.TheRookieGuard.RatKills) + local ratKills = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills) npcHandler:say("You still need to kill " .. (5 - ratKills) .. " more rats. Come back once you've killed enough for some experience and equipment!", player.uid) end) keywordHandler:addAliasKeyword({ "no" }) @@ -350,10 +350,10 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Anyway, I think you're well enough equipped now to leave the village of Rookgaard for another small task. Find Lily south-west of here, she will tell you what she needs done.", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission03) == 1 and player:getStorageValue(Storage.TheRookieGuard.RatKills) >= 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills) >= 5 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission03, 2) - player:setStorageValue(Storage.TheRookieGuard.Mission04, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, 1) player:addExperience(30, true) player:addItemEx(Game.createItem(3273, 1), true, CONST_SLOT_WHEREEVER) end) @@ -363,7 +363,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Actually I think you have killed enough. You should reply with {yes}!", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission03) == 1 and player:getStorageValue(Storage.TheRookieGuard.RatKills) == 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills) == 5 end) -- Mission 4: Finish - Confirm @@ -377,9 +377,9 @@ keywordHandler:addKeyword({ "help" }, StdModule.say, { "Do you dare sneak into the tarantula's lair and retrieve a sample of her web?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 5 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission04, 6) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, 6) end) -- Mission 4: Finish - Wrong Confirm @@ -387,7 +387,7 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { npcHandler = npcHandler, text = "What do you mean? If you're ready to {help} me again, just say so.", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 5 end) keywordHandler:addAliasKeyword({ "no" }) @@ -402,9 +402,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "If you run into her lair, you should have enough time to retrieve a sample of her web before she catches you. Just USE one of her intact cobwebs in her lair. Good luck!", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 6 and player:getStorageValue(Storage.TheRookieGuard.Mission05) == -1 or (player:getStorageValue(Storage.TheRookieGuard.Mission05) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission05) <= 2) + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 6 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == -1 or (player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) <= 2) end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission05, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, 1) player:addMapMark({ x = 32051, y = 32110, z = 7 }, MAPMARK_GREENSOUTH, "Spider Lair") end) @@ -414,7 +414,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Well, if you change your mind, let me know.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission04) == 6 and player:getStorageValue(Storage.TheRookieGuard.Mission05) == -1 or (player:getStorageValue(Storage.TheRookieGuard.Mission05) >= 1 and player:getStorageValue(Storage.TheRookieGuard.Mission05) <= 2) + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) == 6 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == -1 or (player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) >= 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) <= 2) end) -- Mission 5: Finish - Accept Reward (Studded armor) @@ -423,10 +423,10 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { text = "Here, this studded armor will protect you much better. Fits you perfectly! Now - let's work on your footwear. Tom the Tanner can create great boots out of quality leather. You should pay him a visit!", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission05) == 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == 5 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission05, 6) - player:setStorageValue(Storage.TheRookieGuard.Mission06, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, 6) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 1) player:addItemEx(Game.createItem(3378, 1), true, CONST_SLOT_WHEREEVER) end) @@ -435,7 +435,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Seriously, don't reject that offer. I'm going to give you a studded armor. No one refuses free stuff! If you don't like it, you can sell it - I don't need it anymore. I promise it's not too used or smelly. Want it?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission05) == 5 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) == 5 end) -- Mission 7: Start @@ -450,7 +450,7 @@ local mission7 = keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Are you ready to go?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission06) == 7 and player:getStorageValue(Storage.TheRookieGuard.Mission07) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 7 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == -1 end) --keywordHandler:addAliasKeyword({"no"}) @@ -467,8 +467,8 @@ mission7:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission07, 1) - player:setStorageValue(Storage.TheRookieGuard.LibraryDoor, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryDoor, 1) end ) @@ -488,10 +488,10 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "We do have to stop the trolls though before taking care of the orcs. I found their tunnel in the northern ruins. Are you prepared for your next mission?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.TheRookieGuard.LibraryChest) == 1 and player:getItemCount(12675) <= 0 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) == 1 and player:getItemCount(12675) <= 0 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission07, 2) - player:setStorageValue(Storage.TheRookieGuard.LibraryDoor, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryDoor, -1) player:addExperience(100, true) end) keywordHandler:addAliasKeyword({ "no" }) @@ -505,10 +505,10 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "We do have to stop the trolls though before taking care of the orcs. I found their tunnel in the northern ruins. Are you prepared for your next mission?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.TheRookieGuard.LibraryChest) == 1 and player:getItemCount(12675) >= 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) == 1 and player:getItemCount(12675) >= 1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission07, 2) - player:setStorageValue(Storage.TheRookieGuard.LibraryDoor, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryDoor, -1) player:removeItem(12675, 1) player:addExperience(100, true) player:addItemEx(Game.createItem(3035, 1), true, CONST_SLOT_WHEREEVER) @@ -523,10 +523,10 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { "We do have to stop the trolls though before taking care of the orcs. I found their tunnel in the northern ruins. Are you prepared for your next mission?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.TheRookieGuard.LibraryChest) == 1 and player:getItemCount(12675) >= 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) == 1 and player:getItemCount(12675) >= 1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission07, 2) - player:setStorageValue(Storage.TheRookieGuard.LibraryDoor, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryDoor, -1) player:removeItem(12675, 1) player:addExperience(100, true) player:addItemEx(Game.createItem(3035, 1), true, CONST_SLOT_WHEREEVER) @@ -543,9 +543,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Go downstairs and talk to Paulie. I'm sure he can explain to you everything you need to know, and he might also give you a small bonus for your account.", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission07) == 2 and player:getStorageValue(Storage.TheRookieGuard.Mission08) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 2 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == -1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission08, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08, 1) end) -- Mission 8: Decline @@ -554,7 +554,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Take a small break then and return to me when you have recovered... and cleaned your face.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission07) == 2 and player:getStorageValue(Storage.TheRookieGuard.Mission08) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) == 2 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == -1 end) -- Mission 9: Accept @@ -566,11 +566,11 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "And please don't hurt yourself in the process. You'll probably have to fight them, so bring food and maybe a potion. If you need to buy something, don't forget that you can withdraw money from your bank account. Good luck!", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission08) == 2 and player:getStorageValue(Storage.TheRookieGuard.Mission09) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == 2 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == -1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission09, 1) - player:setStorageValue(Storage.TheRookieGuard.TrollChests, 0) - player:setStorageValue(Storage.TheRookieGuard.TunnelPillars, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.TrollChests, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.TunnelPillars, 0) player:addMapMark({ x = 32094, y = 32137, z = 7 }, MAPMARK_GREENSOUTH, "Troll Caves") end) @@ -580,7 +580,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Alright, then just let me know when you're ready. Don't take too much time though.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission08) == 2 and player:getStorageValue(Storage.TheRookieGuard.Mission09) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08) == 2 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == -1 end) -- Mission 9: Finish - Accept Reward (Brass helmet) @@ -589,9 +589,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { text = "This brass helmet will make sure you don't hurt your head. I probably should have given that to you BEFORE you made a rocky tunnel collapse! Take your well-deserved break. Once you're ready for the next mission, talk to me again.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission09) == 9 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 9 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission09, 10) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09, 10) player:addItemEx(Game.createItem(3354, 1), true, CONST_SLOT_WHEREEVER) end) @@ -600,7 +600,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Seriously, don't reject that offer. I'm going to give you a brass helmet. No one refuses free stuff! If you don't like it, you can sell it - I don't need it anymore. I promise there are no fleas in it. Want it?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission09) == 9 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 9 end) -- Mission 10: Accept @@ -614,11 +614,11 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Undead monsters tend to drain your life - because their own life force is gone. If you wear it, you'll be protected from it. Search around in the coffins in the crypt, one of them should hold a nice fleshy bone. See you soon!", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission09) == 10 and player:getStorageValue(Storage.TheRookieGuard.Mission10) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 10 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == -1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission10, 1) - player:setStorageValue(Storage.TheRookieGuard.UnholyCryptDoor, 1) - player:setStorageValue(Storage.TheRookieGuard.UnholyCryptChests, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.UnholyCryptDoor, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.UnholyCryptChests, 0) player:addItemEx(Game.createItem(3083, 1), true, CONST_SLOT_WHEREEVER) player:addMapMark({ x = 32131, y = 32201, z = 7 }, MAPMARK_GREENSOUTH, "Unholy Crypt") end) @@ -629,7 +629,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Let me know when you're ready. This should be fun.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission09) == 10 and player:getStorageValue(Storage.TheRookieGuard.Mission10) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) == 10 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == -1 end) -- Mission 10: Confirm (Explain again) @@ -641,7 +641,7 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Undead monsters tend to drain your life - because their own life force is gone. If you wear the garlic necklace I gave you, you'll be protected from it. Search around in the coffins in the crypt, one of them should hold a nice fleshy bone. See you soon!", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) == -1 end) -- Mission 10: Decline (Explain again) @@ -650,7 +650,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Okay, then good hunting.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) == -1 end) -- Mission 10: Finish - Confirm/Decline (Having the fleshy bone) @@ -658,9 +658,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { npcHandler = npcHandler, text = "Well done, this bone is exactly what I needed! Great. I have to do some preparations, but as reward for your great work, I have a shiny new weapon for you. Here, would you like to have this sword?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) == 1 and player:getItemCount(12674) >= 1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) == 1 and player:getItemCount(12674) >= 1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission10, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10, 2) player:addExperience(150, true) player:removeItem(12674, 1) end) @@ -671,9 +671,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { npcHandler = npcHandler, text = "Ah... well, if you lost that bone on the way, that's too bad. I just hope you didn't get hungry and nibbled on it. I wouldn't eat cursed flesh if I were you. Anyway, you can still have this old sword as reward, do you want it?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) == 1 and player:getItemCount(12674) == 0 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 1 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) == 1 and player:getItemCount(12674) == 0 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission10, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10, 2) player:addExperience(80, true) end) keywordHandler:addAliasKeyword({ "no" }) @@ -684,9 +684,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { text = "This sword has helped me in many fierce battles. I hope you can put it to good use. Once you're ready for the next mission, talk to me again.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 2 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 2 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission10, 3) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10, 3) player:addItemEx(Game.createItem(3264, 1), true, CONST_SLOT_WHEREEVER) end) @@ -695,7 +695,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Seriously, don't reject that offer. Just take that sword, it's free. If you don't like it, you can sell it - I don't need it anymore. I promise there are no blood stains on it. Want it?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 2 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 2 end) -- Mission 11: Start @@ -708,7 +708,7 @@ local mission11 = keywordHandler:addKeyword({ "yes" }, StdModule.say, { "However, I can give you something for protection - a silver amulet. As long as you wear it, poison can't harm you as much as it usually would do. I'll also give you the flask which you have to use on a fresh, dead wasp. Are you prepared for that mission?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 3 and player:getStorageValue(Storage.TheRookieGuard.Mission11) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 3 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == -1 end) -- Mission 11: Decline Start @@ -717,7 +717,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Well, then just let me know when you're ready.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission10) == 3 and player:getStorageValue(Storage.TheRookieGuard.Mission11) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) == 3 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == -1 end) -- Mission 11: Accept @@ -731,7 +731,7 @@ mission11:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission11, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, 1) player:addItemEx(Game.createItem(3054, 1), true, CONST_SLOT_WHEREEVER) player:addItemEx(Game.createItem(12785, 1), true, CONST_SLOT_WHEREEVER) player:addMapMark({ x = 32000, y = 32139, z = 7 }, MAPMARK_GREENSOUTH, "Wasps' Nest") @@ -751,7 +751,7 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { text = "Oh, but there you have it in your inventory! Yeah, your backpack is a bit of a mess. I understand you overlooked it. Dig deeper!", ungreet = true, }, function(player) - return (player:getStorageValue(Storage.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.TheRookieGuard.Mission11) == 2) and player:getItemCount(12785) > 0 + return (player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 2) and player:getItemCount(12785) > 0 end) -- Mission 11: Confirm - Lost Flask (Without having it) @@ -760,7 +760,7 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { text = "No problem. Here's a new one. I can only give you one per hour though, so try not to lose it again this time.", ungreet = true, }, function(player) - return (player:getStorageValue(Storage.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.TheRookieGuard.Mission11) == 2) and player:getItemCount(12785) == 0 + return (player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 2) and player:getItemCount(12785) == 0 end, function(player) player:addItemEx(Game.createItem(12785, 1), true, CONST_SLOT_WHEREEVER) end) @@ -771,7 +771,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Great, then please find the wasps' nest, kill one and use the empty flask on its dead body.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.TheRookieGuard.Mission11) == 2 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 1 or player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 2 end) -- Mission 11: Finish - Confirm Give (Wasp poison flask, having it) @@ -784,9 +784,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "And I have a good shield for you, too. Here, can you carry it?", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 3 and player:getItemCount(12784) > 0 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 3 and player:getItemCount(12784) > 0 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission11, 4) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, 4) player:removeItem(12784, 1) player:addItemEx(Game.createItem(7644, 1), true, CONST_SLOT_WHEREEVER) player:addExperience(150, true) @@ -798,7 +798,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Well, please come back with the poison soon. We won't have much time until Kraknaknork's next attack.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 3 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 3 end) -- Mission 11: Finish - Confirm Give (Wasp poison flask, without having it) @@ -806,7 +806,7 @@ local mission11Reset = keywordHandler:addKeyword({ "yes" }, StdModule.say, { npcHandler = npcHandler, text = "Oh, but you don't carry any - did you lose the flask? I can give you a new empty one, but that will also reset your mission, meaning you have to extract new poison. Would you like that?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 3 and player:getItemCount(12784) == 0 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 3 and player:getItemCount(12784) == 0 end) -- Mission 11: Confirm - Reset Mission @@ -820,7 +820,7 @@ mission11Reset:addChildKeyword( }, nil, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission11, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, 1) player:addItemEx(Game.createItem(12785, 1), true, CONST_SLOT_WHEREEVER) end ) @@ -838,9 +838,9 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { text = "This brass shield is actually brand-new. It's never been used! I hope it will serve you well. Take a small break, regenerate your health, and then talk to me again for your final mission!", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 4 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 4 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission11, 5) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, 5) player:addItemEx(Game.createItem(3411, 1), true, CONST_SLOT_WHEREEVER) end) @@ -849,7 +849,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { npcHandler = npcHandler, text = "Seriously, don't reject that offer. Just take that shield, it's free. If you don't like it, you can sell it - I don't need it anymore. I promise it's really brand-new. Want it?", }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 4 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 4 end) -- Mission 12: Accept @@ -864,12 +864,12 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "|PLAYERNAME|, take the items and go claim your victory. I know you will do us proud. Good luck!", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 5 and player:getStorageValue(Storage.TheRookieGuard.Mission12) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 5 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) == -1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission12, 1) - player:setStorageValue(Storage.TheRookieGuard.AcademyDoor, 1) - player:setStorageValue(Storage.TheRookieGuard.OrcFortressChests, 0) - player:setStorageValue(Storage.TheRookieGuard.KraknaknorkChests, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.AcademyDoor, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.OrcFortressChests, 0) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.KraknaknorkChests, 0) player:addMapMark({ x = 31976, y = 32156, z = 7 }, MAPMARK_SKULL, "Orc Fortress") end) @@ -879,7 +879,7 @@ keywordHandler:addKeyword({ "no" }, StdModule.say, { text = "Rest for a bit, but don't take too much time to come back.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission11) == 5 and player:getStorageValue(Storage.TheRookieGuard.Mission12) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) == 5 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) == -1 end) -- Mission 12: Finish - Confirm/Decline @@ -892,11 +892,11 @@ keywordHandler:addKeyword({ "yes" }, StdModule.say, { "Rookgaard will miss you, but the whole world of Tibia is open to you now. Take care, |PLAYERNAME|. It's good to know you.", }, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Mission12) == 14 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) == 14 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Mission12, 15) - player:setStorageValue(Storage.TheRookieGuard.Questline, 2) - player:setStorageValue(Storage.TheRookieGuard.AcademyDoor, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 15) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.AcademyDoor, -1) end) keywordHandler:addAliasKeyword({ "no" }) @@ -906,9 +906,9 @@ keywordHandler:addKeyword({ "continue" }, StdModule.say, { text = "Alright. Talk to me again to continue with your mission, but heed my words!", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Questline) == 1 and player:getLevel() == 8 and player:getStorageValue(Storage.TheRookieGuard.Level8Warning) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline) == 1 and player:getLevel() == 8 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Level8Warning) == -1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Level8Warning, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Level8Warning, 1) end) -- Missions: Confirm - Delete (Level 8) @@ -917,21 +917,21 @@ keywordHandler:addKeyword({ "delete" }, StdModule.say, { text = "Alright.", ungreet = true, }, function(player) - return player:getStorageValue(Storage.TheRookieGuard.Questline) == 1 and player:getLevel() == 8 and player:getStorageValue(Storage.TheRookieGuard.Level8Warning) == -1 + return player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline) == 1 and player:getLevel() == 8 and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Level8Warning) == -1 end, function(player) - player:setStorageValue(Storage.TheRookieGuard.Questline, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission01, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission02, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission03, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission04, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission05, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission06, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission07, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission08, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission09, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission10, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission11, -1) - player:setStorageValue(Storage.TheRookieGuard.Mission12, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Questline, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission01, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission08, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, -1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, -1) end) npcHandler:setCallback(CALLBACK_GREET, greetCallback) diff --git a/data-otservbr-global/npc/xelvar.lua b/data-otservbr-global/npc/xelvar.lua index 9180dee6f3b..c8b8be4492b 100644 --- a/data-otservbr-global/npc/xelvar.lua +++ b/data-otservbr-global/npc/xelvar.lua @@ -58,7 +58,7 @@ local function creatureSayCallback(npc, creature, type, message) end if MsgContains(message, "adventures") or MsgContains(message, "join") then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) < 1 then npcHandler:say({ "I am glad to hear that. In the spirit of our own foreign legion we suggested the gnomes might hire heroes like you to build some kind of troop. They gave me that strange crystal to allow people passage to their realm. ...", "I hereby grant you permission to use the basic gnomish teleporters. I also give you four gnomish teleport crystals. One will be used up each time you use the teleporter. ...", @@ -68,7 +68,7 @@ local function creatureSayCallback(npc, creature, type, message) "Good luck to you and don't embarrass your race down there! Keep in mind that you are a representative of the big people.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 1) player:addItem(16167, 4) --npcHandler:say("Right now I am sort of {recruiting} people.", npc, creature) @@ -113,7 +113,7 @@ local function creatureSayCallback(npc, creature, type, message) "Good luck to you and don't embarrass your race down there! Keep in mind that you are a representative of the big people.", }, npc, creature) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 1) player:addItem(16167, 4) npcHandler:setTopic(playerId, 0) end @@ -121,6 +121,8 @@ local function creatureSayCallback(npc, creature, type, message) return true end +npcHandler:setMessage(MESSAGE_GREET, "Greetings. Are you interested in adventures?") + npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, creatureSayCallback) npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) diff --git a/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua b/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua index d3d8fb36bc4..8b87cb8a336 100644 --- a/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua +++ b/data-otservbr-global/scripts/creaturescripts/customs/freequests.lua @@ -1,20 +1,20 @@ local stage = configManager.getNumber(configKeys.FREE_QUEST_STAGE) local questTable = { - { storage = Storage.BigfootBurden.QuestLine, storageValue = 2 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 4 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 7 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 9 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 12 }, - { storage = Storage.BigfootBurden.Shooting, storageValue = 5 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 16 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 20 }, - { storage = Storage.BigfootBurden.QuestLine, storageValue = 23 }, - { storage = Storage.BigfootBurden.QuestLineComplete, storageValue = 2 }, - { storage = Storage.BigfootBurden.Rank, storageValue = 1440 }, - { storage = Storage.BigfootBurden.Warzone1Access, storageValue = 2 }, - { storage = Storage.BigfootBurden.Warzone2Access, storageValue = 2 }, - { storage = Storage.BigfootBurden.Warzone3Access, storageValue = 2 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 2 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 4 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 7 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 9 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 12 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.Shooting, storageValue = 5 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 16 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 20 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLine, storageValue = 23 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.QuestLineComplete, storageValue = 2 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.Rank, storageValue = 1440 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.Warzone1Access, storageValue = 2 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.Warzone2Access, storageValue = 2 }, + { storage = Storage.Quest.U9_60.BigfootsBurden.Warzone3Access, storageValue = 2 }, { storage = Storage.DangerousDepths.Questline, storageValue = 10 }, { storage = Storage.DangerousDepths.Access.LavaPumpWarzoneVI, storageValue = 10 }, { storage = Storage.DangerousDepths.Access.LavaPumpWarzoneV, storageValue = 10 }, @@ -356,7 +356,7 @@ local questTable = { { storage = Storage.Quest.U13_10.CradleOfMonsters.Access.Monster, storageValue = 1 }, { storage = Storage.Quest.U13_10.CradleOfMonsters.Access.MutatedAbomination, storageValue = 1 }, { storage = Storage.Quest.U8_54.TheNewFrontier.SnakeHeadTeleport, storageValue = 1 }, - { storage = Storage.LiquidBlackQuest.Visitor, storageValue = 5 }, + { storage = Storage.Quest.U9_4.LiquidBlackQuest.Visitor, storageValue = 5 }, { storage = Storage.Quest.U8_4.BloodBrothers.VengothAccess, storageValue = 1 }, -- Assassin Outfit quests diff --git a/data-otservbr-global/scripts/globalevents/others/check_mount.lua b/data-otservbr-global/scripts/globalevents/others/check_mount.lua index 85c4ece1c82..497ec1a0415 100644 --- a/data-otservbr-global/scripts/globalevents/others/check_mount.lua +++ b/data-otservbr-global/scripts/globalevents/others/check_mount.lua @@ -10,7 +10,7 @@ function rentedMounts.onThink(interval) local player, outfit for i = 1, #players do player = players[i] - if player:getStorageValue(Storage.RentedHorseTimer) < 1 or player:getStorageValue(Storage.RentedHorseTimer) >= os.time() then + if player:getStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer) < 1 or player:getStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer) >= os.time() then break end @@ -24,7 +24,7 @@ function rentedMounts.onThink(interval) player:removeMount(mountIds[m]) end - player:setStorageValue(Storage.RentedHorseTimer, -1) + player:setStorageValue(Storage.Quest.U9_1.HorseStationWorldChange.Timer, -1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your contract with your horse expired and it returned back to the horse station.") end return true diff --git a/data-otservbr-global/scripts/lib/register_actions.lua b/data-otservbr-global/scripts/lib/register_actions.lua index 6d5a05a4c98..6df6b96473a 100644 --- a/data-otservbr-global/scripts/lib/register_actions.lua +++ b/data-otservbr-global/scripts/lib/register_actions.lua @@ -594,7 +594,7 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) -- The Rookie Guard Quest - Mission 09: Rock 'n Troll -- Path: data\scripts\actions\quests\the_rookie_guard\mission09_rock_troll.lua -- Damage tunnel pillars - if player:getStorageValue(Storage.TheRookieGuard.Mission09) ~= -1 and target.itemid == 1600 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) ~= -1 and target.itemid == 1600 then return onUsePickAtTunnelPillar(player, item, fromPosition, target, toPosition) end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_beer.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_beer.lua index 92f3f8245fb..48d013f5b26 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_beer.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_beer.lua @@ -1,7 +1,7 @@ local bigfootBeer = Action() function bigfootBeer.onUse(player, item, fromPosition, target, toPosition, isHotkey) - if player:getStorageValue(Storage.BigfootBurden.NeedsBeer) == 1 then - player:setStorageValue(Storage.BigfootBurden.NeedsBeer, 0) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.NeedsBeer) == 1 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.NeedsBeer, 0) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your mind feels refreshed!") end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_crystal.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_crystal.lua index dec5e560790..6fdab825d00 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_crystal.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_crystal.lua @@ -11,13 +11,13 @@ function bigfootCrystal.onUse(player, item, fromPosition, target, toPosition, is return false end - local repairedCount = player:getStorageValue(Storage.BigfootBurden.RepairedCrystalCount) - if repairedCount == 5 or player:getStorageValue(Storage.BigfootBurden.MissionCrystalKeeper) ~= 1 then + local repairedCount = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount) + if repairedCount == 5 or player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionCrystalKeeper) ~= 1 then return false end if target.itemid == 15796 or target.itemid == 15712 then - player:setStorageValue(Storage.BigfootBurden.RepairedCrystalCount, repairedCount + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.RepairedCrystalCount, repairedCount + 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have repaired a damaged crystal!") addEvent(returnCrystal, math.random(50, 140) * 1000, toPosition, target.itemid) target:transform(15800) diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_extractor.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_extractor.lua index 2112f77694d..48b1d81e3e6 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_extractor.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_extractor.lua @@ -1,7 +1,7 @@ local bigfootExtractor = Action() function bigfootExtractor.onUse(player, item, fromPosition, target, toPosition, isHotkey) - local extractedCount = player:getStorageValue(Storage.BigfootBurden.ExtractedCount) - if extractedCount == 7 or player:getStorageValue(Storage.BigfootBurden.MissionRaidersOfTheLostSpark) ~= 1 then + local extractedCount = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExtractedCount) + if extractedCount == 7 or player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionRaidersOfTheLostSpark) ~= 1 then return false end @@ -14,7 +14,7 @@ function bigfootExtractor.onUse(player, item, fromPosition, target, toPosition, return false end - player:setStorageValue(Storage.BigfootBurden.ExtractedCount, math.max(0, extractedCount) + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExtractedCount, math.max(0, extractedCount) + 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You gathered a spark.") target:transform(16195) toPosition:sendMagicEffect(CONST_ME_ENERGYHIT) diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_matchmaker.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_matchmaker.lua index 02ddeea3343..4c185a2c709 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_matchmaker.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_matchmaker.lua @@ -4,16 +4,16 @@ function bigfootMatch.onUse(player, item, fromPosition, target, toPosition, isHo return false end - if player:getStorageValue(Storage.BigfootBurden.MatchmakerStatus) == 1 or player:getStorageValue(Storage.BigfootBurden.MissionMatchmaker) ~= 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerStatus) == 1 or player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMatchmaker) ~= 1 then return false end - if player:getStorageValue(Storage.BigfootBurden.MatchmakerIdNeeded) ~= target.itemid then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerIdNeeded) ~= target.itemid then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "This is not the crystal you're looking for!") return true end - player:setStorageValue(Storage.BigfootBurden.MatchmakerStatus, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MatchmakerStatus, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! The crystals seem to have fallen in love and your mission is done!") toPosition:sendMagicEffect(CONST_ME_HEARTS) item:remove() diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_mouthpiece.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_mouthpiece.lua index ce0038b7355..030de7833e9 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_mouthpiece.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_mouthpiece.lua @@ -1,8 +1,8 @@ local bigfootPiece = Action() function bigfootPiece.onUse(player, item, fromPosition, target, toPosition, isHotkey) - if player:getStorageValue(Storage.BigfootBurden.Mouthpiece) ~= os.time() then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Mouthpiece) ~= os.time() then player:addItem(20057, 1) - player:setStorageValue(Storage.BigfootBurden.Mouthpiece, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Mouthpiece, os.time() + configManager.getNumber(configKeys.BOSS_DEFAULT_TIME_TO_FIGHT_AGAIN)) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "It is empty.") end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_music.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_music.lua index e972b1986df..a850cb34215 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_music.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_music.lua @@ -1,11 +1,11 @@ local cToneStorages = { - Storage.BigfootBurden.MelodyTone1, - Storage.BigfootBurden.MelodyTone2, - Storage.BigfootBurden.MelodyTone3, - Storage.BigfootBurden.MelodyTone4, - Storage.BigfootBurden.MelodyTone5, - Storage.BigfootBurden.MelodyTone6, - Storage.BigfootBurden.MelodyTone7, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone1, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone2, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone3, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone4, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone5, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone6, + Storage.Quest.U9_60.BigfootsBurden.MelodyTone7, } local Crystals = { @@ -17,20 +17,20 @@ local Crystals = { local bigfootMusic = Action() function bigfootMusic.onUse(player, item, fromPosition, target, toPosition, isHotkey) - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 21 then - local value = player:getStorageValue(Storage.BigfootBurden.MelodyStatus) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 21 then + local value = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MelodyStatus) if Position(Crystals[player:getStorageValue(cToneStorages[value])]) == item:getPosition() then - player:setStorageValue(Storage.BigfootBurden.MelodyStatus, value + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MelodyStatus, value + 1) if value + 1 == 8 then toPosition:sendMagicEffect(CONST_ME_HEARTS) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "That was the correct note! Now you know your soul melody!") - player:setStorageValue(Storage.BigfootBurden.QuestLine, 22) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 22) else toPosition:sendMagicEffect(CONST_ME_SOUND_GREEN) player:getPosition():sendMagicEffect(CONST_ME_FIREWORK_YELLOW) end else - player:setStorageValue(Storage.BigfootBurden.MelodyStatus, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MelodyStatus, 1) toPosition:sendMagicEffect(CONST_ME_SOUND_RED) end end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_pig.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_pig.lua index 7d1a8dc9333..2133cdb1903 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_pig.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_pig.lua @@ -4,8 +4,8 @@ function bigfootPig.onUse(player, item, fromPosition, target, toPosition, isHotk return false end - local mushroomCount = player:getStorageValue(Storage.BigfootBurden.MushroomCount) - if player:getStorageValue(Storage.BigfootBurden.MissionMushroomDigger) ~= 1 then + local mushroomCount = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomCount) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionMushroomDigger) ~= 1 then return false end @@ -14,7 +14,7 @@ function bigfootPig.onUse(player, item, fromPosition, target, toPosition, isHotk return true end - player:setStorageValue(Storage.BigfootBurden.MushroomCount, mushroomCount + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.MushroomCount, mushroomCount + 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The little pig happily eats the truffles.") target:transform(15701) toPosition:sendMagicEffect(CONST_ME_GROUNDSHAKER) diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_repair.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_repair.lua index 07ae2242d84..348e08e9f22 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_repair.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_repair.lua @@ -8,16 +8,16 @@ function bigfootRepair.onUse(player, item, fromPosition, target, toPosition, isH return false end - if player:getStorageValue(Storage.BigfootBurden.MissionTinkersBell) ~= 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionTinkersBell) ~= 1 then return false end - if player:getStorageValue(Storage.BigfootBurden.GolemCount) >= 4 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount) >= 4 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have returned enough golems for now. Give the gnomes some time for their repairs. Report back now.") return true end - player:setStorageValue(Storage.BigfootBurden.GolemCount, player:getStorageValue(Storage.BigfootBurden.GolemCount) + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GolemCount) + 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The golem has been returned to the gnomish workshop.") target:remove() player:getPosition():sendMagicEffect(CONST_ME_ENERGYHIT) diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_rewards.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_rewards.lua index 0a9f9997789..54c53b65ee9 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_rewards.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_rewards.lua @@ -1,6 +1,6 @@ local rewards = { [3148] = { - storage = Storage.BigfootBurden.Warzone1Reward, + storage = Storage.Quest.U9_60.BigfootsBurden.Warzone1Reward, bossName = "Deathstrike", items = { { rand = true, itemId = { 16102, 16233, 16234, 16235 } }, @@ -13,7 +13,7 @@ local rewards = { achievement = { "Final Strike", "Death on Strike" }, }, [3149] = { - storage = Storage.BigfootBurden.Warzone2Reward, + storage = Storage.Quest.U9_60.BigfootsBurden.Warzone2Reward, bossName = "Gnomevil", items = { { rand = true, itemId = { 16237, 16238, 16239 } }, @@ -27,7 +27,7 @@ local rewards = { achievement = { "Gnomebane's Bane", "Fall of the Fallen" }, }, [3150] = { - storage = Storage.BigfootBurden.Warzone3Reward, + storage = Storage.Quest.U9_60.BigfootsBurden.Warzone3Reward, bossName = "Abyssador", items = { { rand = true, itemId = { 16229, 16230, 16231 } }, @@ -44,8 +44,8 @@ local rewards = { local bigfootRewards = Action() function bigfootRewards.onUse(player, item, fromPosition, target, toPosition, isHotkey) if item.uid == 3147 then - if player:getStorageValue(Storage.BigfootBurden.WarzoneStatus) == 4 then - player:setStorageValue(Storage.BigfootBurden.WarzoneStatus, 5) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus) == 4 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus, 5) player:addItem(3020, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found some golden fruits.") else diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_shooting.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_shooting.lua index f8c5cb50ab1..26b33e85dad 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_shooting.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_shooting.lua @@ -1,22 +1,22 @@ local bigfootShooting = Action() function bigfootShooting.onUse(player, item, fromPosition, target, toPosition, isHotkey) local playerPos = player:getPosition() - if player:getStorageValue(Storage.BigfootBurden.Shooting) < 5 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting) < 5 then local pos = Position(playerPos.x, playerPos.y - 5, 10) local tile = Tile(pos) if tile:getItemById(15710) then - player:setStorageValue(Storage.BigfootBurden.Shooting, player:getStorageValue(Storage.BigfootBurden.Shooting) + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting) + 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Hit!") tile:getItemById(15710):remove() pos:sendMagicEffect(CONST_ME_FIREATTACK) for i = 2, 4 do Position(playerPos.x, playerPos.y - i, 10):sendMagicEffect(CONST_ME_TELEPORT) end - if player:getStorageValue(Storage.BigfootBurden.Shooting) >= 5 then - player:setStorageValue(Storage.BigfootBurden.QuestLine, 14) + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting) >= 5 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 14) end elseif tile:getItemById(15711) then - player:setStorageValue(Storage.BigfootBurden.Shooting, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Shooting, 0) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've hit the wrong target and have to start all over!") tile:getItemById(15711):remove() pos:sendMagicEffect(CONST_ME_FIREATTACK) diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_spores.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_spores.lua index 6f7eded510f..786afd2d3d6 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_spores.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_spores.lua @@ -12,20 +12,20 @@ function bigfootSpores.onUse(player, item, fromPosition, target, toPosition, isH return false end - local sporeCount = player:getStorageValue(Storage.BigfootBurden.SporeCount) - if sporeCount == 4 or player:getStorageValue(Storage.BigfootBurden.MissionSporeGathering) ~= 1 then + local sporeCount = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeCount) + if sporeCount == 4 or player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionSporeGathering) ~= 1 then return false end if target.itemid ~= spores then - player:setStorageValue(Storage.BigfootBurden.SporeCount, 0) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeCount, 0) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have gathered the wrong spores. You ruined your collection.") item:transform(15817) toPosition:sendMagicEffect(CONST_ME_POFF) return true end - player:setStorageValue(Storage.BigfootBurden.SporeCount, sporeCount + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.SporeCount, sporeCount + 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have gathered the correct spores.") item:transform(item.itemid + 1) toPosition:sendMagicEffect(CONST_ME_GREEN_RINGS) diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_stone.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_stone.lua index 2c564a938bb..739aa9ffd89 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/actions_stone.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/actions_stone.lua @@ -1,6 +1,6 @@ local bigfootStone = Action() function bigfootStone.onUse(player, item, fromPosition, target, toPosition, isHotkey) - if player:getStorageValue(Storage.BigfootBurden.GrindstoneStatus) == 1 or player:getStorageValue(Storage.BigfootBurden.MissionGrindstoneHunt) ~= 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneStatus) == 1 or player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionGrindstoneHunt) ~= 1 then return false end @@ -12,7 +12,7 @@ function bigfootStone.onUse(player, item, fromPosition, target, toPosition, isHo return true end - player:setStorageValue(Storage.BigfootBurden.GrindstoneStatus, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GrindstoneStatus, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your skill allowed you to grab a whetstone before the stone sinks into lava.") player:addItem(15826, 1) return true diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_bosses_warzone.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_bosses_warzone.lua index 0f95bf236f2..2f4663714fa 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_bosses_warzone.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_bosses_warzone.lua @@ -1,7 +1,7 @@ local bosses = { - ["deathstrike"] = { status = 2, storage = Storage.BigfootBurden.Warzone1Reward }, - ["gnomevil"] = { status = 3, storage = Storage.BigfootBurden.Warzone2Reward }, - ["abyssador"] = { status = 4, storage = Storage.BigfootBurden.Warzone3Reward }, + ["deathstrike"] = { status = 2, storage = Storage.Quest.U9_60.BigfootsBurden.Warzone1Reward }, + ["gnomevil"] = { status = 3, storage = Storage.Quest.U9_60.BigfootsBurden.Warzone2Reward }, + ["abyssador"] = { status = 4, storage = Storage.Quest.U9_60.BigfootsBurden.Warzone3Reward }, } -- This will set the status of warzone (killing 1, 2 and 3 wz bosses in order you can open the chest and get "some golden fruits") and the reward chest storages @@ -13,14 +13,14 @@ function bossesWarzone.onDeath(creature) end onDeathForDamagingPlayers(creature, function(creature, player) - if (player:getStorageValue(Storage.BigfootBurden.WarzoneStatus) + 1) == bossConfig.status then - player:setStorageValue(Storage.BigfootBurden.WarzoneStatus, bossConfig.status) + if (player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus) + 1) == bossConfig.status then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus, bossConfig.status) if bossConfig.status == 4 then - player:setStorageValue(Storage.BigfootBurden.DoorGoldenFruits, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.DoorGoldenFruits, 1) end end player:setStorageValue(bossConfig.storage, 1) - player:setStorageValue(Storage.BigfootBurden.BossKills, player:getStorageValue(Storage.BigfootBurden.BossKills) + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.BossKills, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.BossKills) + 1) end) return true end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_versperoth_kill.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_versperoth_kill.lua index c736981bd4d..89e72454269 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_versperoth_kill.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_versperoth_kill.lua @@ -18,8 +18,8 @@ end local versperothKill = CreatureEvent("VersperothDeath") function versperothKill.onDeath(creature) local config = warzoneConfig.findByName("Abyssador") - Game.setStorageValue(GlobalStorage.BigfootBurden.Versperoth.Battle, 2) - addEvent(Game.setStorageValue, 30 * 60 * 1000, GlobalStorage.BigfootBurden.Versperoth.Battle, 0) + Game.setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Battle, 2) + addEvent(Game.setStorageValue, 30 * 60 * 1000, Storage.Quest.U9_60.BigfootsBurden.Versperoth.Battle, 0) blood = Tile(teleportPosition):getItemById(2886) if blood then diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_wiggler_kill.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_wiggler_kill.lua index 41e41ca1e66..e875ca955f4 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_wiggler_kill.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/creaturescripts_wiggler_kill.lua @@ -1,9 +1,9 @@ local wigglerKill = CreatureEvent("WigglerDeath") function wigglerKill.onDeath(creature, _corpse, _lastHitKiller, mostDamageKiller) onDeathForParty(creature, mostDamageKiller, function(creature, player) - local value = player:getStorageValue(Storage.BigfootBurden.ExterminatedCount) - if value < 10 and player:getStorageValue(Storage.BigfootBurden.MissionExterminators) == 1 then - player:setStorageValue(Storage.BigfootBurden.ExterminatedCount, value + 1) + local value = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount) + if value < 10 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.MissionExterminators) == 1 then + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.ExterminatedCount, value + 1) end end) return true diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_gnomebase_teleport.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_gnomebase_teleport.lua index 5878882c7ec..7ee7b664377 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_gnomebase_teleport.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_gnomebase_teleport.lua @@ -43,21 +43,21 @@ function gnomebaseTeleport.onStepIn(creature, item, position, fromPosition) if teleports[c].teleportsPosition then for d = 1, #teleports[c].teleportsPosition do if player:getPosition() == Position(teleports[c].teleportsPosition[d]) then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) < 1 then fromPosition:sendMagicEffect(CONST_ME_POFF) player:teleportTo(fromPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have no idea on how to use this device. Xelvar in Kazordoon might tell you more about it.") return false end - if player:getPosition() ~= Position(32988, 31862, 9) and player:getStorageValue(Storage.BigfootBurden.QuestLine) < teleports[c].storageValue then + if player:getPosition() ~= Position(32988, 31862, 9) and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) < teleports[c].storageValue then position:sendMagicEffect(CONST_ME_TELEPORT) player:teleportTo(fromPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your rank among the Gnomes is too low.") return false end - if player:getPosition() == Position(32988, 31862, 9) and player:getStorageValue(Storage.BigfootBurden.Rank) < teleports[c].storageValue then + if player:getPosition() == Position(32988, 31862, 9) and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < teleports[c].storageValue then position:sendMagicEffect(CONST_ME_TELEPORT) player:teleportTo(fromPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your rank among the Gnomes is too low.") @@ -76,21 +76,21 @@ function gnomebaseTeleport.onStepIn(creature, item, position, fromPosition) end end elseif player:getPosition() == Position(teleports[c].teleportPosition) then - if player:getStorageValue(Storage.BigfootBurden.QuestLine) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) < 1 then fromPosition:sendMagicEffect(CONST_ME_POFF) player:teleportTo(fromPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have no idea on how to use this device. Xelvar in Kazordoon might tell you more about it.") return false end - if teleports[c].storageValue < 100 and player:getStorageValue(Storage.BigfootBurden.QuestLine) < teleports[c].storageValue then + if teleports[c].storageValue < 100 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) < teleports[c].storageValue then position:sendMagicEffect(CONST_ME_TELEPORT) player:teleportTo(fromPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your rank among the Gnomes is too low.") return false end - if teleports[c].storageValue >= 100 and player:getStorageValue(Storage.BigfootBurden.Rank) < teleports[c].storageValue then + if teleports[c].storageValue >= 100 and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < teleports[c].storageValue then position:sendMagicEffect(CONST_ME_TELEPORT) player:teleportTo(fromPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your rank among the Gnomes is too low.") diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_ear.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_ear.lua index 41770076e5b..eb83a8e3a6d 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_ear.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_ear.lua @@ -15,7 +15,7 @@ local function sendTextMessages(cid, index) return true end - if index ~= player:getStorageValue(Storage.BigfootBurden.GnomedixMsg) then + if index ~= player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GnomedixMsg) then return false end @@ -27,10 +27,10 @@ local function sendTextMessages(cid, index) if messages[index][2] then player:getPosition():sendMagicEffect(messages[index][2]) end - player:setStorageValue(Storage.BigfootBurden.GnomedixMsg, player:getStorageValue(Storage.BigfootBurden.GnomedixMsg) + 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GnomedixMsg, player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GnomedixMsg) + 1) if index == 8 then Game.createMonster("Strange Slime", Position(32767, 31772, 10)) - player:setStorageValue(Storage.BigfootBurden.QuestLine, 11) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 11) end end @@ -41,13 +41,13 @@ function taskEar.onStepIn(creature, item, position, fromPosition) return true end - if player:getStorageValue(Storage.BigfootBurden.QuestLine) ~= 10 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) ~= 10 then return true end - player:setStorageValue(Storage.BigfootBurden.GnomedixMsg, 1) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.GnomedixMsg, 1) position:sendMagicEffect(CONST_ME_LOSEENERGY) - for i = player:getStorageValue(Storage.BigfootBurden.GnomedixMsg), #messages do + for i = player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.GnomedixMsg), #messages do addEvent(sendTextMessages, (i - 1) * 4000, player.uid, i) end return true diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_endurance.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_endurance.lua index 5f06e2f6b32..2c1dc7383bc 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_endurance.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_endurance.lua @@ -26,11 +26,11 @@ function taskEndurance.onStepIn(creature, item, position, fromPosition) player:teleportTo(fromPosition) end elseif item.actionid == 7817 then --finish of the test - player:setStorageValue(Storage.BigfootBurden.QuestLine, 18) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 18) player:teleportTo(Position(32760, 31811, 10)) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) elseif item.actionid == 7818 then -- entrance to the test - if player:getStorageValue(Storage.BigfootBurden.QuestLine) == 17 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 17 then player:teleportTo(Position(32759, 31812, 11)) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) else diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_shooting.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_shooting.lua index f8f65c6df79..d685813fcb6 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_shooting.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_shooting.lua @@ -34,7 +34,7 @@ function taskShooting.onStepIn(creature, item, position, fromPosition) return true end - if player:getStorageValue(Storage.BigfootBurden.QuestLine) ~= 13 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) ~= 13 then player:teleportTo(fromPosition) return true end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_x_ray.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_x_ray.lua index c16d3e81df9..407b8fd827e 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_x_ray.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_task_x_ray.lua @@ -33,11 +33,11 @@ function taskXRay.onStepIn(creature, item, position, fromPosition) end for a = 1, #xRay do - if player:getPosition() == Position(xRay[a]) and player:getStorageValue(Storage.BigfootBurden.QuestLine) == 8 then + if player:getPosition() == Position(xRay[a]) and player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) == 8 then player:addCondition(condition) player:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) if a >= 16 then - player:setStorageValue(Storage.BigfootBurden.QuestLine, 10) + player:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 10) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have been succesfully g-rayed. Now let Doctor Gnomedix inspect your ears!") end end diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_versperoth_spawn.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_versperoth_spawn.lua index 78a9a5d9da9..c86aea3b4c1 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_versperoth_spawn.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_versperoth_spawn.lua @@ -9,7 +9,7 @@ local function removeMinion(mid) end local function executeVersperothBattle(mid) - if Game.getStorageValue(GlobalStorage.BigfootBurden.Versperoth.Battle) ~= 1 then + if Game.getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Battle) ~= 1 then return false end @@ -20,7 +20,7 @@ local function executeVersperothBattle(mid) else end - Game.setStorageValue(GlobalStorage.BigfootBurden.Versperoth.Health, monster:getHealth()) + Game.setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Health, monster:getHealth()) monster:remove() local blood = Tile(versperothPosition):getItemById(2889) if blood then @@ -53,7 +53,7 @@ local function executeVersperothBattle(mid) holee:remove() end versperothPosition:sendMagicEffect(CONST_ME_GROUNDSHAKER) - monster:setHealth(Game.getStorageValue(GlobalStorage.BigfootBurden.Versperoth.Health)) + monster:setHealth(Game.getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Health)) addEvent(executeVersperothBattle, 20 * 1000, monster:getId()) end end @@ -66,13 +66,13 @@ function versperothSpawn.onStepIn(creature, item, position, fromPosition) return true end - if Game.getStorageValue(GlobalStorage.BigfootBurden.Versperoth.Battle) >= 1 then + if Game.getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Battle) >= 1 then player:say("Versperoth has already been defeated in the last 30 minutes.", TALKTYPE_MONSTER_SAY) return true end player:teleportTo(Position(33072, 31877, 12)) - Game.setStorageValue(GlobalStorage.BigfootBurden.Versperoth.Battle, 1) - Game.setStorageValue(GlobalStorage.BigfootBurden.Versperoth.Health, 100000) + Game.setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Battle, 1) + Game.setStorageValue(Storage.Quest.U9_60.BigfootsBurden.Versperoth.Health, 100000) executeVersperothBattle() item:remove() return true diff --git a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_warzone_teleport.lua b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_warzone_teleport.lua index f42f00a8a15..3573a520c7f 100644 --- a/data-otservbr-global/scripts/quests/bigfoot_burden/movements_warzone_teleport.lua +++ b/data-otservbr-global/scripts/quests/bigfoot_burden/movements_warzone_teleport.lua @@ -1,7 +1,7 @@ local teleports = { - { teleportPosition = { x = 33013, y = 31880, z = 9 }, teleportDestination = Position(32996, 31922, 10), storage = Storage.BigfootBurden.Warzone1Access, value = 1 }, - { teleportPosition = { x = 33019, y = 31886, z = 9 }, teleportDestination = Position(33011, 31943, 11), storage = Storage.BigfootBurden.Warzone2Access, value = 2 }, - { teleportPosition = { x = 33022, y = 31902, z = 9 }, teleportDestination = Position(32989, 31909, 12), storage = Storage.BigfootBurden.Warzone3Access, value = 3 }, + { teleportPosition = { x = 33013, y = 31880, z = 9 }, teleportDestination = Position(32996, 31922, 10), storage = Storage.Quest.U9_60.BigfootsBurden.Warzone1Access, value = 1 }, + { teleportPosition = { x = 33019, y = 31886, z = 9 }, teleportDestination = Position(33011, 31943, 11), storage = Storage.Quest.U9_60.BigfootsBurden.Warzone2Access, value = 2 }, + { teleportPosition = { x = 33022, y = 31902, z = 9 }, teleportDestination = Position(32989, 31909, 12), storage = Storage.Quest.U9_60.BigfootsBurden.Warzone3Access, value = 3 }, } local warzoneTeleport = MoveEvent() @@ -13,14 +13,14 @@ function warzoneTeleport.onStepIn(creature, item, position, fromPosition) for a = 1, #teleports do if player:getPosition() == Position(teleports[a].teleportPosition) then - if player:getStorageValue(Storage.BigfootBurden.Rank) < 1440 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) < 1440 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are not permitted to enter.") player:teleportTo(fromPosition) position:sendMagicEffect(CONST_ME_TELEPORT) return true end - if player:getStorageValue(Storage.BigfootBurden.WarzoneStatus) < 1 then + if player:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.WarzoneStatus) < 1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You finally have enough renown among the gnomes, ask Gnomission for a mission to fight on the warzones.") player:teleportTo(fromPosition) position:sendMagicEffect(CONST_ME_TELEPORT) diff --git a/data-otservbr-global/scripts/quests/liquid_black/actions_chairteleport.lua b/data-otservbr-global/scripts/quests/liquid_black/actions_chairteleport.lua index 428af0c9f0a..d1e4fdc54b5 100644 --- a/data-otservbr-global/scripts/quests/liquid_black/actions_chairteleport.lua +++ b/data-otservbr-global/scripts/quests/liquid_black/actions_chairteleport.lua @@ -3,10 +3,10 @@ local teleportplayer = { x = 33269, y = 31832, z = 1 } local chairteleport = Action() function chairteleport.onUse(player, item, frompos, item2, topos) - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == 2 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 2 then player:teleportTo(teleportplayer) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Teleport.") - player:setStorageValue(Storage.LiquidBlackQuest.Visitor, 3) + player:setStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor, 3) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Empty.") end diff --git a/data-otservbr-global/scripts/quests/liquid_black/actions_notescoordinates.lua b/data-otservbr-global/scripts/quests/liquid_black/actions_notescoordinates.lua index c9690fa153f..4dedcdcda5f 100644 --- a/data-otservbr-global/scripts/quests/liquid_black/actions_notescoordinates.lua +++ b/data-otservbr-global/scripts/quests/liquid_black/actions_notescoordinates.lua @@ -1,10 +1,10 @@ local notescoordinates = Action() function notescoordinates.onUse(player, item, frompos, item2, topos) - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) == -1 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) == 1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found notes and coordinates.") player:addItem(14176, 1) - player:setStorageValue(Storage.LiquidBlackQuest.Visitor, 2) + player:setStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor, 2) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Empty.") end diff --git a/data-otservbr-global/scripts/quests/liquid_black/movements_ladder.lua b/data-otservbr-global/scripts/quests/liquid_black/movements_ladder.lua index 9e61a4e0468..7c54eab501d 100644 --- a/data-otservbr-global/scripts/quests/liquid_black/movements_ladder.lua +++ b/data-otservbr-global/scripts/quests/liquid_black/movements_ladder.lua @@ -6,7 +6,7 @@ function ladder.onStepIn(creature, item, toPosition, fromPosition) return true end - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) >= 4 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) >= 4 then player:getPosition():sendMagicEffect(CONST_ME_WATERSPLASH) else player:teleportTo(fromPosition, true) diff --git a/data-otservbr-global/scripts/quests/liquid_black/movements_quick_access.lua b/data-otservbr-global/scripts/quests/liquid_black/movements_quick_access.lua index 1fc0ba94cf2..7dd0b509b0e 100644 --- a/data-otservbr-global/scripts/quests/liquid_black/movements_quick_access.lua +++ b/data-otservbr-global/scripts/quests/liquid_black/movements_quick_access.lua @@ -8,7 +8,7 @@ function quickAccess.onStepIn(creature, item, toPosition, fromPosition) return true end - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) >= 5 then + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) >= 4 then player:teleportTo(enterPosition) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) else diff --git a/data-otservbr-global/scripts/quests/liquid_black/movements_shortcut.lua b/data-otservbr-global/scripts/quests/liquid_black/movements_shortcut.lua index 68f23aa5c89..6b1a2314810 100644 --- a/data-otservbr-global/scripts/quests/liquid_black/movements_shortcut.lua +++ b/data-otservbr-global/scripts/quests/liquid_black/movements_shortcut.lua @@ -8,8 +8,8 @@ function shortcut.onStepIn(creature, item, toPosition, fromPosition) return true end - if player:getStorageValue(Storage.LiquidBlackQuest.Visitor) >= 4 then - player:setStorageValue(Storage.LiquidBlackQuest.Visitor, 5) + if player:getStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor) >= 4 then + player:setStorageValue(Storage.Quest.U9_4.LiquidBlackQuest.Visitor, 5) player:teleportTo(enterPosition) player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) else diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission02_defence.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission02_defence.lua index 3e26e05e295..733121e1a83 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission02_defence.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission02_defence.lua @@ -42,7 +42,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission02) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) -- Skip if not was started or finished if missionState == -1 or missionState >= 4 then return true @@ -51,7 +51,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) -- Check if the tile has bound a catapult(s) local hasUsedCatapult = missionTile.catapults ~= nil or false if hasUsedCatapult then - local catapultsState = player:getStorageValue(Storage.TheRookieGuard.Catapults) + local catapultsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Catapults) for i = 1, #missionTile.catapults do -- Check if the catapult was used hasUsedCatapult = testFlag(catapultsState, missionTile.catapults[i]) @@ -83,18 +83,18 @@ missionGuide:register() local stonePile = Action() function stonePile.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission02) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) -- Skip if not was started if missionState == -1 then return true end if missionState <= 3 then if missionState == 1 then - player:setStorageValue(Storage.TheRookieGuard.Mission02, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02, 2) end -- Gather delay - if player:getStorageValue(Storage.TheRookieGuard.StonePileTimer) - os.time() <= 0 then - player:setStorageValue(Storage.TheRookieGuard.StonePileTimer, os.time() + 2 * 60) + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.StonePileTimer) - os.time() <= 0 then + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.StonePileTimer, os.time() + 2 * 60) player:addItemEx(Game.createItem(12724, 1), true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have to wait a few minutes before you can pick up a new stone.") @@ -120,9 +120,9 @@ local catapults = { local heavyStone = Action() function heavyStone.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission02) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02) if missionState >= 2 and missionState <= 3 and catapults[item2.actionid] then - local catapultsState = player:getStorageValue(Storage.TheRookieGuard.Catapults) + local catapultsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Catapults) local hasUsedCatapult = testFlag(catapultsState, catapults[item2.actionid]) if not hasUsedCatapult then if missionState == 2 then @@ -130,8 +130,8 @@ function heavyStone.onUse(player, item, frompos, item2, topos) elseif missionState == 3 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You loaded the last stone on the catapults. Time to return to Vascalir.") end - player:setStorageValue(Storage.TheRookieGuard.Mission02, missionState + 1) - player:setStorageValue(Storage.TheRookieGuard.Catapults, catapultsState + catapults[item2.actionid]) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission02, missionState + 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Catapults, catapultsState + catapults[item2.actionid]) player:addExperience(5, true) player:removeItem(12724, 1) else diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission03_rational_request.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission03_rational_request.lua index ee2e814656e..a43ee721dfa 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission03_rational_request.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission03_rational_request.lua @@ -6,10 +6,10 @@ local ratKill = CreatureEvent("RationalRequestRatDeath") function ratKill.onDeath(creature, _corpse, _lastHitKiller, mostDamageKiller) onDeathForParty(creature, mostDamageKiller, function(creature, player) - if player:getStorageValue(Storage.TheRookieGuard.Mission03) == 1 then - local counter = player:getStorageValue(Storage.TheRookieGuard.RatKills) + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission03) == 1 then + local counter = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills) if counter < 5 then - player:setStorageValue(Storage.TheRookieGuard.RatKills, counter + 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.RatKills, counter + 1) end end end) diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission04_home_brewed.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission04_home_brewed.lua index 62646d27d03..6e230883656 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission04_home_brewed.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission04_home_brewed.lua @@ -31,7 +31,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission04) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission04) -- Skip if not was started or finished if missionState == -1 or missionState > 2 then return true diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission05_web_terror.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission05_web_terror.lua index 94ebd98f9e5..06fcf61aafd 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission05_web_terror.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission05_web_terror.lua @@ -33,7 +33,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local state = player:getStorageValue(Storage.TheRookieGuard.Mission05) + local state = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) -- Skip if not was started or finished if state == -1 or state == 3 then return true @@ -70,7 +70,7 @@ function spiderLairHole.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission05) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) if missionState == -1 or missionState >= 3 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have no business down there.") player:teleportTo(fromPosition, true) @@ -86,7 +86,7 @@ spiderLairHole:register() local greasyStone = Action() function greasyStone.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission05) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) -- Skip if not was started if missionState == -1 then return true @@ -100,7 +100,7 @@ function greasyStone.onUse(player, item, frompos, item2, topos) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You rub the strange grease on your body. The spider queen will not be able to smell you for about 2 minutes. Hurry!") Position({ x = 32018, y = 32098, z = 11 }):sendMagicEffect(CONST_ME_TUTORIALARROW) end - player:setStorageValue(Storage.TheRookieGuard.Mission05, 2) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, 2) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You already retrieved some of the spider queen's web. No need to go back down there.") end @@ -119,7 +119,7 @@ function spiderQueenChamberHole.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission05) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) if missionState == 1 then -- Check delayed notifications (message/arrow) if not isTutorialNotificationDelayed(player) then @@ -145,14 +145,14 @@ spiderQueenChamberHole:register() local spiderWeb = Action() function spiderWeb.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission05) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05) -- Skip if not was started if missionState == -1 then return true end if missionState == 2 or missionState == 4 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You retrieved some of the spider queen's web. Hurry back before she can smell you again!") - player:setStorageValue(Storage.TheRookieGuard.Mission05, 3) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, 3) end return true end diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission06_run_like_wolf.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission06_run_like_wolf.lua index 16fc3171ce2..533ea7801a1 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission06_run_like_wolf.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission06_run_like_wolf.lua @@ -34,7 +34,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) -- Skip if not was started or finished if missionState == -1 or missionState >= 4 then return true @@ -67,7 +67,7 @@ function warWolfDenHole.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) if missionState == -1 or missionState >= 4 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have no business down there.") player:teleportTo(fromPosition, true) @@ -106,7 +106,7 @@ function warWolfDenTiles.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) if missionState == -1 then return true end @@ -121,7 +121,7 @@ function warWolfDenTiles.onStepIn(creature, item, position, fromPosition) end end if missionTile.newState then - player:setStorageValue(Storage.TheRookieGuard.Mission06, missionTile.newState) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, missionTile.newState) end end return true @@ -136,9 +136,9 @@ warWolfDenTiles:register() local function teleportBack(uid) local player = Player(uid) - if player and player:getStorageValue(Storage.TheRookieGuard.Mission06) == 5 then + if player and player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) == 5 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Oh no... you were too slow and the wolves caught up with you. You may try again.") - player:setStorageValue(Storage.TheRookieGuard.Mission06, 4) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 4) player:teleportTo({ x = 32109, y = 32131, z = 11 }) end end @@ -150,10 +150,10 @@ function warWolfDenBoostTiles.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) if missionState == 4 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "RUUUUUUUUUUUUUUUUUN!") - player:setStorageValue(Storage.TheRookieGuard.Mission06, 5) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 5) player:getPosition():sendMagicEffect(CONST_ME_POFF) local conditionHaste = Condition(CONDITION_HASTE) conditionHaste:setParameter(CONDITION_PARAM_TICKS, 25000) @@ -172,17 +172,18 @@ warWolfDenBoostTiles:register() local poacherCorpse = Action() function poacherCorpse.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) -- Skip if not was started if missionState == -1 then return true end - if missionState == 3 then - local corpseState = player:getStorageValue(Storage.TheRookieGuard.PoacherCorpse) + if missionState == 2 then + local corpseState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.PoacherCorpse) if corpseState == -1 then local reward = Game.createItem(12672, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getArticle() .. " " .. reward:getName() .. ".") - player:setStorageValue(Storage.TheRookieGuard.PoacherCorpse, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.PoacherCorpse, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 3) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") @@ -199,10 +200,10 @@ poacherCorpse:register() local skinningKnife = Action() function skinningKnife.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) if missionState == 3 and itemEx.uid == 40045 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You got the war wolf leather - but you hear a scary howl behind you. Time to get out of here - FAST!") - player:setStorageValue(Storage.TheRookieGuard.Mission06, 4) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06, 4) player:addExperience(50, true) player:removeItem(12672, 1) player:addItemEx(Game.createItem(12740, 1), true, CONST_SLOT_WHEREEVER) @@ -218,16 +219,16 @@ skinningKnife:register() local warWolfDenChest = Action() function warWolfDenChest.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission06) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission06) -- Skip if not was started if missionState == -1 then return true end - local chestState = player:getStorageValue(Storage.TheRookieGuard.WarWolfDenChest) + local chestState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.WarWolfDenChest) if chestState == -1 then local reward = Game.createItem(7876, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getArticle() .. " " .. reward:getName() .. ".") - player:setStorageValue(Storage.TheRookieGuard.WarWolfDenChest, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.WarWolfDenChest, 1) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission07_attack.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission07_attack.lua index 893691e7392..9116f2b4444 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission07_attack.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission07_attack.lua @@ -27,13 +27,13 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission07) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) -- Skip if not was started or finished if missionState == -1 or missionState == 2 then return true end local missionTile = missionTiles[item.actionid] - local libraryChestState = player:getStorageValue(Storage.TheRookieGuard.LibraryChest) + local libraryChestState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) -- Check if the tile is active if missionState == 1 and libraryChestState == -1 then -- Check delayed notifications (message/arrow) @@ -61,7 +61,7 @@ function libraryVaultSteps.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission07) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) -- Skip if not was started or finished if missionState == -1 or missionState == 2 then return true @@ -118,7 +118,7 @@ end local destroyFieldRune = Action() function destroyFieldRune.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission07) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) if missionState == 1 and item2.itemid == 12743 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Fire in this stadium can be crossed without taking damage. Open the chest and get out of here!") item2:getPosition():sendMagicEffect(CONST_ME_POFF) @@ -136,17 +136,17 @@ destroyFieldRune:register() local treasureChest = Action() function treasureChest.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission07) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission07) -- Skip if not was started if missionState == -1 then return true end if missionState == 1 then - local libraryChestState = player:getStorageValue(Storage.TheRookieGuard.LibraryChest) + local libraryChestState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest) if libraryChestState == -1 then local reward = Game.createItem(12675, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getArticle() .. " " .. reward:getName() .. ".") - player:setStorageValue(Storage.TheRookieGuard.LibraryChest, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.LibraryChest, 1) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission09_rock_troll.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission09_rock_troll.lua index 7431dab83d7..2c117c9c4ac 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission09_rock_troll.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission09_rock_troll.lua @@ -33,7 +33,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission09) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) -- Skip if not was started or finished if missionState == -1 or missionState > 7 then return true @@ -49,7 +49,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) end end if missionTile.newState then - player:setStorageValue(Storage.TheRookieGuard.Mission09, missionTile.newState) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09, missionTile.newState) end end return true @@ -69,7 +69,7 @@ function tunnelHole.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission09) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) if missionState == -1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have no business down there.") player:teleportTo(fromPosition, true) @@ -104,19 +104,19 @@ local chests = { local trunkChest = Action() function trunkChest.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission09) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) -- Skip if not was started if missionState == -1 then return true end if missionState >= 2 then local chest = chests[item.uid] - local chestsState = player:getStorageValue(Storage.TheRookieGuard.TrollChests) + local chestsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.TrollChests) local hasOpenedChest = testFlag(chestsState, chest.id) if not hasOpenedChest then local reward = Game.createItem(chest.itemId, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getArticle() .. " " .. reward:getName() .. ".") - player:setStorageValue(Storage.TheRookieGuard.TrollChests, chestsState + chest.id) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.TrollChests, chestsState + chest.id) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") @@ -148,10 +148,10 @@ local tunnelPillars = { -- /data/scripts/lib/register_actions.lua (onUsePick) function onUsePickAtTunnelPillar(player, item, fromPosition, itemEx, toPosition) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission09) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09) local pillarId = tunnelPillars[itemEx.uid] if missionState >= 2 and missionState <= 7 and pillarId then - local pillarsState = player:getStorageValue(Storage.TheRookieGuard.TunnelPillars) + local pillarsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.TunnelPillars) local hasDamagedPillar = testFlag(pillarsState, pillarId) if not hasDamagedPillar then local newMissionState = missionState + 1 @@ -163,8 +163,8 @@ function onUsePickAtTunnelPillar(player, item, fromPosition, itemEx, toPosition) end player:say("", TALKTYPE_MONSTER_SAY, false, player, toPosition) toPosition:sendMagicEffect(CONST_ME_HITAREA) - player:setStorageValue(Storage.TheRookieGuard.Mission09, newMissionState) - player:setStorageValue(Storage.TheRookieGuard.TunnelPillars, pillarsState + pillarId) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission09, newMissionState) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.TunnelPillars, pillarsState + pillarId) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've already weakened this beam. Better leave it alone now so it won't collapse before you are out of here.") end diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission10_tomb_raiding.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission10_tomb_raiding.lua index a9f54ec1ec0..168adf09f2b 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission10_tomb_raiding.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission10_tomb_raiding.lua @@ -45,13 +45,13 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission10) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) -- Skip if not was started or finished if missionState == -1 or missionState > 1 then return true end local missionTile = missionTiles[item.actionid] - local sarcophagusState = player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) + local sarcophagusState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) -- Check mission state cases for the tile for i = 1, #missionTile do -- Check if the tile is active @@ -79,17 +79,17 @@ missionGuide:register() local sarcophagus = Action() function sarcophagus.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission10) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) -- Skip if not was started if missionState == -1 then return true end if missionState >= 1 then - local sarcophagusState = player:getStorageValue(Storage.TheRookieGuard.Sarcophagus) + local sarcophagusState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus) if sarcophagusState == -1 then local reward = Game.createItem(12674, 1) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getArticle() .. " " .. reward:getName() .. ".") - player:setStorageValue(Storage.TheRookieGuard.Sarcophagus, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Sarcophagus, 1) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") @@ -128,13 +128,13 @@ local chests = { local unholyCryptChests = Action() function unholyCryptChests.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission10) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) -- Skip if not was started if missionState == -1 then return true end local chest = chests[item.uid] - local chestsState = player:getStorageValue(Storage.TheRookieGuard.UnholyCryptChests) + local chestsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.UnholyCryptChests) local hasOpenedChest = testFlag(chestsState, chest.id) if not hasOpenedChest then local reward = Game.createItem(chest.item.id, chest.item.amount) @@ -143,7 +143,7 @@ function unholyCryptChests.onUse(player, item, frompos, item2, topos) elseif reward:getCount() > 1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getCount() .. " " .. reward:getPluralName() .. ".") end - player:setStorageValue(Storage.TheRookieGuard.UnholyCryptChests, chestsState + chest.id) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.UnholyCryptChests, chestsState + chest.id) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission11_sweet_poison.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission11_sweet_poison.lua index a51d600bcb2..c91c78274cf 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission11_sweet_poison.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission11_sweet_poison.lua @@ -22,7 +22,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission11) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) -- Skip if not was started or finished if missionState == -1 or missionState > 1 then return true @@ -38,7 +38,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) end end if missionTile.newState then - player:setStorageValue(Storage.TheRookieGuard.Mission11, missionTile.newState) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, missionTile.newState) end end return true @@ -54,10 +54,10 @@ missionGuide:register() local specialFlask = Action() function specialFlask.onUse(player, item, frompos, item2, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission11) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11) if missionState == 2 and item2.itemid == 5989 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You carefully gather some of the wasp poison. Bring it back to Vascalir.") - player:setStorageValue(Storage.TheRookieGuard.Mission11, 3) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission11, 3) player:removeItem(12785, 1) player:addItemEx(Game.createItem(12784, 1), true, CONST_SLOT_WHEREEVER) end diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/mission12_into_fortress.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/mission12_into_fortress.lua index 9c4a8f55a27..4aede9c97a1 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/mission12_into_fortress.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/mission12_into_fortress.lua @@ -5,7 +5,7 @@ local missionTiles = { { states = { 1 }, extra = { - storage = Storage.TheRookieGuard.AcademyChest, + storage = Storage.Quest.U9_1.TheRookieGuard.AcademyChest, state = -1, }, message = "This chest should contain everything you need to infiltrate the fortress.", @@ -14,7 +14,7 @@ local missionTiles = { { states = { 1 }, extra = { - storage = Storage.TheRookieGuard.AcademyChest, + storage = Storage.Quest.U9_1.TheRookieGuard.AcademyChest, state = 1, }, message = "Those items should be what you need to infiltrate the fortress. Go back near the wasps' nest and walk south from there.", @@ -105,7 +105,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) -- Skip if not was started or finished if missionState == -1 then return true @@ -125,7 +125,7 @@ function missionGuide.onStepIn(creature, item, position, fromPosition) end -- Update state if tile[i].newState then - player:setStorageValue(Storage.TheRookieGuard.Mission12, tile[i].newState) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, tile[i].newState) end -- Walk to relative position if tile[i].walkTo then @@ -162,22 +162,22 @@ local reward = { local treasureChest = Action() function treasureChest.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) -- Skip if not was started if missionState == -1 then return true end if missionState >= 1 and missionState <= 13 then - local chestState = player:getStorageValue(Storage.TheRookieGuard.AcademyChest) - local chestTimer = player:getStorageValue(Storage.TheRookieGuard.AcademyChestTimer) + local chestState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.AcademyChest) + local chestTimer = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.AcademyChestTimer) if chestState == -1 or chestTimer - os.time() <= 0 then local container = Game.createItem(reward.containerId) for i = #reward.itemIds, 1, -1 do container:addItem(reward.itemIds[i], 1) end player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. container:getArticle() .. " " .. container:getName() .. ".") - player:setStorageValue(Storage.TheRookieGuard.AcademyChest, 1) - player:setStorageValue(Storage.TheRookieGuard.AcademyChestTimer, os.time() + 24 * 60 * 60) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.AcademyChest, 1) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.AcademyChestTimer, os.time() + 24 * 60 * 60) player:addItemEx(container, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") @@ -204,11 +204,11 @@ end local rollingPin = Action() function rollingPin.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState >= 2 and missionState <= 13 and itemEx.itemid == 12790 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You knock the unsuspicious orc unconscious. Use him to disguise yourself as orc!") if missionState == 2 then - player:setStorageValue(Storage.TheRookieGuard.Mission12, 3) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 3) player:addExperience(50, true) end itemEx:transform(12791, 1) @@ -227,11 +227,11 @@ rollingPin:register() local unconsciousOrc = Action() function unconsciousOrc.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState >= 3 and missionState <= 13 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You look almost like an orc. It won't fool all orcs, but the stupid guardsman in front of the fortress should fall for it.") if missionState == 3 then - player:setStorageValue(Storage.TheRookieGuard.Mission12, 4) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 4) end local conditionOutfit = Condition(CONDITION_OUTFIT) conditionOutfit:setTicks(300000) @@ -272,11 +272,11 @@ end local fleshyBone = Action() function fleshyBone.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState >= 5 and itemEx.itemid == 12792 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "This should be enough distraction for you to sneak into the fortress! Hurry up!") if missionState == 5 then - player:setStorageValue(Storage.TheRookieGuard.Mission12, 6) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 6) player:addExperience(50, true) end local position = itemEx:getPosition() @@ -305,10 +305,10 @@ fleshyBone:register() local poisonFlask = Action() function poisonFlask.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState == 7 and itemEx.actionid == 40012 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You poisoned Kraknaknork's soup. This should weaken him immensely. Time to find his room.") - player:setStorageValue(Storage.TheRookieGuard.Mission12, 8) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 8) player:removeItem(12784, 1) player:addExperience(50, true) end @@ -323,12 +323,12 @@ poisonFlask:register() local taranturaTrap = Action() function taranturaTrap.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) target = Tile(topos):getTopCreature() if missionState >= 8 and target:getName() == "Furious Orc Berserker" then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The berserker can't catch you anymore - but only for 20 seconds. You need to lure him away from the teleporter!") if missionState == 8 then - player:setStorageValue(Storage.TheRookieGuard.Mission12, 9) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 9) end local conditionSlow = Condition(CONDITION_PARALYZE) conditionSlow:setParameter(CONDITION_PARAM_TICKS, 20000) @@ -350,12 +350,12 @@ function bossLairTeleport.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) -- Skip if not was started or finished if missionState == -1 then return true end - if missionState >= 8 then + if missionState >= 9 then local spectators = Game.getSpectators(position, false, false, 2, 2, 2, 2) for i = 1, #spectators do if not spectators[i]:isPlayer() and spectators[i]:getName() == "Furious Orc Berserker" then @@ -368,7 +368,7 @@ function bossLairTeleport.onStepIn(creature, item, position, fromPosition) end if missionState == 9 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You're entering Kraknaknork's lair.") - player:setStorageValue(Storage.TheRookieGuard.Mission12, 10) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 10) end local toPosition = Position(31980, 32173, 10) player:teleportTo(toPosition, false) @@ -470,7 +470,7 @@ end local missionLevers = Action() function missionLevers.onUse(player, item, position, itemEx, toPosition) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState >= 10 then local lever = levers[item.uid] local energyBarrier = Tile(energyBarriers[lever.barrier].position):getItemById(12796) @@ -483,7 +483,7 @@ function missionLevers.onUse(player, item, position, itemEx, toPosition) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "An energy barrier somewhere temporarily disappeared.") end if missionState == 10 and lever.newState then - player:setStorageValue(Storage.TheRookieGuard.Mission12, lever.newState) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, lever.newState) end addEvent(energyBarrierRestore, 60000, lever.barrier) else @@ -537,7 +537,7 @@ function enterBossRoomTeleport.onStepIn(creature, item, position, fromPosition) if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) -- Skip if not was started or finished if missionState == -1 then return true @@ -562,7 +562,7 @@ function enterBossRoomTeleport.onStepIn(creature, item, position, fromPosition) boss.uid = bossCreature.uid -- Teleport the player to the room player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You're entering Kraknaknork's throne room. You have 5 minutes to kill him!") - player:setStorageValue(Storage.TheRookieGuard.Mission12, 12) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 12) local roomPosition = Position(31944, 32174, 10) player:teleportTo(roomPosition, false) position:sendMagicEffect(CONST_ME_TELEPORT) @@ -614,7 +614,7 @@ function enterTreasureRoomTeleport.onStepIn(creature, item, position, fromPositi if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState == 12 then local spectators = Game.getSpectators(Position(boss.roomCenter), false, false, 8, 8, 5, 5) -- Check the boss do not exist @@ -630,7 +630,7 @@ function enterTreasureRoomTeleport.onStepIn(creature, item, position, fromPositi end end -- Teleport the player to the treasure room - player:setStorageValue(Storage.TheRookieGuard.Mission12, 13) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 13) local treasureRoomPosition = Position(31932, 32171, 11) player:teleportTo(treasureRoomPosition, false) position:sendMagicEffect(CONST_ME_TELEPORT) @@ -669,14 +669,14 @@ local chests = { local bossChests = Action() function bossChests.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) -- Skip if not was started if missionState == -1 then return true end if missionState == 13 then local chest = chests[item.uid] - local chestsState = player:getStorageValue(Storage.TheRookieGuard.KraknaknorkChests) + local chestsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.KraknaknorkChests) local hasUsedChest = testFlag(chestsState, chest.id) if not hasUsedChest then local reward = Game.createItem(chest.item.id, chest.item.amount) @@ -685,7 +685,7 @@ function bossChests.onUse(player, item, frompos, itemEx, topos) elseif reward:getCount() > 1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getCount() .. " " .. reward:getPluralName() .. ".") end - player:setStorageValue(Storage.TheRookieGuard.KraknaknorkChests, chestsState + chest.id) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.KraknaknorkChests, chestsState + chest.id) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") @@ -706,7 +706,7 @@ function exitTreasureRoomTeleport.onStepIn(creature, item, position, fromPositio if not player then return true end - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission12) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12) if missionState == 13 then local health, maxHealth = player:getHealth(), player:getBaseMaxHealth() -- Heal the player if needed @@ -715,7 +715,7 @@ function exitTreasureRoomTeleport.onStepIn(creature, item, position, fromPositio end -- Teleport the player to the orcland exit player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "With Kraknaknork's final source of energy, you escape the fortress. Time to return to Vascalir.") - player:setStorageValue(Storage.TheRookieGuard.Mission12, 14) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission12, 14) local exitPosition = Position(32016, 32150, 7) player:teleportTo(exitPosition, false) position:sendMagicEffect(CONST_ME_TELEPORT) @@ -762,13 +762,13 @@ local chests = { local orcFortressChests = Action() function orcFortressChests.onUse(player, item, frompos, itemEx, topos) - local missionState = player:getStorageValue(Storage.TheRookieGuard.Mission10) + local missionState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission10) -- Skip if not was started if missionState == -1 then return true end local chest = chests[item.uid] - local chestsState = player:getStorageValue(Storage.TheRookieGuard.OrcFortressChests) + local chestsState = player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.OrcFortressChests) local hasOpenedChest = testFlag(chestsState, chest.id) if not hasOpenedChest then local reward = Game.createItem(chest.item.id, chest.item.amount) @@ -777,7 +777,7 @@ function orcFortressChests.onUse(player, item, frompos, itemEx, topos) elseif reward:getCount() > 1 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found " .. reward:getCount() .. " " .. reward:getPluralName() .. ".") end - player:setStorageValue(Storage.TheRookieGuard.OrcFortressChests, chestsState + chest.id) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.OrcFortressChests, chestsState + chest.id) player:addItemEx(reward, true, CONST_SLOT_WHEREEVER) else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The " .. item:getName() .. " is empty.") diff --git a/data-otservbr-global/scripts/quests/the_rookie_guard/missions.lua b/data-otservbr-global/scripts/quests/the_rookie_guard/missions.lua index 0899d99529d..6568db09c9f 100644 --- a/data-otservbr-global/scripts/quests/the_rookie_guard/missions.lua +++ b/data-otservbr-global/scripts/quests/the_rookie_guard/missions.lua @@ -3,9 +3,9 @@ -- Handle avoid spam (message and arrow) in mission tiles function isTutorialNotificationDelayed(player) -- Check delay - if player:getStorageValue(Storage.TheRookieGuard.TutorialDelay) - os.time() <= 0 then + if player:getStorageValue(Storage.Quest.U9_1.TheRookieGuard.TutorialDelay) - os.time() <= 0 then -- Reset delay - player:setStorageValue(Storage.TheRookieGuard.TutorialDelay, os.time() + 4) + player:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.TutorialDelay, os.time() + 4) return false end return true @@ -17,12 +17,12 @@ local missionTiles = { -- North exit [50312] = { { - mission = Storage.TheRookieGuard.Mission02, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission02, states = { 1, 2, 3, 4 }, message = "This road is the main access of the village. You might want to finish your business here first.", }, { - mission = Storage.TheRookieGuard.Mission03, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission03, states = { 1 }, message = "This road is the main access of the village. You might want to finish your business here first.", }, @@ -30,44 +30,44 @@ local missionTiles = { -- North bridge exit [50319] = { { - mission = Storage.TheRookieGuard.Mission04, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission04, states = { 2 }, message = "Follow the path to the east to find Hyacinth's little house.", arrowPosition = { x = 32096, y = 32169, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission06, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission06, states = { 2 }, message = "Follow the path east, and when it splits, head north-east to find the wolf forest.", arrowPosition = { x = 32094, y = 32169, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission09, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission09, states = { 1 }, message = "Follow the path to the north past the hill to reach the troll caves.", arrowPosition = { x = 32091, y = 32166, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission10, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission10, states = { 1 }, extra = { - storage = Storage.TheRookieGuard.Sarcophagus, + storage = Storage.Quest.U9_1.TheRookieGuard.Sarcophagus, state = -1, }, message = "Follow the way to the east and go south to reach the graveyard.", arrowPosition = { x = 32095, y = 32169, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission11, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission11, states = { 1 }, message = "To reach the wasps' nests follow the path to the north and cross the bridge to the west as if you wanted to reach the spiders.", arrowPosition = { x = 32090, y = 32165, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission12, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission12, states = { 2 }, extra = { - storage = Storage.TheRookieGuard.AcademyChest, + storage = Storage.Quest.U9_1.TheRookieGuard.AcademyChest, state = 1, }, message = "Follow the path to the north, cross the bridge to the south and walk west to reach the orc fortress.", @@ -76,15 +76,15 @@ local missionTiles = { }, [50321] = { { - mission = Storage.TheRookieGuard.Mission04, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission04, states = { 2 }, message = "This is not the way to Hyacinth. Stay on the path a little more to the south to find Hyacinth's little house.", }, { - mission = Storage.TheRookieGuard.Mission10, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission10, states = { 1 }, extra = { - storage = Storage.TheRookieGuard.Sarcophagus, + storage = Storage.Quest.U9_1.TheRookieGuard.Sarcophagus, state = -1, }, message = "This is not the way to the crypt. Go south to reach the graveyard.", @@ -93,12 +93,12 @@ local missionTiles = { -- Outer east [50323] = { { - mission = Storage.TheRookieGuard.Mission05, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission05, states = { 1 }, message = "This is not the way to the tarantula's lair. Head northwest and go up the little ramp.", }, { - mission = Storage.TheRookieGuard.Mission09, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission09, states = { 1 }, message = "This is not the way to the troll caves. Follow the path to the north past the hill to reach them.", arrowPosition = { x = 32091, y = 32166, z = 7 }, @@ -107,13 +107,13 @@ local missionTiles = { -- North-west drawbridge [50325] = { { - mission = Storage.TheRookieGuard.Mission05, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission05, states = { 1 }, message = "Walk to the north and down the stairs to reach the tarantula's lair.", arrowPosition = { x = 32069, y = 32145, z = 6 }, }, { - mission = Storage.TheRookieGuard.Mission11, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission11, states = { 1 }, message = "Take the southern stairs down the bridge to go to the wasps' lair.", arrowPosition = { x = 32068, y = 32149, z = 6 }, @@ -122,26 +122,26 @@ local missionTiles = { -- Academy entrance [50335] = { { - mission = Storage.TheRookieGuard.Mission07, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission07, states = { 1 }, extra = { - storage = Storage.TheRookieGuard.LibraryChest, + storage = Storage.Quest.U9_1.TheRookieGuard.LibraryChest, state = -1, }, message = "The library vault is below the academy. Go north and head down several stairs until you find a quest door.", arrowPosition = { x = 32097, y = 32197, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission08, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission08, states = { 1 }, message = "The bank is below the academy. Go north and head down the stairs and to the right.", arrowPosition = { x = 32097, y = 32197, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission12, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission12, states = { 1 }, extra = { - storage = Storage.TheRookieGuard.AcademyChest, + storage = Storage.Quest.U9_1.TheRookieGuard.AcademyChest, state = -1, }, message = "You don't have the bag with the items yet. Open the door in the basement of the academy to the left of Paulie to get them!", @@ -151,17 +151,17 @@ local missionTiles = { -- Academy downstairs [50336] = { { - mission = Storage.TheRookieGuard.Mission07, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission07, states = { 1 }, extra = { - storage = Storage.TheRookieGuard.LibraryChest, + storage = Storage.Quest.U9_1.TheRookieGuard.LibraryChest, state = -1, }, message = "Head through the northern door and follow the hallways to find the library vault.", arrowPosition = { x = 32095, y = 32188, z = 8 }, }, { - mission = Storage.TheRookieGuard.Mission08, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission08, states = { 1 }, message = "Go to the right to find the bank and talk to Paulie.", arrowPosition = { x = 32100, y = 32191, z = 8 }, @@ -170,16 +170,16 @@ local missionTiles = { -- North-west drawbridge south downstairs [50351] = { { - mission = Storage.TheRookieGuard.Mission11, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission11, states = { 1 }, message = "Follow the path to the west to find the wasps' lair.", arrowPosition = { x = 32063, y = 32159, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission12, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission12, states = { 2 }, extra = { - storage = Storage.TheRookieGuard.AcademyChest, + storage = Storage.Quest.U9_1.TheRookieGuard.AcademyChest, state = 1, }, message = "Follow the path to the west to reach the orc fortress.", @@ -189,16 +189,16 @@ local missionTiles = { -- Orc land entrance [50352] = { { - mission = Storage.TheRookieGuard.Mission11, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission11, states = { 1 }, message = "This is not the way to the wasps' lair. Choose the northern path to reach it.", arrowPosition = { x = 32003, y = 32148, z = 7 }, }, { - mission = Storage.TheRookieGuard.Mission12, + mission = Storage.Quest.U9_1.TheRookieGuard.Mission12, states = { 2 }, extra = { - storage = Storage.TheRookieGuard.AcademyChest, + storage = Storage.Quest.U9_1.TheRookieGuard.AcademyChest, state = 1, }, message = "You're entering orcland.", diff --git a/data-otservbr-global/scripts/spells/monster/spider_queen_wrap.lua b/data-otservbr-global/scripts/spells/monster/spider_queen_wrap.lua index 475758fcd27..b0621abdcc3 100644 --- a/data-otservbr-global/scripts/spells/monster/spider_queen_wrap.lua +++ b/data-otservbr-global/scripts/spells/monster/spider_queen_wrap.lua @@ -26,7 +26,7 @@ function spell.onCastSpell(creature, var) if combat:execute(creature, var) then target:addCondition(conditionOutfit) target:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The spider queen caught you in her net and paralysed you!") - target:setStorageValue(Storage.TheRookieGuard.Mission05, 4) + target:setStorageValue(Storage.Quest.U9_1.TheRookieGuard.Mission05, 4) addEvent(moveToSpiderNest, 4500, target:getId()) return true end diff --git a/data-otservbr-global/startup/tables/chest.lua b/data-otservbr-global/startup/tables/chest.lua index 6c2033edb64..d4b91c57c93 100644 --- a/data-otservbr-global/startup/tables/chest.lua +++ b/data-otservbr-global/startup/tables/chest.lua @@ -2555,6 +2555,15 @@ ChestUnique = { weight = 43.00, storage = Storage.Quest.U8_6.WrathOfTheEmperor.ChestItems, }, + -- Rookguard + -- 05 Brown Mushrooms + [6301] = { + useKV = true, + itemId = 2472, + itemPos = { x = 32138, y = 32171, z = 3 }, + reward = { { 3725, 5 } }, + questName = "Rookguard05BrownMushrooms", + }, -- Reward of others scrips files (varied rewards) -- The First dragon Quest -- Treasure chests (data\scripts\actions\quests\first_dragon\treasure_chests.lua) diff --git a/data-otservbr-global/startup/tables/door_quest.lua b/data-otservbr-global/startup/tables/door_quest.lua index de2e9854342..cf47a12bfe5 100644 --- a/data-otservbr-global/startup/tables/door_quest.lua +++ b/data-otservbr-global/startup/tables/door_quest.lua @@ -193,7 +193,7 @@ QuestDoorAction = { }, }, -- Bigfoot burden quest - [Storage.BigfootBurden.DoorGoldenFruits] = { + [Storage.Quest.U9_60.BigfootsBurden.DoorGoldenFruits] = { itemId = false, itemPos = { { x = 32822, y = 31745, z = 10 } }, }, @@ -702,19 +702,19 @@ QuestDoorAction = { -- }, -- The Rookie Guard Quest - Mission 07: Attack! -- The library vault door - [Storage.TheRookieGuard.LibraryDoor] = { + [Storage.Quest.U9_1.TheRookieGuard.LibraryDoor] = { itemId = false, itemPos = { { x = 32090, y = 32156, z = 9 } }, }, -- The Rookie Guard Quest - Mission 10: Tomb Raiding -- The unholy crypt door - [Storage.TheRookieGuard.UnholyCryptDoor] = { + [Storage.Quest.U9_1.TheRookieGuard.UnholyCryptDoor] = { itemId = false, itemPos = { { x = 32147, y = 32186, z = 9 } }, }, -- The Rookie Guard Quest - Mission 12: Into The Fortress -- Lower academy floor door - [Storage.TheRookieGuard.AcademyDoor] = { + [Storage.Quest.U9_1.TheRookieGuard.AcademyDoor] = { itemId = false, itemPos = { { x = 32109, y = 32189, z = 8 } }, }, diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index 258708419e1..a2fb8bc0de6 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -121,24 +121,24 @@ function Player.checkGnomeRank(self) return true end - local points = self:getStorageValue(Storage.BigfootBurden.Rank) - local questProgress = self:getStorageValue(Storage.BigfootBurden.QuestLine) + local points = self:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.Rank) + local questProgress = self:getStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine) if points >= 30 and points < 120 then if questProgress <= 25 then - self:setStorageValue(Storage.BigfootBurden.QuestLine, 26) + self:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 26) self:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) self:addAchievement("Gnome Little Helper") end elseif points >= 120 and points < 480 then if questProgress <= 26 then - self:setStorageValue(Storage.BigfootBurden.QuestLine, 27) + self:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 27) self:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) self:addAchievement("Gnome Little Helper") self:addAchievement("Gnome Friend") end elseif points >= 480 and points < 1440 then if questProgress <= 27 then - self:setStorageValue(Storage.BigfootBurden.QuestLine, 28) + self:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 28) self:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) self:addAchievement("Gnome Little Helper") self:addAchievement("Gnome Friend") @@ -146,7 +146,7 @@ function Player.checkGnomeRank(self) end elseif points >= 1440 then if questProgress <= 29 then - self:setStorageValue(Storage.BigfootBurden.QuestLine, 30) + self:setStorageValue(Storage.Quest.U9_60.BigfootsBurden.QuestLine, 30) self:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) self:addAchievement("Gnome Little Helper") self:addAchievement("Gnome Friend") From 06c6a674cccf1b60ba901a74a124b493f23fed79 Mon Sep 17 00:00:00 2001 From: HT Cesta <58153179+htc16@users.noreply.github.com> Date: Mon, 26 Aug 2024 21:16:11 -0300 Subject: [PATCH 05/27] fix: quest typo and item id (#2854) --- data-otservbr-global/startup/tables/chest.lua | 4 ++-- data-otservbr-global/startup/tables/item.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data-otservbr-global/startup/tables/chest.lua b/data-otservbr-global/startup/tables/chest.lua index d4b91c57c93..9ab3ea5a3b1 100644 --- a/data-otservbr-global/startup/tables/chest.lua +++ b/data-otservbr-global/startup/tables/chest.lua @@ -2555,14 +2555,14 @@ ChestUnique = { weight = 43.00, storage = Storage.Quest.U8_6.WrathOfTheEmperor.ChestItems, }, - -- Rookguard + -- Rookgaard -- 05 Brown Mushrooms [6301] = { useKV = true, itemId = 2472, itemPos = { x = 32138, y = 32171, z = 3 }, reward = { { 3725, 5 } }, - questName = "Rookguard05BrownMushrooms", + questName = "Rookgaard05BrownMushrooms", }, -- Reward of others scrips files (varied rewards) -- The First dragon Quest diff --git a/data-otservbr-global/startup/tables/item.lua b/data-otservbr-global/startup/tables/item.lua index 790090f417b..e2f94a88d18 100644 --- a/data-otservbr-global/startup/tables/item.lua +++ b/data-otservbr-global/startup/tables/item.lua @@ -1811,7 +1811,7 @@ ItemUnique = { -- Path: data\scripts\actions\quests\the_rookie_guard\mission06_run_like_wolf.lua -- Poacher corpse [40044] = { - itemId = 3204, + itemId = 111, itemPos = { x = 32135, y = 32133, z = 8 }, }, -- War wolf corpse From 0f8929d614cae30270f43c090a76421a513220cd Mon Sep 17 00:00:00 2001 From: Majesty <32709570+majestyotbr@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:55:41 -0300 Subject: [PATCH 06/27] fix: price mismatch between NPCs and typo in the rent house message (#2830) Monitoring feature to alert whenever there's a price mismatch between NPCs for item transactions. This ensures any instance where an item is sold for less than its purchase price, or vice versa, is promptly identified and reported. Co-authored-by: Leilani A. <168607226+kaleohanopahala@users.noreply.github.com> --- data-otservbr-global/npc/arito.lua | 3 - data-otservbr-global/npc/armenius.lua | 2 - data-otservbr-global/npc/bolfona.lua | 1 - data-otservbr-global/npc/boozer.lua | 2 - data-otservbr-global/npc/clyde.lua | 3 - data-otservbr-global/npc/dane.lua | 2 - data-otservbr-global/npc/maria.lua | 2 - data-otservbr-global/npc/mirabell.lua | 3 - data-otservbr-global/npc/miraia.lua | 2 - data-otservbr-global/npc/norma.lua | 3 - data-otservbr-global/npc/pugwah.lua | 3 - data-otservbr-global/npc/ramina.lua | 4 +- data-otservbr-global/npc/red_lilly.lua | 2 +- data-otservbr-global/npc/swolt.lua | 2 - data-otservbr-global/npc/tanaro.lua | 2 - data-otservbr-global/npc/urkalio.lua | 3 - data/scripts/lib/register_npc_type.lua | 76 ++++++++++++++++++-------- src/map/house/house.cpp | 2 +- 18 files changed, 57 insertions(+), 60 deletions(-) diff --git a/data-otservbr-global/npc/arito.lua b/data-otservbr-global/npc/arito.lua index caa33f8348f..1eab9a186c3 100644 --- a/data-otservbr-global/npc/arito.lua +++ b/data-otservbr-global/npc/arito.lua @@ -134,9 +134,6 @@ npcConfig.shop = { { itemName = "mug of lemonade", clientId = 2880, buy = 2, count = 12 }, { itemName = "mug of water", clientId = 2880, buy = 1, count = 1 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, - { itemName = "vial of water", clientId = 2874, buy = 1, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/armenius.lua b/data-otservbr-global/npc/armenius.lua index 8b24dc1d419..9164a78240e 100644 --- a/data-otservbr-global/npc/armenius.lua +++ b/data-otservbr-global/npc/armenius.lua @@ -97,8 +97,6 @@ npcConfig.shop = { { itemName = "mug of rum", clientId = 2880, buy = 10, count = 13 }, { itemName = "mug of wine", clientId = 2880, buy = 4, count = 2 }, { itemName = "tomato", clientId = 3596, buy = 3 }, - { itemName = "vial of beer", clientId = 2874, buy = 3, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 4, count = 1, subType = 2 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/bolfona.lua b/data-otservbr-global/npc/bolfona.lua index 83ee91f7ac9..adf900568ff 100644 --- a/data-otservbr-global/npc/bolfona.lua +++ b/data-otservbr-global/npc/bolfona.lua @@ -103,7 +103,6 @@ npcConfig.shop = { { itemName = "ham", clientId = 3582, buy = 8 }, { itemName = "meat", clientId = 3577, buy = 5 }, { itemName = "mug of beer", clientId = 2880, buy = 2, count = 3 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/boozer.lua b/data-otservbr-global/npc/boozer.lua index a2eaa22d179..b63fa7620fb 100644 --- a/data-otservbr-global/npc/boozer.lua +++ b/data-otservbr-global/npc/boozer.lua @@ -98,8 +98,6 @@ npcConfig.shop = { { itemName = "mug of lemonade", clientId = 2880, buy = 2, count = 12 }, { itemName = "mug of water", clientId = 2880, buy = 2, count = 1 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/clyde.lua b/data-otservbr-global/npc/clyde.lua index 4142cee2cbc..fcaf9ea011a 100644 --- a/data-otservbr-global/npc/clyde.lua +++ b/data-otservbr-global/npc/clyde.lua @@ -64,9 +64,6 @@ npcConfig.shop = { { itemName = "mug of water", clientId = 2880, buy = 2, count = 1 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, { itemName = "tomato", clientId = 3596, buy = 3 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, - { itemName = "vial of water", clientId = 2874, buy = 1, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/dane.lua b/data-otservbr-global/npc/dane.lua index f2dd71762c0..c2e3c612414 100644 --- a/data-otservbr-global/npc/dane.lua +++ b/data-otservbr-global/npc/dane.lua @@ -67,8 +67,6 @@ npcConfig.shop = { { itemName = "bottle of lemonade", clientId = 2875, buy = 12, count = 12 }, { itemName = "bottle of milk", clientId = 2875, buy = 4, count = 9 }, { itemName = "bottle of water", clientId = 2875, buy = 2, count = 1 }, - { itemName = "vial of milk", clientId = 2874, buy = 4, count = 1, subType = 9 }, - { itemName = "vial of water", clientId = 2874, buy = 2, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/maria.lua b/data-otservbr-global/npc/maria.lua index 74780e4a85a..7c63e11ac02 100644 --- a/data-otservbr-global/npc/maria.lua +++ b/data-otservbr-global/npc/maria.lua @@ -65,8 +65,6 @@ npcConfig.shop = { { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, { itemName = "tomato", clientId = 3596, buy = 5 }, { itemName = "valentine's cake", clientId = 6392, buy = 100 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/mirabell.lua b/data-otservbr-global/npc/mirabell.lua index 06362976254..db5f256ccd4 100644 --- a/data-otservbr-global/npc/mirabell.lua +++ b/data-otservbr-global/npc/mirabell.lua @@ -134,9 +134,6 @@ npcConfig.shop = { { itemName = "mug of lemonade", clientId = 2880, buy = 2, count = 12 }, { itemName = "mug of water", clientId = 2880, buy = 1, count = 1 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, - { itemName = "vial of water", clientId = 2874, buy = 1, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/miraia.lua b/data-otservbr-global/npc/miraia.lua index ad74b08040e..d8fbf586f2a 100644 --- a/data-otservbr-global/npc/miraia.lua +++ b/data-otservbr-global/npc/miraia.lua @@ -200,8 +200,6 @@ npcConfig.shop = { { itemName = "mug of milk", clientId = 2880, buy = 5, count = 9 }, { itemName = "mug of water", clientId = 2880, buy = 2, count = 1 }, { itemName = "scarab cheese", clientId = 169, buy = 100 }, - { itemName = "vial of milk", clientId = 2874, buy = 5, count = 1, subType = 9 }, - { itemName = "vial of water", clientId = 2874, buy = 2, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/norma.lua b/data-otservbr-global/npc/norma.lua index 769bf7b7b3f..693dc7fadbb 100644 --- a/data-otservbr-global/npc/norma.lua +++ b/data-otservbr-global/npc/norma.lua @@ -200,9 +200,6 @@ npcConfig.shop = { { itemName = "mug of milk", clientId = 2880, buy = 2, count = 6 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, { itemName = "party cake", clientId = 6279, buy = 50 }, - { itemName = "vial of beer", clientId = 2874, buy = 3, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, - { itemName = "vial of milk", clientId = 2874, buy = 2, count = 1, subType = 9 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/pugwah.lua b/data-otservbr-global/npc/pugwah.lua index e9c0874d5aa..9e9007a7667 100644 --- a/data-otservbr-global/npc/pugwah.lua +++ b/data-otservbr-global/npc/pugwah.lua @@ -61,9 +61,6 @@ npcConfig.shop = { { itemName = "mug of lemonade", clientId = 2880, buy = 2, count = 12 }, { itemName = "mug of milk", clientId = 2880, buy = 2, count = 6 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, - { itemName = "vial of beer", clientId = 2874, buy = 3, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 4, count = 1, subType = 2 }, - { itemName = "vial of water", clientId = 2874, buy = 2, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/ramina.lua b/data-otservbr-global/npc/ramina.lua index d88b305aaeb..f9b253c477e 100644 --- a/data-otservbr-global/npc/ramina.lua +++ b/data-otservbr-global/npc/ramina.lua @@ -64,9 +64,7 @@ npcConfig.shop = { { itemName = "mug of wine", clientId = 2880, buy = 15, count = 2 }, { itemName = "orange", clientId = 3586, buy = 12 }, { itemName = "peas", clientId = 11683, buy = 5 }, - { itemName = "vial of fruit juice", clientId = 2874, buy = 10, count = 14 }, - { itemName = "vial of water", clientId = 2874, buy = 2, count = 1 }, - { itemName = "vial of wine", clientId = 2874, buy = 15, count = 1, subType = 2 }, + { itemName = "vial of water", clientId = 2874, buy = 10, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/red_lilly.lua b/data-otservbr-global/npc/red_lilly.lua index 357cb02775b..b744fae905d 100644 --- a/data-otservbr-global/npc/red_lilly.lua +++ b/data-otservbr-global/npc/red_lilly.lua @@ -85,7 +85,7 @@ npcConfig.shop = { { itemName = "torch", clientId = 2920, buy = 2 }, { itemName = "vial", clientId = 2874, sell = 5 }, { itemName = "vial of oil", clientId = 2874, buy = 20, count = 7 }, - { itemName = "vial of water", clientId = 2874, buy = 10, count = 1 }, + { itemName = "vial of water", clientId = 2874, buy = 20, count = 1 }, { itemName = "watch", clientId = 2906, buy = 20, sell = 6 }, { itemName = "waterskin of water", clientId = 2901, buy = 10, count = 1 }, { itemName = "wooden hammer", clientId = 3459, sell = 15 }, diff --git a/data-otservbr-global/npc/swolt.lua b/data-otservbr-global/npc/swolt.lua index 7caa8aa4160..5d8a7b48830 100644 --- a/data-otservbr-global/npc/swolt.lua +++ b/data-otservbr-global/npc/swolt.lua @@ -86,8 +86,6 @@ npcConfig.shop = { { itemName = "mug of beer", clientId = 2880, buy = 2, count = 3 }, { itemName = "mug of water", clientId = 2880, buy = 1, count = 1 }, { itemName = "terramite eggs", clientId = 10453, sell = 50 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of water", clientId = 2874, buy = 1, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/tanaro.lua b/data-otservbr-global/npc/tanaro.lua index 467b70989ae..bf43db15d79 100644 --- a/data-otservbr-global/npc/tanaro.lua +++ b/data-otservbr-global/npc/tanaro.lua @@ -67,8 +67,6 @@ npcConfig.shop = { { itemName = "salmon", clientId = 3579, buy = 6 }, { itemName = "valentine's cake", clientId = 6392, buy = 100 }, { itemName = "white mushroom", clientId = 3723, buy = 6 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, - { itemName = "vial of water", clientId = 2874, buy = 2, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data-otservbr-global/npc/urkalio.lua b/data-otservbr-global/npc/urkalio.lua index 5919a42d761..dbcbbc238b1 100644 --- a/data-otservbr-global/npc/urkalio.lua +++ b/data-otservbr-global/npc/urkalio.lua @@ -62,9 +62,6 @@ npcConfig.shop = { { itemName = "mug of lemonade", clientId = 2880, buy = 2, count = 12 }, { itemName = "mug of water", clientId = 2880, buy = 1, count = 1 }, { itemName = "mug of wine", clientId = 2880, buy = 3, count = 2 }, - { itemName = "vial of beer", clientId = 2874, buy = 2, count = 1, subType = 3 }, - { itemName = "vial of wine", clientId = 2874, buy = 3, count = 1, subType = 2 }, - { itemName = "vial of water", clientId = 2874, buy = 1, count = 1 }, } -- On buy npc shop message npcType.onBuyItem = function(npc, player, itemId, subType, amount, ignore, inBackpacks, totalCost) diff --git a/data/scripts/lib/register_npc_type.lua b/data/scripts/lib/register_npc_type.lua index 53a5d525e59..e191bbbe240 100644 --- a/data/scripts/lib/register_npc_type.lua +++ b/data/scripts/lib/register_npc_type.lua @@ -141,25 +141,57 @@ registerNpcType.events = function(npcType, mask) end end +-- Global item tracker to track buy and sell prices across all NPCs +NpcPriceChecker = NpcPriceChecker or {} + registerNpcType.shop = function(npcType, mask) if type(mask.shop) == "table" then for _, shopItems in pairs(mask.shop) do local parent = Shop() - if shopItems.itemName or shopItems.itemname then - parent:setNameItem(shopItems.itemName or shopItems.itemname) + local itemName = shopItems.itemName or shopItems.itemname + local clientId = shopItems.clientId or shopItems.clientid + local buyPrice = shopItems.buy + local sellPrice = shopItems.sell + local npcName = npcType:getName() -- Assuming `npcType` has a `getName` method to get the NPC's name + + if itemName then + parent:setNameItem(itemName) end - if shopItems.clientId or shopItems.clientid then - parent:setId(shopItems.clientId or shopItems.clientid) + if clientId then + parent:setId(clientId) end if shopItems.subType or shopItems.subtype or shopItems.count then parent:setCount(shopItems.subType or shopItems.subtype or shopItems.count) end - if shopItems.buy then - parent:setBuyPrice(shopItems.buy) + if buyPrice then + parent:setBuyPrice(buyPrice) + end + if sellPrice then + parent:setSellPrice(sellPrice) end - if shopItems.sell then - parent:setSellPrice(shopItems.sell) + + if clientId then + if not NpcPriceChecker[clientId] then + NpcPriceChecker[clientId] = { buy = nil, sell = nil, buyNpc = nil, sellNpc = nil } + end + + if buyPrice then + NpcPriceChecker[clientId].buy = buyPrice + NpcPriceChecker[clientId].buyNpc = npcName + end + + if sellPrice then + NpcPriceChecker[clientId].sell = sellPrice + NpcPriceChecker[clientId].sellNpc = npcName + end + + if NpcPriceChecker[clientId].buy and NpcPriceChecker[clientId].sell then + if NpcPriceChecker[clientId].sell > NpcPriceChecker[clientId].buy then + logger.warn("The item {} ({}) is being sold for a value greater than the value it is purchased for by the NPCs. Buy NPC: {}, Sell NPC: {}", itemName, clientId, NpcPriceChecker[clientId].buyNpc, NpcPriceChecker[clientId].sellNpc) + end + end end + if shopItems.storageKey or shopItems.storagekey then parent:setStorageKey(shopItems.storageKey or shopItems.storagekey) end @@ -169,26 +201,26 @@ registerNpcType.shop = function(npcType, mask) if shopItems.child then for _, children in pairs(shopItems.child) do local child = Shop() - if shopItems.itemName or shopItems.itemname then - child:setNameItem(shopItems.itemName or shopItems.itemname) + if children.itemName or children.itemname then + child:setNameItem(children.itemName or children.itemname) end - if shopItems.clientId or shopItems.clientid then - child:setId(shopItems.clientId or shopItems.clientid) + if children.clientId or children.clientid then + child:setId(children.clientId or children.clientid) end - if shopItems.subType or shopItems.subtype or shopItems.count then - child:setCount(shopItems.subType or shopItems.subtype or shopItems.count) + if children.subType or children.subtype or children.count then + child:setCount(children.subType or children.subtype or children.count) end - if shopItems.buy then - child:setBuyPrice(shopItems.buy) + if children.buy then + child:setBuyPrice(children.buy) end - if shopItems.sell then - child:setSellPrice(shopItems.sell) + if children.sell then + child:setSellPrice(children.sell) end - if shopItems.storageKey or shopItems.storagekey then - child:setStorageKey(shopItems.storageKey or shopItems.storagekey) + if children.storageKey or children.storagekey then + child:setStorageKey(children.storageKey or children.storagekey) end - if shopItems.storageValue or shopItems.storagevalue then - child:setStorageValue(shopItems.storageValue or shopItems.storagevalue) + if children.storageValue or children.storagevalue then + child:setStorageValue(children.storageValue or children.storagevalue) end parent:addChildShop(child) end diff --git a/src/map/house/house.cpp b/src/map/house/house.cpp index 261147fff40..f2cb26114bf 100644 --- a/src/map/house/house.cpp +++ b/src/map/house/house.cpp @@ -871,7 +871,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const { } std::ostringstream ss; - ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose static_self_cast() house."; + ss << "Warning! \nThe " << period << " rent of " << house->getRent() << " gold for your house \"" << house->getName() << "\" is payable. Have it within " << daysLeft << " days or you will lose this house."; letter->setAttribute(ItemAttribute_t::TEXT, ss.str()); g_game().internalAddItem(player->getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); house->setPayRentWarnings(house->getPayRentWarnings() + 1); From 0d924e8b251d8840e07022900ee83e57c11080ab Mon Sep 17 00:00:00 2001 From: Majesty <32709570+majestyotbr@users.noreply.github.com> Date: Thu, 29 Aug 2024 08:59:22 -0300 Subject: [PATCH 07/27] feat: Nimmersatt's monsters (#2831) Add Nimmersatt's monsters: - Dragolisk, - Wardragon, - Mega Dragon. - Step in Teleport. Co-authored-by: hecmo94 <63259115+hecmo94@users.noreply.github.com> --- .../monster/dragons/dragolisk.lua | 122 ++++++++++++++++ .../monster/dragons/mega_dragon.lua | 126 +++++++++++++++++ .../monster/dragons/war_dragon.lua | 125 +++++++++++++++++ .../movements/teleport/dragolisk_teleport.lua | 26 ++++ data/items/items.xml | 132 ++++++++++++++++++ 5 files changed, 531 insertions(+) create mode 100644 data-otservbr-global/monster/dragons/dragolisk.lua create mode 100644 data-otservbr-global/monster/dragons/mega_dragon.lua create mode 100644 data-otservbr-global/monster/dragons/war_dragon.lua create mode 100644 data-otservbr-global/scripts/movements/teleport/dragolisk_teleport.lua diff --git a/data-otservbr-global/monster/dragons/dragolisk.lua b/data-otservbr-global/monster/dragons/dragolisk.lua new file mode 100644 index 00000000000..26cf05df8ec --- /dev/null +++ b/data-otservbr-global/monster/dragons/dragolisk.lua @@ -0,0 +1,122 @@ +local mType = Game.createMonsterType("Dragolisk") +local monster = {} + +monster.description = "a dragolisk" +monster.experience = 5050 +monster.outfit = { + lookType = 1707, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.raceId = 2456 +monster.Bestiary = { + class = "Dragon", + race = BESTY_RACE_DRAGON, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 5, + Occurrence = 0, + Locations = "Nimmersatt's Breeding Ground", +} + +monster.health = 6180 +monster.maxHealth = 6180 +monster.race = "blood" +monster.corpse = 44654 +monster.speed = 165 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "Just Look at me!", yell = false }, + { text = "I'll stare you down", yell = false }, + { text = "Let me have a look", yell = false }, +} + +monster.loot = { + { name = "Platinum Coin", chance = 52610, minCount = 1, maxCount = 46 }, + { name = "Dragolisk Poison Gland", chance = 12600 }, + { name = "Nimmersatt's Seal", chance = 8320 }, + { id = 282, chance = 7580 }, + { name = "Dragolisk Eye", chance = 5510 }, + { name = "Green Gem", chance = 8260 }, + { name = "Dragon's Tail", chance = 1003 }, + { name = "Dragon Shield", chance = 400 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -200 }, + { name = "combat", interval = 3000, chance = 27, type = COMBAT_PHYSICALDAMAGE, minDamage = -220, maxDamage = -250, length = 7, spread = 0, effect = CONST_ME_EXPLOSIONAREA, target = false }, + { name = "combat", interval = 2500, chance = 25, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -250, radius = 4, effect = CONST_ME_POFF, target = true }, + { name = "death chain", interval = 2500, chance = 20, minDamage = -250, maxDamage = -300, range = 7 }, +} + +monster.defenses = { + defense = 86, + armor = 86, + mitigation = 2.11, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 15 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = -10 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -5 }, + { type = COMBAT_HOLYDAMAGE, percent = -15 }, + { type = COMBAT_DEATHDAMAGE, percent = -10 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/dragons/mega_dragon.lua b/data-otservbr-global/monster/dragons/mega_dragon.lua new file mode 100644 index 00000000000..66696435486 --- /dev/null +++ b/data-otservbr-global/monster/dragons/mega_dragon.lua @@ -0,0 +1,126 @@ +local mType = Game.createMonsterType("Mega Dragon") +local monster = {} + +monster.description = "a mega dragon" +monster.experience = 7810 +monster.outfit = { + lookType = 1712, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.raceId = 2459 +monster.Bestiary = { + class = "Dragon", + race = BESTY_RACE_DRAGON, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 5, + Occurrence = 0, + Locations = "Nimmersatt's Breeding Ground", +} + +monster.health = 7920 +monster.maxHealth = 7920 +monster.race = "blood" +monster.corpse = 44663 +monster.speed = 170 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "Just Look at me!", yell = false }, + { text = "I'll stare you down", yell = false }, + { text = "Let me have a look", yell = false }, +} + +monster.loot = { + { name = "Platinum Coin", chance = 50780, minCount = 1, maxCount = 48 }, + { name = "Nimmersatt's Seal", chance = 8670 }, + { id = 3039, chance = 10090 }, + { name = "Molten Dragon Essence", chance = 4720 }, + { name = "Prismatic Quartz", chance = 4460 }, + { name = "Rainbow Quartz", chance = 4430, minCount = 1, maxCount = 2 }, + { id = 3041, chance = 6790 }, + { name = "Mega Dragon Heart", chance = 1850 }, + { name = "Violet Gem", chance = 1470 }, + { name = "Dragon Slayer", chance = 130 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 }, + { name = "combat", interval = 2500, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = -250, maxDamage = -350, range = 2, effect = CONST_ME_BIG_SCRATCH, target = true }, + { name = "combat", interval = 2500, chance = 20, type = COMBAT_FIREDAMAGE, minDamage = -250, maxDamage = -300, length = 8, spread = 4, effect = CONST_ME_EXPLOSIONHIT, target = false }, + { name = "combat", interval = 2500, chance = 15, type = COMBAT_FIREDAMAGE, minDamage = -200, maxDamage = -300, range = 7, radius = 4, shootEffect = CONST_ANI_FIRE, effect = CONST_ME_FIREAREA, target = true }, + { name = "combat", interval = 3000, chance = 5, type = COMBAT_PHYSICALDAMAGE, minDamage = -200, maxDamage = -350, radius = 4, effect = CONST_ME_POFF, target = true }, + { name = "death chain", interval = 2500, chance = 15, minDamage = -200, maxDamage = -350, range = 7 }, +} + +monster.defenses = { + defense = 76, + armor = 76, + mitigation = 1.96, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = -10 }, + { type = COMBAT_EARTHDAMAGE, percent = -10 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = -10 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/dragons/war_dragon.lua b/data-otservbr-global/monster/dragons/war_dragon.lua new file mode 100644 index 00000000000..79d04bb2c0c --- /dev/null +++ b/data-otservbr-global/monster/dragons/war_dragon.lua @@ -0,0 +1,125 @@ +local mType = Game.createMonsterType("Wardragon") +local monster = {} + +monster.description = "a wardragon" +monster.experience = 5810 +monster.outfit = { + lookType = 1708, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.raceId = 2458 +monster.Bestiary = { + class = "Dragon", + race = BESTY_RACE_DRAGON, + toKill = 2500, + FirstUnlock = 100, + SecondUnlock = 1000, + CharmsPoints = 50, + Stars = 5, + Occurrence = 0, + Locations = "Nimmersatt's Breeding Ground", +} + +monster.health = 6960 +monster.maxHealth = 6960 +monster.race = "blood" +monster.corpse = 44656 +monster.speed = 165 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "Just Look at me!", yell = false }, + { text = "I'll stare you down", yell = false }, + { text = "Let me have a look", yell = false }, +} + +monster.loot = { + { name = "Platinum Coin", chance = 52560, minCount = 1, maxCount = 45 }, + { name = "Wardragon Claw", chance = 14420 }, + { name = "Nimmersatt's Seal", chance = 10620 }, + { name = "Dragon Tongue", chance = 8450 }, + { name = "Wardragon Tooth", chance = 6330 }, + { name = "Gold Ingot", chance = 6007 }, + { name = "Onyx Chip", chance = 4650, minCount = 1, maxCount = 2 }, + { name = "Black Pearl", chance = 4000 }, + { name = "White Gem", chance = 1000 }, + { name = "Dragonbone Staff", chance = 740 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -450 }, + { name = "combat", interval = 2500, chance = 20, type = COMBAT_DEATHDAMAGE, minDamage = -250, maxDamage = -400, radius = 4, effect = CONST_ME_MORTAREA, target = true }, + { name = "combat", interval = 3000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -300, maxDamage = -400, length = 8, spread = 4, effect = CONST_ME_EXPLOSIONHIT, target = false }, + { name = "combat", interval = 3000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = -275, maxDamage = -400, radius = 4, effect = CONST_ME_POFF, target = true }, + { name = "combat", interval = 3000, chance = 15, type = COMBAT_PHYSICALDAMAGE, minDamage = -250, maxDamage = -300, range = 2, effect = CONST_ME_BIG_SCRATCH, target = true }, +} + +monster.defenses = { + defense = 80, + armor = 80, + mitigation = 2.19, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = -5 }, + { type = COMBAT_EARTHDAMAGE, percent = -10 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = -10 }, + { type = COMBAT_HOLYDAMAGE, percent = -5 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/scripts/movements/teleport/dragolisk_teleport.lua b/data-otservbr-global/scripts/movements/teleport/dragolisk_teleport.lua new file mode 100644 index 00000000000..2c04c3f2a39 --- /dev/null +++ b/data-otservbr-global/scripts/movements/teleport/dragolisk_teleport.lua @@ -0,0 +1,26 @@ +local function teleportToDestination(creature, position) + if not creature:isPlayer() then + return true + end + + if position.x == 33216 and position.y == 31126 and position.z == 14 then + creature:teleportTo(Position(33186, 31190, 7)) + creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true + elseif position.x == 33187 and position.y == 31190 and position.z == 7 then + creature:teleportTo(Position(33217, 31123, 14)) + creature:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true + else + return false + end +end + +local dragoliskTeleport = MoveEvent() +function dragoliskTeleport.onStepIn(creature, item, position, fromPosition) + return teleportToDestination(creature, position) +end + +dragoliskTeleport:position({ x = 33216, y = 31126, z = 14 }) +dragoliskTeleport:position({ x = 33187, y = 31190, z = 7 }) +dragoliskTeleport:register() diff --git a/data/items/items.xml b/data/items/items.xml index 4978cf0bdc4..860a148fda2 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -76112,5 +76112,137 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a15477386a542ebb6b8c15fa742d4efc31fb5196 Mon Sep 17 00:00:00 2001 From: Majesty <32709570+majestyotbr@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:00:26 -0300 Subject: [PATCH 08/27] fix: data-canary errors and warnings (#2844) --- data-canary/monster/magicals/guzzlemaw.lua | 71 +++++++++--------- .../spells/monster/fury_skill_reducer.lua | 27 +++++++ data-canary/world/canary-house.xml | 2 +- data-canary/world/canary-zones.xml | 2 + data-canary/world/canary.otbm | Bin 19718929 -> 19718948 bytes 5 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 data-canary/scripts/spells/monster/fury_skill_reducer.lua create mode 100644 data-canary/world/canary-zones.xml diff --git a/data-canary/monster/magicals/guzzlemaw.lua b/data-canary/monster/magicals/guzzlemaw.lua index 8e38d459779..391a85118bc 100644 --- a/data-canary/monster/magicals/guzzlemaw.lua +++ b/data-canary/monster/magicals/guzzlemaw.lua @@ -23,8 +23,7 @@ monster.Bestiary = { CharmsPoints = 50, Stars = 4, Occurrence = 0, - Locations = "Guzzlemaw Valley, and a single spawn in a tower in Upper Roshamuul \z - (south of the Depot and west of the entrance to Roshamuul Prison).", + Locations = "Guzzlemaw Valley, and a single spawn in a tower in Upper Roshamuul (south of the Depot and west of the entrance to Roshamuul Prison).", } monster.health = 6400 @@ -64,7 +63,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { @@ -78,63 +76,64 @@ monster.voices = { } monster.loot = { - { id = 3031, chance = 100000, maxCount = 100 }, -- gold coin - { id = 3035, chance = 100000, maxCount = 7 }, -- platinum coin - { id = 3104, chance = 10700 }, -- banana skin - { id = 3110, chance = 10500 }, -- piece of iron - { id = 3111, chance = 9500 }, -- fishbone + { name = "gold coin", chance = 100000, maxCount = 100 }, + { name = "platinum coin", chance = 100000, maxCount = 7 }, + { name = "banana skin", chance = 10700 }, + { name = "piece of iron", chance = 10500 }, + { name = "fishbone", chance = 9500 }, { id = 3114, chance = 10400 }, -- skull { id = 3115, chance = 9200 }, -- bone { id = 3116, chance = 4500 }, -- big bone - { id = 3265, chance = 2700 }, -- two handed sword + { name = "two handed sword", chance = 2700 }, { id = 3578, chance = 7000, maxCount = 3 }, -- fish - { id = 3582, chance = 10000 }, -- ham - { id = 5880, chance = 3000 }, -- iron ore - { id = 5895, chance = 5000 }, -- fish fin - { id = 5925, chance = 5700 }, -- hardened bone + { name = "ham", chance = 10000 }, + { name = "iron ore", chance = 3000 }, + { name = "fish fin", chance = 5000 }, + { name = "hardened bone", chance = 5700 }, { id = 5951, chance = 9400 }, -- fish tail - { id = 7404, chance = 1000 }, -- assassin dagger - { id = 7407, chance = 2000 }, -- haunted blade - { id = 7418, chance = 380 }, -- nightmare blade - { id = 238, chance = 17000, maxCount = 3 }, -- great mana potion - { id = 239, chance = 18500, maxCount = 2 }, -- great health potion - { id = 10389, chance = 1200 }, -- sai - { id = 16120, chance = 3000 }, -- violet crystal shard - { id = 16123, chance = 12000, maxCount = 2 }, -- brown crystal splinter - { id = 16126, chance = 7600 }, -- red crystal fragment + { name = "assassin dagger", chance = 1000 }, + { name = "haunted blade", chance = 2000 }, + { name = "nightmare blade", chance = 380 }, + { name = "great mana potion", chance = 17000, maxCount = 3 }, + { name = "great health potion", chance = 18500, maxCount = 2 }, + { name = "sai", chance = 1200 }, + { name = "violet crystal shard", chance = 3000 }, + { name = "brown crystal splinter", chance = 12000, maxCount = 2 }, + { name = "red crystal fragment", chance = 7600 }, { id = 16279, chance = 12000 }, -- crystal rubbish - { id = 20062, chance = 920 }, -- cluster of solace - { id = 20198, chance = 15000 }, -- frazzle tongue - { id = 20199, chance = 14000 }, -- frazzle skin + { name = "cluster of solace", chance = 8920 }, + { name = "frazzle tongue", chance = 15000 }, + { name = "frazzle skin", chance = 14000 }, } monster.attacks = { { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -499 }, -- bleed - { name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, target = false }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -900, length = 8, spread = 3, effect = CONST_ME_EXPLOSIONAREA, target = false }, + { name = "condition", type = CONDITION_BLEEDING, interval = 2000, chance = 10, minDamage = -500, maxDamage = -1000, radius = 3, effect = CONST_ME_DRAWBLOOD, target = false }, + { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -900, length = 8, spread = 0, effect = CONST_ME_EXPLOSIONAREA, target = false }, { name = "combat", interval = 2000, chance = 20, type = COMBAT_PHYSICALDAMAGE, minDamage = 0, maxDamage = -500, radius = 2, shootEffect = CONST_ANI_LARGEROCK, effect = CONST_ME_STONES, target = true }, - { name = "speed", interval = 2000, chance = 15, speedChange = -100, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 3, effect = CONST_ME_MAGIC_RED, target = false }, + { name = "speed", interval = 2000, chance = 15, speedChange = -800, radius = 6, effect = CONST_ME_MAGIC_RED, target = false, duration = 15000 }, + { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = 0, maxDamage = -800, length = 8, spread = 0, effect = CONST_ME_MAGIC_RED, target = false }, } monster.defenses = { defense = 50, - armor = 50, + armor = 74, + mitigation = 2.31, { name = "combat", interval = 2000, chance = 20, type = COMBAT_HEALING, minDamage = 250, maxDamage = 425, effect = CONST_ME_HITBYPOISON, target = false }, } monster.elements = { - { type = COMBAT_PHYSICALDAMAGE, percent = 10 }, - { type = COMBAT_ENERGYDAMAGE, percent = 5 }, - { type = COMBAT_EARTHDAMAGE, percent = 15 }, - { type = COMBAT_FIREDAMAGE, percent = 5 }, + { type = COMBAT_PHYSICALDAMAGE, percent = 5 }, + { type = COMBAT_ENERGYDAMAGE, percent = 15 }, + { type = COMBAT_EARTHDAMAGE, percent = 20 }, + { type = COMBAT_FIREDAMAGE, percent = 10 }, { type = COMBAT_LIFEDRAIN, percent = 0 }, { type = COMBAT_MANADRAIN, percent = 0 }, { type = COMBAT_DROWNDAMAGE, percent = 0 }, { type = COMBAT_ICEDAMAGE, percent = 5 }, - { type = COMBAT_HOLYDAMAGE, percent = 0 }, - { type = COMBAT_DEATHDAMAGE, percent = 5 }, + { type = COMBAT_HOLYDAMAGE, percent = -5 }, + { type = COMBAT_DEATHDAMAGE, percent = 10 }, } monster.immunities = { diff --git a/data-canary/scripts/spells/monster/fury_skill_reducer.lua b/data-canary/scripts/spells/monster/fury_skill_reducer.lua new file mode 100644 index 00000000000..1e8acd0ae7a --- /dev/null +++ b/data-canary/scripts/spells/monster/fury_skill_reducer.lua @@ -0,0 +1,27 @@ +local combat = {} + +for i = 65, 80 do + combat[i] = Combat() + combat[i]:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_SOUND_YELLOW) + + local condition = Condition(CONDITION_ATTRIBUTES) + condition:setParameter(CONDITION_PARAM_TICKS, 5000) + condition:setParameter(CONDITION_PARAM_SKILL_DEFENSEPERCENT, i) + + local area = createCombatArea(AREA_CIRCLE3X3) + combat[i]:setArea(area) + combat[i]:addCondition(condition) +end + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat[math.random(65, 80)]:execute(creature, var) +end + +spell:name("fury skill reducer") +spell:words("###2") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:register() diff --git a/data-canary/world/canary-house.xml b/data-canary/world/canary-house.xml index adaa1a1c8f2..43d8e80d11e 100644 --- a/data-canary/world/canary-house.xml +++ b/data-canary/world/canary-house.xml @@ -1,4 +1,4 @@ - + diff --git a/data-canary/world/canary-zones.xml b/data-canary/world/canary-zones.xml new file mode 100644 index 00000000000..a9224bd3c2d --- /dev/null +++ b/data-canary/world/canary-zones.xml @@ -0,0 +1,2 @@ + + diff --git a/data-canary/world/canary.otbm b/data-canary/world/canary.otbm index 280d57a9194069ec839bb8cab86c1ffbb960ccf8..8f787811a5395efb0e0cbbb2ac7f34ec12b9e0be 100644 GIT binary patch delta 1798 zcmYk)c~}%>9KdmAVRu|tg_VI_L6l1ogtE-+rnJKp)3nm=$Sfkm)XcO?p&E}&t#qmA zpbVnI4AUV?L~x-}3%e{jtVFb$?m|)7_50gpdG?Rb_j!Kv&g{E8`>tu)WM7YsHYM5} z6EP#dD1Yw4oJF&XX3i_ID7r-v7DOTnR@k7T7wqT_2l}8d`XL%IaH2nAp&<_OhDC85 z$Wy%uexI#TGt7yuW~&-$4NhC9x(;fpB{=C71unQT00~IMKqO%h1|u0MNJSdbF$6=A zfnmr*7KS4mIXDHUVgydZ=@^MKFbZd4G|s}=I0xtAJe-dUkc$g(5yoIFF2*Ie6qjKf zF2{JIqF}W`iMRrjjKY%1<$1O}n%to~TPEX5Ou;5N*}?YIL4xD$oA%W$^LS~A+US2p+RrRv=%!ff1wIhc!i zxEJ$r9~R(#EW{!_fCupq9>ybBj3p>W2_D5`c-$ye^(l^=!-;HqNU}PP%yIbrK5NHu zd$r=Tt#!+AE!d0C@HxJ~m-q_%@HM_cE55~d_#QuCKYqkd__;Ev^+0v- z7&m#+^tKe+?!b{Y#_MhCQo9%Q_Nq1rLI+J@`2=Q4#SGn(9&N&BfvOO!9Q> zFh_>27$X|Y1|>9kIvdU6o}qZBxx8lxU(w6FCVs~s_!EEOZ~TLQ@gM$22aezOqKeV*Ummvhd}-iMVF|eaA9Ee3f^v3`UgcE}h2N&Xzpn6U2jy&5oS2$!1B&y*_?q-WI znJsF|6!%`2F-2LFx*|6`@FEdO7>r~L!BC_i6=_I^55q7V85n^~WML$-F$yPOG;(kv zPQnu^10U?y(B zjkpOn;}#U*R@{c$RbT6@x#O(?dAv5I%613t#9g=>vvCjZ#eJBA`|$uC#6x%(kKj=h z<1svrComT!D8-YQhxuxiO(}4US`^Ps_a~8klG%=MIAl3=%wBB@S<5{#96lN{-bSuVUwVCe&jywqPq7unmm}pb5?R2p{7UY{w3?;8T2t zRO* z>t0ZTm7Um&pRo`7@e6)cgO$H^&gl88Jq~TdD&t*d+WpIFwXB{&ZL8Jt96giYS0Ax7 zwO-4OGX9Ze*!@elXiD#pxm7Fc9ax#{w>M~GoceLfu!lKM(R~v7xMKXHcjd>cGaIxO zrrzzbjao_XP~I4Ep~w}bCZOSW{DA{Fh(GZc{>DG}7l&{dM-aqO9K&&RA%yUPnt*8` zBvBHxBwAvTSS2<|jKnVKD{)9-CH*A*B?BY_B~Hm8Nu0zbiI*fu+!Bw(D@l|jNd`-j oB|{`bB`K0rNtz^G;*$)M43}g`Mo2OxS(1^a%{75+)12i007O(&kpKVy From 8056ca6ee747de90bf5bb053bde6f114f42af1fc Mon Sep 17 00:00:00 2001 From: Majesty <32709570+majestyotbr@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:45:55 -0300 Subject: [PATCH 09/27] fix: datapacks tools actions (#2851) --- .../scripts/actions/tools/kitchen_knife.lua | 8 -- data-canary/scripts/actions/tools/sickle.lua | 8 -- data-canary/scripts/lib/register_actions.lua | 100 ++++++++------- .../scripts/lib/register_actions.lua | 115 ++---------------- .../scripts/actions/tools/sickle.lua | 0 src/canary_server.cpp | 1 + 6 files changed, 67 insertions(+), 165 deletions(-) delete mode 100644 data-canary/scripts/actions/tools/kitchen_knife.lua delete mode 100644 data-canary/scripts/actions/tools/sickle.lua rename {data-otservbr-global => data}/scripts/actions/tools/sickle.lua (100%) diff --git a/data-canary/scripts/actions/tools/kitchen_knife.lua b/data-canary/scripts/actions/tools/kitchen_knife.lua deleted file mode 100644 index 921baf9092d..00000000000 --- a/data-canary/scripts/actions/tools/kitchen_knife.lua +++ /dev/null @@ -1,8 +0,0 @@ -local kitchenKnife = Action() - -function kitchenKnife.onUse(player, item, fromPosition, target, toPosition, isHotkey) - return ActionsLib.useKitchenKnife(player, item, fromPosition, target, toPosition, isHotkey) -end - -kitchenKnife:id(3469) -kitchenKnife:register() diff --git a/data-canary/scripts/actions/tools/sickle.lua b/data-canary/scripts/actions/tools/sickle.lua deleted file mode 100644 index a4e024dde20..00000000000 --- a/data-canary/scripts/actions/tools/sickle.lua +++ /dev/null @@ -1,8 +0,0 @@ -local sickle = Action() - -function sickle.onUse(player, item, fromPosition, target, toPosition, isHotkey) - return ActionsLib.useSickle(player, item, fromPosition, target, toPosition, isHotkey) or ActionsLib.destroyItem(player, target, toPosition) -end - -sickle:id(3293, 3306, 32595) -sickle:register() diff --git a/data-canary/scripts/lib/register_actions.lua b/data-canary/scripts/lib/register_actions.lua index 7c0aa49102e..229d385b0de 100644 --- a/data-canary/scripts/lib/register_actions.lua +++ b/data-canary/scripts/lib/register_actions.lua @@ -1,12 +1,17 @@ -local holeId = { 294, 369, 370, 385, 394, 411, 412, 413, 432, 433, 435, 8709, 594, 595, 615, 609, 610, 615, 1156, 482, 483, 868, 874, 4824, 7768, 433, 432, 413, 7767, 411, 370, 369, 7737, 7755, 7768, 7767, 7515, 7516, 7517, 7518, 7519, 7520, 7521, 7522, 7762, 8144, 8690, 8709, 12203, 12961, 17239, 19220, 23364 } -- usable rope holes, for rope spots see global.lua -local wildGrowth = { 2130, 2130, 2982, 2524, 2030, 2029, 10182 } -- wild growth destroyable by machete -local jungleGrass = { [3696] = 3695, [3702] = 3701, [17153] = 17151 } -- grass destroyable by machete -local groundIds = { 354, 355 } -- pick usable ground +local holeId = { 294, 369, 370, 385, 394, 411, 412, 413, 432, 433, 435, 482, 483, 594, 595, 609, 610, 615, 868, 874, 1156, 4824, 7515, 7516, 7517, 7518, 7519, 7520, 7521, 7522, 7737, 7755, 7762, 7767, 7768, 8144, 8690, 8709, 12203, 12961, 17239, 19220, 23364 } -- usable rope holes, for rope spots see global.lua +local wildGrowth = { 3635, 30224 } -- wild growth destroyable by machete +local jungleGrass = { -- grass destroyable by machete + [3696] = 3695, + [3702] = 3701, + [17153] = 17151, +} +local groundIds = { 354, 355 } -- pick usable grounds local sandIds = { 231 } -- desert sand -local fruits = { 2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2684, 2685, 5097, 8839, 8840, 8841 } -- fruits to make decorated cake with knife +local fruits = { 3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3595, 3596, 5096, 8011, 8012, 8013 } -- fruits to make decorated cake with knife local holes = { 593, 606, 608, 867, 21341 } -- holes opened by shovel +local ropeSpots = { 386, 421, 12935, 12936, 14238, 17238, 21501, 21965, 21966, 21967, 21968, 23363 } -function destroyItem(player, target, toPosition) +function destroyItem(player, item, fromPosition, target, toPosition, isHotkey) if type(target) ~= "userdata" or not target:isItem() then return false end @@ -40,10 +45,8 @@ function destroyItem(player, target, toPosition) end end end - target:remove(1) end - toPosition:sendMagicEffect(CONST_ME_POFF) return true end @@ -67,7 +70,7 @@ function onUseMachete(player, item, fromPosition, target, toPosition, isHotkey) player:addAchievementProgress("Nothing Can Stop Me", 100) return true end - return destroyItem(player, target, toPosition) + return destroyItem(player, item, fromPosition, target, toPosition, isHotkey) end function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) @@ -82,7 +85,6 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) else player:addItem(3028) -- 49% chance of getting small diamond end - player:addAchievementProgress("Petrologist", 100) target:getPosition():sendMagicEffect(CONST_ME_BLOCKHIT) target:remove(1) @@ -99,7 +101,7 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) return false end - if table.contains(groundIds, ground.itemid) and ground.actionid == actionIds.pickHole then + if table.contains(groundIds, ground.itemid) and (ground:hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) or ground:hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) then ground:transform(394) ground:decay() toPosition:sendMagicEffect(CONST_ME_POFF) @@ -127,41 +129,28 @@ function onUseRope(player, item, fromPosition, target, toPosition, isHotkey) local ground = tile:getGround() if ground and table.contains(ropeSpots, ground:getId()) or tile:getItemById(12935) then - tile = Tile(toPosition:moveUpstairs()) - if not tile then - return false - end - - if tile:hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then + if Tile(toPosition:moveUpstairs()):hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then player:sendCancelMessage(RETURNVALUE_PLAYERISPZLOCKED) return true end - player:teleportTo(toPosition, false) return true - end - - if table.contains(holeId, target.itemid) then + elseif table.contains(holeId, target.itemid) then toPosition.z = toPosition.z + 1 tile = Tile(toPosition) - if not tile then - return false - end - - local thing = tile:getTopVisibleThing() - if not thing then - return true - end - - if thing:isPlayer() then - if Tile(toPosition:moveUpstairs()):queryAdd(thing) ~= RETURNVALUE_NOERROR then - return false + if tile then + local thing = tile:getTopVisibleThing() + if thing:isPlayer() then + if Tile(toPosition:moveUpstairs()):hasFlag(TILESTATE_PROTECTIONZONE) and thing:isPzLocked() then + return false + end + return thing:teleportTo(toPosition, false) + end + if thing:isItem() and thing:getType():isMovable() then + return thing:moveTo(toPosition:moveUpstairs()) end - - return thing:teleportTo(toPosition, false) - elseif thing:isItem() and thing:getType():isMovable() then - return thing:moveTo(toPosition:moveUpstairs()) end + player:sendCancelMessage(RETURNVALUE_NOTPOSSIBLE) return true end return false @@ -182,7 +171,13 @@ function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) if table.contains(holes, groundId) then ground:transform(groundId + 1) ground:decay() - + toPosition:moveDownstairs() + toPosition.y = toPosition.y - 1 + if Tile(toPosition):hasFlag(TILESTATE_PROTECTIONZONE) and player:isPzLocked() then + player:sendCancelMessage(RETURNVALUE_PLAYERISPZLOCKED) + return true + end + player:teleportTo(toPosition, false) toPosition.z = toPosition.z + 1 tile:relocateTo(toPosition) player:addAchievementProgress("The Undertaker", 500) @@ -192,14 +187,14 @@ function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) player:addAchievementProgress("The Undertaker", 500) elseif target.itemid == 17950 then -- swamp digging if not player:hasExhaustion("swamp-digging") then - local chance = math.random(100) - if chance >= 1 and chance <= 42 then + local chance = math.random(1, 100) + if chance <= 42 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a dead snake.") player:addItem(4259) elseif chance >= 43 and chance <= 79 then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a small diamond.") player:addItem(3028) - elseif chance >= 80 then + else player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You dug up a leech.") player:addItem(17858) end @@ -209,16 +204,15 @@ function onUseShovel(player, item, fromPosition, target, toPosition, isHotkey) end elseif table.contains(sandIds, groundId) then local randomValue = math.random(1, 100) - if target.actionid == actionIds.sandHole and randomValue <= 20 then + if target.actionid == 100 and randomValue <= 20 then ground:transform(615) ground:decay() elseif randomValue == 1 then - Game.createItem(3042, 1, toPosition) + Game.createItem(3042, 1, toPosition) -- Scarab Coin player:addAchievementProgress("Gold Digger", 100) elseif randomValue > 95 then Game.createMonster("Scarab", toPosition) end - toPosition:sendMagicEffect(CONST_ME_POFF) else return false @@ -238,6 +232,13 @@ function onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) player:addAchievementProgress("Happy Farmer", 200) return true end + return destroyItem(player, item, fromPosition, target, toPosition, isHotkey) +end + +function onUseSickle(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({ 3293, 3306 }, item.itemid) then + return false + end if target.itemid == 5464 then -- burning sugar cane target:transform(5463) @@ -246,14 +247,14 @@ function onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) player:addAchievementProgress("Natural Sweetener", 50) return true end - return destroyItem(player, target, toPosition) + return destroyItem(player, item, fromPosition, target, toPosition, isHotkey) end function onUseCrowbar(player, item, fromPosition, target, toPosition, isHotkey) if not table.contains({ 3304, 9598 }, item.itemid) then return false end - return destroyItem(player, target, toPosition) + return destroyItem(player, item, fromPosition, target, toPosition, isHotkey) end function onUseKitchenKnife(player, item, fromPosition, target, toPosition, isHotkey) @@ -269,3 +270,10 @@ function onUseKitchenKnife(player, item, fromPosition, target, toPosition, isHot end return false end + +function onUseSpoon(player, item, fromPosition, target, toPosition, isHotkey) + if not table.contains({ 3468, 3470 }, item.itemid) then + return false + end + return false +end diff --git a/data-otservbr-global/scripts/lib/register_actions.lua b/data-otservbr-global/scripts/lib/register_actions.lua index 6df6b96473a..b696d44c9c6 100644 --- a/data-otservbr-global/scripts/lib/register_actions.lua +++ b/data-otservbr-global/scripts/lib/register_actions.lua @@ -1,106 +1,12 @@ -local holeId = { - 294, - 369, - 370, - 385, - 394, - 411, - 412, - 413, - 432, - 433, - 435, - 8709, - 594, - 595, - 615, - 609, - 610, - 615, - 1156, - 482, - 483, - 868, - 874, - 4824, - 7768, - 433, - 432, - 413, - 7767, - 411, - 370, - 369, - 7737, - 7755, - 7768, - 7767, - 7515, - 7516, - 7517, - 7518, - 7519, - 7520, - 7521, - 7522, - 7762, - 8144, - 8690, - 8709, - 12203, - 12961, - 17239, - 19220, - 23364, -} - -local Itemsgrinder = { +local holeId = { 294, 369, 370, 385, 394, 411, 412, 413, 432, 433, 435, 482, 483, 594, 595, 609, 610, 615, 868, 874, 1156, 4824, 7515, 7516, 7517, 7518, 7519, 7520, 7521, 7522, 7737, 7755, 7762, 7767, 7768, 8144, 8690, 8709, 12203, 12961, 17239, 19220, 23364 } +local itemsGrinder = { [675] = { item_id = 30004, effect = CONST_ME_BLUE_FIREWORKS }, -- Sapphire dust [16122] = { item_id = 21507, effect = CONST_ME_GREENSMOKE }, -- Pinch of crystal dust } - -local holes = { - 593, - 606, - 608, - 867, - 21341, -} - -local JUNGLE_GRASS = { - 3696, - 3702, - 17153, -} -local WILD_GROWTH = { - 2130, - 2130, - 2982, - 2524, - 2030, - 2029, - 10182, -} - -local fruits = { - 3584, - 3585, - 3586, - 3587, - 3588, - 3589, - 3590, - 3591, - 3592, - 3593, - 3595, - 3596, - 5096, - 8011, - 8012, - 8013, -} - +local holes = { 593, 606, 608, 867, 21341 } +local jungleGrass = { 3696, 3702, 17153 } +local wildGrowth = { 3635, 30224 } +local fruits = { 3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3595, 3596, 5096, 8011, 8012, 8013 } local lava = { Position(32808, 32336, 11), Position(32809, 32336, 11), @@ -867,13 +773,13 @@ function onUsePick(player, item, fromPosition, target, toPosition, isHotkey) end function onUseMachete(player, item, fromPosition, target, toPosition, isHotkey) - if table.contains(JUNGLE_GRASS, target.itemid) then + if table.contains(jungleGrass, target.itemid) then target:transform(target.itemid == 17153 and 17151 or target.itemid - 1) target:decay() return true end - if table.contains(WILD_GROWTH, target.itemid) then + if table.contains(wildGrowth, target.itemid) then toPosition:sendMagicEffect(CONST_ME_POFF) target:remove() return true @@ -1035,14 +941,17 @@ function onUseScythe(player, item, fromPosition, target, toPosition, isHotkey) target:transform(5463) target:decay() Game.createItem(5466, 1, toPosition) + return true elseif target.itemid == 3653 then -- wheat target:transform(3651) target:decay() Game.createItem(3605, 1, toPosition) + return true elseif target.itemid == 30623 then -- reed target:transform(30624) target:decay() Game.createItem(30975, 1, toPosition) + return true -- The secret library elseif toPosition == Position(32177, 31925, 7) then player:teleportTo({ x = 32515, y = 32535, z = 12 }) @@ -1135,7 +1044,7 @@ function onGrindItem(player, item, fromPosition, target, toPosition) if not (target.itemid == 21573) then return false end - for index, value in pairs(Itemsgrinder) do + for index, value in pairs(itemsGrinder) do if item.itemid == index then local topParent = item:getTopParent() if topParent.isItem and (not topParent:isItem() or topParent.itemid ~= 470) then diff --git a/data-otservbr-global/scripts/actions/tools/sickle.lua b/data/scripts/actions/tools/sickle.lua similarity index 100% rename from data-otservbr-global/scripts/actions/tools/sickle.lua rename to data/scripts/actions/tools/sickle.lua diff --git a/src/canary_server.cpp b/src/canary_server.cpp index e49a86d7d9f..cca280b69fa 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -367,6 +367,7 @@ void CanaryServer::loadModules() { modulesLoadHelper(g_modules().loadFromXml(), "modules/modules.xml"); logger.debug("Loading datapack scripts on folder: {}/", datapackName); + modulesLoadHelper(g_scripts().loadScripts(datapackFolder + "/scripts/lib", true, false), datapackFolder + "/scripts/libs"); // Load scripts modulesLoadHelper(g_scripts().loadScripts(datapackFolder + "/scripts", false, false), datapackFolder + "/scripts"); // Load monsters From d4b1b61c6c3f982606bf0122508486159eae66fa Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 5 Sep 2024 21:16:38 -0300 Subject: [PATCH 10/27] fix: remove bakragore icons talkaction (#2828) --- .../talkactions/god/icons_functions.lua | 6 +----- src/creatures/players/player.cpp | 14 ++++++++++++++ src/creatures/players/player.hpp | 2 ++ .../creatures/player/player_functions.cpp | 19 +++++++++++++++++++ .../creatures/player/player_functions.hpp | 2 ++ 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/data/scripts/talkactions/god/icons_functions.lua b/data/scripts/talkactions/god/icons_functions.lua index 9f1ee85c0f3..059d7ac6ed9 100644 --- a/data/scripts/talkactions/god/icons_functions.lua +++ b/data/scripts/talkactions/god/icons_functions.lua @@ -78,11 +78,7 @@ function bakragoreIcon.onSay(player, words, param) end if param == "remove" then - for i = 1, 10 do - if player:hasCondition(CONDITION_BAKRAGORE, i) then - player:removeCondition(CONDITION_BAKRAGORE, i) - end - end + player:removeIconBakragore() player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Removed all Bakragore icons.") return true end diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 45f5808d633..9ba9f3b74d6 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -6209,6 +6209,20 @@ void Player::sendIconBakragore(const IconBakragore icon) { } } +void Player::removeBakragoreIcons() { + for (auto icon : magic_enum::enum_values()) { + if (hasCondition(CONDITION_BAKRAGORE, enumToValue(icon))) { + removeCondition(CONDITION_BAKRAGORE, CONDITIONID_DEFAULT, true); + } + } +} + +void Player::removeBakragoreIcon(const IconBakragore icon) { + if (hasCondition(CONDITION_BAKRAGORE, enumToValue(icon))) { + removeCondition(CONDITION_BAKRAGORE, CONDITIONID_DEFAULT, true); + } +} + void Player::sendCyclopediaCharacterAchievements(uint16_t secretsUnlocked, std::vector> achievementsUnlocked) { if (client) { client->sendCyclopediaCharacterAchievements(secretsUnlocked, achievementsUnlocked); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 83515b2e004..5a703a1d0bf 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -1422,6 +1422,8 @@ class Player final : public Creature, public Cylinder, public Bankable { void sendClosePrivate(uint16_t channelId); void sendIcons(); void sendIconBakragore(const IconBakragore icon); + void removeBakragoreIcons(); + void removeBakragoreIcon(const IconBakragore icon); void sendClientCheck() const { if (client) { client->sendClientCheck(); diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index d360b482a7b..7bbe495f7c2 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -4412,3 +4412,22 @@ int PlayerFunctions::luaPlayerSendIconBakragore(lua_State* L) { pushBoolean(L, true); return 1; } + +int PlayerFunctions::luaPlayerRemoveIconBakragore(lua_State* L) { + // player:removeIconBakragore(iconType or nil for remove all bakragore icons) + const auto &player = getUserdataShared(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + auto iconType = getNumber(L, 2, IconBakragore::None); + if (iconType == IconBakragore::None) { + player->removeBakragoreIcons(); + } else { + player->removeBakragoreIcon(iconType); + } + + pushBoolean(L, true); + return 1; +} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 000c5f755c0..7cee43424cd 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -378,6 +378,7 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "takeScreenshot", PlayerFunctions::luaPlayerTakeScreenshot); registerMethod(L, "Player", "sendIconBakragore", PlayerFunctions::luaPlayerSendIconBakragore); + registerMethod(L, "Player", "removeIconBakragore", PlayerFunctions::luaPlayerRemoveIconBakragore); GroupFunctions::init(L); GuildFunctions::init(L); @@ -743,6 +744,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerTakeScreenshot(lua_State* L); static int luaPlayerSendIconBakragore(lua_State* L); + static int luaPlayerRemoveIconBakragore(lua_State* L); friend class CreatureFunctions; }; From dec6ad0f04bf8cdec78917376077d0189d56572e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Morais?= Date: Fri, 6 Sep 2024 10:35:31 -0300 Subject: [PATCH 11/27] fix: container:addItem missing return (#2857) --- data-otservbr-global/monster/reptiles/two-headed_turtle.lua | 2 +- data/libs/functions/container.lua | 6 +++--- src/lua/functions/items/container_functions.cpp | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data-otservbr-global/monster/reptiles/two-headed_turtle.lua b/data-otservbr-global/monster/reptiles/two-headed_turtle.lua index 2d1c2090b37..50b49aa8cc5 100644 --- a/data-otservbr-global/monster/reptiles/two-headed_turtle.lua +++ b/data-otservbr-global/monster/reptiles/two-headed_turtle.lua @@ -70,7 +70,7 @@ monster.loot = { { name = "two-headed turtle heads", chance = 8700 }, { name = "strong mana potion", chance = 13373 }, { name = "hydrophytes", chance = 11000 }, - { id = 1047, chance = 6388 }, -- bone + { id = 3115, chance = 6388 }, -- bone { name = "glacier shoes", chance = 4650 }, { id = 281, chance = 3582 }, -- giant shimmering pearl (green) { name = "small tropical fish", chance = 3582 }, diff --git a/data/libs/functions/container.lua b/data/libs/functions/container.lua index f932b37dfe4..824a12f5e02 100644 --- a/data/libs/functions/container.lua +++ b/data/libs/functions/container.lua @@ -20,7 +20,7 @@ function Container:addLoot(loot) local countToAdd = math.min(remainingCount, stackSize) local tmpItem = self:addItem(itemId, countToAdd, INDEX_WHEREEVER, FLAG_NOLIMIT) if not tmpItem then - logger.warn("Container:addLoot: failed to add stackable item: {}, to corpse {} with id {}", ItemType(itemId):getName(), self:getName(), self:getId()) + logger.warn("Container:addLoot: failed to add stackable item: {} with id {}, to corpse {} with id {}", ItemType(itemId):getName(), itemId, self:getName(), self:getId()) goto continue end remainingCount = remainingCount - countToAdd @@ -28,13 +28,13 @@ function Container:addLoot(loot) elseif iType:getCharges() ~= 0 then local tmpItem = self:addItem(itemId, item.count, INDEX_WHEREEVER, FLAG_NOLIMIT) if not tmpItem then - logger.warn("Container:addLoot: failed to add charge item: {}, to corpse {} with id {}", ItemType(itemId):getName(), self:getName(), self:getId()) + logger.warn("Container:addLoot: failed to add charge item: {} with id {}, to corpse {} with id {}", ItemType(itemId):getName(), itemId, self:getName(), self:getId()) end else for i = 1, item.count do local tmpItem = self:addItem(itemId, 1, INDEX_WHEREEVER, FLAG_NOLIMIT) if not tmpItem then - logger.warn("Container:addLoot: failed to add item: {}, to corpse {} with id {}", ItemType(itemId):getName(), self:getName(), self:getId()) + logger.warn("Container:addLoot: failed to add item: {} with id {}, to corpse {} with id {}", ItemType(itemId):getName(), itemId, self:getName(), self:getId()) goto continue end diff --git a/src/lua/functions/items/container_functions.cpp b/src/lua/functions/items/container_functions.cpp index a2251de641c..30eb26c6801 100644 --- a/src/lua/functions/items/container_functions.cpp +++ b/src/lua/functions/items/container_functions.cpp @@ -166,6 +166,7 @@ int ContainerFunctions::luaContainerAddItem(lua_State* L) { setItemMetatable(L, -1, item); } else { reportErrorFunc(fmt::format("Cannot add item to container, error code: '{}'", getReturnMessage(ret))); + pushBoolean(L, false); } return 1; } From b330a33d90d114eb383ecab1c389aed4e7a2131a Mon Sep 17 00:00:00 2001 From: Karin Date: Mon, 9 Sep 2024 14:22:52 -0300 Subject: [PATCH 12/27] fix: fix load unique items from map (#2881) This fix the load of unique items. Enable purchasing backpacks when the player doesn't have a main backpack --- .../startup/others/functions.lua | 19 ++++++++++++------- src/creatures/npcs/npc.cpp | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/data-otservbr-global/startup/others/functions.lua b/data-otservbr-global/startup/others/functions.lua index 8624dc8b268..362e2a38b97 100644 --- a/data-otservbr-global/startup/others/functions.lua +++ b/data-otservbr-global/startup/others/functions.lua @@ -28,7 +28,7 @@ function loadLuaMapAction(tablename) if not value.itemId == false and tile:getItemCountById(value.itemId) == 0 then logger.error("[loadLuaMapAction] - Wrong item id {} found", value.itemId) logger.warn("Action id: {}, position {}", index, tile:getPosition():toString()) - break + goto continue end if value.itemId ~= false and tile:getItemCountById(value.itemId) > 0 then @@ -49,6 +49,7 @@ function loadLuaMapAction(tablename) tile:getGround():setAttribute(ITEM_ATTRIBUTE_ACTIONID, index) end end + ::continue:: end end end @@ -64,12 +65,12 @@ function loadLuaMapUnique(tablename) if not value.itemId == false and tile:getItemCountById(value.itemId) == 0 then logger.error("[loadLuaMapUnique] - Wrong item id {} found", value.itemId) logger.warn("Unique id: {}, position {}", index, tile:getPosition():toString()) - break + goto continue end if tile:getItemCountById(value.itemId) < 1 or value.itemId == false then logger.warn("[loadLuaMapUnique] - Wrong item id {} found", value.itemId) logger.warn("Unique id: {}, position {}, item id: wrong", index, tile:getPosition():toString()) - break + goto continue end item = tile:getItemById(value.itemId) -- If he found the item, add the unique id @@ -77,6 +78,8 @@ function loadLuaMapUnique(tablename) item:setAttribute(ITEM_ATTRIBUTE_UNIQUEID, index) end end + + ::continue:: end end @@ -91,7 +94,7 @@ function loadLuaMapSign(tablename) if tile:getItemCountById(value.itemId) == 0 then logger.error("[loadLuaMapSign] - Wrong item id {} found", value.itemId) logger.warn("Sign id: {}, position {}, item id: wrong", index, tile:getPosition():toString()) - break + goto continue end if tile:getItemCountById(value.itemId) == 1 then item = tile:getItemById(value.itemId) @@ -101,6 +104,7 @@ function loadLuaMapSign(tablename) item:setAttribute(ITEM_ATTRIBUTE_TEXT, value.text) end end + ::continue:: end end @@ -137,17 +141,18 @@ function loadLuaMapBookDocument(tablename) totals[2] = totals[2] + 1 else logger.warn("[loadLuaMapBookDocument] - Item not found! Index: {}, itemId: {}", index, value.itemId) - break + goto continue end else logger.warn("[loadLuaMapBookDocument] - Container not found! Index: {}, containerId: {}", index, value.containerId) - break + goto continue end else logger.warn("[loadLuaMapBookDocument] - Tile not found! Index: {}, position: x: {} y: {} z: {}", index, value.position.x, value.position.y, value.position.z) - break + goto continue end end + ::continue:: end if totals[1] == totals[2] then logger.debug("Loaded {} books and documents in the map", totals[2]) diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index 18ca0dd0758..6d38524aef3 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -234,8 +234,8 @@ void Npc::onPlayerBuyItem(std::shared_ptr player, uint16_t itemId, uint8 return; } - // Check if the player not have empty slots - if (!ignore && player->getFreeBackpackSlots() == 0) { + // Check if the player not have empty slots or the item is not a container + if (!ignore && (player->getFreeBackpackSlots() == 0 && (player->getInventoryItem(CONST_SLOT_BACKPACK) || (!Item::items[itemId].isContainer() || !(Item::items[itemId].slotPosition & SLOTP_BACKPACK))))) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); return; } From a9e48759de7ab41bc42010cd88520757b47d4da0 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Lisboa Date: Wed, 11 Sep 2024 11:10:25 -0300 Subject: [PATCH 13/27] fix: wild growth (#2885) --- data-canary/scripts/lib/register_actions.lua | 2 +- data-otservbr-global/scripts/lib/register_actions.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data-canary/scripts/lib/register_actions.lua b/data-canary/scripts/lib/register_actions.lua index 229d385b0de..e59a46b3b03 100644 --- a/data-canary/scripts/lib/register_actions.lua +++ b/data-canary/scripts/lib/register_actions.lua @@ -1,5 +1,5 @@ local holeId = { 294, 369, 370, 385, 394, 411, 412, 413, 432, 433, 435, 482, 483, 594, 595, 609, 610, 615, 868, 874, 1156, 4824, 7515, 7516, 7517, 7518, 7519, 7520, 7521, 7522, 7737, 7755, 7762, 7767, 7768, 8144, 8690, 8709, 12203, 12961, 17239, 19220, 23364 } -- usable rope holes, for rope spots see global.lua -local wildGrowth = { 3635, 30224 } -- wild growth destroyable by machete +local wildGrowth = { 2130, 3635, 30224 } -- wild growth destroyable by machete local jungleGrass = { -- grass destroyable by machete [3696] = 3695, [3702] = 3701, diff --git a/data-otservbr-global/scripts/lib/register_actions.lua b/data-otservbr-global/scripts/lib/register_actions.lua index b696d44c9c6..6118f3b837a 100644 --- a/data-otservbr-global/scripts/lib/register_actions.lua +++ b/data-otservbr-global/scripts/lib/register_actions.lua @@ -5,7 +5,7 @@ local itemsGrinder = { } local holes = { 593, 606, 608, 867, 21341 } local jungleGrass = { 3696, 3702, 17153 } -local wildGrowth = { 3635, 30224 } +local wildGrowth = { 2130, 3635, 30224 } local fruits = { 3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3595, 3596, 5096, 8011, 8012, 8013 } local lava = { Position(32808, 32336, 11), From 5bcbc39e5f8cb06c7af774dae86294ed73e7cf26 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 11 Sep 2024 19:32:24 -0300 Subject: [PATCH 14/27] feat: full soul war quest (#2535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remember that for this quest to work properly, it is necessary to delete the old Soul War scripts (the repository had the quest divided into parts, and I completely redid it using modern features and systems, such as BossLever, Zones, EventCallback, and others). It is also necessary to update the soul war map, the new map is in the releases I won’t document all the changes here for obvious reasons; the quest is mostly written in Lua, so there are many monster scripts and other elements that were added or modified, making this a large pull request as a result. I thought about separating the pull request into smaller commits, but I came to the conclusion that this could cause confusion, since the quest would depend on several other commits to work correctly, so I decided to keep everything in one to avoid future "problems". Added talkactions: /distanceeffect (for testing distance effects) /testtaintconditions (for testing taint icons on player inventory bar) • The "Missing configuration for identifier" log has set to debug, since in cpp there are default values ​​already to avoid putting all the values ​​in lua • Fixed some EventCallbacks that were not properly passing arguments as references (using std::ref) • Added a check for EventCallbacks with the same names, improving the resource management of callbacks. Each time a callback was called, a new element was created in the vector. Now there will only be one and it will be reused with each execution • Passed some vectors from CreatureVector for copying, thus avoiding possible crashes when changing the vectors, it was necessary due to the change of maps in ebb and flow in real time (so the player can be in the creature vector during the vector reading and be removed) • Added new callback for MonsterType (lua scripts) called "onSpawn", which will execute whenever the creature is created • Added new callback for MonsterType (lua scripts) called "onPlayerAttack", which will be executed whenever a player attacks the monster, it should be used with care as the same callback will be valid for all instances of the same monster • Added new EventCallback called "playerOnThink", thus facilitating players' onThink • Added new EventCallbacks called "creatureOnCombat" and "mapOnLoad", to facilitate the development of the quest • Added a new function argument on the CreatureEvent "executeOnPrepareDeath" called "realDamage" • The Lua function "monster:setType" now includes a new argument called "restoreHealth" (boolean). This argument determines whether the function will restore the monster's health after setting its type. • The Lua function "player:addOutfit" now accept the "outfit name" and includes new argument called "addon" for setting the outfit's addon • New Lua function "player:sendCreatureAppear" to update the creature's appearance to the client (necessary for changing the ebb and flow maps, same reason I mentioned above, the player is on the map during the change, so it is necessary to send the update to the client) • Removed the exception for the message: "The map in folder 'x' is missing or corrupted", this prevents the console from stopping loading and closing the server, as in the case of a misconfigured worldchange. Co-authored-by: Pedro Henrique Alves Cruz --- data-otservbr-global/lib/quests/quest.lua | 1 + data-otservbr-global/lib/quests/soul_war.lua | 1589 +++++++++++++++++ .../quests/soul_war/aspect_of_power.lua | 25 +- ...nia.lua => goshnar's_megalomania_blue.lua} | 84 +- .../soul_war/goshnar's_megalomania_green.lua | 163 ++ .../soul_war/goshnar's_megalomania_purple.lua | 129 ++ .../quests/soul_war/goshnars_cruelty.lua | 40 +- .../quests/soul_war/goshnars_greed.lua | 55 +- .../quests/soul_war/goshnars_hatred.lua | 28 +- .../quests/soul_war/goshnars_malice.lua | 25 +- .../quests/soul_war/goshnars_spite.lua | 11 +- .../monster/quests/soul_war/greedbeast.lua | 98 + .../monster/quests/soul_war/mirror_image.lua | 34 + .../normal_monsters}/bony_sea_devil.lua | 10 +- .../normal_monsters}/brachiodemon.lua | 10 +- .../normal_monsters}/branchy_crawler.lua | 10 +- .../ashes_of_burning_hatred.lua | 89 + .../blaze_of_burning_hatred.lua | 89 + .../flame_of_burning_hatred.lua | 89 + .../spark_of_burning_hatred.lua | 89 + .../burning_hatred/symbol_of_hatred.lua | 84 + .../normal_monsters}/capricious_phantom.lua | 6 +- .../normal_monsters}/distorted_phantom.lua | 6 +- .../normal_monsters}/druid's_apparition.lua | 6 +- .../furious_crater/a_greedy_eye.lua | 93 + .../furious_crater}/cloak_of_terror.lua | 11 +- .../furious_crater}/courage_leech.lua | 6 +- .../furious_crater/poor_soul.lua | 118 ++ .../furious_crater}/vibrant_phantom.lua | 6 +- .../{ => normal_monsters}/hateful_soul.lua | 2 +- .../normal_monsters}/infernal_demon.lua | 6 +- .../normal_monsters}/infernal_phantom.lua | 6 +- .../normal_monsters}/knight's_apparition.lua | 6 +- .../soul_war/normal_monsters}/many_faces.lua | 10 +- .../greater_splinter_of_madness.lua | 107 ++ .../lesser_splinter_of_madness.lua | 107 ++ .../mighty_splinter_of_madness.lua | 113 ++ .../megalomania_room/necromantic_focus.lua | 82 + .../normal_monsters}/mould_phantom.lua | 6 +- .../normal_monsters}/paladin's_apparition.lua | 6 +- .../normal_monsters}/rotten_golem.lua | 6 +- .../sorcerer's_apparition.lua | 6 +- .../normal_monsters}/turbulent_elemental.lua | 6 +- .../monster/quests/soul_war/powerful_soul.lua | 115 ++ .../monster/quests/soul_war/soul_cage.lua | 71 + .../monster/quests/soul_war/soul_sphere.lua | 122 ++ .../monster/quests/soul_war/soulsnatcher.lua | 115 ++ .../quests/soul_war/spiteful_spitter.lua | 4 + .../monster/quests/soul_war/strong_soul.lua | 106 ++ .../monster/quests/soul_war/weak_soul.lua | 106 ++ .../monster/quests/soul_war/weeping_soul.lua | 91 + .../monster/undeads/hazardous_phantom.lua | 6 +- data-otservbr-global/npc/flickering_soul.lua | 203 +++ .../actions/bosses_levers/goshnar_cruelty.lua | 23 - .../actions/bosses_levers/goshnar_greed.lua | 23 - .../actions/bosses_levers/goshnar_hatred.lua | 23 - .../actions/bosses_levers/goshnar_malice.lua | 23 - .../bosses_levers/goshnar_megalomania.lua | 23 - .../actions/bosses_levers/goshnar_spite.lua | 23 - .../soul_war/action-reward_soul_war.lua | 60 + .../quests/soul_war/actions_bosses_killed.lua | 24 - .../soul_war/actions_portal_megalomania.lua | 38 - .../soul_war/actions_reward_soul_war.lua | 89 - .../soul_war/actions_soulwar_entrances.lua | 63 - .../eventcallback_on_combat_taint.lua | 126 ++ .../globalevent-ebb_and_flow_change_maps.lua | 135 ++ .../moveevent-claustrophobic-inferno-raid.lua | 61 + .../soul_war/moveevent-soul_war_entrances.lua | 147 ++ ...=> moveevent-teleport_entrance_reward.lua} | 7 +- .../quests/soul_war/soul_war_mechanics.lua | 1081 +++++++++++ .../quests/soul_war/spell-eye_beam.lua | 38 + .../soul_war/spell-fire_beam_cruelty.lua | 61 + .../soul_war/spell-fire_beam_megalomania.lua | 54 + .../soul_war/spell-megalomania_blue.lua | 58 + .../quests/soul_war/spell-soulsnatcher.lua | 58 + .../iron_servant_transformation.lua | 2 +- data-otservbr-global/world/otservbr-house.xml | 2 +- .../world/otservbr-monster.xml | 70 + data-otservbr-global/world/otservbr-npc.xml | 3 + data-otservbr-global/world/otservbr-zones.xml | 2 +- .../soul_war/ebb_and_flow/ebb-flow-empty.otbm | Bin 0 -> 129452 bytes .../{inundate.otbm => ebb-flow-inundate.otbm} | Bin 325937 -> 322942 bytes .../quest/soul_war/ebb_and_flow/ebb-flow.otbm | Bin 0 -> 181627 bytes .../quest/soul_war/ebb_and_flow/empty.otbm | Bin 13957 -> 0 bytes data/items/items.xml | 101 +- data/libs/functions/boss_lever.lua | 8 +- data/libs/functions/creature.lua | 2 +- data/libs/functions/monster.lua | 4 +- data/libs/functions/monstertype.lua | 7 +- data/libs/functions/revscriptsys.lua | 8 + data/libs/systems/zones.lua | 10 +- data/scripts/actions/items/cobra_flask.lua | 2 +- data/scripts/eventcallbacks/README.md | 17 +- .../creature/on_area_combat.lua | 2 +- .../eventcallbacks/creature/on_hear.lua | 2 +- .../eventcallbacks/monster/on_spawn.lua | 2 +- .../monster/ondroploot__base.lua | 6 +- .../monster/ondroploot_boosted.lua | 4 +- .../monster/ondroploot_gem_atelier.lua | 2 +- .../monster/ondroploot_hazard.lua | 4 +- .../monster/ondroploot_prey.lua | 4 +- .../monster/ondroploot_wealth_duplex.lua | 4 +- .../monster/postdroploot_analyzer.lua | 2 +- .../eventcallbacks/party/on_disband.lua | 2 +- .../eventcallbacks/player/on_browse_field.lua | 2 +- .../scripts/eventcallbacks/player/on_look.lua | 2 +- .../eventcallbacks/player/on_look_in_shop.lua | 2 +- .../player/on_look_in_trade.lua | 2 +- .../eventcallbacks/player/on_remove_count.lua | 2 +- .../player/on_request_quest_line.lua | 2 +- .../player/on_request_quest_log.lua | 2 +- .../eventcallbacks/player/on_rotate_item.lua | 2 +- .../player/on_storage_update.lua | 2 +- .../eventcallbacks/player/on_trade_accept.lua | 2 +- data/scripts/lib/quests.lua | 2 + data/scripts/lib/register_lever_tables.lua | 4 +- data/scripts/lib/register_monster_type.lua | 4 +- data/scripts/lib/register_spells.lua | 6 + data/scripts/spells/healing/heal_malice.lua | 29 + data/scripts/systems/reward_chest.lua | 4 +- data/scripts/talkactions/gm/afk.lua | 2 +- .../talkactions/gm/distance_effect.lua | 37 + data/scripts/talkactions/gm/position.lua | 43 +- .../scripts/talkactions/god/add_condition.lua | 10 + src/config/configmanager.cpp | 2 +- src/creatures/appearance/outfit/outfit.hpp | 10 + src/creatures/combat/combat.cpp | 18 +- src/creatures/combat/condition.cpp | 32 +- src/creatures/creature.cpp | 2 +- src/creatures/creature.hpp | 10 +- src/creatures/creatures_definitions.hpp | 24 +- src/creatures/monsters/monster.cpp | 103 +- src/creatures/monsters/monster.hpp | 35 +- src/creatures/monsters/monsters.cpp | 37 +- src/creatures/monsters/monsters.hpp | 2 + .../monsters/spawns/spawn_monster.cpp | 3 +- src/creatures/players/grouping/party.cpp | 2 +- src/creatures/players/player.cpp | 18 +- src/creatures/players/player.hpp | 10 +- src/enums/player_icons.hpp | 10 +- src/game/bank/bank.cpp | 4 + src/game/game.cpp | 9 +- src/game/zones/zone.cpp | 5 + src/io/functions/iologindata_load_player.cpp | 7 +- src/io/io_bosstiary.cpp | 1 + src/io/iomap.cpp | 2 +- src/lua/callbacks/callbacks_definitions.hpp | 3 + src/lua/callbacks/event_callback.cpp | 105 +- src/lua/callbacks/event_callback.hpp | 22 +- src/lua/callbacks/events_callbacks.cpp | 31 +- src/lua/callbacks/events_callbacks.hpp | 26 +- src/lua/creature/creatureevent.cpp | 6 +- src/lua/creature/creatureevent.hpp | 2 +- src/lua/creature/events.cpp | 2 +- .../functions/core/game/bank_functions.cpp | 1 + .../functions/core/game/game_functions.cpp | 1 + src/lua/functions/core/game/lua_enums.cpp | 8 +- .../functions/core/game/zone_functions.cpp | 2 +- .../creatures/monster/monster_functions.cpp | 86 +- .../creatures/monster/monster_functions.hpp | 13 + .../monster/monster_type_functions.cpp | 4 +- .../monster/monster_type_functions.hpp | 2 + .../creatures/player/player_functions.cpp | 30 +- .../creatures/player/player_functions.hpp | 4 +- .../events/event_callback_functions.cpp | 14 +- src/map/map.cpp | 15 +- src/map/mapcache.cpp | 16 +- 167 files changed, 7124 insertions(+), 698 deletions(-) create mode 100644 data-otservbr-global/lib/quests/soul_war.lua rename data-otservbr-global/monster/quests/soul_war/{goshnars_megalomania.lua => goshnar's_megalomania_blue.lua} (63%) create mode 100644 data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/greedbeast.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/bony_sea_devil.lua (95%) rename data-otservbr-global/monster/{demons => quests/soul_war/normal_monsters}/brachiodemon.lua (95%) rename data-otservbr-global/monster/{plants => quests/soul_war/normal_monsters}/branchy_crawler.lua (94%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/capricious_phantom.lua (97%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/distorted_phantom.lua (97%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/druid's_apparition.lua (97%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua rename data-otservbr-global/monster/{plants => quests/soul_war/normal_monsters/furious_crater}/cloak_of_terror.lua (94%) rename data-otservbr-global/monster/{extra_dimensional => quests/soul_war/normal_monsters/furious_crater}/courage_leech.lua (97%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters/furious_crater}/vibrant_phantom.lua (97%) rename data-otservbr-global/monster/quests/soul_war/{ => normal_monsters}/hateful_soul.lua (99%) rename data-otservbr-global/monster/{demons => quests/soul_war/normal_monsters}/infernal_demon.lua (97%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/infernal_phantom.lua (97%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/knight's_apparition.lua (97%) rename data-otservbr-global/monster/{demons => quests/soul_war/normal_monsters}/many_faces.lua (94%) create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/mould_phantom.lua (97%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/paladin's_apparition.lua (97%) rename data-otservbr-global/monster/{constructs => quests/soul_war/normal_monsters}/rotten_golem.lua (97%) rename data-otservbr-global/monster/{undeads => quests/soul_war/normal_monsters}/sorcerer's_apparition.lua (97%) rename data-otservbr-global/monster/{elementals => quests/soul_war/normal_monsters}/turbulent_elemental.lua (97%) create mode 100644 data-otservbr-global/monster/quests/soul_war/powerful_soul.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/soul_cage.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/soul_sphere.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/strong_soul.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/weak_soul.lua create mode 100644 data-otservbr-global/monster/quests/soul_war/weeping_soul.lua create mode 100644 data-otservbr-global/npc/flickering_soul.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua delete mode 100644 data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua delete mode 100644 data-otservbr-global/scripts/quests/soul_war/actions_bosses_killed.lua delete mode 100644 data-otservbr-global/scripts/quests/soul_war/actions_portal_megalomania.lua delete mode 100644 data-otservbr-global/scripts/quests/soul_war/actions_reward_soul_war.lua delete mode 100644 data-otservbr-global/scripts/quests/soul_war/actions_soulwar_entrances.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua rename data-otservbr-global/scripts/quests/soul_war/{actions_portal_reward_soulwar.lua => moveevent-teleport_entrance_reward.lua} (78%) create mode 100644 data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua create mode 100644 data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm rename data-otservbr-global/world/quest/soul_war/ebb_and_flow/{inundate.otbm => ebb-flow-inundate.otbm} (80%) create mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm delete mode 100644 data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm create mode 100644 data/scripts/lib/quests.lua create mode 100644 data/scripts/spells/healing/heal_malice.lua create mode 100644 data/scripts/talkactions/gm/distance_effect.lua create mode 100644 data/scripts/talkactions/god/add_condition.lua diff --git a/data-otservbr-global/lib/quests/quest.lua b/data-otservbr-global/lib/quests/quest.lua index 21b1744fec6..392367c9d94 100644 --- a/data-otservbr-global/lib/quests/quest.lua +++ b/data-otservbr-global/lib/quests/quest.lua @@ -5,5 +5,6 @@ dofile(DATA_DIRECTORY .. "/lib/quests/killing_in_the_name_of.lua") dofile(DATA_DIRECTORY .. "/lib/quests/svargrond_arena.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_cursed_crystal.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_queen_of_the_banshees.lua") +dofile(DATA_DIRECTORY .. "/lib/quests/soul_war.lua") dofile(DATA_DIRECTORY .. "/lib/quests/their_masters_voice.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_primal_ordeal.lua") diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua new file mode 100644 index 00000000000..a9e9d920e91 --- /dev/null +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -0,0 +1,1589 @@ +SoulWarQuest = { + -- Item ids + -- Goshnar's Hatred + bagYouDesireItemId = 34109, + goshnarsHatredSorrowId = 33793, + condensedRemorseId = 33792, + -- Goshnar's Spite + weepingSoulCorpseId = 33876, + searingFireId = 33877, + -- Goshnar's Cruelty + pulsatingEnergyId = 34005, + greedyMawId = 33890, + someMortalEssenceId = 33891, + theBloodOfCloakTerrorIds = { 33854, 34006, 34007 }, + -- Goshnar's Megalomania + deadAspectOfPowerCorpseId = 33949, + cleansedSanityItemId = 33950, + necromanticRemainsItemId = 33984, + + poolDamagePercentages = { + [33854] = 0.20, -- 20% of maximum health for the largest pool + [34006] = 0.15, -- 15% for a medium-sized pool + [34007] = 0.10, -- 10% for the smallest pool + }, + + timeToIncreaseCrueltyDefense = 15, -- In seconds, it will increase every 15 seconds if don't use mortal essence in greedy maw + useGreedMawCooldown = 30, -- In seconds + goshnarsCrueltyDefenseChange = 2, -- Defense change, the amount that will decrease or increase defense, the defense cannot decrease more than the monster's original defense amount + goshnarsCrueltyWaveInterval = 7, -- In seconds + + timeToReturnImmuneMegalomania = 70, -- In seconds + + bagYouDesireChancePerTaint = 10, -- Increases % per taint + bagYouDesireMonsters = { + "Bony Sea Devil", + "Brachiodemon", + "Branchy Crawler", + "Capricious Phantom", + "Cloak of Terror", + "Courage Leech", + "Distorted Phantom", + "Druid's Apparition", + "Infernal Demon", + "Infernal Phantom", + "Knight's Apparition", + "Many Faces", + "Mould Phantom", + "Paladin's Apparition", + "Rotten Golem", + "Sorcerer's Apparition", + "Turbulent Elemental", + "Vibrant Phantom", + "Hazardous Phantom", + "Goshnar's Cruelty", + "Goshnar's Spite", + "Goshnar's Malice", + "Goshnar's Hatred", + "Goshnar's Greed", + "Goshnar's Megalomania", + }, + + -- Goshnar's Cruelty pulsating energy monsters + pulsatingEnergyMonsters = { + "Vibrant Phantom", + "Cloak of Terror", + "Courage Leech", + }, + + miniBosses = { + ["Goshnar's Malice"] = true, + ["Goshnar's Hatred"] = true, + ["Goshnar's Spite"] = true, + ["Goshnar's Cruelty"] = true, + ["Goshnar's Greed"] = true, + }, + + finalRewards = { + { id = 34082, name = "soulcutter" }, + { id = 34083, name = "soulshredder" }, + { id = 34084, name = "soulbiter" }, + { id = 34085, name = "souleater" }, + { id = 34086, name = "soulcrusher" }, + { id = 34087, name = "soulmaimer" }, + { id = 34088, name = "soulbleeder" }, + { id = 34089, name = "soulpiercer" }, + { id = 34090, name = "soultainter" }, + { id = 34091, name = "soulhexer" }, + { id = 34092, name = "soulshanks" }, + { id = 34093, name = "soulstrider" }, + { id = 34094, name = "soulshell" }, + { id = 34095, name = "soulmantel" }, + { id = 34096, name = "soulshroud" }, + { id = 34097, name = "pair of soulwalkers" }, + { id = 34098, name = "pair of soulstalkers" }, + { id = 34099, name = "soulbastion" }, + }, + + kvSoulWar = KV.scoped("quest"):scoped("soul-war"), + -- Global KV for storage burning change form time + kvBurning = KV.scoped("quest"):scoped("soul-war"):scoped("burning-change-form"), + + rottenWastelandShrines = { + [33019] = { x = 33926, y = 31091, z = 13 }, + [33021] = { x = 33963, y = 31078, z = 13 }, + [33022] = { x = 33970, y = 30988, z = 13 }, + [33024] = { x = 33970, y = 31012, z = 13 }, + }, + + -- Lever room and teleports positions + goshnarsGreedAccessPosition = { from = { x = 33937, y = 31217, z = 11 }, to = { x = 33782, y = 31665, z = 14 } }, + goshnarsHatredAccessPosition = { from = { x = 33914, y = 31032, z = 12 }, to = { x = 33774, y = 31604, z = 14 } }, + -- Teleports from 1st/2nd/3rd floors + goshnarsCrueltyTeleportRoomPositions = { + { from = Position(33889, 31873, 3), to = Position(33830, 31881, 4), access = "first-floor-access", count = 40 }, + { from = Position(33829, 31880, 4), to = Position(33856, 31889, 5), access = "second-floor-access", count = 55 }, + { from = Position(33856, 31884, 5), to = Position(33857, 31865, 6), access = "third-floor-access", count = 70 }, + }, + + claustrophobicInfernoRaids = { + [1] = { + zoneArea = { + { x = 33985, y = 31053, z = 9 }, + { x = 34045, y = 31077, z = 9 }, + }, + sandTimerPositions = { + { x = 34012, y = 31049, z = 9 }, + { x = 34013, y = 31049, z = 9 }, + { x = 34014, y = 31049, z = 9 }, + { x = 34015, y = 31049, z = 9 }, + }, + zone = Zone("raid.first-claustrophobic-inferno"), + spawns = { + Position(33991, 31064, 9), + Position(34034, 31060, 9), + Position(34028, 31067, 9), + Position(34020, 31067, 9), + Position(34008, 31067, 9), + Position(34001, 31059, 9), + Position(33992, 31069, 9), + Position(34002, 31072, 9), + Position(34013, 31074, 9), + Position(33998, 31060, 9), + Position(34039, 31065, 9), + Position(34032, 31072, 9), + }, + exitPosition = { x = 34009, y = 31083, z = 9 }, + getZone = function() + return SoulWarQuest.claustrophobicInfernoRaids[1].zone + end, + }, + [2] = { + zoneArea = { + { x = 33988, y = 31042, z = 10 }, + { x = 34043, y = 31068, z = 10 }, + }, + sandTimerPositions = { + { x = 34012, y = 31075, z = 10 }, + { x = 34011, y = 31075, z = 10 }, + { x = 34010, y = 31075, z = 10 }, + }, + zone = Zone("raid.second-claustrophobic-inferno"), + spawns = { + Position(33999, 31046, 10), + Position(34011, 31047, 10), + Position(34015, 31052, 10), + Position(34021, 31044, 10), + Position(34029, 31054, 10), + Position(34037, 31052, 10), + Position(34037, 31060, 10), + Position(34023, 31062, 10), + Position(34012, 31061, 10), + Position(33998, 31061, 10), + Position(34005, 31052, 10), + }, + exitPosition = { x = 34011, y = 31028, z = 10 }, + getZone = function() + return SoulWarQuest.claustrophobicInfernoRaids[2].zone + end, + }, + [3] = { + zoneArea = { + { x = 33987, y = 31043, z = 11 }, + { x = 34044, y = 31076, z = 11 }, + }, + sandTimerPositions = { + { x = 34009, y = 31036, z = 11 }, + { x = 34010, y = 31036, z = 11 }, + { x = 34011, y = 31036, z = 11 }, + { x = 34012, y = 31036, z = 11 }, + { x = 34013, y = 31036, z = 11 }, + { x = 34014, y = 31036, z = 11 }, + }, + zone = Zone("raid.third-claustrophobic-inferno"), + spawns = { + Position(34005, 31049, 11), + Position(33999, 31051, 11), + Position(33995, 31055, 11), + Position(33999, 31068, 11), + Position(34016, 31068, 11), + Position(34030, 31070, 11), + Position(34038, 31066, 11), + Position(34038, 31051, 11), + Position(34033, 31051, 11), + Position(34025, 31049, 11), + Position(34013, 31058, 11), + Position(34021, 31059, 11), + Position(34027, 31063, 11), + Position(34007, 31063, 11), + Position(34004, 31059, 11), + }, + exitPosition = { x = 34014, y = 31085, z = 11 }, + getZone = function() + return SoulWarQuest.claustrophobicInfernoRaids[3].zone + end, + }, + spawnTime = 10, -- seconds + suriviveTime = 2 * 60, -- 2 minutes + timeToKick = 5, -- seconds + }, + + areaZones = { + monsters = { + ["zone.claustrophobic-inferno"] = "Brachiodemon", + ["zone.mirrored-nightmare"] = "Many Faces", + ["zone.ebb-and-flow"] = "Bony Sea Devil", + ["zone.furious-crater"] = "Cloak of Terror", + ["zone.rotten-wasteland"] = "Branchy Crawler", + ["boss.goshnar's-malice"] = "Dreadful Harvester", + ["boss.goshnar's-spite"] = "Dreadful Harvester", + ["boss.goshnar's-greed"] = "Dreadful Harvester", + ["boss.goshnar's-hatred"] = "Dreadful Harvester", + ["boss.goshnar's-cruelty"] = "Dreadful Harvester", + ["boss.goshnar's-megalomania-purple"] = "Dreadful Harvester", + }, + + claustrophobicInferno = Zone("zone.claustrophobic-inferno"), + mirroredNightmare = Zone("zone.mirrored-nightmare"), + ebbAndFlow = Zone("zone.ebb-and-flow"), + furiousCrater = Zone("zone.furious-crater"), + rottenWasteland = Zone("zone.rotten-wasteland"), + }, + + -- Levers configuration + levers = { + goshnarsMalicePosition = { x = 33678, y = 31599, z = 14 }, + goshnarsSpitePosition = { x = 33773, y = 31634, z = 14 }, + goshnarsGreedPosition = { x = 33775, y = 31665, z = 14 }, + goshnarsHatredPosition = { x = 33772, y = 31601, z = 14 }, + goshnarsCrueltyPosition = { x = 33853, y = 31854, z = 6 }, + goshnarsMegalomaniaPosition = { x = 33675, y = 31634, z = 14 }, + + -- Levers system + goshnarsSpite = { + boss = { + name = "Goshnar's Spite", + position = Position(33743, 31632, 14), + }, + requiredLevel = 250, + playerPositions = { + { pos = Position(33774, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33775, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33776, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33777, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33778, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = Position(33734, 31624, 14), + to = Position(33751, 31640, 14), + }, + onUseExtra = function(player) + local zone = Zone("boss.goshnar's-spite") + if zone then + local positions = zone:getPositions() + for _, pos in ipairs(positions) do + local tile = Tile(pos) + if tile then + local item = tile:getItemById(SoulWarQuest.weepingSoulCorpseId) + if item then + logger.debug("Weeping Soul Corpse removed from position: {}", pos) + item:remove() + end + end + end + end + end, + exit = Position(33621, 31427, 10), + timeToFightAgain = 20 * 60 * 60, -- 20 hours + }, + goshnarsMalice = { + boss = { + name = "Goshnar's Malice", + position = Position(33709, 31599, 14), + }, + requiredLevel = 250, + playerPositions = { + { pos = Position(33679, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33680, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33681, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33682, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33683, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = Position(33699, 31590, 14), + to = Position(33718, 31607, 14), + }, + onUseExtra = function(player) + addEvent(SpawnSoulCage, 23000) + end, + exit = Position(33621, 31427, 10), + timeToFightAgain = 20 * 60 * 60, -- 20 hours + }, + goshnarsGreed = { + boss = { + name = "Goshnar's Greed", + position = Position(33746, 31666, 14), + }, + requiredLevel = 250, + playerPositions = { + { pos = Position(33776, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33777, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33778, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33779, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33780, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = Position(33737, 31658, 14), + to = Position(33755, 31673, 14), + }, + timeToFightAgain = 0, -- TODO: Remove later + onUseExtra = function() + CreateGoshnarsGreedMonster("Greedbeast", Position(33744, 31666, 14)) + CreateGoshnarsGreedMonster("Soulsnatcher", Position(33747, 31668, 14)) + CreateGoshnarsGreedMonster("Weak Soul", Position(33750, 31666, 14)) + end, + exit = Position(33621, 31427, 10), + timeToFightAgain = 20 * 60 * 60, -- 20 hours + }, + goshnarsHatred = { + boss = { + name = "Goshnar's Hatred", + position = Position(33744, 31599, 14), + }, + monsters = { + { name = "Ashes of Burning Hatred", pos = { x = 33743, y = 31599, z = 14 } }, + }, + requiredLevel = 250, + playerPositions = { + { pos = Position(33773, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33774, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33775, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33776, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33777, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = Position(33735, 31592, 14), + to = Position(33751, 31606, 14), + }, + exit = Position(33621, 31427, 10), + timeToFightAgain = 20 * 60 * 60, -- 20 hours + onUseExtra = function(player) + SoulWarQuest.kvBurning:set("time", 180) + logger.trace("Goshnar's Hatred burning change form time set to: {}", 180) + player:resetGoshnarSymbolTormentCounter() + end, + }, + goshnarsCruelty = { + boss = { + name = "Goshnar's Cruelty", + position = Position(33856, 31866, 7), + }, + monsters = { + { name = "A Greedy Eye", pos = { x = 33856, y = 31858, z = 7 } }, + }, + requiredLevel = 250, + playerPositions = { + { pos = Position(33854, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, + { pos = Position(33855, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, + { pos = Position(33856, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, + { pos = Position(33857, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, + { pos = Position(33858, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = Position(33847, 31858, 7), + to = Position(33864, 31874, 7), + }, + exit = Position(33621, 31427, 10), + timeToFightAgain = 20 * 60 * 60, -- 20 hours + onUseExtra = function(player) + SoulWarQuest.kvSoulWar:remove("greedy-maw-action") + SoulWarQuest.kvSoulWar:remove("goshnars-cruelty-defense-drain") + player:soulWarQuestKV():scoped("furious-crater"):remove("greedy-maw-action") + end, + }, + goshnarsMegalomania = { + boss = { + name = "Goshnar's Megalomania Purple", + position = Position(33710, 31634, 14), + }, + monsters = { + { name = "Aspect of Power", pos = { x = 33710, y = 31635, z = 14 } }, + }, + requiredLevel = 250, + playerPositions = { + { pos = Position(33676, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33677, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33678, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33679, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, + { pos = Position(33680, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, + }, + specPos = { + from = Position(33701, 31626, 14), + to = Position(33719, 31642, 14), + }, + exit = Position(33621, 31427, 10), + timeToFightAgain = 72 * 60 * 60, -- 72 hours + onUseExtra = function(player) + player:resetGoshnarSymbolTormentCounter() + SoulWarQuest.kvSoulWar:remove("cleansed-sanity-action") + player:soulWarQuestKV():scoped("furious-crater"):remove("cleansed-sanity-action") + end, + }, + }, + + -- Goshnar's Greed + apparitionNames = { + "Druid's Apparition", + "Knight's Apparition", + "Paladin's Apparition", + "Sorcerer's Apparition", + }, + + burningTransformations = { + { 180, "Ashes of Burning Hatred" }, + { 135, "Spark of Burning Hatred" }, + { 90, "Flame of Burning Hatred" }, + { 45, "Blaze of Burning Hatred" }, + }, + + burningHatredMonsters = { + "Ashes of Burning Hatred", + "Spark of Burning Hatred", + "Flame of Burning Hatred", + "Blaze of Burning Hatred", + }, + + requiredCountPerApparition = 25, + + -- Ebb and flow + ebbAndFlow = { + zone = Zone("ebb-and-flow-zone"), + -- Positions to teleport into rooms when innundate map is loaded + centerRoomPositions = { + { conor = { x = 33929, y = 31020, z = 9 }, teleportPosition = { x = 33939, y = 31021, z = 8 } }, + { conor = { x = 33929, y = 31047, z = 9 }, teleportPosition = { x = 33938, y = 31047, z = 8 } }, + { conor = { x = 33918, y = 31047, z = 9 }, teleportPosition = { x = 33903, y = 31049, z = 8 } }, + { conor = { x = 33898, y = 31054, z = 9 }, teleportPosition = { x = 33903, y = 31049, z = 8 } }, + { conor = { x = 33929, y = 31047, z = 9 }, teleportPosition = { x = 33938, y = 31047, z = 8 } }, + { conor = { x = 33940, y = 31054, z = 9 }, teleportPosition = { x = 33938, y = 31047, z = 8 } }, + { conor = { x = 33940, y = 31064, z = 9 }, teleportPosition = { x = 33937, y = 31074, z = 8 } }, + { conor = { x = 33937, y = 31086, z = 9 }, teleportPosition = { x = 33937, y = 31074, z = 8 } }, + { conor = { x = 33937, y = 31098, z = 9 }, teleportPosition = { x = 33929, y = 31109, z = 8 } }, + { conor = { x = 33933, y = 31109, z = 9 }, teleportPosition = { x = 33929, y = 31109, z = 8 } }, + { conor = { x = 33921, y = 31113, z = 9 }, teleportPosition = { x = 33929, y = 31109, z = 8 } }, + { conor = { x = 33912, y = 31113, z = 9 }, teleportPosition = { x = 33904, y = 31117, z = 8 } }, + { conor = { x = 33901, y = 31108, z = 9 }, teleportPosition = { x = 33904, y = 31117, z = 8 } }, + { conor = { x = 33901, y = 31098, z = 9 }, teleportPosition = { x = 33904, y = 31082, z = 8 } }, + { conor = { x = 33899, y = 31064, z = 9 }, teleportPosition = { x = 33904, y = 31082, z = 8 } }, + }, + mapsPath = { + empty = "data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm", + inundate = "data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-inundate.otbm", + ebbFlow = "data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm", + }, + + -- In Minutes + intervalChangeMap = 2, + waitPosition = Position(33893, 31020, 8), + + getZone = function() + return SoulWarQuest.ebbAndFlow.zone + end, + + reloadZone = function() + SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 33964, y = 31147, z = 9 }) + end, + + kv = KV.scoped("quest"):scoped("soul-war"):scoped("ebb-and-flow-maps"), + isActive = function() + return SoulWarQuest.ebbAndFlow.kv:get("is-active") + end, + isLoadedEmptyMap = function() + return SoulWarQuest.ebbAndFlow.kv:get("is-loaded-empty-map") + end, + setActive = function(value) + SoulWarQuest.ebbAndFlow.kv:set("is-active", value) + end, + setLoadedEmptyMap = function(value) + SoulWarQuest.ebbAndFlow.kv:set("is-loaded-empty-map", value) + end, + + updateZonePlayers = function() + if SoulWarQuest.ebbAndFlow.zone and SoulWarQuest.ebbAndFlow.getZone():countPlayers() > 0 then + SoulWarQuest.ebbAndFlow.reloadZone() + local players = SoulWarQuest.ebbAndFlow.getZone():getPlayers() + for _, player in ipairs(players) do + logger.trace("Updating player: {}", player:getName()) + player:sendCreatureAppear() + end + end + end, + + -- Add here more positions of the pools that must transform before innundate map is loaded + poolPositions = { + { x = 33906, y = 31026, z = 9 }, + { x = 33901, y = 31026, z = 9 }, + { x = 33932, y = 31011, z = 9 }, + { x = 33941, y = 31033, z = 9 }, + { x = 33946, y = 31037, z = 9 }, + { x = 33939, y = 31056, z = 9 }, + }, + + boatId = 7272, + doorId = 33767, + smallPoolId = 33772, + MediumPoolId = 33773, + }, + + changeBlueEvent = nil, + changePurpleEvent = nil, + + changeMegalomaniaBlue = function() + local boss = Creature("Goshnar's Megalomania") + if boss then + boss:teleportTo(SoulWarQuest.levers.goshnarsMegalomania.boss.position) + boss:say("ENOUGH! I WILL MAKE YOU SUFFER FOR YOUR INSOLENCE! NOW - I - WILL - ANIHILATE - YOU!") + boss:setType("Goshnar's Megalomania Blue") + local function changeBack() + boss:setType("Goshnar's Megalomania Purple") + end + + changePurpleEvent = addEvent(changeBack, 7000) + end + end, + + -- Chance to heal the life of the monster by stepping on the corpse of "weeping soul" + goshnarsSpiteHealChance = 10, + -- Percentage that will heal by stepping and the chance is successful + goshnarsSpiteHealPercentage = 10, + + goshnarSpiteEntrancePosition = { fromPos = Position(33950, 31109, 8), toPos = Position(33780, 31634, 14) }, + + waterElementalOutfit = { + lookType = 286, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, + }, + + goshnarsSpiteFirePositions = { + -- North + { x = 33743, y = 31628, z = 14 }, + -- East + { x = 33736, y = 31632, z = 14 }, + -- West + { x = 33750, y = 31632, z = 14 }, + -- South + { x = 33742, y = 31637, z = 14 }, + }, + + -- Increased defense if the searing fire disappears + goshnarsSpiteIncreaseDefense = 10, + -- Count of monsters to kill for enter in the boss room + hardozousPanthomDeathCount = 20, + -- Time to fire created again + timeToCreateSearingFire = 14, -- In seconds + -- Time to remove the searing fire if player don't step on it + timeToRemoveSearingFire = 5, -- In seconds + cooldownToStepOnSearingFire = 56, -- In seconds (14 seconds x 4) + + -- Positions to teleport into rooms when innundate map is loaded + ebbAndFlowBoatTeleportPositions = { + -- First boat + -- Enter on boat + { register = { x = 33919, y = 31019, z = 8 }, teleportTo = { x = 33923, y = 31019, z = 8 } }, + { register = { x = 33919, y = 31020, z = 8 }, teleportTo = { x = 33923, y = 31020, z = 8 } }, + { register = { x = 33919, y = 31021, z = 8 }, teleportTo = { x = 33923, y = 31021, z = 8 } }, + { register = { x = 33919, y = 31022, z = 8 }, teleportTo = { x = 33923, y = 31022, z = 8 } }, + -- Back to innitial room + { register = { x = 33922, y = 31019, z = 8 }, teleportTo = { x = 33918, y = 31019, z = 8 } }, + { register = { x = 33922, y = 31020, z = 8 }, teleportTo = { x = 33918, y = 31020, z = 8 } }, + { register = { x = 33922, y = 31021, z = 8 }, teleportTo = { x = 33918, y = 31021, z = 8 } }, + { register = { x = 33922, y = 31022, z = 8 }, teleportTo = { x = 33918, y = 31022, z = 8 } }, + -- From boat to room + { register = { x = 33926, y = 31019, z = 8 }, teleportTo = { x = 33930, y = 31019, z = 8 } }, + { register = { x = 33926, y = 31020, z = 8 }, teleportTo = { x = 33930, y = 31020, z = 8 } }, + { register = { x = 33926, y = 31021, z = 8 }, teleportTo = { x = 33930, y = 31021, z = 8 } }, + { register = { x = 33926, y = 31022, z = 8 }, teleportTo = { x = 33930, y = 31022, z = 8 } }, + -- From room to boat + { register = { x = 33929, y = 31019, z = 8 }, teleportTo = { x = 33925, y = 31019, z = 8 } }, + { register = { x = 33929, y = 31020, z = 8 }, teleportTo = { x = 33925, y = 31020, z = 8 } }, + { register = { x = 33929, y = 31021, z = 8 }, teleportTo = { x = 33925, y = 31021, z = 8 } }, + { register = { x = 33929, y = 31022, z = 8 }, teleportTo = { x = 33925, y = 31022, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33929, y = 31045, z = 8 }, teleportTo = { x = 33925, y = 31045, z = 8 } }, + { register = { x = 33929, y = 31046, z = 8 }, teleportTo = { x = 33925, y = 31046, z = 8 } }, + { register = { x = 33929, y = 31047, z = 8 }, teleportTo = { x = 33925, y = 31047, z = 8 } }, + { register = { x = 33929, y = 31048, z = 8 }, teleportTo = { x = 33925, y = 31048, z = 8 } }, + -- Back to room + { register = { x = 33926, y = 31045, z = 8 }, teleportTo = { x = 33930, y = 31045, z = 8 } }, + { register = { x = 33926, y = 31046, z = 8 }, teleportTo = { x = 33930, y = 31046, z = 8 } }, + { register = { x = 33926, y = 31047, z = 8 }, teleportTo = { x = 33930, y = 31047, z = 8 } }, + { register = { x = 33926, y = 31048, z = 8 }, teleportTo = { x = 33930, y = 31048, z = 8 } }, + -- From boat to room + { register = { x = 33922, y = 31045, z = 8 }, teleportTo = { x = 33918, y = 31045, z = 8 } }, + { register = { x = 33922, y = 31046, z = 8 }, teleportTo = { x = 33918, y = 31046, z = 8 } }, + { register = { x = 33922, y = 31047, z = 8 }, teleportTo = { x = 33918, y = 31047, z = 8 } }, + { register = { x = 33922, y = 31048, z = 8 }, teleportTo = { x = 33918, y = 31048, z = 8 } }, + -- From room to boat + { register = { x = 33919, y = 31045, z = 8 }, teleportTo = { x = 33923, y = 31045, z = 8 } }, + { register = { x = 33919, y = 31046, z = 8 }, teleportTo = { x = 33923, y = 31046, z = 8 } }, + { register = { x = 33919, y = 31047, z = 8 }, teleportTo = { x = 33923, y = 31047, z = 8 } }, + { register = { x = 33919, y = 31048, z = 8 }, teleportTo = { x = 33923, y = 31048, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33896, y = 31055, z = 8 }, teleportTo = { x = 33896, y = 31059, z = 8 } }, + { register = { x = 33897, y = 31055, z = 8 }, teleportTo = { x = 33897, y = 31059, z = 8 } }, + { register = { x = 33898, y = 31055, z = 8 }, teleportTo = { x = 33898, y = 31059, z = 8 } }, + { register = { x = 33899, y = 31055, z = 8 }, teleportTo = { x = 33899, y = 31059, z = 8 } }, + { register = { x = 33900, y = 31055, z = 8 }, teleportTo = { x = 33900, y = 31059, z = 8 } }, + { register = { x = 33901, y = 31055, z = 8 }, teleportTo = { x = 33901, y = 31059, z = 8 } }, + -- Back to room + { register = { x = 33896, y = 31058, z = 8 }, teleportTo = { x = 33896, y = 31054, z = 8 } }, + { register = { x = 33897, y = 31058, z = 8 }, teleportTo = { x = 33897, y = 31054, z = 8 } }, + { register = { x = 33898, y = 31058, z = 8 }, teleportTo = { x = 33898, y = 31054, z = 8 } }, + { register = { x = 33899, y = 31058, z = 8 }, teleportTo = { x = 33899, y = 31054, z = 8 } }, + { register = { x = 33900, y = 31058, z = 8 }, teleportTo = { x = 33900, y = 31054, z = 8 } }, + { register = { x = 33901, y = 31058, z = 8 }, teleportTo = { x = 33901, y = 31054, z = 8 } }, + -- From boat to room + { register = { x = 33896, y = 31061, z = 8 }, teleportTo = { x = 33896, y = 31065, z = 8 } }, + { register = { x = 33897, y = 31061, z = 8 }, teleportTo = { x = 33897, y = 31065, z = 8 } }, + { register = { x = 33898, y = 31061, z = 8 }, teleportTo = { x = 33898, y = 31065, z = 8 } }, + { register = { x = 33899, y = 31061, z = 8 }, teleportTo = { x = 33899, y = 31065, z = 8 } }, + { register = { x = 33900, y = 31061, z = 8 }, teleportTo = { x = 33900, y = 31065, z = 8 } }, + { register = { x = 33901, y = 31061, z = 8 }, teleportTo = { x = 33901, y = 31065, z = 8 } }, + -- From room to boat + { register = { x = 33896, y = 31064, z = 8 }, teleportTo = { x = 33896, y = 31060, z = 8 } }, + { register = { x = 33897, y = 31064, z = 8 }, teleportTo = { x = 33897, y = 31060, z = 8 } }, + { register = { x = 33898, y = 31064, z = 8 }, teleportTo = { x = 33898, y = 31060, z = 8 } }, + { register = { x = 33899, y = 31064, z = 8 }, teleportTo = { x = 33899, y = 31060, z = 8 } }, + { register = { x = 33900, y = 31064, z = 8 }, teleportTo = { x = 33900, y = 31060, z = 8 } }, + { register = { x = 33901, y = 31064, z = 8 }, teleportTo = { x = 33901, y = 31060, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33899, y = 31099, z = 8 }, teleportTo = { x = 33899, y = 31103, z = 8 } }, + { register = { x = 33900, y = 31099, z = 8 }, teleportTo = { x = 33900, y = 31103, z = 8 } }, + { register = { x = 33901, y = 31099, z = 8 }, teleportTo = { x = 33901, y = 31103, z = 8 } }, + { register = { x = 33902, y = 31099, z = 8 }, teleportTo = { x = 33902, y = 31103, z = 8 } }, + { register = { x = 33903, y = 31099, z = 8 }, teleportTo = { x = 33903, y = 31103, z = 8 } }, + { register = { x = 33904, y = 31099, z = 8 }, teleportTo = { x = 33904, y = 31103, z = 8 } }, + { register = { x = 33905, y = 31099, z = 8 }, teleportTo = { x = 33905, y = 31103, z = 8 } }, + -- Back from boat to room + { register = { x = 33899, y = 31102, z = 8 }, teleportTo = { x = 33899, y = 31098, z = 8 } }, + { register = { x = 33900, y = 31102, z = 8 }, teleportTo = { x = 33900, y = 31098, z = 8 } }, + { register = { x = 33901, y = 31102, z = 8 }, teleportTo = { x = 33901, y = 31098, z = 8 } }, + { register = { x = 33902, y = 31102, z = 8 }, teleportTo = { x = 33902, y = 31098, z = 8 } }, + { register = { x = 33903, y = 31102, z = 8 }, teleportTo = { x = 33903, y = 31098, z = 8 } }, + { register = { x = 33904, y = 31102, z = 8 }, teleportTo = { x = 33904, y = 31098, z = 8 } }, + { register = { x = 33905, y = 31102, z = 8 }, teleportTo = { x = 33905, y = 31098, z = 8 } }, + -- From boat to room + { register = { x = 33899, y = 31105, z = 8 }, teleportTo = { x = 33899, y = 31109, z = 8 } }, + { register = { x = 33900, y = 31105, z = 8 }, teleportTo = { x = 33900, y = 31109, z = 8 } }, + { register = { x = 33901, y = 31105, z = 8 }, teleportTo = { x = 33901, y = 31109, z = 8 } }, + { register = { x = 33902, y = 31105, z = 8 }, teleportTo = { x = 33902, y = 31109, z = 8 } }, + { register = { x = 33903, y = 31105, z = 8 }, teleportTo = { x = 33903, y = 31109, z = 8 } }, + { register = { x = 33904, y = 31105, z = 8 }, teleportTo = { x = 33904, y = 31109, z = 8 } }, + { register = { x = 33905, y = 31105, z = 8 }, teleportTo = { x = 33905, y = 31109, z = 8 } }, + -- From room to boat + { register = { x = 33899, y = 31108, z = 8 }, teleportTo = { x = 33899, y = 31104, z = 8 } }, + { register = { x = 33900, y = 31108, z = 8 }, teleportTo = { x = 33900, y = 31104, z = 8 } }, + { register = { x = 33901, y = 31108, z = 8 }, teleportTo = { x = 33901, y = 31104, z = 8 } }, + { register = { x = 33902, y = 31108, z = 8 }, teleportTo = { x = 33902, y = 31104, z = 8 } }, + { register = { x = 33903, y = 31108, z = 8 }, teleportTo = { x = 33903, y = 31104, z = 8 } }, + { register = { x = 33904, y = 31108, z = 8 }, teleportTo = { x = 33904, y = 31104, z = 8 } }, + { register = { x = 33905, y = 31108, z = 8 }, teleportTo = { x = 33905, y = 31104, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33913, y = 31112, z = 8 }, teleportTo = { x = 33917, y = 31112, z = 8 } }, + { register = { x = 33913, y = 31113, z = 8 }, teleportTo = { x = 33917, y = 31113, z = 8 } }, + { register = { x = 33913, y = 31114, z = 8 }, teleportTo = { x = 33917, y = 31114, z = 8 } }, + { register = { x = 33913, y = 31115, z = 8 }, teleportTo = { x = 33917, y = 31115, z = 8 } }, + { register = { x = 33913, y = 31116, z = 8 }, teleportTo = { x = 33917, y = 31116, z = 8 } }, + -- Back to room + { register = { x = 33916, y = 31112, z = 8 }, teleportTo = { x = 33912, y = 31112, z = 8 } }, + { register = { x = 33916, y = 31113, z = 8 }, teleportTo = { x = 33912, y = 31113, z = 8 } }, + { register = { x = 33916, y = 31114, z = 8 }, teleportTo = { x = 33912, y = 31114, z = 8 } }, + { register = { x = 33916, y = 31115, z = 8 }, teleportTo = { x = 33912, y = 31115, z = 8 } }, + { register = { x = 33916, y = 31116, z = 8 }, teleportTo = { x = 33912, y = 31116, z = 8 } }, + -- From boat to room + { register = { x = 33918, y = 31112, z = 8 }, teleportTo = { x = 33922, y = 31112, z = 8 } }, + { register = { x = 33918, y = 31113, z = 8 }, teleportTo = { x = 33922, y = 31113, z = 8 } }, + { register = { x = 33918, y = 31114, z = 8 }, teleportTo = { x = 33922, y = 31114, z = 8 } }, + { register = { x = 33918, y = 31115, z = 8 }, teleportTo = { x = 33922, y = 31115, z = 8 } }, + { register = { x = 33918, y = 31116, z = 8 }, teleportTo = { x = 33922, y = 31116, z = 8 } }, + -- From room to boat + { register = { x = 33921, y = 31112, z = 8 }, teleportTo = { x = 33917, y = 31112, z = 8 } }, + { register = { x = 33921, y = 31113, z = 8 }, teleportTo = { x = 33917, y = 31113, z = 8 } }, + { register = { x = 33921, y = 31114, z = 8 }, teleportTo = { x = 33917, y = 31114, z = 8 } }, + { register = { x = 33921, y = 31115, z = 8 }, teleportTo = { x = 33917, y = 31115, z = 8 } }, + { register = { x = 33921, y = 31116, z = 8 }, teleportTo = { x = 33917, y = 31116, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33936, y = 31087, z = 8 }, teleportTo = { x = 33936, y = 31091, z = 8 } }, + { register = { x = 33937, y = 31087, z = 8 }, teleportTo = { x = 33937, y = 31091, z = 8 } }, + { register = { x = 33938, y = 31087, z = 8 }, teleportTo = { x = 33938, y = 31091, z = 8 } }, + { register = { x = 33939, y = 31087, z = 8 }, teleportTo = { x = 33939, y = 31091, z = 8 } }, + { register = { x = 33940, y = 31087, z = 8 }, teleportTo = { x = 33940, y = 31091, z = 8 } }, + { register = { x = 33941, y = 31087, z = 8 }, teleportTo = { x = 33941, y = 31091, z = 8 } }, + -- Back to room + { register = { x = 33936, y = 31090, z = 8 }, teleportTo = { x = 33936, y = 31086, z = 8 } }, + { register = { x = 33937, y = 31090, z = 8 }, teleportTo = { x = 33937, y = 31086, z = 8 } }, + { register = { x = 33938, y = 31090, z = 8 }, teleportTo = { x = 33938, y = 31086, z = 8 } }, + { register = { x = 33939, y = 31090, z = 8 }, teleportTo = { x = 33939, y = 31086, z = 8 } }, + { register = { x = 33940, y = 31090, z = 8 }, teleportTo = { x = 33940, y = 31086, z = 8 } }, + { register = { x = 33941, y = 31090, z = 8 }, teleportTo = { x = 33941, y = 31086, z = 8 } }, + -- From boat to room + { register = { x = 33936, y = 31095, z = 8 }, teleportTo = { x = 33934, y = 31099, z = 8 } }, + { register = { x = 33937, y = 31095, z = 8 }, teleportTo = { x = 33935, y = 31099, z = 8 } }, + { register = { x = 33938, y = 31095, z = 8 }, teleportTo = { x = 33936, y = 31099, z = 8 } }, + { register = { x = 33939, y = 31095, z = 8 }, teleportTo = { x = 33937, y = 31099, z = 8 } }, + { register = { x = 33940, y = 31095, z = 8 }, teleportTo = { x = 33938, y = 31099, z = 8 } }, + { register = { x = 33941, y = 31095, z = 8 }, teleportTo = { x = 33939, y = 31099, z = 8 } }, + -- From room to boat + { register = { x = 33934, y = 31098, z = 8 }, teleportTo = { x = 33936, y = 31094, z = 8 } }, + { register = { x = 33935, y = 31098, z = 8 }, teleportTo = { x = 33937, y = 31094, z = 8 } }, + { register = { x = 33936, y = 31098, z = 8 }, teleportTo = { x = 33938, y = 31094, z = 8 } }, + { register = { x = 33937, y = 31098, z = 8 }, teleportTo = { x = 33939, y = 31094, z = 8 } }, + { register = { x = 33938, y = 31098, z = 8 }, teleportTo = { x = 33940, y = 31094, z = 8 } }, + { register = { x = 33939, y = 31098, z = 8 }, teleportTo = { x = 33941, y = 31094, z = 8 } }, + { register = { x = 33940, y = 31098, z = 8 }, teleportTo = { x = 33942, y = 31094, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33939, y = 31064, z = 8 }, teleportTo = { x = 33939, y = 31060, z = 8 } }, + { register = { x = 33940, y = 31064, z = 8 }, teleportTo = { x = 33940, y = 31060, z = 8 } }, + { register = { x = 33941, y = 31064, z = 8 }, teleportTo = { x = 33941, y = 31060, z = 8 } }, + { register = { x = 33942, y = 31064, z = 8 }, teleportTo = { x = 33942, y = 31060, z = 8 } }, + { register = { x = 33943, y = 31064, z = 8 }, teleportTo = { x = 33943, y = 31060, z = 8 } }, + { register = { x = 33944, y = 31064, z = 8 }, teleportTo = { x = 33944, y = 31060, z = 8 } }, + -- Back to room + { register = { x = 33939, y = 31061, z = 8 }, teleportTo = { x = 33939, y = 31065, z = 8 } }, + { register = { x = 33940, y = 31061, z = 8 }, teleportTo = { x = 33940, y = 31065, z = 8 } }, + { register = { x = 33941, y = 31061, z = 8 }, teleportTo = { x = 33941, y = 31065, z = 8 } }, + { register = { x = 33942, y = 31061, z = 8 }, teleportTo = { x = 33942, y = 31065, z = 8 } }, + { register = { x = 33943, y = 31061, z = 8 }, teleportTo = { x = 33943, y = 31065, z = 8 } }, + { register = { x = 33944, y = 31061, z = 8 }, teleportTo = { x = 33944, y = 31065, z = 8 } }, + -- From boat to room + { register = { x = 33939, y = 31058, z = 8 }, teleportTo = { x = 33939, y = 31054, z = 8 } }, + { register = { x = 33940, y = 31058, z = 8 }, teleportTo = { x = 33940, y = 31054, z = 8 } }, + { register = { x = 33941, y = 31058, z = 8 }, teleportTo = { x = 33941, y = 31054, z = 8 } }, + { register = { x = 33942, y = 31058, z = 8 }, teleportTo = { x = 33942, y = 31054, z = 8 } }, + { register = { x = 33943, y = 31058, z = 8 }, teleportTo = { x = 33943, y = 31054, z = 8 } }, + { register = { x = 33944, y = 31058, z = 8 }, teleportTo = { x = 33944, y = 31054, z = 8 } }, + -- From room to boat + { register = { x = 33939, y = 31055, z = 8 }, teleportTo = { x = 33939, y = 31059, z = 8 } }, + { register = { x = 33940, y = 31055, z = 8 }, teleportTo = { x = 33940, y = 31059, z = 8 } }, + { register = { x = 33941, y = 31055, z = 8 }, teleportTo = { x = 33941, y = 31059, z = 8 } }, + { register = { x = 33942, y = 31055, z = 8 }, teleportTo = { x = 33942, y = 31059, z = 8 } }, + { register = { x = 33943, y = 31055, z = 8 }, teleportTo = { x = 33943, y = 31059, z = 8 } }, + { register = { x = 33944, y = 31055, z = 8 }, teleportTo = { x = 33944, y = 31059, z = 8 } }, + + -- Boat + -- Enter on boat + { register = { x = 33934, y = 31108, z = 8 }, teleportTo = { x = 33938, y = 31108, z = 8 } }, + { register = { x = 33934, y = 31109, z = 8 }, teleportTo = { x = 33938, y = 31109, z = 8 } }, + { register = { x = 33934, y = 31110, z = 8 }, teleportTo = { x = 33938, y = 31110, z = 8 } }, + { register = { x = 33934, y = 31111, z = 8 }, teleportTo = { x = 33938, y = 31111, z = 8 } }, + { register = { x = 33934, y = 31112, z = 8 }, teleportTo = { x = 33938, y = 31112, z = 8 } }, + -- Back to room + { register = { x = 33937, y = 31108, z = 8 }, teleportTo = { x = 33933, y = 31108, z = 8 } }, + { register = { x = 33937, y = 31109, z = 8 }, teleportTo = { x = 33933, y = 31109, z = 8 } }, + { register = { x = 33937, y = 31110, z = 8 }, teleportTo = { x = 33933, y = 31110, z = 8 } }, + { register = { x = 33937, y = 31111, z = 8 }, teleportTo = { x = 33933, y = 31111, z = 8 } }, + { register = { x = 33937, y = 31112, z = 8 }, teleportTo = { x = 33933, y = 31112, z = 8 } }, + -- From boat to room + { register = { x = 33942, y = 31108, z = 8 }, teleportTo = { x = 33946, y = 31108, z = 8 } }, + { register = { x = 33942, y = 31109, z = 8 }, teleportTo = { x = 33946, y = 31109, z = 8 } }, + { register = { x = 33942, y = 31110, z = 8 }, teleportTo = { x = 33946, y = 31110, z = 8 } }, + { register = { x = 33942, y = 31111, z = 8 }, teleportTo = { x = 33946, y = 31111, z = 8 } }, + { register = { x = 33942, y = 31112, z = 8 }, teleportTo = { x = 33946, y = 31112, z = 8 } }, + -- From room to boat + { register = { x = 33945, y = 31108, z = 8 }, teleportTo = { x = 33941, y = 31108, z = 8 } }, + { register = { x = 33945, y = 31109, z = 8 }, teleportTo = { x = 33941, y = 31109, z = 8 } }, + { register = { x = 33945, y = 31110, z = 8 }, teleportTo = { x = 33941, y = 31110, z = 8 } }, + { register = { x = 33945, y = 31111, z = 8 }, teleportTo = { x = 33941, y = 31111, z = 8 } }, + { register = { x = 33945, y = 31112, z = 8 }, teleportTo = { x = 33941, y = 31112, z = 8 } }, + }, +} + +function RegisterSoulWarBossesLevers() + -- Register levers + local goshnarsMaliceLever = BossLever(SoulWarQuest.levers.goshnarsMalice) + goshnarsMaliceLever:position(SoulWarQuest.levers.goshnarsMalicePosition) + goshnarsMaliceLever:register() + logger.debug("Registering soul war boss lever zone: {}", goshnarsMaliceLever:getZone():getName()) + + local goshnarsSpiteLever = BossLever(SoulWarQuest.levers.goshnarsSpite) + goshnarsSpiteLever:position(SoulWarQuest.levers.goshnarsSpitePosition) + goshnarsSpiteLever:register() + logger.debug("Registering soul war boss lever zone: {}", goshnarsSpiteLever:getZone():getName()) + + local goshnarsGreedLever = BossLever(SoulWarQuest.levers.goshnarsGreed) + goshnarsGreedLever:position(SoulWarQuest.levers.goshnarsGreedPosition) + goshnarsGreedLever:register() + logger.debug("Registering soul war boss lever zone: {}", goshnarsGreedLever:getZone():getName()) + + local goshnarsHatredLever = BossLever(SoulWarQuest.levers.goshnarsHatred) + goshnarsHatredLever:position(SoulWarQuest.levers.goshnarsHatredPosition) + goshnarsHatredLever:register() + logger.debug("Registering soul war boss lever zone: {}", goshnarsHatredLever:getZone():getName()) + + local goshnarsCrueltyLever = BossLever(SoulWarQuest.levers.goshnarsCruelty) + goshnarsCrueltyLever:position(SoulWarQuest.levers.goshnarsCrueltyPosition) + goshnarsCrueltyLever:register() + logger.debug("Registering soul war boss lever zone: {}", goshnarsCrueltyLever:getZone():getName()) + + local goshnarsMegalomaniaLever = BossLever(SoulWarQuest.levers.goshnarsMegalomania) + goshnarsMegalomaniaLever:position(SoulWarQuest.levers.goshnarsMegalomaniaPosition) + goshnarsMegalomaniaLever:register() + logger.debug("Registering soul war boss lever zone: {}", goshnarsMegalomaniaLever:getZone():getName()) +end + +-- Initialize ebb and flow zone area +SoulWarQuest.ebbAndFlow.zone:addArea({ x = 33869, y = 30991, z = 8 }, { x = 33964, y = 31147, z = 9 }) + +-- Initialize claustrophobic inferno raid zones and add remove destination + +for _, raid in ipairs(SoulWarQuest.claustrophobicInfernoRaids) do + local zone = raid.getZone() + zone:addArea(raid.zoneArea[1], raid.zoneArea[2]) + zone:setRemoveDestination(raid.exitPosition) +end + +-- Initialize bosses access for taint check +SoulWarQuest.areaZones.claustrophobicInferno:addArea({ x = 33982, y = 30981, z = 9 }, { x = 34051, y = 31110, z = 11 }) + +SoulWarQuest.areaZones.ebbAndFlow:addArea({ x = 33873, y = 30994, z = 8 }, { x = 33968, y = 31150, z = 9 }) + +SoulWarQuest.areaZones.furiousCrater:addArea({ x = 33814, y = 31819, z = 3 }, { x = 33907, y = 31920, z = 7 }) + +SoulWarQuest.areaZones.rottenWasteland:addArea({ x = 33980, y = 30986, z = 11 }, { x = 33901, y = 31105, z = 12 }) + +SoulWarQuest.areaZones.mirroredNightmare:addArea({ x = 33877, y = 31164, z = 9 }, { x = 33991, y = 31241, z = 13 }) + +-- Initialize safe areas (should not spawn monster, teleport, take damage from taint, etc) +SoulWarQuest.areaZones.claustrophobicInferno:subtractArea({ x = 34002, y = 31008, z = 9 }, { x = 34019, y = 31019, z = 9 }) + +SoulWarQuest.areaZones.ebbAndFlow:subtractArea({ x = 33887, y = 31015, z = 8 }, { x = 33920, y = 31024, z = 8 }) + +SoulWarQuest.areaZones.furiousCrater:subtractArea({ x = 33854, y = 31828, z = 3 }, { x = 33869, y = 31834, z = 3 }) + +SoulWarQuest.areaZones.rottenWasteland:subtractArea({ x = 33967, y = 31037, z = 11 }, { x = 33977, y = 31051, z = 11 }) + +SoulWarQuest.areaZones.mirroredNightmare:subtractArea({ x = 33884, y = 31181, z = 10 }, { x = 33892, y = 31198, z = 10 }) + +SoulCagePosition = Position(33709, 31596, 14) +TaintDurationSeconds = 14 * 24 * 60 * 60 -- 14 days +GreedbeastKills = 0 + +SoulWarReflectDamageMap = { + [COMBAT_PHYSICALDAMAGE] = 10, + [COMBAT_FIREDAMAGE] = 10, + [COMBAT_EARTHDAMAGE] = 10, + [COMBAT_ENERGYDAMAGE] = 10, + [COMBAT_ICEDAMAGE] = 10, + [COMBAT_HOLYDAMAGE] = 10, + [COMBAT_DEATHDAMAGE] = 10, +} + +local soulWarTaints = { + "taints-teleport", -- Taint 1 + "taints-spawn", -- Taint 2 + "taints-damage", -- Taint 3 + "taints-heal", -- Taint 4 + "taints-loss", -- Taint 5 +} + +GreedMonsters = { + ["Greedbeast"] = Position(33744, 31666, 14), + ["Soulsnatcher"] = Position(33747, 31668, 14), + ["Weak Soul"] = Position(33750, 31666, 14), + ["Strong Soul"] = Position(33750, 31666, 14), + ["Powerful Soul"] = Position(33750, 31666, 14), +} + +function CreateGoshnarsGreedMonster(name, position) + local function sendEffect() + position:sendMagicEffect(CONST_ME_TELEPORT) + end + + local function spawnMonster() + Game.createMonster(name, position, true, false) + logger.trace("Spawning {} in position {}", name, position:toString()) + end + + for i = 7, 9 do + addEvent(sendEffect, i * 1000) + end + + addEvent(spawnMonster, 10000) +end + +function RemoveSoulCageAndBuffMalice() + local soulCage = Creature("Soul Cage") + if soulCage then + soulCage:remove() + addEvent(SpawnSoulCage, 23000) + local malice = Creature("Goshnar's Malice") + if malice then + logger.trace("Found malice, try adding reflect and defense") + for elementType, reflectPercent in pairs(SoulWarReflectDamageMap) do + malice:addReflectElement(elementType, reflectPercent) + end + malice:addDefense(10) + end + end +end + +function SpawnSoulCage() + local tile = Tile(SoulCagePosition) + local creatures = tile:getCreatures() or {} + local soulCage = Creature("Soul Cage") + if not soulCage then + Game.createMonster("Soul Cage", SoulCagePosition, true, true) + logger.trace("Spawning Soul Cage in position {}", SoulCagePosition:toString()) + addEvent(RemoveSoulCageAndBuffMalice, 40000) + end +end + +local function shuffle(list) + for i = #list, 2, -1 do + local j = math.random(i) + list[i], list[j] = list[j], list[i] + end +end + +local function createConnectedGroup(startPos, groupPositions, groupSize) + local group = { startPos } + local lastPos = startPos + local directions = { + { x = 1, y = 0 }, + { x = -1, y = 0 }, -- Right and left + { x = 0, y = 1 }, + { x = 0, y = -1 }, -- Up and down + { x = 1, y = 1 }, + { x = -1, y = -1 }, -- Diagonals + { x = -1, y = 1 }, + { x = 1, y = -1 }, + } + + for i = 2, groupSize do + shuffle(directions) + local nextPos = nil + for _, dir in ipairs(directions) do + local potentialNextPos = Position(lastPos.x + dir.x, lastPos.y + dir.y, lastPos.z) + if table.contains(groupPositions, potentialNextPos) then + nextPos = potentialNextPos + break + end + end + + if nextPos then + table.insert(group, nextPos) + table.remove(groupPositions, table.find(groupPositions, nextPos)) + lastPos = nextPos + else + break + end + end + + return group +end + +local function generatePositionsInRange(center, range) + local positions = {} + for x = center.x - range, center.x + range do + for y = center.y - range, center.y + range do + table.insert(positions, Position(x, y, center.z)) + end + end + return positions +end + +local toRevertPositions = {} + +local tileItemIds = { + 32906, + 33066, + 33067, + 33068, + 33069, + 33070, +} + +local function revertTilesAndApplyDamage(zonePositions) + for _, pos in ipairs(zonePositions) do + local tile = Tile(pos) + if tile and tile:getGround() then + if tile:getGround():getId() ~= 409 then + local creature = tile:getTopCreature() + if creature then + local player = creature:getPlayer() + if player then + player:addHealth(-8000, COMBAT_DEATHDAMAGE) + end + end + end + + local itemFound = false + for i = 1, #tileItemIds do + local item = tile:getItemById(tileItemIds[i]) + if item then + itemFound = true + break + end + end + + if tile:getGround():getId() == 410 and not itemFound and not tile:getItemByTopOrder(1) and not tile:getItemByTopOrder(3) then + pos:sendMagicEffect(CONST_ME_REDSMOKE) + end + end + end + + for posString, itemId in pairs(toRevertPositions) do + local pos = posString:toPosition() + local tile = Tile(pos) + if tile and tile:getGround() and tile:getGround():getId() == 409 then + tile:getGround():transform(itemId) + toRevertPositions[pos:toString()] = nil + end + end +end + +function Monster:createSoulWarWhiteTiles(centerRoomPosition, zonePositions, executeInterval) + local groupPositions = generatePositionsInRange(centerRoomPosition, 7) + local totalTiles = 11 + local groupSize = 3 + local groupsCreated = 0 + + -- Run only for megalomania boss + if executeInterval then + -- Remove remains + for _, pos in ipairs(zonePositions) do + local tile = Tile(pos) + if tile and tile:getGround() then + local remains = tile:getItemById(33984) + if remains then + remains:remove() + end + end + end + end + + while #groupPositions > 0 and groupsCreated * groupSize < totalTiles do + local randomIndex = math.random(#groupPositions) + local startPos = groupPositions[randomIndex] + table.remove(groupPositions, randomIndex) + + local group = createConnectedGroup(startPos, groupPositions, groupSize) + for _, pos in ipairs(group) do + local tile = Tile(pos) + if tile then + toRevertPositions[pos:toString()] = tile:getGround():getId() + tile:getGround():transform(409) + end + end + + groupsCreated = groupsCreated + 1 + end + + addEvent(revertTilesAndApplyDamage, executeInterval or 3000, zonePositions) +end + +function MonsterType:calculateBagYouDesireChance(player, itemChance) + local playerTaintLevel = player:getTaintLevel() + if not playerTaintLevel or playerTaintLevel == 0 then + return itemChance + end + + local monsterName = self:getName() + local isMonsterValid = table.contains(SoulWarQuest.bagYouDesireMonsters, monsterName) + if not isMonsterValid then + return itemChance + end + + local soulWarQuest = player:soulWarQuestKV() + local megalomaniaKills = soulWarQuest:scoped("megalomania-kills"):get("count") or 0 + + if monsterName == "Goshnar's Megalomania" then + -- Special handling for Goshnar's Megalomania + itemChance = itemChance + megalomaniaKills * SoulWarQuest.bagYouDesireChancePerTaint + else + -- General handling for other monsters (bosses and non-bosses) + itemChance = itemChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) + end + + logger.info("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, itemChance) + + if math.random(1, 100000) <= totalChance then + logger.debug("Player {} killed {} and got a bag you desire with drop chance {}", player:getName(), monsterName, itemChance) + if monsterName == "Goshnar's Megalomania" then + -- Reset kill count on successful drop + soulWarQuest:scoped("megalomania-kills"):set("count", 0) + end + else + if monsterName == "Goshnar's Megalomania" then + -- Increment kill count for unsuccessful attempts + soulWarQuest:scoped("megalomania-kills"):set("count", megalomaniaKills + 1) + end + end + + return itemChance +end + +local intervalBetweenExecutions = 10000 + +local accumulatedTime = 0 +local desiredInterval = 40000 +local bossSayInterval = 38000 + +function Monster:onThinkMegalomaniaWhiteTiles(interval, zonePositions, revertTime) + self:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + + accumulatedTime = accumulatedTime + interval + + if accumulatedTime == bossSayInterval then + self:say("FEEL THE POWER OF MY WRATH!!") + end + -- Execute only after 40 seconds + if accumulatedTime >= desiredInterval then + self:createSoulWarWhiteTiles(SoulWarQuest.levers.goshnarsMegalomania.boss.position, zonePositions, revertTime) + accumulatedTime = 0 + end +end + +TaintTeleportCooldown = {} + +function Player:getTaintNameByNumber(taintNumber, skipKvCheck) + local haveTaintName = nil + local soulWarQuest = self:soulWarQuestKV() + local taintName = soulWarTaints[taintNumber] + if skipKvCheck or taintName and soulWarQuest:get(taintName) then + haveTaintName = taintName + end + + return haveTaintName +end + +function Player:addNextTaint() + local soulWarQuest = self:soulWarQuestKV() + for _, taintName in ipairs(soulWarTaints) do + if not soulWarQuest:get(taintName) then + soulWarQuest:set(taintName, true) + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have gained the " .. taintName .. ".") + self:setTaintIcon() + break + end + end +end + +function Player:setTaintIcon(taintId) + self:resetTaintConditions() + local condition = Condition(CONDITION_GOSHNARTAINT, CONDITIONID_DEFAULT, taintId or self:getTaintLevel()) + condition:setTicks(14 * 24 * 60 * 60 * 1000) + self:addCondition(condition) +end + +function Player:resetTaintConditions() + for i = 1, 5 do + self:removeCondition(CONDITION_GOSHNARTAINT, CONDITIONID_DEFAULT, i) + end +end + +function Player:getTaintLevel() + local taintLevel = nil + local soulWarQuest = self:soulWarQuestKV() + for i, taint in ipairs(soulWarTaints) do + if soulWarQuest:get(taint) then + taintLevel = i + end + end + + return taintLevel +end + +function Player:resetTaints(skipCheckTime) + local soulWarQuest = self:soulWarQuestKV() + local firstTaintTime = soulWarQuest:get("firstTaintTime") + if skipCheckTime or firstTaintTime and os.time() >= (firstTaintTime + TaintDurationSeconds) then + -- Reset all taints and remove condition + for _, taintName in ipairs(soulWarTaints) do + if soulWarQuest:get(taintName) then + soulWarQuest:remove(taintName) + end + end + self:resetTaintConditions() + soulWarQuest:remove("firstTaintTime") + local resetMessage = "Your Goshnar's taints have been reset." + if not skipCheckTime then + resetMessage = resetMessage .. " You didn't finish the quest in 14 days." + end + self:sendTextMessage(MESSAGE_EVENT_ADVANCE, resetMessage) + + for bossName, _ in pairs(SoulWarQuest.miniBosses) do + soulWarQuest:remove(bossName) + end + end +end + +function Monster:tryTeleportToPlayer(sayMessage) + local range = 30 + local spectators = Game.getSpectators(self:getPosition(), false, false, range, range, range, range) + local maxDistance = 0 + local farthestPlayer = nil + for i, spectator in ipairs(spectators) do + if spectator:isPlayer() then + local player = spectator:getPlayer() + if player:getTaintNameByNumber(1, true) and player:getSoulWarZoneMonster() ~= nil then + local distance = self:getPosition():getDistance(player:getPosition()) + if distance > maxDistance then + maxDistance = distance + farthestPlayer = player + logger.trace("Found player {} to teleport", player:getName()) + end + end + end + end + + if farthestPlayer and math.random(100) <= 10 then + local playerPosition = farthestPlayer:getPosition() + if TaintTeleportCooldown[farthestPlayer:getId()] then + logger.trace("Cooldown is active to player {}", farthestPlayer:getName()) + return + end + + if not TaintTeleportCooldown[farthestPlayer:getId()] then + TaintTeleportCooldown[farthestPlayer:getId()] = true + + logger.trace("Scheduling player {} to teleport", farthestPlayer:getName()) + self:getPosition():sendMagicEffect(CONST_ME_MORTAREA) + farthestPlayer:getPosition():sendMagicEffect(CONST_ME_MORTAREA) + addEvent(function(playerId, monsterId) + local monsterEvent = Monster(monsterId) + local playerEvent = Player(playerId) + if monsterEvent and playerEvent then + local destinationTile = Tile(playerPosition) + if destinationTile and not (destinationTile:hasProperty(CONST_PROP_BLOCKPROJECTILE) or destinationTile:hasProperty(CONST_PROP_MOVEABLE)) then + monsterEvent:say(sayMessage) + monsterEvent:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + monsterEvent:teleportTo(playerPosition, true) + monsterEvent:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + end + end + end, 2000, farthestPlayer:getId(), self:getId()) + + addEvent(function(playerId) + local playerEvent = Player(playerId) + if not playerEvent then + return + end + + logger.trace("Cleaning player cooldown") + TaintTeleportCooldown[playerEvent:getId()] = nil + end, 10000, farthestPlayer:getId()) + end + end +end + +function Monster:getSoulWarKV() + return SoulWarQuest.kvSoulWar:scoped("monster"):scoped(self:getName()) +end + +function Monster:getHatredDamageMultiplier() + return self:getSoulWarKV():get("burning-hatred-empowered") or 0 +end + +function Monster:increaseHatredDamageMultiplier(multiplierCount) + local attackMultiplier = self:getHatredDamageMultiplier() + self:getSoulWarKV():set("burning-hatred-empowered", attackMultiplier + multiplierCount) +end + +function Monster:resetHatredDamageMultiplier() + self:getSoulWarKV():remove("burning-hatred-empowered") +end + +function Position:increaseNecromaticMegalomaniaStrength() + local tile = Tile(self) + if tile then + local item = tile:getItemById(SoulWarQuest.necromanticRemainsId) + if item then + local boss = Creature("Goshnar's Megalomania") + if boss then + boss:increaseHatredDamageMultiplier(5) + item:remove() + logger.trace("Necromantic remains strength increased") + end + end + end +end + +local lastExecutionTime = 0 + +-- Damage 24 to 36 have a special damage +local damageTable = { + 1400, + 1600, + 1800, + 2200, + 2400, + 2600, + 3000, + 3400, + 3800, + 4200, + 4800, + 5200, + 5600, +} + +function Monster:onThinkGoshnarTormentCounter(interval, maxLimit, intervalBetweenExecutions, bossPosition) + local interval = os.time() * 1000 + if interval - lastExecutionTime < intervalBetweenExecutions then + return + end + + lastExecutionTime = interval + logger.trace("Icon time count {}", interval) + local spectators = Game.getSpectators(bossPosition, false, true, 15, 15, 15, 15) + for i = 1, #spectators do + local player = spectators[i] + local tormentCounter = player:getGoshnarSymbolTormentCounter() + local goshnarsHatred = Creature(bossName or "Goshnar's Megalomania") + if not goshnarsHatred then + player:resetGoshnarSymbolTormentCounter() + goto continue + end + + if tormentCounter <= maxLimit then + player:increaseGoshnarSymbolTormentCounter(maxLimit) + logger.trace("Player {} has {} damage counter", player:getName(), tormentCounter) + + if tormentCounter > 0 then + local damage = tormentCounter * 35 + if tormentCounter >= 24 then + damage = damageTable[tormentCounter - 23] + end + + logger.trace("Final damage {}", damage) + player:addHealth(-damage, COMBAT_DEATHDAMAGE) + player:getPosition():sendMagicEffect(CONST_ME_PINK_ENERGY_SPARK) + end + end + + if tormentCounter == 5 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The dread starts to torment you! Don't let dread level reach critical value!") + elseif tormentCounter == 15 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The dread's torment becomes unbearable!") + elseif tormentCounter == 24 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The Dread's torment begins to tear you apart!") + elseif tormentCounter == 30 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The dread's torment is killing you!") + elseif tormentCounter == 36 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The dread's torment is now lethal!") + end + + ::continue:: + end +end + +function Monster:increaseAspectOfPowerDeathCount() + local bossKV = self:getSoulWarKV() + local aspectDeathCount = bossKV:get("aspect-of-power-death-count") or 0 + local newCount = aspectDeathCount + 1 + logger.trace("Aspect of Power death count {}", newCount) + bossKV:set("aspect-of-power-death-count", newCount) + if newCount == 4 then + self:setType("Goshnar's Megalomania Green") + self:say("THE DEATH OF ASPECTS DIMINISHES GOSHNAR'S POWER AND HE TURNS VULNERABLE!") + bossKV:set("aspect-of-power-death-count", 0) + SoulWarQuest.changeBlueEvent = addEvent(SoulWarQuest.changeMegalomaniaBlue, 1 * 60 * 1000) + logger.trace("Aspect of Power defeated all and Megalomania is now vulnerable, reseting death count.") + SoulWarQuest.changePurpleEvent = addEvent(function() + local boss = Creature("Goshnar's Megalomania") + if boss and boss:getTypeName() == "Goshnar's Megalomania Green" then + boss:setType("Goshnar's Megalomania Purple") + boss:say("GOSHNAR REGAINED ENOUGH POWER TO TURN INVULNERABLE AGAIN!") + logger.trace("Megalomania is now immune again") + end + end, SoulWarQuest.timeToReturnImmuneMegalomania * 1000) + end +end + +function Monster:goshnarsDefenseIncrease(kvName) + local currentTime = os.time() + -- Gets the time when the "Greedy Maw" item was last used. + local lastItemUseTime = SoulWarQuest.kvSoulWar:get(kvName) or 0 + -- Checks if more than config time have passed since the item was last used. + if currentTime >= lastItemUseTime + SoulWarQuest.timeToIncreaseCrueltyDefense then + self:addDefense(SoulWarQuest.goshnarsCrueltyDefenseChange) + -- Register the drain callback to modify the damage for goshnar's cruelty + local newValue = SoulWarQuest.kvSoulWar:get("goshnars-cruelty-defense-drain") or SoulWarQuest.goshnarsCrueltyDefenseChange + SoulWarQuest.kvSoulWar:set("goshnars-cruelty-defense-drain", newValue + 1) -- Increment the value to track usage or modifications + + --- Updates the KV to reflect the timing of the increase to maintain control. + SoulWarQuest.kvSoulWar:set(kvName, currentTime) + else + -- If config time have not passed, logs the increase has been skipped. + logger.trace("{} skips increase cooldown due to recent item use.", self:getName()) + end +end + +function Monster:removeGoshnarsMegalomaniaMonsters(zone) + if self:getName() ~= "Goshnar's Megalomania" then + return + end + + if zone then + local creatures = zone:getCreatures() + for _, creature in ipairs(creatures) do + if creature:getMonster() then + creature:remove() + end + end + end +end + +function Player:getSoulWarZoneMonster() + local zoneMonsterName = nil + for zoneName, monsterName in pairs(SoulWarQuest.areaZones.monsters) do + local zone = Zone.getByName(zoneName) + if zone and zone:isInZone(self:getPosition()) then + zoneMonsterName = monsterName + break + end + end + + return zoneMonsterName +end + +function Player:isInBoatSpot() + -- Get ebb and flow zone and check if player is in zone + local zone = SoulWarQuest.ebbAndFlow.getZone() + local tile = Tile(self:getPosition()) + local groundId + if tile and tile:getGround() then + groundId = tile:getGround():getId() + end + if zone and zone:isInZone(self:getPosition()) and tile and groundId == SoulWarQuest.ebbAndFlow.boatId then + logger.trace("Player {} is in boat spot", self:getName()) + return true + end + + logger.trace("Player {} is not in boat spot", self:getName()) + return false +end + +function Player:soulWarQuestKV() + return self:kv():scoped("quest"):scoped("soul-war") +end + +function Player:getGoshnarSymbolTormentCounter() + local soulWarKV = self:soulWarQuestKV() + return soulWarKV:get("goshnars-hatred-torment-count") or 0 +end + +function Player:increaseGoshnarSymbolTormentCounter(maxLimit) + local soulWarKV = self:soulWarQuestKV() + local tormentCount = self:getGoshnarSymbolTormentCounter() + if tormentCount == maxLimit then + self:setIcon("goshnars-hatred-damage", CreatureIconCategory_Quests, CreatureIconQuests_RedCross, tormentCount) + return + end + + self:setIcon("goshnars-hatred-damage", CreatureIconCategory_Quests, CreatureIconQuests_RedCross, tormentCount + 1) + soulWarKV:set("goshnars-hatred-torment-count", tormentCount + 1) +end + +function Player:removeGoshnarSymbolTormentCounter(count) + local soulWarKV = self:soulWarQuestKV() + local tormentCount = self:getGoshnarSymbolTormentCounter() + if tormentCount > count then + self:setIcon("goshnars-hatred-damage", CreatureIconCategory_Quests, CreatureIconQuests_RedCross, tormentCount - count) + soulWarKV:set("goshnars-hatred-torment-count", tormentCount - count) + else + self:resetGoshnarSymbolTormentCounter() + end +end + +function Player:resetGoshnarSymbolTormentCounter() + local soulWarKV = self:soulWarQuestKV() + soulWarKV:remove("goshnars-hatred-torment-count") + self:removeIcon("goshnars-hatred-damage") +end + +function Player:furiousCraterKV() + return self:soulWarQuestKV():scoped("furius-crater") +end + +function Player:pulsatingEnergyKV() + return self:furiousCraterKV():scoped("pulsating-energy") +end + +function Zone:getRandomPlayer() + local players = self:getPlayers() + if #players == 0 then + return nil + end + + local randomIndex = math.random(#players) + return players[randomIndex] +end + +local conditionOutfit = Condition(CONDITION_OUTFIT) + +local function delayedCastSpell(cid, var, combat, targetId) + local creature = Creature(cid) + if not creature then + return + end + + local target = Player(targetId) + if target then + combat:execute(creature, positionToVariant(target:getPosition())) + target:removeCondition(conditionOutfit) + end +end + +function Creature:applyZoneEffect(var, combat, zoneName) + local outfitConfig = { + outfit = { lookType = 242, lookHead = 0, lookBody = 0, lookLegs = 0, lookFeet = 0, lookAddons = 0 }, + time = 7000, + } + + local zone = Zone.getByName(zoneName) + if not zone then + logger.error("Could not find zone '" .. zoneName .. "', you need use the 'BossLever' system") + return false + end + + local target = zone:getRandomPlayer() + if not target then + return true + end + + conditionOutfit:setTicks(outfitConfig.time) + conditionOutfit:setOutfit(outfitConfig.outfit) + target:addCondition(conditionOutfit) + target:getPosition():sendMagicEffect(CONST_ME_MAGIC_BLUE) + + addEvent(delayedCastSpell, SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, self:getId(), var, combat, target:getId()) + + return true +end + +function string.toPosition(str) + local patterns = { + -- table format + "{%s*x%s*=%s*(%d+)%s*,%s*y%s*=%s*(%d+)%s*,%s*z%s*=%s*(%d+)%s*}", + -- Position format + "Position%s*%((%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)", + -- x, y, z format + "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)", + } + + for _, pattern in ipairs(patterns) do + local x, y, z = string.match(str, pattern) + if x and y and z then + return Position(tonumber(x), tonumber(y), tonumber(z)) + end + end +end diff --git a/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua b/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua index 8ff012c96ef..896cb520722 100644 --- a/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua +++ b/data-otservbr-global/monster/quests/soul_war/aspect_of_power.lua @@ -4,20 +4,20 @@ local monster = {} monster.description = "an aspect of power" monster.experience = 0 monster.outfit = { - lookType = 1303, + lookType = 1306, lookHead = 0, lookBody = 0, lookLegs = 0, lookFeet = 0, - lookAddons = 1, + lookAddons = 0, lookMount = 0, } monster.health = 25000 monster.maxHealth = 25000 monster.race = "undead" -monster.corpse = 0 -monster.speed = 125 +monster.corpse = 33949 +monster.speed = 175 monster.manaCost = 0 monster.changeTarget = { @@ -25,8 +25,14 @@ monster.changeTarget = { chance = 8, } +monster.events = { + "SoulWarAspectOfPowerDeath", +} + monster.strategiesTarget = { nearest = 100, + health = 20, + damage = 30, } monster.flags = { @@ -64,16 +70,15 @@ monster.attacks = { { name = "combat", interval = 1700, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -400, maxDamage = -950, radius = 3, shootEffect = CONST_ANI_ENVENOMEDARROW, effect = CONST_ME_HITBYPOISON, target = true }, { name = "combat", interval = 1700, chance = 25, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -850, length = 4, spread = 0, effect = CONST_ME_ENERGYHIT, target = false }, { name = "combat", interval = 1700, chance = 35, type = COMBAT_DEATHDAMAGE, minDamage = -700, maxDamage = -1550, radius = 3, effect = CONST_ME_MORTAREA, target = false }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's hatred" }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's greed" }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's malice" }, - { name = "outfit", interval = 1000, chance = 5, radius = 8, effect = CONST_ME_LOSEENERGY, target = false, duration = 5000, outfitMonster = "goshnar's spite" }, } monster.defenses = { defense = 40, - armor = 0, - -- mitigation = ???, + armor = 40, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Malice" }, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Cruelty" }, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Hatred" }, + { name = "outfit", interval = 4000, chance = 30, target = false, duration = 4000, outfitMonster = "Goshnar's Spite" }, } monster.elements = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua similarity index 63% rename from data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua rename to data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 7346e628cfc..1433d2ed7d4 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_megalomania.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -1,44 +1,40 @@ -local mType = Game.createMonsterType("Goshnar's Megalomania") +local mType = Game.createMonsterType("Goshnar's Megalomania Blue") local monster = {} +monster.name = "Goshnar's Megalomania" monster.description = "Goshnar's Megalomania" -monster.experience = 200000 +monster.experience = 3000000 monster.outfit = { - lookType = 1308, - lookHead = 0, - lookBody = 0, - lookLegs = 0, - lookFeet = 0, - lookAddons = 0, - lookMount = 0, + lookType = 1337, } -monster.events = { - "SoulwarsBossDeath", -} - -monster.health = 500000 -monster.maxHealth = 500000 +monster.health = 620000 +monster.maxHealth = 620000 monster.race = "undead" monster.corpse = 33889 -monster.speed = 165 +monster.speed = 0 monster.manaCost = 0 - -monster.changeTarget = { - interval = 2000, - chance = 10, -} +monster.maxSummons = 4 monster.bosstiary = { bossRaceId = 1969, bossRace = RARITY_NEMESIS, } +monster.changeTarget = { + interval = 4000, + chance = 10, +} + monster.strategiesTarget = { - nearest = 70, + nearest = 80, health = 10, damage = 10, - random = 10, +} + +monster.events = { + "GoshnarsHatredBuff", + "MegalomaniaDeath", } monster.flags = { @@ -51,7 +47,7 @@ monster.flags = { illusionable = false, canPushItems = true, canPushCreatures = true, - staticAttackChance = 95, + staticAttackChance = 80, targetDistance = 1, runHealth = 0, healthHidden = false, @@ -67,14 +63,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "aspect of power", chance = 50, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -110,19 +98,14 @@ monster.loot = { } monster.attacks = { - { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -8000 }, - { name = "combat", interval = 2000, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -2950, maxDamage = -4400, range = 7, radius = 3, shootEffect = CONST_ANI_DEATH, effect = CONST_ME_MORTAREA, target = true }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -3000, maxDamage = -5500, length = 8, spread = 0, effect = CONST_ME_INSECTS, target = false }, - { name = "singlecloudchain", interval = 6000, chance = 40, minDamage = -3300, maxDamage = -5500, range = 6, effect = CONST_ME_ENERGYHIT, target = true }, - { name = "combat", interval = 2000, chance = 10, type = COMBAT_DEATHDAMAGE, minDamage = -3300, maxDamage = -5200, length = 10, spread = 0, effect = CONST_ME_BLUE_GHOST, target = false }, + { name = "melee", interval = 2000, chance = 100, minDamage = -400, maxDamage = -2225 }, + { name = "megalomania blue", interval = 6000, chance = 100, target = true }, + { name = "combat", interval = 30000, chance = 100, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -1500, length = 8, radius = 5, spread = 0, effect = CONST_ME_PINK_ENERGY_SPARK, target = true }, } monster.defenses = { - defense = 160, - armor = 160, - mitigation = 8.40, - { name = "speed", interval = 1000, chance = 20, speedChange = 500, effect = CONST_ME_MAGIC_RED, target = false, duration = 10000 }, - { name = "combat", interval = 2000, chance = 25, type = COMBAT_HEALING, minDamage = 2250, maxDamage = 4250, effect = CONST_ME_MAGIC_BLUE, target = false }, + defense = 55, + armor = 55, } monster.elements = { @@ -145,18 +128,25 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end - mType.onAppear = function(monster, creature) if monster:getType():isRewardBoss() then monster:setReward(true) end end -mType.onDisappear = function(monster, creature) end +local intervalBetweenExecutions = 10000 -mType.onMove = function(monster, creature, fromPosition, toPosition) end +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() -mType.onSay = function(monster, creature, type, message) end +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + monsterCallback:onThinkMegalomaniaWhiteTiles(interval, zonePositions, 8000) + monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") +end + +mType.onDisappear = function(monster, creature) + creature:removeGoshnarsMegalomaniaMonsters(zone) +end mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua new file mode 100644 index 00000000000..f6d60b0b747 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -0,0 +1,163 @@ +local mType = Game.createMonsterType("Goshnar's Megalomania Green") +local monster = {} + +monster.name = "Goshnar's Megalomania" +monster.description = "Goshnar's Megalomania" +monster.experience = 3000000 +monster.outfit = { + lookType = 99, + lookHead = 95, + lookBody = 116, + lookLegs = 119, + lookFeet = 115, + lookAddons = 0, + lookMount = 0, +} + +monster.bosstiary = { + bossRaceId = 1969, + bossRace = RARITY_NEMESIS, +} + +monster.health = 620000 +monster.maxHealth = 620000 +monster.race = "undead" +monster.corpse = 33889 +monster.speed = 250 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.events = { + "GoshnarsHatredBuff", + "MegalomaniaDeath", +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.loot = { + { name = "crystal coin", chance = 55000, minCount = 70, maxCount = 75 }, + { id = 281, chance = 1150 }, -- giant shimmering pearl (green) + { name = "giant sapphire", chance = 10000, maxCount = 1 }, + { name = "giant topaz", chance = 10000, maxCount = 1 }, + { name = "violet gem", chance = 6000, maxCount = 1 }, + { name = "blue gem", chance = 10000, maxCount = 3 }, + { id = 3039, chance = 10000, maxCount = 3 }, -- red gem + { name = "green gem", chance = 10000, maxCount = 3 }, + { name = "yellow gem", chance = 10000, maxCount = 3 }, + { name = "white gem", chance = 6000, maxCount = 3 }, + { name = "dragon figurine", chance = 10000, maxCount = 1 }, + { name = "bullseye potion", chance = 15000, minCount = 10, maxCount = 25 }, + { name = "mastermind potion", chance = 15000, minCount = 10, maxCount = 25 }, + { name = "berserk potion", chance = 15000, minCount = 10, maxCount = 25 }, + { name = "ultimate mana potion", chance = 18000, minCount = 50, maxCount = 100 }, + { name = "supreme health potion", chance = 18000, minCount = 50, maxCount = 100 }, + { name = "ultimate spirit potion", chance = 18000, minCount = 50, maxCount = 100 }, + { name = "figurine of malice", chance = 400 }, + { name = "figurine of cruelty", chance = 400 }, + { name = "figurine of hatred", chance = 400 }, + { name = "figurine of greed", chance = 400 }, + { name = "figurine of spite", chance = 400 }, + { name = "figurine of megalomania", chance = 400 }, + { name = "megalomania's skull", chance = 400 }, + { name = "megalomania's essence", chance = 400 }, + { name = "bag you desire", chance = 100 }, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -800, maxDamage = -2500 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_MORTAREA, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_BLACK_BLOOD, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_GHOST_SMOKE, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_SMALLCLOUDS, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -950, maxDamage = -1400, radius = 3, effect = CONST_ME_MORTAREA, target = true }, + { name = "soulwars fear", interval = 35000, chance = 100, target = true }, + { name = "megalomania transform elemental", interval = SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, chance = 50 }, + { name = "combat", interval = 30000, chance = 100, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -1500, length = 8, radius = 5, spread = 0, effect = CONST_ME_PINK_ENERGY_SPARK, target = true }, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onAppear = function(monster, creature) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end +end + +local intervalBetweenExecutions = 10000 + +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() + +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + monsterCallback:onThinkMegalomaniaWhiteTiles(interval, zonePositions, 8000) + monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") +end + +mType.onDisappear = function(monster, creature) + creature:removeGoshnarsMegalomaniaMonsters(zone) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua new file mode 100644 index 00000000000..79914af4935 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua @@ -0,0 +1,129 @@ +local mType = Game.createMonsterType("Goshnar's Megalomania Purple") +local monster = {} + +monster.name = "Goshnar's Megalomania" +monster.description = "Goshnar's Megalomania" +monster.experience = 0 +monster.outfit = { + lookType = 1308, +} + +monster.health = 620000 +monster.maxHealth = 620000 +monster.race = "undead" +monster.corpse = 6028 +monster.speed = 250 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 80, + health = 10, + damage = 10, +} + +monster.events = { + "GoshnarsHatredBuff", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = true, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 80, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -800, maxDamage = -2500 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_MORTAREA, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1550, maxDamage = -2620, length = 8, spread = 0, effect = CONST_ME_BLACK_BLOOD, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_GHOST_SMOKE, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -1050, maxDamage = -2020, length = 8, spread = 3, effect = CONST_ME_SMALLCLOUDS, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -950, maxDamage = -1400, radius = 3, effect = CONST_ME_MORTAREA, target = true }, + { name = "soulwars fear", interval = 35000, chance = 100, target = true }, + { name = "megalomania transform elemental", interval = SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, chance = 50 }, + { name = "combat", interval = 30000, chance = 100, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -1500, length = 8, radius = 5, spread = 0, effect = CONST_ME_PINK_ENERGY_SPARK, target = true }, +} + +monster.defenses = { + defense = 25, + armor = 25, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monster) + if monster:getType():isRewardBoss() then + monster:setReward(true) + end + + if SoulWarQuest.changeBlueEvent then + stopEvent(SoulWarQuest.changeBlueEvent) + end + if SoulWarQuest.changePurpleEvent then + stopEvent(SoulWarQuest.changePurpleEvent) + end + + local bossKV = monster:getSoulWarKV() + bossKV:set("aspect-of-power-death-count", 0) + monster:resetHatredDamageMultiplier() +end + +local intervalBetweenExecutions = 10000 + +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() + +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 36, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsMegalomania.boss.position) + monsterCallback:onThinkMegalomaniaWhiteTiles(interval, zonePositions, 8000) + monsterCallback:goshnarsDefenseIncrease("cleansed-sanity-action") +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index 40957e0bde4..44f87cca574 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -14,7 +14,8 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", + "GoshnarsCrueltyBuff", } monster.health = 300000 @@ -67,14 +68,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "mean maw", chance = 30, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -112,6 +105,7 @@ monster.attacks = { { name = "singlecloudchain", interval = 6000, chance = 40, minDamage = -1700, maxDamage = -2500, range = 6, effect = CONST_ME_ENERGYHIT, target = true }, { name = "combat", interval = 2000, chance = 30, type = COMBAT_PHYSICALDAMAGE, minDamage = -1000, maxDamage = -2500, range = 7, radius = 4, shootEffect = CONST_ANI_EXPLOSION, effect = CONST_ME_DRAWBLOOD, target = true }, { name = "combat", interval = 2000, chance = 15, type = COMBAT_DEATHDAMAGE, minDamage = -1500, maxDamage = -3000, radius = 3, effect = CONST_ME_GROUNDSHAKER, target = false }, + { name = "cruelty transform elemental", interval = SoulWarQuest.goshnarsCrueltyWaveInterval * 1000, chance = 50 }, } monster.defenses = { @@ -142,15 +136,33 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end +local firstTime = 0 +mType.onThink = function(monster, interval) + firstTime = firstTime + interval + -- Run only 15 seconds before creation + if firstTime >= 15000 then + monster:goshnarsDefenseIncrease("greedy-maw-action") + end +end + +mType.onAppear = function(monster, creature) end -mType.onAppear = function(monster, creature) - if monster:getType():isRewardBoss() then - monster:setReward(true) +mType.onSpawn = function(monsterCallback) + if monsterCallback:getType():isRewardBoss() then + monsterCallback:setReward(true) end + + firstTime = 0 end -mType.onDisappear = function(monster, creature) end +mType.onDisappear = function(monster, creature) + if creature:getName() == "Goshnar's Cruelty" then + local eyeCreature = Creature("A Greedy Eye") + if eyeCreature then + eyeCreature:remove() + end + end +end mType.onMove = function(monster, creature, fromPosition, toPosition) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index 171f963f5ff..f1f5284398e 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -14,7 +14,7 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", } monster.health = 300000 @@ -68,11 +68,7 @@ monster.light = { } monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 10, interval = 1000, count = 2 }, - { name = "hateful soul", chance = 10, interval = 1000, count = 2 }, - }, + maxSummons = 1, } monster.voices = { @@ -139,15 +135,56 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end +local immuneTimeCount = 0 +local isImmune = nil +local createdSoulSphere = nil +mType.onThink = function(monsterCallback, interval) + if GreedbeastKills >= 5 and isImmune == nil then + isImmune = monsterCallback:immune(false) + monsterCallback:teleportTo(Position(33741, 31659, 14)) + monsterCallback:setSpeed(0) + createdSoulSphere = Game.createMonster("Soul Sphere", Position(33752, 31659, 14), true, true) + end + if isImmune ~= nil then + immuneTimeCount = immuneTimeCount + interval + logger.info("Immune time count {}", immuneTimeCount) + if immuneTimeCount >= 45000 then + monsterCallback:immune(true) + monsterCallback:setSpeed(monster.speed) + monsterCallback:teleportTo(Position(33746, 31666, 14)) + immuneTimeCount = 0 + GreedbeastKills = 0 + isImmune = nil + if createdSoulSphere then + createdSoulSphere:remove() + end + end + end +end -mType.onAppear = function(monster, creature) +mType.onSpawn = function(monster) if monster:getType():isRewardBoss() then monster:setReward(true) end + + isImmune = nil + monster:immune(true) + immuneTimeCount = 0 + GreedbeastKills = 0 end -mType.onDisappear = function(monster, creature) end +mType.onDisappear = function(monster, creature) + if creature:getName() == "Greedbeast" then + logger.debug("GreedbeastKills {}", GreedbeastKills) + end + if creature:getName() == "Goshnar's Greed" then + logger.debug("Killed goshnar's greed") + if createdSoulSphere then + logger.debug("Found soul sphere, remove it") + createdSoulSphere:remove() + end + end +end mType.onMove = function(monster, creature, fromPosition, toPosition) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua index fa5ccf36984..47fedea4e7c 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua @@ -14,7 +14,8 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "GoshnarsHatredBuff", + "SoulWarBossesDeath", } monster.health = 300000 @@ -67,14 +68,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 10, interval = 1000, count = 2 }, - { name = "hateful soul", chance = 10, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -143,13 +136,26 @@ monster.immunities = { mType.onThink = function(monster, interval) end -mType.onAppear = function(monster, creature) +mType.onAppear = function(monster, creature) end + +mType.onSpawn = function(monster) if monster:getType():isRewardBoss() then monster:setReward(true) end + + monster:resetHatredDamageMultiplier() end -mType.onDisappear = function(monster, creature) end +mType.onDisappear = function(monster, creature) + if creature:getName() == "Goshnar's Hatred" then + for _, monsterName in pairs(SoulWarQuest.burningHatredMonsters) do + local ashesCreature = Creature(monsterName) + if ashesCreature then + ashesCreature:remove() + end + end + end +end mType.onMove = function(monster, creature, fromPosition, toPosition) end diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua index 5e79dcccbdd..e0b054dc10d 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua @@ -14,7 +14,8 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", + "Goshnar's-Malice", } monster.health = 300000 @@ -67,14 +68,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "malicious soul", chance = 30, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -141,7 +134,19 @@ monster.immunities = { { type = "bleed", condition = false }, } -mType.onThink = function(monster, interval) end +local zone = Zone.getByName("boss.goshnar's-malice") +local zonePositions = zone:getPositions() + +local accumulatedTime = 0 +local desiredInterval = 40000 +mType.onThink = function(monster, interval) + accumulatedTime = accumulatedTime + interval + -- Execute only after 40 seconds + if accumulatedTime >= desiredInterval then + monster:createSoulWarWhiteTiles(SoulWarQuest.levers.goshnarsMalice.boss.position, zonePositions) + accumulatedTime = 0 + end +end mType.onAppear = function(monster, creature) if monster:getType():isRewardBoss() then diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua index 19d35cd1af7..40817c335b0 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua @@ -14,7 +14,7 @@ monster.outfit = { } monster.events = { - "SoulwarsBossDeath", + "SoulWarBossesDeath", } monster.health = 300000 @@ -67,14 +67,6 @@ monster.light = { color = 0, } -monster.summon = { - maxSummons = 4, - summons = { - { name = "dreadful harvester", chance = 40, interval = 1000, count = 2 }, - { name = "spiteful spitter", chance = 30, interval = 1000, count = 2 }, - }, -} - monster.voices = { interval = 5000, chance = 10, @@ -111,6 +103,7 @@ monster.attacks = { { name = "singlecloudchain", interval = 6000, chance = 40, minDamage = -1700, maxDamage = -1900, range = 6, effect = CONST_ME_ENERGYHIT, target = true }, { name = "combat", interval = 2000, chance = 30, type = COMBAT_EARTHDAMAGE, minDamage = -1200, maxDamage = -3500, range = 7, radius = 4, shootEffect = CONST_ANI_POISON, effect = CONST_ME_GREEN_RINGS, target = true }, { name = "combat", interval = 2000, chance = 10, type = COMBAT_EARTHDAMAGE, minDamage = -1400, maxDamage = -2200, length = 8, spread = 0, effect = CONST_ME_GREEN_RINGS, target = false }, + { name = "soulwars fear", interval = 2000, chance = 10, target = true }, } monster.defenses = { diff --git a/data-otservbr-global/monster/quests/soul_war/greedbeast.lua b/data-otservbr-global/monster/quests/soul_war/greedbeast.lua new file mode 100644 index 00000000000..db7076d5710 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/greedbeast.lua @@ -0,0 +1,98 @@ +local mType = Game.createMonsterType("Greedbeast") +local monster = {} + +monster.description = "a greedbeast" +monster.experience = 0 +monster.outfit = { + lookType = 101, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 200 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -500, condition = { type = CONDITION_POISON, totalDamage = 100, interval = 4000 } }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_EARTHDAMAGE, minDamage = -50, maxDamage = -90, range = 7, shootEffect = CONST_ANI_POISON, effect = CONST_ME_POISONAREA, target = false }, + { name = "combat", interval = 2000, chance = 10, type = COMBAT_LIFEDRAIN, minDamage = -25, maxDamage = -47, radius = 3, effect = CONST_ME_MAGIC_RED, target = false }, + -- poison + { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -200, maxDamage = -400, radius = 3, effect = CONST_ME_POISONAREA, target = false }, + -- poison + { name = "condition", type = CONDITION_POISON, interval = 2000, chance = 10, minDamage = -200, maxDamage = -400, length = 6, spread = 0, effect = CONST_ME_POISONAREA, target = false }, + { name = "speed", interval = 2000, chance = 15, speedChange = -600, target = true, duration = 13000 }, +} + +monster.defenses = { + defense = 70, + armor = 80, + mitigation = 1.15, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_HEALING, minDamage = 50, maxDamage = 60, effect = CONST_ME_HITBYPOISON, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = -10 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = -25 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/mirror_image.lua b/data-otservbr-global/monster/quests/soul_war/mirror_image.lua index 8813ba2e93f..600550d3d78 100644 --- a/data-otservbr-global/monster/quests/soul_war/mirror_image.lua +++ b/data-otservbr-global/monster/quests/soul_war/mirror_image.lua @@ -13,6 +13,10 @@ monster.outfit = { lookMount = 0, } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 35000 monster.maxHealth = 35000 monster.race = "blood" @@ -106,4 +110,34 @@ monster.events = { "MirrorImageTransform", } +mType.onPlayerAttack = function(monster, attackerPlayer) + logger.info("Player {}, attacking monster {}", attackerPlayer:getName(), monster:getName()) + + local apparitionType = "" + + local sameVocationProbability = 70 -- 70% chance for create monster of first player attack vocation + if attackerPlayer:isDruid() then + apparitionType = "Druid's Apparition" + elseif attackerPlayer:isKnight() then + apparitionType = "Knight's Apparition" + elseif attackerPlayer:isPaladin() then + apparitionType = "Paladin's Apparition" + elseif attackerPlayer:isSorcerer() then + apparitionType = "Sorcerer's Apparition" + end + + if math.random(100) > sameVocationProbability then + repeat + local randomIndex = math.random(#SoulWarQuest.apparitionNames) + if SoulWarQuest.apparitionNames[randomIndex] ~= apparitionType then + apparitionType = SoulWarQuest.apparitionNames[randomIndex] + break + end + until false + end + + Game.createMonster(apparitionType, monster:getPosition(), true, true) + monster:remove() +end + mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/bony_sea_devil.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua similarity index 95% rename from data-otservbr-global/monster/undeads/bony_sea_devil.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua index 1199240df4b..d6e4bcfcafe 100644 --- a/data-otservbr-global/monster/undeads/bony_sea_devil.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/bony_sea_devil.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Ebb and Flow.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 24000 monster.maxHealth = 24000 monster.race = "undead" @@ -96,7 +100,7 @@ monster.loot = { { name = "goblet of gloom", chance = 880 }, { name = "glacier kilt", chance = 880 }, { name = "glacial rod", chance = 1210 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { @@ -136,4 +140,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("Get out the way!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/demons/brachiodemon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua similarity index 95% rename from data-otservbr-global/monster/demons/brachiodemon.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua index 930ecfe469f..a76c9f40ee9 100644 --- a/data-otservbr-global/monster/demons/brachiodemon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/brachiodemon.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Claustrophobic Inferno.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 25000 monster.maxHealth = 25000 monster.race = "blood" @@ -99,7 +103,7 @@ monster.loot = { { name = "mastermind shield", chance = 420 }, { name = "assassin dagger", chance = 340 }, { name = "alloy legs", chance = 170 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { @@ -137,4 +141,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("Burn in hell!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/plants/branchy_crawler.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua similarity index 94% rename from data-otservbr-global/monster/plants/branchy_crawler.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua index d319fcde5e5..f5dccd47eb5 100644 --- a/data-otservbr-global/monster/plants/branchy_crawler.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/branchy_crawler.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Rotten Wasteland.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 27000 monster.maxHealth = 27000 monster.race = "blood" @@ -94,7 +98,7 @@ monster.loot = { { name = "twiceslicer", chance = 420 }, { name = "crystalline sword", chance = 390 }, { name = "ruthless axe", chance = 330 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { @@ -132,4 +136,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("My growth is your death!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua new file mode 100644 index 00000000000..72c4082ee0e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Ashes of Burning Hatred") +local monster = {} + +monster.description = "a ashes of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34009, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua new file mode 100644 index 00000000000..c0b76ce98df --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Blaze of Burning Hatred") +local monster = {} + +monster.description = "a blaze of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34013, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua new file mode 100644 index 00000000000..483c5156fb1 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Flame of Burning Hatred") +local monster = {} + +monster.description = "a flame of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34011, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua new file mode 100644 index 00000000000..dc9ff25f05e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua @@ -0,0 +1,89 @@ +local mType = Game.createMonsterType("Spark of Burning Hatred") +local monster = {} + +monster.description = "a spark of burning hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 34010, +} + +monster.health = 15000 +monster.maxHealth = 15000 +monster.race = "undead" +monster.corpse = 5993 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 2000, + chance = 40, +} + +monster.strategiesTarget = { + nearest = 0, + health = 0, + damage = 0, + random = 100, +} + +monster.events = { + "BurningChangeForm", +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "combat", interval = 2000, chance = 25, type = COMBAT_FIREDAMAGE, minDamage = -555, maxDamage = -703, range = 9, shootEffect = CONST_ANI_FIRE, target = true }, +} + +monster.defenses = { + defense = 5, + armor = 10, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua new file mode 100644 index 00000000000..76d403fab1a --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua @@ -0,0 +1,84 @@ +local mType = Game.createMonsterType("Symbol of Hatred") +local monster = {} + +monster.description = "a symbol of hatred" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 11427, +} + +monster.health = 14000 +monster.maxHealth = 14000 +monster.race = "undead" +monster.corpse = 33792 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local intervalBetweenExecutions = 3000 + +mType.onThink = function(monsterCallback, interval) + monsterCallback:onThinkGoshnarTormentCounter(interval, 30, intervalBetweenExecutions, SoulWarQuest.levers.goshnarsHatred.boss.position, "Goshnar's Hatred") +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/capricious_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua similarity index 97% rename from data-otservbr-global/monster/undeads/capricious_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua index cd402486853..0b3bf5b8572 100644 --- a/data-otservbr-global/monster/undeads/capricious_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/capricious_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Ebb and Flow.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 30000 monster.maxHealth = 30000 monster.race = "undead" @@ -93,7 +97,7 @@ monster.loot = { { id = 23542, chance = 1180 }, -- collar of blue plasma { name = "glacial rod", chance = 940 }, { name = "ornate crossbow", chance = 940 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/distorted_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua similarity index 97% rename from data-otservbr-global/monster/undeads/distorted_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua index c1fdc41461b..3f28aac16b9 100644 --- a/data-otservbr-global/monster/undeads/distorted_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/distorted_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Mirrored Nightmare.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 26000 monster.maxHealth = 26000 monster.race = "undead" @@ -92,7 +96,7 @@ monster.loot = { { name = "spellbook of warding", chance = 2890 }, { id = 23531, chance = 1930 }, -- ring of green plasma { name = "glacial rod", chance = 1290 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/druid's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua similarity index 97% rename from data-otservbr-global/monster/undeads/druid's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua index 414de6155bf..957307c40eb 100644 --- a/data-otservbr-global/monster/undeads/druid's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/druid's_apparition.lua @@ -32,6 +32,10 @@ monster.corpse = 6081 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, @@ -90,7 +94,7 @@ monster.loot = { { name = "platinum amulet", chance = 1750 }, { name = "glacier robe", chance = 880 }, { id = 23544, chance = 440 }, -- collar of red plasma - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua new file mode 100644 index 00000000000..aed9dcfc829 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua @@ -0,0 +1,93 @@ +local mType = Game.createMonsterType("A Greedy Eye") +local monster = {} + +monster.description = "a greedy eye" +monster.experience = 0 +monster.outfit = { + lookType = 925, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 30000 +monster.maxHealth = 30000 +monster.race = "blood" +monster.corpse = 5995 +monster.speed = 0 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 20, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 70, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "greedy eye beam", interval = 2000, chance = 100, minDamage = -1000, maxDamage = -1000 }, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/plants/cloak_of_terror.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua similarity index 94% rename from data-otservbr-global/monster/plants/cloak_of_terror.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua index 184ba93fab2..ff25ab1f058 100644 --- a/data-otservbr-global/monster/plants/cloak_of_terror.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/cloak_of_terror.lua @@ -26,6 +26,11 @@ monster.Bestiary = { Locations = "Furious Crater.", } +monster.events = { + "FourthTaintBossesPrepareDeath", + "CloakOfTerrorHealthLoss", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "undead" @@ -93,7 +98,7 @@ monster.loot = { { name = "blue gem", chance = 1490 }, { name = "brooch of embracement", chance = 1490 }, { name = "wand of defiance", chance = 990 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { @@ -131,4 +136,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("I am your terror!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/extra_dimensional/courage_leech.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua similarity index 97% rename from data-otservbr-global/monster/extra_dimensional/courage_leech.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua index d8ad3147a8e..615e4637841 100644 --- a/data-otservbr-global/monster/extra_dimensional/courage_leech.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/courage_leech.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Furious Crater", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 27000 monster.maxHealth = 27000 monster.race = "undead" @@ -92,7 +96,7 @@ monster.loot = { { name = "stone skin amulet", chance = 910 }, { name = "nightmare blade", chance = 1190 }, { name = "demonrage sword", chance = 600 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua new file mode 100644 index 00000000000..316959ddcd8 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua @@ -0,0 +1,118 @@ +local mType = Game.createMonsterType("Poor Soul") +local monster = {} + +monster.description = "a poor soul" +monster.experience = 0 +monster.outfit = { + lookType = 1296, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 500 +monster.maxHealth = 500 +monster.race = "undead" +monster.corpse = 33891 +monster.speed = 140 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 5, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, + { text = "I have a head start.", yell = false }, + { text = "Look into my eyes! No, the other ones!", yell = false }, + { text = "The mirrors can't contain the night!", yell = false }, +} + +monster.loot = { + { name = "crystal coin", chance = 70540 }, + { name = "ultimate health potion", chance = 12220, maxCount = 7 }, + { name = "violet gem", chance = 4560 }, + { name = "green gem", chance = 5760 }, + { name = "blue gem", chance = 4960 }, + { name = "northwind rod", chance = 5920 }, + { name = "sacred tree amulet", chance = 5520 }, + { id = 33933, chance = 7920 }, -- apron + { id = 3067, chance = 7220 }, -- hailstorm rod + { name = "glacier shoes", chance = 2520 }, + { name = "glacier robe", chance = 2220 }, + { name = "stone skin amulet", chance = 5920 }, + { id = 23533, chance = 4892 }, -- ring of red plasma + { id = 33932, chance = 3520 }, -- head + { name = "glacial rod", chance = 620 }, + { id = 34024, chance = 650 }, -- gruesome fan +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -250, maxDamage = -450 }, +} + +monster.defenses = { + defense = 90, + armor = 105, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = -300 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = true }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/vibrant_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua similarity index 97% rename from data-otservbr-global/monster/undeads/vibrant_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua index 9f17bee76ee..ae4c60df095 100644 --- a/data-otservbr-global/monster/undeads/vibrant_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/vibrant_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Furious Crater.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 27000 monster.maxHealth = 27000 monster.race = "undead" @@ -94,7 +98,7 @@ monster.loot = { { name = "violet crystal shard", chance = 1080 }, { id = 23529, chance = 1080 }, -- ring of blue plasma { name = "green gem", chance = 1080 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/hateful_soul.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/hateful_soul.lua similarity index 99% rename from data-otservbr-global/monster/quests/soul_war/hateful_soul.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/hateful_soul.lua index 4aad05f3f33..69c413aa242 100644 --- a/data-otservbr-global/monster/quests/soul_war/hateful_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/hateful_soul.lua @@ -16,7 +16,7 @@ monster.outfit = { monster.health = 25000 monster.maxHealth = 25000 monster.race = "undead" -monster.corpse = 0 +monster.corpse = 33793 monster.speed = 125 monster.manaCost = 0 diff --git a/data-otservbr-global/monster/demons/infernal_demon.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua similarity index 97% rename from data-otservbr-global/monster/demons/infernal_demon.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua index 7c4a1d80302..0ec107c192f 100644 --- a/data-otservbr-global/monster/demons/infernal_demon.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_demon.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Claustrophobic Inferno.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 32000 monster.maxHealth = 32000 monster.race = "blood" @@ -94,7 +98,7 @@ monster.loot = { { name = "giant sword", chance = 2860 }, { name = "magma boots", chance = 2290 }, { name = "stone skin amulet", chance = 570 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/infernal_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua similarity index 97% rename from data-otservbr-global/monster/undeads/infernal_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua index 0b61e291291..82b7fd2090a 100644 --- a/data-otservbr-global/monster/undeads/infernal_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/infernal_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Claustrophobic Inferno.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 26000 monster.maxHealth = 26000 monster.race = "undead" @@ -95,7 +99,7 @@ monster.loot = { { name = "crystal mace", chance = 1610 }, { name = "war axe", chance = 1410 }, { name = "warrior's axe", chance = 1410 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/knight's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua similarity index 97% rename from data-otservbr-global/monster/undeads/knight's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua index 0a4415aa2fd..f763e9b4dfe 100644 --- a/data-otservbr-global/monster/undeads/knight's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/knight's_apparition.lua @@ -33,6 +33,10 @@ monster.corpse = 111 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, @@ -88,7 +92,7 @@ monster.loot = { { name = "giant sword", chance = 1720 }, { name = "stone skin amulet", chance = 1500 }, { name = "crown shield", chance = 640 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/demons/many_faces.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua similarity index 94% rename from data-otservbr-global/monster/demons/many_faces.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua index 8b9724a6252..f60b999dbce 100644 --- a/data-otservbr-global/monster/demons/many_faces.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/many_faces.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Mirrored Nightmare.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 30000 monster.maxHealth = 30000 monster.race = "undead" @@ -95,7 +99,7 @@ monster.loot = { { name = "glacier robe", chance = 2130 }, { name = "gruesome fan", chance = 610 }, { name = "glacial rod", chance = 610 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { @@ -133,4 +137,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("Hands off my comrades!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua new file mode 100644 index 00000000000..2193aac5447 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua @@ -0,0 +1,107 @@ +local mType = Game.createMonsterType("Greater Splinter of Madness") +local monster = {} + +monster.description = "a greater splinter of madness" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 82, + lookLegs = 0, + lookFeet = 0, + lookAddons = 1, + lookMount = 0, +} + +monster.health = 4000 +monster.maxHealth = 4000 +monster.race = "undead" +monster.corpse = 32610 +monster.speed = 175 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 5000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -450 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -350, maxDamage = -800, range = 4, shootEffect = CONST_ANI_SUDDENDEATH, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -750, range = 4, shootEffect = CONST_ANI_ENERGY, target = true }, +} + +monster.defenses = { + defense = 40, + armor = 79, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monsterCallback) + addEvent(function(monsterId) + local eventMonster = Monster(monsterId) + if eventMonster then + eventMonster:setType("Mighty Splinter of Madness", true) + end + end, 120000, monsterCallback:getId()) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua new file mode 100644 index 00000000000..7927ac18a26 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua @@ -0,0 +1,107 @@ +local mType = Game.createMonsterType("Lesser Splinter of Madness") +local monster = {} + +monster.description = "a lesser splinter of madness" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 57, + lookLegs = 0, + lookFeet = 0, + lookAddons = 1, + lookMount = 0, +} + +monster.health = 4000 +monster.maxHealth = 4000 +monster.race = "undead" +monster.corpse = 32610 +monster.speed = 175 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 5000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -350 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -300, maxDamage = -550, range = 4, shootEffect = CONST_ANI_SUDDENDEATH, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -300, maxDamage = -550, range = 4, shootEffect = CONST_ANI_ENERGY, target = true }, +} + +monster.defenses = { + defense = 40, + armor = 79, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monsterCallback) + addEvent(function(monsterId) + local eventMonster = Monster(monsterId) + if eventMonster then + eventMonster:setType("Greater Splinter of Madness", true) + end + end, 120000, monsterCallback:getId()) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua new file mode 100644 index 00000000000..18dd5825b89 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua @@ -0,0 +1,113 @@ +local mType = Game.createMonsterType("Mighty Splinter of Madness") +local monster = {} + +monster.description = "a mighty splinter of madness" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 93, + lookLegs = 0, + lookFeet = 0, + lookAddons = 1, + lookMount = 0, +} + +monster.health = 4000 +monster.maxHealth = 4000 +monster.race = "undead" +monster.corpse = 32610 +monster.speed = 175 +monster.manaCost = 0 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 5000, + chance = 10, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.voices = { + interval = 5000, + chance = 10, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -100, maxDamage = -750 }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_DEATHDAMAGE, minDamage = -450, maxDamage = -800, range = 4, shootEffect = CONST_ANI_SUDDENDEATH, target = false }, + { name = "combat", interval = 2000, chance = 30, type = COMBAT_ENERGYDAMAGE, minDamage = -400, maxDamage = -800, range = 4, shootEffect = CONST_ANI_ENERGY, target = true }, +} + +monster.defenses = { + defense = 40, + armor = 79, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 100 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 100 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 100 }, + { type = COMBAT_HOLYDAMAGE, percent = 100 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = true }, + { type = "bleed", condition = false }, +} + +mType.onSpawn = function(monsterCallback) + addEvent(function(monsterId) + local eventMonster = Monster(monsterId) + if eventMonster then + creature:say("Goshnar's Megalomania feeds on its own madness and becomes stronger!", TALKTYPE_MONSTER_SAY, 0, 0, Position(34091, 31026, 9)) + creature:remove() + local boss = Creature("Goshnar's Megalomania") + if boss then + boss:increaseHatredDamageMultiplier(5) + logger.debug("Goshnar's Megalomania has increased its damage multiplier to 5.") + end + end + end, 120000, monsterCallback:getId()) +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua new file mode 100644 index 00000000000..53c607ace7f --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua @@ -0,0 +1,82 @@ +local mType = Game.createMonsterType("Necromantic Focus") +local monster = {} + +monster.description = "a necromantic focus" +monster.experience = 0 +monster.outfit = { + lookTypeEx = 7059, +} + +monster.health = 12000 +monster.maxHealth = 12000 +monster.race = "undead" +monster.corpse = 33984 +monster.speed = 0 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.events = { + "NecromanticFocusDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = false, + canWalkOnFire = false, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/mould_phantom.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua similarity index 97% rename from data-otservbr-global/monster/undeads/mould_phantom.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua index c3d7ea8ea86..1af17c877fa 100644 --- a/data-otservbr-global/monster/undeads/mould_phantom.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/mould_phantom.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Rotten Wasteland.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "undead" @@ -93,7 +97,7 @@ monster.loot = { { id = 23529, chance = 1040 }, -- ring of blue plasma { name = "ornate crossbow", chance = 840 }, { name = "crystal crossbow", chance = 620 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/paladin's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua similarity index 97% rename from data-otservbr-global/monster/undeads/paladin's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua index 066266e70e3..2dc7a49555c 100644 --- a/data-otservbr-global/monster/undeads/paladin's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/paladin's_apparition.lua @@ -33,6 +33,10 @@ monster.corpse = 111 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, @@ -91,7 +95,7 @@ monster.loot = { { name = "stone skin amulet", chance = 1560 }, { id = 23542, chance = 1250 }, -- collar of blue plasma { id = 23529, chance = 1250 }, -- ring of blue plasma - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/constructs/rotten_golem.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua similarity index 97% rename from data-otservbr-global/monster/constructs/rotten_golem.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua index edc6f909a70..f5d5808b05c 100644 --- a/data-otservbr-global/monster/constructs/rotten_golem.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/rotten_golem.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Rotten Wasteland.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "venom" @@ -91,7 +95,7 @@ monster.loot = { { name = "stone skin amulet", chance = 740 }, { name = "terra mantle", chance = 510 }, { name = "rubber cap", chance = 430 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/undeads/sorcerer's_apparition.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua similarity index 97% rename from data-otservbr-global/monster/undeads/sorcerer's_apparition.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua index 8cf059be62c..e554c27dfd9 100644 --- a/data-otservbr-global/monster/undeads/sorcerer's_apparition.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/sorcerer's_apparition.lua @@ -33,6 +33,10 @@ monster.corpse = 6081 monster.speed = 235 monster.manaCost = 0 +monster.events = { + "MirroredNightmareBossAccess", +} + monster.changeTarget = { interval = 4000, chance = 0, @@ -92,7 +96,7 @@ monster.loot = { { name = "wand of starstorm", chance = 1310 }, { name = "stone skin amulet", chance = 1310 }, { name = "alloy legs", chance = 440 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/elementals/turbulent_elemental.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua similarity index 97% rename from data-otservbr-global/monster/elementals/turbulent_elemental.lua rename to data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua index 3bef5da44e3..6d7804f2207 100644 --- a/data-otservbr-global/monster/elementals/turbulent_elemental.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/turbulent_elemental.lua @@ -26,6 +26,10 @@ monster.Bestiary = { Locations = "Ebb and Flow.", } +monster.events = { + "FourthTaintBossesPrepareDeath", +} + monster.health = 28000 monster.maxHealth = 28000 monster.race = "blood" @@ -91,7 +95,7 @@ monster.loot = { { name = "crystalline armor", chance = 710 }, { name = "rubber cap", chance = 710 }, { name = "stone skin amulet", chance = 470 }, - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua new file mode 100644 index 00000000000..0ece602eab5 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/powerful_soul.lua @@ -0,0 +1,115 @@ +local mType = Game.createMonsterType("Powerful Soul") +local monster = {} + +monster.description = "a powerful soul" +monster.experience = 0 +monster.outfit = { + lookType = 568, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 30000 +monster.maxHealth = 30000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 1000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -2000, maxDamage = -3000 }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -2000, maxDamage = -3000, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 2, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + CreateGoshnarsGreedMonster("Weak Soul", GreedMonsters[monster:getName()]) + monster:remove() + local boss = Creature("Goshnar's Greed") + if boss then + for elementType, reflectPercent in pairs(SoulWarReflectDamageMap) do + boss:addReflectElement(elementType, reflectPercent) + end + boss:addDefense(10) + boss:setMaxHealth(boss:getMaxHealth() + 10000) + boss:addHealth(10000) + end + transformTimeCount = 0 + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/soul_cage.lua b/data-otservbr-global/monster/quests/soul_war/soul_cage.lua new file mode 100644 index 00000000000..c6c65b32547 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/soul_cage.lua @@ -0,0 +1,71 @@ +local mType = Game.createMonsterType("Soul Cage") +local monster = {} + +monster.description = "a soul cage" +monster.experience = 100000 +monster.outfit = { + lookType = 863, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 100000 +monster.maxHealth = 100000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 0 +monster.manaCost = 0 + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, +} + +monster.events = { + "SoulCageDeath", + "SoulCageHealthChange", +} + +monster.light = { + level = 0, + color = 0, +} + +monster.defenses = { + defense = 80, + armor = 100, + { name = "Heal Malice", interval = 2000, chance = 90, target = false }, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "invisible", condition = true }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua new file mode 100644 index 00000000000..160879ba9db --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/soul_sphere.lua @@ -0,0 +1,122 @@ +local mType = Game.createMonsterType("Soul Sphere") +local monster = {} + +monster.description = "a soul sphere" +monster.experience = 0 +monster.outfit = { + lookType = 979, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.corpse = 0 +monster.speed = 0 +monster.manaCost = 0 + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -500, maxDamage = -1000 }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 0.51, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 100 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local moveTimeCount = 0 +local stop = false +mType.onThink = function(monster, interval) + if stop then + return + end + + moveTimeCount = moveTimeCount + interval + if moveTimeCount >= 3000 then + local currentPos = monster:getPosition() + local newPos = Position(currentPos.x - 1, currentPos.y, currentPos.z) + + local nextTile = Tile(newPos) + if nextTile then + for _, creatureId in pairs(nextTile:getCreatures()) do + local tileMonster = Monster(creatureId) + if tileMonster and tileMonster:getName() == "Goshnar's Greed" then + tileMonster:setHealth(tileMonster:getMaxHealth()) + stop = true + return + end + end + end + + if not stop then + monster:teleportTo(newPos, true) + moveTimeCount = 0 + end + end +end + +mType.onSpawn = function(monster) + moveTimeCount = 0 + stop = false +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua b/data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua new file mode 100644 index 00000000000..faa819dec6e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/soulsnatcher.lua @@ -0,0 +1,115 @@ +local mType = Game.createMonsterType("Soulsnatcher") +local monster = {} + +monster.description = "a soulsnatcher" +monster.experience = 0 +monster.outfit = { + lookType = 1268, + lookHead = 0, + lookBody = 94, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = true, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -1000, maxDamage = -1500 }, + { name = "soulsnatcher-lifedrain-beam", interval = 2000, chance = 20, minDamage = -1000, maxDamage = -1500, target = false }, + { name = "soulsnatcher-lifedrain-missile", interval = 2000, chance = 25, minDamage = -1000, maxDamage = -1500, target = true }, + { name = "soulsnatcher-manadrain-ball", interval = 2000, chance = 30, minDamage = -500, maxDamage = -1000 }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 0.51, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 0 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + transformTimeCount = 0 + local zone = Zone("boss.goshnar's-greed") + if zone then + local players = zone:getPlayers() + for _, player in ipairs(players) do + player:addHealth(-math.random(500, 1000)) + end + end + CreateGoshnarsGreedMonster(monster:getName(), GreedMonsters[monster:getName()]) + monster:remove() + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua b/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua index 8b8cec55ccf..d51b52e2e84 100644 --- a/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua +++ b/data-otservbr-global/monster/quests/soul_war/spiteful_spitter.lua @@ -99,4 +99,8 @@ monster.immunities = { { type = "bleed", condition = false }, } +mType.onThink = function(monster, interval) + monster:tryTeleportToPlayer("You have been chosen for a harvest!") +end + mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/strong_soul.lua b/data-otservbr-global/monster/quests/soul_war/strong_soul.lua new file mode 100644 index 00000000000..ae3615b6eb4 --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/strong_soul.lua @@ -0,0 +1,106 @@ +local mType = Game.createMonsterType("Strong Soul") +local monster = {} + +monster.description = "a strong soul" +monster.experience = 0 +monster.outfit = { + lookType = 566, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 20000 +monster.maxHealth = 20000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 1000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -1000, maxDamage = -2000 }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -1000, maxDamage = -2000, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 2, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + Game.createMonster("Powerful Soul", GreedMonsters[monster:getName()], true, false) + monster:remove() + transformTimeCount = 0 + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/weak_soul.lua b/data-otservbr-global/monster/quests/soul_war/weak_soul.lua new file mode 100644 index 00000000000..b7150efa00f --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/weak_soul.lua @@ -0,0 +1,106 @@ +local mType = Game.createMonsterType("Weak Soul") +local monster = {} + +monster.description = "a weak soul" +monster.experience = 0 +monster.outfit = { + lookType = 48, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 10000 +monster.maxHealth = 10000 +monster.race = "undead" +monster.corpse = 0 +monster.speed = 80 +monster.manaCost = 0 + +monster.events = { + "GreedMonsterDeath", +} + +monster.changeTarget = { + interval = 4000, + chance = 0, +} + +monster.strategiesTarget = { + nearest = 100, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = false, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = 0, maxDamage = -1000 }, + { name = "combat", interval = 2000, chance = 15, type = COMBAT_LIFEDRAIN, minDamage = -500, maxDamage = -1000, range = 1, effect = CONST_ME_MAGIC_RED, target = false }, +} + +monster.defenses = { + defense = 80, + armor = 90, + mitigation = 0.51, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 100 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 100 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 100 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 100 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 100 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +local transformTimeCount = 0 +mType.onThink = function(monster, interval) + transformTimeCount = transformTimeCount + interval + if transformTimeCount == 8000 then + Game.createMonster("Strong Soul", GreedMonsters[monster:getName()], true, false) + monster:remove() + transformTimeCount = 0 + end +end + +mType.onSpawn = function(monster) + transformTimeCount = 0 +end + +mType:register(monster) diff --git a/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua b/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua new file mode 100644 index 00000000000..3631fa3cb8e --- /dev/null +++ b/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua @@ -0,0 +1,91 @@ +local mType = Game.createMonsterType("Weeping Soul") +local monster = {} + +monster.description = "a weeping soul" +monster.experience = 0 +monster.outfit = { + lookType = 48, + lookHead = 0, + lookBody = 0, + lookLegs = 0, + lookFeet = 0, + lookAddons = 0, + lookMount = 0, +} + +monster.health = 7000 +monster.maxHealth = 7000 +monster.race = "undead" +monster.corpse = 33876 +monster.speed = 150 +monster.manaCost = 100 +monster.maxSummons = 0 + +monster.changeTarget = { + interval = 4000, + chance = 15, +} + +monster.strategiesTarget = { + nearest = 60, + health = 10, + damage = 10, + random = 20, +} + +monster.flags = { + summonable = false, + attackable = true, + hostile = true, + convinceable = false, + pushable = false, + rewardBoss = false, + illusionable = false, + canPushItems = true, + canPushCreatures = true, + staticAttackChance = 90, + targetDistance = 1, + runHealth = 0, + healthHidden = false, + isBlockable = false, + canWalkOnEnergy = true, + canWalkOnFire = true, + canWalkOnPoison = true, + pet = false, +} + +monster.light = { + level = 0, + color = 0, +} + +monster.attacks = { + { name = "melee", interval = 2000, chance = 100, minDamage = -220, maxDamage = -650 }, +} + +monster.defenses = { + defense = 55, + armor = 55, +} + +monster.elements = { + { type = COMBAT_PHYSICALDAMAGE, percent = 40 }, + { type = COMBAT_ENERGYDAMAGE, percent = 0 }, + { type = COMBAT_EARTHDAMAGE, percent = 0 }, + { type = COMBAT_FIREDAMAGE, percent = 0 }, + { type = COMBAT_LIFEDRAIN, percent = 0 }, + { type = COMBAT_MANADRAIN, percent = 0 }, + { type = COMBAT_DROWNDAMAGE, percent = 0 }, + { type = COMBAT_ICEDAMAGE, percent = 0 }, + { type = COMBAT_HOLYDAMAGE, percent = 0 }, + { type = COMBAT_DEATHDAMAGE, percent = 0 }, +} + +monster.immunities = { + { type = "paralyze", condition = true }, + { type = "outfit", condition = false }, + { type = "invisible", condition = false }, + { type = "bleed", condition = false }, +} + +mType:register(monster) diff --git a/data-otservbr-global/monster/undeads/hazardous_phantom.lua b/data-otservbr-global/monster/undeads/hazardous_phantom.lua index 29384eafe55..1cd1eb7d38c 100644 --- a/data-otservbr-global/monster/undeads/hazardous_phantom.lua +++ b/data-otservbr-global/monster/undeads/hazardous_phantom.lua @@ -20,6 +20,10 @@ monster.corpse = 34125 monster.speed = 100 monster.manaCost = 0 +monster.events = { + "HazardousPhantomDeath", +} + monster.changeTarget = { interval = 4000, chance = 0, @@ -78,7 +82,7 @@ monster.loot = { { id = 282, chance = 1570 }, -- giant shimmering pearl { name = "wand of everblazing", chance = 790 }, { id = 23542, chance = 790 }, -- collar of blue plasma - { id = 34109, chance = 20 }, -- bag you desire + { name = "bag you desire", chance = 15 }, } monster.attacks = { diff --git a/data-otservbr-global/npc/flickering_soul.lua b/data-otservbr-global/npc/flickering_soul.lua new file mode 100644 index 00000000000..2010198da6e --- /dev/null +++ b/data-otservbr-global/npc/flickering_soul.lua @@ -0,0 +1,203 @@ +local internalNpcName = "Flickering Soul" +local npcType = Game.createNpcType(internalNpcName) +local npcConfig = {} + +npcConfig.name = internalNpcName +npcConfig.description = internalNpcName + +npcConfig.health = 100 +npcConfig.maxHealth = npcConfig.health +npcConfig.walkInterval = 2000 +npcConfig.walkRadius = 2 + +npcConfig.outfit = { + lookType = 1219, + lookHead = 6, + lookBody = 26, + lookLegs = 26, + lookFeet = 6, + lookAddons = 0, + lookMount = 0, +} + +npcConfig.flags = { + floorchange = false, +} + +local keywordHandler = KeywordHandler:new() +local npcHandler = NpcHandler:new(keywordHandler) + +npcType.onThink = function(npc, interval) + npcHandler:onThink(npc, interval) +end + +npcType.onAppear = function(npc, player) + npcHandler:onAppear(npc, player) +end + +npcType.onDisappear = function(npc, player) + npcHandler:onDisappear(npc, player) +end + +npcType.onMove = function(npc, player, fromPosition, toPosition) + npcHandler:onMove(npc, player, fromPosition, toPosition) +end + +npcType.onSay = function(npc, player, type, message) + npcHandler:onSay(npc, player, type, message) +end + +npcType.onCloseChannel = function(npc, player) + npcHandler:onCloseChannel(npc, player) +end + +local function playerSayCallback(npc, player, type, message) + if not npcHandler:checkInteraction(npc, player) then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + + local playerId = player:getId() + if MsgContains(message, "living") then + npcHandler:say("It has been a while since I roamed the world of the living in a mortal shell.", npc, player) + elseif MsgContains(message, "mortal") then + npcHandler:say("I had many names in my live. The one that would be the most recognizable is probably the name Goshnar. Even that was an assumed name that I took when I left my mundane past behind.", npc, player) + elseif MsgContains(message, "Goshnar") then + npcHandler:say({ + "I was once known as the necromant king. ...", + "For some it was meant as a curse, others used the name with reverence. To me it was just another stepping stone, in a life that burned with ambition.", + }, npc, player, 4000) + elseif MsgContains(message, "ambition") then + npcHandler:say({ + "My ambitions were high and knew no limits. Mastery over life and death was but a milestone that I wanted to accomplish. In the end I aspired probably somewhat like godhood. ...", + "Though in hindsight even that wouldn't have been enough. There was a hunger in me that nothing could put to rest.", + }, npc, player, 4000) + elseif MsgContains(message, "milestone") then + npcHandler:say("Everything in my life was just a tool to further my goals. The brotherhood of bones was just a tool for me. As was everyone or everything. In the path I had chosen nothing mattered but me and my ambitions.", npc, player) + elseif MsgContains(message, "everything") then + npcHandler:say("Necromancy was a passion at first, another tool while I amassed power and a crutch when my ambitions surpassed that what it could accomplish.", npc, player) + elseif MsgContains(message, "accomplish") then + npcHandler:say({ + "I was so convinced about my brilliance, my greatness, my destiny. And this hunger for more, it let me not have peace at any point in my life. I was always driven. There was no time to rest. ...", + "And there was no looking back. I never cared to remember my humble beginnings, what I had sacrificed to get where I was. All that I had left behind and that I had lost forever. ...", + "Now I see the bitter irony. I could bring back the dead, but I couldn't create second chances. I couldn't restore the truly important things that I had lost.", + }, npc, player, 4000) + elseif MsgContains(message, "dead") then + npcHandler:say("My demise did not meet me unprepared. As a powerful necromancer I had fettered my soul in the living world and the realms beyond. I had prepared for my return and was confident in my power.", npc, player) + elseif MsgContains(message, "confident") then + npcHandler:say("My soul wandered the plains of Zarganash, waiting for my wards to power up. Waiting for my soul to be slowly pulled back and manifest in the world of the living once again. What I had not taken into consideration was peace.", npc, player) + elseif MsgContains(message, "peace") then + npcHandler:say({ + "Zarganash was not a place without its dangers, but for a soul as powerful as mine, there was little threat at all. For the first time in my existence I had to stop running forward. I had to wait for things to fit into their places. ...", + "And me, who had seen things that horrible, they would have obliterated a lesser man's mind, finally took the time to look back. And what I saw was frightening in its own right. ...", + "A great tiredness overcame me. With the flames of my ambitions calming down for the first time since I could remember, all my aspirations and plans seemed to petty and futile. ...", + "Everything I had worked for and my plans for the things to come seemed pointless, and the things I had lost and never allowed myself to experience weighed heavily on my soul.", + }, npc, player, 4000) + elseif MsgContains(message, "soul") then + npcHandler:say({ + "I talked to other souls, lost in Zarganash, and most of them seemed like mirrors to myself. Their faults, their shortcomings, the things that were important to them and the things they had lost. ...", + "It was all like miniature copies of my own grand plans and losses. It made me think. And the great tiredness weighed even more heavy on me. A weariness of the world, of the hunger that drove me.", + }, npc, player, 4000) + elseif MsgContains(message, "weariness") then + npcHandler:say({ + "Then I met a wise soul. A teacher that did not lecture. I never was impressed by anything but my own accomplishments. But the inner balance and peace of this soul, it did impress me. A lot. ...", + "I, who fancied myself to have been the epitome of knowledge, learned things that were entirely new to me. But this knowledge wasn't about power. It was about me.", + }, npc, player, 4000) + elseif MsgContains(message, "knowledge") then + npcHandler:say("I recognized the extent of my folly and failure. I decided not to return to the world of the living.", npc, player) + elseif MsgContains(message, "return") then + npcHandler:say({ + "I decided to stay here, even pass on into the great beyond at some point. Yet I still feel the pull of my fetters. I can faintly hear those who think they are my followers, calling to me.", + "And I feel others, many others who crave my powers and try to bring me back for their own gain.", + }, npc, player, 4000) + elseif MsgContains(message, "fetters") then + npcHandler:say({ + "Over my time in Zarganash I split away the parts of me that my worldly fetters were bound to. Yet I had to recognize that they are still a part of me and I'm bound to them. ...", + "The fetters and the efforts to call me back are empowering them. I feel them growing in strength and gaining awareness on their own. ...", + "They are beginning to feed not only on the fetters and incarnations but also on me. As I grow weaker, they grow more powerful over time.", + }, npc, player, 4000) + elseif MsgContains(message, "powerful") then + npcHandler:say("The only way I can get rid of them is to disperse them, to 'kill' them so to say. But they are tainted parts of myself and even going near them might endanger my sanity and stability. So all I can do is to ask you to do this daunting task.", npc, player) + elseif MsgContains(message, "task") then + local soulWarQuest = player:soulWarQuestKV() + -- Checks if the boss has already been defeated + if soulWarQuest:get("goshnar's-megalomania-killed") then + npcHandler:say({ + "You did it! For the first time I can feel free from the pull of my past. Now I'm free at last. ...", + "I might stay a while and teach other souls about the inner peace, but will eventually pass on. Thank you so much, my hero. My eternal gratitude and blessings will be with you!", + }, npc, player, 2000) + npcHandler:setTopic(playerId, 2) + player:addOutfit("Revenant") + else + npcHandler:say("I'm aware I have no right to ask and I have little to offer as a payment, but I ask you nonetheless. Will you face my fettered vices and destroy them for me?", npc, player) + npcHandler:setTopic(playerId, 1) + end + elseif MsgContains(message, "yes") and npcHandler:getTopic(playerId) == 1 then + npcHandler:say("Thank you for accepting this burden.", npc, player) + soulWarQuest:set("teleport-access", true) + elseif MsgContains(message, "burden") then + npcHandler:say({ + "You will have to reach each of the negative parts of my personality that I split away. They are hidden deep in the depths of Zarganash and will have corrupted and twisted their surroundings into dangerous nightmares. ...", + "Even worse, you'll likely encounter minions of those who want to claim my soul as their prize for their own depraved reasons. You will have to destroy my shards to set me free.", + }, npc, player, 5000) + elseif MsgContains(message, "shards") then + npcHandler:say("You haven't killed Malice yet. You haven't killed Hatred yet. You haven't killed Spite yet. You haven't killed Cruelty yet. You haven't killed Greed yet.", npc, player) + elseif MsgContains(message, "hate") then + npcHandler:say({ + "I hated the world for its flaws and the reluctance of people to comply with my will. I was convinced I was destined for greatness and to change everything. Ordinary beings were far beneath me and my consideration. ...", + "All this opposition, all the wars were a nuisance on my way to greatness. I would have sacrificed the whole world to reach my goals.", + }, npc, player, 4000) + elseif MsgContains(message, "fermuba") then + npcHandler:say("My daughter was as ambitious as me, yet she lacked my intellect and my raw talent. She still was great and talented yet I sadly let her feel my disdain. One of the many errors that my way of hubris made me do.", npc, player) + elseif MsgContains(message, "ferumbras") then + npcHandler:say({ + "Even in the lands of the dead, this one caused a stir. The dead were whispering his name. It made me feel jealous and angry at first, but at some point, after much self-reflection, I could recognize my own faults in the stories about him.", + "It was almost like looking into a mirror for the first time. However, he lived way later than me, and I never met his soul here, so I can't tell more about him.", + }, npc, player, 4000) + elseif MsgContains(message, "grandson") then + npcHandler:say(" I'm not aware of the fate of my linage. Neither I'm able to relate to the mortal world in that way. Each of us is an individual, not bound by ties of blood or herritage.", npc, player) + elseif MsgContains(message, "pale worm") then + npcHandler:say("His avatar might be destroyed for now and it'd grip on Zarganash considerably weakened. Yet he burrowed to deep into the essence of this realm to be annihilated this easy.", npc, player) + elseif MsgContains(message, "necromant king") then + npcHandler:say({ + "They called me the necromant king, in an act of reverence, but to me it was always more of a slander. To limit my greatness to this insignificant aspect was an insult to my ego. But I let it slip for the greater good. ...", + "I felt it was beneath me to correct them and I went along.", + }, npc, player, 4000) + elseif MsgContains(message, "minions") or MsgContains(message, "followers") then + npcHandler:say("I despised my followers for their petty agendas and for their limited vision of my own goals and personality.", npc, player) + elseif MsgContains(message, "shards") then + local bossesYetToDefeat = {} + for bossName, _ in pairs(SoulWarQuest.miniBosses) do + if not soulWarQuest:get(bossName) then + table.insert(bossesYetToDefeat, bossName) + end + end + + local message + if #bossesYetToDefeat > 0 then + message = "You haven't killed " .. table.concat(bossesYetToDefeat, ", ") .. " yet." + else + message = "You have defeated all the Goshnar's Bosses. Your soul shines brighter with each victory." + end + npcHandler:say(message, npc, player) + elseif MsgContains(message, "taints") or MsgContains(message, "penalties") then + if player:getTaintLevel() ~= nil then + player:resetTaints(true) + npcHandler:say("I have cleansed you from the taints that you carried with you. You are now free from the burden that you should not have to bear.", npc, player) + return + end + + npcHandler:say("You are not tainted by the darkness of the world. You are pure and free from the burdens that others carry.", npc, player) + end + return true +end + +npcHandler:setCallback(CALLBACK_MESSAGE_DEFAULT, playerSayCallback) + +npcHandler:setMessage(MESSAGE_GREET, "Be greeted, living soul!") + +npcHandler:addModule(FocusModule:new(), npcConfig.name, true, true, true) + +-- npcType registering the npcConfig table +npcType:register(npcConfig) diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua deleted file mode 100644 index 2e01880589d..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_cruelty.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Cruelty", - position = Position(33856, 31866, 7), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33854, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33855, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33856, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33857, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - { pos = Position(33858, 31854, 6), teleport = Position(33856, 31872, 7), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33847, 31858, 7), - to = Position(33864, 31874, 7), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33853, y = 31854, z = 6 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua deleted file mode 100644 index 522cde76845..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_greed.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Greed", - position = Position(33746, 31666, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33776, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33777, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33778, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33779, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33780, 31665, 14), teleport = Position(33747, 31671, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33737, 31658, 14), - to = Position(33755, 31673, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33775, y = 31665, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua deleted file mode 100644 index 963404c8d85..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_hatred.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Hatred", - position = Position(33744, 31599, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33773, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33774, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33776, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33777, 31601, 14), teleport = Position(33743, 31604, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33735, 31592, 14), - to = Position(33751, 31606, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33772, y = 31601, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua deleted file mode 100644 index 44d102ad598..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_malice.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Malice", - position = Position(33710, 31599, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33679, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33680, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33681, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33682, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33683, 31599, 14), teleport = Position(33710, 31605, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33699, 31590, 14), - to = Position(33718, 31607, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33678, y = 31599, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua deleted file mode 100644 index 5896faa74c4..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_megalomania.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Megalomania", - position = Position(33710, 31634, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33676, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33677, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33678, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33679, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33680, 31634, 14), teleport = Position(33710, 31639, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33701, 31626, 14), - to = Position(33719, 31642, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33675, y = 31634, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua b/data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua deleted file mode 100644 index 0fd2396ae03..00000000000 --- a/data-otservbr-global/scripts/actions/bosses_levers/goshnar_spite.lua +++ /dev/null @@ -1,23 +0,0 @@ -local config = { - boss = { - name = "Goshnar's Spite", - position = Position(33743, 31632, 14), - }, - requiredLevel = 250, - playerPositions = { - { pos = Position(33774, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33775, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33776, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33777, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - { pos = Position(33778, 31634, 14), teleport = Position(33742, 31639, 14), effect = CONST_ME_TELEPORT }, - }, - specPos = { - from = Position(33734, 31624, 14), - to = Position(33751, 31640, 14), - }, - exit = Position(33621, 31427, 10), -} - -local lever = BossLever(config) -lever:position({ x = 33773, y = 31634, z = 14 }) -lever:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua b/data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua new file mode 100644 index 00000000000..fae3fc59794 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/action-reward_soul_war.lua @@ -0,0 +1,60 @@ +local rewardSoulWar = Action() + +function rewardSoulWar.onUse(creature, item, fromPosition, target, toPosition, isHotkey) + local rewardItem = SoulWarQuest.finalRewards[math.random(1, #SoulWarQuest.finalRewards)] + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + if soulWarQuest:get("final-reward") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already received your reward.") + return true + end + + if not soulWarQuest:get("goshnar's-megalomania-killed") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to defeat Goshnar's Megalomania to receive your reward.") + return true + end + + player:addItem(rewardItem.id, 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a " .. rewardItem.name .. ".") + soulWarQuest:set("final-reward", true) + return true +end + +rewardSoulWar:position({ x = 33620, y = 31400, z = 10 }) +rewardSoulWar:register() + +local phantasmalJadeMount = Action() + +function phantasmalJadeMount.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local soulWarQuest = player:soulWarQuestKV() + if soulWarQuest:get("panthasmal-jade-mount") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You already have Phantasmal Jade mount!") + return true + end + + if table.contains({ 34072, 34073, 34074 }, item.itemid) then + if player:getItemCount(34072) >= 4 and player:getItemCount(34073) == 1 and player:getItemCount(34074) == 1 then + player:removeItem(34072, 4) + player:removeItem(34073, 1) + player:removeItem(34074, 1) + player:addMount(167) + player:addAchievement("You got Horse Power") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won Phantasmal Jade mount.") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won You got Horse Power achievement.") + player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE) + soulWarQuest:set("panthasmal-jade-mount", true) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have the necessary items!") + player:getPosition():sendMagicEffect(CONST_ME_POFF) + end + end + + return true +end + +phantasmalJadeMount:id(34072, 34073, 34074) +phantasmalJadeMount:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/actions_bosses_killed.lua b/data-otservbr-global/scripts/quests/soul_war/actions_bosses_killed.lua deleted file mode 100644 index de4b8ae182f..00000000000 --- a/data-otservbr-global/scripts/quests/soul_war/actions_bosses_killed.lua +++ /dev/null @@ -1,24 +0,0 @@ -local bosses = { - ["goshnar's malice"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarMaliceKilled }, - ["goshnar's hatred"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarHatredKilled }, - ["goshnar's spite"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarSpiteKilled }, - ["goshnar's cruelty"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarCrueltyKilled }, - ["goshnar's greed"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarGreedKilled }, - ["goshnar's megalomania"] = { storage = Storage.Quest.U12_40.SoulWar.GoshnarMegalomaniaKilled }, -} - -local bossesSoulWar = CreatureEvent("SoulwarsBossDeath") -function bossesSoulWar.onDeath(creature) - local bossConfig = bosses[creature:getName():lower()] - if not bossConfig then - return true - end - onDeathForDamagingPlayers(creature, function(creature, player) - if bossConfig.storage then - player:setStorageValue(bossConfig.storage, 1) - end - end) - return true -end - -bossesSoulWar:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/actions_portal_megalomania.lua b/data-otservbr-global/scripts/quests/soul_war/actions_portal_megalomania.lua deleted file mode 100644 index 41d3763c1c7..00000000000 --- a/data-otservbr-global/scripts/quests/soul_war/actions_portal_megalomania.lua +++ /dev/null @@ -1,38 +0,0 @@ -local storagesTable = { - { storage = Storage.Quest.U12_40.SoulWar.GoshnarMaliceKilled, bossName = "Goshnar's Malice" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarHatredKilled, bossName = "Goshnar's Hatred" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarSpiteKilled, bossName = "Goshnar's Spite" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarCrueltyKilled, bossName = "Goshnar's Cruelty" }, - { storage = Storage.Quest.U12_40.SoulWar.GoshnarGreedKilled, bossName = "Goshnar's Greed" }, -} - -local portalMegalomania = MoveEvent() -function portalMegalomania.onStepIn(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return false - end - if player:getLevel() < 250 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need at least level 250 to enter.") - player:teleportTo(fromPosition, true) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return false - end - local text = "" - for value in pairs(storagesTable) do - if player:getStorageValue(storagesTable[value].storage) < 0 then - text = text .. "\n" .. storagesTable[value].bossName - end - end - if text == "" then - return true - else - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You still need to defeat:" .. text) - player:teleportTo(fromPosition, true) - return false - end -end - -portalMegalomania:type("stepin") -portalMegalomania:position({ x = 33611, y = 31430, z = 10 }) -portalMegalomania:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/actions_reward_soul_war.lua b/data-otservbr-global/scripts/quests/soul_war/actions_reward_soul_war.lua deleted file mode 100644 index 1e3c9c773e0..00000000000 --- a/data-otservbr-global/scripts/quests/soul_war/actions_reward_soul_war.lua +++ /dev/null @@ -1,89 +0,0 @@ -local rewards = { - { id = 34082, name = "Soulcutter" }, - { id = 34083, name = "Soulshredder" }, - { id = 34084, name = "Soulbiter" }, - { id = 34085, name = "Souleater" }, - { id = 34086, name = "Soulcrusher" }, - { id = 34087, name = "Soulmaimer" }, - { id = 34088, name = "Soulbleeder" }, - { id = 34089, name = "Soulpiercer" }, - { id = 34090, name = "Soultainter" }, - { id = 34091, name = "Soulhexer" }, - { id = 34092, name = "Soulshanks" }, - { id = 34093, name = "Soulstrider" }, - { id = 34094, name = "Soulshell" }, - { id = 34095, name = "Soulmantel" }, - { id = 34096, name = "Soulshroud" }, - { id = 34097, name = "Pair of Soulwalkers" }, - { id = 34098, name = "Pair of Soulstalkers" }, - { id = 34099, name = "Soulbastion" }, -} -local outfits = { 1322, 1323 } - -local function addOutfits(player) - if player:getStorageValue(Storage.Quest.U12_40.SoulWar.OutfitReward) < 0 then - player:addOutfit(outfits[1], 0) - player:addOutfit(outfits[2], 0) - player:setStorageValue(Storage.Quest.U12_40.SoulWar.OutfitReward, 1) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations you received the Revenant Outfit.") - end -end - -local rewardSoulWar = Action() -function rewardSoulWar.onUse(creature, item, fromPosition, target, toPosition, isHotkey) - local randId = math.random(1, #rewards) - local rewardItem = rewards[randId] - local player = creature:getPlayer() - if not player then - return false - end - if player:getStorageValue(Storage.Quest.U12_40.SoulWar.QuestReward) < 0 then - player:addItem(rewardItem.id, 1) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have found a " .. rewardItem.name .. ".") - player:setStorageValue(Storage.Quest.U12_40.SoulWar.QuestReward, 1) - addOutfits(player) - return true - else - addOutfits(player) - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already collected your reward") - return false - end -end - -rewardSoulWar:position({ x = 33620, y = 31400, z = 10 }) -rewardSoulWar:register() - ------------------------------ --- Phantasmal Jade Mount function - -local phantasmalJadeMount = Action() -function phantasmalJadeMount.onUse(player, item, fromPosition, target, toPosition, isHotkey) - local storage = Storage.Quest.U12_40.SoulWar.MountReward - if player:getStorageValue(storage) == 1 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You already have Phantasmal Jade mount!") - return false - end - - if table.contains({ 34072, 34073, 34074 }, item.itemid) then - -- check items - if player:getItemCount(34072) >= 4 and player:getItemCount(34073) == 1 and player:getItemCount(34074) == 1 then - player:removeItem(34072, 4) - player:removeItem(34073, 1) - player:removeItem(34074, 1) - player:addMount(167) - player:setStorageValue(storage, 1) - player:addAchievement("You got Horse Power") - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won Phantasmal Jade mount.") - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Congratulations! You won You got Horse Power achievement.") - player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE) - return true - else - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have the necessary items!") - player:getPosition():sendMagicEffect(CONST_ME_POFF) - return false - end - end -end - -phantasmalJadeMount:id(34072, 34073, 34074) -phantasmalJadeMount:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/actions_soulwar_entrances.lua b/data-otservbr-global/scripts/quests/soul_war/actions_soulwar_entrances.lua deleted file mode 100644 index 51f8281a04d..00000000000 --- a/data-otservbr-global/scripts/quests/soul_war/actions_soulwar_entrances.lua +++ /dev/null @@ -1,63 +0,0 @@ -local config = { - { position = { x = 33615, y = 31422, z = 10 }, destination = { x = 34009, y = 31014, z = 9 } }, -- hunt infernal demon - { position = { x = 33618, y = 31422, z = 10 }, destination = { x = 33972, y = 31041, z = 11 } }, -- hunt rotten - { position = { x = 33621, y = 31422, z = 10 }, destination = { x = 33894, y = 31019, z = 8 } }, -- hunt bony sea devil - { position = { x = 33624, y = 31422, z = 10 }, destination = { x = 33858, y = 31831, z = 3 } }, -- hunt cloak - { position = { x = 33627, y = 31422, z = 10 }, destination = { x = 33887, y = 31188, z = 10 } }, -- hunt many faces - { position = { x = 33950, y = 31109, z = 8 }, destination = { x = 33780, y = 31634, z = 14 } }, -- goshnar's spite entrance - { position = { x = 33937, y = 31217, z = 11 }, destination = { x = 33782, y = 31665, z = 14 } }, -- goshnar's greed entrance - { position = { x = 34022, y = 31091, z = 11 }, destination = { x = 33685, y = 31599, z = 14 } }, -- goshnar's malice entrance - { position = { x = 33856, y = 31884, z = 5 }, destination = { x = 33857, y = 31865, z = 6 } }, -- goshnar's cruelty entrance - { position = { x = 33889, y = 31873, z = 3 }, destination = { x = 33830, y = 31881, z = 4 } }, -- 1st to 2nd floor cloak - { position = { x = 33829, y = 31880, z = 4 }, destination = { x = 33856, y = 31889, z = 5 } }, -- 2nd to 3rd floor cloak -} - -local portal = { position = { x = 33914, y = 31032, z = 12 }, destination = { x = 33780, y = 31601, z = 14 } } -- goshnar's hatred entrance - -local soulWarEntrances = MoveEvent() -function soulWarEntrances.onStepIn(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return false - end - if player:getLevel() < 250 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need at least level 250 to enter.") - player:teleportTo(fromPosition, true) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return false - end - for value in pairs(config) do - if Position(config[value].position) == player:getPosition() then - player:teleportTo(Position(config[value].destination)) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return true - end - end -end - -soulWarEntrances:type("stepin") -for value in pairs(config) do - soulWarEntrances:position(config[value].position) -end -soulWarEntrances:register() - -local portalHatred = Action() -function portalHatred.onUse(creature, item, position, fromPosition) - local player = creature:getPlayer() - if not player then - return false - end - if player:getLevel() < 250 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need at least level 250 to enter.") - player:teleportTo(fromPosition, true) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return false - end - doSendMagicEffect(item:getPosition(), CONST_ME_TELEPORT) - player:teleportTo(Position(portal.destination)) - player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) - return true -end - -portalHatred:position(portal.position) -portalHatred:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua new file mode 100644 index 00000000000..3e5ec34f930 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/eventcallback_on_combat_taint.lua @@ -0,0 +1,126 @@ +local taintCooldown = {} + +local function createTeleportEffect(position) + position:sendMagicEffect(CONST_ME_TELEPORT) +end + +local function scheduleMonsterCreation(player, monster, monsterName, spawnPosition) + addEvent(createTeleportEffect, 1000, spawnPosition) + addEvent(createTeleportEffect, 2000, spawnPosition) + addEvent(createTeleportEffect, 3000, spawnPosition) + + addEvent(function(playerId, monsterId) + local eventPlayer = Player(playerId) + if not eventPlayer then + return + end + + local eventMonster = Monster(monsterId) + if not eventMonster or eventMonster:isDead() then + return + end + + -- Only create if the player not have cooldown + if not taintCooldown[playerId] or os.time() > taintCooldown[playerId] then + taintCooldown[playerId] = os.time() + 30 + local monster = Game.createMonster(monsterName, spawnPosition, true, true) + if monster then + spawnPosition:sendMagicEffect(CONST_ME_TELEPORT) + logger.debug("Spamming monster with name {} to player {}", monsterName, eventPlayer:getName()) + end + end + end, 4000, player:getId(), monster:getId()) +end + +local function onPlayerAttackMonster(player, target) + local monster = target:getMonster() + if not monster then + return + end + + -- It will only execute if the player has the second taint + if player:getTaintNameByNumber(2) ~= nil then + local chance = math.random(1, 200) + local spawnPosition = player:getPosition() + if chance == 1 then -- 0.5% chance + local foundMonsterName = player:getSoulWarZoneMonster() + if foundMonsterName ~= nil then + scheduleMonsterCreation(player, monster, foundMonsterName, spawnPosition) + end + end + end +end + +local function onMonsterAttackPlayer(target, primaryValue, secondaryValue) + local targetPlayer = target:getPlayer() + if not targetPlayer then + return primaryValue, secondaryValue + end + + if targetPlayer:getTaintNameByNumber(3) ~= nil then + local monsterZone = targetPlayer:getSoulWarZoneMonster() + if monsterZone ~= nil then + logger.debug("Player {} have third taint, primary value {}, secondary {}", targetPlayer:getName(), primaryValue, secondaryValue) + primaryValue = primaryValue + math.ceil(primaryValue * 0.15) + secondaryValue = secondaryValue + math.ceil(secondaryValue * 0.15) + logger.debug("Primary value after {}, secondary {}", primaryValue, secondaryValue) + end + end + + return primaryValue, secondaryValue +end + +local callback = EventCallback("CreatureOnCombatTaint") + +function callback.creatureOnCombat(caster, target, primaryValue, primaryType, secondaryValue, secondaryType, origin) + if not caster or not target then + return primaryValue, primaryType, secondaryValue, secondaryType + end + + -- Second taint + local attackerPlayer = caster:getPlayer() + if attackerPlayer and target:isMonster() then + onPlayerAttackMonster(attackerPlayer, target) + end + + -- Third taint + if caster:getMonster() then + primaryValue, secondaryValue = onMonsterAttackPlayer(target, primaryValue, secondaryValue) + end + + return primaryValue, primaryType, secondaryValue, secondaryType +end + +callback:register() + +callback = EventCallback("PlayerOnThinkTaint") + +local accumulatedTime = {} + +function callback.playerOnThink(player, interval) + if not player then + return + end + + local playerId = player:getId() + if not accumulatedTime[playerId] then + accumulatedTime[playerId] = 0 + end + + accumulatedTime[playerId] = accumulatedTime[playerId] + interval + + if accumulatedTime[playerId] >= 10000 then + local soulWarQuest = player:soulWarQuestKV() + if player:getSoulWarZoneMonster() ~= nil and player:getTaintNameByNumber(5) ~= nil then + local hpLoss = math.ceil(player:getHealth() * 0.1) + local manaLoss = math.ceil(player:getMana() * 0.1) + player:addHealth(-hpLoss) + player:addMana(-manaLoss) + logger.debug("Fifth taint removing '{}' mana and '{}' health from player {}", manaLoss, hpLoss, player:getName()) + end + + accumulatedTime[playerId] = 0 + end +end + +callback:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua new file mode 100644 index 00000000000..d13b5cf2cd6 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua @@ -0,0 +1,135 @@ +local function updateWaterPoolsSize() + for _, pos in ipairs(SoulWarQuest.ebbAndFlow.poolPositions) do + local tile = Tile(pos) + if tile then + local item = tile:getItemById(SoulWarQuest.ebbAndFlow.smallPoolId) + if item then + item:transform(SoulWarQuest.ebbAndFlow.MediumPoolId) + -- Starts another timer for filling after an additional 40 seconds + addEvent(function() + local item = tile:getItemById(SoulWarQuest.ebbAndFlow.MediumPoolId) + if item then + item:transform(SoulWarQuest.ebbAndFlow.smallPoolId) + end + end, 40000) -- 40 seconds + end + end + end +end + +local function loadMapEmpty() + if SoulWarQuest.ebbAndFlow.getZone():countPlayers() > 0 then + local players = SoulWarQuest.ebbAndFlow.getZone():getPlayers() + for _, player in ipairs(players) do + if player:getPosition().z == 8 then + if player:isInBoatSpot() then + local teleportPosition = player:getPosition() + teleportPosition.z = 9 + player:teleportTo(teleportPosition) + logger.trace("Teleporting player to down.") + end + player:sendCreatureAppear() + end + end + end + + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.empty) + SoulWarQuest.ebbAndFlow.setLoadedEmptyMap(true) + SoulWarQuest.ebbAndFlow.setActive(false) + + local updatePlayers = EventCallback("UpdatePlayersEmptyEbbFlowMap") + function updatePlayers.mapOnLoad(mapPath) + if mapPath ~= SoulWarQuest.ebbAndFlow.mapsPath.empty then + return + end + + SoulWarQuest.ebbAndFlow.updateZonePlayers() + end + + updatePlayers:register() + + addEvent(function() + -- Change the appearance of puddles to indicate the next filling + updateWaterPoolsSize() + end, 80000) -- 80 seconds +end + +local function getDistance(pos1, pos2) + return math.sqrt((pos1.x - pos2.x) ^ 2 + (pos1.y - pos2.y) ^ 2 + (pos1.z - pos2.z) ^ 2) +end + +local function findNearestRoomPosition(playerPosition) + local nearestPosition = nil + local smallestDistance = nil + for _, room in ipairs(SoulWarQuest.ebbAndFlow.centerRoomPositions) do + local distance = getDistance(playerPosition, room.conor) + if not smallestDistance or distance < smallestDistance then + smallestDistance = distance + nearestPosition = room.teleportPosition + end + end + return nearestPosition +end + +local function loadMapInundate() + if SoulWarQuest.ebbAndFlow.getZone():countPlayers() > 0 then + local players = SoulWarQuest.ebbAndFlow.getZone():getPlayers() + for _, player in ipairs(players) do + local playerPosition = player:getPosition() + if playerPosition.z == 9 then + if player:isInBoatSpot() then + local nearestCenterPosition = findNearestRoomPosition(playerPosition) + player:teleportTo(nearestCenterPosition) + logger.trace("Teleporting player to the near center position room and updating tile.") + else + player:teleportTo(SoulWarQuest.ebbAndFlow.waitPosition) + logger.trace("Teleporting player to wait position and updating tile.") + end + playerPosition:sendMagicEffect(CONST_ME_TELEPORT) + end + player:sendCreatureAppear() + end + end + + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.inundate) + SoulWarQuest.ebbAndFlow.setLoadedEmptyMap(false) + SoulWarQuest.ebbAndFlow.setActive(true) + + local updatePlayers = EventCallback("UpdatePlayersInundateEbbFlowMap") + function updatePlayers.mapOnLoad(mapPath) + if mapPath ~= SoulWarQuest.ebbAndFlow.mapsPath.inundate then + return + end + + SoulWarQuest.ebbAndFlow.updateZonePlayers() + end + + updatePlayers:register() +end + +local loadEmptyMap = GlobalEvent("SoulWarQuest.ebbAndFlow") + +function loadEmptyMap.onStartup() + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.ebbFlow) + loadMapEmpty() + SoulWarQuest.ebbAndFlow.updateZonePlayers() +end + +loadEmptyMap:register() + +local eddAndFlowInundate = GlobalEvent("eddAndFlowInundate") + +function eddAndFlowInundate.onThink(interval, lastExecution) + if SoulWarQuest.ebbAndFlow.isLoadedEmptyMap() then + logger.trace("Map change to empty in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) + loadMapInundate() + elseif SoulWarQuest.ebbAndFlow.isActive() then + logger.trace("Map change to inundate in {} minutes.", SoulWarQuest.ebbAndFlow.intervalChangeMap) + loadMapEmpty() + end + + return true +end + +eddAndFlowInundate:interval(SoulWarQuest.ebbAndFlow.intervalChangeMap * 60 * 1000) +eddAndFlowInundate:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua new file mode 100644 index 00000000000..0502d53e365 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua @@ -0,0 +1,61 @@ +local firstRaid = MoveEvent() +local secondRaid = MoveEvent() +local thirdRaid = MoveEvent() + +local spawnMonsterName = "Brachiodemon" + +-- Registering encounters, stages and move events +for raidNumber, raid in ipairs(SoulWarQuest.claustrophobicInfernoRaids) do + -- Registering encounter + local raidName = string.format("Claustrophobic Inferno Raid %d", raidNumber) + local encounter = Encounter(raidName, { + zone = raid.getZone(), + timeToSpawnMonsters = "3s", + }) + + local spawnTimes = SoulWarQuest.claustrophobicInfernoRaids.suriviveTime / SoulWarQuest.claustrophobicInfernoRaids.spawnTime + + -- Registering encounter stages + for i = 1, spawnTimes do + encounter + :addSpawnMonsters({ + { + name = spawnMonsterName, + positions = raid.spawns, + }, + }) + :autoAdvance(SoulWarQuest.claustrophobicInfernoRaids.spawnTime * 1000) + end + + function encounter:onReset(position) + encounter:removeMonsters() + addEvent(function(zone) + zone:refresh() + zone:removePlayers() + end, SoulWarQuest.claustrophobicInfernoRaids.timeToKick * 1000, raid.getZone()) + logger.debug("{} has ended", raidName) + end + + encounter:register() + + -- Registering move event + local raidMoveEvent = MoveEvent() + + function raidMoveEvent.onStepIn(creature, item, position, fromPosition) + if not creature:getPlayer() then + return true + end + if fromPosition.y == position.y - (raidNumber % 2 ~= 0 and -1 or 1) then -- if player comes from the raid zone don't start the raid + return + end + logger.debug("{} has started", raidName) + encounter:start() + return true + end + + for _, pos in pairs(raid.sandTimerPositions) do + raidMoveEvent:position(pos) + end + + raidMoveEvent:register() +end diff --git a/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua new file mode 100644 index 00000000000..0d2bd4fffec --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-soul_war_entrances.lua @@ -0,0 +1,147 @@ +local positionsTable = { + -- Hunts + [Position(33615, 31422, 10)] = Position(34009, 31014, 9), -- hunt infernal demon + [Position(33618, 31422, 10)] = Position(33972, 31041, 11), -- hunt rotten + [Position(33621, 31422, 10)] = Position(33894, 31019, 8), -- hunt bony sea devil + [Position(33624, 31422, 10)] = Position(33858, 31831, 3), -- hunt cloak + [Position(33627, 31422, 10)] = Position(33887, 31188, 10), -- hunt many faces +} + +local soul_war_entrances = MoveEvent() + +function soul_war_entrances.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return + end + + if player:getLevel() < 250 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need level 250 to enter here.") + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return + end + + -- Check if player has access to teleport from Flickering Soul npc: "hi/task/yes" + local soulWarQuest = player:soulWarQuestKV() + if not soulWarQuest:get("teleport-access") then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your soul does not yet resonate with the frequency required to enter here.") + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return + end + + for position, destination in pairs(positionsTable) do + if position == player:getPosition() then + fromPosition:sendMagicEffect(CONST_ME_TELEPORT) + player:teleportTo(destination) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + break + end + end + + return true +end + +for key, value in pairs(positionsTable) do + soul_war_entrances:position(key) +end + +soul_war_entrances:register() + +local soul_war_megalomania_entrance = MoveEvent() + +function soul_war_megalomania_entrance.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + if player:getLevel() < 250 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are not allowed to enter here.") + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return false + end + + local text = "" + local soulWarCount = 0 + for bossName, completed in pairs(SoulWarQuest.miniBosses) do + if soulWarQuest:get(bossName) == completed then + soulWarCount = soulWarCount + 1 + else + text = text .. "\n" .. bossName + end + end + + if soulWarCount < 5 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You still need to defeat:" .. text) + player:teleportTo(fromPosition, true) + return false + end + + return true +end + +soul_war_megalomania_entrance:position({ x = 33611, y = 31430, z = 10 }) +soul_war_megalomania_entrance:register() + +local claustrophobicInfernoTeleportPositions = { + [Position(34022, 31091, 11)] = Position(33685, 31599, 14), +} + +local claustrophobicInfernoTeleports = MoveEvent() + +function claustrophobicInfernoTeleports.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + for tablePosition, toPosition in pairs(claustrophobicInfernoTeleportPositions) do + if tablePosition == position then + player:teleportTo(toPosition) + toPosition:sendMagicEffect(CONST_ME_TELEPORT) + break + end + end + + return true +end + +for key, value in pairs(claustrophobicInfernoTeleportPositions) do + claustrophobicInfernoTeleports:position(key) +end + +claustrophobicInfernoTeleports:register() + +local goshnarSpiteEntrance = MoveEvent() + +function goshnarSpiteEntrance.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + local killCount = soulWarQuest:get("hazardous-phantom-death") or 0 + if killCount < 20 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have killed " .. killCount .. " and need to kill 20 Hazardous Phantoms") + player:teleportTo(fromPosition, true) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return false + end + + if position == SoulWarQuest.goshnarSpiteEntrancePosition.fromPos then + player:teleportTo(SoulWarQuest.goshnarSpiteEntrancePosition.toPos) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true + end + + return false +end + +goshnarSpiteEntrance:position(SoulWarQuest.goshnarSpiteEntrancePosition.fromPos) +goshnarSpiteEntrance:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua b/data-otservbr-global/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua similarity index 78% rename from data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua rename to data-otservbr-global/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua index cf999f89c7b..7aaff112056 100644 --- a/data-otservbr-global/scripts/quests/soul_war/actions_portal_reward_soulwar.lua +++ b/data-otservbr-global/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua @@ -1,10 +1,14 @@ local portalReward = MoveEvent() + function portalReward.onStepIn(creature, item, position, fromPosition) local player = creature:getPlayer() if not player then return false end - if player:getStorageValue(Storage.Quest.U12_40.SoulWar.GoshnarMegalomaniaKilled) < 1 then + + local soulWarQuest = player:soulWarQuestKV() + -- Checks if the boss has already been defeated + if not soulWarQuest:get("goshnar's-megalomania-killed") then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Only warriors who defeated Goshnar's Megalomania can access this area.") player:teleportTo(fromPosition, true) return false @@ -15,6 +19,5 @@ function portalReward.onStepIn(creature, item, position, fromPosition) return true end -portalReward:type("stepin") portalReward:position({ x = 33621, y = 31416, z = 10 }) portalReward:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua new file mode 100644 index 00000000000..6a60195277a --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/soul_war_mechanics.lua @@ -0,0 +1,1081 @@ +local login = CreatureEvent("SoulWarLogin") + +function login.onLogin(player) + player:registerEvent("GoshnarsHatredBuff") + player:resetTaints() + player:resetGoshnarSymbolTormentCounter() + return true +end + +login:register() + +-- Goshnar's Malice reflection (100%) of physical and death damage +local goshnarsMaliceReflection = CreatureEvent("Goshnar's-Malice") + +function goshnarsMaliceReflection.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + if not attacker then + return primaryDamage, primaryType, secondaryDamage, secondaryType + end + + local player = attacker:getPlayer() + if player then + if primaryDamage > 0 and (primaryType == COMBAT_PHYSICALDAMAGE or primaryType == COMBAT_DEATHDAMAGE) then + player:addHealth(-primaryDamage) + end + if secondaryDamage > 0 and (secondaryType == COMBAT_PHYSICALDAMAGE or secondaryType == COMBAT_DEATHDAMAGE) then + player:addHealth(-secondaryDamage) + end + end + + return primaryDamage, primaryType, secondaryDamage, secondaryType +end + +goshnarsMaliceReflection:register() + +local soulCageReflection = CreatureEvent("SoulCageHealthChange") + +function soulCageReflection.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + local player = attacker:getPlayer() + if player then + if primaryDamage > 0 then + player:addHealth(-primaryDamage * 0.1) + end + if secondaryDamage > 0 then + player:addHealth(-secondaryDamage * 0.1) + end + end + + return primaryDamage, primaryType, secondaryDamage, secondaryType +end + +soulCageReflection:register() + +local soulCageDeath = CreatureEvent("SoulCageDeath") + +function soulCageDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + if not creature or creature:isPlayer() or creature:getMaster() then + return true + end + + addEvent(SpawnSoulCage, 23000) +end + +soulCageDeath:register() + +local fourthTaintBossesDeath = CreatureEvent("FourthTaintBossesPrepareDeath") + +function fourthTaintBossesDeath.onPrepareDeath(creature, killer, realDamage) + if not creature or not killer:getPlayer() then + return true + end + + if creature:getHealth() - realDamage < 1 then + if killer:getTaintNameByNumber(4) then + local isInZone = killer:getSoulWarZoneMonster() + if isInZone ~= nil then + -- 10% of chance to heal + if math.random(1, 10) == 1 then + creature:say("Health restored by the mystic powers of Zarganash!") + creature:addHealth(creature:getMaxHealth()) + end + end + end + end + return true +end + +fourthTaintBossesDeath:register() + +local bossesDeath = CreatureEvent("SoulWarBossesDeath") + +function bossesDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local bossName = creature:getName() + if SoulWarQuest.miniBosses[bossName] then + local killers = creature:getKillers(true) + for i, killerPlayer in ipairs(killers) do + logger.debug("Player {} killed the boss.", killerPlayer:getName()) + local soulWarQuest = killerPlayer:soulWarQuestKV() + -- Checks if the boss has already been defeated + if not soulWarQuest:get(bossName) then + local firstTaintTime = soulWarQuest:get("firstTaintTime") + if not firstTaintTime then + local currentTime = os.time() + soulWarQuest:set("firstTaintTime", currentTime) + end + + soulWarQuest:set(bossName, true) -- Mark the boss as defeated + -- Adds the next taint in the sequence that the player does not already have + killerPlayer:addNextTaint() + end + end + end +end + +bossesDeath:register() + +fourthTaintBossesDeath:register() + +local lastUse = 0 +local cooldown = 30 + +local mirrorImageCreation = Action() +function mirrorImageCreation.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local currentTime = os.time() + local timePassed = currentTime - lastUse + if timePassed >= cooldown or lastUse == 0 then + Game.createMonster("Mirror Image", player:getPosition()) + lastUse = currentTime + item:transform(33783) + else + local timeLeft = cooldown - timePassed + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait " .. timeLeft .. " second(s) to use this item again.") + end + + return true +end + +mirrorImageCreation:id(33782) +mirrorImageCreation:register() + +local mirroredNightmareApparitionDeath = CreatureEvent("MirroredNightmareBossAccess") + +function mirroredNightmareApparitionDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local creatureName = creature:getName() + if table.contains(SoulWarQuest.apparitionNames, creatureName) then + local damageMap = creature:getMonster():getDamageMap() + for key, _ in pairs(damageMap) do + local player = Player(key) + if player then + local soulWarQuest = player:soulWarQuestKV() + local currentCount = soulWarQuest:get(creatureName) or 0 + soulWarQuest:set(creatureName, currentCount + 1) + end + end + end +end + +mirroredNightmareApparitionDeath:register() + +-- Check mirrored nightmare boss access +local goshnarGreedEntrance = MoveEvent() + +function goshnarGreedEntrance.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarQuest = player:soulWarQuestKV() + local hasAccess = true + local message = "Progress towards Mirrored Nightmare boss access:\n" + + for _, apparitionName in pairs(SoulWarQuest.apparitionNames) do + local count = soulWarQuest:get(apparitionName) or 0 + if count < SoulWarQuest.requiredCountPerApparition then + hasAccess = false + message = message .. apparitionName .. ": " .. count .. "/" .. SoulWarQuest.requiredCountPerApparition .. " kills\n" + else + message = message .. apparitionName .. ": Access achieved!\n" + end + end + + if not hasAccess then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) + player:teleportTo(fromPosition) + return false + end + + player:teleportTo(SoulWarQuest.goshnarsGreedAccessPosition.to) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true +end + +goshnarGreedEntrance:position(SoulWarQuest.goshnarsGreedAccessPosition.from) +goshnarGreedEntrance:register() + +local greedMonsterDeath = CreatureEvent("GreedMonsterDeath") + +function greedMonsterDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local createMonsterPosition = GreedMonsters[creature:getName()] + if creature:getName() == "Greedbeast" then + GreedbeastKills = GreedbeastKills + 1 + end + + CreateGoshnarsGreedMonster(creature:getName(), createMonsterPosition) +end + +greedMonsterDeath:register() + +local checkTaint = TalkAction("!checktaint") + +function checkTaint.onSay(player, words, param) + local taintLevel = player:getTaintLevel() + local taintName = player:getTaintNameByNumber(taintLevel) + if taintLevel ~= nil and taintName ~= nil then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your current taint level is: " .. taintLevel .. " name: " .. taintName) + else + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You currently have no taint.") + end + + return true +end + +checkTaint:groupType("normal") +checkTaint:register() + +local setTaint = TalkAction("/settaint") + +function setTaint.onSay(player, words, param) + local split = param:split(",") + local target = Player(split[1]) + if not target then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Player is offline") + return false + end + + local taintLevel = split[2]:trim():lower() + local taintName = player:getTaintNameByNumber(tonumber(taintLevel), true) + if taintName ~= nil then + target:resetTaints(true) + target:soulWarQuestKV():set(taintName, true) + target:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You new taint level is: " .. taintLevel .. ", name: " .. taintName) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Added taint level: " .. taintLevel .. ", name: " .. taintName .. " to player: " .. target:getName()) + target:setTaintIcon() + end +end + +setTaint:separator(" ") +setTaint:groupType("god") +setTaint:register() + +local goshnarGreedTeleport = MoveEvent() + +function goshnarGreedTeleport.onStepIn(creature, item, position, fromPosition) + local creatureName = creature:getName() + if creatureName == "Greedbeast" then + return + end + + local foundCreaturePosition = GreedMonsters[creatureName] + if not foundCreaturePosition then + return false + end + + if item:getId() == 33791 then + creature:remove() + item:transform(33790) + position:sendMagicEffect(CONST_ME_MORTAREA) + CreateGoshnarsGreedMonster(creatureName, foundCreaturePosition) + end + + return true +end + +goshnarGreedTeleport:id(33790, 33791) +goshnarGreedTeleport:register() + +local setTaint = TalkAction("/removetaint") + +function setTaint.onSay(player, words, param) + local split = param:split(",") + local target = Player(split[1]) + if not target then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Player is offline") + return false + end + + local taintLevel = split[2]:trim():lower() + local taintName = player:getTaintNameByNumber(tonumber(taintLevel)) + if taintName ~= nil then + target:soulWarQuestKV():remove(taintName) + target:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You lose taint level: " .. taintLevel .. ", name: " .. taintName) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Removed taint level: " .. taintLevel .. ", name: " .. taintName .. " from player: " .. target:getName()) + end +end + +setTaint:separator(" ") +setTaint:groupType("god") +setTaint:register() + +local changeMap = TalkAction("/changeflowmap") + +function changeMap.onSay(player, words, param) + if param == "empty" then + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.empty) + elseif param == "inundate" then + Game.loadMap(SoulWarQuest.ebbAndFlow.mapsPath.inundate) + elseif param == "ebb" then + Game.loadMap(SoulWarQuest.ebbAndFlowmapsPath.ebbFlow) + end +end + +changeMap:separator(" ") +changeMap:groupType("god") +changeMap:register() + +local hazardousPhantomDeath = CreatureEvent("HazardousPhantomDeath") + +function hazardousPhantomDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local killers = creature:getKillers(true) + for i, killerPlayer in ipairs(killers) do + -- Checks if the killer is a player + if killerPlayer:isPlayer() then + local soulWarQuest = killerPlayer:soulWarQuestKV() + local deathCount = soulWarQuest:get("hazardous-phantom-death") or 0 + -- Checks that the death count has not yet reached the limit + if deathCount < SoulWarQuest.hardozousPanthomDeathCount then + -- Increases death count + soulWarQuest:set("hazardous-phantom-death", deathCount + 1) + -- Send the count for the player + killerPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You killed " .. (deathCount + 1) .. " of " .. SoulWarQuest.hardozousPanthomDeathCount .. " Hazardous Panthom.") + end + + if deathCount + 1 == SoulWarQuest.hardozousPanthomDeathCount then + killerPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You can now access the boss room.") + end + end + end +end + +hazardousPhantomDeath:register() + +local weepingSoulCorpse = MoveEvent() + +local condition = Condition(CONDITION_OUTFIT) +condition:setOutfit(SoulWarQuest.waterElementalOutfit) +condition:setTicks(14000) + +function weepingSoulCorpse.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return + end + + if player:hasCondition(CONDITION_OUTFIT) then + return + end + + local monster = Creature("Goshnar's Spite") + if monster then + local chance = math.random(100) + if chance <= SoulWarQuest.goshnarsSpiteHealChance then + local healAmount = math.floor(monster:getMaxHealth() * (SoulWarQuest.goshnarsSpiteHealPercentage / 100)) + -- Heal percentage of the maximum health + monster:addHealth(healAmount) + logger.debug("Goshnar's Spite was healed to 10% of its maximum health.") + end + end + + item:remove() + player:addCondition(condition) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You are soaked by tears of the weeping soul!") + return true +end + +weepingSoulCorpse:id(SoulWarQuest.weepingSoulCorpseId) +weepingSoulCorpse:register() + +local function removeSearingFire(position) + local tile = Tile(position) + if tile then + local fire = tile:getItemById(SoulWarQuest.searingFireId) + if fire then + local monster = Creature("Goshnar's Spite") + if monster then + monster:addDefense(SoulWarQuest.goshnarsSpiteIncreaseDefense) + logger.debug("Found Goshnar's Spite on boss zone, adding defense.") + end + fire:remove() + end + end +end + +local goshnarSpiteFire = GlobalEvent("CreateGoshnarSpiteFire") + +function goshnarSpiteFire.onThink(interval) + local randomIndex = math.random(#SoulWarQuest.goshnarsSpiteFirePositions) -- Choose a random index + local firePosition = SoulWarQuest.goshnarsSpiteFirePositions[randomIndex] -- Get the corresponding position + local tile = Tile(firePosition) + if tile then + local fire = Game.createItem(SoulWarQuest.searingFireId, 1, firePosition) + if fire then + addEvent(removeSearingFire, SoulWarQuest.timeToRemoveSearingFire * 1000, firePosition) + end + end + + return true +end + +goshnarSpiteFire:interval(SoulWarQuest.timeToCreateSearingFire * 1000) +goshnarSpiteFire:register() + +local goshnarSpiteSoulFire = MoveEvent() + +function goshnarSpiteSoulFire.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return + end + + local tile = Tile(position) + if not tile then + return + end + + local searingFire = tile:getItemById(SoulWarQuest.searingFireId) + if not searingFire then + return + end + + local soulWarQuest = player:soulWarQuestKV() + local lastSteppedTime = soulWarQuest:get("goshnar-spite-fire") or 0 + local currentTime = os.time() + + if lastSteppedTime + SoulWarQuest.cooldownToStepOnSearingFire > currentTime then + local remainingTime = lastSteppedTime + SoulWarQuest.cooldownToStepOnSearingFire - currentTime + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "His soul won't need to recover again! You need wait " .. remainingTime .. " seconds.") + return true + end + + addEvent(function(playerId) + local eventPlayer = Player(playerId) + if eventPlayer then + eventPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your soul has recovered!") + end + end, SoulWarQuest.cooldownToStepOnSearingFire * 1000, player:getId()) + + soulWarQuest:set("goshnar-spite-fire", currentTime) + searingFire:remove() + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The soul fire was stomped out in time! Your soul will now have to recover before you can do this again.") + + return true +end + +for _, pos in pairs(SoulWarQuest.goshnarsSpiteFirePositions) do + goshnarSpiteSoulFire:position(pos) +end + +goshnarSpiteSoulFire:register() + +local ebbAndFlowBoatTeleports = MoveEvent() + +function ebbAndFlowBoatTeleports.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player or not SoulWarQuest.ebbAndFlow.isActive() then + return + end + + for _, pos in pairs(SoulWarQuest.ebbAndFlowBoatTeleportPositions) do + if Position(pos.register) == position then + player:teleportTo(pos.teleportTo) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true + end + end +end + +for _, pos in pairs(SoulWarQuest.ebbAndFlowBoatTeleportPositions) do + ebbAndFlowBoatTeleports:position(pos.register) +end +ebbAndFlowBoatTeleports:register() + +local ebbAndFlowDoor = Action() + +function ebbAndFlowDoor.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if SoulWarQuest.ebbAndFlow.isActive() then + return false + end + + -- Determines whether the player is north or south of the door + local playerPosition = player:getPosition() + local destination = Position(toPosition.x, toPosition.y, toPosition.z) + if playerPosition.y < toPosition.y then + -- Player is north, move south + destination.y = toPosition.y + 1 + else + -- Player is south (or at the same y position), moves north + destination.y = toPosition.y - 1 + end + + player:teleportTo(destination) + destination:sendMagicEffect(CONST_ME_TELEPORT) + return true +end + +ebbAndFlowDoor:id(SoulWarQuest.ebbAndFlow.doorId) +ebbAndFlowDoor:register() + +local rottenWastelandShrines = Action() + +function rottenWastelandShrines.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local soulWarQuest = player:soulWarQuestKV() + local shrineUsed = soulWarQuest:get("rotten-wasterland-activated-shrine-id") or 0 + if shrineUsed == item:getId() then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already activated this shrine.") + return true + end + + local activatedShrinesCount = soulWarQuest:get("rotten-wasterland-activated-shrine-count") or 0 + if activatedShrinesCount >= 4 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have already activated all the shrines.") + return true + end + + soulWarQuest:set("rotten-wasterland-activated-shrine-id", item:getId()) + + soulWarQuest:set("rotten-wasterland-activated-shrine-count", activatedShrinesCount + 1) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have activated this shrine.") + return true +end + +for itemId, position in pairs(SoulWarQuest.rottenWastelandShrines) do + rottenWastelandShrines:id(itemId) +end + +rottenWastelandShrines:register() + +local goshnarsHatredAccess = Action() + +function goshnarsHatredAccess.onUse(player, item, fromPosition, target, toPosition, isHotkey) + local soulWarQuest = player:soulWarQuestKV() + local activatedShrineCount = soulWarQuest:get("rotten-wasterland-activated-shrine-count") or 0 + if activatedShrineCount < 4 then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You still need to activate all the shrines.") + return true + end + + player:teleportTo(SoulWarQuest.goshnarsHatredAccessPosition.to) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true +end + +goshnarsHatredAccess:position(SoulWarQuest.goshnarsHatredAccessPosition.from) +goshnarsHatredAccess:register() + +local goshnarsHatredSorrow = Action() + +function goshnarsHatredSorrow.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if not target then + return + end + + if not table.contains(SoulWarQuest.burningHatredMonsters, target:getName()) then + logger.error("Player {} tried to use the item on a non-burning hatred monster.", player:getName()) + return + end + + item:remove() + local actualTime = SoulWarQuest.kvBurning:get("time") or 0 + SoulWarQuest.kvBurning:set("time", actualTime + 10) + logger.debug("Player {} used the item on the monster {}, oldTime {}, newTime {}.", player:getName(), target:getName(), actualTime, actualTime + 10) + player:say("The flame of hatred is doused!", TALKTYPE_MONSTER_SAY, 0, 0, target:getPosition()) + return true +end + +goshnarsHatredSorrow:id(SoulWarQuest.goshnarsHatredSorrowId) +goshnarsHatredSorrow:register() + +local burningChangeForm = CreatureEvent("BurningChangeForm") + +function burningChangeForm.onThink(creature) + if not creature or not creature:getMonster() then + return true + end + + local monster = creature:getMonster() + local currentTime = SoulWarQuest.kvBurning:get("time") or 0 + if currentTime == 0 then + SoulWarQuest.kvBurning:set("time", 180) + return true + end + + SoulWarQuest.kvBurning:set("time", currentTime - 1) + + logger.debug("Burning transformation decreased to time : {}", currentTime) + for _, transformation in ipairs(SoulWarQuest.burningTransformations) do + local timeTransformation, newType = unpack(transformation) + if currentTime == timeTransformation and monster:getName() ~= newType then + monster:setType(newType, true) + logger.debug("Changing monster to {} on currentTime {}.", newType, currentTime) + + if newType == "Ashes of Burning Hatred" then + monster:say("The fire of hatred fuels and empowers Goshnar's Hate!", TALKTYPE_MONSTER_SAY, 0, 0, monster:getPosition()) + local boss = Creature("Goshnar's Hatred") + if boss then + logger.debug("Increasing hatred damage multiplier.") + boss:increaseHatredDamageMultiplier(10) + end + logger.debug("Beginning of the burning transformation cycle.") + end + break + end + end + + return true +end + +burningChangeForm:register() + +local goshnarsHatredBuff = CreatureEvent("GoshnarsHatredBuff") + +function goshnarsHatredBuff.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + -- Ensure both attacker and creature are valid and the creature is "Goshnar's Hatred" + if creature then + -- Check if the attacker is a player and the creature is being hit + if attacker and creature:isMonster() and attacker:isPlayer() and (creature:getName() == "Goshnar's Hatred" or creature:getName() == "Goshnar's Megalomania") then + local defenseMultiplier = creature:getHatredDamageMultiplier() + if defenseMultiplier > 0 then + -- Apply the defense multiplier + creature:addDefense(defenseMultiplier) + logger.debug("Adding defense to {}.", creature:getName()) + end + -- Check if the attacker is a monster and the player is being hit + elseif attacker and creature:isPlayer() and attacker:isMonster() and (attacker:getName() == "Goshnar's Hatred" or creature:getName() == "Goshnar's Megalomania") then + local damageMultiplier = attacker:getHatredDamageMultiplier() + if damageMultiplier > 0 then + local multip = 1 + (damageMultiplier / 100) + logger.debug("Adding damage: {} to {}.", multip, attacker:getName()) + -- Return modified damage values + return primaryDamage * multip, primaryType, secondaryDamage, secondaryType + end + end + end + + -- Return original damage values if no conditions are met + return primaryDamage, primaryType, secondaryDamage, secondaryType +end + +goshnarsHatredBuff:register() + +local condensedRemorse = MoveEvent() + +function condensedRemorse.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local soulWarKV = player:soulWarQuestKV() + local remorseCount = soulWarKV:get("condensed-remorse") or 0 + soulWarKV:set("condensed-remorse", remorseCount + 1) + if remorseCount + 1 == 2 then + player:resetGoshnarSymbolTormentCounter() + player:say("The remorse calms your dread!", TALKTYPE_MONSTER_SAY, 0, 0, item:getPosition()) + player:getPosition():sendMagicEffect(CONST_ME_HOLYAREA) + soulWarKV:remove("condensed-remorse") + end + + item:remove() + return true +end + +condensedRemorse:id(SoulWarQuest.condensedRemorseId) +condensedRemorse:register() + +local furiousCraterAccess = EventCallback("FuriousCraterAccessDropLoot") + +function furiousCraterAccess.monsterOnDropLoot(monster, corpse) + if not monster or not corpse then + return + end + + local player = Player(corpse:getCorpseOwner()) + if not player or not player:canReceiveLoot() then + return + end + + local mType = monster:getType() + if not mType then + return + end + + if not table.contains(SoulWarQuest.pulsatingEnergyMonsters, mType:getName()) then + return + end + + Game.createItem(SoulWarQuest.pulsatingEnergyId, 1, monster:getPosition()) +end + +furiousCraterAccess:register() + +local pulsatingEnergy = MoveEvent() + +function pulsatingEnergy.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + local kv = player:pulsatingEnergyKV() + local energyCount = kv:get("access-counter") or 0 + energyCount = energyCount + 1 + kv:set("access-counter", energyCount) + + logger.debug("Player {} stepped on a pulsating energy, current count: {}", player:getName(), energyCount) + + local firstFloorAccess = kv:get("first-floor-access") or false + local secondFloorAccess = kv:get("second-floor-access") or false + local thirdFloorAccess = kv:get("third-floor-access") or false + if thirdFloorAccess then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've already gained access to fight with the Goshnar's Cruelty.") + return true + end + + if energyCount >= 40 and not firstFloorAccess then + kv:set("access-counter", 0) + kv:set("first-floor-access", true) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've gained access to the first floor. Continue collecting Pulsating Energies to gain further access.") + end + + if energyCount >= 55 and not secondFloorAccess then + kv:set("access-counter", 0) + kv:set("second-floor-access", true) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've gained access to the second floor. Continue collecting Pulsating Energies to gain further access.") + end + + if energyCount >= 70 and not thirdFloorAccess then + kv:set("access-counter", 0) + kv:set("third-floor-access", true) + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You've gained access to the third floor. You can now fight with the Goshnar's Cruelty.") + end + + item:remove() + return true +end + +pulsatingEnergy:id(SoulWarQuest.pulsatingEnergyId) +pulsatingEnergy:register() + +local pulsatingEnergyTeleportAccess = MoveEvent() + +function pulsatingEnergyTeleportAccess.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + for _, posData in pairs(SoulWarQuest.goshnarsCrueltyTeleportRoomPositions) do + if posData.from == position then + local kv = player:pulsatingEnergyKV() + local hasAccess = kv:get(posData.access) or false + local energyCount = kv:get("access-counter") or 0 + local energiesNeeded = posData.count - energyCount + if not hasAccess then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You don't have access to this floor yet. You have collected " .. energyCount .. "/" .. posData.count .. ", and need " .. energiesNeeded .. " more pulsating energies to gain access.") + player:teleportTo(fromPosition, true) + fromPosition:sendMagicEffect(CONST_ME_TELEPORT) + else + player:teleportTo(posData.to) + posData.to:sendMagicEffect(CONST_ME_TELEPORT) + end + + break + end + end + + return true +end + +for _, positions in pairs(SoulWarQuest.goshnarsCrueltyTeleportRoomPositions) do + pulsatingEnergyTeleportAccess:position(positions.from) +end + +pulsatingEnergyTeleportAccess:register() + +local cloakOfTerrorHealthLoss = CreatureEvent("CloakOfTerrorHealthLoss") + +function cloakOfTerrorHealthLoss.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + if not creature or not attacker then + return primaryDamage, primaryType, secondaryDamage, secondaryType + end + + if attacker:getPlayer() and primaryDamage > 0 or secondaryDamage > 0 then + local position = creature:getPosition() + local tile = Tile(position) + if tile then + if not tile:getItemById(SoulWarQuest.theBloodOfCloakTerrorIds[1]) then + Game.createItem(SoulWarQuest.theBloodOfCloakTerrorIds[1], 1, position) + end + end + end + + return primaryDamage, primaryType, secondaryDamage, secondaryType +end + +cloakOfTerrorHealthLoss:register() + +local theBloodOfCloakStep = MoveEvent() + +function theBloodOfCloakStep.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + -- If a player steps in blood, it takes damage + if player then + local damagePercentage = SoulWarQuest.poolDamagePercentages[item:getId()] or 0 + local maxHealth = player:getMaxHealth() + local damage = maxHealth * damagePercentage + + player:addHealth(-damage, COMBAT_ENERGYDAMAGE) + end + + -- If a "Cloak of Terror" monster steps in blood, it heals itself + local monster = creature:getMonster() + if monster and monster:getName() == "Cloak of Terror" then + local healAmount = math.random(1500, 2000) + monster:addHealth(healAmount) + end + + item:remove() + + return true +end + +for _, itemId in pairs(SoulWarQuest.theBloodOfCloakTerrorIds) do + theBloodOfCloakStep:id(itemId) +end + +theBloodOfCloakStep:register() + +local greedyMaw = Action() + +function greedyMaw.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if not item or not target then + logger.error("Greedy Maw action failed, item or target is nil.") + return false + end + + if target:getId() == SoulWarQuest.greedyMawId then + local kv = player:soulWarQuestKV():scoped("furious-crater") + local cooldown = kv:get("greedy-maw-action") or 0 + local currentTime = os.time() + if cooldown + SoulWarQuest.useGreedMawCooldown > currentTime then + local timeLeft = cooldown + SoulWarQuest.useGreedMawCooldown - currentTime + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait " .. timeLeft .. " more seconds before using the greedy maw again.") + return true + end + + kv:set("greedy-maw-action", currentTime) + local timeToIncreaseDefense = SoulWarQuest.timeToIncreaseCrueltyDefense + SoulWarQuest.kvSoulWar:set("greedy-maw-action", currentTime + timeToIncreaseDefense) + target:getPosition():sendMagicEffect(CONST_ME_DRAWBLOOD) + item:remove() + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Use the item again within " .. timeToIncreaseDefense .. " seconds, or the monster's defense will increase by 2 every " .. timeToIncreaseDefense .. " seconds.") + local goshnarsCruelty = Creature("Goshnar's Cruelty") + if goshnarsCruelty then + local mtype = goshnarsCruelty:getType() + if not mtype then + logger.error("Greedy Maw action failed, Goshnar's Cruelty has no type.") + return false + end + + -- If the defense of Goshnar's Cruelty is higher than the default defense, decrease it by 2 + if goshnarsCruelty:getDefense() > mtype:defense() then + logger.debug("Greedy Maw used on Goshnar's Cruelty, old defense {}", goshnarsCruelty:getDefense()) + goshnarsCruelty:addDefense(-SoulWarQuest.goshnarsCrueltyDefenseChange) + logger.debug("Greedy Maw used on Goshnar's Cruelty, new defense {}", goshnarsCruelty:getDefense()) + end + + local defenseDrainValue = SoulWarQuest.kvSoulWar:get("goshnars-cruelty-defense-drain") or 0 + if defenseDrainValue > 0 then + SoulWarQuest.kvSoulWar:set("goshnars-cruelty-defense-drain", defenseDrainValue - 1) + end + end + return true + end + + return false +end + +greedyMaw:id(SoulWarQuest.someMortalEssenceId) +greedyMaw:register() + +local soulWarAspectOfPowerDeath = CreatureEvent("SoulWarAspectOfPowerDeath") + +function soulWarAspectOfPowerDeath.onDeath(creature) + local targetMonster = creature:getMonster() + if not targetMonster or targetMonster:getMaster() then + return + end + + logger.debug("Aspect of Power died, checking if all are dead.") + local boss = Creature("Goshnar's Megalomania") + if boss and boss:getTypeName() == "Goshnar's Megalomania Purple" then + boss:increaseAspectOfPowerDeathCount() + end + + local position = boss and boss:getPosition() or creature:getPosition() + addEvent(function(position) + local aspectMonster = Game.createMonster("Aspect of Power", position) + if aspectMonster then + local outfit = aspectMonster:getOutfit() + outfit.lookType = math.random(1303, 1307) + aspectMonster:setOutfit(outfit) + end + end, 5000, position) + + return true +end + +soulWarAspectOfPowerDeath:register() + +local madnessReduce = MoveEvent() + +function madnessReduce.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + item:getPosition():sendMagicEffect(CONST_ME_HOLYAREA) + item:remove() + if player and player:getGoshnarSymbolTormentCounter() > 0 then + player:resetGoshnarSymbolTormentCounter() + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The ooze calms your dread but leaves you vulnerable to phantasmal attacks!") + return true + end + + local creatureName = creature:getName() + if creatureName == "Lesser Splinter of Madness" or creatureName == "Greater Splinter of Madness" or creatureName == "Mighty Splinter of Madness" then + creature:remove() + item:transform(SoulWarQuest.cleansedSanityItemId) + end + + return true +end + +madnessReduce:id(SoulWarQuest.deadAspectOfPowerCorpseId) +madnessReduce:register() + +local cleansedSanity = Action() + +function cleansedSanity.onUse(player, item, fromPosition, target, toPosition, isHotkey) + if not item or not target then + logger.error("Cleansed action failed, item or target is nil.") + return false + end + + local kv = player:soulWarQuestKV():scoped("furious-crater") + local cooldown = kv:get("cleansed-sanity-action") or 0 + local currentTime = os.time() + if cooldown + SoulWarQuest.useGreedMawCooldown > currentTime then + local timeLeft = cooldown + SoulWarQuest.useGreedMawCooldown - currentTime + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You need to wait " .. timeLeft .. " more seconds before using the cleansed again.") + return true + end + + kv:set("cleansed-sanity-action", currentTime) + if target:getId() == SoulWarQuest.greedyMawId then + local timeToIncreaseDefense = SoulWarQuest.timeToIncreaseCrueltyDefense + SoulWarQuest.kvSoulWar:set("cleansed-sanity-action", currentTime + timeToIncreaseDefense) + target:getPosition():sendMagicEffect(CONST_ME_DRAWBLOOD) + item:remove() + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Use the item again within " .. timeToIncreaseDefense .. " seconds, or the monster's defense will increase every " .. timeToIncreaseDefense .. " seconds.") + local boss = Creature("Goshnar's Megalomania") + if boss then + local mtype = boss:getType() + if not mtype then + logger.error("Cleansed action failed, Goshnar's Megalomania has no type.") + return false + end + + -- If the defense of Goshnar's Megalomania is higher than the default defense, decrease it by 2 + if boss:getDefense() > mtype:defense() then + logger.debug("Cleansed used on Goshnar's Megalomania, old defense {}", boss:getDefense()) + boss:addDefense(-SoulWarQuest.goshnarsCrueltyDefenseChange) + logger.debug("Cleansed used on Goshnar's Megalomania, new defense {}", boss:getDefense()) + end + end + return true + end + + return false +end + +cleansedSanity:id(SoulWarQuest.cleansedSanityItemId) +cleansedSanity:register() + +local necromanticRemainsReduce = MoveEvent() + +function necromanticRemainsReduce.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return + end + + player:removeGoshnarSymbolTormentCounter(5) + item:remove() + position:sendMagicEffect(CONST_ME_HOLYAREA) + return true +end + +necromanticRemainsReduce:id(SoulWarQuest.necromanticRemainsItemId) +necromanticRemainsReduce:register() + +local necromanticFocusDeath = CreatureEvent("NecromanticFocusDeath") + +function necromanticFocusDeath.onDeath(creature) + local targetMonster = creature:getMonster() + if not targetMonster or targetMonster:getMaster() then + return + end + + local position = targetMonster:getPosition() + addEvent(function() + position:increaseNecromaticMegalomaniaStrength() + end, 5 * 60 * 1000) + + return true +end + +necromanticFocusDeath:register() + +local megalomaniaDeath = CreatureEvent("MegalomaniaDeath") + +function megalomaniaDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUnjustified, mostDamageUnjustified) + local killers = creature:getKillers(true) + for i, killerPlayer in ipairs(killers) do + local soulWarQuest = killerPlayer:soulWarQuestKV() + -- Checks if the boss has already been defeated + if not soulWarQuest:get("goshnar's-megalomania-killed") then + soulWarQuest:set("goshnar's-megalomania-killed", true) + killerPlayer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have defeated Goshnar's Megalomania. Report the 'task' to Flickering Soul and earn your outfit.") + end + end + return true +end + +megalomaniaDeath:register() + +local teleportStepRemoveIcon = MoveEvent() + +function teleportStepRemoveIcon.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return + end + + player:resetGoshnarSymbolTormentCounter() + return true +end + +local teleportPositions = { + Position(33713, 31642, 14), + Position(33743, 31606, 14), +} + +for _, pos in pairs(teleportPositions) do + teleportStepRemoveIcon:position(pos) +end + +teleportStepRemoveIcon:register() + +local goshnarsCrueltyBuff = CreatureEvent("GoshnarsCrueltyBuff") + +function goshnarsCrueltyBuff.onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + if creature and creature:isMonster() and attacker:isPlayer() and creature:getName() == "Goshnar's Cruelty" then + local newValue = SoulWarQuest.kvSoulWar:get("goshnars-cruelty-defense-drain") or SoulWarQuest.goshnarsCrueltyDefenseChange + if newValue ~= 0 then + local multiplier = math.max(0, 1 - (newValue / 100)) + return primaryDamage * multiplier, primaryType, secondaryDamage * multiplier, secondaryType + end + end + + return primaryDamage, primaryType, secondaryDamage, secondaryType +end + +goshnarsCrueltyBuff:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua b/data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua new file mode 100644 index 00000000000..7a73979930a --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-eye_beam.lua @@ -0,0 +1,38 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_DROWNDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_SMALLCLOUDS) + +combat:setArea(createCombatArea({ + { 1 }, + { 1 }, + { 3 }, +})) + +function onTargetTile(cid, pos) + local tile = Tile(pos) + local target = tile:getTopCreature() + if tile then + if target then + if target:isMonster() and target:getName() == "Poor Soul" then + target:addHealth(-1000) + end + end + end + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("greedy eye beam") +spell:words("greedy eye beam") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua new file mode 100644 index 00000000000..2bc321f24e0 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_cruelty.lua @@ -0,0 +1,61 @@ +local areaSpell = { + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, +} + +local area = createCombatArea(areaSpell) + +local combat = Combat() +combat:setArea(area) +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +function onTargetTile(cid, pos) + local tile = Tile(pos) + if tile then + local target = tile:getTopCreature() + if target and target:isPlayer() then + target:addHealth(math.random(2300, 3000)) + end + end + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local function delayedCastSpell(cid, var, targetId) + local creature = Creature(cid) + if not creature then + return + end + + local target = Player(targetId) + if target then + combat:execute(creature, positionToVariant(target:getPosition())) + end +end + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var, isHotkey) + return creature:applyZoneEffect(var, combat, "boss.goshnar's-cruelty") +end + +spell:name("cruelty transform elemental") +spell:words("cruelty transform elemental") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua new file mode 100644 index 00000000000..2906f213d74 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-fire_beam_megalomania.lua @@ -0,0 +1,54 @@ +local areaSpell = { + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, + { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, +} + +local area = createCombatArea(areaSpell) + +local combat = Combat() +combat:setArea(area) +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_FIREAREA) + +function onTargetTile(cid, pos) + local tile = Tile(pos) + if tile then + local target = tile:getTopCreature() + if target and target:isPlayer() then + target:addHealth(math.random(2300, 3000)) + end + end + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local config = { + outfit = { lookType = 242, lookHead = 0, lookBody = 0, lookLegs = 0, lookFeet = 0, lookAddons = 0 }, + time = 7000, +} + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var, isHotkey) + return creature:applyZoneEffect(var, combat, "boss.goshnar's-megalomania-purple") +end + +spell:name("megalomania transform elemental") +spell:words("megalomania transform elemental") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua b/data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua new file mode 100644 index 00000000000..d8a9c8e9875 --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-megalomania_blue.lua @@ -0,0 +1,58 @@ +local area = { + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, +} + +local createArea = createCombatArea(area) + +local combat = Combat() +combat:setArea(createArea) + +local zone = Zone.getByName("boss.goshnar's-megalomania-purple") +local zonePositions = zone:getPositions() + +function onTargetTile(creature, pos) + for _, pos in ipairs(zonePositions) do + local tile = Tile(pos) + if tile and tile:getGround() and tile:getGround():getId() ~= 409 then + local creature = tile:getTopCreature() + if creature then + local player = creature:getPlayer() + if player then + player:addHealth(-6000, COMBAT_DEATHDAMAGE) + end + end + end + end + + pos:sendMagicEffect(CONST_ME_BLACKSMOKE) + return true +end + +combat:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, positionToVariant(creature:getPosition())) +end + +spell:name("megalomania blue") +spell:words("megalomania blue") +spell:isAggressive(true) +spell:blockWalls(false) +spell:needLearn(true) +spell:register() diff --git a/data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua b/data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua new file mode 100644 index 00000000000..9292f10530f --- /dev/null +++ b/data-otservbr-global/scripts/quests/soul_war/spell-soulsnatcher.lua @@ -0,0 +1,58 @@ +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HOLYDAMAGE) + +combat:setArea(createCombatArea(CrossBeamArea3X2)) + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("soulsnatcher-lifedrain-beam") +spell:words("soulsnatcher-lifedrain-beam") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_HOLYAREA) +combat:setParameter(COMBAT_PARAM_DISTANCEEFFECT, CONST_ANI_HOLY) + +spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("soulsnatcher-lifedrain-missile") +spell:words("soulsnatcher-lifedrain-missile") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:needTarget(true) +spell:register() + +-- Mana drain ball +local combat = Combat() +combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE) +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_ENERGYAREA) + +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) + +spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("soulsnatcher-manadrain-ball") +spell:words("soulsnatcher-manadrain-ball") +spell:isAggressive(true) +spell:blockWalls(true) +spell:needLearn(true) +spell:register() diff --git a/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua b/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua index 65e6af5a3b2..7da5f5d1b99 100644 --- a/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua +++ b/data-otservbr-global/scripts/world_changes/iron_servant_transformation.lua @@ -1,4 +1,4 @@ -local ironServantTransformation = EventCallback() +local ironServantTransformation = EventCallback("IronServantTransformationOnSpawn") ironServantTransformation.monsterOnSpawn = function(monster, position) if monster:getName():lower() ~= "iron servant replica" then diff --git a/data-otservbr-global/world/otservbr-house.xml b/data-otservbr-global/world/otservbr-house.xml index f7e5cd52370..7eff23b4606 100644 --- a/data-otservbr-global/world/otservbr-house.xml +++ b/data-otservbr-global/world/otservbr-house.xml @@ -284,7 +284,7 @@ - + diff --git a/data-otservbr-global/world/otservbr-monster.xml b/data-otservbr-global/world/otservbr-monster.xml index ef003deee1a..80c5f5d4ff3 100644 --- a/data-otservbr-global/world/otservbr-monster.xml +++ b/data-otservbr-global/world/otservbr-monster.xml @@ -34097,6 +34097,9 @@ + + + @@ -34129,6 +34132,9 @@ + + + @@ -34214,6 +34220,9 @@ + + + @@ -161998,9 +162007,15 @@ + + + + + + @@ -162027,6 +162042,15 @@ + + + + + + + + + @@ -162036,6 +162060,12 @@ + + + + + + @@ -162050,6 +162080,9 @@ + + + @@ -162062,6 +162095,9 @@ + + + @@ -162083,6 +162119,12 @@ + + + + + + @@ -162156,6 +162198,9 @@ + + + @@ -162168,12 +162213,22 @@ + + + + + + + + + + @@ -162185,6 +162240,15 @@ + + + + + + + + + @@ -162194,6 +162258,9 @@ + + + @@ -162201,6 +162268,9 @@ + + + diff --git a/data-otservbr-global/world/otservbr-npc.xml b/data-otservbr-global/world/otservbr-npc.xml index 2011d333ad4..2b3f88a00d7 100644 --- a/data-otservbr-global/world/otservbr-npc.xml +++ b/data-otservbr-global/world/otservbr-npc.xml @@ -2460,6 +2460,9 @@ + + + diff --git a/data-otservbr-global/world/otservbr-zones.xml b/data-otservbr-global/world/otservbr-zones.xml index 4740d50385c..44cff897a42 100644 --- a/data-otservbr-global/world/otservbr-zones.xml +++ b/data-otservbr-global/world/otservbr-zones.xml @@ -1,4 +1,4 @@ - + diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow-empty.otbm new file mode 100644 index 0000000000000000000000000000000000000000..9d3fa897425709c4d254f1ac0b598f26e2cbe0bd GIT binary patch literal 129452 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

PUx6Z9|8{7uB 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 delta 6727 zcmZ8ldu&@*8E=x~G~S?F(rRnh$3kFJU+Fr5gv2V=_hXf+w60sBu{B-Y#;@x~oHPr0 zIJU3t^s(8#51&XYP-%Z4B*fMt>a~*)2nI-GELEjx0>K|>OhX85AoZ~^T)unG@A}@G z{E?r(@AsYW`+et}d(O3+e_DR!Kg*Xbp}*#mcKUy!xI(QhJMyg+OCpKUMCjzm>3$`d z$c&sG-tw{%fAnF;ok*RsTwm^ST;b$s=5+2>@m5>$R{PIyb-a16ap|*;O;7Uq$60UI zA6|3+ev>UxmyG+HzTA4hvf)1A2H-~ECW(3QdGLAgdGH1B1@Hy%1;L|ESJS7i_kY$+ zb=2++{@dciM*pL0EXOAPNH6Q<&shHkd)e2rjyM`5#HmQGBDspuZUH{!UuoHe&C)J_FMuxy-f1bf8S~o@ z+#)d#KjjxKQ+|PzS7HPNB_@Y!Vo_K{VHJf{2rGgwf-izEf-iwDfiG?0|NIKu>|O;( zpbA0?RS-y-CBjdcg-sbH!e4^F41XE^@}f>=H~a5(H#>hjPTlz$Pxdi&o$qsU%=0(4 zv)-k5>Eo}*@AI46*}A27VEo%MI(sD3y@L#EgcbI*W!Pg5?_l5L@9$uRrME#pzmw<= z44l}>dVO-(Yi|GBmf=ItpLeqU#<5-OP3D@LGXt{MGlzjkfB}g^;D^8ufgcjQvx?yX zza{1!(R=u?d^EyGGG!AHSI!AHTzz{kFgfEWTu2qkv9jSu*(S(V+!(QO>v#smCc zPDe_BuQ|XDxu%>33zalZB8tSJLIJOUSHLUa6W|lz6W|kzL=GO62i0KEf47^DA7KC8 z=1k?8qq6v!pu{2AL&A1C4Ijl42Ko8tTFVp(^6QQj4f2o4vKK8VN5qcFgC%y{k1cIa zAf4KjR`ec5`3UR?>?rIg>=^9WB6)Ghi&N3BTE|!%RpU<8rcXhX66B)?*{SW$G|+gt z2B<@k!d74>U?)6v8y_5GH@d~M{Tt3-V7=|1ck^Goz}EHMu@2vnK|kxU@T6RTk(Yp{ z{B1qF>qWM%{Z2RUC+usp;>HGPfh;@;d`Vt4W2dZz8?)jGFTF_j%dar_BMWbL^9N5z z+-hM*4-cMTyRzvJ)uCfYJd492u~q3rp$eIhcuJ));x{A04PkCTfu#Ze=U8gZ*evvbE% zYho3U3P{CGCO{@YCZIYAe-eJGNSp8eQu%Jv)mR;2n;CScviv(rOLscUXBBqHwUJZE zl|rr*YNX*$!=IKl>=N%QY*qVK=XsdfB_}!u%mWJoTNC<8lFhhAsfa29OTe;sR5Ha3 zOm2R!T=@JxiG|(r#whH84Za|Fm%iOn-^?E1Uf@3H2A>0;1D^w*&mtg?01_e|coHIz zgh&Km08c`!wZXj6TOS0g5UfJ5iiV2di{Ojk`-%uCA)thS5&}y5_=jolOQa>q-e^=$ z=%acPp_}Rno9a2iR8%?B!JVRo^D7K;C1n62f% z9A<02;aZ~oGIhHYKecDHdU z$3AGo&e9jXGibOiHQbgOZc7cfsSaKTuY;#NGzE@aV?dmI^2#@GolSRbC~dVsG~Tc-_QB%8{K6`)EIA&V*rn+iz;Pc@_sz7D<)z7D<)o`dIo z2;f#gvva37&PWYqvzzA!)z{VI{cKxCAH}@aMv4Fk!EM$!H3$c1KO}^J!XA4?z+KD1e-yA zxYRmb>p?!_SUkwzD6v&pM;et7b4?{ALKZw^D}JVW`JGCS8n%{D$Mv)?TraK^owlH*=Y1BZbbi52h_@D=bC z!6Q%CkFLGO+TB@4kYyBj7C45i;49!O;46Z+vo@}MpEcU>h*UE?`@`0<)H3|1j-_Sz zU&ykzRK~hDoEEESp^6r&XrYQ0Yv60(Yv60(HSijE4ZH?k2d}S1fQ|s&Ge`X4%dIEJ z$na+_x0*3CJbIZOa?PKClm;>y$Y`LX3El*6f;T-C8*fn!`z)-E%GTp%3<^qNg5;4JbCC*g!`ncoVz{-W=n;*V%6OGNB}85~V1UL`b4c z!lq0T!J8gMjRUWsf7h{+Iey`)=hUMdqM=G_ z7ik?-<+rE7qIGut>?(V@1HbH~aK@ zFSu(peUr1Y^T~6B_9Z=e9?lCArw}w{1<_461%3+rH29ur1WY4f`U3y-y62UX>WQhL zdd^D$)ss#kj`Aaj>Ipy9lL(&b37g7E1V0Tv3qA|}kt_nTp6>aI8|*0cWB=eganVX= zuLI4^MV`JvuaI@?-`h9Xj;&7Yq=+S{z-eIClcVv)4fdt(UT0VZJET%@m*QRXEbK~N zQDtA1BCrJXE_rI6J@48^716aVpZ{T=&ALvPDk@h+<)*I&y9Qf>t-;n|>t3aScUi^t z<mFqiQ6_08Sr~!4) mW~SMj<)1FFjr{SO^epba$+o%PRt=0XfpyQ=#>`FDT>XCunI&KV diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/ebb-flow.otbm new file mode 100644 index 0000000000000000000000000000000000000000..5a8d5d16fefe7f43e2bd163cb1f86eac75068fc0 GIT binary patch literal 181627 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 Q|Mc-c{nN)k*zN%TKhH4k761SM literal 0 HcmV?d00001 diff --git a/data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm b/data-otservbr-global/world/quest/soul_war/ebb_and_flow/empty.otbm deleted file mode 100644 index 70036d063b14a9694c6166b1bd317cf887aeb285..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13957 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 HU=#R%=4!6# diff --git a/data/items/items.xml b/data/items/items.xml index 860a148fda2..8cec8f05ec6 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -62032,9 +62032,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> - - - + @@ -62476,6 +62474,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> + @@ -62891,28 +62890,25 @@ hands of its owner. Granted by TibiaRoyal.com"/> - + + + + - - - - - - - - - - - + + + + + - + @@ -63066,6 +63062,10 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + @@ -63161,6 +63161,11 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + + @@ -63180,6 +63185,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> + @@ -63379,6 +63385,10 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + @@ -63525,6 +63535,19 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + + + + + + + + + + + @@ -64262,7 +64285,7 @@ hands of its owner. Granted by TibiaRoyal.com"/> - + @@ -74750,12 +74773,31 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + + + + + + + + + @@ -74790,6 +74832,22 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + + + + + + @@ -74808,6 +74866,15 @@ Granted by TibiaGoals.com"/> + + + + + + + + + diff --git a/data/libs/functions/boss_lever.lua b/data/libs/functions/boss_lever.lua index b95bf7211b0..9cd577ee911 100644 --- a/data/libs/functions/boss_lever.lua +++ b/data/libs/functions/boss_lever.lua @@ -144,6 +144,7 @@ end ---@param player Player ---@return boolean function BossLever:onUse(player) + local monsterName = MonsterType(self.name):getName() local isParticipant = false for _, v in ipairs(self.playerPositions) do if Position(v.pos) == player:getPosition() then @@ -161,7 +162,7 @@ function BossLever:onUse(player) local zone = self:getZone() if zone:countPlayers(IgnoredByMonsters) > 0 then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. self.name .. ".") + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "There's already someone fighting with " .. monsterName .. ".") return true end @@ -173,14 +174,15 @@ function BossLever:onUse(player) return true end - if creature:getLevel() < self.requiredLevel then + local isAccountNormal = creature:getAccountType() == ACCOUNT_TYPE_NORMAL + if isAccountNormal and creature:getLevel() < self.requiredLevel then local message = "All players need to be level " .. self.requiredLevel .. " or higher." creature:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) player:sendTextMessage(MESSAGE_EVENT_ADVANCE, message) return false end - if creature:getGroup():getId() < GROUP_TYPE_GOD and self:lastEncounterTime(creature) > os.time() then + if creature:getGroup():getId() < GROUP_TYPE_GOD and isAccountNormal and self:lastEncounterTime(creature) > os.time() then local infoPositions = lever:getInfoPositions() for _, posInfo in pairs(infoPositions) do local currentPlayer = posInfo.creature diff --git a/data/libs/functions/creature.lua b/data/libs/functions/creature.lua index be95a45144c..376f828c5b0 100644 --- a/data/libs/functions/creature.lua +++ b/data/libs/functions/creature.lua @@ -208,7 +208,7 @@ end function Creature.getKillers(self, onlyPlayers) local killers = {} local inFightTicks = configManager.getNumber(configKeys.PZ_LOCKED) - local timeNow = os.mtime() + local timeNow = systemTime() local getCreature = onlyPlayers and Player or Creature for cid, cb in pairs(self:getDamageMap()) do local creature = getCreature(cid) diff --git a/data/libs/functions/monster.lua b/data/libs/functions/monster.lua index f26f2a5b9b4..0327daab3b3 100644 --- a/data/libs/functions/monster.lua +++ b/data/libs/functions/monster.lua @@ -204,7 +204,7 @@ do return table.contains(equipmentTypes, t) end - function MonsterType.getBossReward(self, lootFactor, topScore, equipmentOnly, lootTable) + function MonsterType.getBossReward(self, lootFactor, topScore, equipmentOnly, lootTable, player) if configManager.getNumber(configKeys.RATE_LOOT) <= 0 then return lootTable or {} end @@ -221,6 +221,6 @@ do end return true end, - }, lootTable) + }, lootTable, player) end end diff --git a/data/libs/functions/monstertype.lua b/data/libs/functions/monstertype.lua index 168cab13109..a9f6fff59f7 100644 --- a/data/libs/functions/monstertype.lua +++ b/data/libs/functions/monstertype.lua @@ -1,7 +1,7 @@ -- return a dictionary of itemId => { count, gut } ---@param config { factor: number, gut: boolean, filter?: fun(itemType: ItemType, unique: boolean): boolean } ---@return LootItems -function MonsterType:generateLootRoll(config, resultTable) +function MonsterType:generateLootRoll(config, resultTable, player) if configManager.getNumber(configKeys.RATE_LOOT) <= 0 then return resultTable or {} end @@ -28,6 +28,11 @@ function MonsterType:generateLootRoll(config, resultTable) end local chance = item.chance + if iType:getId() == SoulWarQuest.bagYouDesireItemId then + result[item.itemId].chance = self:calculateBagYouDesireChance(player, chance) + logger.debug("Final chance for bag you desire: {}, original chance: {}", result[item.itemId].chance, chance) + end + if config.gut and iType:getType() == ITEM_TYPE_CREATUREPRODUCT then chance = math.ceil((chance * GLOBAL_CHARM_GUT) / 100) end diff --git a/data/libs/functions/revscriptsys.lua b/data/libs/functions/revscriptsys.lua index 515522d6443..7026410c5e4 100644 --- a/data/libs/functions/revscriptsys.lua +++ b/data/libs/functions/revscriptsys.lua @@ -290,6 +290,14 @@ do self:eventType(MONSTERS_EVENT_SAY) self:onSay(value) return + elseif key == "onPlayerAttack" then + self:eventType(MONSTERS_EVENT_ATTACKED_BY_PLAYER) + self:onPlayerAttack(value) + return + elseif key == "onSpawn" then + self:eventType(MONSTERS_EVENT_ON_SPAWN) + self:onSpawn(value) + return end rawset(self, key, value) end diff --git a/data/libs/systems/zones.lua b/data/libs/systems/zones.lua index ff37af5b16a..698a464fe87 100644 --- a/data/libs/systems/zones.lua +++ b/data/libs/systems/zones.lua @@ -100,7 +100,7 @@ setmetatable(ZoneEvent, { function ZoneEvent:register() if self.beforeEnter then - local beforeEnter = EventCallback() + local beforeEnter = EventCallback("ZoneEventBeforeEnter", true) function beforeEnter.zoneBeforeCreatureEnter(zone, creature) if zone ~= self.zone then return true @@ -112,7 +112,7 @@ function ZoneEvent:register() end if self.beforeLeave then - local beforeLeave = EventCallback() + local beforeLeave = EventCallback("ZoneEventBeforeLeave", true) function beforeLeave.zoneBeforeCreatureLeave(zone, creature) if zone ~= self.zone then return true @@ -124,7 +124,7 @@ function ZoneEvent:register() end if self.afterEnter then - local afterEnter = EventCallback() + local afterEnter = EventCallback("ZoneEventAfterEnter", true) function afterEnter.zoneAfterCreatureEnter(zone, creature) if zone ~= self.zone then return true @@ -136,7 +136,7 @@ function ZoneEvent:register() end if self.afterLeave then - local afterLeave = EventCallback() + local afterLeave = EventCallback("ZoneEventAfterLeave", true) function afterLeave.zoneAfterCreatureLeave(zone, creature) if zone ~= self.zone then return true @@ -148,7 +148,7 @@ function ZoneEvent:register() end if self.onSpawn then - local afterEnter = EventCallback() + local afterEnter = EventCallback("ZoneEventAfterEnterOnSpawn", true) function afterEnter.zoneAfterCreatureEnter(zone, creature) if zone ~= self.zone then return true diff --git a/data/scripts/actions/items/cobra_flask.lua b/data/scripts/actions/items/cobra_flask.lua index 095a8c39a96..c36d42983c5 100644 --- a/data/scripts/actions/items/cobra_flask.lua +++ b/data/scripts/actions/items/cobra_flask.lua @@ -1,4 +1,4 @@ -local applyCobraFlaskEffectOnMonsterSpawn = EventCallback() +local applyCobraFlaskEffectOnMonsterSpawn = EventCallback("CobraFlaskEffectOnMonsterSpawn") applyCobraFlaskEffectOnMonsterSpawn.monsterOnSpawn = function(monster, position) if table.contains({ "cobra scout", "cobra vizier", "cobra assassin" }, monster:getName():lower()) then diff --git a/data/scripts/eventcallbacks/README.md b/data/scripts/eventcallbacks/README.md index ae5de046bd2..6e0bacfcd6e 100644 --- a/data/scripts/eventcallbacks/README.md +++ b/data/scripts/eventcallbacks/README.md @@ -18,6 +18,7 @@ Event callbacks are available for several categories of game entities, such as ` - `(ReturnValue)` `creatureOnTargetCombat` - `(void)` `creatureOnHear` - `(void)` `creatureOnDrainHealth` +- `(void)` `creatureOnCombat` - `(bool)` `partyOnJoin` - `(bool)` `partyOnLeave` - `(bool)` `partyOnDisband` @@ -62,7 +63,7 @@ Below are examples for each category of game entities: ### Creature Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.creatureOnAreaCombat(creature, tile, isAggressive) -- custom behavior when a creature enters combat area @@ -75,7 +76,7 @@ callback:register() ### Player Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.playerOnLook(player, position, thing, stackpos, lookDistance) -- custom behavior when a player looks at something @@ -87,7 +88,7 @@ callback:register() ### Party Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.partyOnJoin(party, player) -- custom behavior when a player joins a party @@ -99,7 +100,7 @@ callback:register() ### Monster Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.monsterOnSpawn(monster, position) -- custom behavior when a monster spawns @@ -111,7 +112,7 @@ callback:register() ### Npc Callback ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.npcOnSpawn(npc, position) -- custom behavior when a npc spawns @@ -129,7 +130,7 @@ If the callback returns `false`, the execution of the associated function on the Here is an example of a boolean event callback: ```lua -local callback = EventCallback() +local callback = EventCallback("UniqueCallbackName") function callback.playerOnMoveItem(player, item, count, fromPos, toPos, fromCylinder, toCylinder) if item:getId() == ITEM_PARCEL then @@ -180,7 +181,7 @@ Here is an example of defining multiple callbacks for the creatureOnAreaCombat e #### Example 1 ```lua -local example1 = EventCallback() +local example1 = EventCallback("UniqueCallbackName") function example1.creatureOnAreaCombat(creature, tile, isAggressive) -- custom behavior 1 when a creature enters combat area @@ -192,7 +193,7 @@ example1:register() #### Example 2 ```lua -local example2 = EventCallback() +local example2 = EventCallback("UniqueCallbackName") function example2.creatureOnAreaCombat(creature, tile, isAggressive) -- custom behavior 2 when a creature enters combat area diff --git a/data/scripts/eventcallbacks/creature/on_area_combat.lua b/data/scripts/eventcallbacks/creature/on_area_combat.lua index f68cc95ccad..cd8720b25ad 100644 --- a/data/scripts/eventcallbacks/creature/on_area_combat.lua +++ b/data/scripts/eventcallbacks/creature/on_area_combat.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("CreatureOnAreaCombatBaseEvent") function callback.creatureOnAreaCombat(creature, tile, isAggressive) return RETURNVALUE_NOERROR diff --git a/data/scripts/eventcallbacks/creature/on_hear.lua b/data/scripts/eventcallbacks/creature/on_hear.lua index 871f6456c9a..2954c81c8fb 100644 --- a/data/scripts/eventcallbacks/creature/on_hear.lua +++ b/data/scripts/eventcallbacks/creature/on_hear.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("CreatureOnHearBaseEvent") function callback.creatureOnHear(creature, speaker, words, type) end diff --git a/data/scripts/eventcallbacks/monster/on_spawn.lua b/data/scripts/eventcallbacks/monster/on_spawn.lua index e40778aafce..5a490a8edac 100644 --- a/data/scripts/eventcallbacks/monster/on_spawn.lua +++ b/data/scripts/eventcallbacks/monster/on_spawn.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnSpawnBase") function callback.monsterOnSpawn(monster, position) if not monster then diff --git a/data/scripts/eventcallbacks/monster/ondroploot__base.lua b/data/scripts/eventcallbacks/monster/ondroploot__base.lua index cb7bc4207e8..0f724be9f67 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot__base.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot__base.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootBaseEvent") function Player:canReceiveLoot() return self:getStamina() > 840 @@ -15,14 +15,14 @@ function callback.monsterOnDropLoot(monster, corpse) end local mType = monster:getType() if not mType then - logger.warning("monsterOnDropLoot: monster has no type") + logger.warn("monsterOnDropLoot: monster '{}' has no type", monster:getName()) return end local charm = player and player:getCharmMonsterType(CHARM_GUT) local gut = charm and charm:raceId() == mType:raceId() - local lootTable = mType:generateLootRoll({ factor = factor, gut = gut }, {}) + local lootTable = mType:generateLootRoll({ factor = factor, gut = gut }, {}, player) corpse:addLoot(lootTable) for _, item in ipairs(lootTable) do if item.gut then diff --git a/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua b/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua index 1d94033a572..3bb43256772 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_boosted.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootBoosted") function callback.monsterOnDropLoot(monster, corpse) if not monster or not corpse then @@ -22,7 +22,7 @@ function callback.monsterOnDropLoot(monster, corpse) local factor = 1.0 local msgSuffix = " (boosted loot)" - corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {})) + corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {}, player)) local existingSuffix = corpse:getAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX) or "" corpse:setAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX, existingSuffix .. msgSuffix) diff --git a/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua b/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua index d1fb6ad38b1..4305e60e086 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_gem_atelier.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootGemAtelier") function callback.monsterOnDropLoot(monster, corpse) if not monster or not corpse then diff --git a/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua b/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua index ddcaeaee782..78851d186e6 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_hazard.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootHazard") function callback.monsterOnDropLoot(monster, corpse) if not monster:hazard() then @@ -31,7 +31,7 @@ function callback.monsterOnDropLoot(monster, corpse) local lootTable = {} for _ = 1, rolls do - lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable) + lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable, player) end corpse:addLoot(lootTable) diff --git a/data/scripts/eventcallbacks/monster/ondroploot_prey.lua b/data/scripts/eventcallbacks/monster/ondroploot_prey.lua index 1f732f173d2..eb4657ccc4f 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_prey.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_prey.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootPrey") function callback.monsterOnDropLoot(monster, corpse) local player = Player(corpse:getCorpseOwner()) @@ -42,7 +42,7 @@ function callback.monsterOnDropLoot(monster, corpse) msgSuffix = msgSuffix .. " (active prey bonus)" end - corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {})) + corpse:addLoot(mType:generateLootRoll({ factor = factor, gut = false }, {}, player)) local existingSuffix = corpse:getAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX) or "" corpse:setAttribute(ITEM_ATTRIBUTE_LOOTMESSAGE_SUFFIX, existingSuffix .. msgSuffix) end diff --git a/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua b/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua index 20202c0b5cc..a4f3c67fe9c 100644 --- a/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua +++ b/data/scripts/eventcallbacks/monster/ondroploot_wealth_duplex.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterOnDropLootWealthDuplex") function callback.monsterOnDropLoot(monster, corpse) local player = Player(corpse:getCorpseOwner()) @@ -59,7 +59,7 @@ function callback.monsterOnDropLoot(monster, corpse) local lootTable = {} for _ = 1, rolls do - lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable) + lootTable = mType:generateLootRoll({ factor = factor, gut = false }, lootTable, player) end corpse:addLoot(lootTable) diff --git a/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua b/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua index 8214ac98ea1..4c1e53e6f77 100644 --- a/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua +++ b/data/scripts/eventcallbacks/monster/postdroploot_analyzer.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("MonsterPostDropLootAnalyzer") function callback.monsterPostDropLoot(monster, corpse) local player = Player(corpse:getCorpseOwner()) diff --git a/data/scripts/eventcallbacks/party/on_disband.lua b/data/scripts/eventcallbacks/party/on_disband.lua index 60838962395..93bb1578fd1 100644 --- a/data/scripts/eventcallbacks/party/on_disband.lua +++ b/data/scripts/eventcallbacks/party/on_disband.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PartyOnDisbandEventBaseEvent") function callback.partyOnDisband(party) local members = party:getMembers() diff --git a/data/scripts/eventcallbacks/player/on_browse_field.lua b/data/scripts/eventcallbacks/player/on_browse_field.lua index a4f00341202..3b674a608a7 100644 --- a/data/scripts/eventcallbacks/player/on_browse_field.lua +++ b/data/scripts/eventcallbacks/player/on_browse_field.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnBrowseFieldBaseEvent") function callback.playerOnBrowseField(player, position) return true diff --git a/data/scripts/eventcallbacks/player/on_look.lua b/data/scripts/eventcallbacks/player/on_look.lua index 022aebbcc36..6b4be92553a 100644 --- a/data/scripts/eventcallbacks/player/on_look.lua +++ b/data/scripts/eventcallbacks/player/on_look.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnLookBaseEvent") function callback.playerOnLook(player, thing, position, distance) local description = "You see " diff --git a/data/scripts/eventcallbacks/player/on_look_in_shop.lua b/data/scripts/eventcallbacks/player/on_look_in_shop.lua index bd96296624d..0e724009b5e 100644 --- a/data/scripts/eventcallbacks/player/on_look_in_shop.lua +++ b/data/scripts/eventcallbacks/player/on_look_in_shop.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnLookInShopBaseEvent") function callback.playerOnLookInShop(player, itemType, count) return true diff --git a/data/scripts/eventcallbacks/player/on_look_in_trade.lua b/data/scripts/eventcallbacks/player/on_look_in_trade.lua index 77711c5b95d..c82dd9905f0 100644 --- a/data/scripts/eventcallbacks/player/on_look_in_trade.lua +++ b/data/scripts/eventcallbacks/player/on_look_in_trade.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnLookInTradeBaseEvent") function callback.playerOnLookInTrade(player, partner, item, distance) player:sendTextMessage(MESSAGE_LOOK, "You see " .. item:getDescription(distance)) diff --git a/data/scripts/eventcallbacks/player/on_remove_count.lua b/data/scripts/eventcallbacks/player/on_remove_count.lua index 4629d0a1b12..f39438c24ba 100644 --- a/data/scripts/eventcallbacks/player/on_remove_count.lua +++ b/data/scripts/eventcallbacks/player/on_remove_count.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRemoveCountBaseEvent") function callback.playerOnRemoveCount(player, item) player:sendWaste(item:getId()) diff --git a/data/scripts/eventcallbacks/player/on_request_quest_line.lua b/data/scripts/eventcallbacks/player/on_request_quest_line.lua index c233279cfb8..ad5737d3246 100644 --- a/data/scripts/eventcallbacks/player/on_request_quest_line.lua +++ b/data/scripts/eventcallbacks/player/on_request_quest_line.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRequestQuestLineBaseEvent") function callback.playerOnRequestQuestLine(player, questId) player:sendQuestLine(questId) diff --git a/data/scripts/eventcallbacks/player/on_request_quest_log.lua b/data/scripts/eventcallbacks/player/on_request_quest_log.lua index b6bcbbc3a64..15cfbd34719 100644 --- a/data/scripts/eventcallbacks/player/on_request_quest_log.lua +++ b/data/scripts/eventcallbacks/player/on_request_quest_log.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRequestQuestLogBaseEvent") function callback.playerOnRequestQuestLog(player) player:sendQuestLog() diff --git a/data/scripts/eventcallbacks/player/on_rotate_item.lua b/data/scripts/eventcallbacks/player/on_rotate_item.lua index 3692fcdc655..61bb2a99b2a 100644 --- a/data/scripts/eventcallbacks/player/on_rotate_item.lua +++ b/data/scripts/eventcallbacks/player/on_rotate_item.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnRotateItemBaseEvent") function callback.playerOnRotateItem(player, item, position) if item:getActionId() == IMMOVABLE_ACTION_ID then diff --git a/data/scripts/eventcallbacks/player/on_storage_update.lua b/data/scripts/eventcallbacks/player/on_storage_update.lua index 8b3006b1d54..0f4233e51d2 100644 --- a/data/scripts/eventcallbacks/player/on_storage_update.lua +++ b/data/scripts/eventcallbacks/player/on_storage_update.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnStorageUpdateBaseEvent") function callback.playerOnStorageUpdate(player, key, value, oldValue, currentFrameTime) player:updateStorage(key, value, oldValue, currentFrameTime) diff --git a/data/scripts/eventcallbacks/player/on_trade_accept.lua b/data/scripts/eventcallbacks/player/on_trade_accept.lua index 3d1eba7171b..8425bdc10b3 100644 --- a/data/scripts/eventcallbacks/player/on_trade_accept.lua +++ b/data/scripts/eventcallbacks/player/on_trade_accept.lua @@ -1,4 +1,4 @@ -local callback = EventCallback() +local callback = EventCallback("PlayerOnTradeAcceptBaseEvent") function callback.playerOnTradeAccept(player, target, item, targetItem) player:closeForge() diff --git a/data/scripts/lib/quests.lua b/data/scripts/lib/quests.lua new file mode 100644 index 00000000000..08005e34ae2 --- /dev/null +++ b/data/scripts/lib/quests.lua @@ -0,0 +1,2 @@ +-- We need to register the variables beforehand to avoid accessing null values. +RegisterSoulWarBossesLevers() diff --git a/data/scripts/lib/register_lever_tables.lua b/data/scripts/lib/register_lever_tables.lua index 66a78f42a11..de3cba6649e 100644 --- a/data/scripts/lib/register_lever_tables.lua +++ b/data/scripts/lib/register_lever_tables.lua @@ -7,8 +7,8 @@ AscendingFerumbrasConfig = { centerRoom = Position(33392, 31473, 14), -- Center Room exitPosition = Position(33266, 31479, 14), -- Exit Position newPos = Position(33392, 31479, 14), -- Player Position on room - days = 3, + days = 5, range = 20, time = 60, -- time in minutes to remove the player - vortex = 23726, + vortex = 20121, } diff --git a/data/scripts/lib/register_monster_type.lua b/data/scripts/lib/register_monster_type.lua index e42bf4e6ca1..cfb0a6bfaaa 100644 --- a/data/scripts/lib/register_monster_type.lua +++ b/data/scripts/lib/register_monster_type.lua @@ -940,8 +940,8 @@ function readSpell(incomingLua, mtype) if incomingLua.effect then spell:setCombatEffect(incomingLua.effect) end - if incomingLua.shootEffect then - spell:setCombatShootEffect(incomingLua.shootEffect) + if incomingLua.shootEffect or incomingLua.shooteffect then + spell:setCombatShootEffect(incomingLua.shootEffect or incomingLua.shooteffect) end end diff --git a/data/scripts/lib/register_spells.lua b/data/scripts/lib/register_spells.lua index 49a5d7aec2f..8c549859640 100644 --- a/data/scripts/lib/register_spells.lua +++ b/data/scripts/lib/register_spells.lua @@ -393,6 +393,12 @@ AREA_RING1_BURST3 = { { 0, 0, 0, 1, 1, 1, 0, 0, 0 }, } +CrossBeamArea3X2 = { + { 1, 1, 1 }, + { 0, 1, 0 }, + { 0, 3, 0 }, +} + -- The numbered-keys represents the damage values, and their table -- contains the minimum and maximum number of rounds of those damage values. RANGE = { diff --git a/data/scripts/spells/healing/heal_malice.lua b/data/scripts/spells/healing/heal_malice.lua new file mode 100644 index 00000000000..67211a58341 --- /dev/null +++ b/data/scripts/spells/healing/heal_malice.lua @@ -0,0 +1,29 @@ +function onTargetCreature(creature, target) + if target:getName() == "Goshnar's Malice" then + logger.debug("Monster {} Healing {}", creature:getName(), target:getName()) + local min = 15000 + local max = 30000 + doTargetCombatHealth(target, target, COMBAT_HEALING, min, max) + end + return true +end + +local combat = Combat() +combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_MAGIC_RED) +combat:setParameter(COMBAT_PARAM_AGGRESSIVE, 0) +combat:setParameter(COMBAT_PARAM_DISPEL, CONDITION_PARALYZE) +combat:setArea(createCombatArea(AREA_CIRCLE3X3)) +combat:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature") + +local spell = Spell("instant") + +function spell.onCastSpell(creature, var) + return combat:execute(creature, var) +end + +spell:name("Heal Malice") +spell:words("#####462") +spell:blockWalls(true) +spell:needLearn(true) +spell:needDirection(true) +spell:register() diff --git a/data/scripts/systems/reward_chest.lua b/data/scripts/systems/reward_chest.lua index 384115af0b0..07ec9b512a9 100644 --- a/data/scripts/systems/reward_chest.lua +++ b/data/scripts/systems/reward_chest.lua @@ -103,9 +103,9 @@ function bossDeath.onDeath(creature, corpse, killer, mostDamageKiller, lastHitUn end local playerLoot = creature:generateGemAtelierLoot() - playerLoot = monsterType:getBossReward(lootFactor, _ == 1, false, playerLoot) + playerLoot = monsterType:getBossReward(lootFactor, _ == 1, false, playerLoot, player) for _ = 2, rolls do - playerLoot = monsterType:getBossReward(lootFactor, false, true, playerLoot) + playerLoot = monsterType:getBossReward(lootFactor, false, true, playerLoot, player) end -- Add droped items to reward container diff --git a/data/scripts/talkactions/gm/afk.lua b/data/scripts/talkactions/gm/afk.lua index 6167a2b6068..f5342362113 100644 --- a/data/scripts/talkactions/gm/afk.lua +++ b/data/scripts/talkactions/gm/afk.lua @@ -69,7 +69,7 @@ afkEffect:interval(5000) afkEffect:register() ------------------ Stop AFK Message when moves ------------------ -local callback = EventCallback() +local callback = EventCallback("PlayerOnWalk") function callback.playerOnWalk(player, creature, creaturePos, toPos) local isAfk = checkIsAFK(player:getId()) if isAfk.afk then diff --git a/data/scripts/talkactions/gm/distance_effect.lua b/data/scripts/talkactions/gm/distance_effect.lua new file mode 100644 index 00000000000..4c637972cb7 --- /dev/null +++ b/data/scripts/talkactions/gm/distance_effect.lua @@ -0,0 +1,37 @@ +local magicEffect = TalkAction("/distanceeffect") + +function magicEffect.onSay(player, words, param) + -- create log + logCommand(player, words, param) + + if param == "" then + player:sendCancelMessage("Command param required.") + return true + end + + local effect = tonumber(param) + if effect ~= nil and effect > 0 then + local playerPos = player:getPosition() + local direction = player:getDirection() + local targetPos = Position(playerPos.x, playerPos.y, playerPos.z) + + local distance = 7 + if direction == DIRECTION_NORTH then + targetPos.y = targetPos.y - distance + elseif direction == DIRECTION_EAST then + targetPos.x = targetPos.x + distance + elseif direction == DIRECTION_SOUTH then + targetPos.y = targetPos.y + distance + elseif direction == DIRECTION_WEST then + targetPos.x = targetPos.x - distance + end + + player:getPosition():sendDistanceEffect(targetPos, effect) + end + + return true +end + +magicEffect:separator(" ") +magicEffect:groupType("gamemaster") +magicEffect:register() diff --git a/data/scripts/talkactions/gm/position.lua b/data/scripts/talkactions/gm/position.lua index 1869b109c5f..dc95552d5c2 100644 --- a/data/scripts/talkactions/gm/position.lua +++ b/data/scripts/talkactions/gm/position.lua @@ -1,23 +1,5 @@ local position = TalkAction("/pos", "!pos") -local function extractCoordinates(input) - local patterns = { - -- table format - "{%s*x%s*=%s*(%d+)%s*,%s*y%s*=%s*(%d+)%s*,%s*z%s*=%s*(%d+)%s*}", - -- Position format - "Position%s*%((%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*%)", - -- x, y, z format - "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)", - } - - for _, pattern in ipairs(patterns) do - local x, y, z = string.match(input, pattern) - if x and y and z then - return tonumber(x), tonumber(y), tonumber(z) - end - end -end - function position.onSay(player, words, param) -- create log logCommand(player, words, param) @@ -28,18 +10,21 @@ function position.onSay(player, words, param) return end - local x, y, z = extractCoordinates(param) - if x and y and z then - local teleportPosition = Position(x, y, z) - local tile = Tile(teleportPosition) - if not tile then - player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid tile or position. Send a valid position.") - return - end - - player:teleportTo(teleportPosition) - else + local teleportPosition = param:toPosition() + if not teleportPosition then player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid position format. Use one of the following formats: \n/pos {x = ..., y = ..., z = ...}\n/pos Position(..., ..., ...)\n/pos x, y, z.") + return + end + + local tile = Tile(teleportPosition) + if not tile then + player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "Invalid tile or position. Send a valid position.") + return + end + + player:teleportTo(teleportPosition) + if not player:isInGhostMode() then + teleportPosition:sendMagicEffect(CONST_ME_TELEPORT) end end diff --git a/data/scripts/talkactions/god/add_condition.lua b/data/scripts/talkactions/god/add_condition.lua new file mode 100644 index 00000000000..7d92cbc0a09 --- /dev/null +++ b/data/scripts/talkactions/god/add_condition.lua @@ -0,0 +1,10 @@ +local talkaction = TalkAction("/testtaintconditions") + +function talkaction.onSay(player, words, param) + player:setTaintIcon() + return false +end + +talkaction:separator(" ") +talkaction:groupType("god") +talkaction:register() diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 6390d0699b3..80126ddac93 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -379,7 +379,7 @@ bool ConfigManager::reload() { } void ConfigManager::missingConfigWarning(const char* identifier) { - g_logger().warn("[{}]: Missing configuration for identifier: {}", __FUNCTION__, identifier); + g_logger().debug("[{}]: Missing configuration for identifier: {}", __FUNCTION__, identifier); } std::string ConfigManager::loadStringConfig(lua_State* L, const ConfigKey_t &key, const char* identifier, const std::string &defaultValue) { diff --git a/src/creatures/appearance/outfit/outfit.hpp b/src/creatures/appearance/outfit/outfit.hpp index 0d89a2c932d..c4d49a7f1a2 100644 --- a/src/creatures/appearance/outfit/outfit.hpp +++ b/src/creatures/appearance/outfit/outfit.hpp @@ -54,6 +54,16 @@ class Outfits { return outfits[sex]; } + std::shared_ptr getOutfitByName(PlayerSex_t sex, const std::string &name) const { + for (const auto &outfit : outfits[sex]) { + if (outfit->name == name) { + return outfit; + } + } + + return nullptr; + } + private: std::vector> outfits[PLAYERSEX_LAST + 1]; }; diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 8e3de0da1bd..d65d10e4e6f 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -12,6 +12,8 @@ #include "declarations.hpp" #include "creatures/combat/combat.hpp" #include "lua/creature/events.hpp" +#include "lua/callbacks/event_callback.hpp" +#include "lua/callbacks/events_callbacks.hpp" #include "creatures/players/wheel/player_wheel.hpp" #include "game/game.hpp" #include "game/scheduling/dispatcher.hpp" @@ -595,10 +597,15 @@ void Combat::CombatHealthFunc(std::shared_ptr caster, std::shared_ptr< targetPlayer = target->getPlayer(); } + g_logger().trace("[{}] (old) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); + g_callbacks().executeCallback(EventCallback_t::creatureOnCombat, &EventCallback::creatureOnCombat, caster, target, std::ref(damage)); + g_logger().trace("[{}] (new) eventcallback: 'creatureOnCombat', damage primary: '{}', secondary: '{}'", __FUNCTION__, damage.primary.value, damage.secondary.value); + if (attackerPlayer) { std::shared_ptr item = attackerPlayer->getWeapon(); damage = applyImbuementElementalDamage(attackerPlayer, item, damage); g_events().eventPlayerOnCombat(attackerPlayer, target, item, damage); + g_callbacks().executeCallback(EventCallback_t::playerOnCombat, &EventCallback::playerOnCombat, attackerPlayer, target, item, std::ref(damage)); if (targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) { if (damage.primary.type != COMBAT_HEALING) { @@ -624,6 +631,9 @@ void Combat::CombatHealthFunc(std::shared_ptr caster, std::shared_ptr< 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)); } + + // Monster type onPlayerAttack event + targetMonster->onAttackedByPlayer(attackerPlayer); } // Monster attacking player @@ -1157,7 +1167,9 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin if (CreatureVector* creatures = tile->getCreatures()) { const std::shared_ptr topCreature = tile->getTopCreature(); - for (auto &creature : *creatures) { + // A copy of the tile's creature list is made because modifications to this vector, such as adding or removing creatures through a Lua callback, may occur during the iteration within the for loop. + CreatureVector creaturesCopy = *creatures; + for (auto &creature : creaturesCopy) { if (params.targetCasterOrTopMost) { if (caster && caster->getTile() == tile) { if (creature != caster) { @@ -1212,7 +1224,9 @@ void Combat::CombatFunc(std::shared_ptr caster, const Position &origin if (CreatureVector* creatures = tile->getCreatures()) { const std::shared_ptr topCreature = tile->getTopCreature(); - for (auto &creature : *creatures) { + // A copy of the tile's creature list is made because modifications to this vector, such as adding or removing creatures through a Lua callback, may occur during the iteration within the for loop. + CreatureVector creaturesCopy = *creatures; + for (auto &creature : creaturesCopy) { if (params.targetCasterOrTopMost) { if (caster && caster->getTile() == tile) { if (creature != caster) { diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index c06d9a792d5..6784e8786f3 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -257,6 +257,8 @@ std::shared_ptr Condition::createCondition(ConditionId_t id, Conditio return std::make_shared(id, type, ticks, buff, subId); case CONDITION_BAKRAGORE: return std::make_shared(id, type, ticks, buff, subId, isPersistent); + case CONDITION_GOSHNARTAINT: + return std::make_shared(id, type, ticks, buff, subId); default: return nullptr; @@ -345,7 +347,14 @@ bool Condition::isRemovableOnDeath() const { return false; } - if (conditionType == CONDITION_SPELLCOOLDOWN || conditionType == CONDITION_SPELLGROUPCOOLDOWN || conditionType == CONDITION_MUTED) { + static const std::unordered_set nonRemovableConditions = { + CONDITION_SPELLCOOLDOWN, + CONDITION_SPELLGROUPCOOLDOWN, + CONDITION_MUTED, + CONDITION_GOSHNARTAINT + }; + + if (nonRemovableConditions.find(conditionType) != nonRemovableConditions.end()) { return false; } @@ -418,7 +427,26 @@ std::unordered_set ConditionGeneric::getIcons() const { case CONDITION_ROOTED: icons.insert(PlayerIcon::Rooted); break; - + case CONDITION_GOSHNARTAINT: + switch (subId) { + case 1: + icons.insert(PlayerIcon::GoshnarTaint1); + break; + case 2: + icons.insert(PlayerIcon::GoshnarTaint2); + break; + case 3: + icons.insert(PlayerIcon::GoshnarTaint3); + break; + case 4: + icons.insert(PlayerIcon::GoshnarTaint4); + break; + case 5: + icons.insert(PlayerIcon::GoshnarTaint5); + break; + default: + break; + } default: break; } diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index 6c15f6cf1fe..a5cd66a629f 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -1804,7 +1804,7 @@ void Creature::handleLostSummon(bool teleportSummons) { g_game().addMagicEffect(getPosition(), CONST_ME_POFF); } -int32_t Creature::getReflectPercent(CombatType_t combatType, bool useCharges /*= false*/) const { +double_t Creature::getReflectPercent(CombatType_t combatType, bool useCharges /*= false*/) const { try { return reflectPercent.at(combatTypeToIndex(combatType)); } catch (const std::out_of_range &e) { diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index 138e8b364c8..a274b49364c 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -255,6 +255,10 @@ class Creature : virtual public Thing, public SharedObject { return creatureIcons.at(key); } + bool hasIcon(const std::string &key) const { + return creatureIcons.contains(key); + } + void setIcon(const std::string &key, CreatureIcon icon) { creatureIcons[key] = icon; iconChanged(); @@ -609,7 +613,7 @@ class Creature : virtual public Thing, public SharedObject { * @param useCharges Indicates whether charges should be considered. * @return The reflection percentage for the specified combat type. */ - virtual int32_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const; + virtual double_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const; /** * @brief Retrieves the flat reflection value for a given combat type. @@ -707,6 +711,10 @@ class Creature : virtual public Thing, public SharedObject { return false; } + virtual bool isDead() const { + return false; + } + static constexpr int32_t mapWalkWidth = MAP_MAX_VIEW_PORT_X * 2 + 1; static constexpr int32_t mapWalkHeight = MAP_MAX_VIEW_PORT_Y * 2 + 1; static constexpr int32_t maxWalkCacheWidth = (mapWalkWidth - 1) / 2; diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp index 410349a67fb..9594a704eed 100644 --- a/src/creatures/creatures_definitions.hpp +++ b/src/creatures/creatures_definitions.hpp @@ -113,15 +113,11 @@ enum ConditionType_t : uint8_t { CONDITION_LESSERHEX = 31, CONDITION_INTENSEHEX = 32, CONDITION_GREATERHEX = 33, - CONDITION_GOSHNAR1 = 34, - CONDITION_GOSHNAR2 = 35, - CONDITION_GOSHNAR3 = 36, - CONDITION_GOSHNAR4 = 37, - CONDITION_GOSHNAR5 = 38, - CONDITION_BAKRAGORE = 39, + CONDITION_BAKRAGORE = 34, + CONDITION_GOSHNARTAINT = 35, // Need the last ever - CONDITION_COUNT = 39 + CONDITION_COUNT }; // constexpr definiting suppressible conditions @@ -492,12 +488,14 @@ enum BestiaryType_t : uint8_t { }; enum MonstersEvent_t : uint8_t { - MONSTERS_EVENT_NONE = 0, - MONSTERS_EVENT_THINK = 1, - MONSTERS_EVENT_APPEAR = 2, - MONSTERS_EVENT_DISAPPEAR = 3, - MONSTERS_EVENT_MOVE = 4, - MONSTERS_EVENT_SAY = 5, + MONSTERS_EVENT_NONE, + MONSTERS_EVENT_THINK, + MONSTERS_EVENT_APPEAR, + MONSTERS_EVENT_DISAPPEAR, + MONSTERS_EVENT_MOVE, + MONSTERS_EVENT_SAY, + MONSTERS_EVENT_ATTACKED_BY_PLAYER, + MONSTERS_EVENT_ON_SPAWN, }; enum NpcsEvent_t : uint8_t { diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 3ae73f8dc7c..1a364ece2ba 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -108,15 +108,57 @@ bool Monster::canWalkOnFieldType(CombatType_t combatType) const { } } -int32_t Monster::getReflectPercent(CombatType_t reflectType, bool useCharges) const { - int32_t result = Creature::getReflectPercent(reflectType, useCharges); +double_t Monster::getReflectPercent(CombatType_t reflectType, bool useCharges) const { + // Monster type reflect + auto result = Creature::getReflectPercent(reflectType, useCharges); + if (result != 0) { + g_logger().debug("[{}] before mtype reflect element {}, percent {}", __FUNCTION__, fmt::underlying(reflectType), result); + } auto it = mType->info.reflectMap.find(reflectType); if (it != mType->info.reflectMap.end()) { result += it->second; } + + if (result != 0) { + g_logger().debug("[{}] after mtype reflect element {}, percent {}", __FUNCTION__, fmt::underlying(reflectType), result); + } + + // Monster reflect + auto monsterReflectIt = m_reflectElementMap.find(reflectType); + if (monsterReflectIt != m_reflectElementMap.end()) { + result += monsterReflectIt->second; + } + + if (result != 0) { + g_logger().debug("[{}] (final) after monster reflect element {}, percent {}", __FUNCTION__, fmt::underlying(reflectType), result); + } + return result; } +void Monster::addReflectElement(CombatType_t combatType, int32_t percent) { + g_logger().debug("[{}] added reflect element {}, percent {}", __FUNCTION__, fmt::underlying(combatType), percent); + m_reflectElementMap[combatType] += percent; +} + +int32_t Monster::getDefense() const { + auto mtypeDefense = mType->info.defense; + if (mtypeDefense != 0) { + g_logger().trace("[{}] old defense {}", __FUNCTION__, mtypeDefense); + } + mtypeDefense += m_defense; + if (mtypeDefense != 0) { + g_logger().trace("[{}] new defense {}", __FUNCTION__, mtypeDefense); + } + return mtypeDefense * getDefenseMultiplier(); +} + +void Monster::addDefense(int32_t defense) { + g_logger().trace("[{}] adding defense {}", __FUNCTION__, defense); + m_defense += defense; + g_logger().trace("[{}] new defense {}", __FUNCTION__, m_defense); +} + uint32_t Monster::getHealingCombatValue(CombatType_t healingType) const { auto it = mType->info.healingMap.find(healingType); if (it != mType->info.healingMap.end()) { @@ -315,6 +357,57 @@ void Monster::onCreatureSay(std::shared_ptr creature, SpeakClasses typ } } +void Monster::onAttackedByPlayer(std::shared_ptr attackerPlayer) { + if (mType->info.monsterAttackedByPlayerEvent != -1) { + // onPlayerAttack(self, attackerPlayer) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " + "script calls being nested.", + getName(), this->getName()); + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.monsterAttackedByPlayerEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.monsterAttackedByPlayerEvent); + + LuaScriptInterface::pushUserdata(L, getMonster()); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, attackerPlayer); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + scriptInterface->callVoidFunction(2); + } +} + +void Monster::onSpawn() { + if (mType->info.spawnEvent != -1) { + // onSpawn(self) + LuaScriptInterface* scriptInterface = mType->info.scriptInterface; + if (!scriptInterface->reserveScriptEnv()) { + g_logger().error("Monster {} creature {}] Call stack overflow. Too many lua " + "script calls being nested.", + getName(), this->getName()); + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(mType->info.spawnEvent, scriptInterface); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(mType->info.spawnEvent); + + LuaScriptInterface::pushUserdata(L, getMonster()); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + scriptInterface->callVoidFunction(1); + } +} + void Monster::addFriend(const std::shared_ptr &creature) { if (creature == getMonster()) { g_logger().error("[{}]: adding creature is same of monster", __FUNCTION__); @@ -1921,6 +2014,8 @@ void Monster::death(std::shared_ptr) { if (mType) { g_game().sendSingleSoundEffect(static_self_cast()->getPosition(), mType->info.deathSound, getMonster()); } + + setDead(true); } std::shared_ptr Monster::getCorpse(std::shared_ptr lastHitCreature, std::shared_ptr mostDamageCreature) { @@ -2122,11 +2217,11 @@ bool Monster::changeTargetDistance(int32_t distance, uint32_t duration /* = 1200 } bool Monster::isImmune(ConditionType_t conditionType) const { - return mType->info.m_conditionImmunities[static_cast(conditionType)]; + return m_isImmune || mType->info.m_conditionImmunities[static_cast(conditionType)]; } bool Monster::isImmune(CombatType_t combatType) const { - return mType->info.m_damageImmunities[combatTypeToIndex(combatType)]; + return m_isImmune || mType->info.m_damageImmunities[combatTypeToIndex(combatType)]; } void Monster::getPathSearchParams(const std::shared_ptr &creature, FindPathParams &fpp) { diff --git a/src/creatures/monsters/monster.hpp b/src/creatures/monsters/monster.hpp index 061ad2b9879..155ba1e5c87 100644 --- a/src/creatures/monsters/monster.hpp +++ b/src/creatures/monsters/monster.hpp @@ -77,9 +77,9 @@ class Monster final : public Creature { int32_t getArmor() const override { return mType->info.armor * getDefenseMultiplier(); } - int32_t getDefense() const override { - return mType->info.defense * getDefenseMultiplier(); - } + int32_t getDefense() const override; + + void addDefense(int32_t defense); Faction_t getFaction() const override { auto master = getMaster(); @@ -134,9 +134,11 @@ class Monster final : public Creature { this->spawnMonster = newSpawnMonster; } - int32_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const override; + double_t getReflectPercent(CombatType_t combatType, bool useCharges = false) const override; uint32_t getHealingCombatValue(CombatType_t healingType) const; + void addReflectElement(CombatType_t combatType, int32_t percent); + bool canWalkOnFieldType(CombatType_t combatType) const; void onAttackedCreatureDisappear(bool isLogout) override; @@ -144,6 +146,8 @@ class Monster final : public Creature { void onRemoveCreature(std::shared_ptr creature, bool isLogout) override; void onCreatureMove(const std::shared_ptr &creature, const std::shared_ptr &newTile, const Position &newPos, const std::shared_ptr &oldTile, const Position &oldPos, bool teleport) override; void onCreatureSay(std::shared_ptr creature, SpeakClasses type, const std::string &text) override; + void onAttackedByPlayer(std::shared_ptr attackerPlayer); + void onSpawn(); void drainHealth(std::shared_ptr attacker, int32_t damage) override; void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; @@ -333,6 +337,12 @@ class Monster final : public Creature { bool isImmune(ConditionType_t conditionType) const override; bool isImmune(CombatType_t combatType) const override; + void setImmune(bool immune) { + m_isImmune = immune; + } + bool isImmune() const { + return m_isImmune; + } float getAttackMultiplier() const { float multiplier = mType->getAttackMultiplier(); @@ -347,6 +357,14 @@ class Monster final : public Creature { return multiplier * std::pow(1.02f, getForgeStack()); } + bool isDead() const override { + return m_isDead; + } + + void setDead(bool isDead) { + m_isDead = isDead; + } + private: auto getTargetIterator(const std::shared_ptr &creature) { return std::ranges::find_if(targetList.begin(), targetList.end(), [id = creature->getID()](const std::weak_ptr &ref) { @@ -372,6 +390,8 @@ class Monster final : public Creature { int64_t lastMeleeAttack = 0; + uint16_t totalPlayersOnScreen = 0; + uint32_t attackTicks = 0; uint32_t targetChangeTicks = 0; uint32_t defenseTicks = 0; @@ -385,8 +405,10 @@ class Monster final : public Creature { int32_t stepDuration = 0; int32_t targetDistance = 1; int32_t challengeMeleeDuration = 0; - uint16_t totalPlayersOnScreen = 0; int32_t runAwayHealth = 0; + int32_t m_defense = 0; + + std::unordered_map m_reflectElementMap; Position masterPos; @@ -402,6 +424,9 @@ class Monster final : public Creature { bool hazardDamageBoost = false; bool hazardDefenseBoost = false; + bool m_isDead = false; + bool m_isImmune = false; + void onCreatureEnter(std::shared_ptr creature); void onCreatureLeave(std::shared_ptr creature); void onCreatureFound(std::shared_ptr creature, bool pushFront = false); diff --git a/src/creatures/monsters/monsters.cpp b/src/creatures/monsters/monsters.cpp index 78358f69d87..7e192a79575 100644 --- a/src/creatures/monsters/monsters.cpp +++ b/src/creatures/monsters/monsters.cpp @@ -278,17 +278,34 @@ bool MonsterType::loadCallback(LuaScriptInterface* scriptInterface) { } info.scriptInterface = scriptInterface; - if (info.eventType == MONSTERS_EVENT_THINK) { - info.thinkEvent = id; - } else if (info.eventType == MONSTERS_EVENT_APPEAR) { - info.creatureAppearEvent = id; - } else if (info.eventType == MONSTERS_EVENT_DISAPPEAR) { - info.creatureDisappearEvent = id; - } else if (info.eventType == MONSTERS_EVENT_MOVE) { - info.creatureMoveEvent = id; - } else if (info.eventType == MONSTERS_EVENT_SAY) { - info.creatureSayEvent = id; + + switch (info.eventType) { + case MONSTERS_EVENT_THINK: + info.thinkEvent = id; + break; + case MONSTERS_EVENT_APPEAR: + info.creatureAppearEvent = id; + break; + case MONSTERS_EVENT_DISAPPEAR: + info.creatureDisappearEvent = id; + break; + case MONSTERS_EVENT_MOVE: + info.creatureMoveEvent = id; + break; + case MONSTERS_EVENT_SAY: + info.creatureSayEvent = id; + break; + case MONSTERS_EVENT_ATTACKED_BY_PLAYER: + info.monsterAttackedByPlayerEvent = id; + break; + case MONSTERS_EVENT_ON_SPAWN: + info.spawnEvent = id; + break; + default: + g_logger().error("[MonsterType::loadCallback] - Unknown event type"); + return false; } + return true; } diff --git a/src/creatures/monsters/monsters.hpp b/src/creatures/monsters/monsters.hpp index 171bef68e38..88d25e195c6 100644 --- a/src/creatures/monsters/monsters.hpp +++ b/src/creatures/monsters/monsters.hpp @@ -120,7 +120,9 @@ class MonsterType { int32_t creatureDisappearEvent = -1; int32_t creatureMoveEvent = -1; int32_t creatureSayEvent = -1; + int32_t monsterAttackedByPlayerEvent = -1; int32_t thinkEvent = -1; + int32_t spawnEvent = -1; int32_t targetDistance = 1; int32_t runAwayHealth = 0; int32_t health = 100; diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index ca8f4584775..846068e67b7 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -177,7 +177,7 @@ bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const return false; } } else { - g_logger().debug("[SpawnMonster] Spawning {} at {}", monsterType->name, sb.pos.toString()); + g_logger().trace("[SpawnMonster] Spawning {} at {}", monsterType->name, sb.pos.toString()); if (!g_game().placeCreature(monster, sb.pos, false, true)) { return false; } @@ -190,6 +190,7 @@ bool SpawnMonster::spawnMonster(uint32_t spawnMonsterId, spawnBlock_t &sb, const spawnedMonsterMap[spawnMonsterId] = monster; sb.lastSpawn = OTSYS_TIME(); g_events().eventMonsterOnSpawn(monster, sb.pos); + monster->onSpawn(); g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, sb.pos); return true; } diff --git a/src/creatures/players/grouping/party.cpp b/src/creatures/players/grouping/party.cpp index 76f1d955f10..b56e0c68605 100644 --- a/src/creatures/players/grouping/party.cpp +++ b/src/creatures/players/grouping/party.cpp @@ -454,7 +454,7 @@ void Party::shareExperience(uint64_t experience, std::shared_ptr targe uint64_t shareExperience = experience; g_events().eventPartyOnShareExperience(getParty(), shareExperience); - g_callbacks().executeCallback(EventCallback_t::partyOnShareExperience, &EventCallback::partyOnShareExperience, getParty(), shareExperience); + g_callbacks().executeCallback(EventCallback_t::partyOnShareExperience, &EventCallback::partyOnShareExperience, getParty(), std::ref(shareExperience)); for (const auto &member : getMembers()) { member->onGainSharedExperience(shareExperience, target); diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 9ba9f3b74d6..6f7e33eb7f9 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -677,7 +677,7 @@ void Player::addSkillAdvance(skills_t skill, uint64_t count) { } g_events().eventPlayerOnGainSkillTries(static_self_cast(), skill, count); - g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), skill, count); + g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), std::ref(skill), std::ref(count)); if (count == 0) { return; } @@ -2236,6 +2236,8 @@ void Player::onThink(uint32_t interval) { // Wheel of destiny major spells wheel()->onThink(); + + g_callbacks().executeCallback(EventCallback_t::playerOnThink, &EventCallback::playerOnThink, getPlayer(), interval); } uint32_t Player::isMuted() const { @@ -2369,7 +2371,7 @@ void Player::addExperience(std::shared_ptr target, uint64_t exp, bool return; } - g_callbacks().executeCallback(EventCallback_t::playerOnGainExperience, &EventCallback::playerOnGainExperience, getPlayer(), target, exp, rawExp); + g_callbacks().executeCallback(EventCallback_t::playerOnGainExperience, &EventCallback::playerOnGainExperience, getPlayer(), target, std::ref(exp), std::ref(rawExp)); g_events().eventPlayerOnGainExperience(static_self_cast(), target, exp, rawExp); if (exp == 0) { @@ -2485,7 +2487,7 @@ void Player::removeExperience(uint64_t exp, bool sendText /* = false*/) { } g_events().eventPlayerOnLoseExperience(static_self_cast(), exp); - g_callbacks().executeCallback(EventCallback_t::playerOnLoseExperience, &EventCallback::playerOnLoseExperience, getPlayer(), exp); + g_callbacks().executeCallback(EventCallback_t::playerOnLoseExperience, &EventCallback::playerOnLoseExperience, getPlayer(), std::ref(exp)); if (exp == 0) { return; } @@ -5564,15 +5566,15 @@ int32_t Player::getMagicShieldCapacityPercent(bool useCharges) const { return result; } -int32_t Player::getReflectPercent(CombatType_t combat, bool useCharges) const { - int32_t result = reflectPercent[combatTypeToIndex(combat)]; - for (const auto &item : getEquippedItems()) { +double_t Player::getReflectPercent(CombatType_t combat, bool useCharges) const { + double_t result = reflectPercent[combatTypeToIndex(combat)]; + for (const auto item : getEquippedItems()) { const ItemType &itemType = Item::items[item->getID()]; if (!itemType.abilities) { continue; } - int32_t reflectPercent = itemType.abilities->reflectPercent[combatTypeToIndex(combat)]; + double_t reflectPercent = itemType.abilities->reflectPercent[combatTypeToIndex(combat)]; if (reflectPercent != 0) { result += reflectPercent; uint16_t charges = item->getCharges(); @@ -5991,7 +5993,7 @@ bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) { oldPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; g_events().eventPlayerOnGainSkillTries(static_self_cast(), SKILL_MAGLEVEL, tries); - g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), SKILL_MAGLEVEL, tries); + g_callbacks().executeCallback(EventCallback_t::playerOnGainSkillTries, &EventCallback::playerOnGainSkillTries, getPlayer(), SKILL_MAGLEVEL, std::ref(tries)); uint32_t currMagLevel = magLevel; while ((manaSpent + tries) >= nextReqMana) { diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index 5a703a1d0bf..124ad59ed6b 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -417,7 +417,7 @@ class Player final : public Creature, public Cylinder, public Bankable { magicShieldCapacityPercent += value; } - int32_t getReflectPercent(CombatType_t combat, bool useCharges = false) const override; + double_t getReflectPercent(CombatType_t combat, bool useCharges = false) const override; int32_t getReflectFlat(CombatType_t combat, bool useCharges = false) const override; @@ -2944,7 +2944,7 @@ class Player final : public Creature, public Cylinder, public Bankable { bool marketMenu = false; // Menu option 'show in market' bool exerciseTraining = false; bool moved = false; - bool dead = false; + bool m_isDead = false; bool imbuementTrackerWindowOpen = false; // Hazard system @@ -3014,10 +3014,10 @@ class Player final : public Creature, public Cylinder, public Bankable { void getPathSearchParams(const std::shared_ptr &creature, FindPathParams &fpp) override; void setDead(bool isDead) { - dead = isDead; + m_isDead = isDead; } - bool isDead() const { - return dead; + bool isDead() const override { + return m_isDead; } void triggerMomentum(); diff --git a/src/enums/player_icons.hpp b/src/enums/player_icons.hpp index c289144bd7b..7878d9e5037 100644 --- a/src/enums/player_icons.hpp +++ b/src/enums/player_icons.hpp @@ -35,11 +35,11 @@ enum class PlayerIcon : uint8_t { GreaterHex = 18, Rooted = 19, Feared = 20, - Goshnar1 = 21, - Goshnar2 = 22, - Goshnar3 = 23, - Goshnar4 = 24, - Goshnar5 = 25, + GoshnarTaint1 = 21, + GoshnarTaint2 = 22, + GoshnarTaint3 = 23, + GoshnarTaint4 = 24, + GoshnarTaint5 = 25, NewManaShield = 26, Agony = 27, diff --git a/src/game/bank/bank.cpp b/src/game/bank/bank.cpp index d9a056396ce..b6f3b14c6e5 100644 --- a/src/game/bank/bank.cpp +++ b/src/game/bank/bank.cpp @@ -130,6 +130,10 @@ bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) } bool Bank::withdraw(std::shared_ptr player, uint64_t amount) { + if (!player) { + return false; + } + if (!debit(amount)) { return false; } diff --git a/src/game/game.cpp b/src/game/game.cpp index df1b401bf48..7b60220ecf9 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1165,6 +1165,7 @@ bool Game::removeCreature(std::shared_ptr creature, bool isLogout /* = size_t i = 0; for (const auto &spectator : playersSpectators) { if (const auto &player = spectator->getPlayer()) { + player->sendMagicEffect(tilePosition, CONST_ME_POFF); player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } @@ -1476,6 +1477,10 @@ ReturnValue Game::internalMoveCreature(std::shared_ptr creature, Direc return RETURNVALUE_NOTPOSSIBLE; } + if (creature->getBaseSpeed() == 0) { + return RETURNVALUE_NOTMOVABLE; + } + creature->setLastPosition(creature->getPosition()); const Position ¤tPos = creature->getPosition(); Position destPos = getNextPosition(direction, currentPos); @@ -7109,7 +7114,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt if (!isEvent) { g_events().eventCreatureOnDrainHealth(target, attacker, damage.primary.type, damage.primary.value, damage.secondary.type, damage.secondary.value, message.primary.color, message.secondary.color); - g_callbacks().executeCallback(EventCallback_t::creatureOnDrainHealth, &EventCallback::creatureOnDrainHealth, target, attacker, damage.primary.type, damage.primary.value, damage.secondary.type, damage.secondary.value, message.primary.color, message.secondary.color); + g_callbacks().executeCallback(EventCallback_t::creatureOnDrainHealth, &EventCallback::creatureOnDrainHealth, target, attacker, std::ref(damage.primary.type), std::ref(damage.primary.value), std::ref(damage.secondary.type), std::ref(damage.secondary.value), std::ref(message.primary.color), std::ref(message.secondary.color)); } if (damage.origin != ORIGIN_NONE && attacker && damage.primary.type != COMBAT_HEALING) { damage.primary.value *= attacker->getBuff(BUFF_DAMAGEDEALT) / 100.; @@ -7297,7 +7302,7 @@ bool Game::combatChangeHealth(std::shared_ptr attacker, std::shared_pt return true; } else if (realDamage >= targetHealth) { for (const auto &creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { - if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { + if (!creatureEvent->executeOnPrepareDeath(target, attacker, std::ref(realDamage))) { return false; } } diff --git a/src/game/zones/zone.cpp b/src/game/zones/zone.cpp index b6c1191aa78..7ffa4b594f4 100644 --- a/src/game/zones/zone.cpp +++ b/src/game/zones/zone.cpp @@ -15,6 +15,7 @@ #include "creatures/npcs/npc.hpp" #include "creatures/players/player.hpp" #include "utils/pugicast.hpp" +#include "kv/kv.hpp" phmap::parallel_flat_hash_map> Zone::zones = {}; phmap::parallel_flat_hash_map> Zone::zonesByID = {}; @@ -122,6 +123,10 @@ std::vector> Zone::getItems() { void Zone::removePlayers() { for (const auto &player : getPlayers()) { g_game().internalTeleport(player, getRemoveDestination(player)); + // Remove icon from player (soul war quest) + if (player->hasIcon("goshnars-hatred-damage")) { + player->removeIcon("goshnars-hatred-damage"); + } } } diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 764d4641469..63ca7939c46 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -829,7 +829,12 @@ void IOLoginDataLoad::loadPlayerForgeHistory(std::shared_ptr player, DBR } void IOLoginDataLoad::loadPlayerBosstiary(std::shared_ptr player, DBResult_ptr result) { - if (!result || !player) { + if (!result) { + g_logger().warn("[IOLoginData::loadPlayer] - Result nullptr: {}", __FUNCTION__); + return; + } + + if (!player) { g_logger().warn("[IOLoginData::loadPlayer] - Player or Result nullptr: {}", __FUNCTION__); return; } diff --git a/src/io/io_bosstiary.cpp b/src/io/io_bosstiary.cpp index 2f625cdb7cc..7c7c8108ce7 100644 --- a/src/io/io_bosstiary.cpp +++ b/src/io/io_bosstiary.cpp @@ -249,6 +249,7 @@ std::vector IOBosstiary::getBosstiaryFinished(const std::shared_ptrflush(); - g_logger().info("Map Loaded {} ({}x{}) in {} milliseconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); + g_logger().debug("Map Loaded {} ({}x{}) in {} milliseconds", map->path.filename().string(), map->width, map->height, bm_mapLoad.duration()); } void IOMap::parseMapDataAttributes(FileStream &stream, Map* map) { diff --git a/src/lua/callbacks/callbacks_definitions.hpp b/src/lua/callbacks/callbacks_definitions.hpp index 3b8016f5f5b..6c19cc809c9 100644 --- a/src/lua/callbacks/callbacks_definitions.hpp +++ b/src/lua/callbacks/callbacks_definitions.hpp @@ -25,6 +25,7 @@ enum class EventCallback_t : uint16_t { creatureOnTargetCombat, creatureOnHear, creatureOnDrainHealth, + creatureOnCombat, // Party partyOnJoin, partyOnLeave, @@ -57,6 +58,7 @@ enum class EventCallback_t : uint16_t { playerOnInventoryUpdate, playerOnRotateItem, playerOnWalk, + playerOnThink, // Monster monsterOnDropLoot, monsterPostDropLoot, @@ -68,4 +70,5 @@ enum class EventCallback_t : uint16_t { zoneBeforeCreatureLeave, zoneAfterCreatureEnter, zoneAfterCreatureLeave, + mapOnLoad, }; diff --git a/src/lua/callbacks/event_callback.cpp b/src/lua/callbacks/event_callback.cpp index 38e7654d8d5..8280bad6d2b 100644 --- a/src/lua/callbacks/event_callback.cpp +++ b/src/lua/callbacks/event_callback.cpp @@ -25,8 +25,16 @@ * * @see Script */ -EventCallback::EventCallback(LuaScriptInterface* scriptInterface) : - Script(scriptInterface) { +EventCallback::EventCallback(LuaScriptInterface* scriptInterface, const std::string &callbackName, bool skipDuplicationCheck) : + Script(scriptInterface), m_callbackName(callbackName), m_skipDuplicationCheck(skipDuplicationCheck) { +} + +std::string EventCallback::getName() const { + return m_callbackName; +} + +bool EventCallback::skipDuplicationCheck() const { + return m_skipDuplicationCheck; } std::string EventCallback::getScriptTypeName() const { @@ -225,6 +233,58 @@ void EventCallback::creatureOnDrainHealth(std::shared_ptr creature, st getScriptInterface()->resetScriptEnv(); } +void EventCallback::creatureOnCombat(std::shared_ptr attacker, std::shared_ptr target, CombatDamage &damage) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[{} - " + "Creature {} target {}] " + "Call stack overflow. Too many lua script calls being nested.", + __FUNCTION__, attacker->getName(), target->getName()); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushUserdata(L, attacker); + LuaScriptInterface::setCreatureMetatable(L, -1, attacker); + + LuaScriptInterface::pushUserdata(L, target); + LuaScriptInterface::setCreatureMetatable(L, -1, target); + + LuaScriptInterface::pushCombatDamage(L, damage); + + if (getScriptInterface()->protectedCall(L, 7, 4) != 0) { + LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); + } else { + damage.primary.value = std::abs(LuaScriptInterface::getNumber(L, -4)); + damage.primary.type = LuaScriptInterface::getNumber(L, -3); + damage.secondary.value = std::abs(LuaScriptInterface::getNumber(L, -2)); + damage.secondary.type = LuaScriptInterface::getNumber(L, -1); + + lua_pop(L, 4); + if (damage.primary.type != COMBAT_HEALING) { + damage.primary.value = -damage.primary.value; + damage.secondary.value = -damage.secondary.value; + } + /* + Only EK with dealing physical damage will get elemental damage on skill + */ + if (damage.origin == ORIGIN_SPELL && attacker) { + const auto &player = attacker->getPlayer(); + if (player && player->getVocationId() != 4 && player->getVocationId() != 8) { + damage.primary.value = damage.primary.value + damage.secondary.value; + damage.secondary.type = COMBAT_NONE; + damage.secondary.value = 0; + } + } + } + + getScriptInterface()->resetScriptEnv(); +} + // Party bool EventCallback::partyOnJoin(std::shared_ptr party, std::shared_ptr player) const { if (!getScriptInterface()->reserveScriptEnv()) { @@ -852,7 +912,7 @@ void EventCallback::playerOnCombat(std::shared_ptr player, std::shared_p if (target) { LuaScriptInterface::pushUserdata(L, target); - LuaScriptInterface::setMetatable(L, -1, "Creature"); + LuaScriptInterface::setCreatureMetatable(L, -1, target); } else { lua_pushnil(L); } @@ -1034,6 +1094,26 @@ void EventCallback::playerOnStorageUpdate(std::shared_ptr player, const getScriptInterface()->callVoidFunction(5); } +void EventCallback::playerOnThink(std::shared_ptr player, uint32_t interval) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[{}] player {}. Call stack overflow. Too many lua script calls being nested.", __FUNCTION__, player->getName()); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + lua_pushnumber(L, interval); + + getScriptInterface()->callVoidFunction(2); +} + // Monster void EventCallback::monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const { if (!getScriptInterface()->reserveScriptEnv()) { @@ -1235,3 +1315,22 @@ void EventCallback::zoneAfterCreatureLeave(std::shared_ptr zone, std::shar getScriptInterface()->callVoidFunction(2); } + +void EventCallback::mapOnLoad(const std::string &mapFullPath) const { + if (!getScriptInterface()->reserveScriptEnv()) { + g_logger().error("[{} - " + "Call stack overflow. Too many lua script calls being nested.", + __FUNCTION__); + return; + } + + ScriptEnvironment* scriptEnvironment = getScriptInterface()->getScriptEnv(); + scriptEnvironment->setScriptId(getScriptId(), getScriptInterface()); + + lua_State* L = getScriptInterface()->getLuaState(); + getScriptInterface()->pushFunction(getScriptId()); + + LuaScriptInterface::pushString(L, mapFullPath); + + getScriptInterface()->callVoidFunction(1); +} diff --git a/src/lua/callbacks/event_callback.hpp b/src/lua/callbacks/event_callback.hpp index 9141235a028..9e65480c246 100644 --- a/src/lua/callbacks/event_callback.hpp +++ b/src/lua/callbacks/event_callback.hpp @@ -35,13 +35,27 @@ class EventCallback : public Script { private: EventCallback_t m_callbackType = EventCallback_t::none; ///< The type of the event callback. std::string m_scriptTypeName; ///< The name associated with the script type. + std::string m_callbackName; ///< The name of the callback. + bool m_skipDuplicationCheck = false; ///< Whether the callback is silent error for already registered log error. public: /** * @brief Constructor that initializes the EventCallback with a given script interface. * @param scriptInterface Pointer to the LuaScriptInterface object. */ - explicit EventCallback(LuaScriptInterface* scriptInterface); + explicit EventCallback(LuaScriptInterface* scriptInterface, const std::string &callbackName, bool silentAlreadyRegistered); + + /** + * @brief Retrieves the callback name. + * @return The callback name as a string. + */ + std::string getName() const; + + /** + * @brief Retrieves the skip registration status of the callback. + * @return True if the callback is true for skip duplication check and register again the event, false otherwise. + */ + bool skipDuplicationCheck() const; /** * @brief Retrieves the script type name. @@ -84,6 +98,7 @@ class EventCallback : public Script { ReturnValue creatureOnTargetCombat(std::shared_ptr creature, std::shared_ptr target) const; void creatureOnHear(std::shared_ptr creature, std::shared_ptr speaker, const std::string &words, SpeakClasses type) const; void creatureOnDrainHealth(std::shared_ptr creature, std::shared_ptr attacker, CombatType_t &typePrimary, int32_t &damagePrimary, CombatType_t &typeSecondary, int32_t &damageSecondary, TextColor_t &colorPrimary, TextColor_t &colorSecondary) const; + void creatureOnCombat(std::shared_ptr attacker, std::shared_ptr target, CombatDamage &damage) const; // Party bool partyOnJoin(std::shared_ptr party, std::shared_ptr player) const; @@ -117,6 +132,7 @@ class EventCallback : public Script { void playerOnInventoryUpdate(std::shared_ptr player, std::shared_ptr item, Slots_t slot, bool equip) const; bool playerOnRotateItem(std::shared_ptr player, std::shared_ptr item, const Position &position) const; void playerOnWalk(std::shared_ptr player, Direction &dir) const; + void playerOnThink(std::shared_ptr player, uint32_t interval) const; // Monster void monsterOnDropLoot(std::shared_ptr monster, std::shared_ptr corpse) const; @@ -132,7 +148,5 @@ class EventCallback : public Script { void zoneAfterCreatureEnter(std::shared_ptr zone, std::shared_ptr creature) const; void zoneAfterCreatureLeave(std::shared_ptr zone, std::shared_ptr creature) const; - /** - * @note here end the lua binder functions } - */ + void mapOnLoad(const std::string &mapFullPath) const; }; diff --git a/src/lua/callbacks/events_callbacks.cpp b/src/lua/callbacks/events_callbacks.cpp index 4a1830f80fb..13a42baa15a 100644 --- a/src/lua/callbacks/events_callbacks.cpp +++ b/src/lua/callbacks/events_callbacks.cpp @@ -12,6 +12,8 @@ #include "lua/callbacks/events_callbacks.hpp" #include "lua/callbacks/event_callback.hpp" +#include "game/game.hpp" +#include "lib/di/container.hpp" /** * @class EventsCallbacks @@ -28,22 +30,37 @@ EventsCallbacks &EventsCallbacks::getInstance() { return inject(); } -void EventsCallbacks::addCallback(const std::shared_ptr callback) { - m_callbacks.push_back(callback); +bool EventsCallbacks::isCallbackRegistered(const std::shared_ptr &callback) { + if (g_game().getGameState() == GAME_STATE_STARTUP && !callback->skipDuplicationCheck() && m_callbacks.find(callback->getName()) != m_callbacks.end()) { + return true; + } + + return false; +} + +void EventsCallbacks::addCallback(const std::shared_ptr &callback) { + if (m_callbacks.find(callback->getName()) != m_callbacks.end() && !callback->skipDuplicationCheck()) { + g_logger().trace("Event callback already registered: {}", callback->getName()); + return; + } + + g_logger().trace("Registering event callback: {}", callback->getName()); + + m_callbacks[callback->getName()] = callback; } -std::vector> EventsCallbacks::getCallbacks() const { +std::unordered_map> EventsCallbacks::getCallbacks() const { return m_callbacks; } -std::vector> EventsCallbacks::getCallbacksByType(EventCallback_t type) const { - std::vector> eventCallbacks; - for (auto callback : getCallbacks()) { +std::unordered_map> EventsCallbacks::getCallbacksByType(EventCallback_t type) const { + std::unordered_map> eventCallbacks; + for (auto [name, callback] : getCallbacks()) { if (callback->getType() != type) { continue; } - eventCallbacks.push_back(callback); + eventCallbacks[name] = callback; } return eventCallbacks; diff --git a/src/lua/callbacks/events_callbacks.hpp b/src/lua/callbacks/events_callbacks.hpp index f71103047e6..dff5cec30c5 100644 --- a/src/lua/callbacks/events_callbacks.hpp +++ b/src/lua/callbacks/events_callbacks.hpp @@ -45,24 +45,35 @@ class EventsCallbacks { */ static EventsCallbacks &getInstance(); + /** + * @brief Checks if an event callback is already registered. + * + * @details Determines if the game state is at startup and if a callback with the same name already exists. + * @details If both conditions are met, logs an error and indicates the callback is already registered. + * + * @param callback Shared pointer to the event callback being checked. + * @return True if the callback already exists during the game startup state, otherwise false. + */ + bool isCallbackRegistered(const std::shared_ptr &callback); + /** * @brief Adds a new event callback to the list. * @param callback Pointer to the EventCallback object to add. */ - void addCallback(const std::shared_ptr callback); + void addCallback(const std::shared_ptr &callback); /** * @brief Gets all registered event callbacks. * @return Vector of pointers to EventCallback objects. */ - std::vector> getCallbacks() const; + std::unordered_map> getCallbacks() const; /** * @brief Gets event callbacks by their type. * @param type The type of callbacks to retrieve. * @return Vector of pointers to EventCallback objects of the specified type. */ - std::vector> getCallbacksByType(EventCallback_t type) const; + std::unordered_map> getCallbacksByType(EventCallback_t type) const; /** * @brief Clears all registered event callbacks. @@ -77,7 +88,7 @@ class EventsCallbacks { */ template void executeCallback(EventCallback_t eventType, CallbackFunc callbackFunc, Args &&... args) { - for (const auto &callback : getCallbacksByType(eventType)) { + for (const auto &[name, callback] : getCallbacksByType(eventType)) { auto argsCopy = std::make_tuple(args...); if (callback && callback->isLoadedCallback()) { std::apply( @@ -86,6 +97,7 @@ class EventsCallbacks { }, argsCopy ); + g_logger().trace("Executed callback: {}", name); } } } @@ -99,7 +111,7 @@ class EventsCallbacks { template ReturnValue checkCallbackWithReturnValue(EventCallback_t eventType, CallbackFunc callbackFunc, Args &&... args) { ReturnValue res = RETURNVALUE_NOERROR; - for (const auto &callback : getCallbacksByType(eventType)) { + for (const auto &[name, callback] : getCallbacksByType(eventType)) { auto argsCopy = std::make_tuple(args...); if (callback && callback->isLoadedCallback()) { ReturnValue callbackResult = std::apply( @@ -127,7 +139,7 @@ class EventsCallbacks { bool checkCallback(EventCallback_t eventType, CallbackFunc callbackFunc, Args &&... args) { bool allCallbacksSucceeded = true; - for (const auto &callback : getCallbacksByType(eventType)) { + for (const auto &[name, callback] : getCallbacksByType(eventType)) { auto argsCopy = std::make_tuple(args...); if (callback && callback->isLoadedCallback()) { bool callbackResult = std::apply( @@ -144,7 +156,7 @@ class EventsCallbacks { private: // Container for storing registered event callbacks. - std::vector> m_callbacks; + std::unordered_map> m_callbacks; }; constexpr auto g_callbacks = EventsCallbacks::getInstance; diff --git a/src/lua/creature/creatureevent.cpp b/src/lua/creature/creatureevent.cpp index 09dbdf086ce..90d09d2b7f9 100644 --- a/src/lua/creature/creatureevent.cpp +++ b/src/lua/creature/creatureevent.cpp @@ -231,7 +231,7 @@ bool CreatureEvent::executeOnThink(std::shared_ptr creature, uint32_t return getScriptInterface()->callFunction(2); } -bool CreatureEvent::executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer) const { +bool CreatureEvent::executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer, int realDamage) const { // onPrepareDeath(creature, killer) if (!getScriptInterface()->reserveScriptEnv()) { g_logger().error("[CreatureEvent::executeOnPrepareDeath - Creature {} killer {}" @@ -257,7 +257,9 @@ bool CreatureEvent::executeOnPrepareDeath(std::shared_ptr creature, st lua_pushnil(L); } - return getScriptInterface()->callFunction(2); + lua_pushnumber(L, realDamage); + + return getScriptInterface()->callFunction(3); } bool CreatureEvent::executeOnDeath(std::shared_ptr creature, std::shared_ptr corpse, std::shared_ptr killer, std::shared_ptr mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) const { diff --git a/src/lua/creature/creatureevent.hpp b/src/lua/creature/creatureevent.hpp index 8a208343e11..e34bd8eb75e 100644 --- a/src/lua/creature/creatureevent.hpp +++ b/src/lua/creature/creatureevent.hpp @@ -46,7 +46,7 @@ class CreatureEvent final : public Script { bool executeOnLogin(std::shared_ptr player) const; bool executeOnLogout(std::shared_ptr player) const; bool executeOnThink(std::shared_ptr creature, uint32_t interval) const; - bool executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer) const; + bool executeOnPrepareDeath(std::shared_ptr creature, std::shared_ptr killer, int realDamage) const; bool executeOnDeath(std::shared_ptr creature, std::shared_ptr corpse, std::shared_ptr killer, std::shared_ptr mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) const; void executeOnKill(std::shared_ptr creature, std::shared_ptr target, bool lastHit) const; bool executeAdvance(std::shared_ptr player, skills_t, uint32_t, uint32_t) const; diff --git a/src/lua/creature/events.cpp b/src/lua/creature/events.cpp index 8bdd53465b3..144c188e900 100644 --- a/src/lua/creature/events.cpp +++ b/src/lua/creature/events.cpp @@ -1159,7 +1159,7 @@ void Events::eventPlayerOnCombat(std::shared_ptr player, std::shared_ptr if (target) { LuaScriptInterface::pushUserdata(L, target); - LuaScriptInterface::setMetatable(L, -1, "Creature"); + LuaScriptInterface::setCreatureMetatable(L, -1, target); } else { lua_pushnil(L); } diff --git a/src/lua/functions/core/game/bank_functions.cpp b/src/lua/functions/core/game/bank_functions.cpp index f6732b8bf70..cc76bf4c091 100644 --- a/src/lua/functions/core/game/bank_functions.cpp +++ b/src/lua/functions/core/game/bank_functions.cpp @@ -81,6 +81,7 @@ int BankFunctions::luaBankTransferToGuild(lua_State* L) { reportErrorFunc("Source is nullptr"); return 1; } + std::shared_ptr destination = getBank(L, 2, true /* isGuild */); if (destination == nullptr) { reportErrorFunc("Destination is nullptr"); diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index 83ecd091850..10a6379781a 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -449,6 +449,7 @@ int GameFunctions::luaGameCreateMonster(lua_State* L) { if (g_game().placeCreature(monster, position, extended, force)) { g_events().eventMonsterOnSpawn(monster, position); g_callbacks().executeCallback(EventCallback_t::monsterOnSpawn, &EventCallback::monsterOnSpawn, monster, position); + monster->onSpawn(); const auto &mtype = monster->getMonsterType(); if (mtype && mtype->info.raceid > 0 && mtype->info.bosstiaryRace == BosstiaryRarity_t::RARITY_ARCHFOE) { for (const auto &spectator : Spectators().find(monster->getPosition(), true)) { diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 05fc72c019f..e4dc252be0d 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -1206,11 +1206,9 @@ void LuaEnums::initReloadTypeEnums(lua_State* L) { void LuaEnums::initCreaturesEventEnums(lua_State* L) { // Monsters - registerEnum(L, MONSTERS_EVENT_THINK); - registerEnum(L, MONSTERS_EVENT_APPEAR); - registerEnum(L, MONSTERS_EVENT_DISAPPEAR); - registerEnum(L, MONSTERS_EVENT_MOVE); - registerEnum(L, MONSTERS_EVENT_SAY); + for (auto value : magic_enum::enum_values()) { + registerMagicEnum(L, value); + } // Npcs registerEnum(L, NPCS_EVENT_THINK); diff --git a/src/lua/functions/core/game/zone_functions.cpp b/src/lua/functions/core/game/zone_functions.cpp index 5471d01895a..0f19c94f789 100644 --- a/src/lua/functions/core/game/zone_functions.cpp +++ b/src/lua/functions/core/game/zone_functions.cpp @@ -136,7 +136,7 @@ int ZoneFunctions::luaZoneGetCreatures(lua_State* L) { for (auto creature : creatures) { index++; pushUserdata(L, creature); - setMetatable(L, -1, "Creature"); + setCreatureMetatable(L, -1, creature); lua_rawseti(L, -2, index); } return 1; diff --git a/src/lua/functions/creatures/monster/monster_functions.cpp b/src/lua/functions/creatures/monster/monster_functions.cpp index 5578477d6ba..917e90f85f4 100644 --- a/src/lua/functions/creatures/monster/monster_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_functions.cpp @@ -60,7 +60,8 @@ int MonsterFunctions::luaMonsterGetType(lua_State* L) { } int MonsterFunctions::luaMonsterSetType(lua_State* L) { - // monster:setType(name or raceid) + // monster:setType(name or raceid, restoreHealth = false) + bool restoreHealth = getBoolean(L, 3, false); std::shared_ptr monster = getUserdataShared(L, 1); if (monster) { std::shared_ptr mType = nullptr; @@ -81,8 +82,14 @@ int MonsterFunctions::luaMonsterSetType(lua_State* L) { monster->defaultOutfit = mType->info.outfit; monster->currentOutfit = mType->info.outfit; monster->skull = mType->info.skull; - monster->health = mType->info.health * mType->getHealthMultiplier(); - monster->healthMax = mType->info.healthMax * mType->getHealthMultiplier(); + if (restoreHealth) { + auto multiplier = mType->getHealthMultiplier(); + monster->health = mType->info.health * multiplier; + monster->healthMax = mType->info.healthMax * multiplier; + } else { + monster->health = monster->getHealth(); + monster->healthMax = monster->getMaxHealth(); + } monster->baseSpeed = mType->getBaseSpeed(); monster->internalLight = mType->info.light; monster->hiddenHealth = mType->info.hiddenHealth; @@ -630,3 +637,76 @@ int MonsterFunctions::luaMonsterHazardDefenseBoost(lua_State* L) { } return 1; } + +int MonsterFunctions::luaMonsterAddReflectElement(lua_State* L) { + // monster:addReflectElement(type, percent) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + CombatType_t element = getNumber(L, 2); + monster->addReflectElement(element, getNumber(L, 3)); + pushBoolean(L, true); + return 1; +} + +int MonsterFunctions::luaMonsterAddDefense(lua_State* L) { + // monster:addDefense(defense) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + monster->addDefense(getNumber(L, 2)); + pushBoolean(L, true); + return 1; +} + +int MonsterFunctions::luaMonsterGetDefense(lua_State* L) { + // monster:getDefense(defense) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + lua_pushnumber(L, monster->getDefense()); + return 1; +} + +int MonsterFunctions::luaMonsterIsDead(lua_State* L) { + // monster:isDead() + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + pushBoolean(L, monster->isDead()); + return 1; +} + +int MonsterFunctions::luaMonsterImmune(lua_State* L) { + // to get: isImmune = monster:immune() + // to set and get: newImmuneBool = monster:immune(newImmuneBool) + const auto &monster = getUserdataShared(L, 1); + if (!monster) { + reportErrorFunc(getErrorDesc(LUA_ERROR_MONSTER_NOT_FOUND)); + pushBoolean(L, false); + return 0; + } + + if (lua_gettop(L) > 1) { + monster->setImmune(getBoolean(L, 2)); + } + + pushBoolean(L, monster->isImmune()); + return 1; +} diff --git a/src/lua/functions/creatures/monster/monster_functions.hpp b/src/lua/functions/creatures/monster/monster_functions.hpp index dd1c3827344..90f2b526ca4 100644 --- a/src/lua/functions/creatures/monster/monster_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_functions.hpp @@ -64,6 +64,13 @@ class MonsterFunctions final : LuaScriptInterface { registerMethod(L, "Monster", "hazardDamageBoost", MonsterFunctions::luaMonsterHazardDamageBoost); registerMethod(L, "Monster", "hazardDefenseBoost", MonsterFunctions::luaMonsterHazardDefenseBoost); + registerMethod(L, "Monster", "addReflectElement", MonsterFunctions::luaMonsterAddReflectElement); + registerMethod(L, "Monster", "addDefense", MonsterFunctions::luaMonsterAddDefense); + registerMethod(L, "Monster", "getDefense", MonsterFunctions::luaMonsterGetDefense); + + registerMethod(L, "Monster", "isDead", MonsterFunctions::luaMonsterIsDead); + registerMethod(L, "Monster", "immune", MonsterFunctions::luaMonsterImmune); + CharmFunctions::init(L); LootFunctions::init(L); MonsterSpellFunctions::init(L); @@ -124,6 +131,12 @@ class MonsterFunctions final : LuaScriptInterface { static int luaMonsterHazardDodge(lua_State* L); static int luaMonsterHazardDamageBoost(lua_State* L); static int luaMonsterHazardDefenseBoost(lua_State* L); + static int luaMonsterAddReflectElement(lua_State* L); + static int luaMonsterAddDefense(lua_State* L); + static int luaMonsterGetDefense(lua_State* L); + + static int luaMonsterIsDead(lua_State* L); + static int luaMonsterImmune(lua_State* L); friend class CreatureFunctions; }; diff --git a/src/lua/functions/creatures/monster/monster_type_functions.cpp b/src/lua/functions/creatures/monster/monster_type_functions.cpp index 757337bfe3c..2a6e3d1b581 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.cpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.cpp @@ -1055,6 +1055,8 @@ int MonsterTypeFunctions::luaMonsterTypeEventOnCallback(lua_State* L) { // monsterType:onDisappear(callback) // monsterType:onMove(callback) // monsterType:onSay(callback) + // monsterType:onPlayerAttack(callback) + // monsterType:onSpawn(callback) const auto monsterType = getUserdataShared(L, 1); if (monsterType) { if (monsterType->loadCallback(&g_scripts().getScriptInterface())) { @@ -1602,7 +1604,7 @@ int MonsterTypeFunctions::luaMonsterTypeBossRaceId(lua_State* L) { } else { auto raceId = getNumber(L, 2, 0); monsterType->info.raceid = raceId; - g_ioBosstiary().addBosstiaryMonster(raceId, monsterType->name); + g_ioBosstiary().addBosstiaryMonster(raceId, monsterType->typeName); pushBoolean(L, true); } diff --git a/src/lua/functions/creatures/monster/monster_type_functions.hpp b/src/lua/functions/creatures/monster/monster_type_functions.hpp index cdbd0c4158a..6923740db41 100644 --- a/src/lua/functions/creatures/monster/monster_type_functions.hpp +++ b/src/lua/functions/creatures/monster/monster_type_functions.hpp @@ -98,6 +98,8 @@ class MonsterTypeFunctions final : LuaScriptInterface { registerMethod(L, "MonsterType", "onDisappear", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); registerMethod(L, "MonsterType", "onMove", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); registerMethod(L, "MonsterType", "onSay", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); + registerMethod(L, "MonsterType", "onPlayerAttack", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); + registerMethod(L, "MonsterType", "onSpawn", MonsterTypeFunctions::luaMonsterTypeEventOnCallback); registerMethod(L, "MonsterType", "getSummonList", MonsterTypeFunctions::luaMonsterTypeGetSummonList); registerMethod(L, "MonsterType", "addSummon", MonsterTypeFunctions::luaMonsterTypeAddSummon); diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 7bbe495f7c2..421ceefcb0d 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -2272,10 +2272,23 @@ int PlayerFunctions::luaPlayerGetParty(lua_State* L) { } int PlayerFunctions::luaPlayerAddOutfit(lua_State* L) { - // player:addOutfit(lookType) + // player:addOutfit(lookType or name, addon = 0) std::shared_ptr player = getUserdataShared(L, 1); if (player) { - player->addOutfit(getNumber(L, 2), 0); + auto addon = getNumber(L, 3, 0); + if (lua_isnumber(L, 2)) { + player->addOutfit(getNumber(L, 2), addon); + } else if (lua_isstring(L, 2)) { + const std::string &outfitName = getString(L, 2); + const auto &outfit = Outfits::getInstance().getOutfitByName(player->getSex(), outfitName); + if (!outfit) { + reportErrorFunc("Outfit not found"); + return 1; + } + + player->addOutfit(outfit->lookType, addon); + } + pushBoolean(L, true); } else { lua_pushnil(L); @@ -4431,3 +4444,16 @@ int PlayerFunctions::luaPlayerRemoveIconBakragore(lua_State* L) { pushBoolean(L, true); return 1; } + +int PlayerFunctions::luaPlayerSendCreatureAppear(lua_State* L) { + auto player = getUserdataShared(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + return 1; + } + + bool isLogin = getBoolean(L, 2, false); + player->sendCreatureAppear(player, player->getPosition(), isLogin); + pushBoolean(L, true); + return 1; +} diff --git a/src/lua/functions/creatures/player/player_functions.hpp b/src/lua/functions/creatures/player/player_functions.hpp index 7cee43424cd..aa4db5857f7 100644 --- a/src/lua/functions/creatures/player/player_functions.hpp +++ b/src/lua/functions/creatures/player/player_functions.hpp @@ -376,9 +376,9 @@ class PlayerFunctions final : LuaScriptInterface { registerMethod(L, "Player", "createTransactionSummary", PlayerFunctions::luaPlayerCreateTransactionSummary); registerMethod(L, "Player", "takeScreenshot", PlayerFunctions::luaPlayerTakeScreenshot); - registerMethod(L, "Player", "sendIconBakragore", PlayerFunctions::luaPlayerSendIconBakragore); registerMethod(L, "Player", "removeIconBakragore", PlayerFunctions::luaPlayerRemoveIconBakragore); + registerMethod(L, "Player", "sendCreatureAppear", PlayerFunctions::luaPlayerSendCreatureAppear); GroupFunctions::init(L); GuildFunctions::init(L); @@ -746,5 +746,7 @@ class PlayerFunctions final : LuaScriptInterface { static int luaPlayerSendIconBakragore(lua_State* L); static int luaPlayerRemoveIconBakragore(lua_State* L); + static int luaPlayerSendCreatureAppear(lua_State* L); + friend class CreatureFunctions; }; diff --git a/src/lua/functions/events/event_callback_functions.cpp b/src/lua/functions/events/event_callback_functions.cpp index 54457f4367a..ef1c348c113 100644 --- a/src/lua/functions/events/event_callback_functions.cpp +++ b/src/lua/functions/events/event_callback_functions.cpp @@ -34,7 +34,14 @@ void EventCallbackFunctions::init(lua_State* luaState) { } int EventCallbackFunctions::luaEventCallbackCreate(lua_State* luaState) { - const auto eventCallback = std::make_shared(getScriptEnv()->getScriptInterface()); + const auto &callbackName = getString(luaState, 2); + if (callbackName.empty()) { + reportErrorFunc("Invalid callback name"); + return 1; + } + + bool skipDuplicationCheck = getBoolean(luaState, 3, false); + const auto eventCallback = std::make_shared(getScriptEnv()->getScriptInterface(), callbackName, skipDuplicationCheck); pushUserdata(luaState, eventCallback); setMetatable(luaState, -1, "EventCallback"); return 1; @@ -82,6 +89,11 @@ int EventCallbackFunctions::luaEventCallbackRegister(lua_State* luaState) { return 0; } + if (g_callbacks().isCallbackRegistered(callback)) { + reportErrorFunc(fmt::format("EventCallback is duplicated for event with name: {}", callback->getName())); + return 0; + } + g_callbacks().addCallback(callback); pushBoolean(luaState, true); return 1; diff --git a/src/map/map.cpp b/src/map/map.cpp index 85e7de50484..b1706be0759 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -12,6 +12,8 @@ #include "map.hpp" #include "utils/astarnodes.hpp" +#include "lua/callbacks/event_callback.hpp" +#include "lua/callbacks/events_callbacks.hpp" #include "creatures/monsters/monster.hpp" #include "game/game.hpp" #include "game/zones/zone.hpp" @@ -25,11 +27,7 @@ void Map::load(const std::string &identifier, const Position &pos) { path = identifier; IOMap::loadMap(this, pos); } catch (const std::exception &e) { - throw IOMapException(fmt::format( - "\n[Map::load] - The map in folder {} is missing or corrupted" - "\n - {}", - identifier, e.what() - )); + g_logger().warn("[Map::load] - The map in folder {} is missing or corrupted", identifier); } } @@ -97,6 +95,10 @@ void Map::loadMap(const std::string &identifier, bool mainMap /*= false*/, bool housefile.clear(); npcfile.clear(); } + + if (!mainMap) { + g_callbacks().executeCallback(EventCallback_t::mapOnLoad, &EventCallback::mapOnLoad, path.string()); + } } void Map::loadMapCustom(const std::string &mapName, bool loadHouses, bool loadMonsters, bool loadNpcs, bool loadZones, int customMapIndex) { @@ -192,8 +194,7 @@ std::shared_ptr Map::getTile(uint16_t x, uint16_t y, uint8_t z) { return nullptr; } - const auto tile = floor->getTile(x, y); - return tile ? tile : getOrCreateTileFromCache(floor, x, y); + return getOrCreateTileFromCache(floor, x, y); } void Map::refreshZones(uint16_t x, uint16_t y, uint8_t z) { diff --git a/src/map/mapcache.cpp b/src/map/mapcache.cpp index e6e2f79b806..f4cdb524ec9 100644 --- a/src/map/mapcache.cpp +++ b/src/map/mapcache.cpp @@ -104,8 +104,9 @@ std::shared_ptr MapCache::createItem(const std::shared_ptr &Bas std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptr &floor, uint16_t x, uint16_t y) { const auto cachedTile = floor->getTileCache(x, y); + const auto oldTile = floor->getTile(x, y); if (!cachedTile) { - return floor->getTile(x, y); + return oldTile; } std::unique_lock l(floor->getMutex()); @@ -114,6 +115,15 @@ std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptr(this); + std::vector> oldCreatureList; + if (oldTile) { + if (CreatureVector* creatures = oldTile->getCreatures()) { + for (const auto &creature : *creatures) { + oldCreatureList.emplace_back(creature); + } + } + } + std::shared_ptr tile = nullptr; if (cachedTile->isHouse()) { const auto house = map->houses.getHouse(cachedTile->houseId); @@ -127,6 +137,10 @@ std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptrinternalAddThing(creature); + } + if (cachedTile->ground != nullptr) { tile->internalAddThing(createItem(cachedTile->ground, pos)); } From 687aba591eb9b1f8a7d6ea9095d9b76facaad47b Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Thu, 12 Sep 2024 23:40:07 -0300 Subject: [PATCH 15/27] fix: float precision in config retrieval (#2889) Description: This addresses the issue where floating point values retrieved from the configuration were not being rounded properly, leading to slight inaccuracies. This fix ensures that all float values are correctly rounded to two decimal places before being passed to Lua, thereby ensuring consistent behavior and data accuracy. Expected behavior: With the implemented changes, when floating point values are retrieved, they will now be rounded to two decimal places. For example, a configured value of `1.15` will correctly be returned as `1.15` in Lua scripts. --- .../functions/core/game/config_functions.cpp | 13 +++++++-- .../functions/core/game/config_functions.hpp | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/lua/functions/core/game/config_functions.cpp b/src/lua/functions/core/game/config_functions.cpp index d0e77a69352..b839f720057 100644 --- a/src/lua/functions/core/game/config_functions.cpp +++ b/src/lua/functions/core/game/config_functions.cpp @@ -70,12 +70,21 @@ int ConfigFunctions::luaConfigManagerGetBoolean(lua_State* L) { } int ConfigFunctions::luaConfigManagerGetFloat(lua_State* L) { - auto key = getNumber(L, -1); + // configManager.getFloat(key, shouldRound = true) + + // Ensure the first argument (key) is provided and is a valid enum + auto key = getNumber(L, 1); if (!key) { reportErrorFunc("Wrong enum"); return 1; } - lua_pushnumber(L, g_configManager().getFloat(key, __FUNCTION__)); + // Check if the second argument (shouldRound) is provided and is a boolean; default to true if not provided + bool shouldRound = getBoolean(L, 2, true); + float value = g_configManager().getFloat(key, __FUNCTION__); + double finalValue = shouldRound ? static_cast(std::round(value * 100.0) / 100.0) : value; + + g_logger().debug("[{}] key: {}, finalValue: {}, shouldRound: {}", __METHOD_NAME__, magic_enum::enum_name(key), finalValue, shouldRound); + lua_pushnumber(L, finalValue); return 1; } diff --git a/src/lua/functions/core/game/config_functions.hpp b/src/lua/functions/core/game/config_functions.hpp index ae4952e9643..9806a35f426 100644 --- a/src/lua/functions/core/game/config_functions.hpp +++ b/src/lua/functions/core/game/config_functions.hpp @@ -17,6 +17,33 @@ class ConfigFunctions final : LuaScriptInterface { static void init(lua_State* L); private: + /** + * @brief Retrieves a float configuration value from the configuration manager, with an optional rounding. + * + * This function is a Lua binding used to get a float value from the configuration manager. It requires + * a key as the first argument, which should be a valid enumeration. An optional second boolean argument + * specifies whether the retrieved float should be rounded to two decimal places. + * + * @param L Pointer to the Lua state. The first argument must be a valid enum key, and the second argument (optional) + * can be a boolean indicating whether to round the result. + * + * @return Returns 1 after pushing the result onto the Lua stack, indicating the number of return values. + * + * @exception reportErrorFunc Throws an error if the first argument is not a valid enum. + * + * Usage: + * local result = ConfigManager.getFloat(ConfigKey.SomeKey) + * local result_rounded = ConfigManager.getFloat(ConfigKey.SomeKey, false) + * + * Detailed behavior: + * 1. Extracts the key from the first Lua stack argument as an enumeration of type `ConfigKey_t`. + * 2. Checks if the second argument is provided; if not, defaults to true for rounding. + * 3. Retrieves the float value associated with the key from the configuration manager. + * 4. If rounding is requested, rounds the value to two decimal places. + * 5. Logs the method call and the obtained value using the debug logger. + * 6. Pushes the final value (rounded or original) back onto the Lua stack. + * 7. Returns 1 to indicate a single return value. + */ static int luaConfigManagerGetFloat(lua_State* L); static int luaConfigManagerGetBoolean(lua_State* L); static int luaConfigManagerGetNumber(lua_State* L); From e4f0cdbde025abe83d66f8fd66d83ed10e93193a Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Sat, 14 Sep 2024 15:59:58 -0300 Subject: [PATCH 16/27] fix: field doesn`t display the condition (#2882) --- data/items/items.xml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/data/items/items.xml b/data/items/items.xml index 8cec8f05ec6..0afe5ebe858 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -4027,6 +4027,9 @@ + + + @@ -4342,6 +4345,9 @@ + + + @@ -20590,6 +20596,9 @@ + + + @@ -26310,6 +26319,9 @@ + + + @@ -26319,6 +26331,9 @@ + + + @@ -26328,6 +26343,9 @@ + + + @@ -45774,6 +45792,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + @@ -45781,6 +45802,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + @@ -45788,6 +45812,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + @@ -45796,6 +45823,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + @@ -58245,6 +58275,9 @@ hands of its owner. Granted by TibiaRoyal.com"/> + + + From b3b19a68b8e38ad8ed7b350a6f34f17ab99f028c Mon Sep 17 00:00:00 2001 From: Pedro Cruz Date: Sat, 14 Sep 2024 17:58:40 -0300 Subject: [PATCH 17/27] fix: login into another accounts (#2853) Fixes the login to other account when injecting a custom login.php --- src/account/account_repository.hpp | 2 ++ src/account/account_repository_db.cpp | 10 +++++++ src/account/account_repository_db.hpp | 2 ++ src/io/iologindata.cpp | 7 ++++- src/io/iologindata.hpp | 2 +- src/server/network/protocol/protocolgame.cpp | 2 +- .../account/in_memory_account_repository.hpp | 11 ++++++++ tests/unit/account/account_test.cpp | 28 +++++++++++++++++++ 8 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/account/account_repository.hpp b/src/account/account_repository.hpp index 0d4dcc7abcf..6dfe2c65668 100644 --- a/src/account/account_repository.hpp +++ b/src/account/account_repository.hpp @@ -27,6 +27,8 @@ class AccountRepository { virtual bool loadBySession(const std::string &email, AccountInfo &acc) = 0; virtual bool save(const AccountInfo &accInfo) = 0; + virtual bool getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) = 0; + virtual bool getPassword(const uint32_t &id, std::string &password) = 0; virtual bool getCoins(const uint32_t &id, const uint8_t &type, uint32_t &coins) = 0; diff --git a/src/account/account_repository_db.cpp b/src/account/account_repository_db.cpp index b150a636a97..81da30c08c5 100644 --- a/src/account/account_repository_db.cpp +++ b/src/account/account_repository_db.cpp @@ -64,6 +64,16 @@ bool AccountRepositoryDB::save(const AccountInfo &accInfo) { return successful; }; +bool AccountRepositoryDB::getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) { + auto result = g_database().storeQuery(fmt::format("SELECT `id` FROM `players` WHERE `account_id` = {} AND `name` = {}", id, g_database().escapeString(name))); + if (!result) { + g_logger().error("Failed to get character: [{}] from account: [{}]!", name, id); + return false; + } + + return result->countResults() == 1; +} + bool AccountRepositoryDB::getPassword(const uint32_t &id, std::string &password) { auto result = g_database().storeQuery(fmt::format("SELECT * FROM `accounts` WHERE `id` = {}", id)); if (!result) { diff --git a/src/account/account_repository_db.hpp b/src/account/account_repository_db.hpp index 651600e3bc4..e34d864a090 100644 --- a/src/account/account_repository_db.hpp +++ b/src/account/account_repository_db.hpp @@ -20,6 +20,8 @@ class AccountRepositoryDB final : public AccountRepository { bool loadBySession(const std::string &esseionKey, AccountInfo &acc) override; bool save(const AccountInfo &accInfo) override; + bool getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) override; + bool getPassword(const uint32_t &id, std::string &password) override; bool getCoins(const uint32_t &id, const uint8_t &type, uint32_t &coins) override; diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index 132cead6412..fce5d0dc293 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -19,7 +19,7 @@ #include "enums/account_type.hpp" #include "enums/account_errors.hpp" -bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, const std::string &password, std::string &characterName, uint32_t &accountId, bool oldProtocol) { +bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, const std::string &password, std::string &characterName, uint32_t &accountId, bool oldProtocol, const uint32_t ip) { Account account(accountDescriptor); account.setProtocolCompat(oldProtocol); @@ -38,6 +38,11 @@ bool IOLoginData::gameWorldAuthentication(const std::string &accountDescriptor, } } + if (!g_accountRepository().getCharacterByAccountIdAndName(account.getID(), characterName)) { + g_logger().warn("IP [{}] trying to connect into another account character", convertIPToString(ip)); + return false; + } + if (AccountErrors_t::Ok != enumFromValue(account.load())) { g_logger().error("Failed to load account [{}]", accountDescriptor); return false; diff --git a/src/io/iologindata.hpp b/src/io/iologindata.hpp index 1451cf89778..79fa3b59ad7 100644 --- a/src/io/iologindata.hpp +++ b/src/io/iologindata.hpp @@ -17,7 +17,7 @@ using ItemBlockList = std::list>>; class IOLoginData { public: - static bool gameWorldAuthentication(const std::string &accountDescriptor, const std::string &sessionOrPassword, std::string &characterName, uint32_t &accountId, bool oldProcotol); + static bool gameWorldAuthentication(const std::string &accountDescriptor, const std::string &sessionOrPassword, std::string &characterName, uint32_t &accountId, bool oldProcotol, const uint32_t ip); static uint8_t getAccountType(uint32_t accountId); static void updateOnlineStatus(uint32_t guid, bool login); static bool loadPlayerById(std::shared_ptr player, uint32_t id, bool disableIrrelevantInfo = true); diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 032b9b512d0..aab3f88bd47 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -841,7 +841,7 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) { } uint32_t accountId; - if (!IOLoginData::gameWorldAuthentication(accountDescriptor, password, characterName, accountId, oldProtocol)) { + if (!IOLoginData::gameWorldAuthentication(accountDescriptor, password, characterName, accountId, oldProtocol, getIP())) { ss.str(std::string()); if (authType == "session") { ss << "Your session has expired. Please log in again."; diff --git a/tests/fixture/account/in_memory_account_repository.hpp b/tests/fixture/account/in_memory_account_repository.hpp index 40dbda38e08..8a294992274 100644 --- a/tests/fixture/account/in_memory_account_repository.hpp +++ b/tests/fixture/account/in_memory_account_repository.hpp @@ -120,6 +120,17 @@ namespace tests { return true; } + bool getCharacterByAccountIdAndName(const uint32_t &id, const std::string &name) final { + for (auto it = accounts.begin(); it != accounts.end(); ++it) { + if (it->second.id == id) { + if (it->second.players.find(name) != it->second.players.end()) { + return true; + } + } + } + return false; + } + InMemoryAccountRepository &reset() { accounts.clear(); coins_.clear(); diff --git a/tests/unit/account/account_test.cpp b/tests/unit/account/account_test.cpp index 9259703c77c..c0c3ebbb832 100644 --- a/tests/unit/account/account_test.cpp +++ b/tests/unit/account/account_test.cpp @@ -592,4 +592,32 @@ suite<"account"> accountTest = [] { expect(acc.load() == enumToValue(AccountErrors_t::Ok)); expect(acc.authenticate()); }; + + test("Account::getCharacterByAccountIdAndName using an account with the given character.") = [&injectionFixture] { + auto [accountRepository] = injectionFixture.get(); + + Account acc { 1 }; + accountRepository.addAccount( + "session-key", + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", 1 }, { "Canary2", 2 } }, false, getTimeNow() + 24 * 60 * 60 * 1000 } + ); + + const auto hasCharacter = accountRepository.getCharacterByAccountIdAndName(1, "Canary"); + + expect(hasCharacter); + }; + + test("Account::getCharacterByAccountIdAndName using an account without the given character.") = [&injectionFixture] { + auto [accountRepository] = injectionFixture.get(); + + Account acc { 1 }; + accountRepository.addAccount( + "session-key", + AccountInfo { 1, 1, 1, AccountType::ACCOUNT_TYPE_GOD, { { "Canary", 1 }, { "Canary2", 2 } }, false, getTimeNow() + 24 * 60 * 60 * 1000 } + ); + + const auto hasCharacter = accountRepository.getCharacterByAccountIdAndName(1, "Invalid"); + + expect(!hasCharacter); + }; }; From 563b0f7512baf538dd79e52d80290c276afda571 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 18 Sep 2024 22:51:17 -0300 Subject: [PATCH 18/27] fix: qodana linter (#2902) --- qodana.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qodana.yml b/qodana.yml index 1621c979a9d..4857043fe5d 100644 --- a/qodana.yml +++ b/qodana.yml @@ -3,6 +3,8 @@ version: "1.0" profile: name: qodana.recommended +linter: jetbrains/qodana-clang:latest + bootstrap: | set -e sudo apt-get update && sudo apt-get -y dist-upgrade From ae3968295a5ba78fa98389e1e36f4afeb9d20288 Mon Sep 17 00:00:00 2001 From: Karin Date: Thu, 19 Sep 2024 02:01:25 -0300 Subject: [PATCH 19/27] fix: crash in use with creature (add nullptr check) (#2899) --- src/game/game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index 7b60220ecf9..5e99dda81fa 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3909,7 +3909,7 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin } const std::shared_ptr monster = creature->getMonster(); - if (monster && monster->isFamiliar() && creature->getMaster()->getPlayer() == player && (it.isRune() || it.type == ITEM_TYPE_POTION)) { + if (monster && monster->isFamiliar() && creature->getMaster() && creature->getMaster()->getPlayer() == player && (it.isRune() || it.type == ITEM_TYPE_POTION)) { player->setNextPotionAction(OTSYS_TIME() + g_configManager().getNumber(EX_ACTIONS_DELAY_INTERVAL, __FUNCTION__)); if (it.isMultiUse()) { From 10823e3ba7a8cc4100fc56a5197421c71fc52042 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 24 Sep 2024 01:05:21 -0300 Subject: [PATCH 20/27] fix: nil value on soul war quest and log to debug (#2906) Resolves #2897 --- data-otservbr-global/lib/quests/soul_war.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data-otservbr-global/lib/quests/soul_war.lua b/data-otservbr-global/lib/quests/soul_war.lua index a9e9d920e91..668c67f92b9 100644 --- a/data-otservbr-global/lib/quests/soul_war.lua +++ b/data-otservbr-global/lib/quests/soul_war.lua @@ -1112,9 +1112,9 @@ function MonsterType:calculateBagYouDesireChance(player, itemChance) itemChance = itemChance + (playerTaintLevel * SoulWarQuest.bagYouDesireChancePerTaint) end - logger.info("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, itemChance) + logger.debug("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, itemChance) - if math.random(1, 100000) <= totalChance then + if math.random(1, 100000) <= itemChance then logger.debug("Player {} killed {} and got a bag you desire with drop chance {}", player:getName(), monsterName, itemChance) if monsterName == "Goshnar's Megalomania" then -- Reset kill count on successful drop From a28960238fd9b68f134326189a534e353cc179fb Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 24 Sep 2024 17:31:04 -0300 Subject: [PATCH 21/27] fix: remove non-existent 'pet' flag from monsters (#2890) Removed the non-existent 'pet' flag from monsters to avoid unnecessary errors and improve data consistency. No other changes were made to the monster configurations. --- data-canary/monster/demons/demon.lua | 1 - data-canary/monster/demons/destroyer.lua | 1 - data-canary/monster/demons/hellhound.lua | 1 - data-canary/monster/demons/hellspawn.lua | 1 - data-canary/monster/demons/juggernaut.lua | 1 - data-canary/monster/dragons/dragon.lua | 1 - data-canary/monster/dragons/dragon_lord.lua | 1 - data-canary/monster/dragons/hydra.lua | 1 - data-canary/monster/dragons/ice_dragon.lua | 1 - data-canary/monster/dragons/wyrm.lua | 1 - data-canary/monster/giants/behemoth.lua | 1 - data-canary/monster/giants/cyclops_drone.lua | 1 - data-canary/monster/giants/cyclops_smith.lua | 1 - data-canary/monster/humanoids/frost_troll.lua | 1 - data-canary/monster/humanoids/goblin.lua | 1 - data-canary/monster/humanoids/island_troll.lua | 1 - data-canary/monster/humanoids/troll.lua | 1 - data-canary/monster/humans/amazon.lua | 1 - data-canary/monster/humans/assassin.lua | 1 - data-canary/monster/humans/bandit.lua | 1 - data-canary/monster/humans/hunter.lua | 1 - data-canary/monster/humans/monk.lua | 1 - data-canary/monster/humans/valkyrie.lua | 1 - data-canary/monster/mammals/bat.lua | 1 - data-canary/monster/mammals/cave_rat.lua | 1 - data-canary/monster/mammals/winter_wolf.lua | 1 - data-canary/monster/mammals/wolf.lua | 1 - data-canary/monster/reptiles/crocodile.lua | 1 - data-canary/monster/reptiles/snake.lua | 1 - data-canary/monster/reptiles/tortoise.lua | 1 - data-canary/monster/slimes/defiler.lua | 1 - data-canary/monster/slimes/slime.lua | 1 - data-canary/monster/undeads/ghoul.lua | 1 - data-canary/monster/undeads/mummy.lua | 1 - data-canary/monster/undeads/skeleton.lua | 1 - data-canary/monster/vermins/poison_spider.lua | 1 - data-canary/monster/vermins/sandcrawler.lua | 1 - data-canary/monster/vermins/spider.lua | 1 - data-canary/monster/vermins/wasp.lua | 1 - .../event_creatures/memory_creatures/memory_of_a_carnisylvan.lua | 1 - .../monster/quests/soul_war/goshnar's_megalomania_blue.lua | 1 - .../monster/quests/soul_war/goshnar's_megalomania_green.lua | 1 - .../monster/quests/soul_war/goshnar's_megalomania_purple.lua | 1 - .../monster/quests/soul_war/goshnars_cruelty.lua | 1 - data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua | 1 - data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua | 1 - data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua | 1 - data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua | 1 - .../normal_monsters/burning_hatred/ashes_of_burning_hatred.lua | 1 - .../normal_monsters/burning_hatred/blaze_of_burning_hatred.lua | 1 - .../normal_monsters/burning_hatred/flame_of_burning_hatred.lua | 1 - .../normal_monsters/burning_hatred/spark_of_burning_hatred.lua | 1 - .../soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua | 1 - .../soul_war/normal_monsters/furious_crater/a_greedy_eye.lua | 1 - .../quests/soul_war/normal_monsters/furious_crater/poor_soul.lua | 1 - .../megalomania_room/greater_splinter_of_madness.lua | 1 - .../megalomania_room/lesser_splinter_of_madness.lua | 1 - .../megalomania_room/mighty_splinter_of_madness.lua | 1 - .../normal_monsters/megalomania_room/necromantic_focus.lua | 1 - data-otservbr-global/monster/quests/soul_war/weeping_soul.lua | 1 - data-otservbr-global/monster/undeads/ahau.lua | 1 - 61 files changed, 61 deletions(-) diff --git a/data-canary/monster/demons/demon.lua b/data-canary/monster/demons/demon.lua index 37c6d1c6f25..763911485e7 100644 --- a/data-canary/monster/demons/demon.lua +++ b/data-canary/monster/demons/demon.lua @@ -67,7 +67,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/demons/destroyer.lua b/data-canary/monster/demons/destroyer.lua index 7c6bfa0ee7b..2dfa594ee82 100644 --- a/data-canary/monster/demons/destroyer.lua +++ b/data-canary/monster/demons/destroyer.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/demons/hellhound.lua b/data-canary/monster/demons/hellhound.lua index 4af37f63a6b..de0430ca639 100644 --- a/data-canary/monster/demons/hellhound.lua +++ b/data-canary/monster/demons/hellhound.lua @@ -64,7 +64,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/demons/hellspawn.lua b/data-canary/monster/demons/hellspawn.lua index f405a08fbd4..44fa905fa9a 100644 --- a/data-canary/monster/demons/hellspawn.lua +++ b/data-canary/monster/demons/hellspawn.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/demons/juggernaut.lua b/data-canary/monster/demons/juggernaut.lua index 704ce847f13..fa8947d54a7 100644 --- a/data-canary/monster/demons/juggernaut.lua +++ b/data-canary/monster/demons/juggernaut.lua @@ -64,7 +64,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/dragons/dragon.lua b/data-canary/monster/dragons/dragon.lua index 5c9b6f072a0..6d6d1567779 100644 --- a/data-canary/monster/dragons/dragon.lua +++ b/data-canary/monster/dragons/dragon.lua @@ -68,7 +68,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = true, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/dragons/dragon_lord.lua b/data-canary/monster/dragons/dragon_lord.lua index ba8042c99a6..702a1d9af28 100644 --- a/data-canary/monster/dragons/dragon_lord.lua +++ b/data-canary/monster/dragons/dragon_lord.lua @@ -69,7 +69,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/dragons/hydra.lua b/data-canary/monster/dragons/hydra.lua index a2927a36b7d..f665de1a1aa 100644 --- a/data-canary/monster/dragons/hydra.lua +++ b/data-canary/monster/dragons/hydra.lua @@ -68,7 +68,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/dragons/ice_dragon.lua b/data-canary/monster/dragons/ice_dragon.lua index 414dd8a98f0..9d7752859e3 100644 --- a/data-canary/monster/dragons/ice_dragon.lua +++ b/data-canary/monster/dragons/ice_dragon.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/dragons/wyrm.lua b/data-canary/monster/dragons/wyrm.lua index d93a500148d..7aea11e44a2 100644 --- a/data-canary/monster/dragons/wyrm.lua +++ b/data-canary/monster/dragons/wyrm.lua @@ -64,7 +64,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/giants/behemoth.lua b/data-canary/monster/giants/behemoth.lua index 1843aba91a7..8a6abbdc02f 100644 --- a/data-canary/monster/giants/behemoth.lua +++ b/data-canary/monster/giants/behemoth.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/giants/cyclops_drone.lua b/data-canary/monster/giants/cyclops_drone.lua index 1feef59ea39..5ce3edbedb3 100644 --- a/data-canary/monster/giants/cyclops_drone.lua +++ b/data-canary/monster/giants/cyclops_drone.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/giants/cyclops_smith.lua b/data-canary/monster/giants/cyclops_smith.lua index b2eaeb4bc3f..6b0161ce5ce 100644 --- a/data-canary/monster/giants/cyclops_smith.lua +++ b/data-canary/monster/giants/cyclops_smith.lua @@ -62,7 +62,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humanoids/frost_troll.lua b/data-canary/monster/humanoids/frost_troll.lua index 9305c0a5712..531419b333f 100644 --- a/data-canary/monster/humanoids/frost_troll.lua +++ b/data-canary/monster/humanoids/frost_troll.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humanoids/goblin.lua b/data-canary/monster/humanoids/goblin.lua index 712d1ad0321..c232303a02f 100644 --- a/data-canary/monster/humanoids/goblin.lua +++ b/data-canary/monster/humanoids/goblin.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humanoids/island_troll.lua b/data-canary/monster/humanoids/island_troll.lua index a08c76ddb25..dcc7b486d05 100644 --- a/data-canary/monster/humanoids/island_troll.lua +++ b/data-canary/monster/humanoids/island_troll.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humanoids/troll.lua b/data-canary/monster/humanoids/troll.lua index 74c303043ed..6916c409d03 100644 --- a/data-canary/monster/humanoids/troll.lua +++ b/data-canary/monster/humanoids/troll.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humans/amazon.lua b/data-canary/monster/humans/amazon.lua index 1443be056c2..e73dd0be0f2 100644 --- a/data-canary/monster/humans/amazon.lua +++ b/data-canary/monster/humans/amazon.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humans/assassin.lua b/data-canary/monster/humans/assassin.lua index 5b4ab068a86..abab4ea8e7a 100644 --- a/data-canary/monster/humans/assassin.lua +++ b/data-canary/monster/humans/assassin.lua @@ -62,7 +62,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humans/bandit.lua b/data-canary/monster/humans/bandit.lua index ee884cdb586..b96b11556ad 100644 --- a/data-canary/monster/humans/bandit.lua +++ b/data-canary/monster/humans/bandit.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humans/hunter.lua b/data-canary/monster/humans/hunter.lua index fc941c0c31c..c065d8ec4ea 100644 --- a/data-canary/monster/humans/hunter.lua +++ b/data-canary/monster/humans/hunter.lua @@ -62,7 +62,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humans/monk.lua b/data-canary/monster/humans/monk.lua index cb7eecfbfb3..a14fe1936e6 100644 --- a/data-canary/monster/humans/monk.lua +++ b/data-canary/monster/humans/monk.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/humans/valkyrie.lua b/data-canary/monster/humans/valkyrie.lua index 92b69481741..a15e734c4c8 100644 --- a/data-canary/monster/humans/valkyrie.lua +++ b/data-canary/monster/humans/valkyrie.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/mammals/bat.lua b/data-canary/monster/mammals/bat.lua index d84ffd6236a..72cda421545 100644 --- a/data-canary/monster/mammals/bat.lua +++ b/data-canary/monster/mammals/bat.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/mammals/cave_rat.lua b/data-canary/monster/mammals/cave_rat.lua index 099e2fb1dc2..cb044c709ce 100644 --- a/data-canary/monster/mammals/cave_rat.lua +++ b/data-canary/monster/mammals/cave_rat.lua @@ -62,7 +62,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/mammals/winter_wolf.lua b/data-canary/monster/mammals/winter_wolf.lua index 0768f532a01..b53742618cd 100644 --- a/data-canary/monster/mammals/winter_wolf.lua +++ b/data-canary/monster/mammals/winter_wolf.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/mammals/wolf.lua b/data-canary/monster/mammals/wolf.lua index 6184add5843..a542ef6ff5e 100644 --- a/data-canary/monster/mammals/wolf.lua +++ b/data-canary/monster/mammals/wolf.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/reptiles/crocodile.lua b/data-canary/monster/reptiles/crocodile.lua index 0d493212c50..dbadf10db52 100644 --- a/data-canary/monster/reptiles/crocodile.lua +++ b/data-canary/monster/reptiles/crocodile.lua @@ -62,7 +62,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/reptiles/snake.lua b/data-canary/monster/reptiles/snake.lua index f3bed935391..d82fdf67a34 100644 --- a/data-canary/monster/reptiles/snake.lua +++ b/data-canary/monster/reptiles/snake.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/reptiles/tortoise.lua b/data-canary/monster/reptiles/tortoise.lua index a56b44bc264..cc5afa7752c 100644 --- a/data-canary/monster/reptiles/tortoise.lua +++ b/data-canary/monster/reptiles/tortoise.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/slimes/defiler.lua b/data-canary/monster/slimes/defiler.lua index 62c68fba3e5..ac71528076b 100644 --- a/data-canary/monster/slimes/defiler.lua +++ b/data-canary/monster/slimes/defiler.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/slimes/slime.lua b/data-canary/monster/slimes/slime.lua index f59cc2be823..df5afc349d2 100644 --- a/data-canary/monster/slimes/slime.lua +++ b/data-canary/monster/slimes/slime.lua @@ -66,7 +66,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/undeads/ghoul.lua b/data-canary/monster/undeads/ghoul.lua index c2512610363..2e12419d11b 100644 --- a/data-canary/monster/undeads/ghoul.lua +++ b/data-canary/monster/undeads/ghoul.lua @@ -67,7 +67,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/undeads/mummy.lua b/data-canary/monster/undeads/mummy.lua index 6d391772889..c856a67cb64 100644 --- a/data-canary/monster/undeads/mummy.lua +++ b/data-canary/monster/undeads/mummy.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/undeads/skeleton.lua b/data-canary/monster/undeads/skeleton.lua index a9c5d62a27d..8dac97e6575 100644 --- a/data-canary/monster/undeads/skeleton.lua +++ b/data-canary/monster/undeads/skeleton.lua @@ -62,7 +62,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/vermins/poison_spider.lua b/data-canary/monster/vermins/poison_spider.lua index c9b7ec6a0d3..afe2d7fa2ee 100644 --- a/data-canary/monster/vermins/poison_spider.lua +++ b/data-canary/monster/vermins/poison_spider.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-canary/monster/vermins/sandcrawler.lua b/data-canary/monster/vermins/sandcrawler.lua index 08c11f127fb..21340816742 100644 --- a/data-canary/monster/vermins/sandcrawler.lua +++ b/data-canary/monster/vermins/sandcrawler.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/vermins/spider.lua b/data-canary/monster/vermins/spider.lua index a93e7c93f77..d6d943cdf78 100644 --- a/data-canary/monster/vermins/spider.lua +++ b/data-canary/monster/vermins/spider.lua @@ -61,7 +61,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = false, - pet = false, } monster.light = { diff --git a/data-canary/monster/vermins/wasp.lua b/data-canary/monster/vermins/wasp.lua index 89cd57cb575..ce35f28e83f 100644 --- a/data-canary/monster/vermins/wasp.lua +++ b/data-canary/monster/vermins/wasp.lua @@ -63,7 +63,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/event_creatures/memory_creatures/memory_of_a_carnisylvan.lua b/data-otservbr-global/monster/event_creatures/memory_creatures/memory_of_a_carnisylvan.lua index 1ecfdd96e1b..901f7686874 100644 --- a/data-otservbr-global/monster/event_creatures/memory_creatures/memory_of_a_carnisylvan.lua +++ b/data-otservbr-global/monster/event_creatures/memory_creatures/memory_of_a_carnisylvan.lua @@ -47,7 +47,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua index 1433d2ed7d4..cfd0f53f7fa 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_blue.lua @@ -55,7 +55,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua index f6d60b0b747..8e8effb5e08 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_green.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua index 79914af4935..7781c66516d 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnar's_megalomania_purple.lua @@ -48,7 +48,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua index 44f87cca574..3f1cb2c4421 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_cruelty.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua index f1f5284398e..16d995988a5 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_greed.lua @@ -59,7 +59,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua index 47fedea4e7c..f218e0b7e9f 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_hatred.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua index e0b054dc10d..c4598e9bbe0 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_malice.lua @@ -60,7 +60,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua index 40817c335b0..7501e466f54 100644 --- a/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua +++ b/data-otservbr-global/monster/quests/soul_war/goshnars_spite.lua @@ -59,7 +59,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua index 72c4082ee0e..995cad06686 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/ashes_of_burning_hatred.lua @@ -49,7 +49,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua index c0b76ce98df..77b619b70ea 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/blaze_of_burning_hatred.lua @@ -49,7 +49,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua index 483c5156fb1..20f253efa79 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/flame_of_burning_hatred.lua @@ -49,7 +49,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua index dc9ff25f05e..c3c9d73749f 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/spark_of_burning_hatred.lua @@ -49,7 +49,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua index 76d403fab1a..1f94f6998c2 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/burning_hatred/symbol_of_hatred.lua @@ -42,7 +42,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua index aed9dcfc829..8622ed190a2 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/a_greedy_eye.lua @@ -48,7 +48,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua index 316959ddcd8..0596c7516d3 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/furious_crater/poor_soul.lua @@ -51,7 +51,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua index 2193aac5447..d96ae146165 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/greater_splinter_of_madness.lua @@ -51,7 +51,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua index 7927ac18a26..9b39776a4b7 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/lesser_splinter_of_madness.lua @@ -51,7 +51,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua index 18dd5825b89..21d7229366b 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/mighty_splinter_of_madness.lua @@ -51,7 +51,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua index 53c607ace7f..9fcadf66e86 100644 --- a/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua +++ b/data-otservbr-global/monster/quests/soul_war/normal_monsters/megalomania_room/necromantic_focus.lua @@ -46,7 +46,6 @@ monster.flags = { canWalkOnEnergy = false, canWalkOnFire = false, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua b/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua index 3631fa3cb8e..0c45ec43fff 100644 --- a/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua +++ b/data-otservbr-global/monster/quests/soul_war/weeping_soul.lua @@ -51,7 +51,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { diff --git a/data-otservbr-global/monster/undeads/ahau.lua b/data-otservbr-global/monster/undeads/ahau.lua index 0e86a1d2c98..9d7f0f17c9c 100644 --- a/data-otservbr-global/monster/undeads/ahau.lua +++ b/data-otservbr-global/monster/undeads/ahau.lua @@ -55,7 +55,6 @@ monster.flags = { canWalkOnEnergy = true, canWalkOnFire = true, canWalkOnPoison = true, - pet = false, } monster.light = { From f0830c37d71e159b76e69090e2c07af4a59faa7e Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 24 Sep 2024 17:32:22 -0300 Subject: [PATCH 22/27] refactor: removeMoneyBank (#2887) Improved the `Player.removeMoneyBank` function to make it more efficient. It now uses intermediate variables to avoid repeated calls and simplifies the construction of the message. The logic and messages have been kept as they were. --- data/libs/functions/player.lua | 43 ++++++++++++---------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index a2fb8bc0de6..8dbd0cf9aff 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -198,43 +198,30 @@ function Player.withdrawMoney(self, amount) return Bank.withdraw(self, amount) end --- player:removeMoneyBank(money) -function Player:removeMoneyBank(amount) - if type(amount) == "string" then - amount = tonumber(amount) - end - - local moneyCount = self:getMoney() - local bankCount = self:getBankBalance() +function Player.removeMoneyBank(self, amount) + local inventoryMoney = self:getMoney() + local bankBalance = self:getBankBalance() - -- The player have all the money with him - if amount <= moneyCount then - -- Removes player inventory money + if amount <= inventoryMoney then self:removeMoney(amount) - if amount > 0 then self:sendTextMessage(MESSAGE_TRADE, ("Paid %d gold from inventory."):format(amount)) end return true + end - -- The player doens't have all the money with him - elseif amount <= (moneyCount + bankCount) then - -- Check if the player has some money - if moneyCount ~= 0 then - -- Removes player inventory money - self:removeMoney(moneyCount) - local remains = amount - moneyCount - - -- Removes player bank money - Bank.debit(self, remains) + if amount <= (inventoryMoney + bankBalance) then + local remainingAmount = amount - if amount > 0 then - self:sendTextMessage(MESSAGE_TRADE, ("Paid %s from inventory and %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(moneyCount), FormatNumber(amount - moneyCount), FormatNumber(self:getBankBalance()))) - end - return true + if inventoryMoney > 0 then + self:removeMoney(inventoryMoney) + remainingAmount = remainingAmount - inventoryMoney end - self:setBankBalance(bankCount - amount) - self:sendTextMessage(MESSAGE_TRADE, ("Paid %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(amount), FormatNumber(self:getBankBalance()))) + + Bank.debit(self, remainingAmount) + + self:setBankBalance(bankBalance - remainingAmount) + self:sendTextMessage(MESSAGE_TRADE, ("Paid %s from inventory and %s gold from bank account. Your account balance is now %s gold."):format(FormatNumber(amount - remainingAmount), FormatNumber(remainingAmount), FormatNumber(self:getBankBalance()))) return true end return false From a399c263613e7d20efc2118cf8c20ab18bd5cf97 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Tue, 24 Sep 2024 17:34:26 -0300 Subject: [PATCH 23/27] feat: transferring money bank from main/main and rookgaard/rookgaard (#2878) Rookguard town is `town id` 3, so `minTownIdToBankTransfer` defaults to 4 Resolves #2778 --- config.lua.dist | 4 +-- src/config/config_enums.hpp | 2 +- src/config/configmanager.cpp | 2 +- src/game/bank/bank.cpp | 29 +++++++++++++------ src/game/bank/bank.hpp | 2 +- .../functions/core/game/bank_functions.cpp | 13 ++++----- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index 89bd3e68fc3..113c6b2a4c9 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -254,7 +254,7 @@ onlyPremiumAccount = false -- NOTE: enablePlayerPutItemInAmmoSlot = true, will enable players to put any items on ammo slot, more used in custom shopping system -- NOTE: startStreakLevel will make a reward streak level for new players who never logged in -- NOTE: if showLootsInBestiary is true, will cause all loots to be shown in the bestiary even if the player has not reached the required number of kills --- NOTE: minTownIdToBankTransfer blocks towns less than defined from receiving money transfers +-- NOTE: minTownIdToBankTransferFromMain blocks towns less than defined from receiving money transfers -- NOTE: enableSupportOutfit enable GODS and GMS to select support outfit (gamemaster, customer support or community manager) stashMoving = false stashItemCount = 5000 @@ -275,7 +275,7 @@ storeInboxMaxLimit = 2000 enablePlayerPutItemInAmmoSlot = false startStreakLevel = 0 showLootsInBestiary = false -minTownIdToBankTransfer = 3 +minTownIdToBankTransferFromMain = 4 enableSupportOutfit = true -- Teleport summon diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index bf480505014..1dfa71ce7dd 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -157,7 +157,7 @@ enum ConfigKey_t : uint16_t { METRICS_PROMETHEUS_ADDRESS, MIN_DELAY_BETWEEN_CONDITIONS, MIN_ELEMENTAL_RESISTANCE, - MIN_TOWN_ID_TO_BANK_TRANSFER, + MIN_TOWN_ID_TO_BANK_TRANSFER_FROM_MAIN, MOMENTUM_CHANCE_FORMULA_A, MOMENTUM_CHANCE_FORMULA_B, MOMENTUM_CHANCE_FORMULA_C, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 80126ddac93..9ce861665de 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -283,7 +283,7 @@ bool ConfigManager::load() { loadIntConfig(L, METRICS_OSTREAM_INTERVAL, "metricsOstreamInterval", 1000); loadIntConfig(L, MIN_DELAY_BETWEEN_CONDITIONS, "minDelayBetweenConditions", 0); loadIntConfig(L, MIN_ELEMENTAL_RESISTANCE, "minElementalResistance", -200); - loadIntConfig(L, MIN_TOWN_ID_TO_BANK_TRANSFER, "minTownIdToBankTransfer", 3); + loadIntConfig(L, MIN_TOWN_ID_TO_BANK_TRANSFER_FROM_MAIN, "minTownIdToBankTransferFromMain", 4); loadIntConfig(L, MONTH_KILLS_TO_RED, "monthKillsToRedSkull", 10); loadIntConfig(L, MULTIPLIER_ATTACKONFIST, "multiplierSpeedOnFist", 5); loadIntConfig(L, ORANGE_SKULL_DURATION, "orangeSkullDuration", 7); diff --git a/src/game/bank/bank.cpp b/src/game/bank/bank.cpp index b6f3b14c6e5..33d97c92ee5 100644 --- a/src/game/bank/bank.cpp +++ b/src/game/bank/bank.cpp @@ -80,26 +80,28 @@ const std::set deniedNames = { "paladinsample" }; -bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) { +bool Bank::transferTo(const std::shared_ptr &destination, const uint64_t amount) { if (!destination) { g_logger().error("Bank::transferTo: destination is nullptr"); return false; } - auto bankable = getBankable(); + const auto bankable = getBankable(); if (!bankable) { g_logger().error("Bank::transferTo: bankable is nullptr"); return false; } - auto destinationBankable = destination->getBankable(); + const auto destinationBankable = destination->getBankable(); if (!destinationBankable) { g_logger().error("Bank::transferTo: destinationBankable is nullptr"); return false; } - auto destinationPlayer = destinationBankable->getPlayer(); - if (destinationPlayer != nullptr) { + const auto &destinationPlayer = destinationBankable->getPlayer(); + const auto &bankablePlayer = bankable->getPlayer(); + + if (destinationPlayer && bankablePlayer) { auto name = asLowerCaseString(destinationPlayer->getName()); replaceString(name, " ", ""); @@ -108,8 +110,17 @@ bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) return false; } - if (destinationPlayer->getTown()->getID() < g_configManager().getNumber(MIN_TOWN_ID_TO_BANK_TRANSFER, __FUNCTION__)) { - g_logger().warn("Bank::transferTo: denied town: {}", destinationPlayer->getTown()->getID()); + const auto destinationTownId = destinationPlayer->getTown()->getID(); + const auto bankableTownId = bankablePlayer->getTown()->getID(); + const auto minTownIdToTransferFromMain = g_configManager().getNumber(MIN_TOWN_ID_TO_BANK_TRANSFER_FROM_MAIN, __FUNCTION__); + + if (destinationTownId < minTownIdToTransferFromMain && bankableTownId >= minTownIdToTransferFromMain) { + g_logger().warn("[{}] Player {} is from main town, trying to transfer money to player {} in {} town.", __FUNCTION__, bankablePlayer->getName(), destinationPlayer->getName(), destinationTownId); + return false; + } + + if (bankableTownId < minTownIdToTransferFromMain && destinationTownId >= minTownIdToTransferFromMain) { + g_logger().warn("[{}] Player {} is not from main town, trying to transfer money to player {} in {} town.", __FUNCTION__, bankablePlayer->getName(), destinationPlayer->getName(), destinationTownId); return false; } } @@ -122,8 +133,8 @@ bool Bank::transferTo(const std::shared_ptr destination, uint64_t amount) g_metrics().addCounter("balance_increase", amount, { { "player", destinationPlayer->getName() }, { "context", "bank_transfer" } }); } - if (bankable->getPlayer()) { - g_metrics().addCounter("balance_decrease", amount, { { "player", bankable->getPlayer()->getName() }, { "context", "bank_transfer" } }); + if (bankablePlayer) { + g_metrics().addCounter("balance_decrease", amount, { { "player", bankablePlayer->getName() }, { "context", "bank_transfer" } }); } return true; diff --git a/src/game/bank/bank.hpp b/src/game/bank/bank.hpp index 9e65a55aeb9..7fdb740a022 100644 --- a/src/game/bank/bank.hpp +++ b/src/game/bank/bank.hpp @@ -42,7 +42,7 @@ class Bank : public SharedObject { bool balance(uint64_t amount) const; uint64_t balance(); bool hasBalance(uint64_t amount); - bool transferTo(const std::shared_ptr destination, uint64_t amount); + bool transferTo(const std::shared_ptr &destination, const uint64_t amount); bool withdraw(std::shared_ptr player, uint64_t amount); bool deposit(const std::shared_ptr destination); bool deposit(const std::shared_ptr destination, uint64_t amount); diff --git a/src/lua/functions/core/game/bank_functions.cpp b/src/lua/functions/core/game/bank_functions.cpp index cc76bf4c091..eb0bbf00280 100644 --- a/src/lua/functions/core/game/bank_functions.cpp +++ b/src/lua/functions/core/game/bank_functions.cpp @@ -57,37 +57,36 @@ int BankFunctions::luaBankHasBalance(lua_State* L) { int BankFunctions::luaBankTransfer(lua_State* L) { // Bank.transfer(fromPlayerOrGuild, toPlayerOrGuild, amount) - auto source = getBank(L, 1); + const auto &source = getBank(L, 1); if (source == nullptr) { g_logger().debug("BankFunctions::luaBankTransfer: source is null"); reportErrorFunc("Bank is nullptr"); return 1; } - std::shared_ptr destination = getBank(L, 2); + const auto &destination = getBank(L, 2); if (destination == nullptr) { g_logger().debug("BankFunctions::luaBankTransfer: destination is null"); reportErrorFunc("Bank is nullptr"); return 1; } - uint64_t amount = getNumber(L, 3); + const uint64_t amount = getNumber(L, 3); pushBoolean(L, source->transferTo(destination, amount)); return 1; } int BankFunctions::luaBankTransferToGuild(lua_State* L) { // Bank.transfer(fromPlayerOrGuild, toGuild, amount) - auto source = getBank(L, 1); + const auto &source = getBank(L, 1); if (source == nullptr) { reportErrorFunc("Source is nullptr"); return 1; } - - std::shared_ptr destination = getBank(L, 2, true /* isGuild */); + const auto &destination = getBank(L, 2, true /* isGuild */); if (destination == nullptr) { reportErrorFunc("Destination is nullptr"); return 1; } - uint64_t amount = getNumber(L, 3); + const uint64_t amount = getNumber(L, 3); pushBoolean(L, source->transferTo(destination, amount)); return 1; } From a44490191e15efec042764092690f6435aff4857 Mon Sep 17 00:00:00 2001 From: Luan Luciano Date: Tue, 24 Sep 2024 17:48:34 -0300 Subject: [PATCH 24/27] fix: task context is being sent empty (#2871) Solve the problem where the message "[Task::Task] is sent: the task context cannot be empty!" because 'DispatcherContext::addEvent' and 'DispatcherContext::tryAddEvent' methods are not receiving the "context" parameter. --- src/canary_server.cpp | 2 +- src/creatures/combat/condition.cpp | 10 +- src/creatures/creature.cpp | 44 ++-- src/creatures/monsters/monster.cpp | 2 +- .../monsters/spawns/spawn_monster.cpp | 2 +- src/creatures/players/player.cpp | 14 +- src/database/databasetasks.cpp | 4 +- src/game/game.cpp | 219 +++++++++++------- src/game/scheduling/dispatcher.cpp | 8 +- src/game/scheduling/dispatcher.hpp | 6 +- .../functions/core/game/game_functions.cpp | 4 +- src/map/mapcache.cpp | 13 +- src/server/network/connection/connection.cpp | 4 +- src/server/network/protocol/protocol.cpp | 16 +- src/server/network/protocol/protocolgame.cpp | 26 ++- src/server/network/protocol/protocollogin.cpp | 10 +- .../network/protocol/protocolstatus.cpp | 20 +- src/server/signals.cpp | 10 +- 18 files changed, 247 insertions(+), 167 deletions(-) diff --git a/src/canary_server.cpp b/src/canary_server.cpp index cca280b69fa..f71fc71ba9a 100644 --- a/src/canary_server.cpp +++ b/src/canary_server.cpp @@ -123,7 +123,7 @@ int CanaryServer::run() { loaderStatus.notify_one(); }, - "CanaryServer::run" + __FUNCTION__ ); loaderStatus.wait(LoaderStatus::LOADING); diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 6784e8786f3..b308c05a5bc 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -2095,10 +2095,12 @@ bool ConditionFeared::executeCondition(std::shared_ptr creature, int32 } if (getFleePath(creature, currentPos, listDir)) { - g_dispatcher().addEvent([id = creature->getID(), listDir] { - g_game().forcePlayerAutoWalk(id, listDir); - }, - "ConditionFeared::executeCondition"); + g_dispatcher().addEvent( + [id = creature->getID(), listDir] { + g_game().forcePlayerAutoWalk(id, listDir); + }, + __FUNCTION__ + ); g_logger().debug("[ConditionFeared::executeCondition] Walking Scheduled"); } diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index a5cd66a629f..56ed1770039 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -251,17 +251,23 @@ void Creature::addEventWalk(bool firstStep) { return; } - g_dispatcher().context().tryAddEvent([ticks, self = getCreature()]() { - // Take first step right away, but still queue the next - if (ticks == 1) { - g_game().checkCreatureWalk(self->getID()); - } + g_dispatcher().context().tryAddEvent( + [ticks, self = getCreature()]() { + // Take first step right away, but still queue the next + if (ticks == 1) { + g_game().checkCreatureWalk(self->getID()); + } - self->eventWalk = g_dispatcher().scheduleEvent( - static_cast(ticks), - [creatureId = self->getID()] { g_game().checkCreatureWalk(creatureId); }, "Game::checkCreatureWalk" - ); - }); + self->eventWalk = g_dispatcher().scheduleEvent( + static_cast(ticks), + [creatureId = self->getID()] { + g_game().checkCreatureWalk(creatureId); + }, + "Game::checkCreatureWalk" + ); + }, + "Game::checkCreatureWalk" + ); } void Creature::stopEventWalk() { @@ -600,7 +606,7 @@ void Creature::onCreatureMove(const std::shared_ptr &creature, const s if (followCreature && (creature == getCreature() || creature == followCreature)) { if (hasFollowPath) { isUpdatingPath = true; - g_dispatcher().addEvent([creatureId = getID()] { g_game().updateCreatureWalk(creatureId); }, "Game::updateCreatureWalk"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().updateCreatureWalk(creatureId); }, __FUNCTION__); } if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { @@ -615,7 +621,7 @@ void Creature::onCreatureMove(const std::shared_ptr &creature, const s } else { if (hasExtraSwing()) { // our target is moving lets see if we can get in hit - g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, "Game::checkCreatureAttack"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, __FUNCTION__); } if (newTile->getZoneType() != oldTile->getZoneType()) { @@ -825,10 +831,12 @@ bool Creature::dropCorpse(std::shared_ptr lastHitCreature, std::shared auto isReachable = g_game().map.getPathMatching(player->getPosition(), dirList, FrozenPathingConditionCall(corpse->getPosition()), fpp); if (player->checkAutoLoot(monster->isRewardBoss()) && isReachable) { - g_dispatcher().addEvent([player, corpseContainer, corpsePosition = corpse->getPosition()] { - g_game().playerQuickLootCorpse(player, corpseContainer, corpsePosition); - }, - "Game::playerQuickLootCorpse"); + g_dispatcher().addEvent( + [player, corpseContainer, corpsePosition = corpse->getPosition()] { + g_game().playerQuickLootCorpse(player, corpseContainer, corpsePosition); + }, + __FUNCTION__ + ); } } } @@ -872,7 +880,7 @@ void Creature::changeHealth(int32_t healthChange, bool sendHealthChange /* = tru g_game().addCreatureHealth(static_self_cast()); } if (health <= 0) { - g_dispatcher().addEvent([creatureId = getID()] { g_game().executeDeath(creatureId); }, "Game::executeDeath"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().executeDeath(creatureId); }, __FUNCTION__); } } @@ -1061,7 +1069,7 @@ void Creature::goToFollowCreature_async(std::function &&onComplete) { }); if (onComplete) { - g_dispatcher().context().addEvent(std::move(onComplete)); + g_dispatcher().context().addEvent(std::move(onComplete), __FUNCTION__); } } diff --git a/src/creatures/monsters/monster.cpp b/src/creatures/monsters/monster.cpp index 1a364ece2ba..4a6d07fe8d7 100644 --- a/src/creatures/monsters/monster.cpp +++ b/src/creatures/monsters/monster.cpp @@ -792,7 +792,7 @@ bool Monster::selectTarget(const std::shared_ptr &creature) { if (isHostile() || isSummon()) { if (setAttackedCreature(creature)) { - g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, "Game::checkCreatureAttack"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, __FUNCTION__); } } return setFollowCreature(creature); diff --git a/src/creatures/monsters/spawns/spawn_monster.cpp b/src/creatures/monsters/spawns/spawn_monster.cpp index 846068e67b7..93b381da061 100644 --- a/src/creatures/monsters/spawns/spawn_monster.cpp +++ b/src/creatures/monsters/spawns/spawn_monster.cpp @@ -226,7 +226,7 @@ void SpawnMonster::startup(bool delayed) { continue; } if (delayed) { - g_dispatcher().addEvent([this, spawnMonsterId, &sb, mType] { scheduleSpawn(spawnMonsterId, sb, mType, 0, true); }, "SpawnMonster::startup"); + g_dispatcher().addEvent([this, spawnMonsterId, &sb, mType] { scheduleSpawn(spawnMonsterId, sb, mType, 0, true); }, __FUNCTION__); } else { scheduleSpawn(spawnMonsterId, sb, mType, 0, true); } diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 6f7e33eb7f9..8c49226b621 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -1946,7 +1946,7 @@ void Player::onCreatureMove(const std::shared_ptr &creature, const std const auto &followCreature = getFollowCreature(); if (hasFollowPath && (creature == followCreature || (creature.get() == this && followCreature))) { isUpdatingPath = false; - g_dispatcher().addEvent([creatureId = getID()] { g_game().updateCreatureWalk(creatureId); }, "Game::updateCreatureWalk"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().updateCreatureWalk(creatureId); }, __FUNCTION__); } if (creature != getPlayer()) { @@ -4283,7 +4283,7 @@ bool Player::updateSaleShopList(std::shared_ptr item) { return true; } - g_dispatcher().addEvent([creatureId = getID()] { g_game().updatePlayerSaleItems(creatureId); }, "Game::updatePlayerSaleItems"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().updatePlayerSaleItems(creatureId); }, __FUNCTION__); scheduledSaleUpdate = true; return true; } @@ -4354,7 +4354,7 @@ bool Player::setAttackedCreature(std::shared_ptr creature) { } if (creature) { - g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, "Game::checkCreatureAttack"); + g_dispatcher().addEvent([creatureId = getID()] { g_game().checkCreatureAttack(creatureId); }, __FUNCTION__); } return true; } @@ -4416,7 +4416,8 @@ void Player::doAttacking(uint32_t) { const auto &task = createPlayerTask( std::max(SCHEDULER_MINTICKS, delay), - [playerId = getID()] { g_game().checkCreatureAttack(playerId); }, "Game::checkCreatureAttack" + [playerId = getID()] { g_game().checkCreatureAttack(playerId); }, + __FUNCTION__ ); if (!classicSpeed) { @@ -6785,7 +6786,7 @@ void Player::triggerTranscendance() { player->sendBasicData(); } }, - "Player::triggerTranscendance" + __FUNCTION__ ); g_dispatcher().scheduleEvent(task); @@ -7858,8 +7859,7 @@ bool Player::canAutoWalk(const Position &toPosition, const std::function std::vector listDir; if (getPathTo(toPosition, listDir, 0, 1, true, true)) { g_dispatcher().addEvent([creatureId = getID(), listDir] { g_game().playerAutoWalk(creatureId, listDir); }, __FUNCTION__); - - std::shared_ptr task = createPlayerTask(delay, function, __FUNCTION__); + const auto &task = createPlayerTask(delay, function, __FUNCTION__); setNextWalkActionTask(task); return true; } else { diff --git a/src/database/databasetasks.cpp b/src/database/databasetasks.cpp index 06cfda93fc0..8a660f1c031 100644 --- a/src/database/databasetasks.cpp +++ b/src/database/databasetasks.cpp @@ -26,7 +26,7 @@ void DatabaseTasks::execute(const std::string &query, std::functiongetID(), movingCreature->getID(), movingCreature->getPosition(), tile->getPosition()); }, - "Game::playerMoveCreatureByID" + __FUNCTION__ ); player->setNextActionPushTask(task); } else { @@ -1385,7 +1385,11 @@ void Game::playerMoveCreature(std::shared_ptr player, std::shared_ptrcanDoAction()) { const auto &task = createPlayerTask( - 600, [this, player, movingCreature, toTile, movingCreatureOrigPos] { playerMoveCreatureByID(player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()); }, "Game::playerMoveCreatureByID" + 600, + [this, player, movingCreature, toTile, movingCreatureOrigPos] { + playerMoveCreatureByID(player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()); + }, + __FUNCTION__ ); player->setNextActionPushTask(task); @@ -1398,9 +1402,13 @@ void Game::playerMoveCreature(std::shared_ptr player, std::shared_ptr listDir; if (player->getPathTo(movingCreatureOrigPos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); const auto &task = createPlayerTask( - 600, [this, player, movingCreature, toTile, movingCreatureOrigPos] { playerMoveCreatureByID(player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()); }, "Game::playerMoveCreatureByID" + 600, + [this, player, movingCreature, toTile, movingCreatureOrigPos] { + playerMoveCreatureByID(player->getID(), movingCreature->getID(), movingCreatureOrigPos, toTile->getPosition()); + }, + __FUNCTION__ ); player->pushEvent(true); player->setNextActionPushTask(task); @@ -1601,11 +1609,12 @@ void Game::playerMoveItemByPlayerID(uint32_t playerId, const Position &fromPos, void Game::playerMoveItem(std::shared_ptr player, const Position &fromPos, uint16_t itemId, uint8_t fromStackPos, const Position &toPos, uint8_t count, std::shared_ptr item, std::shared_ptr toCylinder) { if (!player->canDoAction()) { uint32_t delay = player->getNextActionTime(); - std::shared_ptr task = createPlayerTask( - delay, [this, playerId = player->getID(), fromPos, itemId, fromStackPos, toPos, count] { + const auto &task = createPlayerTask( + delay, + [this, playerId = player->getID(), fromPos, itemId, fromStackPos, toPos, count] { playerMoveItemByPlayerID(playerId, fromPos, itemId, fromStackPos, toPos, count); }, - "Game::playerMoveItemByPlayerID" + __FUNCTION__ ); player->setNextActionTask(task); return; @@ -1691,13 +1700,13 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo // need to walk to the item first before using it std::vector listDir; if (player->getPathTo(item->getPosition(), listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId = player->getID(), fromPos, itemId, fromStackPos, toPos, count] { + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId = player->getID(), fromPos, itemId, fromStackPos, toPos, count] { playerMoveItemByPlayerID(playerId, fromPos, itemId, fromStackPos, toPos, count); }, - "Game::playerMoveItemByPlayerID" + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -1753,13 +1762,13 @@ void Game::playerMoveItem(std::shared_ptr player, const Position &fromPo std::vector listDir; if (player->getPathTo(walkPos, listDir, 0, 0, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId = player->getID(), itemPos, itemId, itemStackPos, toPos, count] { + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId = player->getID(), itemPos, itemId, itemStackPos, toPos, count] { playerMoveItemByPlayerID(playerId, itemPos, itemId, itemStackPos, toPos, count); }, - "Game::playerMoveItemByPlayerID" + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -3678,10 +3687,13 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f std::vector listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, itemPos, itemStackPos, fromItemId, toPos, toStackPos, toItemId] { playerUseItemEx(playerId, itemPos, itemStackPos, fromItemId, toPos, toStackPos, toItemId); }, "Game::playerUseItemEx" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, itemPos, itemStackPos, fromItemId, toPos, toStackPos, toItemId] { + playerUseItemEx(playerId, itemPos, itemStackPos, fromItemId, toPos, toStackPos, toItemId); + }, + __FUNCTION__ ); if (it.isRune() || it.type == ITEM_TYPE_POTION) { player->setNextPotionActionTask(task); @@ -3708,8 +3720,12 @@ void Game::playerUseItemEx(uint32_t playerId, const Position &fromPos, uint8_t f if (it.isRune() || it.type == ITEM_TYPE_POTION) { delay = player->getNextPotionActionTime(); } - std::shared_ptr task = createPlayerTask( - delay, [this, playerId, fromPos, fromStackPos, fromItemId, toPos, toStackPos, toItemId] { playerUseItemEx(playerId, fromPos, fromStackPos, fromItemId, toPos, toStackPos, toItemId); }, "Game::playerUseItemEx" + const auto &task = createPlayerTask( + delay, + [this, playerId, fromPos, fromStackPos, fromItemId, toPos, toStackPos, toItemId] { + playerUseItemEx(playerId, fromPos, fromStackPos, fromItemId, toPos, toStackPos, toItemId); + }, + __FUNCTION__ ); if (it.isRune() || it.type == ITEM_TYPE_POTION) { player->setNextPotionActionTask(task); @@ -3792,10 +3808,13 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo if (ret == RETURNVALUE_TOOFARAWAY) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos, stackPos, index, itemId] { playerUseItem(playerId, pos, stackPos, index, itemId); }, "Game::playerUseItem" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos, stackPos, index, itemId] { + playerUseItem(playerId, pos, stackPos, index, itemId); + }, + __FUNCTION__ ); if (it.isRune() || it.type == ITEM_TYPE_POTION) { player->setNextPotionActionTask(task); @@ -3822,8 +3841,12 @@ void Game::playerUseItem(uint32_t playerId, const Position &pos, uint8_t stackPo if (it.isRune() || it.type == ITEM_TYPE_POTION) { delay = player->getNextPotionActionTime(); } - std::shared_ptr task = createPlayerTask( - delay, [this, playerId, pos, stackPos, index, itemId] { playerUseItem(playerId, pos, stackPos, index, itemId); }, "Game::playerUseItem" + const auto &task = createPlayerTask( + delay, + [this, playerId, pos, stackPos, index, itemId] { + playerUseItem(playerId, pos, stackPos, index, itemId); + }, + __FUNCTION__ ); if (it.isRune() || it.type == ITEM_TYPE_POTION) { player->setNextPotionActionTask(task); @@ -3949,13 +3972,13 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin std::vector listDir; if (player->getPathTo(walkToPos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, itemPos, itemStackPos, creatureId, itemId] { + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, itemPos, itemStackPos, creatureId, itemId] { playerUseWithCreature(playerId, itemPos, itemStackPos, creatureId, itemId); }, - "Game::playerUseWithCreature" + __FUNCTION__ ); if (it.isRune() || it.type == ITEM_TYPE_POTION) { player->setNextPotionActionTask(task); @@ -3982,10 +4005,13 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position &fromPos, uin if (it.isRune() || it.type == ITEM_TYPE_POTION) { delay = player->getNextPotionActionTime(); } - std::shared_ptr task = createPlayerTask( - delay, [this, playerId, fromPos, fromStackPos, creatureId, itemId] { playerUseWithCreature(playerId, fromPos, fromStackPos, creatureId, itemId); }, "Game::playerUseWithCreature" + const auto &task = createPlayerTask( + delay, + [this, playerId, fromPos, fromStackPos, creatureId, itemId] { + playerUseWithCreature(playerId, fromPos, fromStackPos, creatureId, itemId); + }, + __FUNCTION__ ); - if (it.isRune() || it.type == ITEM_TYPE_POTION) { player->setNextPotionActionTask(task); } else { @@ -4110,13 +4136,13 @@ void Game::playerRotateItem(uint32_t playerId, const Position &pos, uint8_t stac if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos, stackPos, itemId] { + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos, stackPos, itemId] { playerRotateItem(playerId, pos, stackPos, itemId); }, - "Game::playerRotateItem" + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -4166,18 +4192,26 @@ void Game::playerConfigureShowOffSocket(uint32_t playerId, const Position &pos, if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, false)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - std::shared_ptr task; + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); if (isPodiumOfRenown) { - task = createPlayerTask( - 400, [player, item, pos, itemId, stackPos] { player->sendPodiumWindow(item, pos, itemId, stackPos); }, "Game::playerConfigureShowOffSocket" + const auto &task = createPlayerTask( + 400, + [player, item, pos, itemId, stackPos] { + player->sendPodiumWindow(item, pos, itemId, stackPos); + }, + __FUNCTION__ ); + player->setNextWalkActionTask(task); } else { - task = createPlayerTask( - 400, [player, item, pos, itemId, stackPos] { player->sendMonsterPodiumWindow(item, pos, itemId, stackPos); }, "Game::playerConfigureShowOffSocket" + const auto &task = createPlayerTask( + 400, + [player, item, pos, itemId, stackPos] { + player->sendMonsterPodiumWindow(item, pos, itemId, stackPos); + }, + __FUNCTION__ ); + player->setNextWalkActionTask(task); } - player->setNextWalkActionTask(task); } else { player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); } @@ -4227,9 +4261,13 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, false)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos] { playerBrowseField(playerId, pos); }, "Game::playerBrowseField" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos] { + playerBrowseField(playerId, pos); + }, + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -4368,10 +4406,13 @@ void Game::playerWrapableItem(uint32_t playerId, const Position &pos, uint8_t st if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos, stackPos, itemId] { playerWrapableItem(playerId, pos, stackPos, itemId); }, "Game::playerWrapableItem" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos, stackPos, itemId] { + playerWrapableItem(playerId, pos, stackPos, itemId); + }, + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -4549,9 +4590,13 @@ void Game::playerBrowseField(uint32_t playerId, const Position &pos) { if (!Position::areInRange<1, 1>(playerPos, pos)) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos] { playerBrowseField(playerId, pos); }, "Game::playerBrowseField" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos] { + playerBrowseField(playerId, pos); + }, + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -4812,10 +4857,13 @@ void Game::playerRequestTrade(uint32_t playerId, const Position &pos, uint8_t st if (!Position::areInRange<1, 1>(tradeItemPosition, playerPosition)) { std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos, stackPos, tradePlayerId, itemId] { playerRequestTrade(playerId, pos, stackPos, tradePlayerId, itemId); }, "Game::playerRequestTrade" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos, stackPos, tradePlayerId, itemId] { + playerRequestTrade(playerId, pos, stackPos, tradePlayerId, itemId); + }, + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -5375,12 +5423,13 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item } if (!autoLoot && !player->canDoAction()) { - uint32_t delay = player->getNextActionTime(); - std::shared_ptr task = createPlayerTask( - delay, [this, playerId = player->getID(), pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot] { + const uint32_t delay = player->getNextActionTime(); + const auto &task = createPlayerTask( + delay, + [this, playerId = player->getID(), pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot] { playerQuickLoot(playerId, pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot); }, - "Game::playerQuickLoot" + __FUNCTION__ ); player->setNextActionTask(task); return; @@ -5391,12 +5440,13 @@ void Game::playerQuickLoot(uint32_t playerId, const Position &pos, uint16_t item // need to walk to the corpse first before looting it std::vector listDir; if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - std::shared_ptr task = createPlayerTask( - 0, [this, playerId = player->getID(), pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot] { + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 300, + [this, playerId = player->getID(), pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot] { playerQuickLoot(playerId, pos, itemId, stackPos, defaultItem, lootAllCorpses, autoLoot); }, - "Game::playerQuickLoot" + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -5765,7 +5815,7 @@ void Game::playerSetAttackedCreature(uint32_t playerId, uint32_t creatureId) { } player->setAttackedCreature(attackCreature); - g_dispatcher().addEvent([this, plyerId = player->getID()] { updateCreatureWalk(plyerId); }, "Game::updateCreatureWalk"); + g_dispatcher().addEvent([this, plyerId = player->getID()] { updateCreatureWalk(plyerId); }, __FUNCTION__); } void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { @@ -5775,7 +5825,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) { } player->setAttackedCreature(nullptr); - g_dispatcher().addEvent([this, plyerId = player->getID()] { updateCreatureWalk(plyerId); }, "Game::updateCreatureWalk"); + g_dispatcher().addEvent([this, plyerId = player->getID()] { updateCreatureWalk(plyerId); }, __FUNCTION__); player->setFollowCreature(getCreatureByID(creatureId)); } @@ -9483,9 +9533,13 @@ void Game::playerSetMonsterPodium(uint32_t playerId, uint32_t monsterRaceId, con if (!Position::areInRange<1, 1, 0>(pos, player->getPosition())) { if (std::vector listDir; player->getPathTo(pos, listDir, 0, 1, true, false)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos] { playerBrowseField(playerId, pos); }, "Game::playerBrowseField" + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos] { + playerBrowseField(playerId, pos); + }, + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -9583,12 +9637,13 @@ void Game::playerRotatePodium(uint32_t playerId, const Position &pos, uint8_t st if (pos.x != 0xFFFF && !Position::areInRange<1, 1, 0>(pos, player->getPosition())) { if (std::vector listDir; player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, "Game::playerAutoWalk"); - std::shared_ptr task = createPlayerTask( - 400, [this, playerId, pos, stackPos, itemId] { + g_dispatcher().addEvent([this, playerId = player->getID(), listDir] { playerAutoWalk(playerId, listDir); }, __FUNCTION__); + const auto &task = createPlayerTask( + 400, + [this, playerId, pos, stackPos, itemId] { playerRotatePodium(playerId, pos, stackPos, itemId); }, - "Game::playerRotatePodium" + __FUNCTION__ ); player->setNextWalkActionTask(task); } else { @@ -10107,7 +10162,7 @@ uint32_t Game::makeFiendishMonster(uint32_t forgeableMonsterId /* = 0*/, bool cr auto schedulerTask = createPlayerTask( finalTime, [this, monster] { updateFiendishMonsterStatus(monster->getID(), monster->getName()); }, - "Game::updateFiendishMonsterStatus" + __FUNCTION__ ); forgeMonsterEventIds[monster->getID()] = g_dispatcher().scheduleEvent(schedulerTask); return monster->getID(); diff --git a/src/game/scheduling/dispatcher.cpp b/src/game/scheduling/dispatcher.cpp index dbbfc020be5..eaedaa86ac5 100644 --- a/src/game/scheduling/dispatcher.cpp +++ b/src/game/scheduling/dispatcher.cpp @@ -236,17 +236,17 @@ void Dispatcher::stopEvent(uint64_t eventId) { } } -void DispatcherContext::addEvent(std::function &&f) const { - g_dispatcher().addEvent(std::move(f), taskName); +void DispatcherContext::addEvent(std::function &&f, std::string_view context) const { + g_dispatcher().addEvent(std::move(f), context); } -void DispatcherContext::tryAddEvent(std::function &&f) const { +void DispatcherContext::tryAddEvent(std::function &&f, std::string_view context) const { if (!f) { return; } if (isAsync()) { - g_dispatcher().addEvent(std::move(f), taskName); + g_dispatcher().addEvent(std::move(f), context); } else { f(); } diff --git a/src/game/scheduling/dispatcher.hpp b/src/game/scheduling/dispatcher.hpp index 94b284c9316..b50346e4895 100644 --- a/src/game/scheduling/dispatcher.hpp +++ b/src/game/scheduling/dispatcher.hpp @@ -56,10 +56,10 @@ struct DispatcherContext { } // postpone the event - void addEvent(std::function &&f) const; + void addEvent(std::function &&f, std::string_view context) const; // if the context is async, the event will be postponed, if not, it will be executed immediately. - void tryAddEvent(std::function &&f) const; + void tryAddEvent(std::function &&f, std::string_view context) const; private: void reset() { @@ -70,7 +70,7 @@ struct DispatcherContext { DispatcherType type = DispatcherType::None; TaskGroup group = TaskGroup::ThreadPool; - std::string_view taskName = ""; + std::string_view taskName; friend class Dispatcher; }; diff --git a/src/lua/functions/core/game/game_functions.cpp b/src/lua/functions/core/game/game_functions.cpp index 10a6379781a..83544ded535 100644 --- a/src/lua/functions/core/game/game_functions.cpp +++ b/src/lua/functions/core/game/game_functions.cpp @@ -180,7 +180,7 @@ int GameFunctions::luaGameGetPlayers(lua_State* L) { int GameFunctions::luaGameLoadMap(lua_State* L) { // Game.loadMap(path) const std::string &path = getString(L, 1); - g_dispatcher().addEvent([path]() { g_game().loadMap(path); }, "GameFunctions::luaGameLoadMap"); + g_dispatcher().addEvent([path]() { g_game().loadMap(path); }, __FUNCTION__); return 0; } @@ -188,7 +188,7 @@ int GameFunctions::luaGameloadMapChunk(lua_State* L) { // Game.loadMapChunk(path, position, remove) const std::string &path = getString(L, 1); const Position &position = getPosition(L, 2); - g_dispatcher().addEvent([path, position]() { g_game().loadMap(path, position); }, "GameFunctions::luaGameloadMapChunk"); + g_dispatcher().addEvent([path, position]() { g_game().loadMap(path, position); }, __FUNCTION__); return 0; } diff --git a/src/map/mapcache.cpp b/src/map/mapcache.cpp index f4cdb524ec9..ad3c909a8bf 100644 --- a/src/map/mapcache.cpp +++ b/src/map/mapcache.cpp @@ -152,11 +152,14 @@ std::shared_ptr MapCache::getOrCreateTileFromCache(const std::unique_ptrsetFlag(static_cast(cachedTile->flags)); // add zone synchronously - g_dispatcher().context().tryAddEvent([tile, pos] { - for (const auto &zone : Zone::getZones(pos)) { - tile->addZone(zone); - } - }); + g_dispatcher().context().tryAddEvent( + [tile, pos] { + for (const auto &zone : Zone::getZones(pos)) { + tile->addZone(zone); + } + }, + "Zone::getZones" + ); floor->setTile(x, y, tile); diff --git a/src/server/network/connection/connection.cpp b/src/server/network/connection/connection.cpp index 3072384420d..a3782d56cba 100644 --- a/src/server/network/connection/connection.cpp +++ b/src/server/network/connection/connection.cpp @@ -62,7 +62,7 @@ void Connection::close(bool force) { connectionState = CONNECTION_STATE_CLOSED; if (protocol) { - g_dispatcher().addEvent([protocol = protocol] { protocol->release(); }, "Protocol::release", std::chrono::milliseconds(CONNECTION_WRITE_TIMEOUT * 1000).count()); + g_dispatcher().addEvent([protocol = protocol] { protocol->release(); }, __FUNCTION__, std::chrono::milliseconds(CONNECTION_WRITE_TIMEOUT * 1000).count()); } if (messageQueue.empty() || force) { @@ -98,7 +98,7 @@ void Connection::closeSocket() { void Connection::accept(Protocol_ptr protocolPtr) { connectionState = CONNECTION_STATE_IDENTIFYING; protocol = std::move(protocolPtr); - g_dispatcher().addEvent([protocol = protocol] { protocol->onConnect(); }, "Protocol::onConnect", std::chrono::milliseconds(CONNECTION_WRITE_TIMEOUT * 1000).count()); + g_dispatcher().addEvent([protocol = protocol] { protocol->onConnect(); }, __FUNCTION__, std::chrono::milliseconds(CONNECTION_WRITE_TIMEOUT * 1000).count()); acceptInternal(false); } diff --git a/src/server/network/protocol/protocol.cpp b/src/server/network/protocol/protocol.cpp index 255899f2f99..c40ead17ab0 100644 --- a/src/server/network/protocol/protocol.cpp +++ b/src/server/network/protocol/protocol.cpp @@ -44,13 +44,17 @@ bool Protocol::sendRecvMessageCallback(NetworkMessage &msg) { return false; } - g_dispatcher().addEvent([&msg, protocolWeak = std::weak_ptr(shared_from_this())]() { - if (auto protocol = protocolWeak.lock()) { - if (auto protocolConnection = protocol->getConnection()) { - protocol->parsePacket(msg); - protocolConnection->resumeWork(); + g_dispatcher().addEvent( + [&msg, protocolWeak = std::weak_ptr(shared_from_this())]() { + if (auto protocol = protocolWeak.lock()) { + if (auto protocolConnection = protocol->getConnection()) { + protocol->parsePacket(msg); + protocolConnection->resumeWork(); + } } - } }, "Protocol::sendRecvMessageCallback"); + }, + __FUNCTION__ + ); return true; } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index aab3f88bd47..909eef1e92e 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -859,7 +859,7 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage &msg) { return; } - g_dispatcher().addEvent([self = getThis(), characterName, accountId, operatingSystem] { self->login(characterName, accountId, operatingSystem); }, "ProtocolGame::login"); + g_dispatcher().addEvent([self = getThis(), characterName, accountId, operatingSystem] { self->login(characterName, accountId, operatingSystem); }, __FUNCTION__); } void ProtocolGame::onConnect() { @@ -5040,18 +5040,20 @@ void ProtocolGame::updateCoinBalance() { return; } - g_dispatcher().addEvent([playerId = player->getID()] { - const auto &threadPlayer = g_game().getPlayerByID(playerId); - if (threadPlayer && threadPlayer->getAccount()) { - const auto [coins, errCoin] = threadPlayer->getAccount()->getCoins(enumToValue(CoinType::Normal)); - const auto [transferCoins, errTCoin] = threadPlayer->getAccount()->getCoins(enumToValue(CoinType::Transferable)); + g_dispatcher().addEvent( + [playerId = player->getID()] { + const auto &threadPlayer = g_game().getPlayerByID(playerId); + if (threadPlayer && threadPlayer->getAccount()) { + const auto [coins, errCoin] = threadPlayer->getAccount()->getCoins(enumToValue(CoinType::Normal)); + const auto [transferCoins, errTCoin] = threadPlayer->getAccount()->getCoins(enumToValue(CoinType::Transferable)); - threadPlayer->coinBalance = coins; - threadPlayer->coinTransferableBalance = transferCoins; - threadPlayer->sendCoinBalance(); - } - }, - "ProtocolGame::updateCoinBalance"); + threadPlayer->coinBalance = coins; + threadPlayer->coinTransferableBalance = transferCoins; + threadPlayer->sendCoinBalance(); + } + }, + __FUNCTION__ + ); } void ProtocolGame::sendMarketLeave() { diff --git a/src/server/network/protocol/protocollogin.cpp b/src/server/network/protocol/protocollogin.cpp index d6e9e3cb9f1..f8c68634f64 100644 --- a/src/server/network/protocol/protocollogin.cpp +++ b/src/server/network/protocol/protocollogin.cpp @@ -174,8 +174,10 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage &msg) { return; } - g_dispatcher().addEvent([self = std::static_pointer_cast(shared_from_this()), accountDescriptor, password] { - self->getCharacterList(accountDescriptor, password); - }, - "ProtocolLogin::getCharacterList"); + g_dispatcher().addEvent( + [self = std::static_pointer_cast(shared_from_this()), accountDescriptor, password] { + self->getCharacterList(accountDescriptor, password); + }, + __FUNCTION__ + ); } diff --git a/src/server/network/protocol/protocolstatus.cpp b/src/server/network/protocol/protocolstatus.cpp index 15a3f74be07..f9ec878eda9 100644 --- a/src/server/network/protocol/protocolstatus.cpp +++ b/src/server/network/protocol/protocolstatus.cpp @@ -44,10 +44,12 @@ void ProtocolStatus::onRecvFirstMessage(NetworkMessage &msg) { // XML info protocol case 0xFF: { if (msg.getString(4) == "info") { - g_dispatcher().addEvent([self = std::static_pointer_cast(shared_from_this())] { - self->sendStatusString(); - }, - "ProtocolStatus::sendStatusString"); + g_dispatcher().addEvent( + [self = std::static_pointer_cast(shared_from_this())] { + self->sendStatusString(); + }, + __FUNCTION__ + ); return; } break; @@ -60,10 +62,12 @@ void ProtocolStatus::onRecvFirstMessage(NetworkMessage &msg) { if (requestedInfo & REQUEST_PLAYER_STATUS_INFO) { characterName = msg.getString(); } - g_dispatcher().addEvent([self = std::static_pointer_cast(shared_from_this()), requestedInfo, characterName] { - self->sendInfo(requestedInfo, characterName); - }, - "ProtocolStatus::sendInfo"); + g_dispatcher().addEvent( + [self = std::static_pointer_cast(shared_from_this()), requestedInfo, characterName] { + self->sendInfo(requestedInfo, characterName); + }, + __FUNCTION__ + ); return; } diff --git a/src/server/signals.cpp b/src/server/signals.cpp index 626a918f677..ae71d833c93 100644 --- a/src/server/signals.cpp +++ b/src/server/signals.cpp @@ -54,21 +54,21 @@ void Signals::asyncWait() { void Signals::dispatchSignalHandler(int signal) { switch (signal) { case SIGINT: // Shuts the server down - g_dispatcher().addEvent(sigintHandler, "sigintHandler"); + g_dispatcher().addEvent(sigintHandler, __FUNCTION__); break; case SIGTERM: // Shuts the server down - g_dispatcher().addEvent(sigtermHandler, "sigtermHandler"); + g_dispatcher().addEvent(sigtermHandler, __FUNCTION__); break; #ifndef _WIN32 case SIGHUP: // Reload config/data - g_dispatcher().addEvent(sighupHandler, "sighupHandler"); + g_dispatcher().addEvent(sighupHandler, __FUNCTION__); break; case SIGUSR1: // Saves game state - g_dispatcher().addEvent(sigusr1Handler, "sigusr1Handler"); + g_dispatcher().addEvent(sigusr1Handler, __FUNCTION__); break; #else case SIGBREAK: // Shuts the server down - g_dispatcher().addEvent(sigbreakHandler, "sigbreakHandler"); + g_dispatcher().addEvent(sigbreakHandler, __FUNCTION__); // hold the thread until other threads end inject().shutdown(); break; From 15180dcf78b00674ba1b6f3e9df57a7c51b0caed Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 24 Sep 2024 17:51:36 -0300 Subject: [PATCH 25/27] fix: disable irrelevant informations (#2910) The "PlayerBadge" was loading a lot of unnecessary information for all players on the account. I moved the if statement up, ensuring that only the preload and level are properly loaded, which are actually relevant information. If more information is identified that should be loaded in the preload, we should move it to the loadPlayerFirst function. --- src/game/game.cpp | 2 +- src/io/iologindata.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index a44b75d285c..3a579ec2fdf 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -988,7 +988,7 @@ std::shared_ptr Game::getPlayerByName(const std::string &s, bool allowOf return nullptr; } std::shared_ptr tmpPlayer = std::make_shared(nullptr); - if (!IOLoginData::loadPlayerByName(tmpPlayer, s)) { + if (!IOLoginData::loadPlayerByName(tmpPlayer, s, allowOffline)) { if (!isNewName) { g_logger().error("Failed to load player {} from database", s); } else { diff --git a/src/io/iologindata.cpp b/src/io/iologindata.cpp index fce5d0dc293..41f8cea8985 100644 --- a/src/io/iologindata.cpp +++ b/src/io/iologindata.cpp @@ -123,6 +123,10 @@ bool IOLoginData::loadPlayer(std::shared_ptr player, DBResult_ptr result // Experience load IOLoginDataLoad::loadPlayerExperience(player, result); + if (disableIrrelevantInfo) { + return true; + } + // Blessings load IOLoginDataLoad::loadPlayerBlessings(player, result); @@ -180,10 +184,6 @@ bool IOLoginData::loadPlayer(std::shared_ptr player, DBResult_ptr result // Load instant spells list IOLoginDataLoad::loadPlayerInstantSpellList(player, result); - if (disableIrrelevantInfo) { - return true; - } - // load forge history IOLoginDataLoad::loadPlayerForgeHistory(player, result); From 4bc7a5c9c095b01a14be3d1edff088cbf98ef741 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 24 Sep 2024 18:03:05 -0300 Subject: [PATCH 26/27] fix: CPU overload when loading many nested containers (#2909) This addresses an issue where excessive CPU load occurs when dealing with deeply nested containers due to repeated calls to the `isRemoved` function in the `canDecay` method. To mitigate this, the order of checks in `canDecay` has been changed to evaluate the decay conditions first, and only check for removal status (`isRemoved`) as the last step. --- src/items/item.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/items/item.cpp b/src/items/item.cpp index dd2d5c965a6..2ad0cfa4abd 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -3160,16 +3160,17 @@ void Item::addUniqueId(uint16_t uniqueId) { } bool Item::canDecay() { - if (isRemoved() || isDecayDisabled()) { + const ItemType &it = Item::items[id]; + if (it.decayTo < 0 || it.decayTime == 0 || isDecayDisabled()) { return false; } - const ItemType &it = Item::items[id]; - if (it.decayTo < 0 || it.decayTime == 0) { + if (hasAttribute(ItemAttribute_t::UNIQUEID)) { return false; } - if (hasAttribute(ItemAttribute_t::UNIQUEID)) { + // In certain conditions, such as depth nested containers, this can overload the CPU, so it is left last. + if (isRemoved()) { return false; } From 11bd3a689c69d163491dc8667cc2ec29b49fd293 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Tue, 24 Sep 2024 21:37:48 -0300 Subject: [PATCH 27/27] fix: forge correct slots (#2850) Resolves #2718 --- src/server/network/protocol/protocolgame.cpp | 40 ++++++++++++++------ 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index 909eef1e92e..7ae02e31b10 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -5370,7 +5370,8 @@ void ProtocolGame::sendForgingData() { void ProtocolGame::sendOpenForge() { // We will use it when sending the bytes to send the item information to the client std::map> fusionItemsMap; - std::map>> convergenceItemsMap; + std::map>> convergenceFusionItemsMap; + std::map>> convergenceTransferItemsMap; std::map> donorTierItemMap; std::map> receiveTierItemMap; @@ -5405,7 +5406,12 @@ void ProtocolGame::sendOpenForge() { getForgeInfoMap(item, receiveTierItemMap); } if (itemClassification == 4) { - getForgeInfoMap(item, convergenceItemsMap[item->getClassification()]); + auto slotPosition = item->getSlotPosition(); + if ((slotPosition & SLOTP_TWO_HAND) != 0) { + slotPosition = SLOTP_HAND; + } + getForgeInfoMap(item, convergenceFusionItemsMap[slotPosition]); + getForgeInfoMap(item, convergenceTransferItemsMap[item->getClassification()]); } } } @@ -5413,7 +5419,7 @@ void ProtocolGame::sendOpenForge() { // Checking size of map to send in the addByte (total fusion items count) uint8_t fusionTotalItemsCount = 0; for (const auto &[itemId, tierAndCountMap] : fusionItemsMap) { - for (const auto [itemTier, itemCount] : tierAndCountMap) { + for (const auto &[itemTier, itemCount] : tierAndCountMap) { if (itemCount >= 2) { fusionTotalItemsCount++; } @@ -5430,7 +5436,7 @@ void ProtocolGame::sendOpenForge() { msg.add(fusionTotalItemsCount); for (const auto &[itemId, tierAndCountMap] : fusionItemsMap) { - for (const auto [itemTier, itemCount] : tierAndCountMap) { + for (const auto &[itemTier, itemCount] : tierAndCountMap) { if (itemCount >= 2) { msg.addByte(0x01); // Number of friend items? msg.add(itemId); @@ -5452,12 +5458,12 @@ void ProtocolGame::sendOpenForge() { 1 byte: tier 2 bytes: count */ - for (const auto &[slot, itemMap] : convergenceItemsMap) { + for (const auto &[slot, itemMap] : convergenceFusionItemsMap) { uint8_t totalItemsCount = 0; auto totalItemsCountPosition = msg.getBufferPosition(); msg.skipBytes(1); // Total items count for (const auto &[itemId, tierAndCountMap] : itemMap) { - for (const auto [tier, itemCount] : tierAndCountMap) { + for (const auto &[tier, itemCount] : tierAndCountMap) { if (tier >= maxConfigTier) { continue; } @@ -5488,11 +5494,15 @@ void ProtocolGame::sendOpenForge() { // Let's access the itemType to check the item's (donator of tier) classification level // Must be the same as the item that will receive the tier const ItemType &donorType = Item::items[itemId]; + auto donorSlotPosition = donorType.slotPosition; + if ((donorSlotPosition & SLOTP_TWO_HAND) != 0) { + donorSlotPosition = SLOTP_HAND; + } // Total count of item (donator of tier) auto donorTierTotalItemsCount = getIterationIncreaseCount(tierAndCountMap); msg.add(donorTierTotalItemsCount); - for (const auto [donorItemTier, donorItemCount] : tierAndCountMap) { + for (const auto &[donorItemTier, donorItemCount] : tierAndCountMap) { msg.add(itemId); msg.addByte(donorItemTier); msg.add(donorItemCount); @@ -5502,7 +5512,11 @@ void ProtocolGame::sendOpenForge() { for (const auto &[iteratorItemId, unusedTierAndCountMap] : receiveTierItemMap) { // Let's access the itemType to check the item's (receiver of tier) classification level const ItemType &receiveType = Item::items[iteratorItemId]; - if (donorType.upgradeClassification == receiveType.upgradeClassification) { + auto receiveSlotPosition = receiveType.slotPosition; + if ((receiveSlotPosition & SLOTP_TWO_HAND) != 0) { + receiveSlotPosition = SLOTP_HAND; + } + if (donorType.upgradeClassification == receiveType.upgradeClassification && donorSlotPosition == receiveSlotPosition) { receiveTierTotalItemCount++; } } @@ -5513,8 +5527,12 @@ void ProtocolGame::sendOpenForge() { for (const auto &[receiveItemId, receiveTierAndCountMap] : receiveTierItemMap) { // Let's access the itemType to check the item's (receiver of tier) classification level const ItemType &receiveType = Item::items[receiveItemId]; - if (donorType.upgradeClassification == receiveType.upgradeClassification) { - for (const auto [receiveItemTier, receiveItemCount] : receiveTierAndCountMap) { + auto receiveSlotPosition = receiveType.slotPosition; + if ((receiveSlotPosition & SLOTP_TWO_HAND) != 0) { + receiveSlotPosition = SLOTP_HAND; + } + if (donorType.upgradeClassification == receiveType.upgradeClassification && donorSlotPosition == receiveSlotPosition) { + for (const auto &[receiveItemTier, receiveItemCount] : receiveTierAndCountMap) { msg.add(receiveItemId); msg.add(receiveItemCount); } @@ -5540,7 +5558,7 @@ void ProtocolGame::sendOpenForge() { 2 bytes: item id 2 bytes: count */ - for (const auto &[slot, itemMap] : convergenceItemsMap) { + for (const auto &[slot, itemMap] : convergenceTransferItemsMap) { uint16_t donorCount = 0; uint16_t receiverCount = 0; auto donorCountPosition = msg.getBufferPosition();