diff --git a/data-otxserver/lib/quests/quest.lua b/data-otxserver/lib/quests/quest.lua index 0558fe4e0..e32bcaf2e 100644 --- a/data-otxserver/lib/quests/quest.lua +++ b/data-otxserver/lib/quests/quest.lua @@ -1,2 +1,3 @@ dofile(DATA_DIRECTORY .. "/lib/quests/killing_in_the_name_of.lua") dofile(DATA_DIRECTORY .. "/lib/quests/the_primal_ordeal.lua") +dofile(DATA_DIRECTORY .. "/lib/quests/soul_war.lua") diff --git a/data-otxserver/lib/quests/soul_war.lua b/data-otxserver/lib/quests/soul_war.lua new file mode 100644 index 000000000..f5b2d75f5 --- /dev/null +++ b/data-otxserver/lib/quests/soul_war.lua @@ -0,0 +1,1571 @@ +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.debug("Player {} killed {} with {} taints, loot chance {}", player:getName(), monsterName, playerTaintLevel, itemChance) + + 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 + 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 diff --git a/data-otxserver/scripts/quests/soul_war/action-reward_soul_war.lua b/data-otxserver/scripts/quests/soul_war/action-reward_soul_war.lua new file mode 100644 index 000000000..fae3fc597 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/eventcallback_on_combat_taint.lua b/data-otxserver/scripts/quests/soul_war/eventcallback_on_combat_taint.lua new file mode 100644 index 000000000..3e5ec34f9 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua b/data-otxserver/scripts/quests/soul_war/globalevent-ebb_and_flow_change_maps.lua new file mode 100644 index 000000000..91273ac4c --- /dev/null +++ b/data-otxserver/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", true) + 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", true) + 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-otxserver/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua b/data-otxserver/scripts/quests/soul_war/moveevent-claustrophobic-inferno-raid.lua new file mode 100644 index 000000000..0502d53e3 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/moveevent-soul_war_entrances.lua b/data-otxserver/scripts/quests/soul_war/moveevent-soul_war_entrances.lua new file mode 100644 index 000000000..0d2bd4fff --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua b/data-otxserver/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua new file mode 100644 index 000000000..7aaff1120 --- /dev/null +++ b/data-otxserver/scripts/quests/soul_war/moveevent-teleport_entrance_reward.lua @@ -0,0 +1,23 @@ +local portalReward = MoveEvent() + +function portalReward.onStepIn(creature, item, position, fromPosition) + local player = creature:getPlayer() + if not player then + return false + end + + 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 + end + + player:teleportTo(Position(33621, 31411, 10)) + player:getPosition():sendMagicEffect(CONST_ME_TELEPORT) + return true +end + +portalReward:position({ x = 33621, y = 31416, z = 10 }) +portalReward:register() diff --git a/data-otxserver/scripts/quests/soul_war/soul_war_mechanics.lua b/data-otxserver/scripts/quests/soul_war/soul_war_mechanics.lua new file mode 100644 index 000000000..6a6019527 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/spell-eye_beam.lua b/data-otxserver/scripts/quests/soul_war/spell-eye_beam.lua new file mode 100644 index 000000000..7a7397993 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/spell-fire_beam_cruelty.lua b/data-otxserver/scripts/quests/soul_war/spell-fire_beam_cruelty.lua new file mode 100644 index 000000000..2bc321f24 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/spell-fire_beam_megalomania.lua b/data-otxserver/scripts/quests/soul_war/spell-fire_beam_megalomania.lua new file mode 100644 index 000000000..2906f213d --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/spell-megalomania_blue.lua b/data-otxserver/scripts/quests/soul_war/spell-megalomania_blue.lua new file mode 100644 index 000000000..d8a9c8e98 --- /dev/null +++ b/data-otxserver/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-otxserver/scripts/quests/soul_war/spell-soulsnatcher.lua b/data-otxserver/scripts/quests/soul_war/spell-soulsnatcher.lua new file mode 100644 index 000000000..9292f1053 --- /dev/null +++ b/data-otxserver/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()